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.
- package/README.md +38 -0
- package/bin/sync-app-accounts.mjs +276 -0
- package/dist/abstract/ldapFlat.js +1 -1
- package/dist/abstract/plugin.js +1 -1
- package/dist/bin/index.js +1 -1
- package/dist/config/args.js +1 -1
- package/dist/lib/ldapActions.js +1 -1
- package/dist/lib/utils.js +1 -1
- package/dist/logger/winston.js +1 -1
- package/dist/plugins/auth/hmac.js +1 -0
- package/dist/plugins/auth/totp.js +1 -0
- package/dist/plugins/demo/helloworld.js +1 -1
- package/dist/plugins/ldap/groups.js +1 -1
- package/dist/plugins/ldap/onChange.js +1 -1
- package/dist/plugins/static.js +1 -1
- package/dist/plugins/twake/appAccountsApi.js +1 -0
- package/dist/plugins/twake/appAccountsConsistency.js +1 -0
- package/dist/plugins/twake/james.js +1 -1
- package/dist/plugins/weblogs.js +1 -1
- package/dist/src/abstract/ldapFlat.d.ts +0 -5
- package/dist/src/abstract/plugin.d.ts +1 -0
- package/dist/src/bin/index.d.ts +1 -0
- package/dist/src/browser/shared/index.d.ts +1 -0
- package/dist/src/browser/shared/utils/hmac.d.ts +73 -0
- package/dist/src/browser/shared/utils/totp.d.ts +64 -0
- package/dist/src/config/args.d.ts +9 -1
- package/dist/src/hooks.d.ts +2 -2
- package/dist/src/lib/ldapActions.d.ts +3 -7
- package/dist/src/lib/utils.d.ts +17 -0
- package/dist/src/logger/winston.d.ts +5 -0
- package/dist/src/plugins/auth/hmac.d.ts +24 -0
- package/dist/src/plugins/auth/totp.d.ts +35 -0
- package/dist/src/plugins/ldap/onChange.d.ts +1 -0
- package/dist/src/plugins/static.d.ts +4 -0
- package/dist/src/plugins/twake/appAccountsApi.d.ts +52 -0
- package/dist/src/plugins/twake/appAccountsConsistency.d.ts +58 -0
- package/docs/DEVELOPER_GUIDE.md +1 -0
- package/docs/PLUGIN_DEPENDENCIES.md +19 -0
- package/docs/README.md +74 -30
- package/docs/authentication.md +600 -12
- package/docs/plugins/DEVELOPMENT.md +148 -76
- package/docs/testing-with-embedded-ldap.md +342 -0
- package/docs/twakeAppAccountsApi.md +485 -0
- package/docs/weblogs.md +36 -8
- package/examples/web/README.md +166 -0
- package/examples/web/ldap-tree-viewer.html +16 -2
- package/examples/web/totp-client.html +543 -0
- package/package.json +46 -1
- package/static/browser/shared/index.js +1 -0
- package/static/browser/shared/index.js.map +1 -1
- package/static/browser/shared/utils/hmac.js +202 -0
- package/static/browser/shared/utils/hmac.js.map +1 -0
- package/static/browser/shared/utils/totp.js +182 -0
- package/static/browser/shared/utils/totp.js.map +1 -0
- package/static/browser/shared.esm.js +181 -1
- package/static/browser/shared.esm.js.map +1 -1
- package/static/browser/shared.js +184 -0
- package/static/browser/shared.js.map +1 -1
- package/static/browser/shared.min.js +1 -1
- package/static/browser/shared.min.js.map +1 -1
- package/static/schemas/obm/organizations.json +24 -0
- package/static/schemas/obm/users.json +192 -0
package/docs/authentication.md
CHANGED
|
@@ -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. **
|
|
9
|
-
3. **
|
|
10
|
-
4. **
|
|
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
|
-
| **
|
|
361
|
-
| **
|
|
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
|