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