@otplib/uri 13.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/LICENSE +21 -0
- package/README.md +270 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +201 -0
- package/dist/index.d.ts +201 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gerald Yeo
|
|
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,270 @@
|
|
|
1
|
+
# @otplib/uri
|
|
2
|
+
|
|
3
|
+
Parse and generate `otpauth://` URIs for OTP account provisioning.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @otplib/uri
|
|
9
|
+
pnpm add @otplib/uri
|
|
10
|
+
yarn add @otplib/uri
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Overview
|
|
14
|
+
|
|
15
|
+
The `@otplib/uri` package provides utilities for working with `otpauth://` URIs - the standard format for sharing OTP account information. These URIs are commonly used in QR codes for authenticator app setup.
|
|
16
|
+
|
|
17
|
+
### URI Format
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
otpauth://TYPE/LABEL?PARAMETERS
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- **TYPE**: `totp` or `hotp`
|
|
24
|
+
- **LABEL**: `issuer:account` or just `account`
|
|
25
|
+
- **PARAMETERS**: `secret`, `issuer`, `algorithm`, `digits`, `period`/`counter`
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
otpauth://totp/GitHub:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Parsing URIs
|
|
34
|
+
|
|
35
|
+
### Basic Parsing
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
import { parse } from "@otplib/uri";
|
|
39
|
+
|
|
40
|
+
const uri = "otpauth://totp/GitHub:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=GitHub";
|
|
41
|
+
const result = parse(uri);
|
|
42
|
+
|
|
43
|
+
console.log(result);
|
|
44
|
+
// {
|
|
45
|
+
// type: 'totp',
|
|
46
|
+
// label: 'GitHub:user@example.com',
|
|
47
|
+
// params: {
|
|
48
|
+
// secret: 'JBSWY3DPEHPK3PXP',
|
|
49
|
+
// issuer: 'GitHub',
|
|
50
|
+
// algorithm: 'sha1',
|
|
51
|
+
// digits: 6,
|
|
52
|
+
// period: 30
|
|
53
|
+
// }
|
|
54
|
+
// }
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Extracting Account Details
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { parse } from "@otplib/uri";
|
|
61
|
+
|
|
62
|
+
const uri = "otpauth://totp/ACME%20Corp:john@example.com?secret=JBSWY3DPEHPK3PXP";
|
|
63
|
+
const { label, params } = parse(uri);
|
|
64
|
+
|
|
65
|
+
// Split label to get issuer and account
|
|
66
|
+
const [issuer, account] = label.includes(":") ? label.split(":") : [params.issuer, label];
|
|
67
|
+
|
|
68
|
+
console.log("Issuer:", issuer); // 'ACME Corp'
|
|
69
|
+
console.log("Account:", account); // 'john@example.com'
|
|
70
|
+
console.log("Secret:", params.secret);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Error Handling
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import {
|
|
77
|
+
parse,
|
|
78
|
+
URIParseError,
|
|
79
|
+
InvalidURIError,
|
|
80
|
+
MissingParameterError,
|
|
81
|
+
InvalidParameterError,
|
|
82
|
+
} from "@otplib/uri";
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const result = parse("invalid-uri");
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof InvalidURIError) {
|
|
88
|
+
console.error("Not a valid otpauth:// URI");
|
|
89
|
+
} else if (error instanceof MissingParameterError) {
|
|
90
|
+
console.error("Missing required parameter (e.g., secret)");
|
|
91
|
+
} else if (error instanceof InvalidParameterError) {
|
|
92
|
+
console.error("Invalid parameter value");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Generating URIs
|
|
98
|
+
|
|
99
|
+
### TOTP URI
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { generateTOTP } from "@otplib/uri";
|
|
103
|
+
|
|
104
|
+
const uri = generateTOTP({
|
|
105
|
+
issuer: "ACME Corp",
|
|
106
|
+
label: "john@example.com",
|
|
107
|
+
secret: "JBSWY3DPEHPK3PXP",
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
console.log(uri);
|
|
111
|
+
// 'otpauth://totp/ACME%20Corp:john@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME%20Corp'
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### TOTP with Custom Options
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { generateTOTP } from "@otplib/uri";
|
|
118
|
+
|
|
119
|
+
const uri = generateTOTP({
|
|
120
|
+
issuer: "GitHub",
|
|
121
|
+
label: "user@github.com",
|
|
122
|
+
secret: "JBSWY3DPEHPK3PXP",
|
|
123
|
+
algorithm: "sha256", // Non-default algorithm
|
|
124
|
+
digits: 8, // 8-digit tokens
|
|
125
|
+
period: 60, // 60-second period
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### HOTP URI
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { generateHOTP } from "@otplib/uri";
|
|
133
|
+
|
|
134
|
+
const uri = generateHOTP({
|
|
135
|
+
issuer: "MyApp",
|
|
136
|
+
label: "user123",
|
|
137
|
+
secret: "JBSWY3DPEHPK3PXP",
|
|
138
|
+
counter: 0, // Starting counter
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
console.log(uri);
|
|
142
|
+
// 'otpauth://hotp/MyApp:user123?secret=JBSWY3DPEHPK3PXP&issuer=MyApp&counter=0'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Low-Level Generation
|
|
146
|
+
|
|
147
|
+
For more control, use the `generate` function directly:
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { generate } from "@otplib/uri";
|
|
151
|
+
|
|
152
|
+
const uri = generate({
|
|
153
|
+
type: "totp",
|
|
154
|
+
label: "CustomApp:user@example.com",
|
|
155
|
+
params: {
|
|
156
|
+
secret: "JBSWY3DPEHPK3PXP",
|
|
157
|
+
issuer: "CustomApp",
|
|
158
|
+
algorithm: "sha1",
|
|
159
|
+
digits: 6,
|
|
160
|
+
period: 30,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Google Authenticator Compatibility
|
|
166
|
+
|
|
167
|
+
::: warning Google Authenticator Limitations
|
|
168
|
+
Google Authenticator has specific requirements:
|
|
169
|
+
|
|
170
|
+
- Only supports `sha1` algorithm
|
|
171
|
+
- Only supports `6` or `8` digits
|
|
172
|
+
- Only supports `30` second period for TOTP
|
|
173
|
+
- Issuer should be included in both label and parameter
|
|
174
|
+
:::
|
|
175
|
+
|
|
176
|
+
### Compatible URI
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { generateTOTP } from "@otplib/uri";
|
|
180
|
+
|
|
181
|
+
// This URI is fully compatible with Google Authenticator
|
|
182
|
+
const uri = generateTOTP({
|
|
183
|
+
issuer: "MyService",
|
|
184
|
+
label: "user@example.com",
|
|
185
|
+
secret: "JBSWY3DPEHPK3PXP",
|
|
186
|
+
// algorithm: 'sha1', // Default, compatible
|
|
187
|
+
// digits: 6, // Default, compatible
|
|
188
|
+
// period: 30, // Default, compatible
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
## QR Code Integration
|
|
193
|
+
|
|
194
|
+
Generate a QR code for the URI using any QR library:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import { generateTOTP } from "@otplib/uri";
|
|
198
|
+
import QRCode from "qrcode"; // Example library
|
|
199
|
+
|
|
200
|
+
const uri = generateTOTP({
|
|
201
|
+
issuer: "MyApp",
|
|
202
|
+
label: "user@example.com",
|
|
203
|
+
secret: "JBSWY3DPEHPK3PXP",
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Generate QR code as data URL
|
|
207
|
+
const qrDataUrl = await QRCode.toDataURL(uri);
|
|
208
|
+
|
|
209
|
+
// Or generate as SVG
|
|
210
|
+
const qrSvg = await QRCode.toString(uri, { type: "svg" });
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Type Definitions
|
|
214
|
+
|
|
215
|
+
### OTPAuthURI
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
interface OTPAuthURI {
|
|
219
|
+
type: "hotp" | "totp";
|
|
220
|
+
label: string;
|
|
221
|
+
params: OTPAuthParams;
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### OTPAuthParams
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
interface OTPAuthParams {
|
|
229
|
+
secret: string; // Required
|
|
230
|
+
issuer?: string; // Recommended
|
|
231
|
+
algorithm?: "sha1" | "sha256" | "sha512"; // Default: 'sha1'
|
|
232
|
+
digits?: 6 | 7 | 8; // Default: 6
|
|
233
|
+
counter?: number; // HOTP only
|
|
234
|
+
period?: number; // TOTP only, default: 30
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### URIOptions
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
interface URIOptions {
|
|
242
|
+
issuer?: string;
|
|
243
|
+
label?: string;
|
|
244
|
+
secret: string;
|
|
245
|
+
algorithm?: "sha1" | "sha256" | "sha512";
|
|
246
|
+
digits?: 6 | 7 | 8;
|
|
247
|
+
period?: number; // TOTP only
|
|
248
|
+
counter?: number; // HOTP only
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Classes
|
|
253
|
+
|
|
254
|
+
| Error | Description |
|
|
255
|
+
| ----------------------- | -------------------------------------- |
|
|
256
|
+
| `URIParseError` | Base error for all URI parsing errors |
|
|
257
|
+
| `InvalidURIError` | URI format is invalid |
|
|
258
|
+
| `MissingParameterError` | Required parameter (secret) is missing |
|
|
259
|
+
| `InvalidParameterError` | Parameter value is invalid |
|
|
260
|
+
|
|
261
|
+
## Documentation
|
|
262
|
+
|
|
263
|
+
Full documentation available at [otplib.yeojz.dev](https://otplib.yeojz.dev):
|
|
264
|
+
|
|
265
|
+
- [Getting Started Guide](https://otplib.yeojz.dev/guide/getting-started)
|
|
266
|
+
- [API Reference](https://otplib.yeojz.dev/api/)
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
[MIT](./LICENSE) © 2026 Gerald Yeo
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var m=Object.defineProperty;var T=Object.getOwnPropertyDescriptor;var b=Object.getOwnPropertyNames;var x=Object.prototype.hasOwnProperty;var U=(t,e)=>{for(var s in e)m(t,s,{get:e[s],enumerable:!0})},R=(t,e,s,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of b(e))!x.call(t,i)&&i!==s&&m(t,i,{get:()=>e[i],enumerable:!(r=T(e,i))||r.enumerable});return t};var A=t=>R(m({},"__esModule",{value:!0}),t);var v={};U(v,{InvalidParameterError:()=>c,InvalidURIError:()=>a,MissingParameterError:()=>u,URIParseError:()=>l,generate:()=>h,generateHOTP:()=>O,generateTOTP:()=>y,parse:()=>d,parseURI:()=>d});module.exports=A(v);var l=class extends Error{constructor(e){super(e),this.name="URIParseError"}},a=class extends l{constructor(e){super(`Invalid otpauth URI: ${e}`),this.name="InvalidURIError"}},u=class extends l{constructor(e){super(`Missing required parameter: ${e}`),this.name="MissingParameterError"}},c=class extends l{constructor(e,s){super(`Invalid value for parameter '${e}': ${s}`),this.name="InvalidParameterError"}};var f=2048,I=512,w=1024;function $(t,e){let s=t instanceof Error?t.message:String(t);return`Invalid URI encoding in ${e}: ${s}`}function g(t,e,s){if(t.length>e*3)throw new a(`${s} exceeds maximum length`);try{let r=decodeURIComponent(t);if(r.length>e)throw new a(`${s} exceeds maximum length of ${e} characters`);return r}catch(r){throw r instanceof a?r:new a($(r,s))}}function d(t){if(t.length>f)throw new a(`URI exceeds maximum length of ${f} characters`);if(!t.startsWith("otpauth://"))throw new a(t);let e=t.slice(10),s=e.indexOf("/");if(s===-1)throw new a(t);let r=e.slice(0,s);if(r!=="hotp"&&r!=="totp")throw new c("type",r);let i=e.slice(s+1),o=i.indexOf("?"),n,p;o===-1?(n=g(i,I,"label"),p=""):(n=g(i.slice(0,o),I,"label"),p=i.slice(o+1));let P=H(p);return{type:r,label:n,params:P}}function H(t){let e={};if(!t)return e;let s=t.split("&");for(let r of s){let i=r.indexOf("=");if(i===-1)continue;let o=g(r.slice(0,i),64,"parameter key"),n=g(r.slice(i+1),w,`parameter '${o}'`);switch(o){case"secret":e.secret=n;break;case"issuer":e.issuer=n;break;case"algorithm":e.algorithm=E(n);break;case"digits":e.digits=k(n);break;case"counter":e.counter=parseInt(n,10);break;case"period":e.period=parseInt(n,10);break}}return e}function E(t){let e=t.toLowerCase();if(e==="sha1"||e==="sha-1")return"sha1";if(e==="sha256"||e==="sha-256")return"sha256";if(e==="sha512"||e==="sha-512")return"sha512";throw new c("algorithm",t)}function k(t){let e=parseInt(t,10);if(e===6||e===7||e===8)return e;throw new c("digits",t)}function h(t){let{type:e,label:s,params:r}=t,i=s.split(":").map(p=>encodeURIComponent(p)).join(":"),o=`otpauth://${e}/${i}?`,n=[];return r.secret&&n.push(`secret=${r.secret}`),r.issuer&&n.push(`issuer=${encodeURIComponent(r.issuer)}`),r.algorithm&&r.algorithm!=="sha1"&&n.push(`algorithm=${r.algorithm.toUpperCase()}`),r.digits&&r.digits!==6&&n.push(`digits=${r.digits}`),e==="hotp"&&r.counter!==void 0&&n.push(`counter=${r.counter}`),e==="totp"&&r.period!==void 0&&r.period!==30&&n.push(`period=${r.period}`),o+=n.join("&"),o}function y(t){let{issuer:e,label:s,secret:r,algorithm:i="sha1",digits:o=6,period:n=30}=t,p=e?`${e}:${s}`:s;return h({type:"totp",label:p,params:{secret:r,issuer:e,algorithm:i,digits:o,period:n}})}function O(t){let{issuer:e,label:s,secret:r,counter:i=0,algorithm:o="sha1",digits:n=6}=t,p=e?`${e}:${s}`:s;return h({type:"hotp",label:p,params:{secret:r,issuer:e,algorithm:o,digits:n,counter:i}})}0&&(module.exports={InvalidParameterError,InvalidURIError,MissingParameterError,URIParseError,generate,generateHOTP,generateTOTP,parse,parseURI});
|
|
2
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/parse.ts","../src/generate.ts"],"sourcesContent":["export { parse, parse as parseURI } from \"./parse.js\";\n\nexport { generate, generateTOTP, generateHOTP } from \"./generate.js\";\nexport type { URIOptions, TOTPURIOptions, HOTPURIOptions } from \"./generate.js\";\n\nexport {\n URIParseError,\n InvalidURIError,\n MissingParameterError,\n InvalidParameterError,\n} from \"./types.js\";\n\nexport type { OTPAuthURI, OTPAuthParams, OTPType } from \"./types.js\";\n","import type { HashAlgorithm, Digits } from \"@otplib/core\";\n\n/**\n * OTP type (HOTP or TOTP)\n */\nexport type OTPType = \"hotp\" | \"totp\";\n\n/**\n * otpauth:// URI parameters\n */\nexport type OTPAuthParams = {\n /**\n * Base32-encoded shared secret (required)\n */\n readonly secret: string;\n\n /**\n * Service/provider name (recommended)\n */\n readonly issuer?: string;\n\n /**\n * Hash algorithm (default: sha1)\n * Note: Google Authenticator only supports sha1\n */\n readonly algorithm?: HashAlgorithm;\n\n /**\n * Number of digits (default: 6)\n * Google Authenticator supports 6 or 8\n */\n readonly digits?: Digits;\n\n /**\n * Initial counter value for HOTP (default: 0)\n */\n readonly counter?: number;\n\n /**\n * Time step in seconds for TOTP (default: 30)\n */\n readonly period?: number;\n};\n\n/**\n * otpauth:// URI structure\n */\nexport type OTPAuthURI = {\n /**\n * Type of OTP (hotp or totp)\n */\n readonly type: OTPType;\n\n /**\n * The label (typically: issuer:account or account)\n */\n readonly label: string;\n\n /**\n * Parameters from the URI\n */\n readonly params: OTPAuthParams;\n};\n\n/**\n * Error thrown when URI parsing fails\n */\nexport class URIParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"URIParseError\";\n }\n}\n\n/**\n * Error thrown when URI is invalid\n */\nexport class InvalidURIError extends URIParseError {\n constructor(uri: string) {\n super(`Invalid otpauth URI: ${uri}`);\n this.name = \"InvalidURIError\";\n }\n}\n\n/**\n * Error thrown when URI has missing required parameters\n */\nexport class MissingParameterError extends URIParseError {\n constructor(param: string) {\n super(`Missing required parameter: ${param}`);\n this.name = \"MissingParameterError\";\n }\n}\n\n/**\n * Error thrown when URI has invalid parameter value\n */\nexport class InvalidParameterError extends URIParseError {\n constructor(param: string, value: string) {\n super(`Invalid value for parameter '${param}': ${value}`);\n this.name = \"InvalidParameterError\";\n }\n}\n","import { InvalidURIError, InvalidParameterError } from \"./types.js\";\n\nimport type { OTPAuthURI, OTPAuthParams } from \"./types.js\";\nimport type { HashAlgorithm, Digits } from \"@otplib/core\";\n\n// Security limits to prevent DoS attacks\nconst MAX_URI_LENGTH = 2048; // Standard URL length limit\nconst MAX_LABEL_LENGTH = 512;\nconst MAX_PARAM_VALUE_LENGTH = 1024;\n\n/**\n * Format error message for caught errors\n * @internal\n */\nexport function formatErrorMessage(error: unknown, context: string): string {\n const errorStr = error instanceof Error ? error.message : String(error);\n return `Invalid URI encoding in ${context}: ${errorStr}`;\n}\n\n/**\n * Safely decode a URI component with length validation and error handling\n * @throws {InvalidURIError} If decoding fails or length exceeds limit\n */\nfunction safeDecodeURIComponent(str: string, maxLength: number, context: string): string {\n if (str.length > maxLength * 3) {\n throw new InvalidURIError(`${context} exceeds maximum length`);\n }\n\n try {\n const decoded = decodeURIComponent(str);\n if (decoded.length > maxLength) {\n throw new InvalidURIError(`${context} exceeds maximum length of ${maxLength} characters`);\n }\n return decoded;\n } catch (error) {\n if (error instanceof InvalidURIError) {\n throw error;\n }\n throw new InvalidURIError(formatErrorMessage(error, context));\n }\n}\n\n/**\n * Parse an otpauth:// URI into its components\n *\n * @param uri - The otpauth:// URI to parse\n * @returns Parsed URI components\n * @throws {InvalidURIError} If URI is invalid\n * @throws {MissingParameterError} If required parameters are missing\n * @throws {InvalidParameterError} If parameter values are invalid\n *\n * @example\n * ```ts\n * import { parse } from '@otplib/uri';\n *\n * const uri = 'otpauth://totp/ACME:john@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME';\n * const parsed = parse(uri);\n * // {\n * // type: 'totp',\n * // label: 'ACME:john@example.com',\n * // params: { secret: 'JBSWY3DPEHPK3PXP', issuer: 'ACME' }\n * // }\n * ```\n */\nexport function parse(uri: string): OTPAuthURI {\n if (uri.length > MAX_URI_LENGTH) {\n throw new InvalidURIError(`URI exceeds maximum length of ${MAX_URI_LENGTH} characters`);\n }\n\n if (!uri.startsWith(\"otpauth://\")) {\n throw new InvalidURIError(uri);\n }\n\n const withoutScheme = uri.slice(\"otpauth://\".length);\n const slashIndex = withoutScheme.indexOf(\"/\");\n\n if (slashIndex === -1) {\n throw new InvalidURIError(uri);\n }\n\n const type = withoutScheme.slice(0, slashIndex);\n if (type !== \"hotp\" && type !== \"totp\") {\n throw new InvalidParameterError(\"type\", type);\n }\n\n const remaining = withoutScheme.slice(slashIndex + 1);\n const queryIndex = remaining.indexOf(\"?\");\n\n let label: string;\n let queryString: string;\n\n if (queryIndex === -1) {\n label = safeDecodeURIComponent(remaining, MAX_LABEL_LENGTH, \"label\");\n queryString = \"\";\n } else {\n label = safeDecodeURIComponent(remaining.slice(0, queryIndex), MAX_LABEL_LENGTH, \"label\");\n queryString = remaining.slice(queryIndex + 1);\n }\n\n const params = parseQueryString(queryString);\n\n return {\n type,\n label,\n params,\n };\n}\n\n/**\n * Parse query string into parameters object\n */\nfunction parseQueryString(queryString: string): OTPAuthParams {\n // Use mutable type during construction\n const params: {\n secret?: string;\n issuer?: string;\n algorithm?: HashAlgorithm;\n digits?: Digits;\n counter?: number;\n period?: number;\n } = {};\n\n if (!queryString) {\n return params as OTPAuthParams;\n }\n\n const pairs = queryString.split(\"&\");\n for (const pair of pairs) {\n const equalIndex = pair.indexOf(\"=\");\n if (equalIndex === -1) {\n continue;\n }\n\n const key = safeDecodeURIComponent(pair.slice(0, equalIndex), 64, \"parameter key\");\n const value = safeDecodeURIComponent(\n pair.slice(equalIndex + 1),\n MAX_PARAM_VALUE_LENGTH,\n `parameter '${key}'`,\n );\n\n switch (key) {\n case \"secret\":\n params.secret = value;\n break;\n case \"issuer\":\n params.issuer = value;\n break;\n case \"algorithm\":\n params.algorithm = parseAlgorithm(value);\n break;\n case \"digits\":\n params.digits = parseDigits(value);\n break;\n case \"counter\":\n params.counter = parseInt(value, 10);\n break;\n case \"period\":\n params.period = parseInt(value, 10);\n break;\n }\n }\n\n return params as OTPAuthParams;\n}\n\n/**\n * Parse algorithm string\n */\nfunction parseAlgorithm(value: string): HashAlgorithm {\n const normalized = value.toLowerCase();\n if (normalized === \"sha1\" || normalized === \"sha-1\") {\n return \"sha1\";\n }\n if (normalized === \"sha256\" || normalized === \"sha-256\") {\n return \"sha256\";\n }\n if (normalized === \"sha512\" || normalized === \"sha-512\") {\n return \"sha512\";\n }\n throw new InvalidParameterError(\"algorithm\", value);\n}\n\n/**\n * Parse digits string\n */\nfunction parseDigits(value: string): Digits {\n const digits = parseInt(value, 10);\n if (digits === 6 || digits === 7 || digits === 8) {\n return digits;\n }\n throw new InvalidParameterError(\"digits\", value);\n}\n","import type { OTPAuthURI } from \"./types.js\";\nimport type { HashAlgorithm, Digits } from \"@otplib/core\";\n\n/**\n * Base options for URI generation\n */\nexport type URIOptions = {\n /**\n * Service/provider name (e.g., 'ACME Co', 'GitHub', 'AWS')\n */\n issuer?: string;\n\n /**\n * Account identifier (e.g., email, username)\n */\n label?: string;\n\n /**\n * Base32-encoded secret key\n */\n secret: string;\n\n /**\n * Hash algorithm (default: 'sha1')\n * Note: Google Authenticator only supports sha1\n */\n algorithm?: HashAlgorithm;\n\n /**\n * Number of digits (default: 6)\n * Google Authenticator supports 6 or 8, RFC also allows 7\n */\n digits?: Digits;\n\n /**\n * Time step in seconds for TOTP (default: 30)\n */\n period?: number;\n\n /**\n * Counter value for HOTP\n */\n counter?: number;\n};\n\n/**\n * TOTP-specific URI options\n */\nexport type TOTPURIOptions = URIOptions & {\n period?: number;\n counter?: never;\n};\n\n/**\n * HOTP-specific URI options\n */\nexport type HOTPURIOptions = URIOptions & {\n period?: never;\n counter?: number;\n};\n\n/**\n * Generate an otpauth:// URI\n *\n * @param uri - The URI components\n * @returns The otpauth:// URI string\n *\n * @example\n * ```ts\n * import { generate } from '@otplib/uri';\n * import { encode } from '@otplib/base32';\n *\n * const secret = encode(new Uint8Array([1, 2, 3, 4, 5]));\n *\n * const uri = generate({\n * type: 'totp',\n * label: 'ACME:john@example.com',\n * params: {\n * secret,\n * issuer: 'ACME',\n * algorithm: 'sha1',\n * digits: 6,\n * },\n * });\n * // Returns: 'otpauth://totp/ACME:john%40example.com?secret=...'\n * ```\n */\nexport function generate(uri: OTPAuthURI): string {\n const { type, label, params } = uri;\n\n // Encode label parts while preserving ':' as the issuer/account separator\n const encodedLabel = label\n .split(\":\")\n .map((part) => encodeURIComponent(part))\n .join(\":\");\n\n let result = `otpauth://${type}/${encodedLabel}?`;\n\n const queryParams: string[] = [];\n\n if (params.secret) {\n queryParams.push(`secret=${params.secret}`);\n }\n\n if (params.issuer) {\n queryParams.push(`issuer=${encodeURIComponent(params.issuer)}`);\n }\n\n if (params.algorithm && params.algorithm !== \"sha1\") {\n queryParams.push(`algorithm=${params.algorithm.toUpperCase()}`);\n }\n\n if (params.digits && params.digits !== 6) {\n queryParams.push(`digits=${params.digits}`);\n }\n\n if (type === \"hotp\" && params.counter !== undefined) {\n queryParams.push(`counter=${params.counter}`);\n }\n\n if (type === \"totp\" && params.period !== undefined && params.period !== 30) {\n queryParams.push(`period=${params.period}`);\n }\n\n result += queryParams.join(\"&\");\n\n return result;\n}\n\n/**\n * Generate a TOTP otpauth:// URI with simplified parameters\n *\n * @param options - TOTP URI generation options\n * @returns The otpauth:// URI string\n */\nexport function generateTOTP(options: TOTPURIOptions & { issuer: string; label: string }): string {\n const { issuer, label: account, secret, algorithm = \"sha1\", digits = 6, period = 30 } = options;\n\n const fullLabel = issuer ? `${issuer}:${account}` : account;\n\n return generate({\n type: \"totp\",\n label: fullLabel,\n params: {\n secret,\n issuer,\n algorithm,\n digits,\n period,\n },\n });\n}\n\n/**\n * Generate a HOTP otpauth:// URI with simplified parameters\n *\n * @param options - HOTP URI generation options\n * @returns The otpauth:// URI string\n */\nexport function generateHOTP(options: HOTPURIOptions & { issuer: string; label: string }): string {\n const { issuer, label: account, secret, counter = 0, algorithm = \"sha1\", digits = 6 } = options;\n\n const fullLabel = issuer ? `${issuer}:${account}` : account;\n\n return generate({\n type: \"hotp\",\n label: fullLabel,\n params: {\n secret,\n issuer,\n algorithm,\n digits,\n counter,\n },\n });\n}\n"],"mappings":"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","names":["index_exports","__export","InvalidParameterError","InvalidURIError","MissingParameterError","URIParseError","generate","generateHOTP","generateTOTP","parse","__toCommonJS","URIParseError","message","InvalidURIError","uri","MissingParameterError","param","InvalidParameterError","value","MAX_URI_LENGTH","MAX_LABEL_LENGTH","MAX_PARAM_VALUE_LENGTH","formatErrorMessage","error","context","errorStr","safeDecodeURIComponent","str","maxLength","InvalidURIError","decoded","parse","uri","withoutScheme","slashIndex","type","InvalidParameterError","remaining","queryIndex","label","queryString","params","parseQueryString","pairs","pair","equalIndex","key","value","parseAlgorithm","parseDigits","normalized","digits","generate","uri","type","label","params","encodedLabel","part","result","queryParams","generateTOTP","options","issuer","account","secret","algorithm","digits","period","fullLabel","generateHOTP","counter"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { HashAlgorithm, Digits } from '@otplib/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OTP type (HOTP or TOTP)
|
|
5
|
+
*/
|
|
6
|
+
type OTPType = "hotp" | "totp";
|
|
7
|
+
/**
|
|
8
|
+
* otpauth:// URI parameters
|
|
9
|
+
*/
|
|
10
|
+
type OTPAuthParams = {
|
|
11
|
+
/**
|
|
12
|
+
* Base32-encoded shared secret (required)
|
|
13
|
+
*/
|
|
14
|
+
readonly secret: string;
|
|
15
|
+
/**
|
|
16
|
+
* Service/provider name (recommended)
|
|
17
|
+
*/
|
|
18
|
+
readonly issuer?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Hash algorithm (default: sha1)
|
|
21
|
+
* Note: Google Authenticator only supports sha1
|
|
22
|
+
*/
|
|
23
|
+
readonly algorithm?: HashAlgorithm;
|
|
24
|
+
/**
|
|
25
|
+
* Number of digits (default: 6)
|
|
26
|
+
* Google Authenticator supports 6 or 8
|
|
27
|
+
*/
|
|
28
|
+
readonly digits?: Digits;
|
|
29
|
+
/**
|
|
30
|
+
* Initial counter value for HOTP (default: 0)
|
|
31
|
+
*/
|
|
32
|
+
readonly counter?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Time step in seconds for TOTP (default: 30)
|
|
35
|
+
*/
|
|
36
|
+
readonly period?: number;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* otpauth:// URI structure
|
|
40
|
+
*/
|
|
41
|
+
type OTPAuthURI = {
|
|
42
|
+
/**
|
|
43
|
+
* Type of OTP (hotp or totp)
|
|
44
|
+
*/
|
|
45
|
+
readonly type: OTPType;
|
|
46
|
+
/**
|
|
47
|
+
* The label (typically: issuer:account or account)
|
|
48
|
+
*/
|
|
49
|
+
readonly label: string;
|
|
50
|
+
/**
|
|
51
|
+
* Parameters from the URI
|
|
52
|
+
*/
|
|
53
|
+
readonly params: OTPAuthParams;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Error thrown when URI parsing fails
|
|
57
|
+
*/
|
|
58
|
+
declare class URIParseError extends Error {
|
|
59
|
+
constructor(message: string);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Error thrown when URI is invalid
|
|
63
|
+
*/
|
|
64
|
+
declare class InvalidURIError extends URIParseError {
|
|
65
|
+
constructor(uri: string);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Error thrown when URI has missing required parameters
|
|
69
|
+
*/
|
|
70
|
+
declare class MissingParameterError extends URIParseError {
|
|
71
|
+
constructor(param: string);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Error thrown when URI has invalid parameter value
|
|
75
|
+
*/
|
|
76
|
+
declare class InvalidParameterError extends URIParseError {
|
|
77
|
+
constructor(param: string, value: string);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse an otpauth:// URI into its components
|
|
82
|
+
*
|
|
83
|
+
* @param uri - The otpauth:// URI to parse
|
|
84
|
+
* @returns Parsed URI components
|
|
85
|
+
* @throws {InvalidURIError} If URI is invalid
|
|
86
|
+
* @throws {MissingParameterError} If required parameters are missing
|
|
87
|
+
* @throws {InvalidParameterError} If parameter values are invalid
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { parse } from '@otplib/uri';
|
|
92
|
+
*
|
|
93
|
+
* const uri = 'otpauth://totp/ACME:john@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME';
|
|
94
|
+
* const parsed = parse(uri);
|
|
95
|
+
* // {
|
|
96
|
+
* // type: 'totp',
|
|
97
|
+
* // label: 'ACME:john@example.com',
|
|
98
|
+
* // params: { secret: 'JBSWY3DPEHPK3PXP', issuer: 'ACME' }
|
|
99
|
+
* // }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function parse(uri: string): OTPAuthURI;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Base options for URI generation
|
|
106
|
+
*/
|
|
107
|
+
type URIOptions = {
|
|
108
|
+
/**
|
|
109
|
+
* Service/provider name (e.g., 'ACME Co', 'GitHub', 'AWS')
|
|
110
|
+
*/
|
|
111
|
+
issuer?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Account identifier (e.g., email, username)
|
|
114
|
+
*/
|
|
115
|
+
label?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Base32-encoded secret key
|
|
118
|
+
*/
|
|
119
|
+
secret: string;
|
|
120
|
+
/**
|
|
121
|
+
* Hash algorithm (default: 'sha1')
|
|
122
|
+
* Note: Google Authenticator only supports sha1
|
|
123
|
+
*/
|
|
124
|
+
algorithm?: HashAlgorithm;
|
|
125
|
+
/**
|
|
126
|
+
* Number of digits (default: 6)
|
|
127
|
+
* Google Authenticator supports 6 or 8, RFC also allows 7
|
|
128
|
+
*/
|
|
129
|
+
digits?: Digits;
|
|
130
|
+
/**
|
|
131
|
+
* Time step in seconds for TOTP (default: 30)
|
|
132
|
+
*/
|
|
133
|
+
period?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Counter value for HOTP
|
|
136
|
+
*/
|
|
137
|
+
counter?: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* TOTP-specific URI options
|
|
141
|
+
*/
|
|
142
|
+
type TOTPURIOptions = URIOptions & {
|
|
143
|
+
period?: number;
|
|
144
|
+
counter?: never;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* HOTP-specific URI options
|
|
148
|
+
*/
|
|
149
|
+
type HOTPURIOptions = URIOptions & {
|
|
150
|
+
period?: never;
|
|
151
|
+
counter?: number;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Generate an otpauth:// URI
|
|
155
|
+
*
|
|
156
|
+
* @param uri - The URI components
|
|
157
|
+
* @returns The otpauth:// URI string
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* import { generate } from '@otplib/uri';
|
|
162
|
+
* import { encode } from '@otplib/base32';
|
|
163
|
+
*
|
|
164
|
+
* const secret = encode(new Uint8Array([1, 2, 3, 4, 5]));
|
|
165
|
+
*
|
|
166
|
+
* const uri = generate({
|
|
167
|
+
* type: 'totp',
|
|
168
|
+
* label: 'ACME:john@example.com',
|
|
169
|
+
* params: {
|
|
170
|
+
* secret,
|
|
171
|
+
* issuer: 'ACME',
|
|
172
|
+
* algorithm: 'sha1',
|
|
173
|
+
* digits: 6,
|
|
174
|
+
* },
|
|
175
|
+
* });
|
|
176
|
+
* // Returns: 'otpauth://totp/ACME:john%40example.com?secret=...'
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
declare function generate(uri: OTPAuthURI): string;
|
|
180
|
+
/**
|
|
181
|
+
* Generate a TOTP otpauth:// URI with simplified parameters
|
|
182
|
+
*
|
|
183
|
+
* @param options - TOTP URI generation options
|
|
184
|
+
* @returns The otpauth:// URI string
|
|
185
|
+
*/
|
|
186
|
+
declare function generateTOTP(options: TOTPURIOptions & {
|
|
187
|
+
issuer: string;
|
|
188
|
+
label: string;
|
|
189
|
+
}): string;
|
|
190
|
+
/**
|
|
191
|
+
* Generate a HOTP otpauth:// URI with simplified parameters
|
|
192
|
+
*
|
|
193
|
+
* @param options - HOTP URI generation options
|
|
194
|
+
* @returns The otpauth:// URI string
|
|
195
|
+
*/
|
|
196
|
+
declare function generateHOTP(options: HOTPURIOptions & {
|
|
197
|
+
issuer: string;
|
|
198
|
+
label: string;
|
|
199
|
+
}): string;
|
|
200
|
+
|
|
201
|
+
export { type HOTPURIOptions, InvalidParameterError, InvalidURIError, MissingParameterError, type OTPAuthParams, type OTPAuthURI, type OTPType, type TOTPURIOptions, type URIOptions, URIParseError, generate, generateHOTP, generateTOTP, parse, parse as parseURI };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { HashAlgorithm, Digits } from '@otplib/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OTP type (HOTP or TOTP)
|
|
5
|
+
*/
|
|
6
|
+
type OTPType = "hotp" | "totp";
|
|
7
|
+
/**
|
|
8
|
+
* otpauth:// URI parameters
|
|
9
|
+
*/
|
|
10
|
+
type OTPAuthParams = {
|
|
11
|
+
/**
|
|
12
|
+
* Base32-encoded shared secret (required)
|
|
13
|
+
*/
|
|
14
|
+
readonly secret: string;
|
|
15
|
+
/**
|
|
16
|
+
* Service/provider name (recommended)
|
|
17
|
+
*/
|
|
18
|
+
readonly issuer?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Hash algorithm (default: sha1)
|
|
21
|
+
* Note: Google Authenticator only supports sha1
|
|
22
|
+
*/
|
|
23
|
+
readonly algorithm?: HashAlgorithm;
|
|
24
|
+
/**
|
|
25
|
+
* Number of digits (default: 6)
|
|
26
|
+
* Google Authenticator supports 6 or 8
|
|
27
|
+
*/
|
|
28
|
+
readonly digits?: Digits;
|
|
29
|
+
/**
|
|
30
|
+
* Initial counter value for HOTP (default: 0)
|
|
31
|
+
*/
|
|
32
|
+
readonly counter?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Time step in seconds for TOTP (default: 30)
|
|
35
|
+
*/
|
|
36
|
+
readonly period?: number;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* otpauth:// URI structure
|
|
40
|
+
*/
|
|
41
|
+
type OTPAuthURI = {
|
|
42
|
+
/**
|
|
43
|
+
* Type of OTP (hotp or totp)
|
|
44
|
+
*/
|
|
45
|
+
readonly type: OTPType;
|
|
46
|
+
/**
|
|
47
|
+
* The label (typically: issuer:account or account)
|
|
48
|
+
*/
|
|
49
|
+
readonly label: string;
|
|
50
|
+
/**
|
|
51
|
+
* Parameters from the URI
|
|
52
|
+
*/
|
|
53
|
+
readonly params: OTPAuthParams;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Error thrown when URI parsing fails
|
|
57
|
+
*/
|
|
58
|
+
declare class URIParseError extends Error {
|
|
59
|
+
constructor(message: string);
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Error thrown when URI is invalid
|
|
63
|
+
*/
|
|
64
|
+
declare class InvalidURIError extends URIParseError {
|
|
65
|
+
constructor(uri: string);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Error thrown when URI has missing required parameters
|
|
69
|
+
*/
|
|
70
|
+
declare class MissingParameterError extends URIParseError {
|
|
71
|
+
constructor(param: string);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Error thrown when URI has invalid parameter value
|
|
75
|
+
*/
|
|
76
|
+
declare class InvalidParameterError extends URIParseError {
|
|
77
|
+
constructor(param: string, value: string);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Parse an otpauth:// URI into its components
|
|
82
|
+
*
|
|
83
|
+
* @param uri - The otpauth:// URI to parse
|
|
84
|
+
* @returns Parsed URI components
|
|
85
|
+
* @throws {InvalidURIError} If URI is invalid
|
|
86
|
+
* @throws {MissingParameterError} If required parameters are missing
|
|
87
|
+
* @throws {InvalidParameterError} If parameter values are invalid
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* import { parse } from '@otplib/uri';
|
|
92
|
+
*
|
|
93
|
+
* const uri = 'otpauth://totp/ACME:john@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME';
|
|
94
|
+
* const parsed = parse(uri);
|
|
95
|
+
* // {
|
|
96
|
+
* // type: 'totp',
|
|
97
|
+
* // label: 'ACME:john@example.com',
|
|
98
|
+
* // params: { secret: 'JBSWY3DPEHPK3PXP', issuer: 'ACME' }
|
|
99
|
+
* // }
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
declare function parse(uri: string): OTPAuthURI;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Base options for URI generation
|
|
106
|
+
*/
|
|
107
|
+
type URIOptions = {
|
|
108
|
+
/**
|
|
109
|
+
* Service/provider name (e.g., 'ACME Co', 'GitHub', 'AWS')
|
|
110
|
+
*/
|
|
111
|
+
issuer?: string;
|
|
112
|
+
/**
|
|
113
|
+
* Account identifier (e.g., email, username)
|
|
114
|
+
*/
|
|
115
|
+
label?: string;
|
|
116
|
+
/**
|
|
117
|
+
* Base32-encoded secret key
|
|
118
|
+
*/
|
|
119
|
+
secret: string;
|
|
120
|
+
/**
|
|
121
|
+
* Hash algorithm (default: 'sha1')
|
|
122
|
+
* Note: Google Authenticator only supports sha1
|
|
123
|
+
*/
|
|
124
|
+
algorithm?: HashAlgorithm;
|
|
125
|
+
/**
|
|
126
|
+
* Number of digits (default: 6)
|
|
127
|
+
* Google Authenticator supports 6 or 8, RFC also allows 7
|
|
128
|
+
*/
|
|
129
|
+
digits?: Digits;
|
|
130
|
+
/**
|
|
131
|
+
* Time step in seconds for TOTP (default: 30)
|
|
132
|
+
*/
|
|
133
|
+
period?: number;
|
|
134
|
+
/**
|
|
135
|
+
* Counter value for HOTP
|
|
136
|
+
*/
|
|
137
|
+
counter?: number;
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* TOTP-specific URI options
|
|
141
|
+
*/
|
|
142
|
+
type TOTPURIOptions = URIOptions & {
|
|
143
|
+
period?: number;
|
|
144
|
+
counter?: never;
|
|
145
|
+
};
|
|
146
|
+
/**
|
|
147
|
+
* HOTP-specific URI options
|
|
148
|
+
*/
|
|
149
|
+
type HOTPURIOptions = URIOptions & {
|
|
150
|
+
period?: never;
|
|
151
|
+
counter?: number;
|
|
152
|
+
};
|
|
153
|
+
/**
|
|
154
|
+
* Generate an otpauth:// URI
|
|
155
|
+
*
|
|
156
|
+
* @param uri - The URI components
|
|
157
|
+
* @returns The otpauth:// URI string
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```ts
|
|
161
|
+
* import { generate } from '@otplib/uri';
|
|
162
|
+
* import { encode } from '@otplib/base32';
|
|
163
|
+
*
|
|
164
|
+
* const secret = encode(new Uint8Array([1, 2, 3, 4, 5]));
|
|
165
|
+
*
|
|
166
|
+
* const uri = generate({
|
|
167
|
+
* type: 'totp',
|
|
168
|
+
* label: 'ACME:john@example.com',
|
|
169
|
+
* params: {
|
|
170
|
+
* secret,
|
|
171
|
+
* issuer: 'ACME',
|
|
172
|
+
* algorithm: 'sha1',
|
|
173
|
+
* digits: 6,
|
|
174
|
+
* },
|
|
175
|
+
* });
|
|
176
|
+
* // Returns: 'otpauth://totp/ACME:john%40example.com?secret=...'
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
declare function generate(uri: OTPAuthURI): string;
|
|
180
|
+
/**
|
|
181
|
+
* Generate a TOTP otpauth:// URI with simplified parameters
|
|
182
|
+
*
|
|
183
|
+
* @param options - TOTP URI generation options
|
|
184
|
+
* @returns The otpauth:// URI string
|
|
185
|
+
*/
|
|
186
|
+
declare function generateTOTP(options: TOTPURIOptions & {
|
|
187
|
+
issuer: string;
|
|
188
|
+
label: string;
|
|
189
|
+
}): string;
|
|
190
|
+
/**
|
|
191
|
+
* Generate a HOTP otpauth:// URI with simplified parameters
|
|
192
|
+
*
|
|
193
|
+
* @param options - HOTP URI generation options
|
|
194
|
+
* @returns The otpauth:// URI string
|
|
195
|
+
*/
|
|
196
|
+
declare function generateHOTP(options: HOTPURIOptions & {
|
|
197
|
+
issuer: string;
|
|
198
|
+
label: string;
|
|
199
|
+
}): string;
|
|
200
|
+
|
|
201
|
+
export { type HOTPURIOptions, InvalidParameterError, InvalidURIError, MissingParameterError, type OTPAuthParams, type OTPAuthURI, type OTPType, type TOTPURIOptions, type URIOptions, URIParseError, generate, generateHOTP, generateTOTP, parse, parse as parseURI };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var l=class extends Error{constructor(e){super(e),this.name="URIParseError"}},a=class extends l{constructor(e){super(`Invalid otpauth URI: ${e}`),this.name="InvalidURIError"}},g=class extends l{constructor(e){super(`Missing required parameter: ${e}`),this.name="MissingParameterError"}},c=class extends l{constructor(e,n){super(`Invalid value for parameter '${e}': ${n}`),this.name="InvalidParameterError"}};var m=2048,d=512,y=1024;function O(r,e){let n=r instanceof Error?r.message:String(r);return`Invalid URI encoding in ${e}: ${n}`}function u(r,e,n){if(r.length>e*3)throw new a(`${n} exceeds maximum length`);try{let t=decodeURIComponent(r);if(t.length>e)throw new a(`${n} exceeds maximum length of ${e} characters`);return t}catch(t){throw t instanceof a?t:new a(O(t,n))}}function f(r){if(r.length>m)throw new a(`URI exceeds maximum length of ${m} characters`);if(!r.startsWith("otpauth://"))throw new a(r);let e=r.slice(10),n=e.indexOf("/");if(n===-1)throw new a(r);let t=e.slice(0,n);if(t!=="hotp"&&t!=="totp")throw new c("type",t);let i=e.slice(n+1),o=i.indexOf("?"),s,p;o===-1?(s=u(i,d,"label"),p=""):(s=u(i.slice(0,o),d,"label"),p=i.slice(o+1));let I=P(p);return{type:t,label:s,params:I}}function P(r){let e={};if(!r)return e;let n=r.split("&");for(let t of n){let i=t.indexOf("=");if(i===-1)continue;let o=u(t.slice(0,i),64,"parameter key"),s=u(t.slice(i+1),y,`parameter '${o}'`);switch(o){case"secret":e.secret=s;break;case"issuer":e.issuer=s;break;case"algorithm":e.algorithm=T(s);break;case"digits":e.digits=b(s);break;case"counter":e.counter=parseInt(s,10);break;case"period":e.period=parseInt(s,10);break}}return e}function T(r){let e=r.toLowerCase();if(e==="sha1"||e==="sha-1")return"sha1";if(e==="sha256"||e==="sha-256")return"sha256";if(e==="sha512"||e==="sha-512")return"sha512";throw new c("algorithm",r)}function b(r){let e=parseInt(r,10);if(e===6||e===7||e===8)return e;throw new c("digits",r)}function h(r){let{type:e,label:n,params:t}=r,i=n.split(":").map(p=>encodeURIComponent(p)).join(":"),o=`otpauth://${e}/${i}?`,s=[];return t.secret&&s.push(`secret=${t.secret}`),t.issuer&&s.push(`issuer=${encodeURIComponent(t.issuer)}`),t.algorithm&&t.algorithm!=="sha1"&&s.push(`algorithm=${t.algorithm.toUpperCase()}`),t.digits&&t.digits!==6&&s.push(`digits=${t.digits}`),e==="hotp"&&t.counter!==void 0&&s.push(`counter=${t.counter}`),e==="totp"&&t.period!==void 0&&t.period!==30&&s.push(`period=${t.period}`),o+=s.join("&"),o}function x(r){let{issuer:e,label:n,secret:t,algorithm:i="sha1",digits:o=6,period:s=30}=r,p=e?`${e}:${n}`:n;return h({type:"totp",label:p,params:{secret:t,issuer:e,algorithm:i,digits:o,period:s}})}function U(r){let{issuer:e,label:n,secret:t,counter:i=0,algorithm:o="sha1",digits:s=6}=r,p=e?`${e}:${n}`:n;return h({type:"hotp",label:p,params:{secret:t,issuer:e,algorithm:o,digits:s,counter:i}})}export{c as InvalidParameterError,a as InvalidURIError,g as MissingParameterError,l as URIParseError,h as generate,U as generateHOTP,x as generateTOTP,f as parse,f as parseURI};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/types.ts","../src/parse.ts","../src/generate.ts"],"sourcesContent":["import type { HashAlgorithm, Digits } from \"@otplib/core\";\n\n/**\n * OTP type (HOTP or TOTP)\n */\nexport type OTPType = \"hotp\" | \"totp\";\n\n/**\n * otpauth:// URI parameters\n */\nexport type OTPAuthParams = {\n /**\n * Base32-encoded shared secret (required)\n */\n readonly secret: string;\n\n /**\n * Service/provider name (recommended)\n */\n readonly issuer?: string;\n\n /**\n * Hash algorithm (default: sha1)\n * Note: Google Authenticator only supports sha1\n */\n readonly algorithm?: HashAlgorithm;\n\n /**\n * Number of digits (default: 6)\n * Google Authenticator supports 6 or 8\n */\n readonly digits?: Digits;\n\n /**\n * Initial counter value for HOTP (default: 0)\n */\n readonly counter?: number;\n\n /**\n * Time step in seconds for TOTP (default: 30)\n */\n readonly period?: number;\n};\n\n/**\n * otpauth:// URI structure\n */\nexport type OTPAuthURI = {\n /**\n * Type of OTP (hotp or totp)\n */\n readonly type: OTPType;\n\n /**\n * The label (typically: issuer:account or account)\n */\n readonly label: string;\n\n /**\n * Parameters from the URI\n */\n readonly params: OTPAuthParams;\n};\n\n/**\n * Error thrown when URI parsing fails\n */\nexport class URIParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"URIParseError\";\n }\n}\n\n/**\n * Error thrown when URI is invalid\n */\nexport class InvalidURIError extends URIParseError {\n constructor(uri: string) {\n super(`Invalid otpauth URI: ${uri}`);\n this.name = \"InvalidURIError\";\n }\n}\n\n/**\n * Error thrown when URI has missing required parameters\n */\nexport class MissingParameterError extends URIParseError {\n constructor(param: string) {\n super(`Missing required parameter: ${param}`);\n this.name = \"MissingParameterError\";\n }\n}\n\n/**\n * Error thrown when URI has invalid parameter value\n */\nexport class InvalidParameterError extends URIParseError {\n constructor(param: string, value: string) {\n super(`Invalid value for parameter '${param}': ${value}`);\n this.name = \"InvalidParameterError\";\n }\n}\n","import { InvalidURIError, InvalidParameterError } from \"./types.js\";\n\nimport type { OTPAuthURI, OTPAuthParams } from \"./types.js\";\nimport type { HashAlgorithm, Digits } from \"@otplib/core\";\n\n// Security limits to prevent DoS attacks\nconst MAX_URI_LENGTH = 2048; // Standard URL length limit\nconst MAX_LABEL_LENGTH = 512;\nconst MAX_PARAM_VALUE_LENGTH = 1024;\n\n/**\n * Format error message for caught errors\n * @internal\n */\nexport function formatErrorMessage(error: unknown, context: string): string {\n const errorStr = error instanceof Error ? error.message : String(error);\n return `Invalid URI encoding in ${context}: ${errorStr}`;\n}\n\n/**\n * Safely decode a URI component with length validation and error handling\n * @throws {InvalidURIError} If decoding fails or length exceeds limit\n */\nfunction safeDecodeURIComponent(str: string, maxLength: number, context: string): string {\n if (str.length > maxLength * 3) {\n throw new InvalidURIError(`${context} exceeds maximum length`);\n }\n\n try {\n const decoded = decodeURIComponent(str);\n if (decoded.length > maxLength) {\n throw new InvalidURIError(`${context} exceeds maximum length of ${maxLength} characters`);\n }\n return decoded;\n } catch (error) {\n if (error instanceof InvalidURIError) {\n throw error;\n }\n throw new InvalidURIError(formatErrorMessage(error, context));\n }\n}\n\n/**\n * Parse an otpauth:// URI into its components\n *\n * @param uri - The otpauth:// URI to parse\n * @returns Parsed URI components\n * @throws {InvalidURIError} If URI is invalid\n * @throws {MissingParameterError} If required parameters are missing\n * @throws {InvalidParameterError} If parameter values are invalid\n *\n * @example\n * ```ts\n * import { parse } from '@otplib/uri';\n *\n * const uri = 'otpauth://totp/ACME:john@example.com?secret=JBSWY3DPEHPK3PXP&issuer=ACME';\n * const parsed = parse(uri);\n * // {\n * // type: 'totp',\n * // label: 'ACME:john@example.com',\n * // params: { secret: 'JBSWY3DPEHPK3PXP', issuer: 'ACME' }\n * // }\n * ```\n */\nexport function parse(uri: string): OTPAuthURI {\n if (uri.length > MAX_URI_LENGTH) {\n throw new InvalidURIError(`URI exceeds maximum length of ${MAX_URI_LENGTH} characters`);\n }\n\n if (!uri.startsWith(\"otpauth://\")) {\n throw new InvalidURIError(uri);\n }\n\n const withoutScheme = uri.slice(\"otpauth://\".length);\n const slashIndex = withoutScheme.indexOf(\"/\");\n\n if (slashIndex === -1) {\n throw new InvalidURIError(uri);\n }\n\n const type = withoutScheme.slice(0, slashIndex);\n if (type !== \"hotp\" && type !== \"totp\") {\n throw new InvalidParameterError(\"type\", type);\n }\n\n const remaining = withoutScheme.slice(slashIndex + 1);\n const queryIndex = remaining.indexOf(\"?\");\n\n let label: string;\n let queryString: string;\n\n if (queryIndex === -1) {\n label = safeDecodeURIComponent(remaining, MAX_LABEL_LENGTH, \"label\");\n queryString = \"\";\n } else {\n label = safeDecodeURIComponent(remaining.slice(0, queryIndex), MAX_LABEL_LENGTH, \"label\");\n queryString = remaining.slice(queryIndex + 1);\n }\n\n const params = parseQueryString(queryString);\n\n return {\n type,\n label,\n params,\n };\n}\n\n/**\n * Parse query string into parameters object\n */\nfunction parseQueryString(queryString: string): OTPAuthParams {\n // Use mutable type during construction\n const params: {\n secret?: string;\n issuer?: string;\n algorithm?: HashAlgorithm;\n digits?: Digits;\n counter?: number;\n period?: number;\n } = {};\n\n if (!queryString) {\n return params as OTPAuthParams;\n }\n\n const pairs = queryString.split(\"&\");\n for (const pair of pairs) {\n const equalIndex = pair.indexOf(\"=\");\n if (equalIndex === -1) {\n continue;\n }\n\n const key = safeDecodeURIComponent(pair.slice(0, equalIndex), 64, \"parameter key\");\n const value = safeDecodeURIComponent(\n pair.slice(equalIndex + 1),\n MAX_PARAM_VALUE_LENGTH,\n `parameter '${key}'`,\n );\n\n switch (key) {\n case \"secret\":\n params.secret = value;\n break;\n case \"issuer\":\n params.issuer = value;\n break;\n case \"algorithm\":\n params.algorithm = parseAlgorithm(value);\n break;\n case \"digits\":\n params.digits = parseDigits(value);\n break;\n case \"counter\":\n params.counter = parseInt(value, 10);\n break;\n case \"period\":\n params.period = parseInt(value, 10);\n break;\n }\n }\n\n return params as OTPAuthParams;\n}\n\n/**\n * Parse algorithm string\n */\nfunction parseAlgorithm(value: string): HashAlgorithm {\n const normalized = value.toLowerCase();\n if (normalized === \"sha1\" || normalized === \"sha-1\") {\n return \"sha1\";\n }\n if (normalized === \"sha256\" || normalized === \"sha-256\") {\n return \"sha256\";\n }\n if (normalized === \"sha512\" || normalized === \"sha-512\") {\n return \"sha512\";\n }\n throw new InvalidParameterError(\"algorithm\", value);\n}\n\n/**\n * Parse digits string\n */\nfunction parseDigits(value: string): Digits {\n const digits = parseInt(value, 10);\n if (digits === 6 || digits === 7 || digits === 8) {\n return digits;\n }\n throw new InvalidParameterError(\"digits\", value);\n}\n","import type { OTPAuthURI } from \"./types.js\";\nimport type { HashAlgorithm, Digits } from \"@otplib/core\";\n\n/**\n * Base options for URI generation\n */\nexport type URIOptions = {\n /**\n * Service/provider name (e.g., 'ACME Co', 'GitHub', 'AWS')\n */\n issuer?: string;\n\n /**\n * Account identifier (e.g., email, username)\n */\n label?: string;\n\n /**\n * Base32-encoded secret key\n */\n secret: string;\n\n /**\n * Hash algorithm (default: 'sha1')\n * Note: Google Authenticator only supports sha1\n */\n algorithm?: HashAlgorithm;\n\n /**\n * Number of digits (default: 6)\n * Google Authenticator supports 6 or 8, RFC also allows 7\n */\n digits?: Digits;\n\n /**\n * Time step in seconds for TOTP (default: 30)\n */\n period?: number;\n\n /**\n * Counter value for HOTP\n */\n counter?: number;\n};\n\n/**\n * TOTP-specific URI options\n */\nexport type TOTPURIOptions = URIOptions & {\n period?: number;\n counter?: never;\n};\n\n/**\n * HOTP-specific URI options\n */\nexport type HOTPURIOptions = URIOptions & {\n period?: never;\n counter?: number;\n};\n\n/**\n * Generate an otpauth:// URI\n *\n * @param uri - The URI components\n * @returns The otpauth:// URI string\n *\n * @example\n * ```ts\n * import { generate } from '@otplib/uri';\n * import { encode } from '@otplib/base32';\n *\n * const secret = encode(new Uint8Array([1, 2, 3, 4, 5]));\n *\n * const uri = generate({\n * type: 'totp',\n * label: 'ACME:john@example.com',\n * params: {\n * secret,\n * issuer: 'ACME',\n * algorithm: 'sha1',\n * digits: 6,\n * },\n * });\n * // Returns: 'otpauth://totp/ACME:john%40example.com?secret=...'\n * ```\n */\nexport function generate(uri: OTPAuthURI): string {\n const { type, label, params } = uri;\n\n // Encode label parts while preserving ':' as the issuer/account separator\n const encodedLabel = label\n .split(\":\")\n .map((part) => encodeURIComponent(part))\n .join(\":\");\n\n let result = `otpauth://${type}/${encodedLabel}?`;\n\n const queryParams: string[] = [];\n\n if (params.secret) {\n queryParams.push(`secret=${params.secret}`);\n }\n\n if (params.issuer) {\n queryParams.push(`issuer=${encodeURIComponent(params.issuer)}`);\n }\n\n if (params.algorithm && params.algorithm !== \"sha1\") {\n queryParams.push(`algorithm=${params.algorithm.toUpperCase()}`);\n }\n\n if (params.digits && params.digits !== 6) {\n queryParams.push(`digits=${params.digits}`);\n }\n\n if (type === \"hotp\" && params.counter !== undefined) {\n queryParams.push(`counter=${params.counter}`);\n }\n\n if (type === \"totp\" && params.period !== undefined && params.period !== 30) {\n queryParams.push(`period=${params.period}`);\n }\n\n result += queryParams.join(\"&\");\n\n return result;\n}\n\n/**\n * Generate a TOTP otpauth:// URI with simplified parameters\n *\n * @param options - TOTP URI generation options\n * @returns The otpauth:// URI string\n */\nexport function generateTOTP(options: TOTPURIOptions & { issuer: string; label: string }): string {\n const { issuer, label: account, secret, algorithm = \"sha1\", digits = 6, period = 30 } = options;\n\n const fullLabel = issuer ? `${issuer}:${account}` : account;\n\n return generate({\n type: \"totp\",\n label: fullLabel,\n params: {\n secret,\n issuer,\n algorithm,\n digits,\n period,\n },\n });\n}\n\n/**\n * Generate a HOTP otpauth:// URI with simplified parameters\n *\n * @param options - HOTP URI generation options\n * @returns The otpauth:// URI string\n */\nexport function generateHOTP(options: HOTPURIOptions & { issuer: string; label: string }): string {\n const { issuer, label: account, secret, counter = 0, algorithm = \"sha1\", digits = 6 } = options;\n\n const fullLabel = issuer ? `${issuer}:${account}` : account;\n\n return generate({\n type: \"hotp\",\n label: fullLabel,\n params: {\n secret,\n issuer,\n algorithm,\n digits,\n counter,\n },\n });\n}\n"],"mappings":"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","names":["URIParseError","message","InvalidURIError","uri","MissingParameterError","param","InvalidParameterError","value","MAX_URI_LENGTH","MAX_LABEL_LENGTH","MAX_PARAM_VALUE_LENGTH","formatErrorMessage","error","context","errorStr","safeDecodeURIComponent","str","maxLength","InvalidURIError","decoded","parse","uri","withoutScheme","slashIndex","type","InvalidParameterError","remaining","queryIndex","label","queryString","params","parseQueryString","pairs","pair","equalIndex","key","value","parseAlgorithm","parseDigits","normalized","digits","generate","uri","type","label","params","encodedLabel","part","result","queryParams","generateTOTP","options","issuer","account","secret","algorithm","digits","period","fullLabel","generateHOTP","counter"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@otplib/uri",
|
|
3
|
+
"version": "13.0.0",
|
|
4
|
+
"description": "otpauth:// URI parsing and generation for otplib",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Gerald Yeo <support@yeojz.dev>",
|
|
7
|
+
"homepage": "https://otplib.yeojz.dev",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/yeojz/otplib.git",
|
|
11
|
+
"directory": "packages/uri"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/yeojz/otplib/issues"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"otp",
|
|
18
|
+
"otpauth",
|
|
19
|
+
"uri",
|
|
20
|
+
"qrcode",
|
|
21
|
+
"2fa",
|
|
22
|
+
"authenticator"
|
|
23
|
+
],
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"module": "./dist/index.js",
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"import": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"require": {
|
|
36
|
+
"types": "./dist/index.d.cts",
|
|
37
|
+
"default": "./dist/index.cjs"
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"dist",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
],
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@otplib/core": "13.0.0"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"tsup": "^8.0.1",
|
|
51
|
+
"typescript": "^5.3.3",
|
|
52
|
+
"vitest": "^4.0.16",
|
|
53
|
+
"@repo/testing": "13.0.0"
|
|
54
|
+
},
|
|
55
|
+
"publishConfig": {
|
|
56
|
+
"access": "public"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"build": "tsup",
|
|
60
|
+
"dev": "tsup --watch",
|
|
61
|
+
"test": "vitest",
|
|
62
|
+
"test:ci": "vitest run --coverage",
|
|
63
|
+
"typecheck": "tsc --noEmit",
|
|
64
|
+
"lint": "eslint src/",
|
|
65
|
+
"clean": "rm -rf dist .tsbuildinfo"
|
|
66
|
+
}
|
|
67
|
+
}
|