From 5ec7c70e53a28fec406b61109886d5d84463e839 Mon Sep 17 00:00:00 2001 From: Connie Leung Date: Tue, 17 Jan 2017 23:26:47 +0800 Subject: [PATCH 1/7] Create otpauth URL with issuer parameter in generateSecret() function --- README.md | 5 +++-- index.js | 6 +++++- test/generate.js | 18 +++++++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f539db6..7e7c2bf 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ How this works: // Set ASCII secret var secret = 'rNONHRni6BAk7y2TiKrv'; -// Get HOTP counter token at counter = 42 +// Get HOTP counter token at counter = 42 var counter42 = speakeasy.hotp({ secret: secret, counter: 42 }); // => '566646' @@ -346,7 +346,7 @@ var token1 = speakeasy.totp({ secret: secret, time: 1453853945 }); // 625175 var token3 = speakeasy.totp({ secret: secret, time: 1453854005 }); // 222636 // We can check the time at token 3, 1453853975, with token 1, but use a window of 2 -// With a time step of 30 seconds, this will check all tokens from 60 seconds +// With a time step of 30 seconds, this will check all tokens from 60 seconds // before the time to 60 seconds after the time speakeasy.totp.verifyDelta({ secret: secret, token: token1, window: 2, time: 1453854005 }); // => { delta: -2 } @@ -626,6 +626,7 @@ Authenticator URL to obtain a QR code you can scan into the app. | [options.name] | String | | The name to use with Google Authenticator. | | [options.qr_codes] | Boolean | false | (DEPRECATED. Do not use to prevent leaking of secret to a third party. Use your own QR code implementation.) Output QR code URLs for the token. | | [options.google_auth_qr] | Boolean | false | (DEPRECATED. Do not use to prevent leaking of secret to a third party. Use your own QR code implementation.) Output a Google Authenticator otpauth:// QR code URL. | +| [options.issuer] | String | | The provider or service with which the secret key is associated. | ### generateSecretASCII([length], [symbols]) ⇒ String diff --git a/index.js b/index.js index 403924d..0406711 100644 --- a/index.js +++ b/index.js @@ -472,6 +472,8 @@ exports.totp.verify = function totpVerify (options) { * @param {Boolean} [options.google_auth_qr=false] (DEPRECATED. Do not use to * prevent leaking of secret to a third party. Use your own QR code * implementation.) Output a Google Authenticator otpauth:// QR code URL. + * @param {String} [options.issuer=''] The provider or service with which the + * secret key is associated. * @return {Object} * @return {GeneratedSecret} The generated secret key. */ @@ -484,6 +486,7 @@ exports.generateSecret = function generateSecret (options) { var google_auth_qr = options.google_auth_qr || false; var otpauth_url = options.otpauth_url != null ? options.otpauth_url : true; var symbols = true; + var issuer = options.issuer; // turn off symbols only when explicity told to if (options.symbols !== undefined && options.symbols === false) { @@ -511,7 +514,8 @@ exports.generateSecret = function generateSecret (options) { if (otpauth_url) { SecretKey.otpauth_url = exports.otpauthURL({ secret: SecretKey.ascii, - label: name + label: name, + issuer: issuer }); } diff --git a/test/generate.js b/test/generate.js index f731937..547641a 100644 --- a/test/generate.js +++ b/test/generate.js @@ -117,7 +117,7 @@ describe('Generator tests', function () { var secret = new Buffer(answer.base32, 'ascii'); if (Buffer.isBuffer(secret)) secret = base32.encode(secret); - var google_chart_url = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl='; + var google_chart_url = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl='; var encodedOtpUrl = encodeURIComponent('otpauth://totp/Example%3Aalice%40google.com?secret=' + secret); var expect = google_chart_url + encodedOtpUrl; assert.deepEqual( @@ -125,4 +125,20 @@ describe('Generator tests', function () { expect ); }); + + it('Testing otp URL generated with issuer and algorithm by generateSecret', function () { + var answer = speakeasy.generateSecret({ + name: 'Example:alice@google.com', + issuer: 'issuer name' + }); + + var secret = new Buffer(answer.ascii, 'ascii'); + if (Buffer.isBuffer(secret)) secret = base32.encode(secret); + + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret + '&issuer=issuer%20name'; + assert.deepEqual( + answer.otpauth_url, + expect + ); + }); }); From 37616134b09f2d56ffda733cf393f1bbc900dcce Mon Sep 17 00:00:00 2001 From: Connie Leung Date: Wed, 18 Jan 2017 05:46:04 +0800 Subject: [PATCH 2/7] Fixed the description in test case --- test/generate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/generate.js b/test/generate.js index 547641a..c65817d 100644 --- a/test/generate.js +++ b/test/generate.js @@ -126,7 +126,7 @@ describe('Generator tests', function () { ); }); - it('Testing otp URL generated with issuer and algorithm by generateSecret', function () { + it('Testing otp URL generated with issuer by generateSecret', function () { var answer = speakeasy.generateSecret({ name: 'Example:alice@google.com', issuer: 'issuer name' From 8c8624e82e9e0d2fd5c1ac9c6e3174b453c50330 Mon Sep 17 00:00:00 2001 From: Connie Leung Date: Wed, 18 Jan 2017 05:56:06 +0800 Subject: [PATCH 3/7] Create otpauth URL with issuer parameter in generateSecret() function Retain white spaces --- README.md | 2 +- test/generate.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7e7c2bf..c46c888 100644 --- a/README.md +++ b/README.md @@ -294,7 +294,7 @@ How this works: // Set ASCII secret var secret = 'rNONHRni6BAk7y2TiKrv'; -// Get HOTP counter token at counter = 42 +// Get HOTP counter token at counter = 42 var counter42 = speakeasy.hotp({ secret: secret, counter: 42 }); // => '566646' diff --git a/test/generate.js b/test/generate.js index c65817d..219ab03 100644 --- a/test/generate.js +++ b/test/generate.js @@ -117,7 +117,7 @@ describe('Generator tests', function () { var secret = new Buffer(answer.base32, 'ascii'); if (Buffer.isBuffer(secret)) secret = base32.encode(secret); - var google_chart_url = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl='; + var google_chart_url = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl='; var encodedOtpUrl = encodeURIComponent('otpauth://totp/Example%3Aalice%40google.com?secret=' + secret); var expect = google_chart_url + encodedOtpUrl; assert.deepEqual( From 6d9fddc48c5dc92fe243f63b83b96b961b7d35a0 Mon Sep 17 00:00:00 2001 From: Connie Leung Date: Wed, 18 Jan 2017 06:15:03 +0800 Subject: [PATCH 4/7] Add trailing whitespace back. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c46c888..dbc151d 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ var token1 = speakeasy.totp({ secret: secret, time: 1453853945 }); // 625175 var token3 = speakeasy.totp({ secret: secret, time: 1453854005 }); // 222636 // We can check the time at token 3, 1453853975, with token 1, but use a window of 2 -// With a time step of 30 seconds, this will check all tokens from 60 seconds +// With a time step of 30 seconds, this will check all tokens from 60 seconds // before the time to 60 seconds after the time speakeasy.totp.verifyDelta({ secret: secret, token: token1, window: 2, time: 1453854005 }); // => { delta: -2 } From 99f2f7151913ae913ab85348f7967b717875bf6d Mon Sep 17 00:00:00 2001 From: Connie Leung Date: Wed, 18 Jan 2017 07:18:56 +0800 Subject: [PATCH 5/7] Make the documentation of otpAuthURL consistent with code. algorithm. period, digit has no default value default value of encoding is set to ascii --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dbc151d..cee5df6 100644 --- a/README.md +++ b/README.md @@ -676,10 +676,10 @@ generator, such as the `qr-image` module. | [options.type] | String | "totp" | Either "hotp" or "totp". | | [options.counter] | Integer | | The initial counter value, required for HOTP. | | [options.issuer] | String | | The provider or service with which the secret key is associated. | -| [options.algorithm] | String | "sha1" | Hash algorithm (sha1, sha256, sha512). | -| [options.digits] | Integer | 6 | The number of digits for the one-time passcode. Currently ignored by Google Authenticator. | -| [options.period] | Integer | 30 | The length of time for which a TOTP code will be valid, in seconds. Currently ignored by Google Authenticator. | -| [options.encoding] | String | | Key encoding (ascii, hex, base32, base64). If the key is not encoded in Base-32, it will be reencoded. | +| [options.algorithm] | String | | Hash algorithm (sha1, sha256, sha512). | +| [options.digits] | Integer | | The number of digits for the one-time passcode. Currently ignored by Google Authenticator. | +| [options.period] | Integer | | The length of time for which a TOTP code will be valid, in seconds. Currently ignored by Google Authenticator. | +| [options.encoding] | String | ascii | Key encoding (ascii, hex, base32, base64). If the key is not encoded in Base-32, it will be reencoded. | ### GeneratedSecret : Object From a6f312fded219f5e436878f13fd778b32c417852 Mon Sep 17 00:00:00 2001 From: Connie Leung Date: Wed, 18 Jan 2017 07:35:45 +0800 Subject: [PATCH 6/7] Make the default value of otpAuthURL consistent with readme --- README.md | 6 ++--- index.js | 6 ++--- test/generate.js | 8 +++--- test/url_test.js | 68 +++++++++++++++++++++++++++++++++++++++++++++--- 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index cee5df6..2dabe3a 100644 --- a/README.md +++ b/README.md @@ -676,9 +676,9 @@ generator, such as the `qr-image` module. | [options.type] | String | "totp" | Either "hotp" or "totp". | | [options.counter] | Integer | | The initial counter value, required for HOTP. | | [options.issuer] | String | | The provider or service with which the secret key is associated. | -| [options.algorithm] | String | | Hash algorithm (sha1, sha256, sha512). | -| [options.digits] | Integer | | The number of digits for the one-time passcode. Currently ignored by Google Authenticator. | -| [options.period] | Integer | | The length of time for which a TOTP code will be valid, in seconds. Currently ignored by Google Authenticator. | +| [options.algorithm] | String | "sha1" | Hash algorithm (sha1, sha256, sha512). | +| [options.digits] | Integer | 6 | The number of digits for the one-time passcode. Currently ignored by Google Authenticator. | +| [options.period] | Integer | 30 | The length of time for which a TOTP code will be valid, in seconds. Currently ignored by Google Authenticator. | | [options.encoding] | String | ascii | Key encoding (ascii, hex, base32, base64). If the key is not encoded in Base-32, it will be reencoded. | diff --git a/index.js b/index.js index 0406711..d934b9b 100644 --- a/index.js +++ b/index.js @@ -607,9 +607,9 @@ exports.otpauthURL = function otpauthURL (options) { var issuer = options.issuer; var type = (options.type || 'totp').toLowerCase(); var counter = options.counter; - var algorithm = options.algorithm; - var digits = options.digits; - var period = options.period; + var algorithm = (options.algorithm || 'sha1').toLowerCase(); + var digits = options.digits || 6; + var period = options.period || 30; var encoding = options.encoding || 'ascii'; // validate type diff --git a/test/generate.js b/test/generate.js index 219ab03..ea572df 100644 --- a/test/generate.js +++ b/test/generate.js @@ -86,7 +86,7 @@ describe('Generator tests', function () { var secret = new Buffer(answer.ascii, 'ascii'); if (Buffer.isBuffer(secret)) secret = base32.encode(secret); - var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret; + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret + '&algorithm=SHA1&digits=6&period=30'; assert.deepEqual( answer.otpauth_url, expect @@ -101,7 +101,7 @@ describe('Generator tests', function () { var secret = new Buffer(answer.ascii, 'ascii'); if (Buffer.isBuffer(secret)) secret = base32.encode(secret); - var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret; + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret + '&algorithm=SHA1&digits=6&period=30'; assert.deepEqual( answer.otpauth_url, expect @@ -118,7 +118,7 @@ describe('Generator tests', function () { if (Buffer.isBuffer(secret)) secret = base32.encode(secret); var google_chart_url = 'https://chart.googleapis.com/chart?chs=166x166&chld=L|0&cht=qr&chl='; - var encodedOtpUrl = encodeURIComponent('otpauth://totp/Example%3Aalice%40google.com?secret=' + secret); + var encodedOtpUrl = encodeURIComponent('otpauth://totp/Example%3Aalice%40google.com?secret=' + secret + '&algorithm=SHA1&digits=6&period=30'); var expect = google_chart_url + encodedOtpUrl; assert.deepEqual( answer.google_auth_qr, @@ -135,7 +135,7 @@ describe('Generator tests', function () { var secret = new Buffer(answer.ascii, 'ascii'); if (Buffer.isBuffer(secret)) secret = base32.encode(secret); - var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret + '&issuer=issuer%20name'; + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=' + secret + '&issuer=issuer%20name&algorithm=SHA1&digits=6&period=30'; assert.deepEqual( answer.otpauth_url, expect diff --git a/test/url_test.js b/test/url_test.js index 8dd51b8..1e7e283 100644 --- a/test/url_test.js +++ b/test/url_test.js @@ -169,7 +169,7 @@ describe('#url', function () { issuer: 'Example', encoding: 'base32' }); - var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example'; + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30'; assert.deepEqual( url.parse(answer), url.parse(expect) @@ -182,7 +182,7 @@ describe('#url', function () { label: 'Example:alice@google.com', issuer: 'Example' }); - var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JVFWSTSIKR3G2ZSR&issuer=Example'; + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JVFWSTSIKR3G2ZSR&issuer=Example&algorithm=SHA1&digits=6&period=30'; assert.deepEqual( url.parse(answer), url.parse(expect) @@ -198,7 +198,7 @@ describe('#url', function () { type: 'hotp', counter: 199 }); - var expect = 'otpauth://hotp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&counter=199'; + var expect = 'otpauth://hotp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&counter=199&algorithm=SHA1&digits=6&period=30'; assert.deepEqual( url.parse(answer), url.parse(expect) @@ -214,7 +214,67 @@ describe('#url', function () { type: 'hotp', counter: 0 }); - var expect = 'otpauth://hotp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&counter=0'; + var expect = 'otpauth://hotp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&counter=0&algorithm=SHA1&digits=6&period=30'; + assert.deepEqual( + url.parse(answer), + url.parse(expect) + ); + }); + + it('should generate an totp URL compatible with the Google Authenticator app for sha512 algorithm', function () { + var answer = speakeasy.otpauthURL({ + secret: 'JBSWY3DPEHPK3PXP', + label: 'Example:alice@google.com', + issuer: 'Example', + encoding: 'base32', + algorithm: 'sha512' + }); + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA512&digits=6&period=30'; + assert.deepEqual( + url.parse(answer), + url.parse(expect) + ); + }) ; + + it('should generate an totp URL compatible with the Google Authenticator app for sha256 algorithm', function () { + var answer = speakeasy.otpauthURL({ + secret: 'JBSWY3DPEHPK3PXP', + label: 'Example:alice@google.com', + issuer: 'Example', + encoding: 'base32', + algorithm: 'sha256' + }); + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA256&digits=6&period=30'; + assert.deepEqual( + url.parse(answer), + url.parse(expect) + ); + }); + + it('should generate an totp URL compatible with the Google Authenticator app for 8 digits', function () { + var answer = speakeasy.otpauthURL({ + secret: 'JBSWY3DPEHPK3PXP', + label: 'Example:alice@google.com', + issuer: 'Example', + encoding: 'base32', + digits: 8 + }); + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=8&period=30'; + assert.deepEqual( + url.parse(answer), + url.parse(expect) + ); + }); + + it('should generate an totp URL compatible with the Google Authenticator app for period of 60', function () { + var answer = speakeasy.otpauthURL({ + secret: 'JBSWY3DPEHPK3PXP', + label: 'Example:alice@google.com', + issuer: 'Example', + encoding: 'base32', + period: '60' + }); + var expect = 'otpauth://totp/Example%3Aalice%40google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=60'; assert.deepEqual( url.parse(answer), url.parse(expect) From 3b883236aa3eced8cdbcfe4c92534b142c2ba673 Mon Sep 17 00:00:00 2001 From: Connie Date: Wed, 21 Jun 2017 21:08:48 +0800 Subject: [PATCH 7/7] Fix #88 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2dabe3a..9f4d6e4 100644 --- a/README.md +++ b/README.md @@ -80,8 +80,8 @@ Next, we'll want to display a QR code to the user so they can scan in the secret Use a QR code module to generate a QR code that stores the data in `secret.otpauth_url`, and then display the QR code to the user. This is one simple way to do it, which generates a PNG data URL which you can put into an `` tag on a webpage: ```js -// Use the node-qrcode package -// npm install --save node-qrcode +// Use the qrcode package +// npm install --save qrcode var QRCode = require('qrcode'); // Get the data URL of the authenticator URL