working test
This commit is contained in:
parent
c0c0dac081
commit
c38768772a
|
@ -0,0 +1,86 @@
|
||||||
|
(function (exports) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Authenticator = exports.Authenticator || exports;
|
||||||
|
var Unibabel = window.Unibabel || require('unibabel');
|
||||||
|
console.log('window.totp', window.totp);
|
||||||
|
var totp = window.totp || require('notp').totp;
|
||||||
|
|
||||||
|
if (!window.crypto) {
|
||||||
|
document.addEventListener('mousemove', function (event) {
|
||||||
|
var ev = event || window.event;
|
||||||
|
|
||||||
|
window.forge.random.collectInt(ev.pageX, 16);
|
||||||
|
window.forge.random.collectInt(ev.pageY, 16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a key
|
||||||
|
function generateOtpKey() {
|
||||||
|
// 20 cryptographically random binary bytes (160-bit key)
|
||||||
|
if (false && window.crypto) {
|
||||||
|
var key = window.crypto.getRandomValues(new Uint8Array(20));
|
||||||
|
|
||||||
|
return Promise.resolve(key);
|
||||||
|
} else {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
window.forge.random.getBytes(20, function (err, bytes) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(Unibabel.binaryStringToBuffer(bytes));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text-encode the key as base32 (in the style of Google Authenticator - same as Facebook, Microsoft, etc)
|
||||||
|
function encodeGoogleAuthKey(bin) {
|
||||||
|
// 32 ascii characters without trailing '='s
|
||||||
|
var base32 = Unibabel.bufferToBase32(bin).replace(/=/g, '');
|
||||||
|
|
||||||
|
// lowercase with a space every 4 characters
|
||||||
|
var key = base32.toLowerCase().replace(/(\w{4})/g, "$1 ").trim();
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateGoogleAuthKey() {
|
||||||
|
return generateOtpKey().then(encodeGoogleAuthKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binary-decode the key from base32 (Google Authenticator, FB, M$, etc)
|
||||||
|
function decodeGoogleAuthKey(key) {
|
||||||
|
// decode base32 google auth key to binary
|
||||||
|
var unformatted = key.replace(/\W+/g, '').toUpperCase();
|
||||||
|
var bin = Unibabel.base32ToBuffer(unformatted);
|
||||||
|
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a Google Auth Token
|
||||||
|
function generateGoogleAuthToken(key) {
|
||||||
|
var bin = decodeGoogleAuthKey(key);
|
||||||
|
|
||||||
|
return totp.gen(bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a Google Auth Token
|
||||||
|
function verifyGoogleAuthToken(key, token) {
|
||||||
|
var bin = decodeGoogleAuthKey(key);
|
||||||
|
|
||||||
|
token = token.replace(/\W+/g, '');
|
||||||
|
|
||||||
|
// window is +/- 1 period of 30 seconds
|
||||||
|
return totp.verify(token, bin, { window: 1, time: 30 });
|
||||||
|
}
|
||||||
|
|
||||||
|
Authenticator.generateKey = generateGoogleAuthKey;
|
||||||
|
Authenticator.generateToken = generateGoogleAuthToken;
|
||||||
|
Authenticator.verifyToken = verifyGoogleAuthToken;
|
||||||
|
|
||||||
|
}(
|
||||||
|
'undefined' !== typeof window ? (window.Authenticator = {}) : module.exports
|
||||||
|
));
|
|
@ -0,0 +1,66 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>BOTP Test</title>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Authenticator Test</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
Test with the <a href="https://www.authy.com/personal/mobile/" target="_blank">Authy App</a>.
|
||||||
|
</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>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div>
|
||||||
|
<label>Verification Token:</label>
|
||||||
|
<input class="js-token" type="text" placeholder="ex: 000 000" />
|
||||||
|
|
||||||
|
<button class="js-verify">Verify</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- base32 conversion (and binary string for forge) -->
|
||||||
|
<script src="bower_components/unibabel/index.js"></script>
|
||||||
|
<script src="bower_components/unibabel/unibabel.hex.js"></script>
|
||||||
|
<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="test.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,71 @@
|
||||||
|
// forgive the suckiness, but whatever
|
||||||
|
(function (exports) {
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var key;
|
||||||
|
var Authenticator = exports.Authenticator;
|
||||||
|
var $ = function (x) {
|
||||||
|
return document.querySelector(x);
|
||||||
|
};
|
||||||
|
|
||||||
|
function generate(ke) {
|
||||||
|
Authenticator.generateKey().then(function (k) {
|
||||||
|
key = ke || k;
|
||||||
|
|
||||||
|
var companyName = $('.js-company-name').value;
|
||||||
|
var userAccount = $('.js-user-account').value;
|
||||||
|
|
||||||
|
// obviously don't use this in production, but it's not so bad for the demo
|
||||||
|
var src = 'https://www.google.com/chart?chs=166x166&chld=L|0&cht=qr&chl='
|
||||||
|
+ encodeURIComponent(
|
||||||
|
'otpauth://totp/'
|
||||||
|
+ encodeURIComponent(companyName)
|
||||||
|
+ ':'
|
||||||
|
+ encodeURIComponent(userAccount)
|
||||||
|
+ '?secret='
|
||||||
|
+ key.replace(/\s+/g, '').toUpperCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
$('.js-key').innerHTML = key; // safe to inject because I created it
|
||||||
|
$('img.js-qrcode').src = src;
|
||||||
|
|
||||||
|
Authenticator.generateToken(key).then(function (token) {
|
||||||
|
console.log('token', token);
|
||||||
|
|
||||||
|
Authenticator.verifyToken(key, token).then(function (result) {
|
||||||
|
console.log('verify', result);
|
||||||
|
|
||||||
|
Authenticator.verifyToken(key, '000 000').then(function (result) {
|
||||||
|
console.log('reject', result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$('.js-verify').addEventListener('click', function () {
|
||||||
|
var token = $('.js-token').value;
|
||||||
|
|
||||||
|
Authenticator.verifyToken(key, token).then(function (result) {
|
||||||
|
var msg;
|
||||||
|
if (result) {
|
||||||
|
msg = 'Correct!';
|
||||||
|
} else {
|
||||||
|
msg = 'FAIL!';
|
||||||
|
}
|
||||||
|
|
||||||
|
window.alert(msg);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.js-generate').addEventListener('click', function () {
|
||||||
|
generate(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.js-company-name').value = 'ACME Co';
|
||||||
|
$('.js-user-account').value = 'john@example.com';
|
||||||
|
generate('hxdm vjec jjws rb3h wizr 4ifu gftm xboz');
|
||||||
|
|
||||||
|
}(
|
||||||
|
'undefined' !== typeof window ? window : module.exports
|
||||||
|
));
|
Loading…
Reference in New Issue