Merge branch 'design' into gh-pages

This commit is contained in:
AJ ONeal 2015-10-26 13:51:57 -07:00
commit ba4f458756
10 changed files with 648 additions and 49 deletions

5
bootstrap-v3.3.5.min.css vendored Normal file

File diff suppressed because one or more lines are too long

BIN
daplie-logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

BIN
iPhoneMockup.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

6
jquery-2.0.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

173
jquery.countdown.js Normal file
View File

@ -0,0 +1,173 @@
;(function ($, window, document, undefined) {
var pluginName = "countdown360",
defaults = {
radius: 15.5, // radius of arc
strokeStyle: "#9990F0", // the color of the stroke
strokeWidth: 2, // the stroke width, dynamically calulated if omitted in options
fillStyle: "#FFFFFF", // the fill color
fontColor: "#477050", // the font color
fontFamily: "Helvetica Neue", // the font family
fontSize: 28, // the font size, dynamically calulated if omitted in options
fontWeight: 100, // the font weight
autostart: true, // start the countdown automatically
seconds: 30, // the number of seconds to count down
label: ["second", "seconds"], // the label to use or false if none
startOverAfterAdding: true, // Start the timer over after time is added with addSeconds
onComplete: function () {}
};
function Plugin(element, options) {
this.element = element;
this.settings = $.extend({}, defaults, options);
if (!this.settings.fontSize) { this.settings.fontSize = this.settings.radius/1.2; }
if (!this.settings.strokeWidth) { this.settings.strokeWidth = this.settings.radius/4; }
this._defaults = defaults;
this._name = pluginName;
this._init();
}
Plugin.prototype = {
getTimeRemaining: function()
{
var timeRemaining = this._secondsLeft(this.getElapsedTime());
return timeRemaining;
},
getElapsedTime: function()
{
return Math.round((new Date().getTime() - this.startedAt.getTime())/1000);
},
extendTimer: function (value) {
var seconds = parseInt(value),
secondsElapsed = Math.round((new Date().getTime() - this.startedAt.getTime())/1000);
if ((this._secondsLeft(secondsElapsed) + seconds) <= this.settings.seconds) {
this.startedAt.setSeconds(this.startedAt.getSeconds() + parseInt(value));
}
},
addSeconds: function (value) {
var secondsElapsed = Math.round((new Date().getTime() - this.startedAt.getTime())/1000);
if (this.settings.startOverAfterAdding) {
this.settings.seconds = this._secondsLeft(secondsElapsed) + parseInt(value);
this.start();
} else {
this.settings.seconds += parseInt(value);
}
},
start: function (date) {
this.updateInterval = 25;
this.startedAt = date || new Date();
this._drawCountdownShape(Math.PI*3.5, true);
this._drawCountdownLabel(0);
this._interval = setInterval(jQuery.proxy(this._draw, this), this.updateInterval);
},
stop: function (cb) {
clearInterval(this._interval);
if (cb) { cb(); }
},
_init: function () {
this.settings.width = (this.settings.radius * 2) + (this.settings.strokeWidth * 2);
this.settings.height = this.settings.width;
this.settings.arcX = this.settings.radius + this.settings.strokeWidth;
this.settings.arcY = this.settings.arcX;
this._initPen(this._getCanvas());
if (this.settings.autostart) { this.start(); }
},
_getCanvas: function () {
var $canvas = $("<canvas id=\"countdown360_" + $(this.element).attr("id") + "\" width=\"" +
this.settings.width + "\" height=\"" +
this.settings.height + "\">" +
"<span id=\"countdown-text\" role=\"status\" aria-live=\"assertive\"></span></canvas>");
$(this.element).prepend($canvas[0]);
return $canvas[0];
},
_initPen: function (canvas) {
this.pen = canvas.getContext("2d");
this.pen.lineWidth = this.settings.strokeWidth;
this.pen.strokeStyle = this.settings.strokeStyle;
this.pen.fillStyle = this.settings.fillStyle;
this.pen.textAlign = "center";
this.pen.textBaseline = "middle";
this.ariaText = $(canvas).children("#countdown-text");
this._clearRect();
},
_clearRect: function () {
this.pen.clearRect(0, 0, this.settings.width, this.settings.height);
},
_secondsLeft: function(secondsElapsed) {
return this.settings.seconds - secondsElapsed;
},
_drawCountdownLabel: function (secondsElapsed) {
this.ariaText.text(secondsLeft);
this.pen.font = this.settings.fontWeight + " " + this.settings.fontSize + "px " + this.settings.fontFamily;
var secondsLeft = this._secondsLeft(secondsElapsed),
label = secondsLeft === 1 ? this.settings.label[0] : this.settings.label[1],
drawLabel = this.settings.label && this.settings.label.length === 2,
x = this.settings.width/2;
if (drawLabel) {
y = this.settings.height/2 - (this.settings.fontSize/6.2);
} else {
y = this.settings.height/2;
}
this.pen.fillStyle = this.settings.fillStyle;
this.pen.fillText(secondsLeft + 1, x, y);
this.pen.fillStyle = this.settings.fontColor;
this.pen.fillText(secondsLeft, x, y);
if (drawLabel) {
this.pen.font = "normal small-caps " + (this.settings.fontSize/3) + "px " + this.settings.fontFamily;
this.pen.fillText(label, this.settings.width/2, this.settings.height/2 + (this.settings.fontSize/2.2));
}
},
_drawCountdownShape: function (endAngle, drawStroke) {
this.pen.fillStyle = this.settings.fillStyle;
this.pen.beginPath();
this.pen.arc(this.settings.arcX, this.settings.arcY, this.settings.radius, Math.PI*1.5, endAngle, false);
this.pen.fill();
if (drawStroke) { this.pen.stroke(); }
},
_draw: function () {
var secondsElapsed = Math.round((new Date().getTime() - this.startedAt.getTime())/1000);
var milisecondsElapsed = Math.round((Date.now() - this.startedAt.getTime()));
var whole = (Math.PI*2)/(this.settings.seconds * 1000);
var parts = milisecondsElapsed;
var endAngle = (Math.PI*3.5)
- ( (whole) * parts);
//console.log('endAngle', endAngle);
this._clearRect();
this._drawCountdownShape(Math.PI*3.5, false);
if (secondsElapsed < this.settings.seconds) {
this._drawCountdownShape(endAngle, true);
this._drawCountdownLabel(secondsElapsed);
} else {
this._drawCountdownLabel(this.settings.seconds);
this.stop();
this.settings.onComplete();
}
}
};
$.fn[pluginName] = function (options) {
var plugin;
this.each(function() {
plugin = $.data(this, "plugin_" + pluginName);
if (!plugin) {
plugin = new Plugin(this, options);
$.data(this, "plugin_" + pluginName, plugin);
}
});
return plugin;
};
})(jQuery, window, document);

52
phone.html Normal file
View File

@ -0,0 +1,52 @@
<!DOCTYPE html>
<html>
<head>
<title>Authenticator - Daplie, Inc</title>
<meta charset="UTF-8">
<!--meta name="viewport" content="width=device-width, user-scalable=no" /-->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' https://chart.googleapis.com/ data:; child-src 'self'; object-src 'none'">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body class="fade">
<div class="phone-screen">
<div class="logo"></div>
<div class="token-issuer uppercase">your <span class="js-issuer">Company</span> token is:</div>
<div class="token-phone"> <span class="js-token">--- ---</span> </div>
<div class="js-countdown countdown"></div>
<div class="js-account-name account-name">123@abc.xyz</div>
</div>
<!-- extremely lightweight shim for hex conversion -->
<script src="bower_components/unibabel/index.js"></script>
<script src="bower_components/unibabel/unibabel.hex.js"></script>
<!-- base32 conversion (and binary string for forge) (works standalone from the above) -->
<script src="bower_components/unibabel/unibabel.base32.js"></script>
<!-- forge.hmac -->
<script src="bower_components/forge/js/util.js"></script>
<script src="bower_components/forge/js/sha1.js"></script>
<script src="bower_components/forge/js/hmac.js"></script>
<!-- forge.random.getBytes -->
<script src="bower_components/forge/js/sha256.js"></script>
<script src="bower_components/forge/js/cipher.js"></script>
<script src="bower_components/forge/js/cipherModes.js"></script>
<script src="bower_components/forge/js/aes.js"></script>
<script src="bower_components/forge/js/prng.js"></script>
<script src="bower_components/forge/js/random.js"></script>
<!-- botp.totp -->
<script src="bower_components/botp/sha1-hmac.js"></script>
<script src="bower_components/botp/index.js"></script>
<!-- Authenticator -->
<script src="authenticator.js"></script>
<script src="jquery-2.0.0.min.js" type="text/javascript"></script>
<script src="jquery.countdown.js" type="text/javascript"></script>
<!-- The Magic -->
<script src="phone.js" type="text/javascript"></script>
</body>
</html>

113
phone.js Normal file
View File

@ -0,0 +1,113 @@
(function (exports) {
'use strict';
$('body').addClass('in');
var Authenticator = exports.Authenticator;
function parseQuery(search) {
var args = search.substring(1).split('&');
var argsParsed = {};
var i, arg, kvp, key, value;
for (i=0; i < args.length; i++) {
arg = args[i];
if (-1 === arg.indexOf('=')) {
argsParsed[decodeURIComponent(arg).trim()] = true;
}
else {
kvp = arg.split('=');
key = decodeURIComponent(kvp[0]).trim();
value = decodeURIComponent(kvp[1]).trim();
argsParsed[key] = value;
}
}
return argsParsed;
}
/*
function parseQuery(search) {
var args = search.substring(1).split('&');
var argsParsed = {};
var i;
console.log('parse args', args);
for (i = 0; i < args.length; i++) {
var arg = args[i];
if (-1 === arg.indexOf('=')) {
argsParsed[decodeURIComponent(arg).trim()] = true;
}
else {
var kvp = arg.split('=');
argsParsed[decodeURIComponent(kvp[0]).trim()] = decodeURIComponent(kvp[1]).trim();
}
}
return argsParsed;
}
*/
function run() {
var countdown = $(".js-countdown").countdown360({
radius: 30,
seconds: 30,
fontColor: '#000',
autostart: false,
onComplete: function() {
console.log('done');
run();
}
});
// TODO change to token start time, regardless of the time the app began
countdown.start(new Date());
console.log('countdown360 ', countdown);
console.log(document.location.search);
var otpauth = parseQuery(document.location.search).otpuri;
var otplink = document.createElement('a');
var otp;
var meta;
var issuer;
var accountName;
otplink.href = otpauth;
otp = parseQuery(otplink.search);
meta = otplink.pathname.replace(/.*\/totp\//, '').split(':');
// TODO throw if otp.issuer !== decodeURI(meta[0])
if (meta.length > 1) {
issuer = otp.issuer || decodeURI(meta[0]);
accountName = decodeURI(meta[1]);
}
else {
issuer = otp.issuer;
accountName = decodeURI(meta[0]);
}
console.log('otpuri', otpauth);
console.log('otplink', otplink);
console.log('otplink.search', otplink.search);
console.log('otp', otp);
$('.js-issuer').text(issuer);
$('.js-account-name').text(accountName);
Authenticator.generateToken(otp.secret).then(function (token) {
$('.js-token').text(token.replace(/(\d{3})/g, '$1 ').trim());
});
}
run();
}(window));

205
style.css Normal file
View File

@ -0,0 +1,205 @@
.col-xs-6 {
text-align: center;
}
.authenticator {
z-index: 2;
font-size: 32px;
line-height: 1.0556;
font-weight: 100;
letter-spacing: .023em;
text-align: center;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
}
.left {
margin-top: 170px;
padding: 0 0 0 15%;
}
.qrcode {
z-index: 2;
}
.scan {
z-index: 2;
font-size: 13px;
line-height: 1.0556;
font-weight: 200;
letter-spacing: .023em;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
}
.verify {
z-index: 2;
font-size: 13px;
line-height: 1.0556;
font-weight: 200;
letter-spacing: .023em;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
margin-top: 20px;
}
.token-label {
font-size: 14px;
line-height: 1.0556;
font-weight: 200;
letter-spacing: .023em;
text-align: center;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
margin-left: -100px;
}
input {
margin: 10px 10px 5px 0;
width: 165px;
padding: 5px 20px;
border-radius: 10px;
font-size: 16px;
line-height: 1.0556;
font-weight: 200;
letter-spacing: .023em;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
}
.btn {
background: -webkit-linear-gradient(#6D99ED, #7464ED);
color: white;
border-radius: 5px;
font-size: 18px;
border: none;
padding: 5px 20px;
vertical-align: inherit;
}
.btn:hover {
color: #fff;
}
.right {
margin-top:265px;
position: relative;
}
.iPhone {
max-width: 400px;
position: absolute;
top: -250px;
left: 17%;
}
.iframe {
width: 265px;
height: 450px;
border: none !important;
z-index: 2;
}
.logo {
background-image: url("daplie-logo.png");
background-repeat: no-repeat;
background-size: 200px 50px;
width: 200px;
height: 50px;
margin: 50px auto 0 auto;
line-height: 1.0556;
text-align: center;
z-index: 2;
}
.uppercase {
text-transform: uppercase;
}
.token-issuer {
margin-top: 50px;
z-index: 2;
font-size: 12px;
line-height: 1.0556;
font-weight: 200;
letter-spacing: .023em;
text-align: center;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
}
.account-name {
margin-top: 80px;
z-index: 2;
font-size: 18px;
line-height: 1.0556;
font-weight: 200;
letter-spacing: .023em;
text-align: center;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
margin-bottom: 10px;
}
.token-phone {
margin-top: 20px;
z-index: 2;
font-size: 48px;
line-height: 1.0556;
font-weight: 100;
letter-spacing: .043em;
text-align: center;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
}
.countdown {
text-align: center;
margin-top: 40px;
}
.bottom {
margin-top: 220px;
}
.wide {
width: 340px;
}
.console {
z-index: 2;
text-align: center;
font-size: 32px;
line-height: 1.0556;
font-weight: 100;
letter-spacing: .023em;
font-style: normal;
font-family: "Myriad Set Pro","Helvetica Neue","Helvetica","Arial",sans-serif;
}
hr {
border: 0;
height: 1px;
background: #333;
background-image: linear-gradient(to right, #ccc, #333, #ccc);
}
.iframe-container {
max-width: 400px;
position: absolute;
top: -120px;
left: 17%;
width: 400px;
height: 600px;
}
.fade {
opacity: 0;
}
.in {
opacity: 1;
transition: opacity .5s ease-in-out;
-moz-transition: opacity .5s ease-in-out;
-webkit-transition: opacity .5s ease-in-out;
}

View File

@ -1,42 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<title>BOTP Test</title>
<title>Authenticator - Daplie, Inc</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
<!--meta name="viewport" content="width=device-width, user-scalable=no" /-->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src 'self' https://chart.googleapis.com/ data:; child-src 'self'; object-src 'none'">
<link rel="stylesheet" type="text/css" href="bootstrap-v3.3.5.min.css">
<link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
<h1>Authenticator Test</h1>
<div>
Test with the <a href="https://www.authy.com/personal/mobile/" target="_blank">Authy App</a>.
<body class="fade">
<div class="container-fluid">
<div class="row">
<div class="col-xs-6 left">
<h1 class="authenticator">Authenticator Test</h1>
<img alt="qrcode" class="js-qrcode qrcode" width="166" height="166" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />
<div class="scan">
Scan with <a href="https://www.authy.com/personal/mobile/" target="_blank">Authy App</a>
</div>
<div class="verify">
<label class="token-label">Enter Verification Token:</label>
<br />
<input class="js-token token-input" type="text" placeholder="i.e. 123 456" />
<button class="js-verify btn">Verify</button>
</div>
</div>
<br />
<div>
<img alt="qrcode" class="js-qrcode" width="166" height="166" src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" />
<p>20-byte (160-bit) Base32 Key:</p>
<h3 class="js-key">xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx</h3>
<p class="js-otpauth">otpauth://totp/company:user?secret=xxxx</p>
<div class="col-xs-6 right">
<img class="iPhone" src="iPhoneMockup.png" />
<div class="iframe-container">
<iframe class="js-otp-iframe iframe" src="phone.html"></iframe>
</div>
</div>
</div>
</div>
<br />
<div>
<label>Company Name:</label>
<input class="js-company-name" type="text" placeholder="Company Name" />
<label>User Account:</label>
<input class="js-user-account" type="text" placeholder="User Account" />
<button class="js-generate">Regenerate</button>
<div class="container">
<div class="row">
<div class="col-md-3"></div>
<div class="col-md-6 bottom">
<form class="form-horizontal">
<h1 class="console"> Console </h1>
<hr />
<div class="form-group">
<label class="col-sm-3 control-label">Issuer:</label>
<div class="col-sm-9">
<input type="text" class="js-company-name issuer-input wide" placeholder="Company Name">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Account:</label>
<div class="col-sm-9">
<input type="text" class="js-user-account issuer-input wide" placeholder="User Account">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">Key:</label>
<div class="col-sm-9">
<input type="text" class="js-key base-key-input wide">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-6 col-xs-6">
<button type="button" class="btn btn-default js-generate regenerate">Regenerate</button>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">URI:</label>
<div class="col-sm-9">
<p class="js-otpauth">otpauth://totp/company:user?secret=xxxx&amp;issuer=company</p>
</div>
</div>
</form>
</div>
<div class="col-md-3"></div>
</div>
<br />
<div>
<label>Verification Token:</label>
<input class="js-token" type="text" placeholder="ex: 000 000" />
<button class="js-verify">Verify</button>
</div>
<!-- extremely lightweight shim for hex conversion -->

44
test.js
View File

@ -1,7 +1,14 @@
// forgive the suckiness, but whatever
(function (exports) {
'use strict';
'use strict';
window.addEventListener("load", function () {
window.document.body.className += " in";
});
var defaultKey = 'hxdm vjec jjws rb3h wizr 4ifu gftm xboz';
var key;
var Authenticator = exports.Authenticator;
var $ = function (x) {
@ -10,7 +17,19 @@ var $ = function (x) {
function generate(ke) {
Authenticator.generateKey().then(function (k) {
key = ke || k;
var $keyEl = $('.js-key');
if (ke) {
key = ke;
}
else if ($keyEl.value) {
key = $keyEl.value;
$keyEl.placeholder = key;
$keyEl.value = '';
}
else {
key = k;
$keyEl.placeholder = key;
}
var companyName = $('.js-company-name').value;
var userAccount = $('.js-user-account').value;
@ -19,25 +38,13 @@ function generate(ke) {
+ encodeURI(companyName) + ':' + encodeURI(userAccount)
+ '?secret=' + key.replace(/\s+/g, '').toUpperCase()
;
/*
var otpauth = encodeURI('otpauth://totp/'
+ companyName + ':' + userAccount
+ '?secret=') + key.replace(/\s+/g, '').toUpperCase()
;
*/
// TODO figure out the right escaping
/*
var otpauth = 'otpauth://totp/'
+ companyName + ':' + userAccount
+ '?secret=' + key.replace(/\s+/g, '').toUpperCase()
;
*/
// obviously don't use this in production, but it's not so bad for the demo
// (hmm... no one has ever said those words and regretted them... TODO XXX generate QR locally)
var src = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl=' + encodeURIComponent(otpauth);
$('.js-otpauth').innerHTML = otpauth; // safe to inject because I created it
$('.js-key').innerHTML = key; // safe to inject because I created it
$('.js-otpauth').innerHTML = otpauth; // only safe to inject because I created it
$('img.js-qrcode').src = src;
$('.js-otp-iframe').src = 'phone.html?otpuri=' + encodeURIComponent(otpauth);
Authenticator.generateToken(key).then(function (token) {
console.log('token', token);
@ -78,7 +85,8 @@ $('.js-generate').addEventListener('click', function () {
$('.js-company-name').value = 'ACME Co';
$('.js-user-account').value = 'john@example.com';
generate('hxdm vjec jjws rb3h wizr 4ifu gftm xboz');
$('.js-key').placeholder = defaultKey;
generate(defaultKey);
}(
'undefined' !== typeof window ? window : module.exports