Skip to content

Commit f42eef5

Browse files
authored
Merge pull request #8 from danielgraycode/master
Add documentation to the module
2 parents 28e8398 + f933155 commit f42eef5

1 file changed

Lines changed: 127 additions & 43 deletions

File tree

lib/2FA.js

Lines changed: 127 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,56 @@
1-
var crypto = require('crypto');
2-
var base32 = require('thirty-two');
3-
var QR = require('qr-image');
4-
5-
var TFA = module.exports = {};
6-
7-
// get a base36 crypto secure key
1+
var crypto = require("crypto");
2+
var base32 = require("thirty-two");
3+
var QR = require("qr-image");
4+
5+
var TFA = (module.exports = {});
6+
7+
/**
8+
* @description
9+
* Generates the key which can be used to make a QR code for the user to add to their authenticator apps and check codes against.
10+
* @param length The length desired for the key.
11+
* @param cb A callback function you wish to pass the results of the function to.
12+
* @example
13+
* tfa.generateKey(32, function(err, key) {
14+
* //Store key and generate a QR code so users can add it to their authenticator app.
15+
* })
16+
*/
817
TFA.generateKey = function(length, cb) {
9-
if (!cb && typeof length === 'function') {
18+
if (!cb && typeof length === "function") {
1019
cb = length;
1120
length = 20;
1221
}
1322

14-
var key = '';
23+
var key = "";
1524
var get = function() {
1625
// 7 bytes (14 char) is the max JS can handle with precision
1726
// using 6 to be on the safe side eh (nobody trusts JS numbers)
1827
crypto.randomBytes(6, function(err, bytes) {
1928
if (err) return cb(err);
20-
key += parseInt(bytes.toString('hex'), 16).toString(36);
29+
key += parseInt(bytes.toString("hex"), 16).toString(36);
2130
if (key.length < length) return get();
2231
cb(err, key.slice(0, length));
2332
});
2433
};
2534
get();
2635
};
2736

37+
/**
38+
* @description
39+
* Verify a users HOTP code they entered against the given key
40+
* @param key The key to check the HOTP code against
41+
* @param code The code entered by the user from the authenticator app.
42+
* @param counter The time for verifying the code against the key.
43+
* @param opts An optional parameter for specifying drift options.
44+
* @returns Either true or false depending on if the code is valid or not.
45+
* @example
46+
* var counter = Math.floor(Date.now() / 1000 / 30);
47+
* var validCode = tfa.verifyHOTP(key, code, counter);
48+
* if (validCode == true) {
49+
* //Log user in
50+
* } else {
51+
* //Display error message
52+
* }
53+
*/
2854
TFA.verifyHOTP = function(key, code, counter, opts) {
2955
opts = opts || {};
3056

@@ -43,6 +69,20 @@ TFA.verifyHOTP = function(key, code, counter, opts) {
4369
return false;
4470
};
4571

72+
/**
73+
* @description
74+
* Verify a users TOTP code they entered against the given key
75+
* @param key The key to check the TOTP code against
76+
* @param code The code entered by the user from the authenticator app.
77+
* @returns Either true or false depending on if the code is valid or not.
78+
* @example
79+
* var validCode = tfa.verifyTOTP(key, code);
80+
* if (validCode == true) {
81+
* //Log user in
82+
* } else {
83+
* //Display error message
84+
* }
85+
*/
4686
TFA.verifyTOTP = function(key, code, opts) {
4787
opts = opts || {};
4888

@@ -57,7 +97,7 @@ TFA.generateCode = function(key, counter, opts) {
5797
opts = opts || {};
5898
var length = opts.length || 6;
5999

60-
var hmac = crypto.createHmac('sha1', key);
100+
var hmac = crypto.createHmac("sha1", key);
61101

62102
// get the counter as bytes
63103
var counterBytes = new Array(8);
@@ -66,7 +106,7 @@ TFA.generateCode = function(key, counter, opts) {
66106
counter = counter >> 8;
67107
}
68108

69-
var token = hmac.update(new Buffer(counterBytes)).digest('hex');
109+
var token = hmac.update(new Buffer(counterBytes)).digest("hex");
70110

71111
// get the token as bytes
72112
var tokenBytes = [];
@@ -77,77 +117,121 @@ TFA.generateCode = function(key, counter, opts) {
77117
// truncate to 4 bytes
78118
var offset = tokenBytes[19] & 0xf;
79119
var ourCode =
80-
(tokenBytes[offset++] & 0x7f) << 24 |
81-
(tokenBytes[offset++] & 0xff) << 16 |
82-
(tokenBytes[offset++] & 0xff) << 8 |
120+
((tokenBytes[offset++] & 0x7f) << 24) |
121+
((tokenBytes[offset++] & 0xff) << 16) |
122+
((tokenBytes[offset++] & 0xff) << 8) |
83123
(tokenBytes[offset++] & 0xff);
84124

85125
// we want strings!
86-
ourCode += '';
126+
ourCode += "";
87127

88128
// truncate to correct length
89129
ourCode = ourCode.substr(ourCode.length - length);
90130

91131
// 0 pad
92-
while (ourCode.length < length) ourCode = '0' + ourCode;
132+
while (ourCode.length < length) ourCode = "0" + ourCode;
93133

94134
return ourCode;
95135
};
96136

97-
TFA.base32Encode = function (key) {
98-
return base32.encode(key).toString().replace(/=/g, '');
137+
/**
138+
* @description
139+
* Converts a key to a format which can be pasted into an authenticator app.
140+
* @param key The key to convert
141+
* @returns The authenticater-friendly code.
142+
* @example
143+
* var multikey = tfa.base32Encode(key);
144+
* console.log('You can manually enter this code into the authenticator app: ' + multikey)
145+
*/
146+
TFA.base32Encode = function(key) {
147+
return base32
148+
.encode(key)
149+
.toString()
150+
.replace(/=/g, "");
99151
};
100152

101-
TFA.generateUrl = function (name, account, key) {
102-
return 'otpauth://totp/' + encodeURIComponent(account)
103-
+ '?issuer=' + encodeURIComponent(name)
104-
+ '&secret=' + TFA.base32Encode(key);
153+
TFA.generateUrl = function(name, account, key) {
154+
return (
155+
"otpauth://totp/" +
156+
encodeURIComponent(account) +
157+
"?issuer=" +
158+
encodeURIComponent(name) +
159+
"&secret=" +
160+
TFA.base32Encode(key)
161+
);
105162
};
106163

164+
/**
165+
* @description
166+
* Generates a QR code that can be scanned to add the account to an authenciator app.
167+
* @param name The name to be listed on the authenticator app- usually the company or product name.
168+
* @param account The account to be listed on the authenticator app- Usually the users email.
169+
* @param key The key generated for the users multifactor.
170+
* @param opts An optional parameter for specifying drift options.
171+
* @returns A base64 string with the image.
172+
* @example
173+
* tfa.generateGoogleQR('Company', 'john.doe@example.com', key, function(err, qr) {
174+
* //Render a page with the QR code for the user to scan.
175+
* })
176+
*/
107177
TFA.generateGoogleQR = function(name, account, key, opts, cb) {
108-
if (!cb && typeof opts === 'function') {
178+
if (!cb && typeof opts === "function") {
109179
cb = opts;
110180
opts = {};
111181
}
112182

113183
var data = TFA.generateUrl(name, account, key);
114184

115185
var formatter = function(buf) {
116-
switch(opts.encoding) {
117-
case 'buffer':
186+
switch (opts.encoding) {
187+
case "buffer":
118188
return buf;
119-
case 'base64':
120-
return buf.toString('base64');
121-
case 'data': default:
122-
return 'data:image/png;base64,' + buf.toString('base64')
189+
case "base64":
190+
return buf.toString("base64");
191+
case "data":
192+
default:
193+
return "data:image/png;base64," + buf.toString("base64");
123194
}
124195
};
125196

126-
var qrOpts = { type: 'png' };
197+
var qrOpts = { type: "png" };
127198
for (var i in opts) qrOpts[i] = opts[i];
128199

129200
var pngStream = QR.image(data, qrOpts);
130201

131202
var pngData = [];
132-
pngStream.on('data', function(d) { pngData.push(d); });
133-
pngStream.on('end', function() {
203+
pngStream.on("data", function(d) {
204+
pngData.push(d);
205+
});
206+
pngStream.on("end", function() {
134207
var png = Buffer.concat(pngData);
135208
cb(null, formatter(png));
136209
});
137210
};
138211

212+
/**
213+
* @description
214+
* Generates codes which can be checked against and used if the authenticator app is not available.
215+
* @param count The number of backup keys you want to generate
216+
* @param pattern An optional parameter for the pattern you want the codes to be generated in
217+
* @param cb A callback function you wish to pass the results of the function to.
218+
* @example
219+
* tfa.generateBackupCodes(6, 'xxxx-xxxx-xxxx', function(err, codes) {
220+
* //Show and store the backup keys to the user.
221+
* })
222+
*/
139223
TFA.generateBackupCodes = function(count, pattern, cb) {
140-
if (!cb && typeof pattern === 'function') {
224+
if (!cb && typeof pattern === "function") {
141225
cb = pattern;
142-
pattern = 'xxxx-xxxx-xxxx';
226+
pattern = "xxxx-xxxx-xxxx";
143227
}
144228

145229
var codes = [];
146230
for (var c = 0; c < count; c++) {
147231
TFA.generateBackupCode(pattern, function(err, code) {
148232
if (err) {
149233
cb(err);
150-
cb = function(){};
234+
cb = function() {};
151235
return;
152236
}
153237

@@ -158,24 +242,24 @@ TFA.generateBackupCodes = function(count, pattern, cb) {
158242
};
159243

160244
TFA.generateBackupCode = function(pattern, cb) {
161-
if (!cb && typeof pattern === 'function') {
245+
if (!cb && typeof pattern === "function") {
162246
cb = pattern;
163-
pattern = 'xxxx-xxxx-xxxx';
247+
pattern = "xxxx-xxxx-xxxx";
164248
}
165249

166250
// how many crypto bytes do we need?
167-
var patternLength = Math.ceil((pattern.split('x').length) - 1 / 2);
251+
var patternLength = Math.ceil(pattern.split("x").length - 1 / 2);
168252

169253
crypto.randomBytes(patternLength, function(err, buf) {
170254
if (err) return cb(err);
171-
var chars = buf.toString('hex');
172-
var code = '';
255+
var chars = buf.toString("hex");
256+
var code = "";
173257

174258
// number of crypto characters that we've used
175259
var xs = 0;
176260
for (var i = 0; i < pattern.length; i++) {
177-
code += pattern[i] === 'x' ? chars[xs++] : pattern[i];
261+
code += pattern[i] === "x" ? chars[xs++] : pattern[i];
178262
}
179263
cb(err, code);
180264
});
181-
}
265+
};

0 commit comments

Comments
 (0)