v1.0.0
This commit is contained in:
parent
19b38f5cbf
commit
c16ce07792
104
README.md
104
README.md
|
@ -1,2 +1,102 @@
|
||||||
# node-authenticator
|
Node.js Authenticator
|
||||||
Two- / Multi- Factor Authenication (2FA / MFA) for node.js
|
=====================
|
||||||
|
|
||||||
|
Two- and Multi- Factor Authenication (2FA / MFA) for node.js
|
||||||
|
|
||||||
|
There are a number of apps that various websites use to give you 6-digit codes to increase security when you log in:
|
||||||
|
|
||||||
|
* Authy [iPhone](https://itunes.apple.com/us/app/authy/id494168017?mt=8) [Android](https://play.google.com/store/apps/details?id=com.authy.authy&hl=en) [Chrome](https://chrome.google.com/webstore/detail/authy/gaedmjdfmmahhbjefcbgaolhhanlaolb?hl=en) [Linux](https://www.authy.com/personal/) [OS X](https://www.authy.com/personal/) [BlackBerry](https://appworld.blackberry.com/webstore/content/38831914/?countrycode=US&lang=en)
|
||||||
|
* Google Authenticator [iPhone](https://itunes.apple.com/us/app/google-authenticator/id388497605?mt=8) [Android](https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&hl=en)
|
||||||
|
* Microsoft Authenticator [Windows Phone](https://www.microsoft.com/en-us/store/apps/authenticator/9wzdncrfj3rj) [Android](https://play.google.com/store/apps/details?id=com.microsoft.msa.authenticator) [iPhone]()
|
||||||
|
* GAuth [FxOS](https://marketplace.firefox.com/app/gauth/)
|
||||||
|
|
||||||
|
There are many [Services that Support MFA](http://lifehacker.com/5938565/heres-everywhere-you-should-enable-two-factor-authentication-right-now),
|
||||||
|
including Google, Microsoft, Facebook, Digital Ocean, for starters.
|
||||||
|
|
||||||
|
This module uses [`notp`](https://github.com/guyht/notp) which implements `TOTP` [(RFC 6238)](https://www.ietf.org/rfc/rfc6238.txt)
|
||||||
|
(the *Authenticator* standard), which is based on `HOTP` [(RFC 4226)](https://www.ietf.org/rfc/rfc4226.txt)
|
||||||
|
to provide codes that are exactly compatible with all other *Authenticator* apps and services that use them.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install authenticator --save
|
||||||
|
```
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var authenticator = require('authenticator');
|
||||||
|
|
||||||
|
var formattedKey = authenticator.generateKey();
|
||||||
|
// "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz"
|
||||||
|
|
||||||
|
var formattedToken = authenticator.generateToken(formattedKey);
|
||||||
|
// "957 124"
|
||||||
|
|
||||||
|
authenticator.verifyToken(formattedKey, formattedToken);
|
||||||
|
// { delta: 0 }
|
||||||
|
|
||||||
|
authenticator.verifyToken(formattedKey, '000 000');
|
||||||
|
// null
|
||||||
|
```
|
||||||
|
|
||||||
|
QRCode
|
||||||
|
------
|
||||||
|
|
||||||
|
See <https://davidshimjs.github.io/qrcodejs/>
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var el = document.querySelector('.js-qrcode-canvas');
|
||||||
|
var link = "otpauth://totp/{{NAME}}?secret={{KEY}}";
|
||||||
|
var name = "Your Service";
|
||||||
|
// remove spaces, hyphens, equals, whatever
|
||||||
|
var key = "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz".replace(/\W/g, '').toLowerCase();
|
||||||
|
|
||||||
|
var qr = new QRCode(el, {
|
||||||
|
text: link.replace(/{{NAME}}/g, name).replace(/{{KEY}}/g, key)
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Formatting
|
||||||
|
----------
|
||||||
|
|
||||||
|
All non-alphanumeric characters are ignored, so you could just as well use hyphens
|
||||||
|
or periods or whatever suites your use case.
|
||||||
|
|
||||||
|
These are just as valid:
|
||||||
|
|
||||||
|
* "acqo ua72 d3yf a4e5 - uorx ztkh j2xl 3wiz"
|
||||||
|
* "98.24.63"
|
||||||
|
|
||||||
|
0, 1, 8, and 9 also not used (so that base32).
|
||||||
|
To further avoid confusion with O, o, L, l, I, B, and g
|
||||||
|
you may wish to display lowercase instead of uppercase.
|
||||||
|
|
||||||
|
TODO: should this library replace 0 with o, 1 with l (or I?), 8 with b, 9 with g, and so on?
|
||||||
|
|
||||||
|
90-second Window
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The window is set to +/- 1, meaning each token is valid for a total of 90 seconds
|
||||||
|
(-30 seconds, +0 seconds, and +30 seconds)
|
||||||
|
to account for time drift (which should be very rare for mobile devices)
|
||||||
|
and humans who are handicapped or otherwise struggle with quick fine motor skills (like my grandma).
|
||||||
|
|
||||||
|
|
||||||
|
Why not SpeakEasy?
|
||||||
|
------------------
|
||||||
|
|
||||||
|
I took a look at the code and I didn't feel comfortable using it.
|
||||||
|
|
||||||
|
For any module related to security I want to see that the code is clean,
|
||||||
|
well-maintained, and that any security-related bugs are addressed.
|
||||||
|
|
||||||
|
The author was obviously not well-versed in JavaScript at the time
|
||||||
|
that he wrote it and it hasn't been cleaned up since.
|
||||||
|
Also, the author hasn't been responsive to issues and pull requests.
|
||||||
|
|
||||||
|
The notp author has been responsive, but notp doesn't do everything I would like.
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var crypto = require('crypto');
|
||||||
|
var b32 = require('thirty-two');
|
||||||
|
|
||||||
|
// Generate a key
|
||||||
|
function generateOtpKey() {
|
||||||
|
// 20 cryptographically random binary bytes (160-bit key)
|
||||||
|
var key = crypto.randomBytes(20);
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = b32.encode(bin).toString('utf8').replace(/=/g, '');
|
||||||
|
|
||||||
|
// lowercase with a space every 4 characters
|
||||||
|
var key = base32.toLowerCase().replace(/(\w{4})/g, "$1 ").trim();
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateGoogleAuthKey() {
|
||||||
|
return encodeGoogleAuthKey(generateOtpKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = b32.decode(unformatted);
|
||||||
|
|
||||||
|
return bin;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a Google Auth Token
|
||||||
|
function generateGoogleAuthToken(key) {
|
||||||
|
var bin = decodeGoogleAuthKey(key);
|
||||||
|
var notp = require('notp');
|
||||||
|
|
||||||
|
return notp.totp.gen(bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify a Google Auth Token
|
||||||
|
function verifyGoogleAuthToken(key, token) {
|
||||||
|
var bin = decodeGoogleAuthKey(key);
|
||||||
|
var notp = require('notp');
|
||||||
|
|
||||||
|
token = token.replace(/\W+/g, '');
|
||||||
|
|
||||||
|
// window is +/- 1 period of 30 seconds
|
||||||
|
return notp.totp.verify(token, bin, { window: 1, time: 30 });
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.generateKey = generateGoogleAuthKey;
|
||||||
|
module.exports.generateToken = generateGoogleAuthToken;
|
||||||
|
module.exports.verifyToken = verifyGoogleAuthToken;
|
|
@ -0,0 +1,24 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var authenticator = require('./authenticator');
|
||||||
|
|
||||||
|
var formattedKey = authenticator.generateKey();
|
||||||
|
console.log(formattedKey);
|
||||||
|
// "acqo ua72 d3yf a4e5 uorx ztkh j2xl 3wiz"
|
||||||
|
|
||||||
|
var formattedToken = authenticator.generateToken(formattedKey);
|
||||||
|
console.log(formattedToken);
|
||||||
|
// "957 124"
|
||||||
|
|
||||||
|
var result = authenticator.verifyToken(formattedKey, formattedToken);
|
||||||
|
console.log(result);
|
||||||
|
// { delta: 0 }
|
||||||
|
|
||||||
|
result = authenticator.verifyToken(formattedKey, '000-000');
|
||||||
|
console.log(result);
|
||||||
|
// null
|
||||||
|
|
||||||
|
// result will allways be one of
|
||||||
|
// (failure) null
|
||||||
|
// (success) { delta: -1 }, { delta: 0 }, or { delta: 1 }
|
||||||
|
// delta lets you know which way time drift is occurring
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "authenticator",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Two- / Multi- Factor Authenication (2FA / MFA) for node.js",
|
||||||
|
"main": "authenticator.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "node example.js"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Daplie/node-authenticator.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"authenticator",
|
||||||
|
"2fa",
|
||||||
|
"mfa",
|
||||||
|
"token",
|
||||||
|
"key",
|
||||||
|
"base32",
|
||||||
|
"code",
|
||||||
|
"generator",
|
||||||
|
"authy",
|
||||||
|
"google",
|
||||||
|
"microsoft"
|
||||||
|
],
|
||||||
|
"author": "AJ ONeal <coolaj86@gmail.com> (http://coolaj86.com/)",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Daplie/node-authenticator/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Daplie/node-authenticator#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"notp": "^2.0.3",
|
||||||
|
"thirty-two": "0.0.2"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue