Created
          June 12, 2012 22:37 
        
      - 
      
 - 
        
Save ozten/2920556 to your computer and use it in GitHub Desktop.  
    bigten diff
  
        
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
  | diff --git a/ChangeLog b/ChangeLog | |
| index e30219c..74cc136 100644 | |
| --- a/ChangeLog | |
| +++ b/ChangeLog | |
| @@ -1,22 +1,7 @@ | |
| -train-2012.06.22: (in progress) | |
| - * | |
| - | |
| -train-2012.06.08: | |
| - * rebrand from 'browserid' to 'persona': (including regressions #1711 #1706 #1716 #1719) | |
| - * new "router" process added - subsumes responsibility of old "webhead" process, handles all requets forwarding: #1657 | |
| - * Support non-english passwords: #1631 | |
| +train-2012.06.08 (in progress): | |
| + * Support non-english passwords: issue #1631 | |
| + * remove obsolete code - 'code_update' handler: issue #1645 | |
| * allow sessions to persist for 4 weeks after a user confirms ownership of a device (was effectively 1 week): #1632 | |
| - * general code cleanup, removal of obsolete code, accidental globals cleaned up: #1645 #1681 #1699 | |
| - * fix tab order on signup page: #1635 | |
| - * minified include.js again has link to unminified source: #1624 | |
| - * improve handling of uncaught exceptions (new statsd counter): #1558 | |
| - * fix cachify stuff to allow multiple background images per css dec: #1652 | |
| - * 50x error pages added to repo | |
| - * authenticated sessions are now 4 weeks: #1632, #1572 | |
| - * improvements to KPI system: #1614 #1619 #1660 #1698 | |
| - * UI improvements: #1684 | |
| - * new secret debug menu added to dialog | |
| - * "silent assertions" (via observer api) now check cert expiry and don't issue invalid assertions | |
| train-2012.05.25: | |
| * many KPI improvements: #1597, #1613 | |
| diff --git a/bin/browserid b/bin/browserid | |
| index 8806efb..3cb11b8 100755 | |
| --- a/bin/browserid | |
| +++ b/bin/browserid | |
| @@ -60,6 +60,10 @@ app.use(i18n.abide({ | |
| disable_locale_check: config.get('disable_locale_check') | |
| })); | |
| +// limit all content bodies to 10kb, at which point we'll forcefully | |
| +// close down the connection. | |
| +app.use(express.limit("10kb")); | |
| + | |
| var statsd_config = config.get('statsd'); | |
| if (statsd_config && statsd_config.enabled) { | |
| logger_statsd = require("connect-logger-statsd"); | |
| diff --git a/config/local.json b/config/local.json | |
| index 6d346f5..13524e8 100644 | |
| --- a/config/local.json | |
| +++ b/config/local.json | |
| @@ -11,6 +11,7 @@ | |
| }, | |
| "express_log_format": "dev_bid", | |
| "email_to_console": true, | |
| - "env": "local", | |
| - "kpi_backend_sample_rate": 1.0 | |
| + "kpi_backend_sample_rate": 1.0, | |
| + "bigtent_domains": ["gmail.com", "yahoo.com", "hotmail.com"], | |
| + "env": "local" | |
| } | |
| diff --git a/docs/AWS_DEPLOYMENT.md b/docs/AWS_DEPLOYMENT.md | |
| index 5d31edb..f91c5d7 100644 | |
| --- a/docs/AWS_DEPLOYMENT.md | |
| +++ b/docs/AWS_DEPLOYMENT.md | |
| @@ -17,16 +17,7 @@ In order to use these deploy scripts, you need the following: | |
| 1. have built and locally run browserid | |
| 2. an ssh key in `~/.ssh/id_rsa.pub` | |
| 3. an AWS account that is "signed up" for EC2 | |
| - 4. (optionally) a secrets bundle that you get from lloyd (for DNS, SSL, and mail setup) | |
| - | |
| -For the secrets bundle, you'll need gpg to unpack it, and will do | |
| -the following: | |
| - | |
| - $ cd | |
| - $ curl -s http://people.mozilla.org/~lhilaiel/persona_goodies.tgz.gpg | gpg -d | tar xvzf - | |
| - | |
| -You'll be asked for the decryption password from GPG. Get that from | |
| -lloyd. | |
| + 4. the "DNS secret" that you get from lloyd | |
| Once you have these things, you'll need to relay them to deployment | |
| scripts via your environment. you might put something like this | |
| @@ -36,8 +27,8 @@ in your `.bashrc`: | |
| export AWS_ID=<your id> | |
| # This is your Secret Access Key from your AWS Security Credentials | |
| export AWS_SECRET=<your secret> | |
| - # install super magic secrets into your environment | |
| - . $HOME/.persona_secrets/env.sh | |
| + # This is a magic credential you get from lloyd | |
| + export BROWSERID_DEPLOY_DNS_KEY=98...33 | |
| ## Verify the credentials | |
| @@ -55,42 +46,38 @@ you can use a different name that is short but meaningful to what you're | |
| going to deploy. Once chosen, invoke `deploy.js` like this: | |
| $ scripts/deploy.js deploy some_name_i_chose | |
| - awsbox cmd: node_modules/.bin/awsbox create -n some_name_i_chose -p /Users/lth/.persona_secrets/cert.pem -s /Users/lth/.persona_secrets/key.pem -d -u https://some_name_i_chose.personatest.org -x /Users/lth/.persona_secrets/smtp.json | |
| - reading .awsbox.json | |
| - attempting to set up VM "some_name_i_chose" | |
| - ... Checking for DNS availability of some_name_i_chose.personatest.org | |
| + attempting to set up some_name_i_chose.hacksign.in | |
| ... VM launched, waiting for startup (should take about 20s) | |
| - ... Adding DNS Record for some_name_i_chose.personatest.org | |
| - ... Instance ready, setting human readable name in aws | |
| + ... Instance ready, setting up DNS | |
| + ... DNS set up, setting human readable name in aws | |
| ... name set, waiting for ssh access and configuring | |
| - ... adding additional configuration values | |
| - ... public url will be: https://some_name_i_chose.personatest.org | |
| + ... nope. not yet. retrying. | |
| + ... nope. not yet. retrying. | |
| + ... nope. not yet. retrying. | |
| ... nope. not yet. retrying. | |
| ... nope. not yet. retrying. | |
| ... victory! server is accessible and configured | |
| ... and your git remote is all set up | |
| - ... finally, installing custom packages: mysql-server | |
| - ... copying up SSL cert | |
| Yay! You have your very own deployment. Here's the basics: | |
| - 1. deploy your code: git push some_name_i_chose HEAD:master | |
| - 2. visit your server on the web: https://some_name_i_chose.personatest.org | |
| - 3. ssh in with sudo: ssh ec2-user@some_name_i_chose.personatest.org | |
| - 4. ssh as the deployment user: ssh app@some_name_i_chose.personatest.org | |
| + 1. deploy your code: git push some_name_i_chose <mybranch>:master | |
| + 2. visit your server on the web: https://some_name_i_chose.hacksign.in | |
| + 3. test via a website: http://some_name_i_chose.myfavoritebeer.org | |
| + 4. ssh in with sudo: ssh ec2-user@some_name_i_chose.hacksign.in | |
| + 5. ssh as the deployment user: ssh app@some_name_i_chose.hacksign.in | |
| - Here are your server's details: { | |
| - "instanceId": "i-f0b35e89", | |
| - "imageId": "ami-ac8524c5", | |
| + enjoy! Here's your server details { | |
| + "instanceId": "i-8f4beeea", | |
| + "imageId": "ami-6900d100", | |
| "instanceState": { | |
| "code": "16", | |
| "name": "running" | |
| }, | |
| - "dnsName": "ec2-23-21-24-182.compute-1.amazonaws.com", | |
| - "keyName": "awsbox deploy key (4736caec113ccb53aa62bb165c58c17d)", | |
| + "dnsName": "ec2-184-73-84-132.compute-1.amazonaws.com", | |
| + "keyName": "browserid deploy key (4736caec113ccb53aa62bb165c58c17d)", | |
| "instanceType": "t1.micro", | |
| - "ipAddress": "23.21.24.182", | |
| - "name": "i-f0b35e89" | |
| + "ipAddress": "184.73.84.132" | |
| } | |
| The output contains instructions for use. Note that every occurance of | |
| @@ -138,11 +125,8 @@ These things cost money by the hour, not a lot, but money. So when you want to | |
| decommission a VM and release your hold on the DNS name, simply: | |
| $ scripts/deploy.js destroy some_name_i_chose | |
| - awsbox cmd: node_modules/.bin/awsbox destroy some_name_i_chose | |
| - trying to destroy VM for some_name_i_chose: done | |
| - trying to remove git remote: done | |
| - trying to remove DNS: some_name_i_chose.personatest.org | |
| - deleting some_name_i_chose.personatest.org: done | |
| + trying to destroy VM for some_name_i_chose.hacksign.in: done | |
| + trying to remove DNS for some_name_i_chose.hacksign.in: done | |
| ## Overview of what's deployed to VMs | |
| @@ -155,20 +139,18 @@ There are several things that are pre-configured for your pleasure: | |
| on the server, that you can push to. | |
| 3. `post-update` hook: when you push to the `master` branch of the server's | |
| git repository, this code restarts your services to pick up the changes. | |
| - 4. SSL support and 503 support - you'll get SSL for free and will see | |
| + 4. nginx with SSL and 503 support - you'll get SSL for free and will see | |
| a reasonable error message when your servers aren't running. | |
| 5. a mysql database with a browserid user without any password. | |
| ### User accounts | |
| -VMs have three pre-configured users, all of which you have passphraseless SSH | |
| +VMs have two pre-configured users, both which you have passphraseless SSH | |
| access to: | |
| * `ec2-user` is an account with full sudo access. | |
| * `app` is an account that has no sudo, receives and builds code via git | |
| pushes, and runs the application servers. | |
| - * `proxy` is the account the the HTTP reverse proxy that front-ends your server | |
| - runs as. | |
| Feel free to start a new server, and ssh in as `app` to explore all of the | |
| configuration. An attempt has been made to isolate as much configuration | |
| diff --git a/example/rp/i/logo.png b/example/rp/i/logo.png | |
| deleted file mode 100644 | |
| index 4af65cb..0000000 | |
| Binary files a/example/rp/i/logo.png and /dev/null differ | |
| diff --git a/example/rp/index.html b/example/rp/index.html | |
| index fa60c2b..8487ef7 100644 | |
| --- a/example/rp/index.html | |
| +++ b/example/rp/index.html | |
| @@ -8,7 +8,7 @@ | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="initial-scale=1.0; maximum-scale=1.0; width=device-width;"> | |
| <title> | |
| -Persona Relying Party | |
| +BrowserID Relying Party | |
| </title> | |
| <style type="text/css"> | |
| @@ -48,31 +48,23 @@ pre { | |
| </head> | |
| <body> | |
| <div class="title"> | |
| - Persona Test Relying Party | |
| + BrowserID Test Relying Party | |
| </div> | |
| <div class="intro"> | |
| This is a RP for testing, it allows you to drive the <tt>navigator.id.get()</tt> call manually | |
| - to locally test Persona. | |
| + to locally test BrowserID. | |
| </div> | |
| <div class="specify"> | |
| <p><b>What flavor of assertion would you like?</b></p> | |
| <ul> | |
| <li> | |
| - <input type="checkbox" id="privacyPolicy"> | |
| - <label for="privacyPolicy">Supply a privacy policy</label> | |
| + <input type="checkbox" id="privacy"> | |
| + <label for="privacy">Supply a privacy policy</label> | |
| </li><li> | |
| - <input type="checkbox" id="termsOfService"> | |
| - <label for="termsOfService">Supply a ToS</label> | |
| - </li><li> | |
| - <input type="checkbox" id="siteName"> | |
| - <label for="siteName">Supply Site Name</label><br /> | |
| - </li> | |
| - </li><li> | |
| - <input type="checkbox" id="siteLogo"> | |
| - <label for="siteLogo">Supply Site Logo</label><br /> | |
| - </li> | |
| + <input type="checkbox" id="tos"> | |
| + <label for="tos">Supply a ToS</label> | |
| </li><li> | |
| <input type="text" id="requiredEmail" width="80"> | |
| <label for="requiredEmail">Require a specific email</label><br /> | |
| @@ -189,10 +181,8 @@ $(document).ready(function() { | |
| $(".specify button.assertion").attr('disabled', 'true'); | |
| navigator.id.request({ | |
| - privacyPolicy: $('#privacyPolicy').attr('checked') ? "/privacy.html" : undefined, | |
| - termsOfService: $('#termsOfService').attr('checked') ? "/TOS.html" : undefined, | |
| - siteName: $('#siteName').attr('checked') ? "Persona Test Relying Party" : undefined, | |
| - siteLogo: $('#siteLogo').attr('checked') ? "/i/logo.png" : undefined, | |
| + privacyURL: $('#privacy').attr('checked') ? "/privacy.html" : undefined, | |
| + tosURL: $('#tos').attr('checked') ? "/TOS.html" : undefined, | |
| requiredEmail: requiredEmail, | |
| oncancel: function() { | |
| loggit("oncancel"); | |
| diff --git a/lib/browserid/fake_verification.js b/lib/browserid/fake_verification.js | |
| index b9e3a21..03f33f1 100644 | |
| --- a/lib/browserid/fake_verification.js | |
| +++ b/lib/browserid/fake_verification.js | |
| @@ -11,7 +11,7 @@ | |
| const | |
| configuration = require('../configuration.js'), | |
| url = require('url'), | |
| -db = require('../db.js'), | |
| +db = require('../db.js'); | |
| logger = require('../logging.js').logger, | |
| wsapi = require('../wsapi'); | |
| @@ -22,7 +22,7 @@ exports.addVerificationWSAPI = function(app) { | |
| app.get('/wsapi/fake_verification', function(req, res) { | |
| var email = url.parse(req.url, true).query['email']; | |
| db.verificationSecretForEmail(email, function(err, secret) { | |
| - if (err) return wsapi.databaseDown(res, err); | |
| + if (err) return wsapi.databaseDown(resp, err); | |
| if (secret) res.write(secret); | |
| else res.writeHead(400, {"Content-Type": "text/plain"}); | |
| res.end(); | |
| diff --git a/lib/browserid/views.js b/lib/browserid/views.js | |
| index 035cb9e..1fc696f 100644 | |
| --- a/lib/browserid/views.js | |
| +++ b/lib/browserid/views.js | |
| @@ -173,12 +173,12 @@ exports.setup = function(app) { | |
| } else { | |
| // this is stage or production, explicitly disable all resources under /test | |
| app.get(/^\/test/, function(req, res) { | |
| - httputils.notFound(res, "Cannot " + req.method + " " + req.url); | |
| + httputils.notFound("Cannot " + req.method + " " + req.url); | |
| }); | |
| } | |
| // REDIRECTS | |
| - const REDIRECTS = { | |
| + REDIRECTS = { | |
| "/manage": "/", | |
| "/users": "/", | |
| "/users/": "/", | |
| diff --git a/lib/configuration.js b/lib/configuration.js | |
| index 0e0d3c2..b7e3d32 100644 | |
| --- a/lib/configuration.js | |
| +++ b/lib/configuration.js | |
| @@ -91,7 +91,6 @@ var conf = module.exports = convict({ | |
| format: 'string?', | |
| env: 'MYSQL_PASSWORD' | |
| }, | |
| - host: 'string?', | |
| create_schema: 'boolean = true', | |
| may_write: 'boolean = true', | |
| name: { | |
| @@ -115,8 +114,7 @@ var conf = module.exports = convict({ | |
| smtp: { | |
| host: 'string?', | |
| user: 'string?', | |
| - pass: 'string?', | |
| - port: 'integer = 25' | |
| + pass: 'string?' | |
| }, | |
| statsd: { | |
| enabled: { | |
| @@ -200,6 +198,15 @@ var conf = module.exports = convict({ | |
| format: 'string?', | |
| env: 'DBWRITER_URL' | |
| }, | |
| + bigtent_url: { | |
| + doc: "Base URL to a BigTent server for proxyidp support", | |
| + format: 'string = "https://dev.bigtent.mozilla.org"', | |
| + env: 'BIGTENT_URL' | |
| + }, | |
| + bigtent_domains: { | |
| + doc: "List of email domains which should be considered proxyidp candidtes.", | |
| + format: 'array { string }* = []' | |
| + }, | |
| browserid_url: { | |
| format: 'string?', | |
| env: 'BROWSERID_URL' | |
| diff --git a/lib/db.js b/lib/db.js | |
| index 1f08f6e..8fe22be 100644 | |
| --- a/lib/db.js | |
| +++ b/lib/db.js | |
| @@ -117,6 +117,13 @@ exports.onReady = function(f) { | |
| }; | |
| }); | |
| +// Note: createUserWithPrimaryEmail is used for the following email types: | |
| +// * primary | |
| +// * proxyidp | |
| +// This is because proxyidps act very much like Primary IdPs and an a Primary | |
| +// may opt into being a Primary at any point without any data migration in our | |
| +// database. | |
| + | |
| exports.addTestUser = function() { | |
| // would we like to check the environment here? | |
| checkReady(); | |
| diff --git a/lib/db/json.js b/lib/db/json.js | |
| index 376a2a7..497bdf4 100644 | |
| --- a/lib/db/json.js | |
| +++ b/lib/db/json.js | |
| @@ -158,7 +158,7 @@ exports.userOwnsEmail = function(uid, email, cb) { | |
| function addEmailToAccount(userID, email, type, cb) { | |
| // validate 'type' isn't bogus | |
| - if ([ 'secondary', 'primary' ].indexOf(type) === -1) { | |
| + if ([ 'secondary', 'primary', 'proxyidp' ].indexOf(type) === -1) { | |
| return process.nextTick(function() { | |
| cb("invalid type"); | |
| }); | |
| diff --git a/lib/db/mysql.js b/lib/db/mysql.js | |
| index f9d37b7..8c2d805 100644 | |
| --- a/lib/db/mysql.js | |
| +++ b/lib/db/mysql.js | |
| @@ -29,8 +29,6 @@ | |
| * +------------------------+ | |
| */ | |
| -/*global dne:true */ | |
| - | |
| const | |
| mysql = require('./mysql_wrapper.js'), | |
| secrets = require('../secrets.js'), | |
| @@ -458,7 +456,7 @@ exports.updatePassword = function(uid, hash, cb) { | |
| [ hash, uid ], | |
| function (err, rows) { | |
| if (!err && (!rows || rows.affectedRows !== 1)) { | |
| - err = "no record with id " + uid; | |
| + err = "no record with email " + email; | |
| } | |
| cb(err); | |
| }); | |
| diff --git a/lib/email.js b/lib/email.js | |
| index ee44447..4bb0c50 100644 | |
| --- a/lib/email.js | |
| +++ b/lib/email.js | |
| @@ -13,10 +13,7 @@ logger = require('./logging.js').logger; | |
| /* if smtp parameters are configured, use them */ | |
| try { var smtp_params = config.get('smtp'); } catch(e) {}; | |
| if (smtp_params && smtp_params.host) { | |
| - emailer.SMTP = { | |
| - host: smtp_params.host, | |
| - port: smtp_params.port | |
| - }; | |
| + emailer.SMTP = { host: smtp_params.host }; | |
| logger.info("delivering email via SMTP host: " + emailer.SMTP.host); | |
| if (smtp_params.user) { | |
| emailer.SMTP.use_authentication = true; | |
| diff --git a/lib/i18n.js b/lib/i18n.js | |
| index 7b2d214..45944b2 100644 | |
| --- a/lib/i18n.js | |
| +++ b/lib/i18n.js | |
| @@ -115,7 +115,7 @@ exports.abide = function (options) { | |
| if (mo_cache[locale].mo_exists) { | |
| if (mo_cache[locale].gt === null) { | |
| mo_cache[locale].gt = new Gettext(); | |
| - var mo_path = mo_file_path(locale); | |
| + mo_path = mo_file_path(locale); | |
| mo_cache[locale].gt.addTextdomain(locale, | |
| fs.readFileSync(mo_path)); | |
| mo_cache[locale].gt.textdomain(locale); | |
| @@ -153,7 +153,7 @@ function qualityCmp(a, b) { | |
| * lang: 'pl', quality: 0.7 | |
| * } | |
| */ | |
| -var parseAcceptLanguage = exports.parseAcceptLanguage = function (header) { | |
| +exports.parseAcceptLanguage = parseAcceptLanguage = function (header) { | |
| // pl,fr-FR;q=0.3,en-US;q=0.1 | |
| if (! header || ! header.split) { | |
| return []; | |
| @@ -163,7 +163,7 @@ var parseAcceptLanguage = exports.parseAcceptLanguage = function (header) { | |
| var parts = raw_lang.split(';'); | |
| var q = 1; | |
| if (parts.length > 1 && parts[1].indexOf('q=') == 0) { | |
| - var qval = parseFloat(parts[1].split('=')[1]); | |
| + qval = parseFloat(parts[1].split('=')[1]); | |
| if (isNaN(qval) === false) { | |
| q = qval; | |
| } | |
| @@ -179,7 +179,7 @@ var parseAcceptLanguage = exports.parseAcceptLanguage = function (header) { | |
| // supported languages, returns the best match or a default language. | |
| // | |
| // languages must be a sorted list, the first match is returned. | |
| -var bestLanguage = exports.bestLanguage = function(languages, supported_languages, defaultLanguage) { | |
| +exports.bestLanguage = bestLanguage = function(languages, supported_languages, defaultLanguage) { | |
| var lower = supported_languages.map(function (l) { return l.toLowerCase(); }); | |
| for(var i=0; i < languages.length; i++) { | |
| var lq = languages[i]; | |
| @@ -199,7 +199,7 @@ var bestLanguage = exports.bestLanguage = function(languages, supported_language | |
| * language: en-US | |
| * locale: en_US | |
| */ | |
| -var localeFrom = exports.localeFrom = function (language) { | |
| +exports.localeFrom = localeFrom = function (language) { | |
| if (! language || ! language.split) { | |
| return ""; | |
| } | |
| @@ -248,7 +248,7 @@ exports.languageFrom = function (locale) { | |
| * Positional Example: | |
| * format("%s %s", ["Hello", "World"]); | |
| */ | |
| -var format = exports.format = function (fmt, obj, named) { | |
| +exports.format = format = function (fmt, obj, named) { | |
| if (! fmt) return ""; | |
| if (named) { | |
| return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); | |
| diff --git a/lib/load_gen/common.js b/lib/load_gen/common.js | |
| index b02f17a..59758e2 100644 | |
| --- a/lib/load_gen/common.js | |
| +++ b/lib/load_gen/common.js | |
| @@ -97,7 +97,7 @@ exports.genAssertionAndVerify = function(cfg, user, ctx, email, audience, cb) { | |
| }, function (err, r) { | |
| try { | |
| if (err) throw err; | |
| - if (r.code !== 200) throw "non-200 status: " + r.code; | |
| + if (r.code !== 200) throw "non-200 status: " + resp.code; | |
| if (!JSON.parse(r.body).status === 'okay') throw "verification failed with: " + r.reason; | |
| cb(undefined); | |
| } catch(e) { | |
| diff --git a/lib/proxyidp.js b/lib/proxyidp.js | |
| new file mode 100644 | |
| index 0000000..f764e2c | |
| --- /dev/null | |
| +++ b/lib/proxyidp.js | |
| @@ -0,0 +1,17 @@ | |
| +/* This Source Code Form is subject to the terms of the Mozilla Public | |
| + * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| + | |
| +const config = require('./configuration'); | |
| + | |
| +var proxied = config.get('bigtent_domains'); | |
| + | |
| +exports.isProxyIdP = function (email) { | |
| + var pieces = email.split('@'); | |
| + if (pieces.length == 2) { | |
| + if (proxied.indexOf(pieces[1].toLowerCase()) >= 0) { | |
| + return true; | |
| + } | |
| + } | |
| + return false; | |
| +}; | |
| diff --git a/lib/static_resources.js b/lib/static_resources.js | |
| index d86bfd7..ad17eac 100644 | |
| --- a/lib/static_resources.js | |
| +++ b/lib/static_resources.js | |
| @@ -104,21 +104,17 @@ var dialog_js = und.flatten([ | |
| '/dialog/controllers/generate_assertion.js', | |
| '/dialog/controllers/is_this_your_computer.js', | |
| '/dialog/controllers/set_password.js', | |
| - '/dialog/controllers/rp_info.js', | |
| + | |
| '/dialog/start.js' | |
| ]]); | |
| exports.resources = { | |
| '/production/dialog.css': [ | |
| - '/fonts/fonts_common.css', | |
| - '/fonts/fonts_dialog.css', | |
| '/css/common.css', | |
| '/dialog/css/popup.css', | |
| '/dialog/css/m.css' | |
| ], | |
| '/production/browserid.css': [ | |
| - '/fonts/fonts_common.css', | |
| - '/fonts/fonts_mainsite.css', | |
| '/css/common.css', | |
| '/css/style.css', | |
| '/css/m.css' | |
| @@ -182,7 +178,7 @@ exports.all = function(langs) { | |
| /** | |
| * Get all resource urls for a specified resource based on the locale | |
| */ | |
| -var getResources = exports.getResources = function(path, locale) { | |
| +exports.getResources = getResources = function(path, locale) { | |
| var res = []; | |
| if (exports.resources[path]) { | |
| exports.resources[path].forEach(function(r) { | |
| diff --git a/lib/verifier/certassertion.js b/lib/verifier/certassertion.js | |
| index a8342f3..ae0ee22 100644 | |
| --- a/lib/verifier/certassertion.js | |
| +++ b/lib/verifier/certassertion.js | |
| @@ -11,6 +11,7 @@ config = require("../configuration.js"), | |
| logger = require("../logging.js").logger, | |
| secrets = require('../secrets.js'), | |
| primary = require('../primary.js'), | |
| +proxyidp = require('../proxyidp'), | |
| urlparse = require('urlparse'); | |
| require("jwcrypto/lib/algs/ds"); | |
| @@ -134,7 +135,9 @@ function verify(assertion, audience, successCB, errorCB) { | |
| // that the email's domain delegated authority to the issuer | |
| var domainFromEmail = principal.email.replace(/^.*@/, ''); | |
| - if (ultimateIssuer != HOSTNAME && ultimateIssuer !== domainFromEmail) | |
| + if (ultimateIssuer != HOSTNAME && | |
| + ultimateIssuer !== domainFromEmail && | |
| + ! proxyidp.isProxyIdP(principal.email)) | |
| { | |
| primary.delegatesAuthority(domainFromEmail, ultimateIssuer, function (delegated) { | |
| if (delegated) { | |
| diff --git a/lib/wsapi/add_email_with_assertion.js b/lib/wsapi/add_email_with_assertion.js | |
| index e8649ce..7b208d3 100644 | |
| --- a/lib/wsapi/add_email_with_assertion.js | |
| +++ b/lib/wsapi/add_email_with_assertion.js | |
| @@ -23,6 +23,7 @@ exports.i18n = false; | |
| // authenticated. | |
| exports.process = function(req, res) { | |
| // first let's verify that the assertion is valid | |
| + logger.info('AOK PROXYIPD add_email_with_assertion assertion=' + req.body.assertion); | |
| primary.verifyAssertion(req.body.assertion, function(err, email) { | |
| if (err) { | |
| return res.json({ | |
| diff --git a/lib/wsapi/address_info.js b/lib/wsapi/address_info.js | |
| index 1694382..a2938b4 100644 | |
| --- a/lib/wsapi/address_info.js | |
| +++ b/lib/wsapi/address_info.js | |
| @@ -3,12 +3,14 @@ | |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| const | |
| +config = require('../configuration.js'), | |
| db = require('../db.js'), | |
| +httputils = require('../httputils.js'), | |
| primary = require('../primary.js'), | |
| +proxyidp = require('../proxyidp.js'), | |
| wsapi = require('../wsapi.js'), | |
| -httputils = require('../httputils.js'), | |
| url = require('url'), | |
| -logger = require('../logging.js').logger; | |
| +util = require('util'); | |
| // return information about an email address. | |
| // type: is this an address with 'primary' or 'secondary' support? | |
| @@ -35,6 +37,7 @@ exports.process = function(req, res) { | |
| } | |
| // Saftey value for production branch only | |
| var done = false; | |
| + var bt_done = false; | |
| primary.checkSupport(m[1], function(err, urls, publicKey, delegates) { | |
| if (done) { | |
| return; | |
| @@ -48,6 +51,22 @@ exports.process = function(req, res) { | |
| if (urls) { | |
| urls.type = 'primary'; | |
| res.json(urls); | |
| + } else if (proxyidp.isProxyIdP(email)) { | |
| + // TODO DRY violation: BigTent /.well-known/browserid has these urls too | |
| + var bigtent = url.parse(config.get('bigtent_url')).hostname; | |
| + | |
| + primary.checkSupport(bigtent, function(err, urls, publicKey, delegates) { | |
| + if (bt_done) { | |
| + return; | |
| + } | |
| + bt_done = true; | |
| + if (err || ! urls) { | |
| + logger.warn('error checking BigTent for IdP details: ' + err); | |
| + return httputils.serverError(res, "BigTent unavailable"); | |
| + } | |
| + urls.type = 'primary'; | |
| + res.json(urls); | |
| + }); | |
| } else { | |
| db.emailKnown(email, function(err, known) { | |
| if (err) { | |
| diff --git a/lib/wsapi/auth_with_assertion.js b/lib/wsapi/auth_with_assertion.js | |
| index 37127d6..db49ec8 100644 | |
| --- a/lib/wsapi/auth_with_assertion.js | |
| +++ b/lib/wsapi/auth_with_assertion.js | |
| @@ -38,7 +38,8 @@ exports.process = function(req, res) { | |
| if (err) return wsapi.databaseDown(res, err); | |
| // if this is a known primary email, authenticate the user and we're done! | |
| - if (type === 'primary') { | |
| + if (type === 'primary' || | |
| + type === 'proxyidp') { | |
| return db.emailToUID(email, function(err, uid) { | |
| if (err) return wsapi.databaseDown(res, err); | |
| if (!uid) return res.json({ success: false, reason: "internal error" }); | |
| @@ -92,7 +93,6 @@ exports.process = function(req, res) { | |
| return res.json({ success: false, reason: "internal error creating account" }); | |
| } | |
| - logger.info("successfully created primary acct for " + email + " (" + r.userid + ")"); | |
| wsapi.authenticateSession(req.session, r.userid, 'assertion', | |
| req.body.ephemeral ? config.get('ephemeral_session_duration_ms') | |
| : config.get('authentication_duration_ms')); | |
| diff --git a/lib/wsapi/complete_email_addition.js b/lib/wsapi/complete_email_addition.js | |
| index 53ddb36..abd0124 100644 | |
| --- a/lib/wsapi/complete_email_addition.js | |
| +++ b/lib/wsapi/complete_email_addition.js | |
| @@ -23,12 +23,39 @@ exports.process = function(req, res) { | |
| // 1. you must already be authenticated as the user who initiated the verification | |
| // 2. you must provide the password of the initiator. | |
| + // TRANSITIONAL CODE COMMENT | |
| + // for issue 1000 we moved initial password selection to the browserid dialog (from | |
| + // the verification page). Rolling out this change causes some temporal pain. | |
| + // Outstannding verification links sent before the change was deployed will have | |
| + // email addition requests that require passwords without passwords in the stage table. | |
| + // When the verification page is loaded for | |
| + // these links, we prompt the user for a password. That password is sent up with | |
| + // the request. this code and comment should all be purged after the new code | |
| + // has been in production for 2 weeks. | |
| + | |
| + var transitionalPassword = null; | |
| + | |
| + // END TRANSITIONAL CODE COMMENT | |
| + | |
| + | |
| db.authForVerificationSecret(req.body.token, function(err, initiator_hash, initiator_uid) { | |
| if (err) { | |
| logger.info("unknown verification secret: " + err); | |
| return wsapi.databaseDown(res, err); | |
| } | |
| + // TRANSITIONAL CODE | |
| + if (!initiator_hash) { | |
| + if (!req.body.pass) return httputils.authRequired(res, "password required"); | |
| + var err = wsapi.checkPassword(req.body.pass); | |
| + if (err) { | |
| + logger.warn("invalid password received: " + err); | |
| + return httputils.badRequest(res, err); | |
| + } | |
| + transitionalPassword = req.body.pass; | |
| + postAuthentication(); | |
| + } else | |
| + // END TRANSITIONAL CODE | |
| if (req.session.userid === initiator_uid) { | |
| postAuthentication(); | |
| } else if (typeof req.body.pass === 'string') { | |
| @@ -54,6 +81,23 @@ exports.process = function(req, res) { | |
| } else { | |
| wsapi.authenticateSession(req.session, uid, 'password'); | |
| res.json({ success: true }); | |
| + | |
| + // TRANSITIONAL CODE | |
| + if (transitionalPassword) { | |
| + wsapi.bcryptPassword(transitionalPassword, function(err, hash) { | |
| + if (err) { | |
| + logger.warn("couldn't bcrypt pass for old verification link: " + err); | |
| + return; | |
| + } | |
| + | |
| + db.updatePassword(uid, hash, function(err) { | |
| + if (err) { | |
| + logger.warn("couldn't bcrypt pass for old verification link: " + err); | |
| + } | |
| + }); | |
| + }); | |
| + } | |
| + // END TRANSITIONAL CODE | |
| } | |
| }); | |
| }; | |
| diff --git a/lib/wsapi/complete_user_creation.js b/lib/wsapi/complete_user_creation.js | |
| index 7a65ec4..5b87c3e 100644 | |
| --- a/lib/wsapi/complete_user_creation.js | |
| +++ b/lib/wsapi/complete_user_creation.js | |
| @@ -7,8 +7,7 @@ db = require('../db.js'), | |
| wsapi = require('../wsapi.js'), | |
| httputils = require('../httputils'), | |
| logger = require('../logging.js').logger, | |
| -bcrypt = require('../bcrypt'), | |
| -config = require('../configuration'); | |
| +bcrypt = require('../bcrypt'); | |
| exports.method = 'post'; | |
| exports.writes_db = true; | |
| @@ -29,6 +28,16 @@ exports.process = function(req, res) { | |
| // and then control a browserid account that they can use to prove they own | |
| // the email address of the attacked. | |
| + // TRANSITIONAL CODE COMMENT | |
| + // for issue 1000 we moved initial password selection to the browserid dialog (from | |
| + // the verification page). Rolling out this change causes some temporal pain. | |
| + // Outstannding verification links sent before the change was deployed will have | |
| + // new user requests without passwords. When the verification page is loaded for | |
| + // these links, we prompt the user for a password. That password is sent up with | |
| + // the request. this code and comment should all be purged after the new code | |
| + // has been in production for 2 weeks. | |
| + // END TRANSITIONAL CODE COMMENT | |
| + | |
| // is this the same browser? | |
| if (typeof req.session.pendingCreation === 'string' && | |
| req.body.token === req.session.pendingCreation) { | |
| @@ -37,6 +46,13 @@ exports.process = function(req, res) { | |
| // is a password provided? | |
| else if (typeof req.body.pass === 'string') { | |
| return db.authForVerificationSecret(req.body.token, function(err, hash) { | |
| + // TRANSITIONAL CODE | |
| + // if hash is null, no password was provided during verification and | |
| + // this is an old-style verification. We accept the password and will | |
| + // update it after the verification is complete. | |
| + if (err == 'no password for user' || !hash) return postAuthentication(); | |
| + // END TRANSITIONAL CODE | |
| + | |
| if (err) { | |
| logger.warn("couldn't get password for verification secret: " + err); | |
| return wsapi.databaseDown(res, err); | |
| @@ -58,33 +74,67 @@ exports.process = function(req, res) { | |
| } | |
| function postAuthentication() { | |
| + // the time the email verification is performed, we'll clear the pendingCreation | |
| + // data on the session. | |
| + delete req.session.pendingCreation; | |
| + | |
| db.haveVerificationSecret(req.body.token, function(err, known) { | |
| if (err) return wsapi.databaseDown(res, err); | |
| - if (!known) { | |
| - // clear the pendingCreation token from the session if we find no such | |
| - // token in the database | |
| - delete req.session.pendingCreation; | |
| - return res.json({ success: false} ); | |
| - } | |
| + if (!known) return res.json({ success: false} ); | |
| - db.gotVerificationSecret(req.body.token, function(err, email, uid) { | |
| - if (err) { | |
| - logger.warn("couldn't complete email verification: " + err); | |
| - wsapi.databaseDown(res, err); | |
| - } else { | |
| - // clear the pendingCreation token from the session once we | |
| - // successfully complete user creation | |
| - delete req.session.pendingCreation; | |
| + // TRANSITIONAL CODE | |
| + // user is authorized (1 or 2 above) OR user has no password set, in which | |
| + // case for a short time we'll accept the password provided with the verification | |
| + // link, and set it as theirs. | |
| + var transitionalPassword = null; | |
| - // At this point, the user is either on the same browser with a token from | |
| - // their email address, OR they've provided their account password. It's | |
| - // safe to grant them an authenticated session. | |
| - wsapi.authenticateSession(req.session, uid, 'password', | |
| - config.get('ephemeral_session_duration_ms')); | |
| - res.json({ success: true }); | |
| + db.authForVerificationSecret(req.body.token, function(err, hash) { | |
| + if (err == 'no password for user' || !hash) { | |
| + if (!req.body.pass) return httputils.authRequired(res, "password required"); | |
| + err = wsapi.checkPassword(req.body.pass); | |
| + if (err) { | |
| + logger.warn("invalid password received: " + err); | |
| + return httputils.badRequest(res, err); | |
| + } | |
| + transitionalPassword = req.body.pass; | |
| } | |
| + completeCreation(); | |
| }); | |
| + // END TRANSITIONAL CODE | |
| + | |
| + function completeCreation() { | |
| + db.gotVerificationSecret(req.body.token, function(err, email, uid) { | |
| + if (err) { | |
| + logger.warn("couldn't complete email verification: " + err); | |
| + wsapi.databaseDown(res, err); | |
| + } else { | |
| + // FIXME: not sure if we want to do this (ba) | |
| + // at this point the user has set a password associated with an email address | |
| + // that they've verified. We create an authenticated session. | |
| + wsapi.authenticateSession(req.session, uid, 'password', | |
| + config.get('ephemeral_session_duration_ms')); | |
| + res.json({ success: true }); | |
| + | |
| + // TRANSITIONAL CODE | |
| + if (transitionalPassword) { | |
| + wsapi.bcryptPassword(transitionalPassword, function(err, hash) { | |
| + if (err) { | |
| + logger.warn("couldn't bcrypt pass for old verification link: " + err); | |
| + return; | |
| + } | |
| + | |
| + db.updatePassword(uid, hash, function(err) { | |
| + if (err) { | |
| + logger.warn("couldn't bcrypt pass for old verification link: " + err); | |
| + } | |
| + }); | |
| + }); | |
| + } | |
| + // END TRANSITIONAL CODE | |
| + } | |
| + }); | |
| + } | |
| }); | |
| } | |
| }; | |
| diff --git a/lib/wsapi/stage_email.js b/lib/wsapi/stage_email.js | |
| index 9ad39e9..60129dd 100644 | |
| --- a/lib/wsapi/stage_email.js | |
| +++ b/lib/wsapi/stage_email.js | |
| @@ -8,8 +8,7 @@ wsapi = require('../wsapi.js'), | |
| httputils = require('../httputils'), | |
| logger = require('../logging.js').logger, | |
| email = require('../email.js'), | |
| -sanitize = require('../sanitize'), | |
| -config = require('../configuration'); | |
| +sanitize = require('../sanitize'); | |
| /* First half of account creation. Stages a user account for creation. | |
| * this involves creating a secret url that must be delivered to the | |
| diff --git a/lib/wsapi/stage_user.js b/lib/wsapi/stage_user.js | |
| index 8498194..ff1dd24 100644 | |
| --- a/lib/wsapi/stage_user.js | |
| +++ b/lib/wsapi/stage_user.js | |
| @@ -8,8 +8,7 @@ wsapi = require('../wsapi.js'), | |
| httputils = require('../httputils'), | |
| logger = require('../logging.js').logger, | |
| email = require('../email.js'), | |
| -sanitize = require('../sanitize'), | |
| -config = require('../configuration'); | |
| +sanitize = require('../sanitize'); | |
| /* First half of account creation. Stages a user account for creation. | |
| * this involves creating a secret url that must be delivered to the | |
| @@ -60,7 +59,7 @@ exports.process = function(req, res) { | |
| if (err) { | |
| if (err.indexOf('exceeded') != -1) { | |
| logger.warn("max load hit, failing on auth request with 503: " + err); | |
| - return httputils.serviceUnavailable(res, "server is too busy"); | |
| + return httputils.serviceUnavailable("server is too busy"); | |
| } | |
| logger.error("can't bcrypt: " + err); | |
| return res.json({ success: false }); | |
| diff --git a/lib/wsapi_client.js b/lib/wsapi_client.js | |
| index 81dbce7..c6ae5bd 100644 | |
| --- a/lib/wsapi_client.js | |
| +++ b/lib/wsapi_client.js | |
| @@ -43,7 +43,7 @@ exports.clearCookies = function(ctx) { | |
| }; | |
| exports.getCookie = function(ctx, which) { | |
| - if (typeof which === 'string') which = new RegExp('/^' + which + '$/'); | |
| + if (typeof which === 'string') which = new Regex('/^' + which + '$/'); | |
| var cookieNames = Object.keys(ctx.cookieJar); | |
| for (var i = 0; i < cookieNames.length; i++) { | |
| if (which.test(cookieNames[i])) return ctx.cookieJar[cookieNames[i]]; | |
| @@ -114,7 +114,6 @@ exports.post = function(cfg, path, context, postArgs, cb) { | |
| // parse the server URL (cfg.browserid) | |
| var uObj; | |
| var meth; | |
| - var body; | |
| try { | |
| uObj = url.parse(cfg.browserid); | |
| meth = uObj.protocol === 'http:' ? http : https; | |
| diff --git a/package.json b/package.json | |
| index 8157d2e..c9412e2 100644 | |
| --- a/package.json | |
| +++ b/package.json | |
| @@ -35,8 +35,9 @@ | |
| "winston": "0.5.6" | |
| }, | |
| "devDependencies": { | |
| + "xml2js": "0.1.13", | |
| "vows": "0.5.13", | |
| - "awsbox": "0.2.10", | |
| + "aws-lib": "0.0.5", | |
| "irc": "0.3.3" | |
| }, | |
| "scripts": { | |
| diff --git a/resources/static/css/common.css b/resources/static/css/common.css | |
| index c12bdae..c380446 100644 | |
| --- a/resources/static/css/common.css | |
| +++ b/resources/static/css/common.css | |
| @@ -291,6 +291,7 @@ a.secondary[disabled], .submit_disabled a.secondary, .submit_disabled a.secondar | |
| } | |
| .headline-main, h1, h2, h3, h4 { | |
| + font-family: 'Open Sans', "Lucida Sans", "Lucida Grande", "Lucida Sans Unicode", Verdana, sans-serif; | |
| font-weight: normal; | |
| text-shadow: 0px 1px 0px rgba(255,255,255,0.75); | |
| } | |
| diff --git a/resources/static/dialog/controllers/actions.js b/resources/static/dialog/controllers/actions.js | |
| index 80435e5..e01cb57 100644 | |
| --- a/resources/static/dialog/controllers/actions.js | |
| +++ b/resources/static/dialog/controllers/actions.js | |
| @@ -58,10 +58,6 @@ BrowserID.Modules.Actions = (function() { | |
| if(data.ready) _.defer(data.ready); | |
| }, | |
| - doRPInfo: function(info) { | |
| - startService("rp_info", info); | |
| - }, | |
| - | |
| doCancel: function() { | |
| if(onsuccess) onsuccess(null); | |
| }, | |
| diff --git a/resources/static/dialog/controllers/authenticate.js b/resources/static/dialog/controllers/authenticate.js | |
| index ac413aa..2069a57 100644 | |
| --- a/resources/static/dialog/controllers/authenticate.js | |
| +++ b/resources/static/dialog/controllers/authenticate.js | |
| @@ -40,7 +40,6 @@ BrowserID.Modules.Authenticate = (function() { | |
| function checkEmail(info) { | |
| var email = getEmail(), | |
| self = this; | |
| - | |
| if (!email) return; | |
| if(info && info.type) { | |
| @@ -54,11 +53,9 @@ BrowserID.Modules.Authenticate = (function() { | |
| function onAddressInfo(info) { | |
| addressInfo = info; | |
| - | |
| - if(info.type === "primary") { | |
| + if(info.IdPEnabled) { | |
| self.close("primary_user", info, info); | |
| - } | |
| - else if(info.known) { | |
| + } else if(info.known) { | |
| enterPasswordState.call(self); | |
| } else { | |
| createSecondaryUser.call(self); | |
| @@ -120,8 +117,6 @@ BrowserID.Modules.Authenticate = (function() { | |
| function enterPasswordState(callback) { | |
| var self=this; | |
| - dom.setInner("#password", ""); | |
| - | |
| self.publish("enter_password", addressInfo); | |
| self.submit = authenticate; | |
| showHint("returning", function() { | |
| @@ -149,7 +144,6 @@ BrowserID.Modules.Authenticate = (function() { | |
| var Module = bid.Modules.PageModule.extend({ | |
| start: function(options) { | |
| options = options || {}; | |
| - | |
| addressInfo = null; | |
| lastEmail = options.email || ""; | |
| diff --git a/resources/static/dialog/controllers/dialog.js b/resources/static/dialog/controllers/dialog.js | |
| index 8d9725d..8e5b8f1 100644 | |
| --- a/resources/static/dialog/controllers/dialog.js | |
| +++ b/resources/static/dialog/controllers/dialog.js | |
| @@ -75,6 +75,7 @@ BrowserID.Modules.Dialog = (function() { | |
| function setOrigin(origin) { | |
| user.setOrigin(origin); | |
| + dom.setInner("#sitename", user.getHostname()); | |
| } | |
| function onWindowUnload() { | |
| @@ -92,12 +93,6 @@ BrowserID.Modules.Dialog = (function() { | |
| return encodeURI(u.validate().normalize().toString()); | |
| } | |
| - function fixupAbsolutePath(origin_url, path) { | |
| - if (/^\//.test(path)) return fixupURL(origin_url, path); | |
| - | |
| - throw "must be an absolute path: (" + path + ")"; | |
| - } | |
| - | |
| var Dialog = bid.Modules.PageModule.extend({ | |
| start: function(options) { | |
| var self=this; | |
| @@ -173,17 +168,6 @@ BrowserID.Modules.Dialog = (function() { | |
| params.privacyURL = fixupURL(origin_url, paramsFromRP.privacyPolicy); | |
| } | |
| - if (paramsFromRP.siteLogo) { | |
| - // Until we have our head around the dangers of data uris and images | |
| - // that come from other domains, only allow absolute paths from the | |
| - // origin. | |
| - params.siteLogo = fixupAbsolutePath(origin_url, paramsFromRP.siteLogo); | |
| - } | |
| - | |
| - if (paramsFromRP.siteName) { | |
| - params.siteName = _.escape(paramsFromRP.siteName); | |
| - } | |
| - | |
| if (hash.indexOf("#CREATE_EMAIL=") === 0) { | |
| var email = hash.replace(/#CREATE_EMAIL=/, ""); | |
| if (!bid.verifyEmail(email)) | |
| diff --git a/resources/static/dialog/controllers/provision_primary_user.js b/resources/static/dialog/controllers/provision_primary_user.js | |
| index 367195e..7f9469a 100644 | |
| --- a/resources/static/dialog/controllers/provision_primary_user.js | |
| +++ b/resources/static/dialog/controllers/provision_primary_user.js | |
| @@ -57,7 +57,7 @@ BrowserID.Modules.ProvisionPrimaryUser = (function() { | |
| if(!(auth && prov)) { | |
| user.addressInfo(email, function(status) { | |
| - if(status.type === "primary") { | |
| + if(status.IdPEnabled) { | |
| provisionPrimaryUser.call(self, email, status.auth, status.prov); | |
| } | |
| else { | |
| diff --git a/resources/static/dialog/controllers/required_email.js b/resources/static/dialog/controllers/required_email.js | |
| index 6e5f97e..982a727 100644 | |
| --- a/resources/static/dialog/controllers/required_email.js | |
| +++ b/resources/static/dialog/controllers/required_email.js | |
| @@ -140,7 +140,7 @@ BrowserID.Modules.RequiredEmail = (function() { | |
| // 2) Authenticated user who does not control address. | |
| // 3) Unauthenticated user. | |
| user.addressInfo(email, function(info) { | |
| - if(info.type === "primary") primaryInfo = info; | |
| + if(info.IdPEnabled) primaryInfo = info; | |
| if (info.type === "primary" && info.authed) { | |
| // this is a primary user who is authenticated with their IdP. | |
| diff --git a/resources/static/dialog/controllers/rp_info.js b/resources/static/dialog/controllers/rp_info.js | |
| deleted file mode 100644 | |
| index 3bc9b20..0000000 | |
| --- a/resources/static/dialog/controllers/rp_info.js | |
| +++ /dev/null | |
| @@ -1,47 +0,0 @@ | |
| -/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */ | |
| -/*global _: true, BrowserID: true, PageController: true */ | |
| -/* This Source Code Form is subject to the terms of the Mozilla Public | |
| - * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| - | |
| - | |
| -/** | |
| - * Purpose: | |
| - * Display to the user RP related data such as hostname, name, and logo. | |
| - */ | |
| -BrowserID.Modules.RPInfo = (function() { | |
| - "use strict"; | |
| - | |
| - var bid = BrowserID, | |
| - renderer = bid.Renderer, | |
| - sc; | |
| - | |
| - var Module = bid.Modules.PageModule.extend({ | |
| - start: function(options) { | |
| - options = options || {}; | |
| - | |
| - /** | |
| - * Very important security info - it is assumed all parameters are | |
| - * already properly escaped before being passed here. This is done | |
| - * in dialog.js. Check it. | |
| - * | |
| - * hostname is set internally based on the RP URL, | |
| - * so it will not be escaped. It is set initially in user.js at the very | |
| - * bottom for the main site, and then in dialog.js->get for the dialog. | |
| - */ | |
| - renderer.render("#rp_info", "rp_info", { | |
| - hostname: options.hostname, | |
| - siteName: options.siteName, | |
| - siteLogo: options.siteLogo | |
| - }); | |
| - | |
| - sc.start.call(this, options); | |
| - } | |
| - }); | |
| - | |
| - sc = Module.sc; | |
| - | |
| - return Module; | |
| - | |
| -}()); | |
| - | |
| diff --git a/resources/static/dialog/controllers/verify_primary_user.js b/resources/static/dialog/controllers/verify_primary_user.js | |
| index 9e38e6b..67da7d1 100644 | |
| --- a/resources/static/dialog/controllers/verify_primary_user.js | |
| +++ b/resources/static/dialog/controllers/verify_primary_user.js | |
| @@ -1,5 +1,6 @@ | |
| -/*jshint browser:true, jQuery: true, forin: true, laxbreak:true */ | |
| -/*global _: true, BrowserID: true, PageController: true */ | |
| +/*jshint browser:true, jQuery: true, forin: false, laxbreak:true */ | |
| +/*global _: true, BrowserID: true */ | |
| + | |
| /* This Source Code Form is subject to the terms of the Mozilla Public | |
| * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| @@ -7,56 +8,106 @@ BrowserID.Modules.VerifyPrimaryUser = (function() { | |
| "use strict"; | |
| var bid = BrowserID, | |
| + user = bid.User, | |
| + errors = bid.Errors, | |
| + wait = bid.Wait, | |
| sc, | |
| win, | |
| add, | |
| email, | |
| auth_url, | |
| helpers = bid.Helpers, | |
| - complete = helpers.complete; | |
| - | |
| - function verify(callback) { | |
| - this.publish("primary_user_authenticating"); | |
| - | |
| - // replace any hashes that may be there already. | |
| - var returnTo = win.document.location.href.replace(/#.*$/, ""); | |
| - | |
| - var type = add ? "ADD_EMAIL" : "CREATE_EMAIL"; | |
| - var url = helpers.toURL(auth_url, { | |
| - email: email, | |
| - return_to: returnTo + "#" + type + "=" +email | |
| - }); | |
| + complete = helpers.complete, | |
| + delayScreenTimeout, | |
| + isProxyIdP; | |
| + | |
| + // yahoo resizes themselves | |
| + var RESIZE_TABLE = { | |
| + "gmail.com$": { w: 900, h: 600 }, | |
| + "hotmail.com$": { w: 700, h: 488 } | |
| + }; | |
| + | |
| + function resizeWindow(email) { | |
| + for(var key in RESIZE_TABLE) { | |
| + var regExp = new RegExp(key); | |
| + if (regExp.test(email)) { | |
| + var dimensions = RESIZE_TABLE[key]; | |
| + win.resizeTo(dimensions.w, dimensions.h); | |
| + return; | |
| + } | |
| + } | |
| + } | |
| - win.document.location = url; | |
| - complete(callback); | |
| + function verify(callback) { | |
| + var self = this, | |
| + // replace any hashes that may be there already. | |
| + returnTo = win.document.location.href.replace(/#.*$/, ""), | |
| + type = add ? "ADD_EMAIL" : "CREATE_EMAIL", | |
| + url = helpers.toURL(auth_url, { | |
| + email: email, | |
| + return_to: returnTo + "#" + type + "=" +email | |
| + }); | |
| + | |
| + // primary_user_authenticating must be published before the wait | |
| + // screen is rendered or else the wait screen is taken away when all the | |
| + // modules are stopped. | |
| + self.publish("primary_user_authenticating", { url: url }); | |
| + self.renderWait("wait", wait.redirectToIdP); | |
| + | |
| + // Use the setTimeout delay so the wait screen actually renders. If the | |
| + // document's location is redirected before the screen is displayed, the | |
| + // user never sees it and it looks pretty ugly. | |
| + setTimeout(function() { | |
| + // only resize the window if redirecting to a proxyIdP. All other IdPs | |
| + // should abide by our rules of 700x400 default. | |
| + if (isProxyIdP) resizeWindow(email); | |
| + win.document.location = url; | |
| + | |
| + complete(callback); | |
| + }, delayScreenTimeout); | |
| } | |
| function cancel(callback) { | |
| this.close("cancel_state"); | |
| - callback && callback(); | |
| + complete(callback); | |
| } | |
| var Module = bid.Modules.PageModule.extend({ | |
| - start: function(data) { | |
| + start: function(options) { | |
| var self=this; | |
| - data = data || {}; | |
| - | |
| - win = data.window || window; | |
| - add = data.add; | |
| - email = data.email; | |
| - auth_url = data.auth_url; | |
| - | |
| - var templateData = helpers.extend({}, data, { | |
| - requiredEmail: data.requiredEmail || false, | |
| - privacy_url: data.privacyURL || null, | |
| - tos_url: data.tosURL || null | |
| - }); | |
| - self.renderDialog("verify_primary_user", templateData); | |
| - | |
| - self.click("#cancel", cancel); | |
| - | |
| - sc.start.call(self, data); | |
| + options = options || {}; | |
| + | |
| + win = options.window || window; | |
| + add = options.add; | |
| + email = options.email; | |
| + delayScreenTimeout = typeof options.delay_screen_timeout === "number" ? options.delay_screen_timeout : 500; | |
| + | |
| + sc.start.call(self, options); | |
| + | |
| + user.addressInfo(email, function(addressInfo) { | |
| + auth_url = addressInfo.auth; | |
| + isProxyIdP = addressInfo.type === "proxyidp"; | |
| + | |
| + // immediately call verify if the user is being shuffled off to a proxy | |
| + // idp. This skips the verification screen that normal IdP users see. | |
| + // Inconsistent - yet. Perhaps we will change this universally. | |
| + if (isProxyIdP) { | |
| + verify.call(self, options.ready); | |
| + } | |
| + else { | |
| + var templateData = helpers.extend({}, options, { | |
| + auth_url: addressInfo.auth || null, | |
| + requiredEmail: options.requiredEmail || false, | |
| + privacy_url: options.privacyURL || null, | |
| + tos_url: options.tosURL || null | |
| + }); | |
| + self.renderDialog("verify_primary_user", templateData); | |
| + | |
| + self.click("#cancel", cancel); | |
| + complete(options.ready); | |
| + } | |
| + }, self.getErrorDialog(errors.addressInfo)); | |
| }, | |
| submit: verify | |
| diff --git a/resources/static/dialog/css/m.css b/resources/static/dialog/css/m.css | |
| index 2a75780..6991dbe 100644 | |
| --- a/resources/static/dialog/css/m.css | |
| +++ b/resources/static/dialog/css/m.css | |
| @@ -43,7 +43,7 @@ | |
| } | |
| #signIn { | |
| - top: auto; /* this will be set in JS to be at the bottom of the header */ | |
| + top: 45px; /* 45px is a magic number - the height of the favicon area */ | |
| right: 0; | |
| width: auto; | |
| padding: 0; | |
| @@ -57,41 +57,13 @@ | |
| * being partially cut off by the site URL bar | |
| */ | |
| position: static; | |
| - padding: 10px; | |
| - border-bottom: 1px solid rgba(0,0,0,0.05); | |
| - text-align: center; | |
| - left: 0; | |
| - } | |
| - | |
| - #favicon img { | |
| - max-width: 32px; | |
| - max-height: 32px; | |
| - display: inline; | |
| - margin: 0 10px 0 0; | |
| - vertical-align: middle; | |
| - } | |
| - | |
| - #favicon h2, #favicon h3 { | |
| - margin: 0 5px; | |
| - height: auto; | |
| - line-height: 32px; | |
| - font-size: 20px; | |
| - display: inline; | |
| - vertical-align: middle; | |
| - } | |
| - | |
| - #favicon h3 { | |
| - font-size: 16px; | |
| } | |
| - #favicon .vertical { | |
| - height: auto; | |
| - line-height: 32px; | |
| - vertical-align: middle; | |
| + #favicon { | |
| + padding: 10px; | |
| } | |
| - | |
| - #signIn .table, #signIn .container { | |
| + #signIn .table { | |
| width: 100%; | |
| } | |
| @@ -107,8 +79,21 @@ | |
| padding: 0; | |
| } | |
| - #formWrap { | |
| - background-color: transparent; | |
| + #favicon { | |
| + text-align: center; | |
| + } | |
| + | |
| + #favicon img { | |
| + width: 32px; | |
| + height: auto; | |
| + display: inline; | |
| + margin: 0; | |
| + vertical-align: middle; | |
| + } | |
| + | |
| + #favicon .vertical { | |
| + height: auto; | |
| + line-height: 20px; | |
| } | |
| .form #formWrap, .waiting #wait, .delay #delay, #error #error { | |
| diff --git a/resources/static/dialog/css/popup.css b/resources/static/dialog/css/popup.css | |
| index 39d97a1..8a655b7 100644 | |
| --- a/resources/static/dialog/css/popup.css | |
| +++ b/resources/static/dialog/css/popup.css | |
| @@ -51,6 +51,9 @@ header { | |
| * overflow the dialog box. | |
| */ | |
| *padding: 10px 0; | |
| + /* | |
| + font-weight: bold; | |
| + */ | |
| border-bottom: 1px solid #c7c6c1; | |
| /*-ms-filter through zoom: 1 are fixes for IE6 and IE7 so they show the header | |
| @@ -71,8 +74,6 @@ header { | |
| background: url("/i/persona-logo-transparent.png") 0 0 no-repeat; | |
| text-indent: -9999px; | |
| display: inline-block; | |
| - *display: block; | |
| - zoom: 1; | |
| } | |
| footer { | |
| @@ -152,10 +153,10 @@ section > .contents { | |
| /* Fix for IE6 not displaying the unsupported dialog correctly. IE6 by | |
| * default sets the height and width of the element to 0 meaning nothing | |
| * shows up on the screen. | |
| - * Note, height is a magic number that depend on the height of the | |
| + * Note, these are magic numbers that depend on the width and height of the | |
| * dialog. The height also depends on the height of the header and footer. | |
| */ | |
| - _width: 100%; | |
| + _width: 682px; | |
| _height: 250px; | |
| } | |
| @@ -270,8 +271,7 @@ section > .contents { | |
| left: 400px; | |
| top: 0; | |
| bottom: 0; | |
| - right: 20px; /* The same as the left padding of the left hand side */ | |
| - overflow: hidden; | |
| + right: 0; | |
| z-index: 10; | |
| } | |
| @@ -283,31 +283,14 @@ section > .contents { | |
| #favicon img { | |
| display: block; | |
| - margin: 0 auto; | |
| - max-height: 128px; | |
| - max-width: 128px; | |
| -} | |
| - | |
| -#favicon h2, #favicon h3 { | |
| - white-space: nowrap; | |
| - text-overflow: ellipsis; | |
| - line-height: 1.3; /* the 1.3em is to keep y, g, j, etc from having their bottoms chopped off */ | |
| - overflow: hidden; | |
| -} | |
| - | |
| -#favicon h2 { | |
| - margin: 10px 0 0 0; | |
| -} | |
| - | |
| -#favicon h3 { | |
| - font-size: 19px; | |
| - margin-top: 0; | |
| + margin: 0 auto 10px; | |
| } | |
| #favicon .vertical { | |
| display: table-cell; | |
| text-align: center; | |
| - max-width: 0; | |
| + overflow: hidden; | |
| + text-overflow: ellipsis; | |
| } | |
| div#required_email { | |
| @@ -458,7 +441,3 @@ a.emphasize:active { | |
| min-width: 4em; | |
| } | |
| -.unsupported, .cookies_disabled { | |
| - text-align: center; | |
| -} | |
| - | |
| diff --git a/resources/static/dialog/resources/helpers.js b/resources/static/dialog/resources/helpers.js | |
| index 6422560..9f86736 100644 | |
| --- a/resources/static/dialog/resources/helpers.js | |
| +++ b/resources/static/dialog/resources/helpers.js | |
| @@ -105,7 +105,7 @@ | |
| } | |
| else { | |
| user.addressInfo(email, function(info) { | |
| - if (info.type === "primary") { | |
| + if (info.IdPEnabled) { | |
| var info = _.extend(info, { email: email, add: true }); | |
| self.publish("primary_user", info, info); | |
| complete(callback, true); | |
| diff --git a/resources/static/dialog/resources/screen_size_hacks.js b/resources/static/dialog/resources/screen_size_hacks.js | |
| index 33685cb..503d848 100644 | |
| --- a/resources/static/dialog/resources/screen_size_hacks.js | |
| +++ b/resources/static/dialog/resources/screen_size_hacks.js | |
| @@ -8,15 +8,13 @@ | |
| */ | |
| function onResize() { | |
| var selectEmailEl = $("#selectEmail"), | |
| - contentEl = $("#content"), | |
| - signInEl = $("#signIn"); | |
| + contentEl = $("#content"); | |
| selectEmailEl.css("position", "static"); | |
| if($(window).width() >= 640) { | |
| // First, remove the mobile hacks | |
| selectEmailEl.css("width", ""); | |
| contentEl.css("min-height", ""); | |
| - signInEl.css("top", ""); | |
| // This is a hack for desktop mode which centers the form vertically in | |
| // the middle of its container. We have to do this hack because we use | |
| @@ -107,11 +105,6 @@ | |
| contentEl.css("min-height", contentHeight + "px"); | |
| $("section,#signIn").css("position", ""); | |
| - | |
| - favIconHeight = $("#favicon").outerHeight(); | |
| - | |
| - // Force the top of the main content area to be below the favicon area. | |
| - signInEl.css("top", favIconHeight + "px"); | |
| } | |
| selectEmailEl.css("position", ""); | |
| diff --git a/resources/static/dialog/resources/state.js b/resources/static/dialog/resources/state.js | |
| index c56eee0..d9756d1 100644 | |
| --- a/resources/static/dialog/resources/state.js | |
| +++ b/resources/static/dialog/resources/state.js | |
| @@ -35,7 +35,7 @@ BrowserID.State = (function() { | |
| save = true; | |
| } | |
| - var func = controller[msg].bind(controller); | |
| + var func = self.controller[msg].bind(self.controller); | |
| self.gotoState(save, func, options); | |
| }, | |
| cancelState = self.popState.bind(self); | |
| @@ -44,9 +44,7 @@ BrowserID.State = (function() { | |
| self.hostname = info.hostname; | |
| self.privacyURL = info.privacyURL; | |
| self.tosURL = info.tosURL; | |
| - requiredEmail = info.requiredEmail; | |
| - | |
| - startAction(false, "doRPInfo", info); | |
| + var requiredEmail = self.requiredEmail = info.requiredEmail; | |
| if (info.email && info.type === "primary") { | |
| primaryVerificationInfo = info; | |
| @@ -71,7 +69,8 @@ BrowserID.State = (function() { | |
| }); | |
| handleState("authentication_checked", function(msg, info) { | |
| - var authenticated = info.authenticated; | |
| + var authenticated = info.authenticated, | |
| + requiredEmail = self.requiredEmail; | |
| if (requiredEmail) { | |
| self.email = requiredEmail; | |
| @@ -112,15 +111,15 @@ BrowserID.State = (function() { | |
| */ | |
| info = _.extend({ email: self.newUserEmail || self.addEmailEmail || self.resetPasswordEmail }, info); | |
| - if(self.newUserEmail) { | |
| + if (self.newUserEmail) { | |
| self.newUserEmail = null; | |
| startAction(false, "doStageUser", info); | |
| } | |
| - else if(self.addEmailEmail) { | |
| + else if (self.addEmailEmail) { | |
| self.addEmailEmail = null; | |
| startAction(false, "doStageEmail", info); | |
| } | |
| - else if(self.resetPasswordEmail) { | |
| + else if (self.resetPasswordEmail) { | |
| self.resetPasswordEmail = null; | |
| startAction(false, "doResetPassword", info); | |
| } | |
| @@ -128,7 +127,7 @@ BrowserID.State = (function() { | |
| handleState("user_staged", function(msg, info) { | |
| self.stagedEmail = info.email; | |
| - info.required = !!requiredEmail; | |
| + info.required = !!self.requiredEmail; | |
| startAction("doConfirmUser", info); | |
| }); | |
| @@ -138,10 +137,9 @@ BrowserID.State = (function() { | |
| }); | |
| handleState("primary_user", function(msg, info) { | |
| - addPrimaryUser = !!info.add; | |
| - email = info.email; | |
| - | |
| - var idInfo = storage.getEmail(email); | |
| + self.addPrimaryUser = !!info.add; | |
| + var email = self.email = info.email, | |
| + idInfo = storage.getEmail(email); | |
| if (idInfo && idInfo.cert) { | |
| redirectToState("primary_user_ready", info); | |
| } | |
| @@ -154,25 +152,28 @@ BrowserID.State = (function() { | |
| }); | |
| handleState("primary_user_provisioned", function(msg, info) { | |
| - info.add = !!addPrimaryUser; | |
| // The user is is authenticated with their IdP. Two possibilities exist | |
| // for the email - 1) create a new account or 2) add address to the | |
| // existing account. If the user is authenticated with BrowserID, #2 | |
| // will happen. If not, #1. | |
| + info = info || {}; | |
| + info.add = !!self.addPrimaryUser; | |
| startAction("doPrimaryUserProvisioned", info); | |
| }); | |
| handleState("primary_user_unauthenticated", function(msg, info) { | |
| - info = helpers.extend(info, { | |
| - add: !!addPrimaryUser, | |
| - email: email, | |
| + var requiredEmail = self.requiredEmail; | |
| + | |
| + info = helpers.extend(info || {}, { | |
| + add: !!self.addPrimaryUser, | |
| + email: self.email, | |
| requiredEmail: !!requiredEmail, | |
| privacyURL: self.privacyURL, | |
| tosURL: self.tosURL | |
| }); | |
| - if (primaryVerificationInfo) { | |
| - primaryVerificationInfo = null; | |
| + if (self.primaryVerificationInfo) { | |
| + self.primaryVerificationInfo = null; | |
| if (requiredEmail) { | |
| startAction("doCannotVerifyRequiredPrimary", info); | |
| } | |
| @@ -188,6 +189,7 @@ BrowserID.State = (function() { | |
| } | |
| else { | |
| startAction("doVerifyPrimaryUser", info); | |
| + complete(info.complete); | |
| } | |
| }); | |
| @@ -355,7 +357,7 @@ BrowserID.State = (function() { | |
| handleState("stage_email", function(msg, info) { | |
| user.passwordNeededToAddSecondaryEmail(function(passwordNeeded) { | |
| - if(passwordNeeded) { | |
| + if (passwordNeeded) { | |
| self.addEmailEmail = info.email; | |
| // cancel is disabled if the user is doing the initial password set | |
| // for a requiredEmail. | |
| @@ -372,7 +374,7 @@ BrowserID.State = (function() { | |
| handleState("email_staged", function(msg, info) { | |
| self.stagedEmail = info.email; | |
| - info.required = !!requiredEmail; | |
| + info.required = !!self.requiredEmail; | |
| startAction("doConfirmEmail", info); | |
| }); | |
| @@ -388,18 +390,17 @@ BrowserID.State = (function() { | |
| var State = BrowserID.StateMachine.extend({ | |
| start: function(options) { | |
| + var self=this; | |
| + | |
| options = options || {}; | |
| - controller = options.controller; | |
| - if (!controller) { | |
| + self.controller = options.controller; | |
| + if (!self.controller) { | |
| throw "start: controller must be specified"; | |
| } | |
| - addPrimaryUser = false; | |
| - email = requiredEmail = null; | |
| - | |
| - State.sc.start.call(this, options); | |
| - startStateMachine.call(this); | |
| + State.sc.start.call(self, options); | |
| + startStateMachine.call(self); | |
| } | |
| }); | |
| diff --git a/resources/static/dialog/start.js b/resources/static/dialog/start.js | |
| index 70c574b..543136b 100644 | |
| --- a/resources/static/dialog/start.js | |
| +++ b/resources/static/dialog/start.js | |
| @@ -42,7 +42,6 @@ | |
| moduleManager.register("xhr_delay", modules.XHRDelay); | |
| moduleManager.register("xhr_disable_form", modules.XHRDisableForm); | |
| moduleManager.register("set_password", modules.SetPassword); | |
| - moduleManager.register("rp_info", modules.RPInfo); | |
| moduleManager.start("xhr_delay"); | |
| moduleManager.start("xhr_disable_form"); | |
| diff --git a/resources/static/dialog/views/rp_info.ejs b/resources/static/dialog/views/rp_info.ejs | |
| deleted file mode 100644 | |
| index 5928a18..0000000 | |
| --- a/resources/static/dialog/views/rp_info.ejs | |
| +++ /dev/null | |
| @@ -1,21 +0,0 @@ | |
| -<% /* This Source Code Form is subject to the terms of the Mozilla Public | |
| - License, v. 2.0. If a copy of the MPL was not distributed with this | |
| - file, You can obtain one at http://mozilla.org/MPL/2.0/. */ %> | |
| - | |
| -<% if(siteLogo) { %> | |
| - <img id="rp_logo" src="<%= siteLogo %>" /> | |
| -<% } %> | |
| - | |
| - | |
| -<% if(siteName) { %> | |
| - <h2 id="rp_name"><%= siteName %></h2> | |
| -<% } %> | |
| - | |
| -<% if(hostname) { %> | |
| - <% if(siteName) { %> | |
| - <h3 id="rp_hostname"><%= hostname %></h3> | |
| - <% } else { %> | |
| - <h2 id="rp_hostname"><%= hostname %></h2> | |
| - <% } %> | |
| -<% } %> | |
| - | |
| diff --git a/resources/static/dialog/views/test_template_with_input.ejs b/resources/static/dialog/views/test_template_with_input.ejs | |
| index ff302b1..86d4ffa 100644 | |
| --- a/resources/static/dialog/views/test_template_with_input.ejs | |
| +++ b/resources/static/dialog/views/test_template_with_input.ejs | |
| @@ -2,7 +2,5 @@ | |
| - License, v. 2.0. If a copy of the MPL was not distributed with this | |
| - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> | |
| -<form> | |
| - <input id="templateInput" type="text" value="" /> | |
| -</form> | |
| +<input id="templateInput" type="text" value="" /> | |
| diff --git a/resources/static/fonts/OpenSans-Bold.eot b/resources/static/fonts/OpenSans-Bold.eot | |
| deleted file mode 100644 | |
| index 7c74b9c..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Bold.eot and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Bold.ttf b/resources/static/fonts/OpenSans-Bold.ttf | |
| deleted file mode 100644 | |
| index fd79d43..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Bold.ttf and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Bold.woff b/resources/static/fonts/OpenSans-Bold.woff | |
| deleted file mode 100644 | |
| index a8b5bc2..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Bold.woff and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-BoldItalic.eot b/resources/static/fonts/OpenSans-BoldItalic.eot | |
| deleted file mode 100644 | |
| index e1c6979..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-BoldItalic.eot and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-BoldItalic.ttf b/resources/static/fonts/OpenSans-BoldItalic.ttf | |
| deleted file mode 100644 | |
| index 9bc8009..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-BoldItalic.ttf and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-BoldItalic.woff b/resources/static/fonts/OpenSans-BoldItalic.woff | |
| deleted file mode 100644 | |
| index 6a18338..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-BoldItalic.woff and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Italic.eot b/resources/static/fonts/OpenSans-Italic.eot | |
| deleted file mode 100644 | |
| index 3fa5327..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Italic.eot and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Italic.ttf b/resources/static/fonts/OpenSans-Italic.ttf | |
| deleted file mode 100644 | |
| index c90da48..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Italic.ttf and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Italic.woff b/resources/static/fonts/OpenSans-Italic.woff | |
| deleted file mode 100644 | |
| index 039a294..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Italic.woff and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Light.eot b/resources/static/fonts/OpenSans-Light.eot | |
| deleted file mode 100644 | |
| index 3c203d8..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Light.eot and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Light.ttf b/resources/static/fonts/OpenSans-Light.ttf | |
| deleted file mode 100644 | |
| index 0d38189..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Light.ttf and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Light.woff b/resources/static/fonts/OpenSans-Light.woff | |
| deleted file mode 100644 | |
| index 8025ba6..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Light.woff and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-LightItalic.eot b/resources/static/fonts/OpenSans-LightItalic.eot | |
| deleted file mode 100644 | |
| index 6005799..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-LightItalic.eot and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-LightItalic.ttf b/resources/static/fonts/OpenSans-LightItalic.ttf | |
| deleted file mode 100644 | |
| index 68299c4..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-LightItalic.ttf and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-LightItalic.woff b/resources/static/fonts/OpenSans-LightItalic.woff | |
| deleted file mode 100644 | |
| index c1e29dc..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-LightItalic.woff and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Regular.eot b/resources/static/fonts/OpenSans-Regular.eot | |
| deleted file mode 100644 | |
| index 091cd51..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Regular.eot and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Regular.ttf b/resources/static/fonts/OpenSans-Regular.ttf | |
| deleted file mode 100644 | |
| index db43334..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Regular.ttf and /dev/null differ | |
| diff --git a/resources/static/fonts/OpenSans-Regular.woff b/resources/static/fonts/OpenSans-Regular.woff | |
| deleted file mode 100644 | |
| index 58e6cb3..0000000 | |
| Binary files a/resources/static/fonts/OpenSans-Regular.woff and /dev/null differ | |
| diff --git a/resources/static/fonts/fonts_common.css b/resources/static/fonts/fonts_common.css | |
| deleted file mode 100644 | |
| index a88cb32..0000000 | |
| --- a/resources/static/fonts/fonts_common.css | |
| +++ /dev/null | |
| @@ -1,33 +0,0 @@ | |
| -@font-face { | |
| - font-family: 'Open Sans'; | |
| - font-style: normal; | |
| - font-weight: 400; | |
| - src: url('/fonts/OpenSans-Regular.eot'); | |
| - src: local('Open Sans'), | |
| - local('OpenSans'), | |
| - url('/fonts/OpenSans-Regular.eot') format('embedded-opentype'), | |
| - url('/fonts/OpenSans-Regular.woff') format('woff'), | |
| - url('/fonts/OpenSans-Regular.ttf') format('truetype'); | |
| -} | |
| -@font-face { | |
| - font-family: 'Open Sans'; | |
| - font-style: normal; | |
| - font-weight: 300; | |
| - src: url('/fonts/OpenSans-Light.eot'); | |
| - src: local('Open Sans Light'), | |
| - local('OpenSans-Light'), | |
| - url('/fonts/OpenSans-Light.eot') format('embedded-opentype'), | |
| - url('/fonts/OpenSans-Light.woff') format('woff'), | |
| - url('/fonts/OpenSans-Light.ttf') format('truetype'); | |
| -} | |
| -@font-face { | |
| - font-family: 'Open Sans'; | |
| - font-style: normal; | |
| - font-weight: 700; | |
| - src: url('/fonts/OpenSans-Bold.eot'); | |
| - src: local('Open Sans Bold'), | |
| - local('OpenSans-Bold'), | |
| - url('/fonts/OpenSans-Bold.eot') format('embedded-opentype'), | |
| - url('/fonts/OpenSans-Bold.woff') format('woff'), | |
| - url('/fonts/OpenSans-Bold.ttf') format('truetype'); | |
| -} | |
| diff --git a/resources/static/fonts/fonts_dialog.css b/resources/static/fonts/fonts_dialog.css | |
| deleted file mode 100644 | |
| index e69de29..0000000 | |
| diff --git a/resources/static/fonts/fonts_mainsite.css b/resources/static/fonts/fonts_mainsite.css | |
| deleted file mode 100644 | |
| index a425735..0000000 | |
| --- a/resources/static/fonts/fonts_mainsite.css | |
| +++ /dev/null | |
| @@ -1,12 +0,0 @@ | |
| -@font-face { | |
| - font-family: 'Open Sans'; | |
| - font-style: italic; | |
| - font-weight: 400; | |
| - src: url('/fonts/OpenSans-Italic.eot'); | |
| - src: local('Open Sans Italic'), | |
| - local('OpenSans-Italic'), | |
| - url('/fonts/OpenSans-Italic.eot') format('embedded-opentype'), | |
| - url('/fonts/OpenSans-Italic.woff') format('woff'), | |
| - url('/fonts/OpenSans-Italic.ttf') format('truetype'); | |
| -} | |
| - | |
| diff --git a/resources/static/include_js/include.js b/resources/static/include_js/include.js | |
| index 42c8bf9..6da3e97 100644 | |
| --- a/resources/static/include_js/include.js | |
| +++ b/resources/static/include_js/include.js | |
| @@ -1112,15 +1112,11 @@ | |
| navigator.id = { | |
| request: function(options) { | |
| - if (this != navigator.id) | |
| - throw new Error("all navigator.id calls must be made on the navigator.id object"); | |
| options = options || {}; | |
| checkCompat(false); | |
| return internalRequest(options); | |
| }, | |
| watch: function(options) { | |
| - if (this != navigator.id) | |
| - throw new Error("all navigator.id calls must be made on the navigator.id object"); | |
| checkCompat(false); | |
| internalWatch(options); | |
| }, | |
| @@ -1128,8 +1124,6 @@ | |
| // The callback parameter is DEPRECATED, instead you should use the | |
| // the .onlogout observer of the .watch() api. | |
| logout: function(callback) { | |
| - if (this != navigator.id) | |
| - throw new Error("all navigator.id calls must be made on the navigator.id object"); | |
| // allocate iframe if it is not allocated | |
| _open_hidden_iframe(); | |
| // send logout message if the commChan exists | |
| diff --git a/resources/static/pages/page_helpers.js b/resources/static/pages/page_helpers.js | |
| index 8510ff2..966cf9b 100644 | |
| --- a/resources/static/pages/page_helpers.js | |
| +++ b/resources/static/pages/page_helpers.js | |
| @@ -145,6 +145,7 @@ BrowserID.PageHelpers = (function() { | |
| url: "https://browserid.org/authenticate_with_primary", | |
| // This is the relay that will be used when the IdP redirects to sign_in_complete | |
| relay_url: "https://browserid.org/relay", | |
| + // Caution: If you change this width or height, also update mozilla/browserid-bigtent | |
| window_features: "width=700,height=375", | |
| params: url | |
| }, function(error, result) { | |
| diff --git a/resources/static/pages/signup.js b/resources/static/pages/signup.js | |
| index 3fef51b..dcb59a7 100644 | |
| --- a/resources/static/pages/signup.js | |
| +++ b/resources/static/pages/signup.js | |
| @@ -108,7 +108,7 @@ BrowserID.signUp = (function() { | |
| } | |
| else { | |
| user.addressInfo(email, function(info) { | |
| - if(info.type === "primary") { | |
| + if(info.IdPEnabled) { | |
| createPrimaryUser.call(self, info, oncomplete); | |
| } | |
| else { | |
| diff --git a/resources/static/pages/start.js b/resources/static/pages/start.js | |
| index f6bb1b2..7f735a5 100644 | |
| --- a/resources/static/pages/start.js | |
| +++ b/resources/static/pages/start.js | |
| @@ -59,35 +59,20 @@ $(function() { | |
| return divHeight === 110; | |
| } | |
| - function elementHeightWithMargins(element) { | |
| - element = $(element); | |
| - var height = element.outerHeight() | |
| - + parseInt(element.css("margin-top"), 10) | |
| - + parseInt(element.css("margin-bottom"), 10); | |
| - return height; | |
| - } | |
| - | |
| - | |
| xhr.init({ time_until_delay: 10 * 1000 }); | |
| network.init(); | |
| $(".display_always,.display_auth,.display_nonauth").hide(); | |
| - | |
| $(window).bind('resize', function() { | |
| - var height = $(window).height() | |
| - // To find the height of the content, subtract the height of the | |
| - // header and footer INCLUDING any top and bottom margins they | |
| - // have. If the margins are not included, the center content | |
| - // will be too tall and a scroll bar appears. | |
| - - elementHeightWithMargins("header") | |
| - - elementHeightWithMargins("footer"); | |
| - | |
| + var height = $(window).height() - $("header").outerHeight() - $("footer").outerHeight(); | |
| $("#vAlign").css({ "height": height }); | |
| + | |
| // On the manage page, the content element sometimes does not take up the | |
| // full height of the screen, leaving the footer to float somewhere in the | |
| // middle. To compensate, force the min-height of the content so that the | |
| // footer remains at the bottom of the screen. | |
| + | |
| var paddingTop = 0, paddingBottom = 0; | |
| if(paddingAddedToMinHeight()) { | |
| diff --git a/resources/static/shared/modules/page_module.js b/resources/static/shared/modules/page_module.js | |
| index 54b5708..36aa78c 100644 | |
| --- a/resources/static/shared/modules/page_module.js | |
| +++ b/resources/static/shared/modules/page_module.js | |
| @@ -15,16 +15,6 @@ BrowserID.Modules.PageModule = (function() { | |
| cancelEvent = helpers.cancelEvent, | |
| mediator = bid.Mediator; | |
| - function onKeypress(event) { | |
| - if (event.which === 13) { | |
| - // IE8 does not trigger the submit event when hitting enter. Submit the | |
| - // form if the key press was an enter and prevent the default action so | |
| - // the form is not submitted twice. | |
| - event.preventDefault(); | |
| - this.submit(); | |
| - } | |
| - } | |
| - | |
| function onSubmit() { | |
| if (!dom.hasClass("body", "submit_disabled") && this.validate()) { | |
| this.submit(); | |
| @@ -69,7 +59,6 @@ BrowserID.Modules.PageModule = (function() { | |
| self.options = options || {}; | |
| self.bind("form", "submit", cancelEvent(onSubmit)); | |
| - self.bind("input", "keypress", onKeypress); | |
| }, | |
| stop: function() { | |
| diff --git a/resources/static/shared/network.js b/resources/static/shared/network.js | |
| index 752ce42..7430957 100644 | |
| --- a/resources/static/shared/network.js | |
| +++ b/resources/static/shared/network.js | |
| @@ -94,10 +94,6 @@ BrowserID.Network = (function() { | |
| // Any time the context info changes, we want to know about it. | |
| mediator.subscribe('context_info', onContextChange); | |
| - // BEGIN TEST API | |
| - this.cookiesEnabledOverride = config && config.cookiesEnabledOverride; | |
| - // END TEST API | |
| - | |
| clearContext(); | |
| }, | |
| @@ -513,15 +509,15 @@ BrowserID.Network = (function() { | |
| /** | |
| * Get information about an email address. Who vouches for it? | |
| - * (is it a primary or a secondary) | |
| + * (is it a primary, proxyidp or a secondary) | |
| * @method addressInfo | |
| * @param {string} email - Email address to check. | |
| * @param {function} [onComplete] - Called with an object on success, | |
| * containing these properties: | |
| - * type: <secondary|primary> | |
| + * type: <secondary|primary|proxyidp> | |
| * known: boolean, present - present if type is secondary | |
| - * auth: string - url to send users for auth - present if type is primary | |
| - * prov: string - url to embed for silent provisioning - present if type is secondary | |
| + * auth: string - url to send users for auth | |
| + * prov: string - url to embed for silent provisioning | |
| * @param {function} [onFailure] - Called on XHR failure. | |
| */ | |
| addressInfo: function(email, onComplete, onFailure) { | |
| @@ -681,12 +677,6 @@ BrowserID.Network = (function() { | |
| enabled = false; | |
| } | |
| - // BEGIN TESTING API | |
| - if (typeof Network.cookiesEnabledOverride === "boolean") { | |
| - enabled = Network.cookiesEnabledOverride; | |
| - } | |
| - // END TESTING API | |
| - | |
| complete(onComplete, enabled); | |
| }, onFailure); | |
| }, | |
| diff --git a/resources/static/shared/user.js b/resources/static/shared/user.js | |
| index 0695c83..e892ad4 100644 | |
| --- a/resources/static/shared/user.js | |
| +++ b/resources/static/shared/user.js | |
| @@ -679,7 +679,6 @@ BrowserID.User = (function() { | |
| if(cookiesEnabled) { | |
| network.checkAuth(function(authenticated) { | |
| setAuthenticationStatus(authenticated); | |
| - if (!authenticated) authenticated = false; | |
| complete(onComplete, authenticated); | |
| }, onFailure); | |
| } | |
| @@ -693,13 +692,12 @@ BrowserID.User = (function() { | |
| * Check whether the current user is authenticated. If authenticated, sync | |
| * identities. | |
| * @method checkAuthenticationAndSync | |
| - * @param {function} [onComplete] - Called on sync completion with one | |
| - * boolean parameter, authenticated. authenticated will be true if user | |
| - * is authenticated, false otw. | |
| + * @param {function} [onComplete] - Called on sync completion. | |
| * @param {function} [onFailure] - Called on error. | |
| */ | |
| checkAuthenticationAndSync: function(onComplete, onFailure) { | |
| - User.checkAuthentication(function(authenticated) { | |
| + network.checkAuth(function(authenticated) { | |
| + setAuthenticationStatus(authenticated); | |
| if (authenticated) { | |
| User.syncEmails(function() { | |
| onComplete && onComplete(authenticated); | |
| @@ -750,20 +748,19 @@ BrowserID.User = (function() { | |
| /** | |
| * Get information about an email address. Who vouches for it? | |
| - * (is it a primary or a secondary) | |
| + * (is it a primary, proxyidp or a secondary) | |
| * @method addressInfo | |
| * @param {string} email - Email address to check. | |
| * @param {function} [onComplete] - Called with an object on success, | |
| * containing these properties: | |
| - * type: <secondary|primary> | |
| + * type: <secondary|primary|proxyidp> | |
| * known: boolean, present if type is secondary. True if email | |
| * address is registered with BrowserID. | |
| - * authed: boolean, present if type is primary - whether the user | |
| + * authed: boolean, present if type is primary or proxyidp - whether the user | |
| * is authenticated to the IdP as this user. | |
| * auth: string - url to send users for auth - present if type is | |
| - * primary. | |
| - * prov: string - url to embed for silent provisioning - present | |
| - * if type is secondary. | |
| + * primary or proxyidp. | |
| + * prov: string - url to embed for silent provisioning | |
| * @param {function} [onFailure] - Called on XHR failure. | |
| */ | |
| addressInfo: function(email, onComplete, onFailure) { | |
| @@ -780,7 +777,12 @@ BrowserID.User = (function() { | |
| else { | |
| network.addressInfo(email, function(info) { | |
| info.email = email; | |
| - if(info.type === "primary") { | |
| + // Note - even though addressInfo may return an email of type | |
| + // primary, proxyidp or secondary, emails are only stored in | |
| + // localStorage as primary or secondary. proxyidp is stored as primary. | |
| + if(info.type === "primary" || info.type === "proxyidp") { | |
| + info.IdPEnabled = true; | |
| + | |
| User.isUserAuthenticatedToPrimary(email, info, function(authed) { | |
| info.authed = authed; | |
| complete(info); | |
| @@ -1055,7 +1057,7 @@ BrowserID.User = (function() { | |
| * Get an assertion for the current domain if the user is signed into it | |
| * @method getPersistentSigninAssertion | |
| * @param {function} onComplete - called on completion. Called with an | |
| - * an email and assertion if successful, null otw. | |
| + * assertion if successful, null otw. | |
| * @param {function} onFailure - called on XHR failure. | |
| */ | |
| getSilentAssertion: function(siteSpecifiedEmail, onComplete, onFailure) { | |
| @@ -1067,7 +1069,7 @@ BrowserID.User = (function() { | |
| // so if we rely on localstorage only and check authentication status | |
| // only when we know a network request will be required, we very well | |
| // might have fewer race conditions and do fewer network requests. | |
| - User.checkAuthenticationAndSync(function(authenticated) { | |
| + User.checkAuthentication(function(authenticated) { | |
| if (authenticated) { | |
| var loggedInEmail = storage.getLoggedIn(origin); | |
| if (loggedInEmail !== siteSpecifiedEmail) { | |
| diff --git a/resources/static/shared/wait-messages.js b/resources/static/shared/wait-messages.js | |
| index 00a64aa..63ec7aa 100644 | |
| --- a/resources/static/shared/wait-messages.js | |
| +++ b/resources/static/shared/wait-messages.js | |
| @@ -15,6 +15,11 @@ BrowserID.Wait = (function(){ | |
| message: gettext("Please wait a few seconds while we sign you into the site.") | |
| }, | |
| + redirectToIdP: { | |
| + title: gettext("You are being redirected to your email provider"), | |
| + message: "" | |
| + }, | |
| + | |
| slowXHR: { | |
| title: gettext("We are sorry, this request is taking a LOOONG time."), | |
| message: gettext("This message will go away when the request completes (hopefully soon). If you wait too long, close this window and try again."), | |
| diff --git a/resources/static/test/cases/controllers/actions.js b/resources/static/test/cases/controllers/actions.js | |
| index 8d90d69..4118557 100644 | |
| --- a/resources/static/test/cases/controllers/actions.js | |
| +++ b/resources/static/test/cases/controllers/actions.js | |
| @@ -8,6 +8,7 @@ | |
| var bid = BrowserID, | |
| user = bid.User, | |
| + xhr = bid.Mocks.xhr, | |
| controller, | |
| el, | |
| testHelpers = bid.TestHelpers, | |
| @@ -53,8 +54,7 @@ | |
| }); | |
| asyncTest("doVerifyPrimaryUser - start the verify_primary_user service", function() { | |
| - testActionStartsModule("doVerifyPrimaryUser", {}, | |
| - "verify_primary_user"); | |
| + testActionStartsModule("doVerifyPrimaryUser", {}, "verify_primary_user"); | |
| }); | |
| asyncTest("doCannotVerifyRequiredPrimary - show the error screen", function() { | |
| diff --git a/resources/static/test/cases/controllers/authenticate.js b/resources/static/test/cases/controllers/authenticate.js | |
| index 6827fce..b22e5c5 100644 | |
| --- a/resources/static/test/cases/controllers/authenticate.js | |
| +++ b/resources/static/test/cases/controllers/authenticate.js | |
| @@ -117,42 +117,30 @@ | |
| controller.checkEmail(); | |
| }); | |
| - asyncTest("clear password if user changes email address", function() { | |
| - xhr.useResult("known_secondary"); | |
| - $("#email").val("registered@testuser.com"); | |
| - | |
| - var enterPasswordCount = 0; | |
| - mediator.subscribe("enter_password", function() { | |
| - // The first time the password is shown, change the email address. The | |
| - // second time the password is shown, make sure the password was cleared. | |
| - | |
| - if(enterPasswordCount == 0) { | |
| - // simulate the user changing the email address. This should clear the | |
| - // password. | |
| - $("#password").val("password"); | |
| - $("#email").val("testuser@testuser.com"); | |
| - $("#email").keyup(); | |
| - controller.checkEmail(); | |
| - } | |
| - else { | |
| - equal($("#password").val(), "", "password field was cleared"); | |
| - start(); | |
| - } | |
| + asyncTest("checkEmail with email that has normal IdP support, expect 'primary_user' message", function() { | |
| + $("#email").val("unregistered@testuser.com"); | |
| + xhr.useResult("primary"); | |
| - enterPasswordCount++; | |
| + register("primary_user", function(msg, info) { | |
| + equal(info.email, "unregistered@testuser.com", "email correctly passed"); | |
| + equal(info.auth, "https://auth_url", "IdP authentication URL passed"); | |
| + equal(info.prov, "https://prov_url", "IdP provisioning URL passed"); | |
| + equal(info.type, "primary", "correct IdP type set"); | |
| + start(); | |
| }); | |
| controller.checkEmail(); | |
| }); | |
| - asyncTest("checkEmail with email that has IdP support - 'primary_user' message", function() { | |
| + asyncTest("checkEmail with email that has proxy IdP support, expect 'primary_user' message", function() { | |
| $("#email").val("unregistered@testuser.com"); | |
| - xhr.useResult("primary"); | |
| + xhr.useResult("proxyidp"); | |
| register("primary_user", function(msg, info) { | |
| equal(info.email, "unregistered@testuser.com", "email correctly passed"); | |
| equal(info.auth, "https://auth_url", "IdP authentication URL passed"); | |
| equal(info.prov, "https://prov_url", "IdP provisioning URL passed"); | |
| + equal(info.type, "proxyidp", "correct IdP type set"); | |
| start(); | |
| }); | |
| diff --git a/resources/static/test/cases/controllers/dialog.js b/resources/static/test/cases/controllers/dialog.js | |
| index ed8aba8..4190cd9 100644 | |
| --- a/resources/static/test/cases/controllers/dialog.js | |
| +++ b/resources/static/test/cases/controllers/dialog.js | |
| @@ -469,123 +469,5 @@ | |
| }); | |
| }); | |
| - asyncTest("get with relative siteLogo - not allowed", function() { | |
| - createController({ | |
| - ready: function() { | |
| - mediator.subscribe("start", function(msg, info) { | |
| - ok(false, "start should not have been called"); | |
| - }); | |
| - | |
| - var retval = controller.get(HTTP_TEST_DOMAIN, { | |
| - siteLogo: "logo.png", | |
| - }); | |
| - | |
| - equal(retval, "must be an absolute path: (logo.png)", "expected error"); | |
| - testErrorVisible(); | |
| - start(); | |
| - } | |
| - }); | |
| - }); | |
| - | |
| - asyncTest("get with javascript: siteLogo - not allowed", function() { | |
| - createController({ | |
| - ready: function() { | |
| - mediator.subscribe("start", function(msg, info) { | |
| - ok(false, "start should not have been called"); | |
| - }); | |
| - | |
| - var retval = controller.get(HTTP_TEST_DOMAIN, { | |
| - siteLogo: "javascript:alert('xss')", | |
| - }); | |
| - | |
| - equal(retval, "must be an absolute path: (javascript:alert('xss'))", "expected error"); | |
| - testErrorVisible(); | |
| - start(); | |
| - } | |
| - }); | |
| - }); | |
| - | |
| - asyncTest("get with data-uri: siteLogo - not allowed", function() { | |
| - createController({ | |
| - ready: function() { | |
| - mediator.subscribe("start", function(msg, info) { | |
| - ok(false, "start should not have been called"); | |
| - }); | |
| - | |
| - var retval = controller.get(HTTP_TEST_DOMAIN, { | |
| - siteLogo: "data:image/png,FAKEDATA", | |
| - }); | |
| - | |
| - equal(retval, "must be an absolute path: (data:image/png,FAKEDATA)", "expected error"); | |
| - testErrorVisible(); | |
| - start(); | |
| - } | |
| - }); | |
| - }); | |
| - | |
| - asyncTest("get with http: siteLogo - not allowed", function() { | |
| - createController({ | |
| - ready: function() { | |
| - mediator.subscribe("start", function(msg, info) { | |
| - ok(false, "start should not have been called"); | |
| - }); | |
| - | |
| - var retval = controller.get(HTTP_TEST_DOMAIN, { | |
| - siteLogo: HTTP_TEST_DOMAIN + "://logo.png", | |
| - }); | |
| - | |
| - equal(retval, "must be an absolute path: (" + HTTP_TEST_DOMAIN + "://logo.png)", "expected error"); | |
| - testErrorVisible(); | |
| - start(); | |
| - } | |
| - }); | |
| - }); | |
| - | |
| - asyncTest("get with https: siteLogo - not allowed", function() { | |
| - createController({ | |
| - ready: function() { | |
| - mediator.subscribe("start", function(msg, info) { | |
| - ok(false, "start should not have been called"); | |
| - }); | |
| - | |
| - var retval = controller.get(HTTP_TEST_DOMAIN, { | |
| - siteLogo: HTTPS_TEST_DOMAIN + "://logo.png", | |
| - }); | |
| - | |
| - equal(retval, "must be an absolute path: (" + HTTPS_TEST_DOMAIN + "://logo.png)", "expected error"); | |
| - testErrorVisible(); | |
| - start(); | |
| - } | |
| - }); | |
| - }); | |
| - | |
| - asyncTest("get with absolute path - allowed URL but it must be properly escaped", function() { | |
| - createController({ | |
| - ready: function() { | |
| - var startInfo; | |
| - mediator.subscribe("start", function(msg, info) { | |
| - startInfo = info; | |
| - }); | |
| - | |
| - var siteLogo = '/i/card.png" onerror="alert(\'xss\')" <script>alert(\'more xss\')</script>'; | |
| - var retval = controller.get(HTTP_TEST_DOMAIN, { | |
| - siteLogo: siteLogo | |
| - }); | |
| - | |
| - start(); | |
| - | |
| - testHelpers.testObjectValuesEqual(startInfo, { | |
| - siteLogo: encodeURI(HTTP_TEST_DOMAIN + siteLogo) | |
| - }); | |
| - equal(typeof retval, "undefined", "no error expected"); | |
| - testErrorNotVisible(); | |
| - start(); | |
| - } | |
| - }); | |
| - | |
| - }); | |
| - | |
| - | |
| - | |
| }()); | |
| diff --git a/resources/static/test/cases/controllers/rp_info.js b/resources/static/test/cases/controllers/rp_info.js | |
| deleted file mode 100644 | |
| index 0b89868..0000000 | |
| --- a/resources/static/test/cases/controllers/rp_info.js | |
| +++ /dev/null | |
| @@ -1,88 +0,0 @@ | |
| -/*jshint browsers:true, forin: true, laxbreak: true */ | |
| -/*global test: true, start: true, stop: true, module: true, ok: true, equal: true, BrowserID:true */ | |
| -/* This Source Code Form is subject to the terms of the Mozilla Public | |
| - * License, v. 2.0. If a copy of the MPL was not distributed with this | |
| - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | |
| -(function() { | |
| - "use strict"; | |
| - | |
| - var controller, | |
| - bid = BrowserID, | |
| - user = bid.User, | |
| - testHelpers = bid.TestHelpers, | |
| - register = bid.TestHelpers.register, | |
| - WindowMock = bid.Mocks.WindowMock, | |
| - RP_HOSTNAME = "hostname.org", | |
| - RP_NAME = "RP Name", | |
| - RP_HTTPS_LOGO = "https://en.gravatar.com/userimage/6966791/c4feac761b8544cce13e0406f36230aa.jpg"; | |
| - | |
| - module("controllers/rp_info", { | |
| - setup: testHelpers.setup, | |
| - | |
| - teardown: function() { | |
| - if (controller) { | |
| - try { | |
| - controller.destroy(); | |
| - controller = null; | |
| - } catch(e) { | |
| - // could already be destroyed from the close | |
| - } | |
| - } | |
| - window.scriptRun = null; | |
| - delete window.scriptRun; | |
| - testHelpers.teardown(); | |
| - } | |
| - }); | |
| - | |
| - | |
| - function createController(options) { | |
| - options = _.extend({ hostname: RP_HOSTNAME }, options); | |
| - | |
| - controller = bid.Modules.RPInfo.create(); | |
| - controller.start(options || {}); | |
| - } | |
| - | |
| - test("neither siteName nor logo specified - show rp_hostname only", function() { | |
| - createController(); | |
| - equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in"); | |
| - ok(!$("#rp_name").html(), "rp_name empty"); | |
| - ok(!$("#rp_logo").attr("src"), "rp logo not shown"); | |
| - }); | |
| - | |
| - test("siteName only specified - show specified siteName and rp_hostname", function() { | |
| - createController({ | |
| - siteName: RP_NAME, | |
| - }); | |
| - | |
| - equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in"); | |
| - equal($("#rp_name").html(), RP_NAME, "rp_name filled in"); | |
| - ok(!$("#rp_logo").attr("src"), "rp logo not shown"); | |
| - }); | |
| - | |
| - test("siteLogos are allowed", function() { | |
| - var docMock = new WindowMock().document; | |
| - docMock.location.protocol = "http:"; | |
| - | |
| - createController({ | |
| - document: docMock, | |
| - siteLogo: RP_HTTPS_LOGO | |
| - }); | |
| - | |
| - equal($("#rp_logo").attr("src"), RP_HTTPS_LOGO, "rp logo shown"); | |
| - equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in"); | |
| - ok(!$("#rp_name").html(), "rp_name empty"); | |
| - }); | |
| - | |
| - test("both siteName and siteLogo specified - show siteName, siteLogo and rp_hostname", function() { | |
| - createController({ | |
| - siteName: RP_NAME, | |
| - siteLogo: RP_HTTPS_LOGO | |
| - }); | |
| - | |
| - equal($("#rp_hostname").html(), RP_HOSTNAME, "rp_hostname filled in"); | |
| - equal($("#rp_name").html(), RP_NAME, "rp_name filled in"); | |
| - equal($("#rp_logo").attr("src"), RP_HTTPS_LOGO, "rp logo shown"); | |
| - }); | |
| - | |
| -}()); | |
| - | |
| diff --git a/resources/static/test/cases/controllers/verify_primary_user.js b/resources/static/test/cases/controllers/verify_primary_user.js | |
| index 5203792..d5a0791 100644 | |
| Binary files a/resources/static/test/cases/controllers/verify_primary_user.js and b/resources/static/test/cases/controllers/verify_primary_user.js differ | |
| diff --git a/resources/static/test/cases/include.js b/resources/static/test/cases/include.js | |
| index 0cadc3e..00bd855 100644 | |
| --- a/resources/static/test/cases/include.js | |
| +++ b/resources/static/test/cases/include.js | |
| @@ -24,24 +24,5 @@ | |
| }); | |
| }); | |
| - test("DOM calls fails when unbound from navigator.id", function() { | |
| - _.each([ | |
| - "watch", | |
| - "request", | |
| - "logout" | |
| - ], function(item, index) { | |
| - var the_func = navigator.id[item]; | |
| - | |
| - var fails = false; | |
| - try { | |
| - the_func(); | |
| - } catch (x) { | |
| - fails = true; | |
| - } | |
| - | |
| - ok(fails); | |
| - }); | |
| - }); | |
| - | |
| }()); | |
| diff --git a/resources/static/test/cases/resources/state.js b/resources/static/test/cases/resources/state.js | |
| index 1addb96..80fe489 100644 | |
| --- a/resources/static/test/cases/resources/state.js | |
| +++ b/resources/static/test/cases/resources/state.js | |
| @@ -34,6 +34,15 @@ | |
| } | |
| } | |
| + function testActionCalled(action, requiredData) { | |
| + ok(actions.called[action], action + " called"); | |
| + | |
| + for(var key in requiredData) { | |
| + equal(actions.info[action][key], requiredData[key], key + " value set correctly"); | |
| + } | |
| + | |
| + } | |
| + | |
| function createMachine() { | |
| machine = bid.State.create(); | |
| actions = new ActionsMock(); | |
| @@ -179,9 +188,11 @@ | |
| mediator.publish("primary_user", { email: TEST_EMAIL }); | |
| }); | |
| - test("primary_user with unprovisioned primary user - call doProvisionPrimaryUser", function() { | |
| + test("primary_user with unprovisioned user - call doProvisionPrimaryUser", function() { | |
| mediator.publish("primary_user", { email: TEST_EMAIL }); | |
| - ok(actions.called.doProvisionPrimaryUser, "doPrimaryUserProvisioned called"); | |
| + testActionCalled("doProvisionPrimaryUser", { | |
| + email: TEST_EMAIL | |
| + }); | |
| }); | |
| test("primary_user_provisioned - call doEmailChosen", function() { | |
| @@ -189,10 +200,13 @@ | |
| ok(actions.called.doPrimaryUserProvisioned, "doPrimaryUserProvisioned called"); | |
| }); | |
| - test("primary_user_unauthenticated before verification - call doVerifyPrimaryUser", function() { | |
| + asyncTest("primary_user_unauthenticated before IdP authentication - call doVerifyPrimaryUser", function() { | |
| mediator.publish("start"); | |
| - mediator.publish("primary_user_unauthenticated"); | |
| - ok(actions.called.doVerifyPrimaryUser, "doVerifyPrimaryUser called"); | |
| + mediator.publish("primary_user", { email: TEST_EMAIL }); | |
| + mediator.publish("primary_user_unauthenticated", { complete: function() { | |
| + testActionCalled("doVerifyPrimaryUser", { email: TEST_EMAIL }); | |
| + start(); | |
| + }}); | |
| }); | |
| test("primary_user_unauthenticated after required email - call doCannotVerifyRequiredPrimary", function() { | |
| diff --git a/resources/static/test/cases/shared/modules/page_module.js b/resources/static/test/cases/shared/modules/page_module.js | |
| index 0544d20..7baac24 100644 | |
| --- a/resources/static/test/cases/shared/modules/page_module.js | |
| +++ b/resources/static/test/cases/shared/modules/page_module.js | |
| @@ -198,35 +198,6 @@ | |
| $("body").removeClass("submit_disabled"); | |
| controller.onSubmit(); | |
| equal(submitCalled, true, "submit permitted to complete"); | |
| - }); | |
| - | |
| - test("form is submitted once 'enter keypress' event", function() { | |
| - createController(); | |
| - controller.renderDialog("test_template_with_input", { | |
| - title: "Test title", | |
| - message: "Test message" | |
| - }); | |
| - | |
| - controller.start(); | |
| - | |
| - | |
| - var submitCalled = 0; | |
| - controller.submit = function() { | |
| - submitCalled++; | |
| - }; | |
| - | |
| - // synthesize the entire series of key* events so we replicate the behavior | |
| - // of keyboard interaction. | |
| - var e = jQuery.Event("keydown", { keyCode: 13, which: 13 }); | |
| - $("#templateInput").trigger(e); | |
| - | |
| - var e = jQuery.Event("keyup", { keyCode: 13, which: 13 }); | |
| - $("#templateInput").trigger(e); | |
| - | |
| - var e = jQuery.Event("keypress", { keyCode: 13, which: 13 }); | |
| - $("#templateInput").trigger(e); | |
| - | |
| - equal(submitCalled, 1, "submit called a single time"); | |
| - }); | |
| + }) | |
| }()); | |
| diff --git a/resources/static/test/cases/shared/network.js b/resources/static/test/cases/shared/network.js | |
| index 4ce7680..27a219e 100644 | |
| --- a/resources/static/test/cases/shared/network.js | |
| +++ b/resources/static/test/cases/shared/network.js | |
| @@ -20,6 +20,7 @@ | |
| module("shared/network", { | |
| setup: function() { | |
| testHelpers.setup(); | |
| + network.init(); | |
| }, | |
| teardown: function() { | |
| testHelpers.teardown(); | |
| @@ -563,6 +564,19 @@ | |
| equal(data.type, "primary", "type is primary"); | |
| ok("auth" in data, "auth field exists"); | |
| ok("prov" in data, "prov field exists"); | |
| + equal(data.type, "primary", "type correctly set to primary"); | |
| + start(); | |
| + }, testHelpers.unexpectedXHRFailure); | |
| + }); | |
| + | |
| + asyncTest("addressInfo with proxyidp email", function() { | |
| + transport.useResult("proxyidp"); | |
| + | |
| + network.addressInfo(TEST_EMAIL, function onComplete(data) { | |
| + equal(data.type, "proxyidp", "type is proxyidp"); | |
| + ok("auth" in data, "auth field exists"); | |
| + ok("prov" in data, "prov field exists"); | |
| + equal(data.type, "proxyidp", "type correctly set to proxyidp"); | |
| start(); | |
| }, testHelpers.unexpectedXHRFailure); | |
| }); | |
| @@ -592,28 +606,12 @@ | |
| }); | |
| asyncTest("cookiesEnabled with cookies enabled - return true status", function() { | |
| - network.init({ cookiesEnabledOverride: true }); | |
| network.cookiesEnabled(function(status) { | |
| equal(status, true, "cookies are enabled, correct status"); | |
| start(); | |
| }, testHelpers.unexpectedXHRFailure); | |
| }); | |
| - asyncTest("cookiesEnabled with cookies disabled - return true status", function() { | |
| - network.init({ cookiesEnabledOverride: false }); | |
| - network.cookiesEnabled(function(status) { | |
| - equal(status, false, "cookies are disabled, correct status"); | |
| - start(); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }); | |
| - | |
| - asyncTest("cookiesEnabled with browser defined cookie status - wait and see", function() { | |
| - network.cookiesEnabled(function(status) { | |
| - equal(status, true, "hopefully cookies are enabled, correct status"); | |
| - start(); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }); | |
| - | |
| asyncTest("cookiesEnabled with onComplete exception thrown - should not call onComplete a second time", function() { | |
| // Since we are manually throwing an exception, it must be caught | |
| // below. | |
| diff --git a/resources/static/test/cases/shared/user.js b/resources/static/test/cases/shared/user.js | |
| index a07a5fe..a287d9b 100644 | |
| --- a/resources/static/test/cases/shared/user.js | |
| +++ b/resources/static/test/cases/shared/user.js | |
| @@ -14,8 +14,6 @@ var jwcrypto = require("./lib/jwcrypto"); | |
| testHelpers = bid.TestHelpers, | |
| testOrigin = testHelpers.testOrigin, | |
| failureCheck = testHelpers.failureCheck, | |
| - testUndefined = testHelpers.testUndefined, | |
| - testNotUndefined = testHelpers.testNotUndefined, | |
| provisioning = bid.Mocks.Provisioning, | |
| TEST_EMAIL = "testuser@testuser.com"; | |
| @@ -514,40 +512,24 @@ var jwcrypto = require("./lib/jwcrypto"); | |
| asyncTest("checkAuthentication with valid authentication", function() { | |
| - storage.addSecondaryEmail(TEST_EMAIL); | |
| xhr.setContextInfo("auth_level", "primary"); | |
| - | |
| lib.checkAuthentication(function(authenticated) { | |
| equal(authenticated, "primary", "We are authenticated!"); | |
| - testNotUndefined(storage.getEmail(TEST_EMAIL), "localStorage is not cleared"); | |
| start(); | |
| }); | |
| }); | |
| - asyncTest("checkAuthentication with invalid authentication - localStorage cleared", function() { | |
| - storage.addSecondaryEmail(TEST_EMAIL); | |
| + asyncTest("checkAuthentication with invalid authentication", function() { | |
| xhr.setContextInfo("auth_level", undefined); | |
| - | |
| lib.checkAuthentication(function(authenticated) { | |
| - equal(authenticated, false, "We are not authenticated!"); | |
| - testUndefined(storage.getEmail(TEST_EMAIL), "localStorage was cleared"); | |
| + equal(authenticated, undefined, "We are not authenticated!"); | |
| start(); | |
| }); | |
| }); | |
| - asyncTest("checkAuthentication with cookies disabled - localStorage is not cleared, user can enable their cookies and try again", function() { | |
| - storage.addSecondaryEmail(TEST_EMAIL); | |
| - network.init({ cookiesEnabledOverride: false }); | |
| - | |
| - lib.checkAuthentication(function(authenticated) { | |
| - equal(authenticated, false, "We are not authenticated!"); | |
| - testNotUndefined(storage.getEmail(TEST_EMAIL), "localStorage is not cleared"); | |
| - start(); | |
| - }); | |
| - }); | |
| asyncTest("checkAuthentication with XHR failure", function() { | |
| xhr.useResult("contextAjaxError"); | |
| @@ -566,24 +548,12 @@ var jwcrypto = require("./lib/jwcrypto"); | |
| }); | |
| - asyncTest("checkAuthenticationAndSync with invalid authentication - localStorage cleared", function() { | |
| - storage.addSecondaryEmail(TEST_EMAIL); | |
| - xhr.setContextInfo("auth_level", undefined); | |
| - | |
| - lib.checkAuthenticationAndSync(function onComplete(authenticated) { | |
| - equal(authenticated, false, "We are not authenticated!"); | |
| - testUndefined(storage.getEmail(TEST_EMAIL), "localStorage was cleared"); | |
| - start(); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }); | |
| - asyncTest("checkAuthenticationAndSync with cookies disabled - localStorage not cleared, user can enable their cookies and try again", function() { | |
| - storage.addSecondaryEmail(TEST_EMAIL); | |
| - network.init({ cookiesEnabledOverride: false }); | |
| + asyncTest("checkAuthenticationAndSync with invalid authentication", function() { | |
| + xhr.setContextInfo("auth_level", undefined); | |
| lib.checkAuthenticationAndSync(function onComplete(authenticated) { | |
| - equal(authenticated, false, "We are not authenticated!"); | |
| - testNotUndefined(storage.getEmail(TEST_EMAIL), "localStorage is not cleared"); | |
| + equal(authenticated, undefined, "We are not authenticated!"); | |
| start(); | |
| }, testHelpers.unexpectedXHRFailure); | |
| }); | |
| @@ -972,34 +942,6 @@ var jwcrypto = require("./lib/jwcrypto"); | |
| }); | |
| - asyncTest("getSilentAssertion with logged in user, emails match - user already logged in, call callback with email and null assertion", function() { | |
| - var LOGGED_IN_EMAIL = TEST_EMAIL; | |
| - xhr.setContextInfo("auth_level", "password"); | |
| - | |
| - lib.syncEmailKeypair(LOGGED_IN_EMAIL, function() { | |
| - storage.setLoggedIn(lib.getOrigin(), LOGGED_IN_EMAIL); | |
| - lib.getSilentAssertion(LOGGED_IN_EMAIL, function(email, assertion) { | |
| - equal(email, LOGGED_IN_EMAIL, "correct email"); | |
| - strictEqual(assertion, null, "correct assertion"); | |
| - start(); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }); | |
| - | |
| - asyncTest("getSilentAssertion with logged in user, request email different from logged in email, valid cert for logged in email - logged in user needs assertion - call callback with email and assertion.", function() { | |
| - xhr.setContextInfo("auth_level", "password"); | |
| - var LOGGED_IN_EMAIL = TEST_EMAIL; | |
| - var REQUESTED_EMAIL = "requested@testuser.com"; | |
| - | |
| - lib.syncEmailKeypair(LOGGED_IN_EMAIL, function() { | |
| - storage.setLoggedIn(lib.getOrigin(), LOGGED_IN_EMAIL); | |
| - lib.getSilentAssertion(REQUESTED_EMAIL, function(email, assertion) { | |
| - equal(email, LOGGED_IN_EMAIL, "correct email"); | |
| - testAssertion(assertion, start); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }, testHelpers.unexpectedXHRFailure); | |
| - }); | |
| - | |
| asyncTest("logoutUser", function(onSuccess) { | |
| lib.authenticate(TEST_EMAIL, "testuser", function(authenticated) { | |
| lib.syncEmails(function() { | |
| @@ -1091,13 +1033,14 @@ var jwcrypto = require("./lib/jwcrypto"); | |
| ); | |
| }); | |
| - asyncTest("addressInfo with primary authenticated user", function() { | |
| + asyncTest("addressInfo with normal IdP authenticated user", function() { | |
| xhr.useResult("primary"); | |
| provisioning.setStatus(provisioning.AUTHENTICATED); | |
| lib.addressInfo( | |
| "registered@testuser.com", | |
| function(info) { | |
| equal(info.type, "primary", "correct type"); | |
| + equal(info.IdPEnabled, true, "a primary user is IdPEnabled"); | |
| equal(info.email, "registered@testuser.com", "correct email"); | |
| equal(info.authed, true, "user is authenticated with IdP"); | |
| start(); | |
| @@ -1121,6 +1064,37 @@ var jwcrypto = require("./lib/jwcrypto"); | |
| ); | |
| }); | |
| + asyncTest("addressInfo with proxy IdP authenticated user", function() { | |
| + xhr.useResult("proxyidp"); | |
| + provisioning.setStatus(provisioning.AUTHENTICATED); | |
| + lib.addressInfo( | |
| + "registered@testuser.com", | |
| + function(info) { | |
| + equal(info.type, "proxyidp", "correct type"); | |
| + equal(info.IdPEnabled, true, "a primary user is IdPEnabled"); | |
| + equal(info.email, "registered@testuser.com", "correct email"); | |
| + equal(info.authed, true, "user is authenticated with IdP"); | |
| + start(); | |
| + }, | |
| + testHelpers.unexpectedFailure | |
| + ); | |
| + }); | |
| + | |
| + asyncTest("addressInfo with proxy IdP unauthenticated user", function() { | |
| + xhr.useResult("proxyidp"); | |
| + provisioning.setStatus(provisioning.NOT_AUTHENTICATED); | |
| + lib.addressInfo( | |
| + "registered@testuser.com", | |
| + function(info) { | |
| + equal(info.type, "proxyidp", "correct type"); | |
| + equal(info.email, "registered@testuser.com", "correct email"); | |
| + equal(info.authed, false, "user is not authenticated with IdP"); | |
| + start(); | |
| + }, | |
| + testHelpers.unexpectedFailure | |
| + ); | |
| + }); | |
| + | |
| asyncTest("hasSecondary returns false if the user has 0 secondary email address", function() { | |
| lib.hasSecondary(function(hasSecondary) { | |
| equal(hasSecondary, false, "hasSecondary is false"); | |
| diff --git a/resources/static/test/mocks/window.js b/resources/static/test/mocks/window.js | |
| index 5105142..d000f3f 100644 | |
| --- a/resources/static/test/mocks/window.js | |
| +++ b/resources/static/test/mocks/window.js | |
| @@ -19,7 +19,14 @@ BrowserID.Mocks.WindowMock = (function() { | |
| WindowMock.prototype = { | |
| open: function(url, name, options) { | |
| this.open_url = url; | |
| - } | |
| + }, | |
| + | |
| + resizeTo: function(width, height) { | |
| + this.width = width; | |
| + this.height = height; | |
| + }, | |
| + width: 0, | |
| + height: 0 | |
| }; | |
| return WindowMock; | |
| diff --git a/resources/static/test/mocks/xhr.js b/resources/static/test/mocks/xhr.js | |
| index e2d5602..a206f99 100644 | |
| --- a/resources/static/test/mocks/xhr.js | |
| +++ b/resources/static/test/mocks/xhr.js | |
| @@ -15,7 +15,9 @@ BrowserID.Mocks.xhr = (function() { | |
| code_version: "ABC123", | |
| random_seed: "H+ZgKuhjVckv/H4i0Qvj/JGJEGDVOXSIS5RCOjY9/Bo=", | |
| data_sample_rate: 1 | |
| - }; | |
| + }, | |
| + AUTH_URL = "https://auth_url", | |
| + PROV_URL = "https://prov_url"; | |
| // this cert is meaningless, but it has the right format | |
| var random_cert = "eyJhbGciOiJSUzEyOCJ9.eyJpc3MiOiJpc3N1ZXIuY29tIiwiZXhwIjoxMzE2Njk1MzY3NzA3LCJwdWJsaWMta2V5Ijp7ImFsZ29yaXRobSI6IlJTIiwibiI6IjU2MDYzMDI4MDcwNDMyOTgyMzIyMDg3NDE4MTc2ODc2NzQ4MDcyMDM1NDgyODk4MzM0ODExMzY4NDA4NTI1NTk2MTk4MjUyNTE5MjY3MTA4MTMyNjA0MTk4MDA0NzkyODQ5MDc3ODY4OTUxOTA2MTcwODEyNTQwNzEzOTgyOTU0NjUzODEwNTM5OTQ5Mzg0NzEyNzczMzkwMjAwNzkxOTQ5NTY1OTAzNDM5NTIxNDI0OTA5NTc2ODMyNDE4ODkwODE5MjA0MzU0NzI5MjE3MjA3MzYwMTA1OTA2MDM5MDIzMjk5NTYxMzc0MDk4OTQyNzg5OTk2NzgwMTAyMDczMDcxNzYwODUyODQxMDY4OTg5ODYwNDAzNDMxNzM3NDgwMTgyNzI1ODUzODk5NzMzNzA2MDY5IiwiZSI6IjY1NTM3In0sInByaW5jaXBhbCI6eyJlbWFpbCI6InRlc3R1c2VyQHRlc3R1c2VyLmNvbSJ9fQ.aVIO470S_DkcaddQgFUXciGwq2F_MTdYOJtVnEYShni7I6mqBwK3fkdWShPEgLFWUSlVUtcy61FkDnq2G-6ikSx1fUZY7iBeSCOKYlh6Kj9v43JX-uhctRSB2pI17g09EUtvmb845EHUJuoowdBLmLa4DSTdZE-h4xUQ9MsY7Ik"; | |
| @@ -117,13 +119,20 @@ BrowserID.Mocks.xhr = (function() { | |
| "get /wsapi/address_info?email=unregistered%40testuser.com valid": { type: "secondary", known: false }, | |
| "get /wsapi/address_info?email=unregistered%40testuser.com unknown_secondary": { type: "secondary", known: false }, | |
| "get /wsapi/address_info?email=registered%40testuser.com known_secondary": { type: "secondary", known: true }, | |
| - "get /wsapi/address_info?email=registered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, | |
| - "get /wsapi/address_info?email=unregistered%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, | |
| + "get /wsapi/address_info?email=registered%40testuser.com primary": { type: "primary", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=unregistered%40testuser.com primary": { type: "primary", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=registered%40testuser.com proxyidp": { type: "proxyidp", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=unregistered%40testuser.com proxyidp": { type: "proxyidp", auth: AUTH_URL, prov: PROV_URL }, | |
| "get /wsapi/address_info?email=testuser%40testuser.com unknown_secondary": { type: "secondary", known: false }, | |
| "get /wsapi/address_info?email=testuser%40testuser.com known_secondary": { type: "secondary", known: true }, | |
| "get /wsapi/address_info?email=registered%40testuser.com mustAuth": { type: "secondary", known: true }, | |
| - "get /wsapi/address_info?email=testuser%40testuser.com primary": { type: "primary", auth: "https://auth_url", prov: "https://prov_url" }, | |
| + "get /wsapi/address_info?email=testuser%40testuser.com primary": { type: "primary", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=testuser%40testuser.com proxyidp": { type: "proxyidp", auth: AUTH_URL, prov: PROV_URL }, | |
| "get /wsapi/address_info?email=testuser%40testuser.com ajaxError": undefined, | |
| + "get /wsapi/address_info?email=testuser%40gmail.com primary": { type: "primary", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=testuser%40gmail.com proxyidp": { type: "proxyidp", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=testuser%40yahoo.com proxyidp": { type: "proxyidp", auth: AUTH_URL, prov: PROV_URL }, | |
| + "get /wsapi/address_info?email=testuser%40hotmail.com proxyidp": { type: "proxyidp", auth: AUTH_URL, prov: PROV_URL }, | |
| "post /wsapi/add_email_with_assertion invalid": { success: false }, | |
| "post /wsapi/add_email_with_assertion valid": { success: true }, | |
| "post /wsapi/prolong_session valid": { success: true }, | |
| diff --git a/resources/static/test/testHelpers/helpers.js b/resources/static/test/testHelpers/helpers.js | |
| index c309dee..2c77f42 100644 | |
| --- a/resources/static/test/testHelpers/helpers.js | |
| +++ b/resources/static/test/testHelpers/helpers.js | |
| @@ -68,7 +68,7 @@ BrowserID.TestHelpers = (function() { | |
| transport.setContextInfo("cookies_enabled", true); | |
| transport.useResult("valid"); | |
| - network.init({ forceCookieStatus: undefined }); | |
| + network.init(); | |
| clearStorage(); | |
| $("body").stop().show(); | |
| @@ -223,14 +223,6 @@ BrowserID.TestHelpers = (function() { | |
| testHasClass: function(selector, className, msg) { | |
| ok($(selector).hasClass(className), | |
| selector + " has className " + className + " - " + msg); | |
| - }, | |
| - | |
| - testUndefined: function(toTest, msg) { | |
| - equal(typeof toTest, "undefined", msg || "object is undefined"); | |
| - }, | |
| - | |
| - testNotUndefined: function(toTest, msg) { | |
| - notEqual(typeof toTest, "undefined", msg || "object is defined"); | |
| } | |
| }; | |
| diff --git a/resources/views/dialog.ejs b/resources/views/dialog.ejs | |
| index 9bf2250..1752efd 100644 | |
| --- a/resources/views/dialog.ejs | |
| +++ b/resources/views/dialog.ejs | |
| @@ -5,7 +5,8 @@ | |
| <form novalidate> | |
| <div id="favicon"> | |
| <div class="table"> | |
| - <div class="vertical" id="rp_info"> | |
| + <div class="vertical"> | |
| + <strong id="sitename"></strong> | |
| </div> | |
| </div> | |
| </div> | |
| diff --git a/resources/views/dialog_layout.ejs b/resources/views/dialog_layout.ejs | |
| index 6ca9a9a..c98dd03 100644 | |
| --- a/resources/views/dialog_layout.ejs | |
| +++ b/resources/views/dialog_layout.ejs | |
| @@ -16,6 +16,7 @@ | |
| <!--[if lt IE 9]> | |
| <%- cachify_css('/production/ie8_dialog.css') %> | |
| <![endif]--> | |
| + <link href='https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,700,700italic' rel='stylesheet' type='text/css'> | |
| <title><%= gettext('Mozilla Persona') %></title> | |
| </head> | |
| <body class="waiting"> | |
| diff --git a/resources/views/layout.ejs b/resources/views/layout.ejs | |
| index 15f6cc7..8c8d01c 100644 | |
| --- a/resources/views/layout.ejs | |
| +++ b/resources/views/layout.ejs | |
| @@ -10,12 +10,13 @@ | |
| <!--[if lt IE 9]> | |
| <script src="/lib/html5shim.js"></script> | |
| <![endif]--> | |
| + <link href='https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,700,700italic' rel='stylesheet' type='text/css'> | |
| <%- cachify_css('/production/browserid.css') %> | |
| <!--[if lt IE 9]> | |
| <%- cachify_css('/production/ie8_main.css') %> | |
| <![endif]--> | |
| <%- cachify_js(util.format('/production/%s/browserid.js', locale)) %> | |
| - <title><%= format(gettext("Mozilla Persona: %s"), [title]) %></title> | |
| + <title><%= format(gettext("Mozilla Person: %s"), [title]) %></title> | |
| </head> | |
| <body class="loading"> | |
| <a href="#" id="showDevelopment"> </a> | |
| diff --git a/resources/views/signin.ejs b/resources/views/signin.ejs | |
| index a0ed5a5..e9fa217 100644 | |
| --- a/resources/views/signin.ejs | |
| +++ b/resources/views/signin.ejs | |
| @@ -42,7 +42,7 @@ | |
| </div> | |
| <div id="cannot_authenticate" class="tooltip" for="password"> | |
| - The account cannot be logged in with this username and password. | |
| + The account cannot be logged in with this username and password.') | |
| </li> | |
| </ul> | |
| diff --git a/resources/views/test.ejs b/resources/views/test.ejs | |
| index 8f6ea43..dd25eca 100644 | |
| --- a/resources/views/test.ejs | |
| +++ b/resources/views/test.ejs | |
| @@ -25,9 +25,6 @@ | |
| <a href="#" onclick="$('#contents').hide(); return false;">Close</a> | |
| <h3>Test Contents, this will be updated and can be safely ignored</h3> | |
| - <div id="rp_info"> | |
| - </div> | |
| - | |
| <div id="page_head"> | |
| </div> | |
| @@ -134,7 +131,6 @@ | |
| <script src="/dialog/controllers/primary_user_provisioned.js"></script> | |
| <script src="/dialog/controllers/is_this_your_computer.js"></script> | |
| <script src="/dialog/controllers/set_password.js"></script> | |
| - <script src="/dialog/controllers/rp_info.js"></script> | |
| <script src="/pages/page_helpers.js"></script> | |
| <script src="/pages/verify_secondary_address.js"></script> | |
| @@ -195,7 +191,6 @@ | |
| <script src="cases/controllers/primary_user_provisioned.js"></script> | |
| <script src="cases/controllers/is_this_your_computer.js"></script> | |
| <script src="cases/controllers/set_password.js"></script> | |
| - <script src="cases/controllers/rp_info.js"></script> | |
| <!-- must go last or all other tests will fail. --> | |
| <script src="cases/controllers/dialog.js"></script> | |
| diff --git a/scripts/awsbox/post_create.sh b/scripts/awsbox/post_create.sh | |
| deleted file mode 100755 | |
| index 33e1de7..0000000 | |
| --- a/scripts/awsbox/post_create.sh | |
| +++ /dev/null | |
| @@ -1,9 +0,0 @@ | |
| -#!/usr/bin/env bash | |
| - | |
| -sudo /sbin/chkconfig mysqld on | |
| -sudo /sbin/service mysqld start | |
| -sudo mysql -u root < $(dirname "${BASH_SOURCE[0]}")/create_browserid_user.sql | |
| -echo "CREATE USER 'browserid'@'localhost';" | mysql -u root | |
| -echo "CREATE DATABASE browserid;" | mysql -u root | |
| -echo "GRANT ALL ON browserid.* TO 'browserid'@'localhost';" | mysql -u root | |
| - | |
| diff --git a/scripts/awsbox/post_deploy.js b/scripts/awsbox/post_deploy.js | |
| deleted file mode 100755 | |
| index af133aa..0000000 | |
| --- a/scripts/awsbox/post_deploy.js | |
| +++ /dev/null | |
| @@ -1,16 +0,0 @@ | |
| -#!/bin/bash | |
| - | |
| -if [ ! -f $HOME/var/root.cert ] ; then | |
| - echo ">> generating keypair" | |
| - scripts/generate_ephemeral_keys.sh | |
| - mv var/root.{cert,secretkey} $HOME/var | |
| -else | |
| - echo ">> no keypair needed. you gots one" | |
| -fi | |
| - | |
| -echo ">> updating strings" | |
| -svn co -q http://svn.mozilla.org/projects/l10n-misc/trunk/browserid/locale | |
| -./locale/compile-mo.sh locale/ | |
| - | |
| -echo ">> generating production resources" | |
| -scripts/compress | |
| diff --git a/scripts/browserid.spec b/scripts/browserid.spec | |
| index 25fa883..c1b5c71 100644 | |
| --- a/scripts/browserid.spec | |
| +++ b/scripts/browserid.spec | |
| @@ -1,7 +1,7 @@ | |
| %define _rootdir /opt/browserid | |
| Name: browserid-server | |
| -Version: 0.2012.06.22 | |
| +Version: 0.2012.06.08 | |
| Release: 1%{?dist}_%{svnrev} | |
| Summary: BrowserID server | |
| Packager: Pete Fritchman <petef@mozilla.com> | |
| diff --git a/scripts/deploy.js b/scripts/deploy.js | |
| index fdc90bf..aebb6c7 100755 | |
| --- a/scripts/deploy.js | |
| +++ b/scripts/deploy.js | |
| @@ -1,82 +1,155 @@ | |
| #!/usr/bin/env node | |
| -var path = require('path'), | |
| -child_process = require('child_process'); | |
| - | |
| -/* | |
| - * A thin wrapper around awsbox that expects certain env | |
| - * vars and invokes awsbox for ya to deploy a VM. | |
| - */ | |
| - | |
| -if (!process.env['AWS_ID'] || ! process.env['AWS_SECRET']) { | |
| - console.log("You haven't defined AWS_ID and AWS_SECRET in the environment"); | |
| - console.log("Get these values from the amazon web console and try again."); | |
| - process.exit(1); | |
| +const | |
| +aws = require('./deploy/aws.js'); | |
| +path = require('path'); | |
| +vm = require('./deploy/vm.js'), | |
| +key = require('./deploy/key.js'), | |
| +ssh = require('./deploy/ssh.js'), | |
| +git = require('./deploy/git.js'), | |
| +dns = require('./deploy/dns.js'); | |
| + | |
| +var verbs = {}; | |
| + | |
| +function checkErr(err) { | |
| + if (err) { | |
| + process.stderr.write('fatal error: ' + err + "\n"); | |
| + process.exit(1); | |
| + } | |
| } | |
| -if (!process.env['ZERIGO_DNS_KEY'] && process.env['PERSONA_DEPLOY_DNS_KEY']) { | |
| - process.env['ZERIGO_DNS_KEY'] = process.env['PERSONA_DEPLOY_DNS_KEY']; | |
| +function printInstructions(name, deets) { | |
| + console.log("Yay! You have your very own deployment. Here's the basics:\n"); | |
| + console.log(" 1. deploy your code: git push " + name + " <mybranch>:master"); | |
| + console.log(" 2. visit your server on the web: https://" + name + ".hacksign.in"); | |
| + console.log(" 3. test via a website: http://" + name + ".myfavoritebeer.org"); | |
| + console.log(" 4. ssh in with sudo: ssh ec2-user@" + name + ".hacksign.in"); | |
| + console.log(" 5. ssh as the deployment user: ssh app@" + name + ".hacksign.in\n"); | |
| + console.log("enjoy! Here's your server details", JSON.stringify(deets, null, 4)); | |
| } | |
| -var cmd = path.join(__dirname, '..', 'node_modules', '.bin', 'awsbox'); | |
| -cmd = path.relative(process.env['PWD'], cmd); | |
| - | |
| -if (process.argv.length > 1 && | |
| - process.argv[2] === 'create' || | |
| - process.argv[2] === 'deploy') | |
| -{ | |
| - var options = {}; | |
| - | |
| - if (process.argv.length > 3) options.n = process.argv[3]; | |
| - | |
| - if (process.env['PERSONA_SSL_PRIV'] || process.env['PERSONA_SSL_PUB']) { | |
| - options.p = process.env['PERSONA_SSL_PUB']; | |
| - options.s = process.env['PERSONA_SSL_PRIV']; | |
| +function validateName(name) { | |
| + if (!/^[a-z][0-9a-z_\-]*$/.test(name)) { | |
| + throw "invalid name! must be a valid dns fragment ([z-a0-9\-_])"; | |
| } | |
| +} | |
| - if (process.env['ZERIGO_DNS_KEY']) { | |
| - options.d = true; | |
| - | |
| - // when we have a DNS key, we can set a hostname! | |
| - var scheme = (options.p ? 'https' : 'http') + '://'; | |
| +verbs['destroy'] = function(args) { | |
| + if (!args || args.length != 1) { | |
| + throw 'missing required argument: name of instance'; | |
| + } | |
| + var name = args[0]; | |
| + validateName(name); | |
| + var hostname = name + ".hacksign.in"; | |
| + | |
| + process.stdout.write("trying to destroy VM for " + hostname + ": "); | |
| + vm.destroy(name, function(err, deets) { | |
| + console.log(err ? ("failed: " + err) : "done"); | |
| + process.stdout.write("trying to remove DNS for " + hostname + ": "); | |
| + dns.deleteRecord(hostname, function(err) { | |
| + console.log(err ? "failed: " + err : "done"); | |
| + if (deets && deets.ipAddress) { | |
| + process.stdout.write("trying to remove git remote: "); | |
| + git.removeRemote(name, deets.ipAddress, function(err) { | |
| + console.log(err ? "failed: " + err : "done"); | |
| + }); | |
| + } | |
| + }); | |
| + }); | |
| +} | |
| - if (process.env['PERSONA_DEPLOYMENT_HOSTNAME']) { | |
| - options.u = scheme + process.env['PERSONA_DEPLOYMENT_HOSTNAME']; | |
| - } else if (options.n) { | |
| - options.u = scheme + options.n + ".personatest.org"; | |
| - } | |
| +verbs['test'] = function() { | |
| + // let's see if we can contact aws and zerigo | |
| + process.stdout.write("Checking DNS management access: "); | |
| + dns.inUse("somerandomname", function(err) { | |
| + console.log(err ? "NOT ok: " + err : "good"); | |
| + process.stdout.write("Checking AWS access: "); | |
| + vm.list(function(err) { | |
| + console.log(err ? "NOT ok: " + err : "good"); | |
| + }); | |
| + }); | |
| +} | |
| - } else { | |
| - console.log('WARNING: No DNS key defined in the environment! ' + | |
| - 'I cannot set up DNS for you. We\'ll do this by IP.'); | |
| +verbs['deploy'] = function(args) { | |
| + if (!args || args.length != 1) { | |
| + throw 'missing required argument: name of instance'; | |
| } | |
| + var name = args[0]; | |
| + validateName(name); | |
| + var hostname = name + ".hacksign.in"; | |
| + var longName = 'browserid deployment (' + name + ')'; | |
| + | |
| + console.log("attempting to set up " + name + ".hacksign.in"); | |
| + | |
| + dns.inUse(hostname, function(err, r) { | |
| + checkErr(err); | |
| + if (r) checkErr("sorry! that name '" + name + "' is already being used. so sad"); | |
| + | |
| + vm.startImage(function(err, r) { | |
| + checkErr(err); | |
| + console.log(" ... VM launched, waiting for startup (should take about 20s)"); | |
| + | |
| + vm.waitForInstance(r.instanceId, function(err, deets) { | |
| + checkErr(err); | |
| + console.log(" ... Instance ready, setting up DNS"); | |
| + dns.updateRecord(name, "hacksign.in", deets.ipAddress, function(err) { | |
| + checkErr(err); | |
| + console.log(" ... DNS set up, setting human readable name in aws"); | |
| + | |
| + vm.setName(r.instanceId, longName, function(err) { | |
| + checkErr(err); | |
| + console.log(" ... name set, waiting for ssh access and configuring"); | |
| + var config = { public_url: "https://" + name + ".hacksign.in"}; | |
| + | |
| + ssh.copyUpConfig(deets.ipAddress, config, function(err, r) { | |
| + checkErr(err); | |
| + console.log(" ... victory! server is accessible and configured"); | |
| + git.addRemote(name, deets.ipAddress, function(err, r) { | |
| + if (err && /already exists/.test(err)) { | |
| + console.log("OOPS! you already have a git remote named 'test'!"); | |
| + console.log("to create a new one: git remote add <name> " + | |
| + "app@" + deets.ipAddress + ":git"); | |
| + } else { | |
| + checkErr(err); | |
| + } | |
| + console.log(" ... and your git remote is all set up"); | |
| + console.log(""); | |
| + printInstructions(name, deets); | |
| + }); | |
| + }); | |
| + }); | |
| + }); | |
| + }); | |
| + }); | |
| + }); | |
| +}; | |
| - // pass through/override with user provided vars | |
| - for (var i = 3; i < process.argv.length; i++) { | |
| - var k = process.argv[i]; | |
| - if (i + 1 < process.argv.length && k.length === 2 && k[0] === '-') { | |
| - options[k[1]] = process.argv[++i]; | |
| +verbs['list'] = function(args) { | |
| + vm.list(function(err, r) { | |
| + checkErr(err); | |
| + console.log(JSON.stringify(r, null, 2)); | |
| + }); | |
| +}; | |
| + | |
| +var error = (process.argv.length <= 2); | |
| + | |
| +if (!error) { | |
| + var verb = process.argv[2]; | |
| + if (!verbs[verb]) error = "no such command: " + verb; | |
| + else { | |
| + try { | |
| + verbs[verb](process.argv.slice(3)); | |
| + } catch(e) { | |
| + error = "error running '" + verb + "' command: " + e; | |
| } | |
| } | |
| +} | |
| - if (process.env['PERSONA_EPHEMERAL_CONFIG']) { | |
| - options.x = process.env['PERSONA_EPHEMERAL_CONFIG']; | |
| - } | |
| - | |
| - cmd += " create --ssl=force"; | |
| +if (error) { | |
| + if (typeof error === 'string') process.stderr.write('fatal error: ' + error + "\n\n"); | |
| - Object.keys(options).forEach(function(opt) { | |
| - cmd += " -" + opt; | |
| - cmd += typeof options[opt] === 'string' ? " " + options[opt] : ""; | |
| - }); | |
| -} else { | |
| - cmd += " " + process.argv.slice(2).join(' '); | |
| + process.stderr.write('A command line tool to deploy BrowserID onto Amazon\'s EC2\n'); | |
| + process.stderr.write('Usage: ' + path.basename(__filename) + | |
| + ' <' + Object.keys(verbs).join('|') + "> [args]\n"); | |
| + process.exit(1); | |
| } | |
| - | |
| -console.log("awsbox cmd: " + cmd); | |
| -var cp = child_process.exec(cmd, function(err) { | |
| - if (err) process.exit(err.code); | |
| - else process.exit(0); | |
| -}); | |
| -cp.stdout.pipe(process.stdout); | |
| -cp.stderr.pipe(process.stderr); | |
| diff --git a/scripts/deploy/aws.js b/scripts/deploy/aws.js | |
| new file mode 100644 | |
| index 0000000..6641989 | |
| --- /dev/null | |
| +++ b/scripts/deploy/aws.js | |
| @@ -0,0 +1,7 @@ | |
| +const | |
| +awslib = require('aws-lib'); | |
| + | |
| +module.exports = awslib.createEC2Client(process.env['AWS_ID'], process.env['AWS_SECRET'], { | |
| + version: '2011-12-15' | |
| +}); | |
| + | |
| diff --git a/scripts/deploy/dns.js b/scripts/deploy/dns.js | |
| new file mode 100644 | |
| index 0000000..99a2f58 | |
| --- /dev/null | |
| +++ b/scripts/deploy/dns.js | |
| @@ -0,0 +1,83 @@ | |
| +const | |
| +http = require('http'), | |
| +xml2js = new (require('xml2js')).Parser(), | |
| +jsel = require('JSONSelect'); | |
| + | |
| +const envVar = 'BROWSERID_DEPLOY_DNS_KEY'; | |
| +if (!process.env[envVar]) { | |
| + throw "Missing api key! contact lloyd and set the key in your env: " | |
| + + envVar; | |
| +} | |
| + | |
| +const api_key = process.env[envVar]; | |
| + | |
| +function doRequest(method, path, body, cb) { | |
| + var req = http.request({ | |
| + auth: 'lloyd@hilaiel.com:' + api_key, | |
| + host: 'ns.zerigo.com', | |
| + port: 80, | |
| + path: path, | |
| + method: method, | |
| + headers: { | |
| + 'Content-Type': 'application/xml', | |
| + 'Content-Length': body ? body.length : 0 | |
| + } | |
| + }, function(r) { | |
| + if ((r.statusCode / 100).toFixed(0) != 2 && | |
| + r.statusCode != 404) { | |
| + return cb("non 200 status: " + r.statusCode); | |
| + } | |
| + buf = ""; | |
| + r.on('data', function(chunk) { | |
| + buf += chunk; | |
| + }); | |
| + r.on('end', function() { | |
| + xml2js.parseString(buf, cb); | |
| + }); | |
| + }); | |
| + if (body) req.write(body); | |
| + req.end(); | |
| +}; | |
| + | |
| +exports.updateRecord = function (hostname, zone, ip, cb) { | |
| + doRequest('GET', '/api/1.1/zones.xml', null, function(err, r) { | |
| + if (err) return cb(err); | |
| + var m = jsel.match('object:has(:root > .domain:val(?)) > .id .#', | |
| + [ zone ], r); | |
| + if (m.length != 1) return cb("couldn't extract domain id from zerigo"); | |
| + var path = '/api/1.1/hosts.xml?zone_id=' + m[0]; | |
| + var body = '<host><data>' + ip + '</data><host-type>A</host-type>'; | |
| + body += '<hostname>' + hostname + '</hostname>' | |
| + body += '</host>'; | |
| + doRequest('POST', path, body, function(err, r) { | |
| + cb(err); | |
| + }); | |
| + }); | |
| +}; | |
| + | |
| +exports.deleteRecord = function (hostname, cb) { | |
| + doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) { | |
| + if (err) return cb(err); | |
| + var m = jsel.match('.host .id > .#', r); | |
| + if (!m.length) return cb("no such DNS record"); | |
| + function deleteOne() { | |
| + if (!m.length) return cb(null); | |
| + var one = m.shift(); | |
| + doRequest('DELETE', '/api/1.1/hosts/' + one + '.xml', null, function(err) { | |
| + if (err) return cb(err); | |
| + deleteOne(); | |
| + }); | |
| + } | |
| + deleteOne(); | |
| + }); | |
| +}; | |
| + | |
| +exports.inUse = function (hostname, cb) { | |
| + doRequest('GET', '/api/1.1/hosts.xml?fqdn=' + hostname, null, function(err, r) { | |
| + if (err) return cb(err); | |
| + var m = jsel.match('.host', r); | |
| + // we shouldn't have multiple! oops! let's return the first one | |
| + if (m.length) return cb(null, m[0]); | |
| + cb(null, null); | |
| + }); | |
| +} | |
| diff --git a/scripts/deploy/git.js b/scripts/deploy/git.js | |
| new file mode 100644 | |
| index 0000000..4fc20d1 | |
| --- /dev/null | |
| +++ b/scripts/deploy/git.js | |
| @@ -0,0 +1,120 @@ | |
| +const | |
| +child_process = require('child_process'); | |
| +spawn = child_process.spawn, | |
| +path = require('path'); | |
| + | |
| +exports.addRemote = function(name, host, cb) { | |
| + var cmd = 'git remote add ' + name + ' app@'+ host + ':git'; | |
| + child_process.exec(cmd, cb); | |
| +}; | |
| + | |
| +// remove a remote, but only if it is pointed to a specific | |
| +// host. This will keep deploy from killing manuall remotes | |
| +// that you've set up | |
| +exports.removeRemote = function(name, host, cb) { | |
| + var desired = 'app@'+ host + ':git'; | |
| + var cmd = 'git remote -v show | grep push'; | |
| + child_process.exec(cmd, function(err, r) { | |
| + try { | |
| + var remotes = {}; | |
| + r.split('\n').forEach(function(line) { | |
| + if (!line.length) return; | |
| + var line = line.split('\t'); | |
| + if (!line.length == 2) return; | |
| + remotes[line[0]] = line[1].split(" ")[0]; | |
| + }); | |
| + if (remotes[name] && remotes[name] === desired) { | |
| + child_process.exec('git remote rm ' + name, cb); | |
| + } else { | |
| + throw "no such remote"; | |
| + } | |
| + } catch(e) { | |
| + cb(e); | |
| + } | |
| + }); | |
| +}; | |
| + | |
| +exports.currentSHA = function(dir, cb) { | |
| + if (typeof dir === 'function' && cb === undefined) { | |
| + cb = dir; | |
| + dir = path.join(__dirname, '..', '..'); | |
| + } | |
| + console.log(dir); | |
| + | |
| + var p = spawn('git', [ 'log', '--pretty=%h', '-1' ], { | |
| + env: { GIT_DIR: path.join(dir, ".git") } | |
| + }); | |
| + var buf = ""; | |
| + p.stdout.on('data', function(d) { | |
| + buf += d; | |
| + }); | |
| + p.on('exit', function(code, signal) { | |
| + console.log(buf); | |
| + var gitsha = buf.toString().trim(); | |
| + if (gitsha && gitsha.length === 7) { | |
| + return cb(null, gitsha); | |
| + } | |
| + cb("can't extract git sha from " + dir); | |
| + }); | |
| +}; | |
| + | |
| +function splitAndEmit(chunk, cb) { | |
| + if (chunk) chunk = chunk.toString(); | |
| + if (typeof chunk === 'string') { | |
| + chunk.split('\n').forEach(function (line) { | |
| + line = line.trim(); | |
| + if (line.length) cb(line); | |
| + }); | |
| + } | |
| +} | |
| + | |
| +exports.push = function(dir, host, pr, cb) { | |
| + if (typeof host === 'function' && cb === undefined) { | |
| + cb = pr; | |
| + pr = host; | |
| + host = dir; | |
| + dir = path.join(__dirname, '..', '..'); | |
| + } | |
| + | |
| + var p = spawn('git', [ 'push', 'app@' + host + ":git", 'dev:master' ], { | |
| + env: { | |
| + GIT_DIR: path.join(dir, ".git"), | |
| + GIT_WORK_TREE: dir | |
| + } | |
| + }); | |
| + p.stdout.on('data', function(c) { splitAndEmit(c, pr); }); | |
| + p.stderr.on('data', function(c) { splitAndEmit(c, pr); }); | |
| + p.on('exit', function(code, signal) { | |
| + return cb(code = 0); | |
| + }); | |
| +}; | |
| + | |
| +exports.pull = function(dir, remote, branch, pr, cb) { | |
| + var p = spawn('git', [ 'pull', "-f", remote, branch + ":" + branch ], { | |
| + env: { | |
| + GIT_DIR: path.join(dir, ".git"), | |
| + GIT_WORK_TREE: dir, | |
| + PWD: dir | |
| + }, | |
| + cwd: dir | |
| + }); | |
| + | |
| + p.stdout.on('data', function(c) { splitAndEmit(c, pr); }); | |
| + p.stderr.on('data', function(c) { splitAndEmit(c, pr); }); | |
| + | |
| + p.on('exit', function(code, signal) { | |
| + return cb(code = 0); | |
| + }); | |
| +} | |
| + | |
| +exports.init = function(dir, cb) { | |
| + var p = spawn('git', [ 'init' ], { | |
| + env: { | |
| + GIT_DIR: path.join(dir, ".git"), | |
| + GIT_WORK_TREE: dir | |
| + } | |
| + }); | |
| + p.on('exit', function(code, signal) { | |
| + return cb(code = 0); | |
| + }); | |
| +}; | |
| diff --git a/scripts/deploy/key.js b/scripts/deploy/key.js | |
| new file mode 100644 | |
| index 0000000..d93da01 | |
| --- /dev/null | |
| +++ b/scripts/deploy/key.js | |
| @@ -0,0 +1,57 @@ | |
| +const | |
| +aws = require('./aws.js'), | |
| +path = require('path'), | |
| +fs = require('fs'), | |
| +child_process = require('child_process'), | |
| +jsel = require('JSONSelect'), | |
| +crypto = require('crypto'); | |
| + | |
| +const keyPath = process.env['PUBKEY'] || path.join(process.env['HOME'], ".ssh", "id_rsa.pub"); | |
| + | |
| +exports.read = function(cb) { | |
| + fs.readFile(keyPath, cb); | |
| +}; | |
| + | |
| +exports.fingerprint = function(cb) { | |
| + exports.read(function(err, buf) { | |
| + if (err) return cb(err); | |
| + var b = new Buffer(buf.toString().split(' ')[1], 'base64'); | |
| + var md5sum = crypto.createHash('md5'); | |
| + md5sum.update(b); | |
| + cb(null, md5sum.digest('hex')); | |
| + }); | |
| +/* | |
| + child_process.exec( | |
| + "ssh-keygen -lf " + keyPath, | |
| + function(err, r) { | |
| + if (!err) r = r.split(' ')[1]; | |
| + cb(err, r); | |
| + }); | |
| +*/ | |
| +}; | |
| + | |
| +exports.getName = function(cb) { | |
| + exports.fingerprint(function(err, fingerprint) { | |
| + if (err) return cb(err); | |
| + | |
| + var keyName = "browserid deploy key (" + fingerprint + ")"; | |
| + | |
| + // is this fingerprint known? | |
| + aws.call('DescribeKeyPairs', {}, function(result) { | |
| + var found = jsel.match(":has(.keyName:val(?)) > .keyName", [ keyName ], result); | |
| + if (found.length) return cb(null, keyName); | |
| + | |
| + // key isn't yet installed! | |
| + exports.read(function(err, key) { | |
| + aws.call('ImportKeyPair', { | |
| + KeyName: keyName, | |
| + PublicKeyMaterial: new Buffer(key).toString('base64') | |
| + }, function(result) { | |
| + if (!result) return cb('null result from ec2 on key addition'); | |
| + if (result.Errors) return cb(result.Errors.Error.Message); | |
| + cb(null, keyName); | |
| + }); | |
| + }); | |
| + }); | |
| + }); | |
| +}; | |
| diff --git a/scripts/deploy/sec.js b/scripts/deploy/sec.js | |
| new file mode 100644 | |
| index 0000000..d821169 | |
| --- /dev/null | |
| +++ b/scripts/deploy/sec.js | |
| @@ -0,0 +1,59 @@ | |
| +const | |
| +aws = require('./aws.js'); | |
| +jsel = require('JSONSelect'), | |
| +key = require('./key.js'); | |
| + | |
| +// every time you change the security group, change this version number | |
| +// so new deployments will create a new group with the changes | |
| +const SECURITY_GROUP_VERSION = 1; | |
| + | |
| +function createError(msg, r) { | |
| + var m = jsel.match('.Message', r); | |
| + if (m.length) msg += ": " + m[0]; | |
| + return msg; | |
| +} | |
| + | |
| +exports.getName = function(cb) { | |
| + var groupName = "browserid group v" + SECURITY_GROUP_VERSION; | |
| + | |
| + // is this fingerprint known? | |
| + aws.call('DescribeSecurityGroups', { | |
| + GroupName: groupName | |
| + }, function(r) { | |
| + if (jsel.match('.Code:val("InvalidGroup.NotFound")', r).length) { | |
| + aws.call('CreateSecurityGroup', { | |
| + GroupName: groupName, | |
| + GroupDescription: 'A security group for browserid deployments' | |
| + }, function(r) { | |
| + if (!r || !r.return === 'true') { | |
| + return cb(createError('failed to create security group', r)); | |
| + } | |
| + aws.call('AuthorizeSecurityGroupIngress', { | |
| + GroupName: groupName, | |
| + "IpPermissions.1.IpProtocol": 'tcp', | |
| + "IpPermissions.1.FromPort": 80, | |
| + "IpPermissions.1.ToPort": 80, | |
| + "IpPermissions.1.IpRanges.1.CidrIp": "0.0.0.0/0", | |
| + "IpPermissions.2.IpProtocol": 'tcp', | |
| + "IpPermissions.2.FromPort": 22, | |
| + "IpPermissions.2.ToPort": 22, | |
| + "IpPermissions.2.IpRanges.1.CidrIp": "0.0.0.0/0", | |
| + "IpPermissions.3.IpProtocol": 'tcp', | |
| + "IpPermissions.3.FromPort": 443, | |
| + "IpPermissions.3.ToPort": 443, | |
| + "IpPermissions.3.IpRanges.1.CidrIp" : "0.0.0.0/0" | |
| + }, function(r) { | |
| + if (!r || !r.return === 'true') { | |
| + return cb(createError('failed to create security group', r)); | |
| + } | |
| + cb(null, groupName); | |
| + }); | |
| + }); | |
| + } else { | |
| + // already exists? | |
| + var m = jsel.match('.securityGroupInfo > .item > .groupName', r); | |
| + if (m.length && m[0] === groupName) return cb(null, groupName); | |
| + cb(createError('error creating group', r)); | |
| + } | |
| + }); | |
| +}; | |
| diff --git a/scripts/deploy/ssh.js b/scripts/deploy/ssh.js | |
| new file mode 100644 | |
| index 0000000..290abf1 | |
| --- /dev/null | |
| +++ b/scripts/deploy/ssh.js | |
| @@ -0,0 +1,43 @@ | |
| +const | |
| +child_process = require('child_process'), | |
| +temp = require('temp'), | |
| +fs = require('fs'); | |
| + | |
| +const MAX_TRIES = 20; | |
| + | |
| +exports.copyUpConfig = function(host, config, cb) { | |
| + var tries = 0; | |
| + temp.open({}, function(err, r) { | |
| + fs.writeFileSync(r.path, JSON.stringify(config, null, 4)); | |
| + var cmd = 'scp -o "StrictHostKeyChecking no" ' + r.path + ' app@' + host + ":config.json"; | |
| + function oneTry() { | |
| + child_process.exec(cmd, function(err, r) { | |
| + if (err) { | |
| + if (++tries > MAX_TRIES) return cb("can't connect via SSH. stupid amazon"); | |
| + console.log(" ... nope. not yet. retrying."); | |
| + setTimeout(oneTry, 5000); | |
| + } else { | |
| + cb(); | |
| + } | |
| + }); | |
| + } | |
| + oneTry(); | |
| + }); | |
| +}; | |
| + | |
| +exports.copySSL = function(host, pub, priv, cb) { | |
| + var cmd = 'scp -o "StrictHostKeyChecking no" ' + pub + ' ec2-user@' + host + ":/etc/ssl/certs/hacksign.in.crt"; | |
| + child_process.exec(cmd, function(err, r) { | |
| + if (err) return cb(err); | |
| + var cmd = 'scp -o "StrictHostKeyChecking no" ' + priv + ' ec2-user@' + host + ":/etc/ssl/certs/hacksign.in.key"; | |
| + child_process.exec(cmd, function(err, r) { | |
| + var cmd = 'ssh -o "StrictHostKeyChecking no" ec2-user@' + host + " 'sudo /etc/init.d/nginx restart'"; | |
| + child_process.exec(cmd, cb); | |
| + }); | |
| + }); | |
| +}; | |
| + | |
| +exports.addSSHPubKey = function(host, pubkey, cb) { | |
| + var cmd = 'ssh -o "StrictHostKeyChecking no" ec2-user@' + host + " 'echo \'" + pubkey + "\' >> .ssh/authorized_keys'"; | |
| + child_process.exec(cmd, cb); | |
| +}; | |
| diff --git a/scripts/deploy/vm.js b/scripts/deploy/vm.js | |
| new file mode 100644 | |
| index 0000000..de38451 | |
| --- /dev/null | |
| +++ b/scripts/deploy/vm.js | |
| @@ -0,0 +1,121 @@ | |
| +const | |
| +aws = require('./aws.js'); | |
| +jsel = require('JSONSelect'), | |
| +key = require('./key.js'), | |
| +sec = require('./sec.js'); | |
| + | |
| +const BROWSERID_TEMPLATE_IMAGE_ID = 'ami-6ed07107'; | |
| + | |
| +function extractInstanceDeets(horribleBlob) { | |
| + var instance = {}; | |
| + ["instanceId", "imageId", "instanceState", "dnsName", "keyName", "instanceType", | |
| + "ipAddress"].forEach(function(key) { | |
| + if (horribleBlob[key]) instance[key] = horribleBlob[key]; | |
| + }); | |
| + var name = jsel.match('.tagSet :has(.key:val("Name")) > .value', horribleBlob); | |
| + if (name.length) { | |
| + instance.fullName = name[0]; | |
| + // if this is a 'browserid deployment', we'll only display the hostname chosen by the | |
| + // user | |
| + var m = /^browserid deployment \((.*)\)$/.exec(instance.fullName); | |
| + instance.name = m ? m[1] : instance.fullName; | |
| + } else { | |
| + instance.name = instance.instanceId; | |
| + } | |
| + return instance; | |
| +} | |
| + | |
| +exports.list = function(cb) { | |
| + aws.call('DescribeInstances', {}, function(result) { | |
| + var instances = {}; | |
| + var i = 1; | |
| + jsel.forEach( | |
| + '.instancesSet > .item:has(.instanceState .name:val("running"))', | |
| + result, function(item) { | |
| + var deets = extractInstanceDeets(item); | |
| + instances[deets.name || 'unknown ' + i++] = deets; | |
| + }); | |
| + cb(null, instances); | |
| + }); | |
| +}; | |
| + | |
| +exports.destroy = function(name, cb) { | |
| + exports.list(function(err, r) { | |
| + if (err) return cb('failed to list vms: ' + err); | |
| + if (!r[name]) return cb('no such vm'); | |
| + | |
| + aws.call('TerminateInstances', { | |
| + InstanceId: r[name].instanceId | |
| + }, function(result) { | |
| + try { return cb(result.Errors.Error.Message); } catch(e) {}; | |
| + cb(null, r[name]); | |
| + }); | |
| + }); | |
| +}; | |
| + | |
| +function returnSingleImageInfo(result, cb) { | |
| + if (!result) return cb('no results from ec2 api'); | |
| + try { return cb(result.Errors.Error.Message); } catch(e) {}; | |
| + try { | |
| + result = jsel.match('.instancesSet > .item', result)[0]; | |
| + cb(null, extractInstanceDeets(result)); | |
| + } catch(e) { | |
| + return cb("couldn't extract new instance details from ec2 response: " + e); | |
| + } | |
| +} | |
| + | |
| +exports.startImage = function(cb) { | |
| + key.getName(function(err, keyName) { | |
| + if (err) return cb(err); | |
| + sec.getName(function(err, groupName) { | |
| + if (err) return cb(err); | |
| + aws.call('RunInstances', { | |
| + ImageId: BROWSERID_TEMPLATE_IMAGE_ID, | |
| + KeyName: keyName, | |
| + SecurityGroup: groupName, | |
| + InstanceType: 't1.micro', | |
| + MinCount: 1, | |
| + MaxCount: 1 | |
| + }, function (result) { | |
| + returnSingleImageInfo(result, cb); | |
| + }); | |
| + }); | |
| + }); | |
| +}; | |
| + | |
| +exports.waitForInstance = function(id, cb) { | |
| + aws.call('DescribeInstanceStatus', { | |
| + InstanceId: id | |
| + }, function(r) { | |
| + if (!r) return cb('no response from ec2'); | |
| + // we're waiting and amazon might not have created the image yet! that's | |
| + // not an error, just an api timing quirk | |
| + var waiting = jsel.match('.Error .Code:val("InvalidInstanceID.NotFound")', r); | |
| + if (waiting.length) { | |
| + return setTimeout(function(){ exports.waitForInstance(id, cb); }, 1000); | |
| + } | |
| + | |
| + if (!r.instanceStatusSet) return cb('malformed response from ec2' + JSON.stringify(r, null, 2)); | |
| + if (Object.keys(r.instanceStatusSet).length) { | |
| + var deets = extractInstanceDeets(r.instanceStatusSet.item); | |
| + if (deets && deets.instanceState && deets.instanceState.name === 'running') { | |
| + return aws.call('DescribeInstances', { InstanceId: id }, function(result) { | |
| + returnSingleImageInfo(result, cb); | |
| + }); | |
| + } | |
| + } | |
| + setTimeout(function(){ exports.waitForInstance(id, cb); }, 1000); | |
| + }); | |
| +}; | |
| + | |
| +exports.setName = function(id, name, cb) { | |
| + aws.call('CreateTags', { | |
| + "ResourceId.0": id, | |
| + "Tag.0.Key": 'Name', | |
| + "Tag.0.Value": name | |
| + }, function(result) { | |
| + if (result && result.return === 'true') return cb(null); | |
| + try { return cb(result.Errors.Error.Message); } catch(e) {}; | |
| + return cb('unknown error setting instance name'); | |
| + }); | |
| +}; | |
| diff --git a/scripts/deploy_dev.js b/scripts/deploy_dev.js | |
| new file mode 100755 | |
| index 0000000..21c2308 | |
| --- /dev/null | |
| +++ b/scripts/deploy_dev.js | |
| @@ -0,0 +1,132 @@ | |
| +#!/usr/bin/env node | |
| + | |
| +/* | |
| + * Deploy dev.diresworb.org, for fun and profit. | |
| + */ | |
| + | |
| +const | |
| +aws = require('./deploy/aws.js'); | |
| +path = require('path'); | |
| +vm = require('./deploy/vm.js'), | |
| +key = require('./deploy/key.js'), | |
| +ssh = require('./deploy/ssh.js'), | |
| +git = require('./deploy/git.js'), | |
| +dns = require('./deploy/dns.js'), | |
| +util = require('util'), | |
| +events = require('events'), | |
| +fs = require('fs'); | |
| + | |
| +// verify we have files we need | |
| + | |
| +// a class capable of deploying and emmitting events along the way | |
| +function DevDeployer() { | |
| + events.EventEmitter.call(this); | |
| + | |
| + this.sslpub = process.env['DEV_SSL_PUB']; | |
| + this.sslpriv = process.env['DEV_SSL_PRIV']; | |
| + this.keypairs = []; | |
| + if (process.env['ADDITIONAL_KEYPAIRS']) { | |
| + this.keypairs = process.env['ADDITIONAL_KEYPAIRS'].split(','); | |
| + } | |
| + | |
| + if (!this.sslpub || !this.sslpriv) { | |
| + throw("you must provide ssl cert paths via DEV_SSL_PUB & DEV_SSL_PRIV"); | |
| + } | |
| + | |
| + if (!fs.statSync(this.sslpub).isFile() || !fs.statSync(this.sslpriv).isFile()) { | |
| + throw("DEV_SSL_PUB & DEV_SSL_PRIV must be paths to actual files. duh"); | |
| + } | |
| +} | |
| + | |
| +util.inherits(DevDeployer, events.EventEmitter); | |
| + | |
| +DevDeployer.prototype.setup = function(cb) { | |
| + var self = this; | |
| + git.currentSHA(function(err, r) { | |
| + if (err) return cb(err); | |
| + self.sha = r; | |
| + vm.startImage(function(err, r) { | |
| + if (err) return cb(err); | |
| + self.emit('progress', "starting new image"); | |
| + vm.waitForInstance(r.instanceId, function(err, d) { | |
| + if (err) return cb(err); | |
| + self.deets = d; | |
| + self.emit('progress', "image started"); | |
| + vm.setName(r.instanceId, "dev.diresworb.org (" + self.sha + ")", function(err, r) { | |
| + if (err) return cb(err); | |
| + self.emit('progress', "name set"); | |
| + cb(null); | |
| + }); | |
| + }); | |
| + }); | |
| + }); | |
| +} | |
| + | |
| +DevDeployer.prototype.configure = function(cb) { | |
| + var self = this; | |
| + var config = { public_url: "https://dev.diresworb.org" }; | |
| + ssh.copyUpConfig(self.deets.ipAddress, config, function (err) { | |
| + if (err) return cb(err); | |
| + ssh.copySSL(self.deets.ipAddress, self.sslpub, self.sslpriv, function(err) { | |
| + if (err) return cb(err); | |
| + | |
| + // now copy up addtional keypairs | |
| + var i = 0; | |
| + function copyNext() { | |
| + if (i == self.keypairs.length) return cb(null); | |
| + ssh.addSSHPubKey(self.deets.ipAddress, self.keypairs[i++], function(err) { | |
| + if (err) return cb(err); | |
| + self.emit('progress', "key added..."); | |
| + copyNext(); | |
| + }); | |
| + } | |
| + copyNext(); | |
| + }); | |
| + }); | |
| +} | |
| + | |
| +DevDeployer.prototype.pushCode = function(cb) { | |
| + var self = this; | |
| + git.push(this.deets.ipAddress, function(d) { self.emit('build_output', d); }, cb); | |
| +} | |
| + | |
| +DevDeployer.prototype.updateDNS = function(cb) { | |
| + var self = this; | |
| + dns.deleteRecord('dev.diresworb.org', function() { | |
| + dns.updateRecord('', 'dev.diresworb.org', self.deets.ipAddress, cb); | |
| + }); | |
| +} | |
| + | |
| +var deployer = new DevDeployer(); | |
| + | |
| +deployer.on('progress', function(d) { | |
| + console.log("PR: " + d); | |
| +}); | |
| + | |
| +deployer.on('build_output', function(d) { | |
| + console.log("BO: " + d); | |
| +}); | |
| + | |
| +function checkerr(err) { | |
| + if (err) { | |
| + process.stderr.write("fatal error: " + err + "\n"); | |
| + process.exit(1); | |
| + } | |
| +} | |
| + | |
| +var startTime = new Date(); | |
| +deployer.setup(function(err) { | |
| + checkerr(err); | |
| + deployer.configure(function(err) { | |
| + checkerr(err); | |
| + deployer.updateDNS(function(err) { | |
| + checkerr(err); | |
| + deployer.pushCode(function(err) { | |
| + checkerr(err); | |
| + console.log("dev.diresworb.org (" + deployer.sha + ") deployed to " + | |
| + deployer.deets.ipAddress + " in " + | |
| + ((new Date() - startTime) / 1000.0).toFixed(2) + "s"); | |
| + }); | |
| + }); | |
| + }); | |
| +}); | |
| diff --git a/scripts/deploy_server.js b/scripts/deploy_server.js | |
| new file mode 100755 | |
| index 0000000..f38338e | |
| --- /dev/null | |
| +++ b/scripts/deploy_server.js | |
| @@ -0,0 +1,293 @@ | |
| +#!/usr/bin/env node | |
| + | |
| +const | |
| +temp = require('temp'), | |
| +path = require('path'), | |
| +util = require('util'), | |
| +events = require('events'), | |
| +git = require('./deploy/git.js'), | |
| +https = require('https'), | |
| +vm = require('./deploy/vm.js'), | |
| +jsel = require('JSONSelect'), | |
| +fs = require('fs'), | |
| +express = require('express'), | |
| +irc = require('irc'); | |
| + | |
| +console.log("deploy server starting up"); | |
| + | |
| +// a class capable of deploying and emmitting events along the way | |
| +function Deployer() { | |
| + events.EventEmitter.call(this); | |
| + | |
| + // a directory where we'll keep code | |
| + this._codeDir = process.env['CODE_DIR'] || temp.mkdirSync(); | |
| + console.log("code dir is:", this._codeDir); | |
| + var self = this; | |
| + | |
| + git.init(this._codeDir, function(err) { | |
| + if (err) { | |
| + console.log("can't init code dir:", err); | |
| + process.exit(1); | |
| + } | |
| + self.emit('ready'); | |
| + }); | |
| +} | |
| + | |
| +util.inherits(Deployer, events.EventEmitter); | |
| + | |
| +Deployer.prototype._getLatestRunningSHA = function(cb) { | |
| + var self = this; | |
| + | |
| + // failure is not fatal. maybe nothing is running? | |
| + var fail = function(err) { | |
| + self.emit('info', { msg: "can't get current running sha", reason: err }); | |
| + cb(null, null); | |
| + } | |
| + | |
| + https.get({ host: 'dev.diresworb.org', path: '/ver.txt' }, function(res) { | |
| + var buf = ""; | |
| + res.on('data', function (c) { buf += c }); | |
| + res.on('end', function() { | |
| + try { | |
| + var sha = buf.split(' ')[0]; | |
| + if (sha.length == 7) { | |
| + self.emit('info', 'latest running is ' + sha); | |
| + return cb(null, sha); | |
| + } | |
| + fail('malformed ver.txt: ' + buf); | |
| + } catch(e) { | |
| + fail(e); | |
| + } | |
| + }); | |
| + }).on('error', function(err) { | |
| + fail(err); | |
| + }); | |
| + | |
| +} | |
| + | |
| +Deployer.prototype._cleanUpOldVMs = function() { | |
| + var self = this; | |
| + // what's our sha | |
| + git.currentSHA(self._codeDir, function(err, latest) { | |
| + if (err) return self.emit('info', err); | |
| + vm.list(function(err, r) { | |
| + if (err) return self.emit('info', err); | |
| + // only check the vms that have 'dev.diresworb.org' as a name | |
| + jsel.forEach("object:has(:root > .name:contains(?))", [ "dev.diresworb.org" ], r, function(o) { | |
| + // don't delete the current one | |
| + if (o.name.indexOf(latest) == -1) { | |
| + self.emit('info', 'decommissioning VM: ' + o.name + ' - ' + o.instanceId); | |
| + vm.destroy(o.name, function(err, r) { | |
| + if (err) self.emit('info', 'decomissioning failed: ' + err); | |
| + else self.emit('info', 'decomissioning succeeded of ' + r); | |
| + }) | |
| + } | |
| + }); | |
| + }); | |
| + }); | |
| +} | |
| + | |
| +Deployer.prototype._deployNewCode = function(cb) { | |
| + var self = this; | |
| + | |
| + function splitAndEmit(chunk) { | |
| + if (chunk) chunk = chunk.toString(); | |
| + if (typeof chunk === 'string') { | |
| + chunk.split('\n').forEach(function (line) { | |
| + line = line.trim(); | |
| + if (line.length) self.emit('progress', line); | |
| + }); | |
| + } | |
| + } | |
| + | |
| + var npmInstall = spawn('npm', [ 'install' ], { cwd: self._codeDir }); | |
| + | |
| + npmInstall.stdout.on('data', splitAndEmit); | |
| + npmInstall.stderr.on('data', splitAndEmit); | |
| + | |
| + npmInstall.on('exit', function(code, signal) { | |
| + if (code != 0) { | |
| + self.emit('error', "can't npm install to prepare to run deploy_dev"); | |
| + return; | |
| + } | |
| + var p = spawn('scripts/deploy_dev.js', [], { cwd: self._codeDir }); | |
| + | |
| + p.stdout.on('data', splitAndEmit); | |
| + p.stderr.on('data', splitAndEmit); | |
| + | |
| + p.on('exit', function(code, signal) { | |
| + return cb(code != 0); | |
| + }); | |
| + }); | |
| +}; | |
| + | |
| +Deployer.prototype._pullLatest = function(cb) { | |
| + var self = this; | |
| + git.pull(this._codeDir, 'git://github.com/mozilla/browserid', 'dev', function(l) { | |
| + self.emit('progress', l); | |
| + }, function(err) { | |
| + if (err) return cb(err); | |
| + git.currentSHA(self._codeDir, function(err, latest) { | |
| + if (err) return cb(err); | |
| + self.emit('info', 'latest available sha is ' + latest); | |
| + self._getLatestRunningSHA(function(err, running) { | |
| + if (latest != running) { | |
| + self.emit('deployment_begins', { | |
| + sha: latest, | |
| + }); | |
| + var startTime = new Date(); | |
| + | |
| + self._deployNewCode(function(err, res) { | |
| + if (err) return cb(err); | |
| + // deployment is complete! | |
| + self.emit('deployment_complete', { | |
| + sha: latest, | |
| + time: (new Date() - startTime) | |
| + }); | |
| + // finally, let's clean up old servers | |
| + self._cleanUpOldVMs(); | |
| + cb(null, null); | |
| + }); | |
| + } else { | |
| + self.emit('info', 'up to date'); | |
| + cb(null, null); | |
| + } | |
| + }); | |
| + }); | |
| + }); | |
| +} | |
| + | |
| +// may be invoked any time we suspect updates have occured to re-deploy | |
| +// if needed | |
| +Deployer.prototype.checkForUpdates = function() { | |
| + var self = this; | |
| + | |
| + if (this._busy) return; | |
| + | |
| + this._busy = true; | |
| + self.emit('info', 'checking for updates'); | |
| + | |
| + self._pullLatest(function(err, sha) { | |
| + if (err) self.emit('error', err); | |
| + self._busy = false; | |
| + }); | |
| +} | |
| + | |
| +var deployer = new Deployer(); | |
| + | |
| +var currentLogFile = null; | |
| +// a directory where we'll keep deployment logs | |
| +var deployLogDir = process.env['DEPLOY_LOG_DIR'] || temp.mkdirSync(); | |
| + | |
| +var deployingSHA = null; | |
| + | |
| +console.log("deployment log dir is:", deployLogDir); | |
| + | |
| +[ 'info', 'ready', 'error', 'deployment_begins', 'deployment_complete', 'progress' ].forEach(function(evName) { | |
| + deployer.on(evName, function(data) { | |
| + if (typeof data != 'string') data = JSON.stringify(data, null, 2); | |
| + var msg = evName + ": " + data; | |
| + console.log(msg) | |
| + if (currentLogFile) currentLogFile.write(msg + "\n"); | |
| + }); | |
| +}); | |
| + | |
| +// irc integration! | |
| +var ircClient = null; | |
| +const ircChannel = '#identity'; | |
| +function ircSend(msg) { | |
| + if (!ircClient) { | |
| + ircClient = new irc.Client('irc.mozilla.org', 'browserid_deployer', { | |
| + channels: [ircChannel] | |
| + }); | |
| + ircClient.on('error', function(e) { | |
| + console.log('irc error: ', e); | |
| + }); | |
| + ircClient.once('join' + ircChannel, function(e) { | |
| + ircClient.say(ircChannel, msg); | |
| + }); | |
| + } else { | |
| + ircClient.say(ircChannel, msg); | |
| + } | |
| +} | |
| + | |
| +function ircDisconnect() { | |
| + setTimeout(function() { | |
| + if (ircClient) { | |
| + ircClient.disconnect(); | |
| + ircClient = null; | |
| + } | |
| + }, 1000); | |
| +} | |
| + | |
| + | |
| +// now when deployment begins, we log all events | |
| +deployer.on('deployment_begins', function(r) { | |
| + currentLogFile = fs.createWriteStream(path.join(deployLogDir, r.sha + ".txt")); | |
| + currentLogFile.write("deployment of " + r.sha + " begins\n"); | |
| + deployingSHA = r.sha; | |
| + ircSend("deploying " + r.sha + " - status https://deployer.hacksign.in/" + r.sha + ".txt"); | |
| +}); | |
| + | |
| +function closeLogFile() { | |
| + if (currentLogFile) { | |
| + currentLogFile.end(); | |
| + currentLogFile = null; | |
| + } | |
| +} | |
| + | |
| +deployer.on('deployment_complete', function(r) { | |
| + ircSend("deployment of " + deployingSHA + " completed successfully in " + | |
| + (r.time / 1000.0).toFixed(2) + "s"); | |
| + ircDisconnect(); | |
| + | |
| + closeLogFile(); | |
| + deployingSHA = null; | |
| + | |
| + // always check to see if we should try another deployment after one succeeds to handle rapid fire | |
| + // commits | |
| + deployer.checkForUpdates(); | |
| +}); | |
| + | |
| +deployer.on('error', function(r) { | |
| + ircSend("deployment of " + deployingSHA + " failed. check logs for deets"); | |
| + ircDisconnect(); | |
| + | |
| + closeLogFile(); | |
| + deployingSHA = null; | |
| + | |
| + // on error, try again in 2 minutes | |
| + setTimeout(function () { | |
| + deployer.checkForUpdates(); | |
| + }, 2 * 60 * 1000); | |
| +}); | |
| + | |
| + | |
| +// we check every 3 minutes no mattah what. (checks are cheap, github webhooks are flakey) | |
| +setInterval(function () { | |
| + deployer.checkForUpdates(); | |
| +}, (1000 * 60 * 3)); | |
| + | |
| +// check for updates at startup | |
| +deployer.on('ready', function() { | |
| + deployer.checkForUpdates(); | |
| + | |
| + var app = express.createServer(); | |
| + | |
| + app.get('/check', function(req, res) { | |
| + deployer.checkForUpdates(); | |
| + res.send('ok'); | |
| + }); | |
| + | |
| + app.get('/', function(req, res) { | |
| + var what = "idle"; | |
| + if (deployingSHA) what = "deploying " + deployingSHA; | |
| + res.send(what); | |
| + }); | |
| + | |
| + app.use(express.static(deployLogDir)); | |
| + | |
| + app.listen(process.env['PORT'] || 8080, function() { | |
| + console.log("deploy server bound"); | |
| + }); | |
| +}); | |
| diff --git a/tests/i18n-tests.js b/tests/i18n-tests.js | |
| index 9001546..4add4f4 100755 | |
| --- a/tests/i18n-tests.js | |
| +++ b/tests/i18n-tests.js | |
| @@ -61,7 +61,7 @@ suite.addBatch({ | |
| var supported = ['af', 'en-US', 'pa']; | |
| var def = 'en-US'; | |
| return i18n.bestLanguage( | |
| - i18n.parseAcceptLanguage(accept), | |
| + parseAcceptLanguage(accept), | |
| supported, def); | |
| }, | |
| "For Punjabi": function (err, locale) { | |
| @@ -74,7 +74,7 @@ suite.addBatch({ | |
| var supported = ['af', 'en-US', 'pa']; | |
| var def = 'en-US'; | |
| return i18n.bestLanguage( | |
| - i18n.parseAcceptLanguage(accept), | |
| + parseAcceptLanguage(accept), | |
| supported, def); | |
| }, | |
| "For Punjabi (India) serve Punjabi": function (err, locale) { | |
| @@ -87,7 +87,7 @@ suite.addBatch({ | |
| var supported = ['af', 'en-US', 'pa-IT']; | |
| var def = 'en-US'; | |
| return i18n.bestLanguage( | |
| - i18n.parseAcceptLanguage(accept), | |
| + parseAcceptLanguage(accept), | |
| supported, def); | |
| }, | |
| "Don't choose Punjabi (India)": function (err, locale) { | 
  
    Sign up for free
    to join this conversation on GitHub.
    Already have an account?
    Sign in to comment