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
@@ -5,9 +5,11 @@ LDAP-Rest provides multiple authentication plugins to secure API access. These p
5
5
  ## Available Authentication Methods
6
6
 
7
7
  1. **Token Authentication** (`core/auth/token`) - Simple bearer token authentication
8
- 2. **LemonLDAP::NG** (`core/auth/llng`) - Integration with LemonLDAP::NG SSO
9
- 3. **OpenID Connect** (`core/auth/openidconnect`) - OAuth 2.0 / OpenID Connect authentication
10
- 4. **Authorization Per Branch** (`core/auth/authzPerBranch`) - Branch-level access control (see [authzPerBranch.md](authzPerBranch.md))
8
+ 2. **TOTP Authentication** (`core/auth/totp`) - Time-based One-Time Password authentication
9
+ 3. **HMAC Authentication** (`core/auth/hmac`) - HMAC-SHA256 request signing for backend services
10
+ 4. **LemonLDAP::NG** (`core/auth/llng`) - Integration with LemonLDAP::NG SSO
11
+ 5. **OpenID Connect** (`core/auth/openidconnect`) - OAuth 2.0 / OpenID Connect authentication
12
+ 6. **Authorization Per Branch** (`core/auth/authzPerBranch`) - Branch-level access control (see [authzPerBranch.md](authzPerBranch.md))
11
13
 
12
14
  ## Token Authentication
13
15
 
@@ -135,6 +137,514 @@ You can mix named and unnamed tokens for backward compatibility:
135
137
  --auth-token "ghi789:staging-app"
136
138
  ```
137
139
 
140
+ ## TOTP Authentication
141
+
142
+ Time-based One-Time Password (TOTP) authentication using dynamic codes compatible with authenticator apps.
143
+
144
+ ### Configuration
145
+
146
+ #### Basic Configuration
147
+
148
+ ```bash
149
+ --plugin core/auth/totp \
150
+ --auth-totp "JBSWY3DPEHPK3PXP:admin:6" \
151
+ --auth-totp "HXDMVJECJJWSRB3H:service:8"
152
+ ```
153
+
154
+ **Environment Variable:**
155
+
156
+ ```bash
157
+ DM_AUTH_TOTP="JBSWY3DPEHPK3PXP:admin:6,HXDMVJECJJWSRB3H:service:8"
158
+ ```
159
+
160
+ **Syntax:** `secret:name[:digits]`
161
+
162
+ - **secret**: Base32-encoded secret (e.g., generated by authenticator apps)
163
+ - **name**: Descriptive user/service name
164
+ - **digits**: Number of digits in TOTP code (optional, 6-10, default: 6)
165
+
166
+ #### Advanced Configuration
167
+
168
+ ```bash
169
+ --auth-totp-window 1 # Time window tolerance (±30s with step=30)
170
+ --auth-totp-step 30 # Time step in seconds (default: 30)
171
+ ```
172
+
173
+ **Environment Variables:**
174
+
175
+ ```bash
176
+ DM_AUTH_TOTP_WINDOW=1 # Default: 1
177
+ DM_AUTH_TOTP_STEP=30 # Default: 30
178
+ ```
179
+
180
+ - **window**: Number of time steps to check before/after current time (compensates for clock drift)
181
+ - **step**: Time interval in seconds for code generation (typically 30)
182
+
183
+ ### Generating Secrets
184
+
185
+ Use an authenticator app or generate Base32 secrets:
186
+
187
+ ```bash
188
+ # Using Node.js crypto (example)
189
+ node -e "console.log(require('crypto').randomBytes(20).toString('base32'))"
190
+ ```
191
+
192
+ ### Usage
193
+
194
+ #### HTTP Requests
195
+
196
+ Include the current TOTP code in the `Authorization` header:
197
+
198
+ ```bash
199
+ # Get current TOTP code from your authenticator app
200
+ curl -H "Authorization: Bearer 123456" \
201
+ http://localhost:8081/api/v1/ldap/users
202
+ ```
203
+
204
+ #### Browser Client
205
+
206
+ The TOTP client library is available as an npm module export:
207
+
208
+ ```bash
209
+ npm install ldap-rest
210
+ ```
211
+
212
+ ```typescript
213
+ import {
214
+ TotpAuthClient,
215
+ generateTotp,
216
+ getRemainingSeconds,
217
+ isValidBase32,
218
+ } from 'ldap-rest/browser-shared-utils-totp';
219
+
220
+ const client = new TotpAuthClient({
221
+ secret: 'JBSWY3DPEHPK3PXP',
222
+ digits: 6,
223
+ step: 30,
224
+ });
225
+
226
+ // Automatic authentication with current TOTP code
227
+ const response = await client.get('/api/v1/ldap/users');
228
+ const users = await response.json();
229
+
230
+ // Or get current code manually
231
+ const code = await client.getCode();
232
+ console.log(`Current TOTP code: ${code}`);
233
+ ```
234
+
235
+ **Live Demo:** See [examples/web/totp-client.html](../../examples/web/totp-client.html) for an interactive demonstration.
236
+
237
+ ### How It Works
238
+
239
+ 1. Extracts TOTP code from `Authorization: Bearer <code>` header
240
+ 2. Generates expected TOTP codes for current time window
241
+ 3. Validates code against all configured users' secrets
242
+ 4. Supports multiple time windows (±window × step seconds) for clock drift tolerance
243
+ 5. Sets `req.user` to the user's name on successful validation
244
+ 6. Returns 401 Unauthorized if code is missing, invalid, or expired
245
+
246
+ ### Multi-User Example
247
+
248
+ ```bash
249
+ # Configure multiple users with different code lengths
250
+ --plugin core/auth/totp \
251
+ --auth-totp "JBSWY3DPEHPK3PXP:admin:6" \
252
+ --auth-totp "HXDMVJECJJWSRB3H:api-service:8" \
253
+ --auth-totp "IXDMVJECJJWSRB2A:monitoring:10"
254
+ ```
255
+
256
+ **In logs:**
257
+
258
+ ```
259
+ INFO: TOTP authentication successful for user: admin
260
+ INFO: TOTP authentication successful for user: api-service
261
+ ```
262
+
263
+ ### Use Cases
264
+
265
+ - **Enhanced Security**: Dynamic codes that expire every 30 seconds
266
+ - **No Shared Secrets**: Each user/service has unique TOTP secret
267
+ - **Compatible**: Works with Google Authenticator, Authy, 1Password, etc.
268
+ - **API Access**: Suitable for automated scripts with TOTP generation
269
+ - **Multi-Factor**: Can be combined with other authentication methods
270
+
271
+ ### Security Considerations
272
+
273
+ - **Secret Storage**: Store Base32 secrets securely (encrypted configuration, environment variables)
274
+ - **HTTPS Required**: Always use HTTPS to protect TOTP codes in transit
275
+ - **Clock Synchronization**: Ensure server and client clocks are synchronized (NTP recommended)
276
+ - **Window Size**: Larger windows are more tolerant but slightly less secure
277
+ - **Secret Rotation**: Rotate TOTP secrets periodically for better security
278
+ - **Rate Limiting**: Consider adding rate limiting to prevent brute force attacks
279
+
280
+ ### Browser Client Features
281
+
282
+ The TOTP browser library provides these functions and classes:
283
+
284
+ ```typescript
285
+ import {
286
+ generateTotp,
287
+ getRemainingSeconds,
288
+ isValidBase32,
289
+ TotpAuthClient
290
+ } from 'ldap-rest/browser-shared-utils-totp';
291
+
292
+ // Generate TOTP code
293
+ const code = await generateTotp({
294
+ secret: 'JBSWY3DPEHPK3PXP',
295
+ digits: 6,
296
+ step: 30
297
+ });
298
+
299
+ // Check validity time
300
+ const remaining = getRemainingSeconds(30);
301
+ console.log(`Code expires in ${remaining} seconds`);
302
+
303
+ // Validate secret format
304
+ if (isValidBase32('JBSWY3DPEHPK3PXP')) {
305
+ console.log('Valid Base32 secret');
306
+ }
307
+
308
+ // HTTP client with automatic TOTP authentication
309
+ const client = new TotpAuthClient({
310
+ secret: 'JBSWY3DPEHPK3PXP',
311
+ digits: 6,
312
+ step: 30
313
+ });
314
+
315
+ // All HTTP methods automatically include TOTP in Authorization header
316
+ await client.post('/api/v1/ldap/users', { uid: 'user1', ... });
317
+ await client.put('/api/v1/ldap/users/user1', { mail: 'new@example.com' });
318
+ await client.delete('/api/v1/ldap/users/user1');
319
+ ```
320
+
321
+ **Module Export:** `ldap-rest/browser-shared-utils-totp`
322
+
323
+ **Live Demo:** Run the server and open [http://localhost:8081/static/examples/web/totp-client.html](http://localhost:8081/static/examples/web/totp-client.html)
324
+
325
+ ### Troubleshooting
326
+
327
+ **Problem:** 401 Unauthorized despite correct code
328
+
329
+ **Solutions:**
330
+
331
+ 1. Verify secret is correctly configured (Base32 format)
332
+ 2. Check system clock synchronization (use NTP)
333
+ 3. Increase `--auth-totp-window` if experiencing timing issues
334
+ 4. Ensure code is used immediately (codes expire every 30s)
335
+ 5. Verify `Authorization: Bearer {code}` format
336
+
337
+ **Problem:** Codes don't match authenticator app
338
+
339
+ **Solutions:**
340
+
341
+ 1. Verify secret matches exactly (case-sensitive Base32)
342
+ 2. Check `--auth-totp-step` matches app configuration (usually 30)
343
+ 3. Ensure both server and device clocks are synchronized
344
+ 4. Confirm correct number of digits configured
345
+
346
+ **Problem:** Invalid Base32 secret warning
347
+
348
+ **Solutions:**
349
+
350
+ 1. Secret must contain only A-Z and 2-7 characters
351
+ 2. Use proper Base32 encoding (not Base64)
352
+ 3. Remove padding `=` characters if present (optional)
353
+
354
+ ## HMAC Authentication
355
+
356
+ HMAC-SHA256 request signing authentication for backend services (Registration Service, admin panel backend, cloudery, etc.).
357
+
358
+ ### Configuration
359
+
360
+ #### Basic Configuration
361
+
362
+ ```bash
363
+ --plugin core/auth/hmac \
364
+ --auth-hmac "registration-service:secret-key-minimum-32-chars:Registration Service" \
365
+ --auth-hmac "cloudery:another-secret-key-long-enough:Cloudery Backend"
366
+ ```
367
+
368
+ **Environment Variable:**
369
+
370
+ ```bash
371
+ DM_AUTH_HMAC="registration-service:secret-key-minimum-32-chars:Registration Service,cloudery:another-secret-key-long-enough:Cloudery Backend"
372
+ ```
373
+
374
+ **Syntax:** `service-id:secret:name`
375
+
376
+ - **service-id**: Service identifier (e.g., `registration-service`, `cloudery`)
377
+ - **secret**: Shared secret for HMAC (minimum 32 characters recommended)
378
+ - **name**: Descriptive service name (can contain colons)
379
+
380
+ #### Advanced Configuration
381
+
382
+ ```bash
383
+ --auth-hmac-window 120000 # Time window in milliseconds (default: 120000 = 2 minutes)
384
+ ```
385
+
386
+ **Environment Variable:**
387
+
388
+ ```bash
389
+ DM_AUTH_HMAC_WINDOW=120000 # Default: 120000ms (2 minutes)
390
+ ```
391
+
392
+ - **window**: Maximum time difference (in milliseconds) between server and client timestamp
393
+ - Prevents replay attacks by rejecting requests with timestamps outside the window
394
+
395
+ ### How It Works
396
+
397
+ #### Request Signing Process
398
+
399
+ 1. **Client calculates signature:**
400
+
401
+ ```
402
+ signature = HMAC-SHA256(
403
+ secret,
404
+ "METHOD|PATH|timestamp|body-hash"
405
+ )
406
+ ```
407
+
408
+ Where:
409
+ - `METHOD`: HTTP method (GET, POST, PATCH, DELETE, PUT, etc.)
410
+ - `PATH`: Request path with query string (e.g., `/api/v1/ldap/users?filter=active`)
411
+ - `timestamp`: Unix timestamp in milliseconds (e.g., `1698765432000`)
412
+ - `body-hash`: SHA256(request_body) for POST/PATCH/PUT, empty string for GET/DELETE/HEAD
413
+
414
+ 2. **Client sends Authorization header:**
415
+
416
+ ```
417
+ Authorization: HMAC-SHA256 service-id:timestamp:signature
418
+ ```
419
+
420
+ 3. **Server validates:**
421
+ - Extracts `service-id`, `timestamp`, and `signature` from header
422
+ - Verifies timestamp is within configured window (prevents replay attacks)
423
+ - Retrieves service secret using `service-id`
424
+ - Recalculates signature using same method
425
+ - Compares signatures using constant-time comparison (prevents timing attacks)
426
+ - Rejects if mismatch or timestamp expired
427
+
428
+ 4. **On success:**
429
+ - Sets `req.user` to service name
430
+ - Allows request to proceed
431
+
432
+ ### Usage
433
+
434
+ #### Manual Request Example
435
+
436
+ ```bash
437
+ # Example: GET request
438
+ SERVICE_ID="registration-service"
439
+ SECRET="secret-key-minimum-32-chars"
440
+ METHOD="GET"
441
+ PATH="/api/v1/ldap/users"
442
+ TIMESTAMP=$(date +%s000) # Unix timestamp in milliseconds
443
+
444
+ # Calculate body hash (empty for GET)
445
+ BODY_HASH=""
446
+
447
+ # Create signing string
448
+ SIGNING_STRING="${METHOD}|${PATH}|${TIMESTAMP}|${BODY_HASH}"
449
+
450
+ # Calculate HMAC-SHA256 signature
451
+ SIGNATURE=$(echo -n "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
452
+
453
+ # Make request
454
+ curl -H "Authorization: HMAC-SHA256 ${SERVICE_ID}:${TIMESTAMP}:${SIGNATURE}" \
455
+ http://localhost:8081${PATH}
456
+ ```
457
+
458
+ #### POST Request with Body
459
+
460
+ ```bash
461
+ SERVICE_ID="registration-service"
462
+ SECRET="secret-key-minimum-32-chars"
463
+ METHOD="POST"
464
+ PATH="/api/v1/ldap/users"
465
+ TIMESTAMP=$(date +%s000)
466
+ BODY='{"uid":"user1","mail":"user1@example.com"}'
467
+
468
+ # Calculate body hash
469
+ BODY_HASH=$(echo -n "$BODY" | openssl dgst -sha256 | cut -d' ' -f2)
470
+
471
+ # Create signing string
472
+ SIGNING_STRING="${METHOD}|${PATH}|${TIMESTAMP}|${BODY_HASH}"
473
+
474
+ # Calculate signature
475
+ SIGNATURE=$(echo -n "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
476
+
477
+ # Make request
478
+ curl -X POST \
479
+ -H "Authorization: HMAC-SHA256 ${SERVICE_ID}:${TIMESTAMP}:${SIGNATURE}" \
480
+ -H "Content-Type: application/json" \
481
+ -d "$BODY" \
482
+ http://localhost:8081${PATH}
483
+ ```
484
+
485
+ #### Browser Client
486
+
487
+ The HMAC client library is available as an npm module export:
488
+
489
+ ```bash
490
+ npm install ldap-rest
491
+ ```
492
+
493
+ ```typescript
494
+ import { HmacAuthClient } from 'ldap-rest/browser-shared-utils-hmac';
495
+
496
+ const client = new HmacAuthClient({
497
+ serviceId: 'registration-service',
498
+ secret: 'secret-key-minimum-32-chars',
499
+ });
500
+
501
+ // Automatic authentication with HMAC signature
502
+ const response = await client.get('/api/v1/ldap/users');
503
+ const users = await response.json();
504
+
505
+ // POST with automatic body hashing and signing
506
+ await client.post('/api/v1/ldap/users', {
507
+ uid: 'user1',
508
+ mail: 'user1@example.com',
509
+ });
510
+
511
+ // Other methods: put, patch, delete
512
+ await client.put('/api/v1/ldap/users/user1', { mail: 'new@example.com' });
513
+ await client.delete('/api/v1/ldap/users/user1');
514
+ ```
515
+
516
+ **Manual signature generation:**
517
+
518
+ ```typescript
519
+ import { generateHmacSignature } from 'ldap-rest/browser-shared-utils-hmac';
520
+
521
+ const signature = await generateHmacSignature(
522
+ 'secret-key-minimum-32-chars',
523
+ 'GET',
524
+ '/api/v1/ldap/users',
525
+ Date.now(),
526
+ undefined // no body for GET
527
+ );
528
+
529
+ const authHeader = `HMAC-SHA256 registration-service:${Date.now()}:${signature}`;
530
+ ```
531
+
532
+ ### Use Cases
533
+
534
+ - **Backend Services**: Service-to-service authentication (Registration Service, admin panels, etc.)
535
+ - **Microservices**: Secure inter-service communication
536
+ - **Webhooks**: Verify request authenticity and integrity
537
+ - **API Gateways**: Backend service authentication
538
+ - **Cloudery Integration**: Secure communication with cloudery backend
539
+ - **Automated Services**: CI/CD, monitoring, backup services
540
+
541
+ ### Security Considerations
542
+
543
+ - **Secret Strength**: Use secrets of at least 32 characters (64+ recommended)
544
+ - **Secret Storage**: Store secrets securely (encrypted configuration, environment variables, secret managers)
545
+ - **HTTPS Required**: Always use HTTPS to protect signatures in transit
546
+ - **Time Synchronization**: Ensure server and client clocks are synchronized (NTP recommended)
547
+ - **Replay Protection**: Time window prevents replay attacks (adjust `auth-hmac-window` as needed)
548
+ - **Constant-Time Comparison**: Signature validation uses `timingSafeEqual` to prevent timing attacks
549
+ - **Secret Rotation**: Rotate secrets periodically for enhanced security
550
+ - **Body Integrity**: Body hash ensures request payload hasn't been tampered with
551
+ - **Path Integrity**: Full path (including query parameters) is signed
552
+
553
+ ### Security Features
554
+
555
+ 1. **Replay Attack Prevention**: Timestamp validation with configurable window
556
+ 2. **Timing Attack Protection**: Constant-time signature comparison
557
+ 3. **Request Integrity**: Body hash ensures payload integrity
558
+ 4. **Path Integrity**: Query parameters included in signature
559
+ 5. **Multi-Service Support**: Isolated secrets per service
560
+ 6. **Clock Drift Tolerance**: Configurable time window (default 2 minutes)
561
+
562
+ ### Advantages Over Other Methods
563
+
564
+ | Feature | HMAC | Token | TOTP |
565
+ | ------------------------- | ------------------ | -------------- | ------------------- |
566
+ | **Request Integrity** | ✅ Yes (body hash) | ❌ No | ❌ No |
567
+ | **Replay Protection** | ✅ Yes (timestamp) | ❌ No | ✅ Yes (time-based) |
568
+ | **Signature Per Request** | ✅ Yes | ❌ No (static) | ✅ Yes (30s codes) |
569
+ | **Body Tampering Detect** | ✅ Yes | ❌ No | ❌ No |
570
+ | **Path/Query Protection** | ✅ Yes | ❌ No | ❌ No |
571
+ | **Setup Complexity** | Medium | Simple | Simple |
572
+ | **Best For** | Backend services | Simple APIs | MFA, user auth |
573
+
574
+ ### Comparison with Standards
575
+
576
+ This implementation is similar to:
577
+
578
+ - **AWS Signature V4**: Similar approach but simpler (AWS is more complex with canonicalization)
579
+ - **HTTP Message Signatures (RFC 9421)**: IETF standard for HTTP signatures
580
+ - **HMAC-Based Authentication**: Industry-standard pattern for service authentication
581
+
582
+ Our approach provides a good balance between security and simplicity:
583
+
584
+ - ✅ Simpler than AWS Signature V4 (easier to implement)
585
+ - ✅ More secure than static tokens (request-specific signatures)
586
+ - ✅ Better integrity than TOTP (includes body and path hashing)
587
+ - ✅ Standard cryptography (HMAC-SHA256)
588
+
589
+ ### Troubleshooting
590
+
591
+ **Problem:** 401 Unauthorized despite correct signature
592
+
593
+ **Solutions:**
594
+
595
+ 1. Verify service-id matches exactly (case-sensitive)
596
+ 2. Verify secret is correctly configured (minimum 32 characters)
597
+ 3. Check system clock synchronization (use NTP: `ntpdate -q pool.ntp.org`)
598
+ 4. Increase `--auth-hmac-window` if experiencing timing issues
599
+ 5. Verify Authorization header format: `HMAC-SHA256 service-id:timestamp:signature`
600
+ 6. Check server logs for specific error messages
601
+ 7. Ensure timestamp is in milliseconds, not seconds
602
+ 8. Verify body hash calculation (must match exactly)
603
+
604
+ **Problem:** Timestamp outside allowed window
605
+
606
+ **Solutions:**
607
+
608
+ 1. Synchronize clocks using NTP
609
+ 2. Increase `--auth-hmac-window` (default: 120000ms = 2 minutes)
610
+ 3. Check if timestamp is in milliseconds (not seconds)
611
+ 4. Verify both server and client are using same timezone reference (UTC)
612
+
613
+ **Problem:** Signature mismatch
614
+
615
+ **Solutions:**
616
+
617
+ 1. Verify signing string format: `METHOD|PATH|timestamp|body-hash`
618
+ 2. Ensure METHOD is uppercase (GET, POST, etc.)
619
+ 3. Include full path with query parameters
620
+ 4. For POST/PATCH/PUT: verify body hash is SHA256(JSON.stringify(body))
621
+ 5. For GET/DELETE/HEAD: body-hash must be empty string
622
+ 6. Ensure secret is exactly the same on client and server
623
+ 7. Use hex encoding for signature (lowercase)
624
+ 8. Check for trailing whitespace in secret
625
+
626
+ **Problem:** Warning about short secrets
627
+
628
+ **Solutions:**
629
+
630
+ 1. Use secrets of at least 32 characters
631
+ 2. Recommended: 64+ characters for enhanced security
632
+ 3. Generate secure random secrets:
633
+ ```bash
634
+ openssl rand -hex 32 # 64-character hex string
635
+ node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
636
+ ```
637
+
638
+ **Debug Example:**
639
+
640
+ ```bash
641
+ # Enable debug logging
642
+ DM_LOG_LEVEL=debug npm start
643
+
644
+ # Check signing string in logs
645
+ # Expected format: "GET|/api/v1/ldap/users|1698765432000|"
646
+ ```
647
+
138
648
  ## LemonLDAP::NG Authentication
139
649
 
140
650
  Integration with [LemonLDAP::NG](https://lemonldap-ng.org/) (LLNG) Web SSO solution.
@@ -350,15 +860,18 @@ hooks: {
350
860
 
351
861
  ## Choosing an Authentication Method
352
862
 
353
- | Feature | Token | LemonLDAP::NG | OpenID Connect |
354
- | ---------------------- | ------------- | --------------------- | -------------------------------- |
355
- | **Setup Complexity** | Simple | Medium | Medium |
356
- | **User Management** | None | External (LLNG) | External (Provider) |
357
- | **SSO Support** | No | Yes | Yes |
358
- | **MFA Support** | No | Yes (via LLNG) | Yes (via Provider) |
359
- | **Session Management** | Stateless | LLNG Sessions | OIDC Sessions |
360
- | **Best For** | APIs, Scripts | Enterprises with LLNG | Cloud/SaaS, Enterprises with SSO |
361
- | **Dependencies** | None | lemonldap-ng-handler | express-openid-connect |
863
+ | Feature | Token | TOTP | HMAC | LemonLDAP::NG | OpenID Connect |
864
+ | ---------------------- | ------------- | ----------------- | ---------------------- | --------------------- | -------------------------------- |
865
+ | **Setup Complexity** | Simple | Simple | Medium | Medium | Medium |
866
+ | **User Management** | None | Manual | Manual (service-based) | External (LLNG) | External (Provider) |
867
+ | **SSO Support** | No | No | No | Yes | Yes |
868
+ | **MFA Support** | No | Yes (TOTP itself) | N/A | Yes (via LLNG) | Yes (via Provider) |
869
+ | **Session Management** | Stateless | Stateless | Stateless | LLNG Sessions | OIDC Sessions |
870
+ | **Code Expiration** | Never | 30-60 seconds | Per-request | Session-based | Session-based |
871
+ | **Request Integrity** | No | No | Yes (body + path hash) | No | No |
872
+ | **Replay Protection** | No | Yes (time-based) | Yes (timestamp) | Session-based | Session-based |
873
+ | **Best For** | APIs, Scripts | APIs, Enhanced | Backend Services | Enterprises with LLNG | Cloud/SaaS, Enterprises with SSO |
874
+ | **Dependencies** | None | None | None | lemonldap-ng-handler | express-openid-connect |
362
875
 
363
876
  ## Combining with Authorization
364
877
 
@@ -395,6 +908,53 @@ curl http://localhost:8081/api/v1/ldap/users
395
908
  # Returns: 401 Unauthorized
396
909
  ```
397
910
 
911
+ ### TOTP Authentication
912
+
913
+ ```bash
914
+ # Get current code from authenticator app (e.g., Google Authenticator)
915
+ # Code changes every 30 seconds
916
+
917
+ # Valid request with current code
918
+ curl -H "Authorization: Bearer 123456" \
919
+ http://localhost:8081/api/v1/ldap/users
920
+
921
+ # Invalid/expired code
922
+ curl -H "Authorization: Bearer 999999" \
923
+ http://localhost:8081/api/v1/ldap/users
924
+ # Returns: 401 Unauthorized
925
+
926
+ # Test with browser client
927
+ node -e "
928
+ import { generateTotp } from './src/browser/shared/utils/totp.js';
929
+ const code = await generateTotp({ secret: 'JBSWY3DPEHPK3PXP', digits: 6 });
930
+ console.log('Current TOTP code:', code);
931
+ "
932
+ ```
933
+
934
+ ### HMAC Authentication
935
+
936
+ ```bash
937
+ # Example: GET request with HMAC signature
938
+ SERVICE_ID="registration-service"
939
+ SECRET="secret-key-minimum-32-chars"
940
+ METHOD="GET"
941
+ PATH="/api/hello"
942
+ TIMESTAMP=$(date +%s000)
943
+
944
+ # Calculate signature
945
+ SIGNING_STRING="${METHOD}|${PATH}|${TIMESTAMP}|"
946
+ SIGNATURE=$(echo -n "$SIGNING_STRING" | openssl dgst -sha256 -hmac "$SECRET" | cut -d' ' -f2)
947
+
948
+ # Test request
949
+ curl -H "Authorization: HMAC-SHA256 ${SERVICE_ID}:${TIMESTAMP}:${SIGNATURE}" \
950
+ http://localhost:8081${PATH}
951
+
952
+ # Invalid signature should return 401
953
+ curl -H "Authorization: HMAC-SHA256 ${SERVICE_ID}:${TIMESTAMP}:invalid-sig" \
954
+ http://localhost:8081${PATH}
955
+ # Returns: 401 Unauthorized
956
+ ```
957
+
398
958
  ### LemonLDAP::NG
399
959
 
400
960
  Access through LLNG portal - authentication is handled by LLNG infrastructure.
@@ -416,6 +976,38 @@ Navigate to API in browser - will redirect to OIDC provider login page, then ret
416
976
  3. Verify no whitespace in token
417
977
  4. Check server logs for token mismatch
418
978
 
979
+ ### TOTP Authentication
980
+
981
+ **Problem:** 401 Unauthorized despite correct code from authenticator app
982
+
983
+ **Solutions:**
984
+
985
+ 1. Verify secret matches exactly (Base32, case-sensitive)
986
+ 2. Check system clock synchronization (use NTP: `ntpdate -q pool.ntp.org`)
987
+ 3. Increase `--auth-totp-window` if experiencing timing issues
988
+ 4. Ensure `--auth-totp-step` matches authenticator app (usually 30)
989
+ 5. Check server logs: `"Unauthorized TOTP token: {code}"`
990
+ 6. Verify `Authorization: Bearer {code}` format
991
+
992
+ **Problem:** Invalid Base32 secret warning in logs
993
+
994
+ **Solutions:**
995
+
996
+ 1. Secret must be valid Base32 (A-Z, 2-7 characters only)
997
+ 2. Remove any whitespace from secret
998
+ 3. Generate new secret if corrupted:
999
+ ```bash
1000
+ node -e "console.log(require('crypto').randomBytes(20).toString('base32'))"
1001
+ ```
1002
+
1003
+ **Problem:** Codes expire too quickly
1004
+
1005
+ **Solutions:**
1006
+
1007
+ 1. Increase `--auth-totp-window` (default: 1, try 2-3 for more tolerance)
1008
+ 2. This allows codes from previous/next time windows
1009
+ 3. Be aware: larger windows reduce security slightly
1010
+
419
1011
  ### LemonLDAP::NG
420
1012
 
421
1013
  **Problem:** Handler not found