foronce 0.0.5 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/base32.cjs CHANGED
@@ -5,148 +5,112 @@
5
5
  * Copyright(c) 2024 Reaper
6
6
  * MIT Licensed
7
7
  */
8
-
9
- // Simple implementation based of RFC 4648 for base32 encoding and decoding
10
-
11
- const pad = '=';
12
- const base32alphaMap = {
13
- 0: 'A',
14
- 1: 'B',
15
- 2: 'C',
16
- 3: 'D',
17
- 4: 'E',
18
- 5: 'F',
19
- 6: 'G',
20
- 7: 'H',
21
- 8: 'I',
22
- 9: 'J',
23
- 10: 'K',
24
- 11: 'L',
25
- 12: 'M',
26
- 13: 'N',
27
- 14: 'O',
28
- 15: 'P',
29
- 16: 'Q',
30
- 17: 'R',
31
- 18: 'S',
32
- 19: 'T',
33
- 20: 'U',
34
- 21: 'V',
35
- 22: 'W',
36
- 23: 'X',
37
- 24: 'Y',
38
- 25: 'Z',
39
- 26: '2',
40
- 27: '3',
41
- 28: '4',
42
- 29: '5',
43
- 30: '6',
44
- 31: '7',
45
- };
46
-
47
- const base32alphaMapDecode = Object.fromEntries(
48
- Object.entries(base32alphaMap).map(([k, v]) => [v, k])
49
- );
50
-
51
- /**
52
- * @param {string} str
53
- */
54
- const encode = str => {
55
- const splits = str.split('');
56
-
8
+ const pad = "=";
9
+ const base32alphaMap = [
10
+ "A",
11
+ "B",
12
+ "C",
13
+ "D",
14
+ "E",
15
+ "F",
16
+ "G",
17
+ "H",
18
+ "I",
19
+ "J",
20
+ "K",
21
+ "L",
22
+ "M",
23
+ "N",
24
+ "O",
25
+ "P",
26
+ "Q",
27
+ "R",
28
+ "S",
29
+ "T",
30
+ "U",
31
+ "V",
32
+ "W",
33
+ "X",
34
+ "Y",
35
+ "Z",
36
+ "2",
37
+ "3",
38
+ "4",
39
+ "5",
40
+ "6",
41
+ "7"
42
+ ];
43
+ const encode = (str) => {
44
+ const splits = str.split("");
57
45
  if (!splits.length) {
58
- return ''
46
+ return "";
59
47
  }
60
-
61
48
  let binaryGroup = [];
62
- let bitText = '';
63
-
64
- splits.forEach(c => {
49
+ let bitText = "";
50
+ splits.forEach((c) => {
65
51
  bitText += toBinary(c);
66
-
67
52
  if (bitText.length == 40) {
68
53
  binaryGroup.push(bitText);
69
- bitText = '';
54
+ bitText = "";
70
55
  }
71
56
  });
72
-
73
57
  if (bitText.length > 0) {
74
58
  binaryGroup.push(bitText);
75
- bitText = '';
59
+ bitText = "";
76
60
  }
77
-
78
- return binaryGroup
79
- .map(x => {
80
- let fiveBitGrouping = [];
81
- let lex = '';
82
- let bitOn = x;
83
-
84
- bitOn.split('').forEach(d => {
85
- lex += d;
86
- if (lex.length == 5) {
87
- fiveBitGrouping.push(lex);
88
- lex = '';
89
- }
90
- });
91
-
92
- if (lex.length > 0) {
93
- fiveBitGrouping.push(lex.padEnd(5, '0'));
94
- lex = '';
61
+ return binaryGroup.map((x) => {
62
+ let fiveBitGrouping = [];
63
+ let lex = "";
64
+ let bitOn = x;
65
+ bitOn.split("").forEach((d) => {
66
+ lex += d;
67
+ if (lex.length == 5) {
68
+ fiveBitGrouping.push(lex);
69
+ lex = "";
95
70
  }
96
-
97
- let paddedArray = Array.from(fiveBitGrouping);
98
- paddedArray.length = 8;
99
- paddedArray = paddedArray.fill('-1', fiveBitGrouping.length, 8);
100
-
101
- return paddedArray
102
- .map(f => {
103
- if (f == '-1') {
104
- return pad
105
- }
106
- return base32alphaMap[parseInt(f, 2).toString(10)]
107
- })
108
- .join('')
109
- })
110
- .join('')
111
- };
112
-
113
- /**
114
- * @param {string} str
115
- * @returns
116
- */
117
- const decode = str => {
118
- const overallBinary = str
119
- .split('')
120
- .map(x => {
121
- if (x === pad) {
122
- return '00000'
71
+ });
72
+ if (lex.length > 0) {
73
+ fiveBitGrouping.push(lex.padEnd(5, "0"));
74
+ lex = "";
75
+ }
76
+ let paddedArray = Array.from(fiveBitGrouping);
77
+ paddedArray.length = 8;
78
+ paddedArray = paddedArray.fill("-1", fiveBitGrouping.length, 8);
79
+ return paddedArray.map((f) => {
80
+ if (f == "-1") {
81
+ return pad;
123
82
  }
124
- const d = base32alphaMapDecode[x];
125
- const binary = parseInt(d, 10).toString(2);
126
- return binary.padStart(5, '0')
127
- })
128
- .join('');
129
-
130
- const characterBitGrouping = chunk(overallBinary.split(''), 8);
131
- return characterBitGrouping
132
- .map(x => {
133
- const binaryL = x.join('');
134
- const str = String.fromCharCode(+parseInt(binaryL, 2).toString(10));
135
- return str.replace('\x00', '')
136
- })
137
- .join('')
83
+ return base32alphaMap[parseInt(f, 2).toString(10)];
84
+ }).join("");
85
+ }).join("");
86
+ };
87
+ const decode = (str) => {
88
+ const overallBinary = str.split("").map((x) => {
89
+ if (x === pad) {
90
+ return "00000";
91
+ }
92
+ const decodeIndex = base32alphaMap.indexOf(x);
93
+ const binary = decodeIndex.toString(2);
94
+ return binary.padStart(5, "0");
95
+ }).join("");
96
+ const characterBitGrouping = chunk(overallBinary.split(""), 8);
97
+ return characterBitGrouping.map((x) => {
98
+ const binaryL = x.join("");
99
+ const str2 = String.fromCharCode(+parseInt(binaryL, 2).toString(10));
100
+ return str2.replace("\0", "");
101
+ }).join("");
138
102
  };
139
-
140
103
  const toBinary = (char, padLimit = 8) => {
141
104
  const binary = String(char).charCodeAt(0).toString(2);
142
- return binary.padStart(padLimit, '0')
105
+ return binary.padStart(padLimit, "0");
143
106
  };
144
-
145
107
  const chunk = (arr, chunkSize = 1, cache = []) => {
146
108
  const tmp = [...arr];
147
- if (chunkSize <= 0) return cache
148
- while (tmp.length) cache.push(tmp.splice(0, chunkSize));
149
- return cache
109
+ if (chunkSize <= 0)
110
+ return cache;
111
+ while (tmp.length)
112
+ cache.push(tmp.splice(0, chunkSize));
113
+ return cache;
150
114
  };
151
115
 
152
116
  exports.decode = decode;
@@ -0,0 +1,4 @@
1
+ declare function encode(str: string): string;
2
+ declare function decode(str: string): string;
3
+
4
+ export { decode, encode };
@@ -0,0 +1,4 @@
1
+ declare function encode(str: string): string;
2
+ declare function decode(str: string): string;
3
+
4
+ export { decode, encode };
package/dist/base32.d.ts CHANGED
@@ -1,2 +1,4 @@
1
- export function encode(str: string): string;
2
- export function decode(str: string): string;
1
+ declare function encode(str: string): string;
2
+ declare function decode(str: string): string;
3
+
4
+ export { decode, encode };
@@ -0,0 +1,114 @@
1
+ /*!
2
+ * base-32.js
3
+ * Copyright(c) 2024 Reaper
4
+ * MIT Licensed
5
+ */
6
+ const pad = "=";
7
+ const base32alphaMap = [
8
+ "A",
9
+ "B",
10
+ "C",
11
+ "D",
12
+ "E",
13
+ "F",
14
+ "G",
15
+ "H",
16
+ "I",
17
+ "J",
18
+ "K",
19
+ "L",
20
+ "M",
21
+ "N",
22
+ "O",
23
+ "P",
24
+ "Q",
25
+ "R",
26
+ "S",
27
+ "T",
28
+ "U",
29
+ "V",
30
+ "W",
31
+ "X",
32
+ "Y",
33
+ "Z",
34
+ "2",
35
+ "3",
36
+ "4",
37
+ "5",
38
+ "6",
39
+ "7"
40
+ ];
41
+ const encode = (str) => {
42
+ const splits = str.split("");
43
+ if (!splits.length) {
44
+ return "";
45
+ }
46
+ let binaryGroup = [];
47
+ let bitText = "";
48
+ splits.forEach((c) => {
49
+ bitText += toBinary(c);
50
+ if (bitText.length == 40) {
51
+ binaryGroup.push(bitText);
52
+ bitText = "";
53
+ }
54
+ });
55
+ if (bitText.length > 0) {
56
+ binaryGroup.push(bitText);
57
+ bitText = "";
58
+ }
59
+ return binaryGroup.map((x) => {
60
+ let fiveBitGrouping = [];
61
+ let lex = "";
62
+ let bitOn = x;
63
+ bitOn.split("").forEach((d) => {
64
+ lex += d;
65
+ if (lex.length == 5) {
66
+ fiveBitGrouping.push(lex);
67
+ lex = "";
68
+ }
69
+ });
70
+ if (lex.length > 0) {
71
+ fiveBitGrouping.push(lex.padEnd(5, "0"));
72
+ lex = "";
73
+ }
74
+ let paddedArray = Array.from(fiveBitGrouping);
75
+ paddedArray.length = 8;
76
+ paddedArray = paddedArray.fill("-1", fiveBitGrouping.length, 8);
77
+ return paddedArray.map((f) => {
78
+ if (f == "-1") {
79
+ return pad;
80
+ }
81
+ return base32alphaMap[parseInt(f, 2).toString(10)];
82
+ }).join("");
83
+ }).join("");
84
+ };
85
+ const decode = (str) => {
86
+ const overallBinary = str.split("").map((x) => {
87
+ if (x === pad) {
88
+ return "00000";
89
+ }
90
+ const decodeIndex = base32alphaMap.indexOf(x);
91
+ const binary = decodeIndex.toString(2);
92
+ return binary.padStart(5, "0");
93
+ }).join("");
94
+ const characterBitGrouping = chunk(overallBinary.split(""), 8);
95
+ return characterBitGrouping.map((x) => {
96
+ const binaryL = x.join("");
97
+ const str2 = String.fromCharCode(+parseInt(binaryL, 2).toString(10));
98
+ return str2.replace("\0", "");
99
+ }).join("");
100
+ };
101
+ const toBinary = (char, padLimit = 8) => {
102
+ const binary = String(char).charCodeAt(0).toString(2);
103
+ return binary.padStart(padLimit, "0");
104
+ };
105
+ const chunk = (arr, chunkSize = 1, cache = []) => {
106
+ const tmp = [...arr];
107
+ if (chunkSize <= 0)
108
+ return cache;
109
+ while (tmp.length)
110
+ cache.push(tmp.splice(0, chunkSize));
111
+ return cache;
112
+ };
113
+
114
+ export { decode, encode };
package/dist/index.cjs CHANGED
@@ -1,90 +1,51 @@
1
1
  'use strict';
2
2
 
3
- var node_crypto = require('node:crypto');
4
- var node_buffer = require('node:buffer');
5
- var base32 = require('./base32.cjs');
3
+ const node_crypto = require('node:crypto');
4
+ const buffer = require('buffer');
5
+ const base32 = require('./base32.cjs');
6
6
 
7
7
  /*!
8
8
  * base-32.js
9
9
  * Copyright(c) 2024 Reaper
10
10
  * MIT Licensed
11
11
  */
12
-
13
-
14
12
  const { floor } = Math;
15
-
16
- /**
17
- *
18
- * @param {string} secret
19
- * @param {number} when
20
- * @param {object} [options]
21
- * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
22
- * @param {"sha1" | "sha256" | "sha512"} [options.algorithm] (default: sha512)
23
- * @returns {string}
24
- */
25
- function totp(secret, when = floor(Date.now() / 1000), options = {}) {
26
- const _options = Object.assign({ period: 30, algorithm: 'sha512' }, options);
13
+ function totp(secret, when = floor(Date.now() / 1e3), options = {}) {
14
+ const _options = Object.assign({ period: 30, algorithm: "sha512" }, options);
27
15
  const now = floor(when / _options.period);
28
16
  const key = base32.decode(secret);
29
17
  const buff = bigEndian64(BigInt(now));
30
18
  const hmac = node_crypto.createHmac(_options.algorithm, key).update(buff).digest();
31
- const offset = hmac[hmac.length - 1] & 0xf;
19
+ const offset = hmac[hmac.length - 1] & 15;
32
20
  const truncatedHash = hmac.subarray(offset, offset + 4);
33
- const otp = (
34
- (truncatedHash.readInt32BE() & 0x7f_ff_ff_ff) %
35
- 1_000_000
36
- ).toString(10);
37
- return otp.length < 6 ? `${otp}`.padStart(6, '0') : otp
21
+ const otp = ((truncatedHash.readInt32BE() & 2147483647) % 1e6).toString(10);
22
+ return otp.length < 6 ? `${otp}`.padStart(6, "0") : otp;
38
23
  }
39
-
40
- /**
41
- * @param {string} secret
42
- * @param {string} token
43
- * @param {object} [options]
44
- * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
45
- * @param {"sha1" | "sha256" | "sha512"} [options.algorithm] (default: sha512)
46
- * @returns {boolean}
47
- */
48
24
  function isTOTPValid(secret, token, options = {}) {
49
- const _options = Object.assign({ period: 30, algorithm: 'sha512' }, options);
25
+ const _options = Object.assign({ period: 30, algorithm: "sha512" }, options);
50
26
  for (let index = -2; index < 3; index += 1) {
51
- const fromSys = totp(secret, Date.now() / 1000 + index, _options);
27
+ const fromSys = totp(secret, Date.now() / 1e3 + index, _options);
52
28
  const valid = fromSys === token;
53
- if (valid) return true
29
+ if (valid)
30
+ return true;
54
31
  }
55
- return false
32
+ return false;
56
33
  }
57
-
58
- /**
59
- * @param {string} secret
60
- * @param {object} options
61
- * @param {string} options.company
62
- * @param {string} options.email
63
- * @returns {string}
64
- */
65
34
  function generateTOTPURL(secret, options) {
66
35
  const parameters = new URLSearchParams();
67
- parameters.append('secret', secret);
68
- parameters.append('issuer', options.company);
69
- parameters.append('digits', '6');
70
- const url = `otpauth://totp/${options.company}:${
71
- options.email
72
- }?${parameters.toString()}`;
73
- return new URL(url).toString()
36
+ parameters.append("secret", secret);
37
+ parameters.append("issuer", options.company);
38
+ parameters.append("digits", "6");
39
+ const url = `otpauth://totp/${options.company}:${options.email}?${parameters.toString()}`;
40
+ return new URL(url).toString();
74
41
  }
75
-
76
- /**
77
- * @param {bigint} hash
78
- * @returns {Buffer}
79
- */
80
42
  function bigEndian64(hash) {
81
- const buf = node_buffer.Buffer.allocUnsafe(64 / 8);
43
+ const buf = buffer.Buffer.allocUnsafe(64 / 8);
82
44
  buf.writeBigInt64BE(hash, 0);
83
- return buf
45
+ return buf;
84
46
  }
85
-
86
47
  function generateTOTPSecret(num = 32) {
87
- return base32.encode(node_crypto.randomBytes(num).toString('ascii'))
48
+ return base32.encode(node_crypto.randomBytes(num).toString("ascii"));
88
49
  }
89
50
 
90
51
  exports.generateTOTPSecret = generateTOTPSecret;
@@ -0,0 +1,15 @@
1
+ declare function totp(secret: string, when?: number, options?: {
2
+ period?: number;
3
+ algorithm?: "sha1" | "sha256" | "sha512";
4
+ }): string;
5
+ declare function isTOTPValid(secret: string, token: string, options?: {
6
+ period?: number;
7
+ algorithm?: "sha1" | "sha256" | "sha512";
8
+ }): boolean;
9
+ declare function generateTOTPURL(secret: string, options: {
10
+ company: string;
11
+ email: string;
12
+ }): string;
13
+ declare function generateTOTPSecret(num?: number): string;
14
+
15
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
@@ -0,0 +1,15 @@
1
+ declare function totp(secret: string, when?: number, options?: {
2
+ period?: number;
3
+ algorithm?: "sha1" | "sha256" | "sha512";
4
+ }): string;
5
+ declare function isTOTPValid(secret: string, token: string, options?: {
6
+ period?: number;
7
+ algorithm?: "sha1" | "sha256" | "sha512";
8
+ }): boolean;
9
+ declare function generateTOTPURL(secret: string, options: {
10
+ company: string;
11
+ email: string;
12
+ }): string;
13
+ declare function generateTOTPSecret(num?: number): string;
14
+
15
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,15 @@
1
- export function totp(secret: string, when?: number, options?: {
1
+ declare function totp(secret: string, when?: number, options?: {
2
2
  period?: number;
3
3
  algorithm?: "sha1" | "sha256" | "sha512";
4
4
  }): string;
5
- export function isTOTPValid(secret: string, token: string, options?: {
5
+ declare function isTOTPValid(secret: string, token: string, options?: {
6
6
  period?: number;
7
7
  algorithm?: "sha1" | "sha256" | "sha512";
8
8
  }): boolean;
9
- export function generateTOTPURL(secret: string, options: {
9
+ declare function generateTOTPURL(secret: string, options: {
10
10
  company: string;
11
11
  email: string;
12
12
  }): string;
13
- export function generateTOTPSecret(num?: number): string;
13
+ declare function generateTOTPSecret(num?: number): string;
14
+
15
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
package/dist/index.mjs ADDED
@@ -0,0 +1,49 @@
1
+ import { createHmac, randomBytes } from 'node:crypto';
2
+ import { Buffer } from 'buffer';
3
+ import { decode, encode } from './base32.mjs';
4
+
5
+ /*!
6
+ * base-32.js
7
+ * Copyright(c) 2024 Reaper
8
+ * MIT Licensed
9
+ */
10
+ const { floor } = Math;
11
+ function totp(secret, when = floor(Date.now() / 1e3), options = {}) {
12
+ const _options = Object.assign({ period: 30, algorithm: "sha512" }, options);
13
+ const now = floor(when / _options.period);
14
+ const key = decode(secret);
15
+ const buff = bigEndian64(BigInt(now));
16
+ const hmac = createHmac(_options.algorithm, key).update(buff).digest();
17
+ const offset = hmac[hmac.length - 1] & 15;
18
+ const truncatedHash = hmac.subarray(offset, offset + 4);
19
+ const otp = ((truncatedHash.readInt32BE() & 2147483647) % 1e6).toString(10);
20
+ return otp.length < 6 ? `${otp}`.padStart(6, "0") : otp;
21
+ }
22
+ function isTOTPValid(secret, token, options = {}) {
23
+ const _options = Object.assign({ period: 30, algorithm: "sha512" }, options);
24
+ for (let index = -2; index < 3; index += 1) {
25
+ const fromSys = totp(secret, Date.now() / 1e3 + index, _options);
26
+ const valid = fromSys === token;
27
+ if (valid)
28
+ return true;
29
+ }
30
+ return false;
31
+ }
32
+ function generateTOTPURL(secret, options) {
33
+ const parameters = new URLSearchParams();
34
+ parameters.append("secret", secret);
35
+ parameters.append("issuer", options.company);
36
+ parameters.append("digits", "6");
37
+ const url = `otpauth://totp/${options.company}:${options.email}?${parameters.toString()}`;
38
+ return new URL(url).toString();
39
+ }
40
+ function bigEndian64(hash) {
41
+ const buf = Buffer.allocUnsafe(64 / 8);
42
+ buf.writeBigInt64BE(hash, 0);
43
+ return buf;
44
+ }
45
+ function generateTOTPSecret(num = 32) {
46
+ return encode(randomBytes(num).toString("ascii"));
47
+ }
48
+
49
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const uncrypto = require('uncrypto');
4
+ const base32 = require('../base32.cjs');
5
+
6
+ function bigEndian64(hash) {
7
+ const buf = Buffer.allocUnsafe(64 / 8);
8
+ buf.writeBigInt64BE(hash, 0);
9
+ return buf;
10
+ }
11
+
12
+ const algoMap = {
13
+ sha1: "SHA-1",
14
+ sha256: "SHA-256",
15
+ sha512: "SHA-512"
16
+ };
17
+ async function createHmac(algorithm, secret, data) {
18
+ const key = await uncrypto.subtle.importKey(
19
+ "raw",
20
+ // raw format of the key - should be Uint8Array
21
+ secret,
22
+ {
23
+ // algorithm details
24
+ name: "HMAC",
25
+ hash: { name: algoMap[algorithm] }
26
+ },
27
+ false,
28
+ // export = false
29
+ ["sign", "verify"]
30
+ // what this key can do
31
+ );
32
+ const signature = await uncrypto.subtle.sign("HMAC", key, data);
33
+ return Buffer.from(signature);
34
+ }
35
+
36
+ const { floor } = Math;
37
+ async function totp(secret, when = floor(Date.now() / 1e3), options = {}) {
38
+ const _options = Object.assign(
39
+ {
40
+ period: 30,
41
+ algorithm: "sha512"
42
+ },
43
+ options
44
+ );
45
+ const now = floor(when / _options.period);
46
+ const key = base32.decode(secret);
47
+ const buff = bigEndian64(BigInt(now));
48
+ const hmac = await createHmac(_options.algorithm, key, buff);
49
+ const offset = hmac.at(-1) & 15;
50
+ const truncatedHash = hmac.subarray(offset, offset + 4);
51
+ const otp = ((truncatedHash.readInt32BE() & 2147483647) % 1e6).toString(10);
52
+ return otp.length < 6 ? `${otp}`.padStart(6, "0") : otp;
53
+ }
54
+ async function isTOTPValid(secret, totpToken, options = {}) {
55
+ const _options = Object.assign({ period: 30, algorithm: "sha512" }, options);
56
+ for (let index = -2; index < 3; index += 1) {
57
+ const fromSys = await totp(secret, Date.now() / 1e3 + index, _options);
58
+ const valid = fromSys === totpToken;
59
+ if (valid) {
60
+ return true;
61
+ }
62
+ }
63
+ return false;
64
+ }
65
+ function generateTOTPURL(secret, options) {
66
+ const parameters = new URLSearchParams();
67
+ parameters.append("secret", secret);
68
+ parameters.append("issuer", options.company);
69
+ parameters.append("digits", "6");
70
+ const url = `otpauth://totp/${options.company}:${options.email}?${parameters.toString()}`;
71
+ return new URL(url).toString();
72
+ }
73
+ function generateTOTPSecret(num = 32) {
74
+ const array = new Uint32Array(num);
75
+ const vals = uncrypto.getRandomValues(array);
76
+ return base32.encode(Buffer.from(vals).toString("ascii"));
77
+ }
78
+
79
+ exports.generateTOTPSecret = generateTOTPSecret;
80
+ exports.generateTOTPURL = generateTOTPURL;
81
+ exports.isTOTPValid = isTOTPValid;
82
+ exports.totp = totp;
@@ -0,0 +1,10 @@
1
+ interface TOTPURLOptions {
2
+ company: string;
3
+ email: string;
4
+ }
5
+ declare function totp(secret: string, when?: number, options?: {}): Promise<string>;
6
+ declare function isTOTPValid(secret: string, totpToken: string, options?: {}): Promise<boolean>;
7
+ declare function generateTOTPURL(secret: string, options: TOTPURLOptions): string;
8
+ declare function generateTOTPSecret(num?: number): string;
9
+
10
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
@@ -0,0 +1,10 @@
1
+ interface TOTPURLOptions {
2
+ company: string;
3
+ email: string;
4
+ }
5
+ declare function totp(secret: string, when?: number, options?: {}): Promise<string>;
6
+ declare function isTOTPValid(secret: string, totpToken: string, options?: {}): Promise<boolean>;
7
+ declare function generateTOTPURL(secret: string, options: TOTPURLOptions): string;
8
+ declare function generateTOTPSecret(num?: number): string;
9
+
10
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
@@ -0,0 +1,10 @@
1
+ interface TOTPURLOptions {
2
+ company: string;
3
+ email: string;
4
+ }
5
+ declare function totp(secret: string, when?: number, options?: {}): Promise<string>;
6
+ declare function isTOTPValid(secret: string, totpToken: string, options?: {}): Promise<boolean>;
7
+ declare function generateTOTPURL(secret: string, options: TOTPURLOptions): string;
8
+ declare function generateTOTPSecret(num?: number): string;
9
+
10
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
@@ -0,0 +1,77 @@
1
+ import { subtle, getRandomValues } from 'uncrypto';
2
+ import { decode, encode } from '../base32.mjs';
3
+
4
+ function bigEndian64(hash) {
5
+ const buf = Buffer.allocUnsafe(64 / 8);
6
+ buf.writeBigInt64BE(hash, 0);
7
+ return buf;
8
+ }
9
+
10
+ const algoMap = {
11
+ sha1: "SHA-1",
12
+ sha256: "SHA-256",
13
+ sha512: "SHA-512"
14
+ };
15
+ async function createHmac(algorithm, secret, data) {
16
+ const key = await subtle.importKey(
17
+ "raw",
18
+ // raw format of the key - should be Uint8Array
19
+ secret,
20
+ {
21
+ // algorithm details
22
+ name: "HMAC",
23
+ hash: { name: algoMap[algorithm] }
24
+ },
25
+ false,
26
+ // export = false
27
+ ["sign", "verify"]
28
+ // what this key can do
29
+ );
30
+ const signature = await subtle.sign("HMAC", key, data);
31
+ return Buffer.from(signature);
32
+ }
33
+
34
+ const { floor } = Math;
35
+ async function totp(secret, when = floor(Date.now() / 1e3), options = {}) {
36
+ const _options = Object.assign(
37
+ {
38
+ period: 30,
39
+ algorithm: "sha512"
40
+ },
41
+ options
42
+ );
43
+ const now = floor(when / _options.period);
44
+ const key = decode(secret);
45
+ const buff = bigEndian64(BigInt(now));
46
+ const hmac = await createHmac(_options.algorithm, key, buff);
47
+ const offset = hmac.at(-1) & 15;
48
+ const truncatedHash = hmac.subarray(offset, offset + 4);
49
+ const otp = ((truncatedHash.readInt32BE() & 2147483647) % 1e6).toString(10);
50
+ return otp.length < 6 ? `${otp}`.padStart(6, "0") : otp;
51
+ }
52
+ async function isTOTPValid(secret, totpToken, options = {}) {
53
+ const _options = Object.assign({ period: 30, algorithm: "sha512" }, options);
54
+ for (let index = -2; index < 3; index += 1) {
55
+ const fromSys = await totp(secret, Date.now() / 1e3 + index, _options);
56
+ const valid = fromSys === totpToken;
57
+ if (valid) {
58
+ return true;
59
+ }
60
+ }
61
+ return false;
62
+ }
63
+ function generateTOTPURL(secret, options) {
64
+ const parameters = new URLSearchParams();
65
+ parameters.append("secret", secret);
66
+ parameters.append("issuer", options.company);
67
+ parameters.append("digits", "6");
68
+ const url = `otpauth://totp/${options.company}:${options.email}?${parameters.toString()}`;
69
+ return new URL(url).toString();
70
+ }
71
+ function generateTOTPSecret(num = 32) {
72
+ const array = new Uint32Array(num);
73
+ const vals = getRandomValues(array);
74
+ return encode(Buffer.from(vals).toString("ascii"));
75
+ }
76
+
77
+ export { generateTOTPSecret, generateTOTPURL, isTOTPValid, totp };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foronce",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "The OTP Library",
5
5
  "repository": "git@github.com:dumbjs/foronce.git",
6
6
  "license": "MIT",
@@ -8,33 +8,46 @@
8
8
  "type": "module",
9
9
  "exports": {
10
10
  ".": {
11
- "types": "./dist/index.d.ts",
12
- "import": "./src/index.js",
11
+ "types": {
12
+ "import": "./dist/index.d.mts",
13
+ "require": "./dist/index.d.cts"
14
+ },
15
+ "import": "./dist/index.mjs",
13
16
  "require": "./dist/index.cjs"
14
17
  },
15
18
  "./base32": {
16
- "types": "./dist/base32.d.ts",
17
- "import": "./src/base32.js",
19
+ "types": {
20
+ "import": "./dist/base32.d.mts",
21
+ "require": "./dist/base32.d.cts"
22
+ },
23
+ "import": "./dist/base32.mjs",
18
24
  "require": "./dist/base32.cjs"
19
25
  },
26
+ "./universal": {
27
+ "types": {
28
+ "import": "./dist/universal/universal.d.mts",
29
+ "require": "./dist/universal/universal.d.cts"
30
+ },
31
+ "import": "./dist/universal/universal.mjs",
32
+ "require": "./dist/universal/universal.cjs"
33
+ },
20
34
  "./package.json": "./package.json"
21
35
  },
22
36
  "main": "./dist/index.cjs",
23
- "module": "./src/index.js",
37
+ "module": "./dist/index.mjs",
24
38
  "types": "./dist/index.d.ts",
25
39
  "files": [
26
40
  "dist",
27
41
  "src"
28
42
  ],
29
43
  "scripts": {
30
- "build": "rollup -c; tsc",
31
- "dev": "rollup -c --watch",
44
+ "build": "unbuild",
32
45
  "fix": "prettier --write .",
33
46
  "next": "bumpp",
34
47
  "prepare": "husky install",
35
48
  "size": "sizesnap",
36
- "test": "uvu tests",
37
- "test:ci": "c8 uvu tests "
49
+ "test": "uvu -r tsm tests",
50
+ "test:ci": "c8 uvu -r tsm tests "
38
51
  },
39
52
  "keywords": [
40
53
  "otp",
@@ -57,11 +70,9 @@
57
70
  "lint-staged": "^14.0.1",
58
71
  "prettier": "^2.7.1",
59
72
  "publint": "^0.2.7",
60
- "rollup": "^4.9.4",
61
- "rollup-plugin-node-externals": "^6.1.2",
62
73
  "sizesnap": "^0.2.1",
63
- "tsup": "^6.1.2",
64
- "typescript": "^4.7.4",
74
+ "tsm": "^2.3.0",
75
+ "unbuild": "^2.0.0",
65
76
  "uvu": "^0.5.6"
66
77
  },
67
78
  "publishConfig": {
@@ -74,5 +85,8 @@
74
85
  "dist/*.js",
75
86
  "dist/*.cjs"
76
87
  ]
88
+ },
89
+ "dependencies": {
90
+ "uncrypto": "^0.1.3"
77
91
  }
78
92
  }
package/src/base32.js CHANGED
@@ -7,44 +7,40 @@
7
7
  // Simple implementation based of RFC 4648 for base32 encoding and decoding
8
8
 
9
9
  const pad = '='
10
- const base32alphaMap = {
11
- 0: 'A',
12
- 1: 'B',
13
- 2: 'C',
14
- 3: 'D',
15
- 4: 'E',
16
- 5: 'F',
17
- 6: 'G',
18
- 7: 'H',
19
- 8: 'I',
20
- 9: 'J',
21
- 10: 'K',
22
- 11: 'L',
23
- 12: 'M',
24
- 13: 'N',
25
- 14: 'O',
26
- 15: 'P',
27
- 16: 'Q',
28
- 17: 'R',
29
- 18: 'S',
30
- 19: 'T',
31
- 20: 'U',
32
- 21: 'V',
33
- 22: 'W',
34
- 23: 'X',
35
- 24: 'Y',
36
- 25: 'Z',
37
- 26: '2',
38
- 27: '3',
39
- 28: '4',
40
- 29: '5',
41
- 30: '6',
42
- 31: '7',
43
- }
44
-
45
- const base32alphaMapDecode = Object.fromEntries(
46
- Object.entries(base32alphaMap).map(([k, v]) => [v, k])
47
- )
10
+ const base32alphaMap = [
11
+ 'A',
12
+ 'B',
13
+ 'C',
14
+ 'D',
15
+ 'E',
16
+ 'F',
17
+ 'G',
18
+ 'H',
19
+ 'I',
20
+ 'J',
21
+ 'K',
22
+ 'L',
23
+ 'M',
24
+ 'N',
25
+ 'O',
26
+ 'P',
27
+ 'Q',
28
+ 'R',
29
+ 'S',
30
+ 'T',
31
+ 'U',
32
+ 'V',
33
+ 'W',
34
+ 'X',
35
+ 'Y',
36
+ 'Z',
37
+ '2',
38
+ '3',
39
+ '4',
40
+ '5',
41
+ '6',
42
+ '7',
43
+ ]
48
44
 
49
45
  /**
50
46
  * @param {string} str
@@ -119,8 +115,8 @@ export const decode = str => {
119
115
  if (x === pad) {
120
116
  return '00000'
121
117
  }
122
- const d = base32alphaMapDecode[x]
123
- const binary = parseInt(d, 10).toString(2)
118
+ const decodeIndex = base32alphaMap.indexOf(x)
119
+ const binary = decodeIndex.toString(2)
124
120
  return binary.padStart(5, '0')
125
121
  })
126
122
  .join('')
@@ -133,8 +129,6 @@ export const decode = str => {
133
129
  return str.replace('\x00', '')
134
130
  })
135
131
  .join('')
136
-
137
- return ''
138
132
  }
139
133
 
140
134
  const toBinary = (char, padLimit = 8) => {
@@ -0,0 +1,31 @@
1
+ import { subtle } from 'uncrypto'
2
+
3
+ const algoMap = {
4
+ sha1: 'SHA-1',
5
+ sha256: 'SHA-256',
6
+ sha512: 'SHA-512',
7
+ }
8
+
9
+ export async function createHmac(algorithm, secret, data) {
10
+ // let enc
11
+ // if (TextEncoder.constructor.length == 1) {
12
+ // // @ts-ignore
13
+ // enc = new TextEncoder('utf-8')
14
+ // } else {
15
+ // enc = new TextEncoder()
16
+ // }
17
+
18
+ const key = await subtle.importKey(
19
+ 'raw', // raw format of the key - should be Uint8Array
20
+ secret,
21
+ {
22
+ // algorithm details
23
+ name: 'HMAC',
24
+ hash: { name: algoMap[algorithm] },
25
+ },
26
+ false, // export = false
27
+ ['sign', 'verify'] // what this key can do
28
+ )
29
+ const signature = await subtle.sign('HMAC', key, data)
30
+ return Buffer.from(signature)
31
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @param {bigint} hash
3
+ * @returns {Buffer}
4
+ */
5
+ export function bigEndian64(hash) {
6
+ const buf = Buffer.allocUnsafe(64 / 8)
7
+ buf.writeBigInt64BE(hash, 0)
8
+ return buf
9
+ }
@@ -0,0 +1,29 @@
1
+ import { subtle } from 'uncrypto'
2
+
3
+ const algoMap = {
4
+ sha1: 'SHA-1',
5
+ sha256: 'SHA-256',
6
+ sha512: 'SHA-512',
7
+ }
8
+
9
+ export type AlgoEnum = 'sha1' | 'sha256' | 'sha512'
10
+
11
+ export async function createHmac(
12
+ algorithm: AlgoEnum,
13
+ secret: string,
14
+ data: Buffer
15
+ ) {
16
+ const key = await subtle.importKey(
17
+ 'raw', // raw format of the key - should be Uint8Array
18
+ secret,
19
+ {
20
+ // algorithm details
21
+ name: 'HMAC',
22
+ hash: { name: algoMap[algorithm] },
23
+ },
24
+ false, // export = false
25
+ ['sign', 'verify'] // what this key can do
26
+ )
27
+ const signature = await subtle.sign('HMAC', key, data)
28
+ return Buffer.from(signature)
29
+ }
@@ -0,0 +1,85 @@
1
+ import { getRandomValues } from 'uncrypto'
2
+ import { decode, encode } from '../base32.js'
3
+ import { bigEndian64 } from '../lib/utils.js'
4
+ import { AlgoEnum, createHmac } from './hmac.js'
5
+
6
+ interface TOTPURLOptions {
7
+ company: string
8
+ email: string
9
+ }
10
+
11
+ const { floor } = Math
12
+
13
+ /**
14
+ * @param {string} secret - the secret to be used, needs to be a base32 encoded string
15
+ * @param {number} when - point of time in seconds (default: Date.now()/1000)
16
+ * @param {object} [options]
17
+ * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
18
+ * @param {import("./hmac.js").AlgoEnum} [options.algorithm] (default: sha512)
19
+ * @returns {Promise<string>}
20
+ */
21
+ export async function totp(
22
+ secret: string,
23
+ when = floor(Date.now() / 1000),
24
+ options = {}
25
+ ) {
26
+ const _options = Object.assign(
27
+ {
28
+ period: 30,
29
+ algorithm: 'sha512' as AlgoEnum,
30
+ },
31
+ options
32
+ )
33
+ const now = floor(when / _options.period)
34
+ const key = decode(secret)
35
+ const buff = bigEndian64(BigInt(now))
36
+ const hmac = await createHmac(_options.algorithm, key, buff)
37
+ const offset = hmac.at(-1)! & 0xf
38
+ const truncatedHash = hmac.subarray(offset, offset + 4)
39
+ const otp = (
40
+ (truncatedHash.readInt32BE() & 0x7f_ff_ff_ff) %
41
+ 1_000_000
42
+ ).toString(10)
43
+ return otp.length < 6 ? `${otp}`.padStart(6, '0') : otp
44
+ }
45
+
46
+ /**
47
+ * @param {string} secret - the secret to be used, needs to be a base32 encoded string
48
+ * @param {string} totpToken - the totp token
49
+ * @param {object} [options]
50
+ * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
51
+ * @param {import("./hmac.js").AlgoEnum} [options.algorithm] (default: sha512)
52
+ * @returns {Promise<boolean>}
53
+ */
54
+ export async function isTOTPValid(
55
+ secret: string,
56
+ totpToken: string,
57
+ options = {}
58
+ ) {
59
+ const _options = Object.assign({ period: 30, algorithm: 'sha512' }, options)
60
+ for (let index = -2; index < 3; index += 1) {
61
+ const fromSys = await totp(secret, Date.now() / 1000 + index, _options)
62
+ const valid = fromSys === totpToken
63
+ if (valid) {
64
+ return true
65
+ }
66
+ }
67
+ return false
68
+ }
69
+
70
+ export function generateTOTPURL(secret: string, options: TOTPURLOptions) {
71
+ const parameters = new URLSearchParams()
72
+ parameters.append('secret', secret)
73
+ parameters.append('issuer', options.company)
74
+ parameters.append('digits', '6')
75
+ const url = `otpauth://totp/${options.company}:${
76
+ options.email
77
+ }?${parameters.toString()}`
78
+ return new URL(url).toString()
79
+ }
80
+
81
+ export function generateTOTPSecret(num = 32) {
82
+ const array = new Uint32Array(num)
83
+ const vals = getRandomValues(array)
84
+ return encode(Buffer.from(vals).toString('ascii'))
85
+ }