@nauth-toolkit/mfa-totp 0.1.93 → 0.1.95
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/src/totp.service.d.ts.map +1 -1
- package/dist/src/totp.service.js +22 -19
- package/dist/src/totp.service.js.map +1 -1
- package/dist/src/totp.utils.d.ts +99 -0
- package/dist/src/totp.utils.d.ts.map +1 -0
- package/dist/src/totp.utils.js +179 -0
- package/dist/src/totp.utils.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totp.service.d.ts","sourceRoot":"","sources":["../../src/totp.service.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"totp.service.d.ts","sourceRoot":"","sources":["../../src/totp.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAc,WAAW,EAAiC,MAAM,qBAAqB,CAAC;AAC1G,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,qBAAa,WAAW;IASpB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;IATzB,OAAO,CAAC,QAAQ,CAAC,aAAa,CAK5B;gBAGiB,MAAM,EAAE,WAAW,EACnB,MAAM,EAAE,WAAW;IAStC;;;;;OAKG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;;OAKG;IACH,OAAO,CAAC,SAAS;IAQjB;;;;;;;;;;;;;;;;OAgBG;IACG,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC;IA4CxE;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,0BAA0B;IAQlC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAwChE;;;;;;;;;;;;;;;;OAgBG;IACG,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAkCtG;;;;;;;;;;;;;;;OAeG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUnD;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAkBtC;;;;;;;;;;;;;OAaG;IACH,gBAAgB,IAAI,MAAM;CAM3B"}
|
package/dist/src/totp.service.js
CHANGED
|
@@ -34,9 +34,9 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.TOTPService = void 0;
|
|
37
|
-
const otplib_1 = require("otplib");
|
|
38
37
|
const QRCode = __importStar(require("qrcode"));
|
|
39
38
|
const core_1 = require("@nauth-toolkit/core");
|
|
39
|
+
const totp_utils_1 = require("./totp.utils");
|
|
40
40
|
/**
|
|
41
41
|
* TOTP (Time-based One-Time Password) Service
|
|
42
42
|
*
|
|
@@ -119,12 +119,13 @@ class TOTPService {
|
|
|
119
119
|
async generateSecret(accountName) {
|
|
120
120
|
this.logger?.log?.(`Generating TOTP secret for: ${accountName}`);
|
|
121
121
|
// Generate base32-encoded secret
|
|
122
|
-
const secret = (0,
|
|
122
|
+
const secret = (0, totp_utils_1.generateBase32Secret)();
|
|
123
123
|
// Get issuer name
|
|
124
124
|
const issuer = this.getIssuer();
|
|
125
|
-
// Generate otpauth URI for QR code
|
|
126
125
|
const totpConfig = this.getTOTPConfig();
|
|
127
|
-
|
|
126
|
+
// Generate `otpauth://` URI for QR code.
|
|
127
|
+
// This avoids `otplib` (which can trigger CJS/ESM interop failures on Node 22 in CJS apps).
|
|
128
|
+
const otpauthUrl = (0, totp_utils_1.buildOtpAuthUri)({
|
|
128
129
|
label: accountName,
|
|
129
130
|
issuer,
|
|
130
131
|
secret,
|
|
@@ -199,28 +200,28 @@ class TOTPService {
|
|
|
199
200
|
try {
|
|
200
201
|
// Remove spaces and validate format
|
|
201
202
|
const cleanCode = code.replace(/\s/g, '');
|
|
202
|
-
|
|
203
|
+
const totpConfig = this.getTOTPConfig();
|
|
204
|
+
// Validate against configured digits (default 6)
|
|
205
|
+
const digits = totpConfig.digits;
|
|
206
|
+
const tokenRegex = new RegExp(`^\\d{${digits}}$`);
|
|
207
|
+
if (!tokenRegex.test(cleanCode)) {
|
|
203
208
|
this.logger?.warn?.('Invalid TOTP code format');
|
|
204
209
|
return false;
|
|
205
210
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const result = await (0, otplib_1.verify)({
|
|
211
|
+
// Verify code using RFC 6238 implementation (no `otplib` dependency).
|
|
212
|
+
const isValid = (0, totp_utils_1.totpVerify)({
|
|
209
213
|
secret,
|
|
210
|
-
token: cleanCode,
|
|
211
|
-
strategy: 'totp',
|
|
212
|
-
epochTolerance: totpConfig.window,
|
|
213
214
|
period: totpConfig.stepSeconds,
|
|
214
|
-
digits
|
|
215
|
+
digits,
|
|
215
216
|
algorithm: totpConfig.algorithm,
|
|
216
|
-
});
|
|
217
|
-
if (
|
|
217
|
+
}, cleanCode, totpConfig.window);
|
|
218
|
+
if (isValid) {
|
|
218
219
|
this.logger?.debug?.('TOTP code verified successfully');
|
|
219
220
|
}
|
|
220
221
|
else {
|
|
221
222
|
this.logger?.warn?.('TOTP code verification failed');
|
|
222
223
|
}
|
|
223
|
-
return
|
|
224
|
+
return isValid;
|
|
224
225
|
}
|
|
225
226
|
catch (error) {
|
|
226
227
|
this.logger?.error?.('TOTP verification error', error);
|
|
@@ -250,8 +251,11 @@ class TOTPService {
|
|
|
250
251
|
if (!cleanCode) {
|
|
251
252
|
return { valid: false, error: 'Code is required' };
|
|
252
253
|
}
|
|
253
|
-
|
|
254
|
-
|
|
254
|
+
const totpConfig = this.getTOTPConfig();
|
|
255
|
+
const digits = totpConfig.digits;
|
|
256
|
+
const tokenRegex = new RegExp(`^\\d{${digits}}$`);
|
|
257
|
+
if (!tokenRegex.test(cleanCode)) {
|
|
258
|
+
return { valid: false, error: `Code must be ${digits} digits` };
|
|
255
259
|
}
|
|
256
260
|
// Verify secret format
|
|
257
261
|
if (!secret || secret.length < 16) {
|
|
@@ -285,9 +289,8 @@ class TOTPService {
|
|
|
285
289
|
*/
|
|
286
290
|
async generateCode(secret) {
|
|
287
291
|
const totpConfig = this.getTOTPConfig();
|
|
288
|
-
return
|
|
292
|
+
return (0, totp_utils_1.totpGenerate)({
|
|
289
293
|
secret,
|
|
290
|
-
strategy: 'totp',
|
|
291
294
|
period: totpConfig.stepSeconds,
|
|
292
295
|
digits: totpConfig.digits,
|
|
293
296
|
algorithm: totpConfig.algorithm,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"totp.service.js","sourceRoot":"","sources":["../../src/totp.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA
|
|
1
|
+
{"version":3,"file":"totp.service.js","sourceRoot":"","sources":["../../src/totp.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+CAAiC;AACjC,8CAA0G;AAE1G,6CAA+F;AAE/F;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,MAAa,WAAW;IASH;IACA;IATF,aAAa,GAAyB;QACrD,MAAM,EAAE,CAAC;QACT,WAAW,EAAE,EAAE;QACf,MAAM,EAAE,CAAC;QACT,SAAS,EAAE,MAAM;KAClB,CAAC;IAEF,YACmB,MAAmB,EACnB,MAAmB;QADnB,WAAM,GAAN,MAAM,CAAa;QACnB,WAAM,GAAN,MAAM,CAAa;QAEpC,yBAAyB;QACzB,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAClB,yBAAyB,UAAU,CAAC,WAAW,aAAa,UAAU,CAAC,MAAM,YAAY,UAAU,CAAC,MAAM,EAAE,CAC7G,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACK,aAAa;QACnB,OAAO;YACL,GAAG,IAAI,CAAC,aAAa;YACrB,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,IAAI;SACzB,CAAC;IACJ,CAAC;IAED;;;;;OAKG;IACK,SAAS;QACf,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,IAAI,eAAe,CAAC;IACpD,CAAC;IAED,+EAA+E;IAC/E,oBAAoB;IACpB,+EAA+E;IAE/E;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,cAAc,CAAC,WAAmB;QACtC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,+BAA+B,WAAW,EAAE,CAAC,CAAC;QAEjE,iCAAiC;QACjC,MAAM,MAAM,GAAG,IAAA,iCAAoB,GAAE,CAAC;QAEtC,kBAAkB;QAClB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAEhC,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,yCAAyC;QACzC,4FAA4F;QAC5F,MAAM,UAAU,GAAG,IAAA,4BAAe,EAAC;YACjC,KAAK,EAAE,WAAW;YAClB,MAAM;YACN,MAAM;YACN,SAAS,EAAE,UAAU,CAAC,SAAS;YAC/B,MAAM,EAAE,UAAU,CAAC,MAAe;YAClC,MAAM,EAAE,UAAU,CAAC,WAAW;SAC/B,CAAC,CAAC;QAEH,+BAA+B;QAC/B,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,4BAA4B,EAAE,KAAK,CAAC,CAAC;YAC1D,MAAM,IAAI,qBAAc,CAAC,oBAAa,CAAC,cAAc,EAAE,4BAA4B,CAAC,CAAC;QACvF,CAAC;QAED,0DAA0D;QAC1D,MAAM,cAAc,GAAG,IAAI,CAAC,0BAA0B,CAAC,MAAM,CAAC,CAAC;QAE/D,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,2CAA2C,WAAW,EAAE,CAAC,CAAC;QAE7E,OAAO;YACL,MAAM;YACN,MAAM;YACN,cAAc;YACd,MAAM;YACN,WAAW;SACZ,CAAC;IACJ,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACK,0BAA0B,CAAC,MAAc;QAC/C,OAAO,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC;IACtD,CAAC;IAED,+EAA+E;IAC/E,oBAAoB;IACpB,+EAA+E;IAE/E;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAK,CAAC,UAAU,CAAC,MAAc,EAAE,IAAY;QAC3C,IAAI,CAAC;YACH,oCAAoC;YACpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;YAE1C,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;YAExC,iDAAiD;YACjD,MAAM,MAAM,GAAG,UAAU,CAAC,MAAe,CAAC;YAC1C,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,QAAQ,MAAM,IAAI,CAAC,CAAC;YAClD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;gBAChC,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,0BAA0B,CAAC,CAAC;gBAChD,OAAO,KAAK,CAAC;YACf,CAAC;YAED,sEAAsE;YACtE,MAAM,OAAO,GAAG,IAAA,uBAAU,EACxB;gBACE,MAAM;gBACN,MAAM,EAAE,UAAU,CAAC,WAAW;gBAC9B,MAAM;gBACN,SAAS,EAAE,UAAU,CAAC,SAAS;aAChC,EACD,SAAS,EACT,UAAU,CAAC,MAAM,CAClB,CAAC;YAEF,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,iCAAiC,CAAC,CAAC;YAC1D,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,+BAA+B,CAAC,CAAC;YACvD,CAAC;YAED,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,yBAAyB,EAAE,KAAK,CAAC,CAAC;YACvD,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,KAAK,CAAC,qBAAqB,CAAC,MAAc,EAAE,IAAY;QACtD,uBAAuB;QACvB,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAE1C,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,kBAAkB,EAAE,CAAC;QACrD,CAAC;QAED,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,UAAU,CAAC,MAAe,CAAC;QAC1C,MAAM,UAAU,GAAG,IAAI,MAAM,CAAC,QAAQ,MAAM,IAAI,CAAC,CAAC;QAClD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;YAChC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,MAAM,SAAS,EAAE,CAAC;QAClE,CAAC;QAED,uBAAuB;QACvB,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YAClC,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC;QACnD,CAAC;QAED,cAAc;QACd,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAEzD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,yBAAyB,EAAE,CAAC;QAC5D,CAAC;QAED,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACzB,CAAC;IAED,+EAA+E;IAC/E,kBAAkB;IAClB,+EAA+E;IAE/E;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,YAAY,CAAC,MAAc;QAC/B,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,OAAO,IAAA,yBAAY,EAAC;YAClB,MAAM;YACN,MAAM,EAAE,UAAU,CAAC,WAAW;YAC9B,MAAM,EAAE,UAAU,CAAC,MAAe;YAClC,SAAS,EAAE,UAAU,CAAC,SAAS;SAChC,CAAC,CAAC;IACL,CAAC;IAED;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,MAAc;QAC1B,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC1C,OAAO,KAAK,CAAC;QACf,CAAC;QAED,8DAA8D;QAC9D,IAAI,MAAM,CAAC,MAAM,GAAG,EAAE,EAAE,CAAC;YACvB,OAAO,KAAK,CAAC;QACf,CAAC;QAED,0DAA0D;QAC1D,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YAChC,OAAO,KAAK,CAAC;QACf,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,gBAAgB;QACd,MAAM,UAAU,GAAG,IAAI,CAAC,aAAa,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,MAAM,OAAO,GAAG,GAAG,GAAG,UAAU,CAAC,WAAW,CAAC;QAC7C,OAAO,UAAU,CAAC,WAAW,GAAG,OAAO,CAAC;IAC1C,CAAC;CACF;AAlUD,kCAkUC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supported hash algorithms for RFC 6238 TOTP.
|
|
3
|
+
*/
|
|
4
|
+
export type TotpHashAlgorithm = 'sha1' | 'sha256' | 'sha512';
|
|
5
|
+
/**
|
|
6
|
+
* Parameters required to generate/verify RFC 6238 TOTP tokens.
|
|
7
|
+
*/
|
|
8
|
+
export interface TotpParams {
|
|
9
|
+
/**
|
|
10
|
+
* Base32-encoded shared secret (RFC 4648, no padding).
|
|
11
|
+
*/
|
|
12
|
+
secret: string;
|
|
13
|
+
/**
|
|
14
|
+
* Number of digits in the token (typically 6 or 8).
|
|
15
|
+
*/
|
|
16
|
+
digits: 6 | 8;
|
|
17
|
+
/**
|
|
18
|
+
* Time step in seconds (typically 30).
|
|
19
|
+
*/
|
|
20
|
+
period: number;
|
|
21
|
+
/**
|
|
22
|
+
* HMAC hash algorithm used for token generation.
|
|
23
|
+
*/
|
|
24
|
+
algorithm: TotpHashAlgorithm;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Parameters required to build a standard `otpauth://` URI for authenticator apps.
|
|
28
|
+
*/
|
|
29
|
+
export interface OtpAuthUriParams extends TotpParams {
|
|
30
|
+
/**
|
|
31
|
+
* Issuer name shown in authenticator apps (e.g. "Acme").
|
|
32
|
+
*/
|
|
33
|
+
issuer: string;
|
|
34
|
+
/**
|
|
35
|
+
* Account label shown in authenticator apps (e.g. user email).
|
|
36
|
+
*/
|
|
37
|
+
label: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Decode a base32 string (RFC 4648, no padding) into bytes.
|
|
41
|
+
*
|
|
42
|
+
* ⚠️ WARNING: This is security-sensitive. We reject invalid characters to avoid
|
|
43
|
+
* ambiguous decoding behavior.
|
|
44
|
+
*
|
|
45
|
+
* @param input - Base32 string, case-insensitive, without padding.
|
|
46
|
+
* @returns Decoded bytes.
|
|
47
|
+
* @throws {Error} When input contains invalid base32 characters.
|
|
48
|
+
*/
|
|
49
|
+
export declare function base32Decode(input: string): Uint8Array;
|
|
50
|
+
/**
|
|
51
|
+
* Encode bytes into a base32 string (RFC 4648, no padding).
|
|
52
|
+
*
|
|
53
|
+
* @param bytes - Input bytes.
|
|
54
|
+
* @returns Base32 string without padding.
|
|
55
|
+
*/
|
|
56
|
+
export declare function base32Encode(bytes: Uint8Array): string;
|
|
57
|
+
/**
|
|
58
|
+
* Generate a random base32 secret suitable for TOTP.
|
|
59
|
+
*
|
|
60
|
+
* @param byteLength - Number of random bytes to generate (default 20 = 160-bit secret).
|
|
61
|
+
* @returns Base32 secret (uppercase, no padding).
|
|
62
|
+
*/
|
|
63
|
+
export declare function generateBase32Secret(byteLength?: number): string;
|
|
64
|
+
/**
|
|
65
|
+
* Generate an HOTP token for a secret and counter (RFC 4226).
|
|
66
|
+
*
|
|
67
|
+
* @param secretBytes - Shared secret bytes.
|
|
68
|
+
* @param counter - HOTP counter value.
|
|
69
|
+
* @param digits - Token length (6 or 8).
|
|
70
|
+
* @param algorithm - HMAC hash algorithm.
|
|
71
|
+
* @returns Numeric token as a zero-padded string.
|
|
72
|
+
*/
|
|
73
|
+
export declare function hotpGenerate(secretBytes: Uint8Array, counter: bigint, digits: 6 | 8, algorithm: TotpHashAlgorithm): string;
|
|
74
|
+
/**
|
|
75
|
+
* Generate a TOTP token for the current time (RFC 6238).
|
|
76
|
+
*
|
|
77
|
+
* @param params - TOTP generation parameters.
|
|
78
|
+
* @param nowMs - Current time in milliseconds (defaults to `Date.now()`).
|
|
79
|
+
* @returns Token as a numeric string.
|
|
80
|
+
*/
|
|
81
|
+
export declare function totpGenerate(params: TotpParams, nowMs?: number): string;
|
|
82
|
+
/**
|
|
83
|
+
* Verify a TOTP token within a time-step window.
|
|
84
|
+
*
|
|
85
|
+
* @param params - TOTP verification parameters.
|
|
86
|
+
* @param token - Token to verify (numeric string).
|
|
87
|
+
* @param window - Number of time steps to check before/after the current step.
|
|
88
|
+
* @param nowMs - Current time in milliseconds (defaults to `Date.now()`).
|
|
89
|
+
* @returns `true` when token matches within the window.
|
|
90
|
+
*/
|
|
91
|
+
export declare function totpVerify(params: TotpParams, token: string, window: number, nowMs?: number): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Build an `otpauth://` URI compatible with major authenticator apps.
|
|
94
|
+
*
|
|
95
|
+
* @param params - URI parameters.
|
|
96
|
+
* @returns `otpauth://totp/...` URI.
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildOtpAuthUri(params: OtpAuthUriParams): string;
|
|
99
|
+
//# sourceMappingURL=totp.utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.utils.d.ts","sourceRoot":"","sources":["../../src/totp.utils.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC;IAEd;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,EAAE,iBAAiB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAiB,SAAQ,UAAU;IAClD;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,KAAK,EAAE,MAAM,CAAC;CACf;AAgBD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CA+BtD;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAoBtD;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,GAAE,MAAW,GAAG,MAAM,CAGpE;AAMD;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,WAAW,EAAE,UAAU,EACvB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,CAAC,GAAG,CAAC,EACb,SAAS,EAAE,iBAAiB,GAC3B,MAAM,CAgBR;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,GAAE,MAAmB,GAAG,MAAM,CAKnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAmB,GACzB,OAAO,CAoBT;AAMD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAehE"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.base32Decode = base32Decode;
|
|
4
|
+
exports.base32Encode = base32Encode;
|
|
5
|
+
exports.generateBase32Secret = generateBase32Secret;
|
|
6
|
+
exports.hotpGenerate = hotpGenerate;
|
|
7
|
+
exports.totpGenerate = totpGenerate;
|
|
8
|
+
exports.totpVerify = totpVerify;
|
|
9
|
+
exports.buildOtpAuthUri = buildOtpAuthUri;
|
|
10
|
+
const crypto_1 = require("crypto");
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// Base32 (RFC 4648) helpers
|
|
13
|
+
// ============================================================================
|
|
14
|
+
const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
|
|
15
|
+
const BASE32_LOOKUP = (() => {
|
|
16
|
+
const lookup = {};
|
|
17
|
+
for (let i = 0; i < BASE32_ALPHABET.length; i += 1) {
|
|
18
|
+
lookup[BASE32_ALPHABET[i]] = i;
|
|
19
|
+
}
|
|
20
|
+
return lookup;
|
|
21
|
+
})();
|
|
22
|
+
/**
|
|
23
|
+
* Decode a base32 string (RFC 4648, no padding) into bytes.
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ WARNING: This is security-sensitive. We reject invalid characters to avoid
|
|
26
|
+
* ambiguous decoding behavior.
|
|
27
|
+
*
|
|
28
|
+
* @param input - Base32 string, case-insensitive, without padding.
|
|
29
|
+
* @returns Decoded bytes.
|
|
30
|
+
* @throws {Error} When input contains invalid base32 characters.
|
|
31
|
+
*/
|
|
32
|
+
function base32Decode(input) {
|
|
33
|
+
const normalized = input.toUpperCase().replace(/\s+/g, '');
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
return new Uint8Array(0);
|
|
36
|
+
}
|
|
37
|
+
// Reject padding to match common authenticator secret formats and our own validation.
|
|
38
|
+
if (normalized.includes('=')) {
|
|
39
|
+
throw new Error('Invalid base32: padding is not supported');
|
|
40
|
+
}
|
|
41
|
+
let buffer = 0;
|
|
42
|
+
let bits = 0;
|
|
43
|
+
const bytes = [];
|
|
44
|
+
for (const ch of normalized) {
|
|
45
|
+
const val = BASE32_LOOKUP[ch];
|
|
46
|
+
if (val === undefined) {
|
|
47
|
+
throw new Error(`Invalid base32: illegal character "${ch}"`);
|
|
48
|
+
}
|
|
49
|
+
buffer = (buffer << 5) | val;
|
|
50
|
+
bits += 5;
|
|
51
|
+
while (bits >= 8) {
|
|
52
|
+
bits -= 8;
|
|
53
|
+
bytes.push((buffer >> bits) & 0xff);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return Uint8Array.from(bytes);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Encode bytes into a base32 string (RFC 4648, no padding).
|
|
60
|
+
*
|
|
61
|
+
* @param bytes - Input bytes.
|
|
62
|
+
* @returns Base32 string without padding.
|
|
63
|
+
*/
|
|
64
|
+
function base32Encode(bytes) {
|
|
65
|
+
let buffer = 0;
|
|
66
|
+
let bits = 0;
|
|
67
|
+
let out = '';
|
|
68
|
+
for (const b of bytes) {
|
|
69
|
+
buffer = (buffer << 8) | b;
|
|
70
|
+
bits += 8;
|
|
71
|
+
while (bits >= 5) {
|
|
72
|
+
bits -= 5;
|
|
73
|
+
out += BASE32_ALPHABET[(buffer >> bits) & 31];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (bits > 0) {
|
|
77
|
+
out += BASE32_ALPHABET[(buffer << (5 - bits)) & 31];
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Generate a random base32 secret suitable for TOTP.
|
|
83
|
+
*
|
|
84
|
+
* @param byteLength - Number of random bytes to generate (default 20 = 160-bit secret).
|
|
85
|
+
* @returns Base32 secret (uppercase, no padding).
|
|
86
|
+
*/
|
|
87
|
+
function generateBase32Secret(byteLength = 20) {
|
|
88
|
+
const bytes = (0, crypto_1.randomBytes)(byteLength);
|
|
89
|
+
return base32Encode(bytes);
|
|
90
|
+
}
|
|
91
|
+
// ============================================================================
|
|
92
|
+
// RFC 4226 HOTP + RFC 6238 TOTP
|
|
93
|
+
// ============================================================================
|
|
94
|
+
/**
|
|
95
|
+
* Generate an HOTP token for a secret and counter (RFC 4226).
|
|
96
|
+
*
|
|
97
|
+
* @param secretBytes - Shared secret bytes.
|
|
98
|
+
* @param counter - HOTP counter value.
|
|
99
|
+
* @param digits - Token length (6 or 8).
|
|
100
|
+
* @param algorithm - HMAC hash algorithm.
|
|
101
|
+
* @returns Numeric token as a zero-padded string.
|
|
102
|
+
*/
|
|
103
|
+
function hotpGenerate(secretBytes, counter, digits, algorithm) {
|
|
104
|
+
// 8-byte counter, big-endian
|
|
105
|
+
const msg = Buffer.alloc(8);
|
|
106
|
+
msg.writeBigUInt64BE(counter);
|
|
107
|
+
const hmac = (0, crypto_1.createHmac)(algorithm, Buffer.from(secretBytes)).update(msg).digest();
|
|
108
|
+
const offset = hmac[hmac.length - 1] & 0x0f;
|
|
109
|
+
const code = ((hmac[offset] & 0x7f) << 24) |
|
|
110
|
+
((hmac[offset + 1] & 0xff) << 16) |
|
|
111
|
+
((hmac[offset + 2] & 0xff) << 8) |
|
|
112
|
+
(hmac[offset + 3] & 0xff);
|
|
113
|
+
const mod = 10 ** digits;
|
|
114
|
+
const token = String(code % mod).padStart(digits, '0');
|
|
115
|
+
return token;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Generate a TOTP token for the current time (RFC 6238).
|
|
119
|
+
*
|
|
120
|
+
* @param params - TOTP generation parameters.
|
|
121
|
+
* @param nowMs - Current time in milliseconds (defaults to `Date.now()`).
|
|
122
|
+
* @returns Token as a numeric string.
|
|
123
|
+
*/
|
|
124
|
+
function totpGenerate(params, nowMs = Date.now()) {
|
|
125
|
+
const { secret, digits, period, algorithm } = params;
|
|
126
|
+
const secretBytes = base32Decode(secret);
|
|
127
|
+
const counter = BigInt(Math.floor(nowMs / 1000 / period));
|
|
128
|
+
return hotpGenerate(secretBytes, counter, digits, algorithm);
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Verify a TOTP token within a time-step window.
|
|
132
|
+
*
|
|
133
|
+
* @param params - TOTP verification parameters.
|
|
134
|
+
* @param token - Token to verify (numeric string).
|
|
135
|
+
* @param window - Number of time steps to check before/after the current step.
|
|
136
|
+
* @param nowMs - Current time in milliseconds (defaults to `Date.now()`).
|
|
137
|
+
* @returns `true` when token matches within the window.
|
|
138
|
+
*/
|
|
139
|
+
function totpVerify(params, token, window, nowMs = Date.now()) {
|
|
140
|
+
const { secret, digits, period, algorithm } = params;
|
|
141
|
+
const secretBytes = base32Decode(secret);
|
|
142
|
+
const current = BigInt(Math.floor(nowMs / 1000 / period));
|
|
143
|
+
// Normalize token once to avoid repeated work in window scan.
|
|
144
|
+
const cleanToken = token.replace(/\s+/g, '');
|
|
145
|
+
for (let w = -window; w <= window; w += 1) {
|
|
146
|
+
const counter = current + BigInt(w);
|
|
147
|
+
if (counter < 0n) {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const expected = hotpGenerate(secretBytes, counter, digits, algorithm);
|
|
151
|
+
if (expected === cleanToken) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
// ============================================================================
|
|
158
|
+
// otpauth:// URI builder
|
|
159
|
+
// ============================================================================
|
|
160
|
+
/**
|
|
161
|
+
* Build an `otpauth://` URI compatible with major authenticator apps.
|
|
162
|
+
*
|
|
163
|
+
* @param params - URI parameters.
|
|
164
|
+
* @returns `otpauth://totp/...` URI.
|
|
165
|
+
*/
|
|
166
|
+
function buildOtpAuthUri(params) {
|
|
167
|
+
const { issuer, label, secret, algorithm, digits, period } = params;
|
|
168
|
+
// Common convention: label in the path contains "issuer:account".
|
|
169
|
+
const pathLabel = encodeURIComponent(`${issuer}:${label}`);
|
|
170
|
+
const query = new URLSearchParams({
|
|
171
|
+
secret,
|
|
172
|
+
issuer,
|
|
173
|
+
algorithm: algorithm.toUpperCase(),
|
|
174
|
+
digits: String(digits),
|
|
175
|
+
period: String(period),
|
|
176
|
+
});
|
|
177
|
+
return `otpauth://totp/${pathLabel}?${query.toString()}`;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=totp.utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"totp.utils.js","sourceRoot":"","sources":["../../src/totp.utils.ts"],"names":[],"mappings":"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"}
|