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