add createKey and createKeySVG methods, support base32 key encoding
[squeep-totp] / test / hotp.js
index f79f816f916c2d16098c42c89cc1c385bf4fd270..dffd9ef514b480b93fba162709527b8fc08a8cd5 100644 (file)
@@ -1,4 +1,4 @@
-/* eslint-env: mocha */
+/* eslint-env mocha */
 'use strict';
 
 const assert = require('assert');
@@ -39,11 +39,28 @@ describe('HOTP', function () {
     it('covers missing options', function () {
       assert.throws(() => new HOTP());
     });
-    it('covers counter', function () {
+    it('covers counter type conversion', function () {
       options.counter = 4;
       hotp = new HOTP(options);
       assert.strictEqual(hotp.counter, 4n);
     });
+    it('covers key specified as buffer', function () {
+      options.keyEncoding = 'buffer';
+      hotp = new HOTP(options);
+      assert(hotp);
+    });
+    it('covers key specified as base32', function () {
+      options.key = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ';
+      options.keyEncoding = 'base32';
+      hotp = new HOTP(options);
+      assert(hotp);
+    });
+    it('covers key specified as ascii', function () {
+      options.key = '12345678901234567890';
+      options.keyEncoding = 'ascii';
+      hotp = new HOTP(options);
+      assert(hotp);
+    });
   }); // constructor
 
   it('_hmac sanity', function () {
@@ -80,4 +97,123 @@ describe('HOTP', function () {
     });
   }); // validate
 
+  describe('createKey', function () {
+    const algorithm = 'sha1';
+    it('creates a sha1 key by default', async function () {
+      const key = await HOTP.createKey();
+      assert.strictEqual(key.length, 40);
+    });
+    it('creates a sha1 key', async function () {
+      const key = await HOTP.createKey(algorithm);
+      assert.strictEqual(key.length, 40);
+    });
+    it('creates a sha1 key in base32 encoding', async function () {
+      const key = await HOTP.createKey(algorithm, 'base32');
+      assert.strictEqual(key.length, 32);
+    });
+    it('creates a sha1 key in alternate encoding', async function () {
+      const key = await HOTP.createKey(algorithm, 'base64');
+      assert.strictEqual(key.length, 28);
+    });
+    it('creates a sha1 key as a buffer', async function () {
+      const key = await HOTP.createKey(algorithm, 'buffer');
+      assert(Buffer.isBuffer(key));
+    });
+    it('rejects unknown algorithm', async function () {
+      await assert.rejects(() => HOTP.createKey('md5'), RangeError);
+    });
+  }); // createKey
+
+  describe('createKeySVG', function () {
+    let options, key, keyEncoding;
+    beforeEach(function () {
+      options = {
+        issuer: 'Squeep',
+        accountname: 'test@example.com',
+        counter: 0n,
+      };
+      key = undefined;
+      keyEncoding = undefined;
+    });
+    it('creates an svg from base32 key', function () {
+      key = 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ';
+      keyEncoding = 'base32';
+      const { secret, svg, uri } = HOTP.createKeySVG(options, key, keyEncoding);
+      assert(svg);
+      assert(uri.includes('hotp'));
+      assert.strictEqual(secret, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
+    });
+    it('creates an svg from buffer key', function () {
+      key = Buffer.from('12345678901234567890');
+      keyEncoding = 'buffer';
+      const { secret, svg, uri } = HOTP.createKeySVG(options, key, keyEncoding);
+      assert(svg);
+      assert(uri.includes('hotp'));
+      assert.strictEqual(secret, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
+    });
+    it('creates an svg from buffer key', function () {
+      key = Buffer.from('12345678901234567890');
+      const { secret, svg, uri } = HOTP.createKeySVG(options, key, keyEncoding);
+      assert(svg);
+      assert(uri.includes('hotp'));
+      assert.strictEqual(secret, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
+    });
+    it('creates an svg from ascii key', function () {
+      key = '12345678901234567890';
+      keyEncoding = 'ascii';
+      const { secret, svg, uri } = HOTP.createKeySVG(options, key, keyEncoding);
+      assert(svg);
+      assert(uri.includes('hotp'));
+      assert.strictEqual(secret, 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ');
+    });
+  }); // createKeySVG
+
+  describe('_qrURI', function () {
+    let options;
+    beforeEach(function () {
+      options = {
+        secret: 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ',
+        accountname: 'test@example.com',
+        issuer: 'Squeep',
+        counter: 0n,
+      };
+    });
+    it('requires accountname', function () {
+      delete options.accountname;
+      assert.throws(() => HOTP._qrURI(options), RangeError);
+    });
+    it('requires secret', function () {
+      delete options.secret;
+      assert.throws(() => HOTP._qrURI(options), RangeError);
+    });
+    it('requires counter', function () {
+      delete options.counter;
+      assert.throws(() => HOTP._qrURI(options), RangeError);
+    });
+    it('requires secret', function () {
+      delete options.secret;
+      assert.throws(() => HOTP._qrURI(options), RangeError);
+    });
+    it('requires known digits', function () {
+      options.digits = 10;
+      assert.throws(() => HOTP._qrURI(options), RangeError);
+    });
+    it('requires known algorithm', function () {
+      options.algorithm = 'md5';
+      assert.throws(() => HOTP._qrURI(options), RangeError);
+    });
+    it('covers no issuer', function () {
+      delete options.issuer;
+      const uri = HOTP._qrURI(options);
+      assert(!uri.includes('issuer'));
+    });
+    it('covers params', function () {
+      options.digits = 6;
+      options.algorithm = 'sha1';
+      const uri = HOTP._qrURI(options);
+      assert(uri.includes('digits'));
+      assert(uri.includes('SHA1'));
+    });
+  }); // _qrURI
+
 }); // HOTP
\ No newline at end of file