foronce 0.0.0

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/README.md ADDED
@@ -0,0 +1 @@
1
+ # foronce
package/dist/index.cjs ADDED
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ var node_crypto = require('node:crypto');
4
+ var node_buffer = require('node:buffer');
5
+ var base32 = require('hi-base32');
6
+
7
+ /**
8
+ * Simplified implementation of TOTP with existing node libs
9
+ *
10
+ * helpers to handle the following
11
+ * - generate QR codes with the otpauth:// URL scheme
12
+ * - algo and implmentation taken from https://drewdevault.com/2022/10/18/TOTP-is-easy.html
13
+ */
14
+
15
+ const { floor } = Math;
16
+
17
+ /**
18
+ *
19
+ * @param {string} secret
20
+ * @param {number} when
21
+ * @param {object} [options]
22
+ * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
23
+ * @returns {string}
24
+ */
25
+ function totp(secret, when = floor(Date.now() / 1000), options = {}) {
26
+ const _options = Object.assign({ period: 30 }, options);
27
+ const now = floor(when / _options.period);
28
+ const key = base32.decode(secret);
29
+ const buff = bigEndian64(BigInt(now));
30
+ const hmac = node_crypto.createHmac('sha512', key).update(buff).digest();
31
+ const offset = hmac[hmac.length - 1] & 0xf;
32
+ 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
38
+ }
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
+ * @returns {boolean}
46
+ */
47
+ function isValid(secret, token, options = {}) {
48
+ const _options = Object.assign({ period: 30 }, options);
49
+ for (let index = -2; index < 3; index += 1) {
50
+ const fromSys = totp(secret, Date.now() / 1000 + index, _options);
51
+ const valid = fromSys === token;
52
+ if (valid) return true
53
+ }
54
+ return false
55
+ }
56
+
57
+ /**
58
+ * @param {string} secret
59
+ * @param {object} options
60
+ * @param {string} options.company
61
+ * @param {string} options.email
62
+ * @returns {string}
63
+ */
64
+ function generateTOTPURL(secret, options) {
65
+ const parameters = new URLSearchParams();
66
+ parameters.append('secret', secret);
67
+ parameters.append('issuer', options.company);
68
+ parameters.append('digits', '6');
69
+ const url = `otpauth://totp/${options.company}:${
70
+ options.email
71
+ }?${parameters.toString()}`;
72
+ return new URL(url).toString()
73
+ }
74
+
75
+ /**
76
+ * @param {bigint} hash
77
+ * @returns {Buffer}
78
+ */
79
+ function bigEndian64(hash) {
80
+ const buf = node_buffer.Buffer.allocUnsafe(64 / 8);
81
+ buf.writeBigInt64BE(hash, 0);
82
+ return buf
83
+ }
84
+
85
+ function generateTOTPSecret(num = 32) {
86
+ return base32.encode(node_crypto.randomBytes(num).toString('ascii'))
87
+ }
88
+
89
+ exports.generateTOTPSecret = generateTOTPSecret;
90
+ exports.generateTOTPURL = generateTOTPURL;
91
+ exports.isValid = isValid;
92
+ exports.totp = totp;
@@ -0,0 +1,11 @@
1
+ export function totp(secret: string, when?: number, options?: {
2
+ period?: number;
3
+ }): string;
4
+ export function isValid(secret: string, token: string, options?: {
5
+ period?: number;
6
+ }): boolean;
7
+ export function generateTOTPURL(secret: string, options: {
8
+ company: string;
9
+ email: string;
10
+ }): string;
11
+ export function generateTOTPSecret(num?: number): string;
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ 'use strict';
2
+
3
+ var node_crypto = require('node:crypto');
4
+ var node_buffer = require('node:buffer');
5
+ var base32 = require('hi-base32');
6
+
7
+ /**
8
+ * Simplified implementation of TOTP with existing node libs
9
+ *
10
+ * helpers to handle the following
11
+ * - generate QR codes with the otpauth:// URL scheme
12
+ * - algo and implmentation taken from https://drewdevault.com/2022/10/18/TOTP-is-easy.html
13
+ */
14
+
15
+ const { floor } = Math;
16
+
17
+ /**
18
+ *
19
+ * @param {string} secret
20
+ * @param {number} when
21
+ * @param {object} [options]
22
+ * @param {number} [options.period]
23
+ * @returns {string}
24
+ */
25
+ function totp(secret, when = floor(Date.now() / 1000), options = {}) {
26
+ const _options = Object.assign({ period: 30 }, options);
27
+ const now = floor(when / _options.period);
28
+ const key = base32.decode(secret);
29
+ const buff = bigEndian64(BigInt(now));
30
+ const hmac = node_crypto.createHmac('sha1', key).update(buff).digest();
31
+ const offset = hmac[hmac.length - 1] & 0xf;
32
+ 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
38
+ }
39
+
40
+ /**
41
+ * @param {string} secret
42
+ * @param {string} token
43
+ * @returns {boolean}
44
+ */
45
+ function isValid(secret, token) {
46
+ for (let index = -2; index < 3; index += 1) {
47
+ const fromSys = totp(secret, Date.now() / 1000 + index);
48
+ const valid = fromSys === token;
49
+ if (valid) return true
50
+ }
51
+ return false
52
+ }
53
+
54
+ /**
55
+ * @param {string} secret
56
+ * @param {object} options
57
+ * @param {string} options.company
58
+ * @param {string} options.email
59
+ * @returns {string}
60
+ */
61
+ function generateTOTPURL(secret, options) {
62
+ const parameters = new URLSearchParams();
63
+ parameters.append('secret', secret);
64
+ parameters.append('issuer', options.company);
65
+ parameters.append('digits', '6');
66
+ const url = `otpauth://totp/${options.company}:${
67
+ options.email
68
+ }?${parameters.toString()}`;
69
+ return new URL(url).toString()
70
+ }
71
+
72
+ /**
73
+ * @param {bigint} hash
74
+ * @returns {Buffer}
75
+ */
76
+ function bigEndian64(hash) {
77
+ const buf = node_buffer.Buffer.allocUnsafe(64 / 8);
78
+ buf.writeBigInt64BE(hash, 0);
79
+ return buf
80
+ }
81
+
82
+ function generateTOTPSecret(num = 32) {
83
+ return base32.encode(node_crypto.randomBytes(num).toString('ascii'))
84
+ }
85
+
86
+ exports.generateTOTPSecret = generateTOTPSecret;
87
+ exports.generateTOTPURL = generateTOTPURL;
88
+ exports.isValid = isValid;
89
+ exports.totp = totp;
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "foronce",
3
+ "version": "0.0.0",
4
+ "repository": "git@github.com:dumbjs/foronce.git",
5
+ "license": "MIT",
6
+ "author": "Reaper <ahoy@barelyhuman.dev>",
7
+ "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./src/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "main": "./dist/index.cjs",
17
+ "module": "./src/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "files": [
20
+ "dist",
21
+ "src"
22
+ ],
23
+ "scripts": {
24
+ "build": "rollup -c; tsc",
25
+ "dev": "rollup -c --watch",
26
+ "fix": "prettier --write .",
27
+ "next": "bumpp",
28
+ "prepare": "husky install",
29
+ "size": "sizesnap",
30
+ "test": "uvu tests",
31
+ "test:ci": "c8 uvu tests "
32
+ },
33
+ "lint-staged": {
34
+ "*.{js,css,md,json}": "prettier --write"
35
+ },
36
+ "prettier": "@barelyhuman/prettier-config",
37
+ "dependencies": {
38
+ "hi-base32": "^0.5.1",
39
+ "uncrypto": "^0.1.3"
40
+ },
41
+ "devDependencies": {
42
+ "@barelyhuman/prettier-config": "^1.0.0",
43
+ "@types/node": "^20.10.8",
44
+ "buffer": "^6.0.3",
45
+ "bumpp": "^9.2.0",
46
+ "c8": "^8.0.1",
47
+ "esm": "^3.2.25",
48
+ "husky": "^8.0.3",
49
+ "lint-staged": "^14.0.1",
50
+ "prettier": "^2.7.1",
51
+ "publint": "^0.2.7",
52
+ "rollup": "^4.9.4",
53
+ "rollup-plugin-node-externals": "^6.1.2",
54
+ "sizesnap": "^0.2.1",
55
+ "tsup": "^6.1.2",
56
+ "typescript": "^4.7.4",
57
+ "uvu": "^0.5.6"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "sizesnap": {
63
+ "files": [
64
+ "dist/*.dts",
65
+ "dist/*.ts",
66
+ "dist/*.js"
67
+ ]
68
+ }
69
+ }
package/src/index.js ADDED
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Simplified implementation of TOTP with existing node libs
3
+ *
4
+ * helpers to handle the following
5
+ * - generate QR codes with the otpauth:// URL scheme
6
+ * - algo and implmentation taken from https://drewdevault.com/2022/10/18/TOTP-is-easy.html
7
+ */
8
+ import { createHmac, randomBytes } from 'node:crypto'
9
+ import { Buffer } from 'buffer'
10
+ import base32 from 'hi-base32'
11
+
12
+ const { floor } = Math
13
+
14
+ /**
15
+ *
16
+ * @param {string} secret
17
+ * @param {number} when
18
+ * @param {object} [options]
19
+ * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
20
+ * @returns {string}
21
+ */
22
+ export function totp(secret, when = floor(Date.now() / 1000), options = {}) {
23
+ const _options = Object.assign({ period: 30 }, options)
24
+ const now = floor(when / _options.period)
25
+ const key = base32.decode(secret)
26
+ const buff = bigEndian64(BigInt(now))
27
+ const hmac = createHmac('sha512', key).update(buff).digest()
28
+ const offset = hmac[hmac.length - 1] & 0xf
29
+ const truncatedHash = hmac.subarray(offset, offset + 4)
30
+ const otp = (
31
+ (truncatedHash.readInt32BE() & 0x7f_ff_ff_ff) %
32
+ 1_000_000
33
+ ).toString(10)
34
+ return otp.length < 6 ? `${otp}`.padStart(6, '0') : otp
35
+ }
36
+
37
+ /**
38
+ * @param {string} secret
39
+ * @param {string} token
40
+ * @param {object} [options]
41
+ * @param {number} [options.period] in seconds (eg: 30 => 30 seconds)
42
+ * @returns {boolean}
43
+ */
44
+ export function isValid(secret, token, options = {}) {
45
+ const _options = Object.assign({ period: 30 }, options)
46
+ for (let index = -2; index < 3; index += 1) {
47
+ const fromSys = totp(secret, Date.now() / 1000 + index, _options)
48
+ const valid = fromSys === token
49
+ if (valid) return true
50
+ }
51
+ return false
52
+ }
53
+
54
+ /**
55
+ * @param {string} secret
56
+ * @param {object} options
57
+ * @param {string} options.company
58
+ * @param {string} options.email
59
+ * @returns {string}
60
+ */
61
+ export function generateTOTPURL(secret, options) {
62
+ const parameters = new URLSearchParams()
63
+ parameters.append('secret', secret)
64
+ parameters.append('issuer', options.company)
65
+ parameters.append('digits', '6')
66
+ const url = `otpauth://totp/${options.company}:${
67
+ options.email
68
+ }?${parameters.toString()}`
69
+ return new URL(url).toString()
70
+ }
71
+
72
+ /**
73
+ * @param {bigint} hash
74
+ * @returns {Buffer}
75
+ */
76
+ function bigEndian64(hash) {
77
+ const buf = Buffer.allocUnsafe(64 / 8)
78
+ buf.writeBigInt64BE(hash, 0)
79
+ return buf
80
+ }
81
+
82
+ export function generateTOTPSecret(num = 32) {
83
+ return base32.encode(randomBytes(num).toString('ascii'))
84
+ }