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+ */
817TFA . 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+ */
2854TFA . 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+ */
4686TFA . 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+ */
107177TFA . 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+ */
139223TFA . 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
160244TFA . 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