ldap-rest 0.0.1 → 0.1.1

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 +28 -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 +76 -30
  40. package/docs/authentication.md +604 -12
  41. package/docs/plugins/DEVELOPMENT.md +162 -76
  42. package/docs/testing-with-embedded-ldap.md +347 -0
  43. package/docs/twakeAppAccountsApi.md +485 -0
  44. package/docs/weblogs.md +37 -8
  45. package/examples/web/README.md +175 -0
  46. package/examples/web/ldap-tree-viewer.html +16 -2
  47. package/examples/web/totp-client.html +541 -0
  48. package/package.json +132 -87
  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,541 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>TOTP Client Demo</title>
7
+
8
+ <!-- Google Fonts -->
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=Roboto+Mono:wght@400;500;700&family=Roboto:wght@300;400;500;700&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ <link
14
+ href="https://fonts.googleapis.com/icon?family=Material+Icons"
15
+ rel="stylesheet"
16
+ />
17
+
18
+ <!-- Shared CSS -->
19
+ <link rel="stylesheet" href="/static/browser/common-demo.css" />
20
+
21
+ <style>
22
+ :root {
23
+ --primary-color: #00bfa5;
24
+ --primary-color-dark: #00897b;
25
+ --success-color: #4caf50;
26
+ --error-color: #f44336;
27
+ }
28
+
29
+ body {
30
+ background: linear-gradient(135deg, #00bfa5 0%, #00695c 100%);
31
+ }
32
+
33
+ .demo-header h1 {
34
+ color: #00bfa5;
35
+ }
36
+
37
+ .totp-code {
38
+ font-family: 'Roboto Mono', monospace;
39
+ font-size: 48px;
40
+ font-weight: 700;
41
+ text-align: center;
42
+ padding: 30px;
43
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
44
+ color: white;
45
+ border-radius: 12px;
46
+ letter-spacing: 8px;
47
+ margin: 20px 0;
48
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
49
+ user-select: all;
50
+ }
51
+
52
+ .totp-code.expired {
53
+ opacity: 0.5;
54
+ }
55
+
56
+ .countdown {
57
+ text-align: center;
58
+ font-size: 18px;
59
+ color: #666;
60
+ margin-top: 10px;
61
+ }
62
+
63
+ .countdown-bar {
64
+ height: 6px;
65
+ background: #e0e0e0;
66
+ border-radius: 3px;
67
+ overflow: hidden;
68
+ margin: 10px 0;
69
+ }
70
+
71
+ .countdown-bar-fill {
72
+ height: 100%;
73
+ background: linear-gradient(
74
+ 90deg,
75
+ #4caf50 0%,
76
+ #ff9800 50%,
77
+ #f44336 100%
78
+ );
79
+ transition: width 0.3s ease;
80
+ }
81
+
82
+ .secret-input {
83
+ font-family: 'Roboto Mono', monospace;
84
+ width: 100%;
85
+ padding: 12px;
86
+ font-size: 16px;
87
+ border: 2px solid #ddd;
88
+ border-radius: 8px;
89
+ margin: 10px 0;
90
+ text-transform: uppercase;
91
+ }
92
+
93
+ .secret-input:focus {
94
+ outline: none;
95
+ border-color: var(--primary-color);
96
+ }
97
+
98
+ .config-grid {
99
+ display: grid;
100
+ grid-template-columns: 1fr 1fr;
101
+ gap: 15px;
102
+ margin: 20px 0;
103
+ }
104
+
105
+ .config-field label {
106
+ display: block;
107
+ font-weight: 500;
108
+ margin-bottom: 5px;
109
+ color: #555;
110
+ }
111
+
112
+ .config-field input,
113
+ .config-field select {
114
+ width: 100%;
115
+ padding: 10px;
116
+ border: 2px solid #ddd;
117
+ border-radius: 8px;
118
+ font-size: 14px;
119
+ }
120
+
121
+ .api-test {
122
+ margin-top: 30px;
123
+ padding: 20px;
124
+ background: #f9f9f9;
125
+ border-radius: 8px;
126
+ }
127
+
128
+ .api-response {
129
+ font-family: 'Roboto Mono', monospace;
130
+ font-size: 12px;
131
+ background: #263238;
132
+ color: #aed581;
133
+ padding: 15px;
134
+ border-radius: 8px;
135
+ overflow-x: auto;
136
+ white-space: pre-wrap;
137
+ word-wrap: break-word;
138
+ max-height: 300px;
139
+ overflow-y: auto;
140
+ margin-top: 10px;
141
+ }
142
+
143
+ .api-response.error {
144
+ color: #ef5350;
145
+ }
146
+
147
+ .info-box {
148
+ background: #e3f2fd;
149
+ border-left: 4px solid #2196f3;
150
+ padding: 15px;
151
+ margin: 20px 0;
152
+ border-radius: 4px;
153
+ }
154
+
155
+ .info-box h3 {
156
+ margin-top: 0;
157
+ color: #1976d2;
158
+ }
159
+
160
+ .info-box code {
161
+ background: #fff;
162
+ padding: 2px 6px;
163
+ border-radius: 3px;
164
+ font-family: 'Roboto Mono', monospace;
165
+ }
166
+
167
+ .button-group {
168
+ display: flex;
169
+ gap: 10px;
170
+ margin: 15px 0;
171
+ }
172
+
173
+ .demo-button.secondary {
174
+ background: #6c757d;
175
+ }
176
+
177
+ .demo-button.secondary:hover {
178
+ background: #5a6268;
179
+ }
180
+
181
+ .demo-button.success {
182
+ background: #4caf50;
183
+ }
184
+
185
+ .demo-button.success:hover {
186
+ background: #45a049;
187
+ }
188
+
189
+ @media (max-width: 768px) {
190
+ .config-grid {
191
+ grid-template-columns: 1fr;
192
+ }
193
+
194
+ .totp-code {
195
+ font-size: 36px;
196
+ letter-spacing: 6px;
197
+ padding: 20px;
198
+ }
199
+ }
200
+ </style>
201
+ </head>
202
+ <body>
203
+ <div class="demo-container">
204
+ <div class="demo-header">
205
+ <h1>🔐 TOTP Client Demo</h1>
206
+ <p>Time-based One-Time Password Generator and API Client</p>
207
+ </div>
208
+
209
+ <div id="status-container"></div>
210
+
211
+ <div class="demo-grid">
212
+ <!-- TOTP Generator Panel -->
213
+ <div class="demo-panel">
214
+ <h2>
215
+ <span class="material-icons">vpn_key</span>
216
+ TOTP Generator
217
+ </h2>
218
+
219
+ <div class="info-box">
220
+ <h3>What is TOTP?</h3>
221
+ <p>
222
+ TOTP generates temporary codes that change every 30 seconds. Enter
223
+ your Base32 secret to generate codes compatible with Google
224
+ Authenticator, Authy, and other authenticator apps.
225
+ </p>
226
+ </div>
227
+
228
+ <label
229
+ for="secret-input"
230
+ style="font-weight: 500; margin-bottom: 5px; display: block"
231
+ >
232
+ Base32 Secret
233
+ </label>
234
+ <input
235
+ type="text"
236
+ id="secret-input"
237
+ class="secret-input"
238
+ placeholder="JBSWY3DPEHPK3PXP"
239
+ value="JBSWY3DPEHPK3PXP"
240
+ />
241
+
242
+ <div class="config-grid">
243
+ <div class="config-field">
244
+ <label for="digits-input">Digits</label>
245
+ <select id="digits-input">
246
+ <option value="6" selected>6 digits</option>
247
+ <option value="8">8 digits</option>
248
+ <option value="10">10 digits</option>
249
+ </select>
250
+ </div>
251
+ <div class="config-field">
252
+ <label for="step-input">Time Step (seconds)</label>
253
+ <input
254
+ type="number"
255
+ id="step-input"
256
+ value="30"
257
+ min="10"
258
+ max="120"
259
+ />
260
+ </div>
261
+ </div>
262
+
263
+ <div class="button-group">
264
+ <button id="btn-generate" class="demo-button success">
265
+ <span class="material-icons" style="vertical-align: middle">
266
+ refresh
267
+ </span>
268
+ Generate Code
269
+ </button>
270
+ <button id="btn-copy" class="demo-button secondary">
271
+ <span class="material-icons" style="vertical-align: middle">
272
+ content_copy
273
+ </span>
274
+ Copy Code
275
+ </button>
276
+ </div>
277
+
278
+ <div id="totp-display">
279
+ <div class="totp-code" id="totp-code">------</div>
280
+ <div class="countdown-bar">
281
+ <div class="countdown-bar-fill" id="countdown-bar"></div>
282
+ </div>
283
+ <div class="countdown" id="countdown-text">
284
+ Waiting for code generation...
285
+ </div>
286
+ </div>
287
+ </div>
288
+
289
+ <!-- API Client Panel -->
290
+ <div class="demo-panel">
291
+ <h2>
292
+ <span class="material-icons">http</span>
293
+ API Client Test
294
+ </h2>
295
+
296
+ <div class="info-box">
297
+ <h3>API Authentication</h3>
298
+ <p>
299
+ The TOTP client automatically adds the current code to the
300
+ <code>Authorization: Bearer</code> header for each request.
301
+ </p>
302
+ </div>
303
+
304
+ <div class="api-test">
305
+ <h3>Test API Request</h3>
306
+ <p>
307
+ Test the TOTP authentication by making a request to the LDAP REST
308
+ API.
309
+ </p>
310
+
311
+ <div class="config-field">
312
+ <label for="api-endpoint">API Endpoint</label>
313
+ <input
314
+ type="text"
315
+ id="api-endpoint"
316
+ value="/api/hello"
317
+ placeholder="/api/v1/ldap/users"
318
+ />
319
+ </div>
320
+
321
+ <div class="button-group">
322
+ <button id="btn-test-api" class="demo-button">
323
+ <span class="material-icons" style="vertical-align: middle">
324
+ send
325
+ </span>
326
+ Test Request
327
+ </button>
328
+ </div>
329
+
330
+ <div id="api-response-container" style="display: none">
331
+ <h4>Response:</h4>
332
+ <div id="api-response" class="api-response"></div>
333
+ </div>
334
+ </div>
335
+ </div>
336
+ </div>
337
+
338
+ <!-- Usage Examples Panel -->
339
+ <div class="demo-panel" style="margin-top: 20px">
340
+ <h2>
341
+ <span class="material-icons">code</span>
342
+ Usage Examples
343
+ </h2>
344
+
345
+ <h3>Installation</h3>
346
+ <div class="api-response" style="margin-bottom: 20px">
347
+ npm install ldap-rest # or yarn add ldap-rest
348
+ </div>
349
+
350
+ <h3>Import the TOTP Client</h3>
351
+ <div class="api-response" style="margin-bottom: 20px">
352
+ import { generateTotp, TotpAuthClient } from
353
+ 'ldap-rest/browser-shared-utils-totp';
354
+ </div>
355
+
356
+ <h3>Generate TOTP Code</h3>
357
+ <div class="api-response" style="margin-bottom: 20px">
358
+ // Generate a TOTP code const code = await generateTotp({ secret:
359
+ 'JBSWY3DPEHPK3PXP', digits: 6, step: 30 }); console.log('Current TOTP
360
+ code:', code);
361
+ </div>
362
+
363
+ <h3>Use TotpAuthClient for API Requests</h3>
364
+ <div class="api-response" style="margin-bottom: 20px">
365
+ // Create authenticated client const client = new TotpAuthClient({
366
+ secret: 'JBSWY3DPEHPK3PXP', digits: 6, step: 30 }); // Make
367
+ authenticated requests const users = await
368
+ client.get('/api/v1/ldap/users'); const userData = await users.json();
369
+ // POST with automatic authentication await
370
+ client.post('/api/v1/ldap/users', { uid: 'user1', mail:
371
+ 'user1@example.com' }); // Other methods: put, patch, delete await
372
+ client.put('/api/v1/ldap/users/user1', { mail: 'new@example.com' });
373
+ await client.delete('/api/v1/ldap/users/user1');
374
+ </div>
375
+
376
+ <h3>Helper Functions</h3>
377
+ <div class="api-response">
378
+ import { getRemainingSeconds, isValidBase32 } from
379
+ 'ldap-rest/browser-shared-utils-totp'; // Check how much time until
380
+ next code const remaining = getRemainingSeconds(30); console.log(`Code
381
+ expires in ${remaining} seconds`); // Validate Base32 secret if
382
+ (isValidBase32('JBSWY3DPEHPK3PXP')) { console.log('Valid secret!'); }
383
+ </div>
384
+ </div>
385
+ </div>
386
+
387
+ <script type="module">
388
+ import {
389
+ generateTotp,
390
+ TotpAuthClient,
391
+ getRemainingSeconds,
392
+ isValidBase32,
393
+ } from '/static/browser/shared/utils/totp.js';
394
+ import { showStatus } from '/static/browser/shared/components/StatusMessage.js';
395
+
396
+ const secretInput = document.getElementById('secret-input');
397
+ const digitsInput = document.getElementById('digits-input');
398
+ const stepInput = document.getElementById('step-input');
399
+ const totpCodeEl = document.getElementById('totp-code');
400
+ const countdownBar = document.getElementById('countdown-bar');
401
+ const countdownText = document.getElementById('countdown-text');
402
+ const btnGenerate = document.getElementById('btn-generate');
403
+ const btnCopy = document.getElementById('btn-copy');
404
+ const btnTestApi = document.getElementById('btn-test-api');
405
+ const apiEndpoint = document.getElementById('api-endpoint');
406
+ const apiResponseContainer = document.getElementById(
407
+ 'api-response-container'
408
+ );
409
+ const apiResponse = document.getElementById('api-response');
410
+
411
+ let intervalId = null;
412
+ let currentCode = '';
413
+
414
+ async function updateTotpCode() {
415
+ const secret = secretInput.value.trim().toUpperCase();
416
+ const digits = parseInt(digitsInput.value);
417
+ const step = parseInt(stepInput.value);
418
+
419
+ if (!secret) {
420
+ showStatus('error', 'Please enter a Base32 secret');
421
+ return;
422
+ }
423
+
424
+ if (!isValidBase32(secret)) {
425
+ showStatus('error', 'Invalid Base32 secret (use A-Z and 2-7)');
426
+ totpCodeEl.textContent = 'ERROR';
427
+ totpCodeEl.classList.add('expired');
428
+ return;
429
+ }
430
+
431
+ try {
432
+ const code = await generateTotp({ secret, digits, step });
433
+ currentCode = code;
434
+ totpCodeEl.textContent = code;
435
+ totpCodeEl.classList.remove('expired');
436
+
437
+ // Update countdown
438
+ const remaining = getRemainingSeconds(step);
439
+ const percentage = (remaining / step) * 100;
440
+ countdownBar.style.width = `${percentage}%`;
441
+ countdownText.textContent = `Expires in ${remaining} seconds`;
442
+
443
+ // Mark as expired if less than 3 seconds
444
+ if (remaining <= 3) {
445
+ totpCodeEl.classList.add('expired');
446
+ }
447
+ } catch (error) {
448
+ showStatus('error', `Error generating code: ${error.message}`);
449
+ totpCodeEl.textContent = 'ERROR';
450
+ totpCodeEl.classList.add('expired');
451
+ }
452
+ }
453
+
454
+ function startAutoRefresh() {
455
+ if (intervalId) {
456
+ clearInterval(intervalId);
457
+ }
458
+
459
+ updateTotpCode();
460
+ intervalId = setInterval(updateTotpCode, 1000);
461
+ }
462
+
463
+ btnGenerate.addEventListener('click', () => {
464
+ startAutoRefresh();
465
+ showStatus('success', 'TOTP code generated and auto-refresh started');
466
+ });
467
+
468
+ btnCopy.addEventListener('click', async () => {
469
+ if (
470
+ currentCode &&
471
+ currentCode !== '------' &&
472
+ currentCode !== 'ERROR'
473
+ ) {
474
+ try {
475
+ await navigator.clipboard.writeText(currentCode);
476
+ showStatus('success', `Code ${currentCode} copied to clipboard!`);
477
+ } catch (error) {
478
+ showStatus('error', 'Failed to copy to clipboard');
479
+ }
480
+ } else {
481
+ showStatus('error', 'No valid code to copy');
482
+ }
483
+ });
484
+
485
+ btnTestApi.addEventListener('click', async () => {
486
+ const secret = secretInput.value.trim().toUpperCase();
487
+ const digits = parseInt(digitsInput.value);
488
+ const step = parseInt(stepInput.value);
489
+ const endpoint = apiEndpoint.value;
490
+
491
+ if (!secret) {
492
+ showStatus('error', 'Please enter a Base32 secret first');
493
+ return;
494
+ }
495
+
496
+ if (!isValidBase32(secret)) {
497
+ showStatus('error', 'Invalid Base32 secret');
498
+ return;
499
+ }
500
+
501
+ try {
502
+ showStatus('info', 'Sending request...');
503
+
504
+ const client = new TotpAuthClient({ secret, digits, step });
505
+ const response = await client.get(endpoint);
506
+
507
+ apiResponseContainer.style.display = 'block';
508
+
509
+ const data = await response.json();
510
+ apiResponse.textContent = JSON.stringify(data, null, 2);
511
+ apiResponse.classList.remove('error');
512
+
513
+ if (response.ok) {
514
+ showStatus(
515
+ 'success',
516
+ `API request successful (${response.status})`
517
+ );
518
+ } else {
519
+ showStatus('warning', `API returned status ${response.status}`);
520
+ apiResponse.classList.add('error');
521
+ }
522
+ } catch (error) {
523
+ apiResponseContainer.style.display = 'block';
524
+ apiResponse.textContent = `Error: ${error.message}`;
525
+ apiResponse.classList.add('error');
526
+ showStatus('error', `Request failed: ${error.message}`);
527
+ }
528
+ });
529
+
530
+ // Auto-start on load
531
+ startAutoRefresh();
532
+
533
+ // Clean up on page unload
534
+ window.addEventListener('beforeunload', () => {
535
+ if (intervalId) {
536
+ clearInterval(intervalId);
537
+ }
538
+ });
539
+ </script>
540
+ </body>
541
+ </html>