@nowarajs/totp 1.1.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/CHANGELOG.md ADDED
@@ -0,0 +1,75 @@
1
+
2
+ ## v1.1.2
3
+
4
+ [compare changes](https://github.com/NowaraJS/totp/compare/v1.1.1...v1.1.2)
5
+
6
+ ### ๐Ÿฆ‰ Chore
7
+
8
+ - **๐Ÿฆ‰:** [Update package.json with correct name and description] ([e007586](https://github.com/NowaraJS/totp/commit/e007586))
9
+
10
+ ### โค๏ธ Contributors
11
+
12
+ - Komiroko <komiriko@pm.me>
13
+
14
+ ## v1.1.1
15
+
16
+ [compare changes](https://github.com/NowaraJS/totp/compare/v1.1.0...v1.1.1)
17
+
18
+ ### ๐Ÿ“– Documentation
19
+
20
+ - **๐Ÿ“–:** [Update README with comprehensive TOTP features and usage] ([e8f8369](https://github.com/NowaraJS/totp/commit/e8f8369))
21
+
22
+ ### โค๏ธ Contributors
23
+
24
+ - Komiroko <komiriko@pm.me>
25
+
26
+ ## v1.1.0
27
+
28
+
29
+ ### ๐Ÿš€ Enhancements
30
+
31
+ - **๐Ÿš€:** [Add Base32 encoding and decoding utilities] ([580be95](https://github.com/NowaraJS/totp/commit/580be95))
32
+ - **๐Ÿš€:** [Add TOTP error keys enumeration] ([cd31d9d](https://github.com/NowaraJS/totp/commit/cd31d9d))
33
+ - **๐Ÿš€:** [Add createCounterBuffer utility for big-endian conversion] ([6effcbf](https://github.com/NowaraJS/totp/commit/6effcbf))
34
+ - **๐Ÿš€:** [Add dynamic truncation utility for HMAC results] ([8b9d226](https://github.com/NowaraJS/totp/commit/8b9d226))
35
+ - **๐Ÿš€:** [Add generateHmac utility for HMAC generation] ## Features - Introduced a new utility function `generateHmac` for generating HMACs. ([86a62ee](https://github.com/NowaraJS/totp/commit/86a62ee))
36
+ - **๐Ÿš€:** [Add generateSecretBytes utility for TOTP secret generation] ([f0fa3b4](https://github.com/NowaraJS/totp/commit/f0fa3b4))
37
+ - **๐Ÿš€:** [Add timeRemaining utility for TOTP code calculation] ([38c2591](https://github.com/NowaraJS/totp/commit/38c2591))
38
+ - **๐Ÿš€:** [Implement HMAC-based One-Time Password (HOTP) functionality] ([57cc8db](https://github.com/NowaraJS/totp/commit/57cc8db))
39
+ - **๐Ÿš€:** [Add OTPAuth URI building and parsing functionality] ([3c3108c](https://github.com/NowaraJS/totp/commit/3c3108c))
40
+ - **๐Ÿš€:** [Add TOTP implementation and verification functionality] ([e9ca7da](https://github.com/NowaraJS/totp/commit/e9ca7da))
41
+
42
+ ### ๐Ÿ“– Documentation
43
+
44
+ - **๐Ÿ“–:** [Update copilot instructions for clarity and structure] ([57ca1ea](https://github.com/NowaraJS/totp/commit/57ca1ea))
45
+
46
+ ### ๐Ÿ“ฆ Build
47
+
48
+ - **๐Ÿ“ฆ:** [Update devDependencies and exports in package.json] ([2e32770](https://github.com/NowaraJS/totp/commit/2e32770))
49
+ - **๐Ÿ“ฆ:** [Refactor entrypoints in builder.ts for clarity] ([7e241b4](https://github.com/NowaraJS/totp/commit/7e241b4))
50
+ - **๐Ÿ“ฆ:** [Clean and add entrypoints] ([6a82236](https://github.com/NowaraJS/totp/commit/6a82236))
51
+
52
+ ### ๐Ÿฆ‰ Chore
53
+
54
+ - **๐Ÿฆ‰:** [Remove unused utility files and related tests] ## Chores - Deleted `exampleKeyError.ts`, `foo.ts`, and their corresponding test files. ## Description This commit removes unused utility files and their associated tests to clean up the codebase and reduce clutter. These files are no longer needed and their removal helps maintain a more manageable project structure. ([62c6231](https://github.com/NowaraJS/totp/commit/62c6231))
55
+
56
+ ### ๐Ÿงช Tests
57
+
58
+ - **๐Ÿงช:** [Add unit tests for Base32 encoding and decoding] ([7292a8d](https://github.com/NowaraJS/totp/commit/7292a8d))
59
+ - **๐Ÿงช:** [Add unit tests for createCounterBuffer utility] ([b13eea0](https://github.com/NowaraJS/totp/commit/b13eea0))
60
+ - **๐Ÿงช:** [Add unit tests for dynamic truncation utility] ([9dfb41c](https://github.com/NowaraJS/totp/commit/9dfb41c))
61
+ - **๐Ÿงช:** [Add unit tests for generateHmac utility] ([67aa60b](https://github.com/NowaraJS/totp/commit/67aa60b))
62
+ - **๐Ÿงช:** [Add unit tests for generateSecretBytes utility] ([52f2ec6](https://github.com/NowaraJS/totp/commit/52f2ec6))
63
+ - **๐Ÿงช:** [Add unit tests for timeRemaining utility] ## Tests - Introduced unit tests for the timeRemaining utility function. ## Description This commit adds a test suite for the timeRemaining function, ensuring that it correctly calculates the remaining time until the next TOTP code based on a specified period. The test checks that the remaining time is less than the period, validating the function's expected behavior. ([ae820e8](https://github.com/NowaraJS/totp/commit/ae820e8))
64
+ - **๐Ÿงช:** [Add unit tests for HOTP code generation] ([932fb33](https://github.com/NowaraJS/totp/commit/932fb33))
65
+ - **๐Ÿงช:** [Add unit tests for OTP Auth URI building and parsing] ## Tests - Implemented unit tests for `buildOtpAuthUri` to ensure it constructs a valid OTP Auth URI. - Added tests for `parseOtpAuthUri` to validate parsing of a valid OTP Auth URI and error handling for invalid cases. ([6160cbd](https://github.com/NowaraJS/totp/commit/6160cbd))
66
+ - **๐Ÿงช:** [Add unit tests for TOTP generation and verification] ## Tests - Added tests for TOTP code generation to ensure it produces a valid 6-digit code. - Implemented tests for TOTP code verification, including both valid and invalid scenarios. ## Description This commit introduces unit tests for the TOTP functionality, validating both the generation of TOTP codes and their verification. The tests check that generated codes are of the correct length and format, and that the verification function correctly identifies valid and invalid codes. ([445a954](https://github.com/NowaraJS/totp/commit/445a954))
67
+
68
+ ### ๐Ÿค– CI
69
+
70
+ - **๐Ÿค–:** [Enhance CI workflows with additional MSSQL secrets] ([b96813c](https://github.com/NowaraJS/totp/commit/b96813c))
71
+
72
+ ### โค๏ธ Contributors
73
+
74
+ - Komiroko <komiriko@pm.me>
75
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 [Project Name]
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,216 @@
1
+ # ๐Ÿ” NowaraJS - TOTP
2
+
3
+ ## ๐Ÿ“Œ Table of Contents
4
+
5
+ - [๐Ÿ” NowaraJS - TOTP](#-nowarajs---totp)
6
+ - [๐Ÿ“Œ Table of Contents](#-table-of-contents)
7
+ - [๐Ÿ“ Description](#-description)
8
+ - [โœจ Features](#-features)
9
+ - [๐Ÿ”ง Installation](#-installation)
10
+ - [โš™๏ธ Usage](#-usage)
11
+ - [Basic TOTP Generation](#basic-totp-generation)
12
+ - [TOTP Verification](#totp-verification)
13
+ - [HOTP Support](#hotp-support)
14
+ - [OTPAuth URI Generation](#otpauth-uri-generation)
15
+ - [Secret Generation](#secret-generation)
16
+ - [๐Ÿ”‘ Advanced Configuration](#-advanced-configuration)
17
+ - [๐Ÿ› ๏ธ Utilities](#-utilities)
18
+ - [๐Ÿ“š API Reference](#-api-reference)
19
+ - [โš–๏ธ License](#-license)
20
+ - [๐Ÿ“ง Contact](#-contact)
21
+
22
+ ## ๐Ÿ“ Description
23
+
24
+ > A comprehensive Time-based One-Time Password (TOTP) and HMAC-based One-Time Password (HOTP) implementation for TypeScript/JavaScript.
25
+
26
+ **NowaraJS TOTP** provides a secure and RFC-compliant implementation of TOTP and HOTP algorithms with full support for QR code generation, secret management, and various authentication configurations. Perfect for implementing two-factor authentication (2FA) in your applications.
27
+
28
+ ## โœจ Features
29
+
30
+ - ๐Ÿ” **RFC 6238 TOTP**: Full RFC-compliant Time-based One-Time Password implementation
31
+ - ๐Ÿ”‘ **RFC 4226 HOTP**: Complete HMAC-based One-Time Password support
32
+ - ๐Ÿ“ฑ **QR Code Support**: Generate OTPAuth URIs for easy mobile app integration
33
+ - ๐Ÿ”’ **Crypto Secure**: Uses Web Crypto API for secure random number generation
34
+ - โšก **High Performance**: Optimized for speed with minimal dependencies
35
+ - ๐Ÿ› ๏ธ **Configurable**: Support for different algorithms (SHA-1, SHA-256, SHA-512)
36
+ - ๐Ÿ“ **Flexible Digits**: Support for 6-8 digit codes
37
+ - โฐ **Time Window**: Configurable time periods and verification windows
38
+ - ๐ŸŽฏ **Base32 Encoding**: Built-in Base32 encoding/decoding utilities
39
+ - ๐Ÿงช **Type Safe**: Full TypeScript support with comprehensive type definitions
40
+
41
+ ## ๐Ÿ”ง Installation
42
+
43
+ ```bash
44
+ bun add @nowarajs/totp @nowarajs/error
45
+ ```
46
+
47
+ ## โš™๏ธ Usage
48
+
49
+ ### Basic TOTP Generation
50
+
51
+ ```ts
52
+ import { totp, generateSecretBytes, base32Encode } from '@nowarajs/totp';
53
+
54
+ // Generate a secret
55
+ const secret = generateSecretBytes(20); // 20 bytes = 160 bits
56
+ const secretBase32 = base32Encode(secret);
57
+
58
+ // Generate TOTP code
59
+ const code = await totp(secret, {
60
+ algorithm: 'SHA-1',
61
+ digits: 6,
62
+ period: 30
63
+ });
64
+
65
+ console.log('TOTP Code:', code); // e.g., "123456"
66
+ ```
67
+
68
+ ### TOTP Verification
69
+
70
+ ```ts
71
+ import { verifyTotp } from '@nowarajs/totp';
72
+
73
+ // Verify a TOTP code
74
+ const isValid = await verifyTotp(secret, userInputCode, {
75
+ algorithm: 'SHA-1',
76
+ digits: 6,
77
+ period: 30,
78
+ window: 1 // Allow 1 time step before/after current time
79
+ });
80
+
81
+ if (isValid) {
82
+ console.log('โœ… Code is valid!');
83
+ } else {
84
+ console.log('โŒ Invalid code');
85
+ }
86
+ ```
87
+
88
+ ### HOTP Support
89
+
90
+ ```ts
91
+ import { hotp } from '@nowarajs/totp';
92
+
93
+ // Generate HOTP code with counter
94
+ const counter = 123;
95
+ const hotpCode = await hotp(secret, counter, {
96
+ algorithm: 'SHA-1',
97
+ digits: 6
98
+ });
99
+
100
+ console.log('HOTP Code:', hotpCode);
101
+ ```
102
+
103
+ ### OTPAuth URI Generation
104
+
105
+ ```ts
106
+ import { buildOtpAuthUri, base32Encode } from '@nowarajs/totp';
107
+
108
+ const secret = generateSecretBytes(20);
109
+ const secretBase32 = base32Encode(secret);
110
+
111
+ // Create URI for QR code
112
+ const uri = buildOtpAuthUri({
113
+ secretBase32,
114
+ label: 'user@example.com',
115
+ issuer: 'MyApp',
116
+ algorithm: 'SHA-1',
117
+ digits: 6,
118
+ period: 30
119
+ });
120
+
121
+ console.log('QR Code URI:', uri);
122
+ // otpauth://totp/user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=MyApp
123
+ ```
124
+
125
+ ### Secret Generation
126
+
127
+ ```ts
128
+ import { generateSecretBytes, base32Encode, base32Decode } from '@nowarajs/totp/utils';
129
+
130
+ // Generate cryptographically secure secret
131
+ const secret = generateSecretBytes(32); // 256 bits for extra security
132
+
133
+ // Encode for storage/transmission
134
+ const encoded = base32Encode(secret);
135
+ console.log('Base32 Secret:', encoded);
136
+
137
+ // Decode when needed
138
+ const decoded = base32Decode(encoded);
139
+ console.log('Original bytes match:', secret.every((byte, i) => byte === decoded[i]));
140
+ ```
141
+
142
+ ## ๐Ÿ”‘ Advanced Configuration
143
+
144
+ ### Custom Algorithm and Settings
145
+
146
+ ```ts
147
+ // Use SHA-256 with 8 digits and 60-second period
148
+ const advancedCode = await totp(secret, {
149
+ algorithm: 'SHA-256',
150
+ digits: 8,
151
+ period: 60
152
+ });
153
+
154
+ // Verify with larger time window for clock drift tolerance
155
+ const isValid = await verifyTotp(secret, userCode, {
156
+ algorithm: 'SHA-256',
157
+ digits: 8,
158
+ period: 60,
159
+ window: 2 // Allow ยฑ2 time steps (ยฑ2 minutes)
160
+ });
161
+ ```
162
+
163
+ ### Parse Existing OTPAuth URIs
164
+
165
+ ```ts
166
+ import { parseOtpAuthUri } from '@nowarajs/totp';
167
+
168
+ const uri = 'otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example';
169
+ const parsed = parseOtpAuthUri(uri);
170
+
171
+ console.log(parsed);
172
+ // {
173
+ // type: 'totp',
174
+ // label: 'Example:alice@google.com',
175
+ // secret: 'JBSWY3DPEHPK3PXP',
176
+ // issuer: 'Example',
177
+ // algorithm: 'SHA-1',
178
+ // digits: 6,
179
+ // period: 30
180
+ // }
181
+ ```
182
+
183
+ ## ๐Ÿ› ๏ธ Utilities
184
+
185
+ The package includes several utility functions available through subpath imports:
186
+
187
+ ```ts
188
+ // Base32 encoding/decoding
189
+ import { base32Encode, base32Decode } from '@nowarajs/totp/utils';
190
+
191
+ // Secret generation
192
+ import { generateSecretBytes } from '@nowarajs/totp/utils';
193
+
194
+ // Time utilities
195
+ import { timeRemaining } from '@nowarajs/totp/utils';
196
+
197
+ // Get seconds until next TOTP generation
198
+ const remaining = timeRemaining(30); // for 30-second period
199
+ console.log(`Next code in ${remaining} seconds`);
200
+ ```
201
+
202
+ ## ๐Ÿ“š API Reference
203
+
204
+ You can find the complete API reference documentation for `NowaraJS TOTP` at:
205
+
206
+ - [Reference Documentation](https://nowarajs.github.io/totp/)
207
+
208
+ ## โš–๏ธ License
209
+
210
+ Distributed under the MIT License. See [LICENSE](./LICENSE) for more information.
211
+
212
+ ## ๐Ÿ“ง Contact
213
+
214
+ - GitHub: [NowaraJS](https://github.com/NowaraJS)
215
+ - Package: [@nowarajs/totp](https://www.npmjs.com/package/@nowarajs/totp)
216
+
@@ -0,0 +1,10 @@
1
+ // source/enums/totpErrorKeys.ts
2
+ var TOTP_ERROR_KEYS = {
3
+ INVALID_BASE32_CHARACTER: "totp.error.invalid_base32_character",
4
+ INVALID_SECRET_LENGTH: "totp.error.invalid_secret_length",
5
+ INVALID_ALGORITHM: "totp.error.invalid_algorithm",
6
+ INVALID_OTP_AUTH_URI: "totp.error.invalid_otp_auth_uri",
7
+ MISSING_SECRET: "totp.error.missing_secret"
8
+ };
9
+
10
+ export { TOTP_ERROR_KEYS };
@@ -0,0 +1,26 @@
1
+ // source/utils/createCounterBuffer.ts
2
+ var createCounterBuffer = (counter) => {
3
+ const counterBuffer = new ArrayBuffer(8);
4
+ const counterView = new DataView(counterBuffer);
5
+ if (typeof counter === "bigint")
6
+ counterView.setBigUint64(0, counter, false);
7
+ else
8
+ counterView.setUint32(4, counter, false);
9
+ return counterBuffer;
10
+ };
11
+
12
+ // source/utils/dynamicTruncation.ts
13
+ var dynamicTruncation = (hmacArray, digits) => {
14
+ const offset = hmacArray[hmacArray.length - 1] & 15;
15
+ const code = ((hmacArray[offset] & 127) << 24 | (hmacArray[offset + 1] & 255) << 16 | (hmacArray[offset + 2] & 255) << 8 | hmacArray[offset + 3] & 255) % 10 ** digits;
16
+ return code.toString().padStart(digits, "0");
17
+ };
18
+
19
+ // source/utils/generateHmac.ts
20
+ import { webcrypto } from "crypto";
21
+ var generateHmac = async (key, data) => {
22
+ const hmac = await webcrypto.subtle.sign("HMAC", key, data);
23
+ return new Uint8Array(hmac);
24
+ };
25
+
26
+ export { createCounterBuffer, dynamicTruncation, generateHmac };
@@ -0,0 +1 @@
1
+ export { TOTP_ERROR_KEYS } from './totpErrorKeys';
@@ -0,0 +1,7 @@
1
+ // @bun
2
+ import {
3
+ TOTP_ERROR_KEYS
4
+ } from "../chunk-4qsch0ea.js";
5
+ export {
6
+ TOTP_ERROR_KEYS
7
+ };
@@ -0,0 +1,7 @@
1
+ export declare const TOTP_ERROR_KEYS: {
2
+ readonly INVALID_BASE32_CHARACTER: "totp.error.invalid_base32_character";
3
+ readonly INVALID_SECRET_LENGTH: "totp.error.invalid_secret_length";
4
+ readonly INVALID_ALGORITHM: "totp.error.invalid_algorithm";
5
+ readonly INVALID_OTP_AUTH_URI: "totp.error.invalid_otp_auth_uri";
6
+ readonly MISSING_SECRET: "totp.error.missing_secret";
7
+ };
package/dist/hotp.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ import type { TotpOptions } from './types/totpOptions';
2
+ /**
3
+ * HMAC-based One-Time Password (HOTP) implementation
4
+ *
5
+ * @param secret - Secret key as bytes
6
+ * @param counter - Counter value
7
+ * @param opts - HOTP options
8
+ *
9
+ * @returns Promise resolving to the HOTP code
10
+ */
11
+ export declare const hotp: (secret: Uint8Array, counter: number | bigint, { algorithm, digits }?: TotpOptions) => Promise<string>;
@@ -0,0 +1,3 @@
1
+ export { hotp } from './hotp';
2
+ export { buildOtpAuthUri, parseOtpAuthUri } from './otpAuthUri';
3
+ export { totp, verifyTotp } from './totp';
package/dist/index.js ADDED
@@ -0,0 +1,108 @@
1
+ // @bun
2
+ import {
3
+ createCounterBuffer,
4
+ dynamicTruncation,
5
+ generateHmac
6
+ } from "./chunk-hhdy89hc.js";
7
+ import {
8
+ TOTP_ERROR_KEYS
9
+ } from "./chunk-4qsch0ea.js";
10
+
11
+ // source/hotp.ts
12
+ import { webcrypto } from "crypto";
13
+ var hotp = async (secret, counter, {
14
+ algorithm = "SHA-1",
15
+ digits = 6
16
+ } = {}) => {
17
+ const counterBuffer = createCounterBuffer(counter);
18
+ const key = await webcrypto.subtle.importKey("raw", secret, { name: "HMAC", hash: algorithm }, false, ["sign"]);
19
+ const hmacArray = await generateHmac(key, counterBuffer);
20
+ return dynamicTruncation(hmacArray, digits);
21
+ };
22
+ // source/otpAuthUri.ts
23
+ import { BaseError } from "@nowarajs/error";
24
+ var buildOtpAuthUri = ({
25
+ secretBase32,
26
+ label,
27
+ issuer,
28
+ algorithm = "SHA-1",
29
+ digits = 6,
30
+ period = 30
31
+ }) => {
32
+ const encodedLabel = encodeURIComponent(label);
33
+ const encodedIssuer = issuer ? encodeURIComponent(issuer) : undefined;
34
+ let uri = `otpauth://totp/${encodedLabel}?secret=${secretBase32}`;
35
+ if (encodedIssuer)
36
+ uri += `&issuer=${encodedIssuer}`;
37
+ if (algorithm !== "SHA-1")
38
+ uri += `&algorithm=${algorithm}`;
39
+ if (digits !== 6)
40
+ uri += `&digits=${digits}`;
41
+ if (period !== 30)
42
+ uri += `&period=${period}`;
43
+ return uri;
44
+ };
45
+ var parseOtpAuthUri = (uri) => {
46
+ const url = new URL(uri);
47
+ if (url.protocol !== "otpauth:")
48
+ throw new BaseError({
49
+ message: TOTP_ERROR_KEYS.INVALID_OTP_AUTH_URI
50
+ });
51
+ if (url.hostname !== "totp")
52
+ throw new BaseError({
53
+ message: TOTP_ERROR_KEYS.INVALID_OTP_AUTH_URI
54
+ });
55
+ const label = decodeURIComponent(url.pathname.slice(1));
56
+ const secretBase32 = url.searchParams.get("secret");
57
+ if (!secretBase32)
58
+ throw new BaseError({
59
+ message: TOTP_ERROR_KEYS.MISSING_SECRET
60
+ });
61
+ const issuerParam = url.searchParams.get("issuer");
62
+ const issuer = issuerParam ? decodeURIComponent(issuerParam) : undefined;
63
+ const algorithm = url.searchParams.get("algorithm") || "SHA-1";
64
+ const digits = parseInt(url.searchParams.get("digits") || "6", 10);
65
+ const period = parseInt(url.searchParams.get("period") || "30", 10);
66
+ const result = {
67
+ secretBase32,
68
+ label,
69
+ algorithm,
70
+ digits,
71
+ period,
72
+ ...issuer && { issuer }
73
+ };
74
+ return result;
75
+ };
76
+ // source/totp.ts
77
+ var totp = async (secret, {
78
+ algorithm = "SHA-1",
79
+ digits = 6,
80
+ period = 30,
81
+ now = Date.now()
82
+ } = {}) => {
83
+ const timeStep = Math.floor(now / 1000 / period);
84
+ return hotp(secret, timeStep, { algorithm, digits });
85
+ };
86
+ var verifyTotp = async (secret, code, {
87
+ algorithm = "SHA-1",
88
+ digits = 6,
89
+ period = 30,
90
+ window = 0,
91
+ now = Date.now()
92
+ } = {}) => {
93
+ const currentTimeStep = Math.floor(now / 1000 / period);
94
+ for (let i = -window;i <= window; ++i) {
95
+ const timeStep = currentTimeStep + i;
96
+ const expectedCode = await hotp(secret, timeStep, { algorithm, digits });
97
+ if (expectedCode === code)
98
+ return true;
99
+ }
100
+ return false;
101
+ };
102
+ export {
103
+ verifyTotp,
104
+ totp,
105
+ parseOtpAuthUri,
106
+ hotp,
107
+ buildOtpAuthUri
108
+ };
@@ -0,0 +1,21 @@
1
+ import type { OtpAuthUri } from './types/otpAuthUri';
2
+ /**
3
+ * Build an OTPAuth URI for QR code generation
4
+ *
5
+ * @param params - URI parameters
6
+ *
7
+ * @returns OTPAuth URI string
8
+ */
9
+ export declare const buildOtpAuthUri: ({ secretBase32, label, issuer, algorithm, digits, period }: OtpAuthUri) => string;
10
+ /**
11
+ * Parse an OTPAuth URI
12
+ *
13
+ * @param uri - OTPAuth URI to parse
14
+ *
15
+ * @throws ({@link BaseError}) - if the URI is invalid or missing required parameters
16
+ *
17
+ * @returns Parsed URI parameters
18
+ */
19
+ export declare const parseOtpAuthUri: (uri: string) => Required<Omit<OtpAuthUri, "issuer">> & {
20
+ issuer?: string;
21
+ };
package/dist/totp.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { TotpOptions } from './types/totpOptions';
2
+ import type { VerifyOptions } from './types/verifyOptions';
3
+ /**
4
+ * Time-based One-Time Password (TOTP) implementation
5
+ *
6
+ * @param secret - Secret key as bytes
7
+ * @param opts - TOTP options including current time
8
+ *
9
+ * @returns Promise resolving to the TOTP code
10
+ */
11
+ export declare const totp: (secret: Uint8Array, { algorithm, digits, period, now }?: TotpOptions & {
12
+ now?: number;
13
+ }) => Promise<string>;
14
+ /**
15
+ * Verify a TOTP code against a secret
16
+ *
17
+ * @param secret - Secret key as bytes
18
+ * @param code - Code to verify
19
+ * @param opts - Verification options
20
+ *
21
+ * @returns Promise resolving to true if code is valid
22
+ */
23
+ export declare const verifyTotp: (secret: Uint8Array, code: string, { algorithm, digits, period, window, now }?: VerifyOptions) => Promise<boolean>;
@@ -0,0 +1,3 @@
1
+ export type { OtpAuthUri } from './otpAuthUri';
2
+ export type { TotpOptions } from './totpOptions';
3
+ export type { VerifyOptions } from './verifyOptions';
@@ -0,0 +1 @@
1
+ // @bun
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Complete OTPAuth URI data structure
3
+ */
4
+ export interface OtpAuthUri {
5
+ /**
6
+ * Base32 encoded secret
7
+ */
8
+ secretBase32: string;
9
+ /**
10
+ * Label for the account (usually email or username)
11
+ */
12
+ label: string;
13
+ /**
14
+ * Issuer name (app/service name)
15
+ */
16
+ issuer: string;
17
+ /**
18
+ * Hash algorithm
19
+ *
20
+ * @defaultValue 'SHA-1'
21
+ */
22
+ algorithm?: 'SHA-1' | 'SHA-256' | 'SHA-512';
23
+ /**
24
+ * Number of digits
25
+ *
26
+ * @defaultValue 6
27
+ */
28
+ digits?: 6 | 8;
29
+ /**
30
+ * Time period in seconds
31
+ *
32
+ * @defaultValue 30
33
+ */
34
+ period?: number;
35
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Options for TOTP/HOTP generation
3
+ */
4
+ export interface TotpOptions {
5
+ /**
6
+ * Hash algorithm to use
7
+ *
8
+ * @defaultValue SHA-1
9
+ */
10
+ algorithm?: 'SHA-1' | 'SHA-256' | 'SHA-512';
11
+ /**
12
+ * Number of digits in the code
13
+ *
14
+ * @defaultValue 6
15
+ */
16
+ digits?: 6 | 8;
17
+ /**
18
+ * Time step in seconds for TOTP
19
+ *
20
+ * @defaultValue 30
21
+ */
22
+ period?: number;
23
+ }
@@ -0,0 +1,18 @@
1
+ import type { TotpOptions } from './totpOptions';
2
+ /**
3
+ * Options for TOTP verification
4
+ */
5
+ export interface VerifyOptions extends TotpOptions {
6
+ /**
7
+ * Time window for verification (ยฑwindow periods)
8
+ *
9
+ * @defaultValue 1
10
+ */
11
+ window?: number;
12
+ /**
13
+ * Current timestamp in milliseconds
14
+ *
15
+ * @defaultValue Date.now()
16
+ */
17
+ now?: number;
18
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Encode bytes to Base32 string
3
+ *
4
+ * @param input - Bytes or string to encode
5
+ * @param withPadding - Whether to include padding (default: true)
6
+ *
7
+ * @returns Base32 encoded string
8
+ */
9
+ export declare const base32Encode: (input: string | Uint8Array, withPadding?: boolean) => string;
10
+ /**
11
+ * Decode Base32 string to bytes
12
+ *
13
+ * @param base32 - Base32 string to decode
14
+ *
15
+ * @throws ({@link BaseError}) - if invalid Base32 character is found
16
+ *
17
+ * @returns Decoded bytes
18
+ */
19
+ export declare const base32Decode: (base32: string) => Uint8Array;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Convert a counter value to an 8-byte big-endian buffer
3
+ *
4
+ * @param counter - Counter value as number or bigint
5
+ *
6
+ * @returns ArrayBuffer containing the counter in big-endian format
7
+ */
8
+ export declare const createCounterBuffer: (counter: number | bigint) => ArrayBuffer;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Perform dynamic truncation on HMAC result according to RFC 4226
3
+ *
4
+ * @param hmacArray - HMAC result as byte array
5
+ * @param digits - Number of digits in the final code
6
+ *
7
+ * @returns Truncated code as string with leading zeros
8
+ */
9
+ export declare const dynamicTruncation: (hmacArray: Uint8Array, digits: number) => string;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Generate HMAC for given data using a crypto key
3
+ *
4
+ * @param key - Crypto key for HMAC
5
+ * @param data - Data to sign
6
+ *
7
+ * @returns Promise resolving to HMAC as Uint8Array
8
+ */
9
+ export declare const generateHmac: (key: CryptoKey, data: ArrayBuffer) => Promise<Uint8Array>;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Generate cryptographically secure random bytes for TOTP secret
3
+ *
4
+ * @param length - Number of bytes to generate (default: 20)
5
+ *
6
+ * @throws ({@link BaseError}) if length is not positive
7
+ *
8
+ * @returns Uint8Array containing the random bytes
9
+ */
10
+ export declare const generateSecretBytes: (length?: number) => Uint8Array;
@@ -0,0 +1,6 @@
1
+ export { base32Decode, base32Encode } from './base32';
2
+ export { createCounterBuffer } from './createCounterBuffer';
3
+ export { dynamicTruncation } from './dynamicTruncation';
4
+ export { generateHmac } from './generateHmac';
5
+ export { generateSecretBytes } from './generateSecretBytes';
6
+ export { timeRemaining } from './timeRemaining';
@@ -0,0 +1,80 @@
1
+ // @bun
2
+ import {
3
+ createCounterBuffer,
4
+ dynamicTruncation,
5
+ generateHmac
6
+ } from "../chunk-hhdy89hc.js";
7
+ import {
8
+ TOTP_ERROR_KEYS
9
+ } from "../chunk-4qsch0ea.js";
10
+
11
+ // source/utils/base32.ts
12
+ import { BaseError } from "@nowarajs/error";
13
+ var BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
14
+ var base32Encode = (input, withPadding = true) => {
15
+ let result = "";
16
+ let bits = 0;
17
+ let value = 0;
18
+ const bytes = input instanceof Uint8Array ? input : new TextEncoder().encode(input);
19
+ for (const byte of bytes) {
20
+ value = value << 8 | byte;
21
+ bits += 8;
22
+ while (bits >= 5) {
23
+ result += BASE32_ALPHABET[value >>> bits - 5 & 31];
24
+ bits -= 5;
25
+ }
26
+ }
27
+ if (bits > 0)
28
+ result += BASE32_ALPHABET[value << 5 - bits & 31];
29
+ if (withPadding)
30
+ while (result.length % 8 !== 0)
31
+ result += "=";
32
+ return result;
33
+ };
34
+ var base32Decode = (base32) => {
35
+ const cleanBase32 = base32.replace(/=+$/, "");
36
+ if (cleanBase32.length === 0)
37
+ return new Uint8Array(0);
38
+ const result = [];
39
+ let bits = 0;
40
+ let value = 0;
41
+ for (const char of cleanBase32) {
42
+ const charValue = BASE32_ALPHABET.indexOf(char);
43
+ if (charValue === -1)
44
+ throw new BaseError({
45
+ message: TOTP_ERROR_KEYS.INVALID_BASE32_CHARACTER,
46
+ cause: `Invalid Base32 character: ${char}`
47
+ });
48
+ value = value << 5 | charValue;
49
+ bits += 5;
50
+ if (bits >= 8) {
51
+ result.push(value >>> bits - 8 & 255);
52
+ bits -= 8;
53
+ }
54
+ }
55
+ return new Uint8Array(result);
56
+ };
57
+ // source/utils/generateSecretBytes.ts
58
+ import { BaseError as BaseError2 } from "@nowarajs/error";
59
+ import { getRandomValues } from "crypto";
60
+ var generateSecretBytes = (length = 20) => {
61
+ if (length <= 0)
62
+ throw new BaseError2({
63
+ message: TOTP_ERROR_KEYS.INVALID_SECRET_LENGTH
64
+ });
65
+ return getRandomValues(new Uint8Array(length));
66
+ };
67
+ // source/utils/timeRemaining.ts
68
+ var timeRemaining = (period = 30, now = Date.now()) => {
69
+ const elapsed = Math.floor(now / 1000) % period;
70
+ return period - elapsed;
71
+ };
72
+ export {
73
+ timeRemaining,
74
+ generateSecretBytes,
75
+ generateHmac,
76
+ dynamicTruncation,
77
+ createCounterBuffer,
78
+ base32Encode,
79
+ base32Decode
80
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Calculate remaining time until next TOTP code
3
+ *
4
+ * @param period - Time period in seconds (default: 30)
5
+ * @param now - Current timestamp in milliseconds (default: Date.now())
6
+ *
7
+ * @returns Seconds remaining until next code
8
+ */
9
+ export declare const timeRemaining: (period?: number, now?: number) => number;
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@nowarajs/totp",
3
+ "version": "1.1.2",
4
+ "author": "Komiriko",
5
+ "description": "A comprehensive Time-based One-Time Password (TOTP) and HMAC-based One-Time Password (HOTP)",
6
+ "type": "module",
7
+ "license": "MIT",
8
+ "keywords": [
9
+ "bun",
10
+ "nowarajs",
11
+ "totp",
12
+ "hotp"
13
+ ],
14
+ "scripts": {
15
+ "build": "bun builder.ts",
16
+ "dev": "bun --watch sandbox/index.ts",
17
+ "docs": "bunx typedoc --tsconfig tsconfig.build.json",
18
+ "fix-lint": "eslint --fix ./source",
19
+ "lint": "eslint ./source",
20
+ "test:integration": "bun test $(find test/integration -name '*.spec.ts')",
21
+ "test:unit": "bun test --coverage $(find test/unit -name '*.spec.ts')",
22
+ "test": "bun test --coverage"
23
+ },
24
+ "devDependencies": {
25
+ "@eslint/js": "^9.33.0",
26
+ "@stylistic/eslint-plugin": "^5.2.3",
27
+ "@types/bun": "^1.2.20",
28
+ "eslint": "^9.33.0",
29
+ "globals": "^16.3.0",
30
+ "typescript-eslint": "^8.39.1",
31
+ "typescript": "^5.9.2"
32
+ },
33
+ "exports": {
34
+ "./enums": "./dist/enums/index.js",
35
+ "./types": "./dist/types/index.js",
36
+ "./utils": "./dist/utils/index.js",
37
+ ".": "./dist/index.js"
38
+ },
39
+ "changelog": {
40
+ "types": {
41
+ "feat": {
42
+ "title": "๐Ÿš€ Enhancements",
43
+ "semver": "minor"
44
+ },
45
+ "perf": {
46
+ "title": "โšก Performance",
47
+ "semver": "patch"
48
+ },
49
+ "fix": {
50
+ "title": "๐Ÿ”ง Fixes",
51
+ "semver": "patch"
52
+ },
53
+ "refactor": {
54
+ "title": "๐Ÿงน Refactors",
55
+ "semver": "patch"
56
+ },
57
+ "docs": {
58
+ "title": "๐Ÿ“– Documentation",
59
+ "semver": "patch"
60
+ },
61
+ "build": {
62
+ "title": "๐Ÿ“ฆ Build",
63
+ "semver": "patch"
64
+ },
65
+ "types": {
66
+ "title": "๐ŸŒŠ Types",
67
+ "semver": "patch"
68
+ },
69
+ "chore": {
70
+ "title": "๐Ÿฆ‰ Chore"
71
+ },
72
+ "examples": {
73
+ "title": "๐Ÿ€ Examples"
74
+ },
75
+ "test": {
76
+ "title": "๐Ÿงช Tests"
77
+ },
78
+ "style": {
79
+ "title": "๐ŸŽจ Styles"
80
+ },
81
+ "ci": {
82
+ "title": "๐Ÿค– CI"
83
+ }
84
+ },
85
+ "templates": {
86
+ "commitMessage": "chore(๐Ÿฆ‰): v{{newVersion}}",
87
+ "tagMessage": "v{{newVersion}}",
88
+ "tagBody": "v{{newVersion}}"
89
+ }
90
+ },
91
+ "peerDependencies": {
92
+ "@nowarajs/error": "^1.1.6"
93
+ }
94
+ }