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