@nowarajs/totp 1.2.1 → 1.2.2
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/{chunk-q8z45f9z.js → chunk-t1hx3d3e.js} +19 -5
- package/dist/chunk-zfdfmrcn.js +17 -0
- package/dist/enums/index.js +1 -1
- package/dist/enums/totp-error-keys.d.ts +6 -0
- package/dist/hotp.d.ts +15 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +72 -15
- package/dist/otp-auth-uri.d.ts +7 -0
- package/dist/totp.d.ts +11 -3
- package/dist/types/index.d.ts +8 -0
- package/dist/types/otp-auth-uri.d.ts +1 -1
- package/dist/types/verify-options.d.ts +1 -1
- package/dist/utils/base32.d.ts +10 -1
- package/dist/utils/create-counter-buffer.d.ts +9 -1
- package/dist/utils/dynamic-truncation.d.ts +6 -3
- package/dist/utils/index.js +27 -17
- package/package.json +1 -1
- package/dist/chunk-sx6rwmqe.js +0 -11
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
TOTP_ERROR_KEYS
|
|
4
|
+
} from "./chunk-zfdfmrcn.js";
|
|
5
|
+
|
|
2
6
|
// source/utils/create-counter-buffer.ts
|
|
3
7
|
var createCounterBuffer = (counter) => {
|
|
4
8
|
const counterBuffer = new ArrayBuffer(8);
|
|
5
9
|
const counterView = new DataView(counterBuffer);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
else
|
|
9
|
-
counterView.setUint32(4, counter, false);
|
|
10
|
+
const counterBigInt = typeof counter === "bigint" ? counter : BigInt(Math.floor(counter));
|
|
11
|
+
counterView.setBigUint64(0, counterBigInt, false);
|
|
10
12
|
return counterBuffer;
|
|
11
13
|
};
|
|
12
14
|
|
|
13
15
|
// source/utils/dynamic-truncation.ts
|
|
16
|
+
import { InternalError } from "@nowarajs/error";
|
|
17
|
+
var _DIGIT_MODULO = {
|
|
18
|
+
6: 1e6,
|
|
19
|
+
8: 1e8
|
|
20
|
+
};
|
|
14
21
|
var dynamicTruncation = (hmacArray, digits) => {
|
|
22
|
+
if (hmacArray.length < 20)
|
|
23
|
+
throw new InternalError(TOTP_ERROR_KEYS.INVALID_HMAC_LENGTH, "HMAC must be at least 20 bytes");
|
|
24
|
+
const modulo = _DIGIT_MODULO[digits];
|
|
25
|
+
if (modulo === undefined)
|
|
26
|
+
throw new InternalError(TOTP_ERROR_KEYS.INVALID_DIGITS, "Digits must be 6 or 8");
|
|
15
27
|
const offset = hmacArray[hmacArray.length - 1] & 15;
|
|
16
|
-
|
|
28
|
+
if (offset + 4 > hmacArray.length)
|
|
29
|
+
throw new InternalError(TOTP_ERROR_KEYS.INVALID_HMAC_LENGTH, "HMAC too short for computed offset");
|
|
30
|
+
const code = ((hmacArray[offset] & 127) << 24 | (hmacArray[offset + 1] & 255) << 16 | (hmacArray[offset + 2] & 255) << 8 | hmacArray[offset + 3] & 255) % modulo;
|
|
17
31
|
return code.toString().padStart(digits, "0");
|
|
18
32
|
};
|
|
19
33
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// source/enums/totp-error-keys.ts
|
|
3
|
+
var TOTP_ERROR_KEYS = {
|
|
4
|
+
INVALID_ALGORITHM: "nowarajs.totp.error.invalid_algorithm",
|
|
5
|
+
INVALID_BASE32_CHARACTER: "nowarajs.totp.error.invalid_base32_character",
|
|
6
|
+
INVALID_DIGITS: "nowarajs.totp.error.invalid_digits",
|
|
7
|
+
INVALID_HMAC_LENGTH: "nowarajs.totp.error.invalid_hmac_length",
|
|
8
|
+
INVALID_OTP_AUTH_URI: "nowarajs.totp.error.invalid_otp_auth_uri",
|
|
9
|
+
INVALID_PERIOD: "nowarajs.totp.error.invalid_period",
|
|
10
|
+
INVALID_SECRET_LENGTH: "nowarajs.totp.error.invalid_secret_length",
|
|
11
|
+
INVALID_WINDOW: "nowarajs.totp.error.invalid_window",
|
|
12
|
+
MISSING_LABEL: "nowarajs.totp.error.missing_label",
|
|
13
|
+
MISSING_SECRET: "nowarajs.totp.error.missing_secret",
|
|
14
|
+
WEAK_SECRET: "nowarajs.totp.error.weak_secret"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export { TOTP_ERROR_KEYS };
|
package/dist/enums/index.js
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export declare const TOTP_ERROR_KEYS: {
|
|
2
2
|
readonly INVALID_ALGORITHM: "nowarajs.totp.error.invalid_algorithm";
|
|
3
3
|
readonly INVALID_BASE32_CHARACTER: "nowarajs.totp.error.invalid_base32_character";
|
|
4
|
+
readonly INVALID_DIGITS: "nowarajs.totp.error.invalid_digits";
|
|
5
|
+
readonly INVALID_HMAC_LENGTH: "nowarajs.totp.error.invalid_hmac_length";
|
|
4
6
|
readonly INVALID_OTP_AUTH_URI: "nowarajs.totp.error.invalid_otp_auth_uri";
|
|
7
|
+
readonly INVALID_PERIOD: "nowarajs.totp.error.invalid_period";
|
|
5
8
|
readonly INVALID_SECRET_LENGTH: "nowarajs.totp.error.invalid_secret_length";
|
|
9
|
+
readonly INVALID_WINDOW: "nowarajs.totp.error.invalid_window";
|
|
10
|
+
readonly MISSING_LABEL: "nowarajs.totp.error.missing_label";
|
|
6
11
|
readonly MISSING_SECRET: "nowarajs.totp.error.missing_secret";
|
|
12
|
+
readonly WEAK_SECRET: "nowarajs.totp.error.weak_secret";
|
|
7
13
|
};
|
package/dist/hotp.d.ts
CHANGED
|
@@ -1,11 +1,24 @@
|
|
|
1
1
|
import type { TotpOptions } from './types/totp-options';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Clear the CryptoKey cache
|
|
4
4
|
*
|
|
5
|
-
* @
|
|
5
|
+
* @remarks
|
|
6
|
+
* Useful for testing or when secrets should be purged from memory.
|
|
7
|
+
*/
|
|
8
|
+
export declare const clearKeyCache: () => void;
|
|
9
|
+
/**
|
|
10
|
+
* HMAC-based One-Time Password (HOTP) implementation per RFC 4226
|
|
11
|
+
*
|
|
12
|
+
* @remarks
|
|
13
|
+
* Security: Validates minimum secret length (128 bits per RFC 4226).
|
|
14
|
+
* Performance: Caches CryptoKey objects for repeated calls.
|
|
15
|
+
*
|
|
16
|
+
* @param secret - Secret key as bytes (minimum 16 bytes)
|
|
6
17
|
* @param counter - Counter value
|
|
7
18
|
* @param opts - HOTP options
|
|
8
19
|
*
|
|
20
|
+
* @throws ({@link InternalError}) - if secret is too short
|
|
21
|
+
*
|
|
9
22
|
* @returns Promise resolving to the HOTP code
|
|
10
23
|
*/
|
|
11
24
|
export declare const hotp: (secret: Uint8Array, counter: number | bigint, { algorithm, digits }?: TotpOptions) => Promise<string>;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -3,24 +3,51 @@ import {
|
|
|
3
3
|
createCounterBuffer,
|
|
4
4
|
dynamicTruncation,
|
|
5
5
|
generateHmac
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-t1hx3d3e.js";
|
|
7
7
|
import {
|
|
8
8
|
TOTP_ERROR_KEYS
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-zfdfmrcn.js";
|
|
10
10
|
|
|
11
11
|
// source/hotp.ts
|
|
12
12
|
import { webcrypto } from "crypto";
|
|
13
|
+
import { InternalError } from "@nowarajs/error";
|
|
14
|
+
var _keyCache = new Map;
|
|
15
|
+
var _createCacheKey = (secret, algorithm) => {
|
|
16
|
+
const prefix = secret.slice(0, 8);
|
|
17
|
+
const suffix = secret.slice(-8);
|
|
18
|
+
const fingerprint = [...prefix, ...suffix, secret.length].join(",");
|
|
19
|
+
return `${fingerprint}:${algorithm}`;
|
|
20
|
+
};
|
|
21
|
+
var _getCryptoKey = async (secret, algorithm) => {
|
|
22
|
+
const cacheKey = _createCacheKey(secret, algorithm);
|
|
23
|
+
const cached = _keyCache.get(cacheKey);
|
|
24
|
+
if (cached)
|
|
25
|
+
return cached;
|
|
26
|
+
if (_keyCache.size >= 100) {
|
|
27
|
+
const firstKey = _keyCache.keys().next().value;
|
|
28
|
+
if (firstKey)
|
|
29
|
+
_keyCache.delete(firstKey);
|
|
30
|
+
}
|
|
31
|
+
const key = await webcrypto.subtle.importKey("raw", secret, { name: "HMAC", hash: algorithm }, false, ["sign"]);
|
|
32
|
+
_keyCache.set(cacheKey, key);
|
|
33
|
+
return key;
|
|
34
|
+
};
|
|
35
|
+
var clearKeyCache = () => {
|
|
36
|
+
_keyCache.clear();
|
|
37
|
+
};
|
|
13
38
|
var hotp = async (secret, counter, {
|
|
14
39
|
algorithm = "SHA-1",
|
|
15
40
|
digits = 6
|
|
16
41
|
} = {}) => {
|
|
42
|
+
if (secret.length < 16)
|
|
43
|
+
throw new InternalError(TOTP_ERROR_KEYS.WEAK_SECRET, "Secret must be at least 16 bytes (128 bits)");
|
|
17
44
|
const counterBuffer = createCounterBuffer(counter);
|
|
18
|
-
const key = await
|
|
45
|
+
const key = await _getCryptoKey(secret, algorithm);
|
|
19
46
|
const hmacArray = await generateHmac(key, counterBuffer);
|
|
20
47
|
return dynamicTruncation(hmacArray, digits);
|
|
21
48
|
};
|
|
22
49
|
// source/otp-auth-uri.ts
|
|
23
|
-
import { InternalError } from "@nowarajs/error";
|
|
50
|
+
import { InternalError as InternalError2 } from "@nowarajs/error";
|
|
24
51
|
var buildOtpAuthUri = ({
|
|
25
52
|
secretBase32,
|
|
26
53
|
label,
|
|
@@ -45,18 +72,28 @@ var buildOtpAuthUri = ({
|
|
|
45
72
|
var parseOtpAuthUri = (uri) => {
|
|
46
73
|
const url = new URL(uri);
|
|
47
74
|
if (url.protocol !== "otpauth:")
|
|
48
|
-
throw new
|
|
75
|
+
throw new InternalError2(TOTP_ERROR_KEYS.INVALID_OTP_AUTH_URI, "Invalid protocol, expected otpauth:");
|
|
49
76
|
if (url.hostname !== "totp")
|
|
50
|
-
throw new
|
|
77
|
+
throw new InternalError2(TOTP_ERROR_KEYS.INVALID_OTP_AUTH_URI, "Invalid type, expected totp");
|
|
51
78
|
const label = decodeURIComponent(url.pathname.slice(1));
|
|
79
|
+
if (!label)
|
|
80
|
+
throw new InternalError2(TOTP_ERROR_KEYS.MISSING_LABEL, "Label is required");
|
|
52
81
|
const secretBase32 = url.searchParams.get("secret");
|
|
53
82
|
if (!secretBase32)
|
|
54
|
-
throw new
|
|
83
|
+
throw new InternalError2(TOTP_ERROR_KEYS.MISSING_SECRET, "Secret is required");
|
|
55
84
|
const issuerParam = url.searchParams.get("issuer");
|
|
56
85
|
const issuer = issuerParam ? decodeURIComponent(issuerParam) : undefined;
|
|
57
|
-
const
|
|
58
|
-
|
|
86
|
+
const algorithmParam = url.searchParams.get("algorithm") || "SHA-1";
|
|
87
|
+
if (algorithmParam !== "SHA-1" && algorithmParam !== "SHA-256" && algorithmParam !== "SHA-512")
|
|
88
|
+
throw new InternalError2(TOTP_ERROR_KEYS.INVALID_ALGORITHM, "Algorithm must be SHA-1, SHA-256, or SHA-512");
|
|
89
|
+
const algorithm = algorithmParam;
|
|
90
|
+
const digitsParam = parseInt(url.searchParams.get("digits") || "6", 10);
|
|
91
|
+
if (digitsParam !== 6 && digitsParam !== 8)
|
|
92
|
+
throw new InternalError2(TOTP_ERROR_KEYS.INVALID_DIGITS, "Digits must be 6 or 8");
|
|
93
|
+
const digits = digitsParam;
|
|
59
94
|
const period = parseInt(url.searchParams.get("period") || "30", 10);
|
|
95
|
+
if (!Number.isFinite(period) || period <= 0)
|
|
96
|
+
throw new InternalError2(TOTP_ERROR_KEYS.INVALID_PERIOD, "Period must be a positive integer");
|
|
60
97
|
const result = {
|
|
61
98
|
secretBase32,
|
|
62
99
|
label,
|
|
@@ -68,6 +105,8 @@ var parseOtpAuthUri = (uri) => {
|
|
|
68
105
|
return result;
|
|
69
106
|
};
|
|
70
107
|
// source/totp.ts
|
|
108
|
+
import { timingSafeEqual } from "crypto";
|
|
109
|
+
import { InternalError as InternalError3 } from "@nowarajs/error";
|
|
71
110
|
var totp = async (secret, {
|
|
72
111
|
algorithm = "SHA-1",
|
|
73
112
|
digits = 6,
|
|
@@ -77,6 +116,13 @@ var totp = async (secret, {
|
|
|
77
116
|
const timeStep = Math.floor(now / 1000 / period);
|
|
78
117
|
return hotp(secret, timeStep, { algorithm, digits });
|
|
79
118
|
};
|
|
119
|
+
var _timingSafeCompare = (a, b) => {
|
|
120
|
+
if (a.length !== b.length)
|
|
121
|
+
return false;
|
|
122
|
+
const bufferA = Buffer.from(a);
|
|
123
|
+
const bufferB = Buffer.from(b);
|
|
124
|
+
return timingSafeEqual(bufferA, bufferB);
|
|
125
|
+
};
|
|
80
126
|
var verifyTotp = async (secret, code, {
|
|
81
127
|
algorithm = "SHA-1",
|
|
82
128
|
digits = 6,
|
|
@@ -84,19 +130,30 @@ var verifyTotp = async (secret, code, {
|
|
|
84
130
|
window = 0,
|
|
85
131
|
now = Date.now()
|
|
86
132
|
} = {}) => {
|
|
133
|
+
if (window < 0 || window > 10)
|
|
134
|
+
throw new InternalError3(TOTP_ERROR_KEYS.INVALID_WINDOW, "Window must be between 0 and 10");
|
|
135
|
+
if (!/^\d+$/.test(code) || code.length !== digits)
|
|
136
|
+
return false;
|
|
87
137
|
const currentTimeStep = Math.floor(now / 1000 / period);
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
if (expectedCode === code)
|
|
92
|
-
return true;
|
|
138
|
+
if (window === 0) {
|
|
139
|
+
const expectedCode = await hotp(secret, currentTimeStep, { algorithm, digits });
|
|
140
|
+
return _timingSafeCompare(expectedCode, code);
|
|
93
141
|
}
|
|
94
|
-
|
|
142
|
+
const timeSteps = [];
|
|
143
|
+
for (let i = -window;i <= window; ++i)
|
|
144
|
+
timeSteps.push(currentTimeStep + i);
|
|
145
|
+
const expectedCodes = await Promise.all(timeSteps.map((timeStep) => hotp(secret, timeStep, { algorithm, digits })));
|
|
146
|
+
let isValid = false;
|
|
147
|
+
for (const expectedCode of expectedCodes)
|
|
148
|
+
if (_timingSafeCompare(expectedCode, code))
|
|
149
|
+
isValid = true;
|
|
150
|
+
return isValid;
|
|
95
151
|
};
|
|
96
152
|
export {
|
|
97
153
|
verifyTotp,
|
|
98
154
|
totp,
|
|
99
155
|
parseOtpAuthUri,
|
|
100
156
|
hotp,
|
|
157
|
+
clearKeyCache,
|
|
101
158
|
buildOtpAuthUri
|
|
102
159
|
};
|
package/dist/otp-auth-uri.d.ts
CHANGED
|
@@ -10,6 +10,13 @@ export declare const buildOtpAuthUri: ({ secretBase32, label, issuer, algorithm,
|
|
|
10
10
|
/**
|
|
11
11
|
* Parse an OTPAuth URI
|
|
12
12
|
*
|
|
13
|
+
* @remarks
|
|
14
|
+
* Security: Validates all parameters to prevent injection or invalid configurations.
|
|
15
|
+
* - Algorithm must be SHA-1, SHA-256, or SHA-512
|
|
16
|
+
* - Digits must be 6 or 8
|
|
17
|
+
* - Period must be a positive integer
|
|
18
|
+
* - Label is required per otpauth specification
|
|
19
|
+
*
|
|
13
20
|
* @param uri - OTPAuth URI to parse
|
|
14
21
|
*
|
|
15
22
|
* @throws ({@link InternalError}) - if the URI is invalid or missing required parameters
|
package/dist/totp.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type { TotpOptions } from './types/totp-options';
|
|
2
2
|
import type { VerifyOptions } from './types/verify-options';
|
|
3
3
|
/**
|
|
4
|
-
* Time-based One-Time Password (TOTP) implementation
|
|
4
|
+
* Time-based One-Time Password (TOTP) implementation per RFC 6238
|
|
5
5
|
*
|
|
6
|
-
* @param secret - Secret key as bytes
|
|
6
|
+
* @param secret - Secret key as bytes (minimum 16 bytes)
|
|
7
7
|
* @param opts - TOTP options including current time
|
|
8
8
|
*
|
|
9
9
|
* @returns Promise resolving to the TOTP code
|
|
@@ -14,10 +14,18 @@ export declare const totp: (secret: Uint8Array, { algorithm, digits, period, now
|
|
|
14
14
|
/**
|
|
15
15
|
* Verify a TOTP code against a secret
|
|
16
16
|
*
|
|
17
|
-
* @
|
|
17
|
+
* @remarks
|
|
18
|
+
* Security: Uses constant-time comparison to prevent timing attacks (CWE-208).
|
|
19
|
+
* Security: Always iterates through all windows to prevent leaking which time step matched.
|
|
20
|
+
* Security: Validates window size to prevent DoS attacks (CWE-400).
|
|
21
|
+
* Performance: Parallelizes code generation when window > 0.
|
|
22
|
+
*
|
|
23
|
+
* @param secret - Secret key as bytes (minimum 16 bytes)
|
|
18
24
|
* @param code - Code to verify
|
|
19
25
|
* @param opts - Verification options
|
|
20
26
|
*
|
|
27
|
+
* @throws ({@link InternalError}) - if window is invalid
|
|
28
|
+
*
|
|
21
29
|
* @returns Promise resolving to true if code is valid
|
|
22
30
|
*/
|
|
23
31
|
export declare const verifyTotp: (secret: Uint8Array, code: string, { algorithm, digits, period, window, now }?: VerifyOptions) => Promise<boolean>;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
1
|
export type { OtpAuthUri } from './otp-auth-uri';
|
|
2
2
|
export type { TotpOptions } from './totp-options';
|
|
3
3
|
export type { VerifyOptions } from './verify-options';
|
|
4
|
+
/**
|
|
5
|
+
* Supported hash algorithms for TOTP/HOTP
|
|
6
|
+
*/
|
|
7
|
+
export type HashAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-512';
|
|
8
|
+
/**
|
|
9
|
+
* Valid digit counts for OTP codes
|
|
10
|
+
*/
|
|
11
|
+
export type OtpDigits = 6 | 8;
|
package/dist/utils/base32.d.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Encode bytes to Base32 string
|
|
3
3
|
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Performance: Uses array.join() instead of string concatenation
|
|
6
|
+
* to avoid creating intermediate strings in the hot path.
|
|
7
|
+
*
|
|
4
8
|
* @param input - Bytes or string to encode
|
|
5
9
|
* @param withPadding - Whether to include padding (default: true)
|
|
6
10
|
*
|
|
@@ -10,7 +14,12 @@ export declare const base32Encode: (input: string | Uint8Array, withPadding?: bo
|
|
|
10
14
|
/**
|
|
11
15
|
* Decode Base32 string to bytes
|
|
12
16
|
*
|
|
13
|
-
* @
|
|
17
|
+
* @remarks
|
|
18
|
+
* Security: Case-insensitive decoding per RFC 4648 recommendation.
|
|
19
|
+
* Performance: Uses Map lookup for O(1) character value retrieval.
|
|
20
|
+
* Performance: Pre-allocates output array based on input length.
|
|
21
|
+
*
|
|
22
|
+
* @param base32 - Base32 string to decode (case-insensitive)
|
|
14
23
|
*
|
|
15
24
|
* @throws ({@link InternalError}) - if invalid Base32 character is found
|
|
16
25
|
*
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Convert a counter value to an 8-byte big-endian buffer
|
|
3
3
|
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Always uses BigInt internally to support full 64-bit counter values.
|
|
6
|
+
* This fixes potential integer overflow issues when counter exceeds 2^32-1.
|
|
7
|
+
*
|
|
8
|
+
* For TOTP, time steps are calculated as Math.floor(Date.now() / 1000 / period).
|
|
9
|
+
* Current time steps are ~57 million, well within 32-bit range, but this
|
|
10
|
+
* ensures correctness for far-future dates and high-precision use cases.
|
|
11
|
+
*
|
|
4
12
|
* @param counter - Counter value as number or bigint
|
|
5
13
|
*
|
|
6
|
-
* @returns ArrayBuffer containing the counter in big-endian format
|
|
14
|
+
* @returns ArrayBuffer containing the counter in big-endian format (8 bytes)
|
|
7
15
|
*/
|
|
8
16
|
export declare const createCounterBuffer: (counter: number | bigint) => ArrayBuffer;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import type { OtpDigits } from '../types';
|
|
1
2
|
/**
|
|
2
3
|
* Perform dynamic truncation on HMAC result according to RFC 4226
|
|
3
4
|
*
|
|
4
|
-
* @param hmacArray - HMAC result as byte array
|
|
5
|
-
* @param digits - Number of digits in the final code
|
|
5
|
+
* @param hmacArray - HMAC result as byte array (minimum 20 bytes)
|
|
6
|
+
* @param digits - Number of digits in the final code (6 or 8)
|
|
7
|
+
*
|
|
8
|
+
* @throws ({@link InternalError}) - if HMAC array is too short or digits is invalid
|
|
6
9
|
*
|
|
7
10
|
* @returns Truncated code as string with leading zeros
|
|
8
11
|
*/
|
|
9
|
-
export declare const dynamicTruncation: (hmacArray: Uint8Array, digits:
|
|
12
|
+
export declare const dynamicTruncation: (hmacArray: Uint8Array, digits: OtpDigits) => string;
|
package/dist/utils/index.js
CHANGED
|
@@ -3,53 +3,63 @@ import {
|
|
|
3
3
|
createCounterBuffer,
|
|
4
4
|
dynamicTruncation,
|
|
5
5
|
generateHmac
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-t1hx3d3e.js";
|
|
7
7
|
import {
|
|
8
8
|
TOTP_ERROR_KEYS
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-zfdfmrcn.js";
|
|
10
10
|
|
|
11
11
|
// source/utils/base32.ts
|
|
12
12
|
import { InternalError } from "@nowarajs/error";
|
|
13
13
|
var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
|
14
|
+
var _BASE32_LOOKUP = new Map(Array.from(BASE32_ALPHABET, (char, index) => [char, index]));
|
|
14
15
|
var base32Encode = (input, withPadding = true) => {
|
|
15
|
-
|
|
16
|
+
const bytes = input instanceof Uint8Array ? input : new TextEncoder().encode(input);
|
|
17
|
+
if (bytes.length === 0)
|
|
18
|
+
return "";
|
|
19
|
+
const estimatedLength = Math.ceil(bytes.length * 8 / 5);
|
|
20
|
+
const chars = new Array(estimatedLength);
|
|
16
21
|
let bits = 0;
|
|
17
22
|
let value = 0;
|
|
18
|
-
|
|
19
|
-
for (
|
|
20
|
-
value = value << 8 |
|
|
23
|
+
let charIndex = 0;
|
|
24
|
+
for (let i = 0;i < bytes.length; ++i) {
|
|
25
|
+
value = value << 8 | bytes[i];
|
|
21
26
|
bits += 8;
|
|
22
27
|
while (bits >= 5) {
|
|
23
|
-
|
|
28
|
+
chars[charIndex++] = BASE32_ALPHABET[value >>> bits - 5 & 31];
|
|
24
29
|
bits -= 5;
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
32
|
if (bits > 0)
|
|
28
|
-
|
|
33
|
+
chars[charIndex++] = BASE32_ALPHABET[value << 5 - bits & 31];
|
|
34
|
+
let result = chars.slice(0, charIndex).join("");
|
|
29
35
|
if (withPadding)
|
|
30
36
|
while (result.length % 8 !== 0)
|
|
31
37
|
result += "=";
|
|
32
38
|
return result;
|
|
33
39
|
};
|
|
34
40
|
var base32Decode = (base32) => {
|
|
35
|
-
const cleanBase32 = base32.replace(/=+$/, "");
|
|
36
|
-
|
|
41
|
+
const cleanBase32 = base32.toUpperCase().replace(/=+$/, "");
|
|
42
|
+
const inputLength = cleanBase32.length;
|
|
43
|
+
if (inputLength === 0)
|
|
37
44
|
return new Uint8Array(0);
|
|
38
|
-
const
|
|
45
|
+
const outputLength = Math.floor(inputLength * 5 / 8);
|
|
46
|
+
const result = new Uint8Array(outputLength);
|
|
39
47
|
let bits = 0;
|
|
40
48
|
let value = 0;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
49
|
+
let outputIndex = 0;
|
|
50
|
+
for (let i = 0;i < inputLength; ++i) {
|
|
51
|
+
const char = cleanBase32[i];
|
|
52
|
+
const charValue = _BASE32_LOOKUP.get(char);
|
|
53
|
+
if (charValue === undefined)
|
|
54
|
+
throw new InternalError(TOTP_ERROR_KEYS.INVALID_BASE32_CHARACTER, "Invalid Base32 character");
|
|
45
55
|
value = value << 5 | charValue;
|
|
46
56
|
bits += 5;
|
|
47
57
|
if (bits >= 8) {
|
|
48
|
-
result
|
|
58
|
+
result[outputIndex++] = value >>> bits - 8 & 255;
|
|
49
59
|
bits -= 8;
|
|
50
60
|
}
|
|
51
61
|
}
|
|
52
|
-
return
|
|
62
|
+
return result;
|
|
53
63
|
};
|
|
54
64
|
// source/utils/generate-secret-bytes.ts
|
|
55
65
|
import { InternalError as InternalError2 } from "@nowarajs/error";
|
package/package.json
CHANGED
package/dist/chunk-sx6rwmqe.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
// source/enums/totp-error-keys.ts
|
|
3
|
-
var TOTP_ERROR_KEYS = {
|
|
4
|
-
INVALID_ALGORITHM: "nowarajs.totp.error.invalid_algorithm",
|
|
5
|
-
INVALID_BASE32_CHARACTER: "nowarajs.totp.error.invalid_base32_character",
|
|
6
|
-
INVALID_OTP_AUTH_URI: "nowarajs.totp.error.invalid_otp_auth_uri",
|
|
7
|
-
INVALID_SECRET_LENGTH: "nowarajs.totp.error.invalid_secret_length",
|
|
8
|
-
MISSING_SECRET: "nowarajs.totp.error.missing_secret"
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export { TOTP_ERROR_KEYS };
|