ldap-rest 0.0.1 → 0.1.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.
Files changed (62) hide show
  1. package/README.md +38 -0
  2. package/bin/sync-app-accounts.mjs +276 -0
  3. package/dist/abstract/ldapFlat.js +1 -1
  4. package/dist/abstract/plugin.js +1 -1
  5. package/dist/bin/index.js +1 -1
  6. package/dist/config/args.js +1 -1
  7. package/dist/lib/ldapActions.js +1 -1
  8. package/dist/lib/utils.js +1 -1
  9. package/dist/logger/winston.js +1 -1
  10. package/dist/plugins/auth/hmac.js +1 -0
  11. package/dist/plugins/auth/totp.js +1 -0
  12. package/dist/plugins/demo/helloworld.js +1 -1
  13. package/dist/plugins/ldap/groups.js +1 -1
  14. package/dist/plugins/ldap/onChange.js +1 -1
  15. package/dist/plugins/static.js +1 -1
  16. package/dist/plugins/twake/appAccountsApi.js +1 -0
  17. package/dist/plugins/twake/appAccountsConsistency.js +1 -0
  18. package/dist/plugins/twake/james.js +1 -1
  19. package/dist/plugins/weblogs.js +1 -1
  20. package/dist/src/abstract/ldapFlat.d.ts +0 -5
  21. package/dist/src/abstract/plugin.d.ts +1 -0
  22. package/dist/src/bin/index.d.ts +1 -0
  23. package/dist/src/browser/shared/index.d.ts +1 -0
  24. package/dist/src/browser/shared/utils/hmac.d.ts +73 -0
  25. package/dist/src/browser/shared/utils/totp.d.ts +64 -0
  26. package/dist/src/config/args.d.ts +9 -1
  27. package/dist/src/hooks.d.ts +2 -2
  28. package/dist/src/lib/ldapActions.d.ts +3 -7
  29. package/dist/src/lib/utils.d.ts +17 -0
  30. package/dist/src/logger/winston.d.ts +5 -0
  31. package/dist/src/plugins/auth/hmac.d.ts +24 -0
  32. package/dist/src/plugins/auth/totp.d.ts +35 -0
  33. package/dist/src/plugins/ldap/onChange.d.ts +1 -0
  34. package/dist/src/plugins/static.d.ts +4 -0
  35. package/dist/src/plugins/twake/appAccountsApi.d.ts +52 -0
  36. package/dist/src/plugins/twake/appAccountsConsistency.d.ts +58 -0
  37. package/docs/DEVELOPER_GUIDE.md +1 -0
  38. package/docs/PLUGIN_DEPENDENCIES.md +19 -0
  39. package/docs/README.md +74 -30
  40. package/docs/authentication.md +600 -12
  41. package/docs/plugins/DEVELOPMENT.md +148 -76
  42. package/docs/testing-with-embedded-ldap.md +342 -0
  43. package/docs/twakeAppAccountsApi.md +485 -0
  44. package/docs/weblogs.md +36 -8
  45. package/examples/web/README.md +166 -0
  46. package/examples/web/ldap-tree-viewer.html +16 -2
  47. package/examples/web/totp-client.html +543 -0
  48. package/package.json +46 -1
  49. package/static/browser/shared/index.js +1 -0
  50. package/static/browser/shared/index.js.map +1 -1
  51. package/static/browser/shared/utils/hmac.js +202 -0
  52. package/static/browser/shared/utils/hmac.js.map +1 -0
  53. package/static/browser/shared/utils/totp.js +182 -0
  54. package/static/browser/shared/utils/totp.js.map +1 -0
  55. package/static/browser/shared.esm.js +181 -1
  56. package/static/browser/shared.esm.js.map +1 -1
  57. package/static/browser/shared.js +184 -0
  58. package/static/browser/shared.js.map +1 -1
  59. package/static/browser/shared.min.js +1 -1
  60. package/static/browser/shared.min.js.map +1 -1
  61. package/static/schemas/obm/organizations.json +24 -0
  62. package/static/schemas/obm/users.json +192 -0
@@ -0,0 +1,202 @@
1
+ /**
2
+ * HMAC-SHA256 request signing utility for browser
3
+ * For backend services authentication
4
+ * @module browser/shared/utils/hmac
5
+ */
6
+ /**
7
+ * Calculate SHA-256 hash of data
8
+ */
9
+ async function sha256(data) {
10
+ const encoder = new TextEncoder();
11
+ const dataBuffer = encoder.encode(data);
12
+ const hashBuffer = await crypto.subtle.digest('SHA-256', dataBuffer);
13
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
14
+ return hashArray.map((b) => b.toString(16).padStart(2, '0')).join('');
15
+ }
16
+ /**
17
+ * Calculate HMAC-SHA256 signature
18
+ */
19
+ async function hmacSha256(secret, message) {
20
+ const encoder = new TextEncoder();
21
+ const keyData = encoder.encode(secret);
22
+ const messageData = encoder.encode(message);
23
+ // Import secret as HMAC key
24
+ const cryptoKey = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
25
+ // Calculate HMAC
26
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, messageData);
27
+ const signatureArray = Array.from(new Uint8Array(signature));
28
+ return signatureArray.map((b) => b.toString(16).padStart(2, '0')).join('');
29
+ }
30
+ /**
31
+ * Generate HMAC signature for a request
32
+ *
33
+ * Signature = HMAC-SHA256(secret, "METHOD|PATH|timestamp|body-hash")
34
+ */
35
+ async function generateHmacSignature(secret, method, path, timestamp, body) {
36
+ // Calculate body hash for POST/PATCH/PUT methods
37
+ let bodyHash = '';
38
+ const methodUpper = method.toUpperCase();
39
+ if (body &&
40
+ (methodUpper === 'POST' || methodUpper === 'PATCH' || methodUpper === 'PUT')) {
41
+ const bodyString = typeof body === 'string' ? body : JSON.stringify(body);
42
+ bodyHash = await sha256(bodyString);
43
+ }
44
+ // Create signing string: METHOD|PATH|timestamp|body-hash
45
+ const signingString = `${methodUpper}|${path}|${timestamp}|${bodyHash}`;
46
+ // Calculate HMAC-SHA256 signature
47
+ return await hmacSha256(secret, signingString);
48
+ }
49
+ /**
50
+ * HMAC Authentication Client
51
+ * Provides HTTP client with HMAC-SHA256 request signing
52
+ */
53
+ class HmacAuthClient {
54
+ constructor(config) {
55
+ this.config = config;
56
+ if (!this.config.serviceId) {
57
+ throw new Error('Service ID is required');
58
+ }
59
+ if (!this.config.secret || this.config.secret.length < 32) {
60
+ throw new Error('Secret must be at least 32 characters long');
61
+ }
62
+ }
63
+ /**
64
+ * Generate Authorization header for a request
65
+ *
66
+ * Format: HMAC-SHA256 service-id:timestamp:signature
67
+ */
68
+ async getAuthHeader(method, path, body) {
69
+ const timestamp = Date.now();
70
+ const signature = await generateHmacSignature(this.config.secret, method, path, timestamp, body);
71
+ return `HMAC-SHA256 ${this.config.serviceId}:${timestamp}:${signature}`;
72
+ }
73
+ /**
74
+ * Extract path with query string from URL
75
+ */
76
+ extractPath(url) {
77
+ try {
78
+ const urlObj = new URL(url, window.location.origin);
79
+ return urlObj.pathname + urlObj.search;
80
+ }
81
+ catch {
82
+ // If URL parsing fails, assume it's already a path
83
+ return url;
84
+ }
85
+ }
86
+ /**
87
+ * Perform an authenticated fetch request
88
+ */
89
+ async fetch(url, options = {}) {
90
+ const method = options.method || 'GET';
91
+ const path = this.extractPath(url);
92
+ // Get body for signature calculation
93
+ let body = undefined;
94
+ if (options.body) {
95
+ if (typeof options.body === 'string') {
96
+ body = options.body;
97
+ }
98
+ else if (options.body instanceof FormData) {
99
+ // For FormData, we can't easily sign it, so skip body hash
100
+ body = undefined;
101
+ }
102
+ else {
103
+ body = options.body;
104
+ }
105
+ }
106
+ const authHeader = await this.getAuthHeader(method, path, body);
107
+ const headers = new Headers(options.headers);
108
+ headers.set('Authorization', authHeader);
109
+ return fetch(url, {
110
+ ...options,
111
+ headers,
112
+ });
113
+ }
114
+ /**
115
+ * Prepare request body and headers for methods with body
116
+ */
117
+ prepareRequestBody(body, headers) {
118
+ if (!body) {
119
+ return undefined;
120
+ }
121
+ if (typeof body === 'string') {
122
+ return body;
123
+ }
124
+ if (body instanceof FormData) {
125
+ return body;
126
+ }
127
+ if (typeof body === 'object') {
128
+ headers.set('Content-Type', 'application/json');
129
+ return JSON.stringify(body);
130
+ }
131
+ return undefined;
132
+ }
133
+ /**
134
+ * Perform an authenticated GET request
135
+ */
136
+ async get(url, options = {}) {
137
+ return this.fetch(url, { ...options, method: 'GET' });
138
+ }
139
+ /**
140
+ * Perform an authenticated POST request
141
+ */
142
+ async post(url, body, options = {}) {
143
+ const headers = new Headers(options.headers);
144
+ const requestBody = this.prepareRequestBody(body, headers);
145
+ return this.fetch(url, {
146
+ ...options,
147
+ method: 'POST',
148
+ headers,
149
+ body: requestBody,
150
+ });
151
+ }
152
+ /**
153
+ * Perform an authenticated PUT request
154
+ */
155
+ async put(url, body, options = {}) {
156
+ const headers = new Headers(options.headers);
157
+ const requestBody = this.prepareRequestBody(body, headers);
158
+ return this.fetch(url, {
159
+ ...options,
160
+ method: 'PUT',
161
+ headers,
162
+ body: requestBody,
163
+ });
164
+ }
165
+ /**
166
+ * Perform an authenticated DELETE request
167
+ */
168
+ async delete(url, options = {}) {
169
+ return this.fetch(url, { ...options, method: 'DELETE' });
170
+ }
171
+ /**
172
+ * Perform an authenticated PATCH request
173
+ */
174
+ async patch(url, body, options = {}) {
175
+ const headers = new Headers(options.headers);
176
+ const requestBody = this.prepareRequestBody(body, headers);
177
+ return this.fetch(url, {
178
+ ...options,
179
+ method: 'PATCH',
180
+ headers,
181
+ body: requestBody,
182
+ });
183
+ }
184
+ }
185
+ /**
186
+ * Verify the minimum recommended secret length
187
+ */
188
+ function isSecretSecure(secret) {
189
+ return secret.length >= 32;
190
+ }
191
+ /**
192
+ * Generate a random secure secret for HMAC (for testing/development)
193
+ * In production, secrets should be generated securely on the backend
194
+ */
195
+ async function generateRandomSecret(length = 64) {
196
+ const array = new Uint8Array(length);
197
+ crypto.getRandomValues(array);
198
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
199
+ }
200
+
201
+ export { HmacAuthClient, generateHmacSignature, generateRandomSecret, isSecretSecure };
202
+ //# sourceMappingURL=hmac.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hmac.js","sources":["../../../../../src/browser/shared/utils/hmac.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -0,0 +1,182 @@
1
+ /**
2
+ * TOTP (Time-based One-Time Password) utility for browser
3
+ * @module browser/shared/utils/totp
4
+ */
5
+ /**
6
+ * Generate a TOTP code from a Base32 secret
7
+ */
8
+ async function generateTotp(config) {
9
+ const digits = config.digits ?? 6;
10
+ const step = config.step ?? 30;
11
+ // Get current time counter
12
+ const now = Math.floor(Date.now() / 1000);
13
+ const counter = Math.floor(now / step);
14
+ return await generateTotpAtTime(config.secret, counter, digits);
15
+ }
16
+ /**
17
+ * Generate a TOTP code for a specific counter value
18
+ */
19
+ async function generateTotpAtTime(secret, counter, digits) {
20
+ // Decode Base32 secret
21
+ const key = base32Decode(secret);
22
+ // Convert counter to 8-byte array (big-endian)
23
+ const counterBytes = new Uint8Array(8);
24
+ const view = new DataView(counterBytes.buffer);
25
+ view.setBigUint64(0, BigInt(counter), false); // false = big-endian
26
+ // Import key for HMAC-SHA1
27
+ const cryptoKey = await crypto.subtle.importKey('raw', key.buffer, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
28
+ // Generate HMAC-SHA1
29
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, counterBytes);
30
+ const hash = new Uint8Array(signature);
31
+ // Dynamic truncation (RFC 4226)
32
+ const offset = hash[hash.length - 1] & 0x0f;
33
+ const truncatedHash = ((hash[offset] & 0x7f) << 24) |
34
+ ((hash[offset + 1] & 0xff) << 16) |
35
+ ((hash[offset + 2] & 0xff) << 8) |
36
+ (hash[offset + 3] & 0xff);
37
+ // Generate N-digit code
38
+ const code = truncatedHash % Math.pow(10, digits);
39
+ return code.toString().padStart(digits, '0');
40
+ }
41
+ /**
42
+ * Decode a Base32 string to Uint8Array
43
+ */
44
+ function base32Decode(input) {
45
+ const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
46
+ // Remove trailing padding characters (avoid ReDoS by using simple loop instead of regex)
47
+ let cleanInput = input.toUpperCase();
48
+ while (cleanInput.endsWith('=')) {
49
+ cleanInput = cleanInput.slice(0, -1);
50
+ }
51
+ let bits = 0;
52
+ let value = 0;
53
+ const output = [];
54
+ for (let i = 0; i < cleanInput.length; i++) {
55
+ const idx = base32Chars.indexOf(cleanInput[i]);
56
+ if (idx === -1) {
57
+ throw new Error(`Invalid Base32 character: ${cleanInput[i]}`);
58
+ }
59
+ value = (value << 5) | idx;
60
+ bits += 5;
61
+ if (bits >= 8) {
62
+ output.push((value >>> (bits - 8)) & 0xff);
63
+ bits -= 8;
64
+ }
65
+ }
66
+ return new Uint8Array(output);
67
+ }
68
+ /**
69
+ * Validate if a string is valid Base32
70
+ */
71
+ function isValidBase32(input) {
72
+ const base32Regex = /^[A-Z2-7]+=*$/i;
73
+ return base32Regex.test(input);
74
+ }
75
+ /**
76
+ * Get remaining seconds until next TOTP code
77
+ */
78
+ function getRemainingSeconds(step = 30) {
79
+ const now = Math.floor(Date.now() / 1000);
80
+ return step - (now % step);
81
+ }
82
+ /**
83
+ * TOTP Authentication Client
84
+ * Provides HTTP client with TOTP authentication header
85
+ */
86
+ class TotpAuthClient {
87
+ constructor(config) {
88
+ this.config = {
89
+ secret: config.secret,
90
+ digits: config.digits ?? 6,
91
+ step: config.step ?? 30,
92
+ };
93
+ if (!isValidBase32(this.config.secret)) {
94
+ throw new Error('Invalid Base32 secret');
95
+ }
96
+ }
97
+ /**
98
+ * Get current TOTP code
99
+ */
100
+ async getCode() {
101
+ return await generateTotp(this.config);
102
+ }
103
+ /**
104
+ * Get Authorization header with Bearer token
105
+ */
106
+ async getAuthHeader() {
107
+ const code = await this.getCode();
108
+ return `Bearer ${code}`;
109
+ }
110
+ /**
111
+ * Perform an authenticated fetch request
112
+ */
113
+ async fetch(url, options = {}) {
114
+ const authHeader = await this.getAuthHeader();
115
+ const headers = new Headers(options.headers);
116
+ headers.set('Authorization', authHeader);
117
+ return fetch(url, {
118
+ ...options,
119
+ headers,
120
+ });
121
+ }
122
+ /**
123
+ * Perform an authenticated GET request
124
+ */
125
+ async get(url, options = {}) {
126
+ return this.fetch(url, { ...options, method: 'GET' });
127
+ }
128
+ /**
129
+ * Perform an authenticated POST request
130
+ */
131
+ async post(url, body, options = {}) {
132
+ const headers = new Headers(options.headers);
133
+ if (body && typeof body === 'object') {
134
+ headers.set('Content-Type', 'application/json');
135
+ }
136
+ return this.fetch(url, {
137
+ ...options,
138
+ method: 'POST',
139
+ headers,
140
+ body: body ? JSON.stringify(body) : undefined,
141
+ });
142
+ }
143
+ /**
144
+ * Perform an authenticated PUT request
145
+ */
146
+ async put(url, body, options = {}) {
147
+ const headers = new Headers(options.headers);
148
+ if (body && typeof body === 'object') {
149
+ headers.set('Content-Type', 'application/json');
150
+ }
151
+ return this.fetch(url, {
152
+ ...options,
153
+ method: 'PUT',
154
+ headers,
155
+ body: body ? JSON.stringify(body) : undefined,
156
+ });
157
+ }
158
+ /**
159
+ * Perform an authenticated DELETE request
160
+ */
161
+ async delete(url, options = {}) {
162
+ return this.fetch(url, { ...options, method: 'DELETE' });
163
+ }
164
+ /**
165
+ * Perform an authenticated PATCH request
166
+ */
167
+ async patch(url, body, options = {}) {
168
+ const headers = new Headers(options.headers);
169
+ if (body && typeof body === 'object') {
170
+ headers.set('Content-Type', 'application/json');
171
+ }
172
+ return this.fetch(url, {
173
+ ...options,
174
+ method: 'PATCH',
175
+ headers,
176
+ body: body ? JSON.stringify(body) : undefined,
177
+ });
178
+ }
179
+ }
180
+
181
+ export { TotpAuthClient, generateTotp, getRemainingSeconds, isValidBase32 };
182
+ //# sourceMappingURL=totp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totp.js","sources":["../../../../../src/browser/shared/utils/totp.ts"],"sourcesContent":[null],"names":[],"mappings":"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"}
@@ -423,6 +423,186 @@ async function loadSchema(schemaSource) {
423
423
  return schemaSource;
424
424
  }
425
425
 
426
+ /**
427
+ * TOTP (Time-based One-Time Password) utility for browser
428
+ * @module browser/shared/utils/totp
429
+ */
430
+ /**
431
+ * Generate a TOTP code from a Base32 secret
432
+ */
433
+ async function generateTotp(config) {
434
+ const digits = config.digits ?? 6;
435
+ const step = config.step ?? 30;
436
+ // Get current time counter
437
+ const now = Math.floor(Date.now() / 1000);
438
+ const counter = Math.floor(now / step);
439
+ return await generateTotpAtTime(config.secret, counter, digits);
440
+ }
441
+ /**
442
+ * Generate a TOTP code for a specific counter value
443
+ */
444
+ async function generateTotpAtTime(secret, counter, digits) {
445
+ // Decode Base32 secret
446
+ const key = base32Decode(secret);
447
+ // Convert counter to 8-byte array (big-endian)
448
+ const counterBytes = new Uint8Array(8);
449
+ const view = new DataView(counterBytes.buffer);
450
+ view.setBigUint64(0, BigInt(counter), false); // false = big-endian
451
+ // Import key for HMAC-SHA1
452
+ const cryptoKey = await crypto.subtle.importKey('raw', key.buffer, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
453
+ // Generate HMAC-SHA1
454
+ const signature = await crypto.subtle.sign('HMAC', cryptoKey, counterBytes);
455
+ const hash = new Uint8Array(signature);
456
+ // Dynamic truncation (RFC 4226)
457
+ const offset = hash[hash.length - 1] & 0x0f;
458
+ const truncatedHash = ((hash[offset] & 0x7f) << 24) |
459
+ ((hash[offset + 1] & 0xff) << 16) |
460
+ ((hash[offset + 2] & 0xff) << 8) |
461
+ (hash[offset + 3] & 0xff);
462
+ // Generate N-digit code
463
+ const code = truncatedHash % Math.pow(10, digits);
464
+ return code.toString().padStart(digits, '0');
465
+ }
466
+ /**
467
+ * Decode a Base32 string to Uint8Array
468
+ */
469
+ function base32Decode(input) {
470
+ const base32Chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
471
+ // Remove trailing padding characters (avoid ReDoS by using simple loop instead of regex)
472
+ let cleanInput = input.toUpperCase();
473
+ while (cleanInput.endsWith('=')) {
474
+ cleanInput = cleanInput.slice(0, -1);
475
+ }
476
+ let bits = 0;
477
+ let value = 0;
478
+ const output = [];
479
+ for (let i = 0; i < cleanInput.length; i++) {
480
+ const idx = base32Chars.indexOf(cleanInput[i]);
481
+ if (idx === -1) {
482
+ throw new Error(`Invalid Base32 character: ${cleanInput[i]}`);
483
+ }
484
+ value = (value << 5) | idx;
485
+ bits += 5;
486
+ if (bits >= 8) {
487
+ output.push((value >>> (bits - 8)) & 0xff);
488
+ bits -= 8;
489
+ }
490
+ }
491
+ return new Uint8Array(output);
492
+ }
493
+ /**
494
+ * Validate if a string is valid Base32
495
+ */
496
+ function isValidBase32(input) {
497
+ const base32Regex = /^[A-Z2-7]+=*$/i;
498
+ return base32Regex.test(input);
499
+ }
500
+ /**
501
+ * Get remaining seconds until next TOTP code
502
+ */
503
+ function getRemainingSeconds(step = 30) {
504
+ const now = Math.floor(Date.now() / 1000);
505
+ return step - (now % step);
506
+ }
507
+ /**
508
+ * TOTP Authentication Client
509
+ * Provides HTTP client with TOTP authentication header
510
+ */
511
+ class TotpAuthClient {
512
+ constructor(config) {
513
+ this.config = {
514
+ secret: config.secret,
515
+ digits: config.digits ?? 6,
516
+ step: config.step ?? 30,
517
+ };
518
+ if (!isValidBase32(this.config.secret)) {
519
+ throw new Error('Invalid Base32 secret');
520
+ }
521
+ }
522
+ /**
523
+ * Get current TOTP code
524
+ */
525
+ async getCode() {
526
+ return await generateTotp(this.config);
527
+ }
528
+ /**
529
+ * Get Authorization header with Bearer token
530
+ */
531
+ async getAuthHeader() {
532
+ const code = await this.getCode();
533
+ return `Bearer ${code}`;
534
+ }
535
+ /**
536
+ * Perform an authenticated fetch request
537
+ */
538
+ async fetch(url, options = {}) {
539
+ const authHeader = await this.getAuthHeader();
540
+ const headers = new Headers(options.headers);
541
+ headers.set('Authorization', authHeader);
542
+ return fetch(url, {
543
+ ...options,
544
+ headers,
545
+ });
546
+ }
547
+ /**
548
+ * Perform an authenticated GET request
549
+ */
550
+ async get(url, options = {}) {
551
+ return this.fetch(url, { ...options, method: 'GET' });
552
+ }
553
+ /**
554
+ * Perform an authenticated POST request
555
+ */
556
+ async post(url, body, options = {}) {
557
+ const headers = new Headers(options.headers);
558
+ if (body && typeof body === 'object') {
559
+ headers.set('Content-Type', 'application/json');
560
+ }
561
+ return this.fetch(url, {
562
+ ...options,
563
+ method: 'POST',
564
+ headers,
565
+ body: body ? JSON.stringify(body) : undefined,
566
+ });
567
+ }
568
+ /**
569
+ * Perform an authenticated PUT request
570
+ */
571
+ async put(url, body, options = {}) {
572
+ const headers = new Headers(options.headers);
573
+ if (body && typeof body === 'object') {
574
+ headers.set('Content-Type', 'application/json');
575
+ }
576
+ return this.fetch(url, {
577
+ ...options,
578
+ method: 'PUT',
579
+ headers,
580
+ body: body ? JSON.stringify(body) : undefined,
581
+ });
582
+ }
583
+ /**
584
+ * Perform an authenticated DELETE request
585
+ */
586
+ async delete(url, options = {}) {
587
+ return this.fetch(url, { ...options, method: 'DELETE' });
588
+ }
589
+ /**
590
+ * Perform an authenticated PATCH request
591
+ */
592
+ async patch(url, body, options = {}) {
593
+ const headers = new Headers(options.headers);
594
+ if (body && typeof body === 'object') {
595
+ headers.set('Content-Type', 'application/json');
596
+ }
597
+ return this.fetch(url, {
598
+ ...options,
599
+ method: 'PATCH',
600
+ headers,
601
+ body: body ? JSON.stringify(body) : undefined,
602
+ });
603
+ }
604
+ }
605
+
426
606
  /**
427
607
  * Reusable Modal component
428
608
  * @module browser/shared/components/Modal
@@ -690,5 +870,5 @@ const THEMES = {
690
870
  },
691
871
  };
692
872
 
693
- export { Modal, StatusMessage, THEMES, clearPointerOptionsCache, collectPointerArrayValues, createElement, escapeHtml, generateFormField, getElementByIdSafe, getFieldLabel, getOrganizationPath, groupSchemaFields, loadPointerOptions, loadSchema, processFormData, querySelectorAllSafe, querySelectorSafe, replacePlaceholders, replaceSchemaPlaceholders, setInnerHTML, showStatus, toTitleCase };
873
+ export { Modal, StatusMessage, THEMES, TotpAuthClient, clearPointerOptionsCache, collectPointerArrayValues, createElement, escapeHtml, generateFormField, generateTotp, getElementByIdSafe, getFieldLabel, getOrganizationPath, getRemainingSeconds, groupSchemaFields, isValidBase32, loadPointerOptions, loadSchema, processFormData, querySelectorAllSafe, querySelectorSafe, replacePlaceholders, replaceSchemaPlaceholders, setInnerHTML, showStatus, toTitleCase };
694
874
  //# sourceMappingURL=shared.esm.js.map