@lifeaitools/clauth 0.3.5 → 0.3.7
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/cli/commands/serve.js +80 -2
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -110,6 +110,11 @@ function dashboardHtml(port, whitelist) {
|
|
|
110
110
|
.btn-cancel{background:transparent;color:#64748b;padding:6px 10px}.btn-cancel:hover{color:#94a3b8}
|
|
111
111
|
.btn-check{background:#0f2d2d;color:#34d399;border:1px solid #064e3b;padding:7px 16px;font-size:.85rem;border-radius:7px;cursor:pointer;font-weight:500;transition:all .15s}
|
|
112
112
|
.btn-check:hover{background:#134e4a;border-color:#34d399}.btn-check:disabled{opacity:.5;cursor:not-allowed}
|
|
113
|
+
.btn-enable{background:#14291a;color:#4ade80;border:1px solid #166534}.btn-enable:hover{background:#1a3d22;border-color:#4ade80}
|
|
114
|
+
.btn-disable{background:#2d1f1f;color:#f87171;border:1px solid #7f1d1d}.btn-disable:hover{background:#3d2525;border-color:#f87171}
|
|
115
|
+
.svc-badge{font-size:.7rem;font-weight:600;padding:2px 7px;border-radius:4px;letter-spacing:.4px;text-transform:uppercase}
|
|
116
|
+
.svc-badge.on{background:rgba(74,222,128,.12);color:#4ade80;border:1px solid rgba(74,222,128,.25)}
|
|
117
|
+
.svc-badge.off{background:rgba(248,113,113,.1);color:#f87171;border:1px solid rgba(248,113,113,.2)}
|
|
113
118
|
.status-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;opacity:0;transition:opacity .3s;margin-top:4px;cursor:default}
|
|
114
119
|
.status-dot.checking{background:#f59e0b;opacity:1;animation:pulse 1s infinite}
|
|
115
120
|
.status-dot.ok{background:#22c55e;opacity:1}
|
|
@@ -180,6 +185,13 @@ function dashboardHtml(port, whitelist) {
|
|
|
180
185
|
<script>
|
|
181
186
|
const BASE = "http://127.0.0.1:${port}";
|
|
182
187
|
|
|
188
|
+
const SERVICE_HINTS = {
|
|
189
|
+
"neo4j": "neo4j+s://username:password@instance.databases.neo4j.io",
|
|
190
|
+
"supabase-db": "postgresql://postgres:password@db.ref.supabase.co:5432/postgres",
|
|
191
|
+
"r2": "accountId:accessKeyId:secretAccessKey",
|
|
192
|
+
"namecheap": "apiUser:apiKey",
|
|
193
|
+
};
|
|
194
|
+
|
|
183
195
|
const KEY_URLS = {
|
|
184
196
|
"github": "https://github.com/settings/tokens",
|
|
185
197
|
"vercel": "https://vercel.com/account/tokens",
|
|
@@ -287,7 +299,10 @@ async function loadServices() {
|
|
|
287
299
|
<div style="display:flex;align-items:flex-start;justify-content:space-between">
|
|
288
300
|
<div>
|
|
289
301
|
<div class="card-name">\${s.name}</div>
|
|
290
|
-
<div
|
|
302
|
+
<div style="display:flex;align-items:center;gap:6px;margin-top:2px">
|
|
303
|
+
<div class="card-type">\${s.key_type || "secret"}</div>
|
|
304
|
+
<span class="svc-badge \${s.enabled === false ? "off" : "on"}" id="badge-\${s.name}">\${s.enabled === false ? "disabled" : "enabled"}</span>
|
|
305
|
+
</div>
|
|
291
306
|
\${KEY_URLS[s.name] ? \`<a class="card-getkey" href="\${KEY_URLS[s.name]}" target="_blank" rel="noopener">↗ Get / rotate key</a>\` : ""}
|
|
292
307
|
</div>
|
|
293
308
|
<div class="status-dot" id="sdot-\${s.name}" title=""></div>
|
|
@@ -297,10 +312,12 @@ async function loadServices() {
|
|
|
297
312
|
<button class="btn btn-reveal" onclick="reveal('\${s.name}', this)">Reveal</button>
|
|
298
313
|
<button class="btn btn-copy" id="copybtn-\${s.name}" style="display:none" onclick="copyKey('\${s.name}')">Copy</button>
|
|
299
314
|
<button class="btn btn-set" onclick="toggleSet('\${s.name}')">Set</button>
|
|
315
|
+
<button class="btn \${s.enabled === false ? "btn-enable" : "btn-disable"}" id="togbtn-\${s.name}" onclick="toggleService('\${s.name}')">\${s.enabled === false ? "Enable" : "Disable"}</button>
|
|
300
316
|
</div>
|
|
301
317
|
<div class="set-panel" id="set-panel-\${s.name}">
|
|
302
318
|
<label>New value for <strong>\${s.name}</strong> — paste here, never in chat</label>
|
|
303
|
-
<textarea class="set-input" id="set-input-\${s.name}" placeholder="Paste credential…" spellcheck="false"></textarea>
|
|
319
|
+
<textarea class="set-input" id="set-input-\${s.name}" placeholder="\${SERVICE_HINTS[s.name] || "Paste credential…"}" spellcheck="false"></textarea>
|
|
320
|
+
\${SERVICE_HINTS[s.name] ? \`<div style="font-size:.72rem;color:#475569;margin-top:4px;font-family:'Courier New',monospace">\${SERVICE_HINTS[s.name]}</div>\` : ""}
|
|
304
321
|
<div class="set-foot">
|
|
305
322
|
<button class="btn btn-save" onclick="saveKey('\${s.name}')">Save</button>
|
|
306
323
|
<button class="btn btn-cancel" onclick="toggleSet('\${s.name}')">Cancel</button>
|
|
@@ -384,6 +401,39 @@ async function saveKey(name) {
|
|
|
384
401
|
}
|
|
385
402
|
}
|
|
386
403
|
|
|
404
|
+
// ── Enable / Disable service ────────────────
|
|
405
|
+
async function toggleService(name) {
|
|
406
|
+
const badge = document.getElementById("badge-" + name);
|
|
407
|
+
const btn = document.getElementById("togbtn-" + name);
|
|
408
|
+
const currently = badge.classList.contains("on");
|
|
409
|
+
const newState = !currently;
|
|
410
|
+
|
|
411
|
+
btn.disabled = true; btn.textContent = "…";
|
|
412
|
+
|
|
413
|
+
try {
|
|
414
|
+
const r = await fetch(BASE + "/toggle/" + name, {
|
|
415
|
+
method: "POST",
|
|
416
|
+
headers: { "Content-Type": "application/json" },
|
|
417
|
+
body: JSON.stringify({ enabled: newState })
|
|
418
|
+
}).then(r => r.json());
|
|
419
|
+
|
|
420
|
+
if (r.locked) { showLockScreen(); return; }
|
|
421
|
+
if (r.error) throw new Error(r.error);
|
|
422
|
+
|
|
423
|
+
badge.className = "svc-badge " + (newState ? "on" : "off");
|
|
424
|
+
badge.textContent = newState ? "enabled" : "disabled";
|
|
425
|
+
btn.className = "btn " + (newState ? "btn-disable" : "btn-enable");
|
|
426
|
+
btn.textContent = newState ? "Disable" : "Enable";
|
|
427
|
+
} catch (e) {
|
|
428
|
+
btn.textContent = "Error";
|
|
429
|
+
setTimeout(() => {
|
|
430
|
+
btn.textContent = currently ? "Disable" : "Enable";
|
|
431
|
+
}, 2000);
|
|
432
|
+
} finally {
|
|
433
|
+
btn.disabled = false;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
387
437
|
// ── Check all credentials ───────────────────
|
|
388
438
|
async function checkAll() {
|
|
389
439
|
const btn = document.getElementById("check-btn");
|
|
@@ -666,6 +716,34 @@ function createServer(initPassword, whitelist, port) {
|
|
|
666
716
|
return ok(res, { ok: true, locked: true });
|
|
667
717
|
}
|
|
668
718
|
|
|
719
|
+
// POST /toggle/:service — enable or disable a service
|
|
720
|
+
const toggleMatch = reqPath.match(/^\/toggle\/([a-zA-Z0-9_-]+)$/);
|
|
721
|
+
if (method === "POST" && toggleMatch) {
|
|
722
|
+
if (lockedGuard(res)) return;
|
|
723
|
+
const service = toggleMatch[1].toLowerCase();
|
|
724
|
+
|
|
725
|
+
let body;
|
|
726
|
+
try { body = await readBody(req); } catch {
|
|
727
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
728
|
+
return res.end(JSON.stringify({ error: "Invalid JSON body" }));
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const { enabled } = body;
|
|
732
|
+
if (typeof enabled !== "boolean") {
|
|
733
|
+
res.writeHead(400, { "Content-Type": "application/json", ...CORS });
|
|
734
|
+
return res.end(JSON.stringify({ error: "enabled must be boolean" }));
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
try {
|
|
738
|
+
const { token, timestamp } = deriveToken(password, machineHash);
|
|
739
|
+
const result = await api.enable(password, machineHash, token, timestamp, service, enabled);
|
|
740
|
+
if (result.error) return strike(res, 502, result.error);
|
|
741
|
+
return ok(res, { ok: true, service, enabled });
|
|
742
|
+
} catch (err) {
|
|
743
|
+
return strike(res, 502, err.message);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
669
747
|
// GET /check-all — soft health check, no strikes for missing/disabled keys
|
|
670
748
|
if (method === "GET" && reqPath === "/check-all") {
|
|
671
749
|
if (lockedGuard(res)) return;
|