@lifeaitools/clauth 1.4.4 → 1.4.6

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 (2) hide show
  1. package/cli/commands/serve.js +106 -12
  2. package/package.json +1 -1
@@ -502,10 +502,18 @@ function dashboardHtml(port, whitelist, isStaged = false) {
502
502
  .card:hover{border-color:#3b82f6}
503
503
  .card-name{font-size:1rem;font-weight:600;color:#f8fafc;margin-bottom:3px}
504
504
  .card-type{font-size:.78rem;color:#64748b;text-transform:uppercase;letter-spacing:.5px}
505
- .card-getkey{font-size:.75rem;color:#3b82f6;text-decoration:none;opacity:.7;transition:opacity .15s}
506
- .card-getkey:hover{opacity:1;text-decoration:underline}
505
+ .card-getkey{font-size:.75rem;color:#60a5fa;text-decoration:none;transition:color .15s}
506
+ .card-getkey:hover{color:#93c5fd;text-decoration:underline}
507
507
  .card-value{font-family:'Courier New',monospace;font-size:.82rem;color:#22c55e;background:#0f172a;border-radius:4px;padding:8px 10px;margin-top:10px;word-break:break-all;max-height:80px;overflow:auto;display:none}
508
508
  .card-actions{margin-top:10px;display:flex;gap:7px;flex-wrap:wrap}
509
+ .btn-rename{background:#1e293b;color:#94a3b8;border:1px solid #334155;padding:3px 8px;font-size:.75rem;border-radius:4px;cursor:pointer;transition:color .15s,border-color .15s}
510
+ .btn-rename:hover{color:#e2e8f0;border-color:#60a5fa}
511
+ .btn-delete{background:#1e293b;color:#f87171;border:1px solid #4b2020;padding:3px 8px;font-size:.75rem;border-radius:4px;cursor:pointer;transition:background .15s,border-color .15s}
512
+ .btn-delete:hover{background:#2d1f1f;border-color:#ef4444}
513
+ .rename-panel{display:none;margin-top:8px;background:#0f172a;border-radius:6px;padding:8px 10px;border:1px solid #1e3a5f}
514
+ .rename-input{width:calc(100% - 100px);background:#0a0f1a;border:1px solid #1e3a5f;border-radius:4px;color:#e2e8f0;font-size:.85rem;padding:5px 8px;outline:none}
515
+ .rename-input:focus{border-color:#3b82f6}
516
+ .rename-msg{font-size:.75rem;margin-left:6px}
509
517
  .set-panel{display:none;margin-top:10px;background:#0f172a;border-radius:6px;padding:10px;border:1px solid #1e3a5f}
510
518
  .set-panel label{font-size:.75rem;color:#64748b;display:block;margin-bottom:6px}
511
519
  .set-input{width:100%;background:#0a0f1a;border:1px solid #1e3a5f;border-radius:4px;color:#e2e8f0;font-family:'Courier New',monospace;font-size:.85rem;padding:7px 10px;outline:none;resize:vertical;min-height:58px;transition:border-color .2s}
@@ -885,16 +893,7 @@ const KEY_URLS = {
885
893
  };
886
894
 
887
895
  // Extra links shown below the primary KEY_URLS link
888
- const EXTRA_LINKS = {
889
- "gmail": [
890
- { label: "↗ OAuth Playground (get refresh token)", url: "https://developers.google.com/oauthplayground/" },
891
- { label: "↗ Enable Gmail API", url: "https://console.cloud.google.com/apis/library/gmail.googleapis.com" },
892
- ],
893
- "gcal": [
894
- { label: "↗ OAuth Playground (get refresh token)", url: "https://developers.google.com/oauthplayground/" },
895
- { label: "↗ Enable Calendar API", url: "https://console.cloud.google.com/apis/library/calendar-json.googleapis.com" },
896
- ],
897
- };
896
+ const EXTRA_LINKS = {};
898
897
 
899
898
  // ── OAuth import config ─────────────────────
900
899
  // Services where Google downloads a JSON file. jsonFields = keys to extract
@@ -1192,6 +1191,14 @@ function renderServiceGrid(services) {
1192
1191
  <button class="btn-project" onclick="toggleProjectEdit('\${s.name}')">\${s.project ? "✎ Project" : "+ Project"}</button>
1193
1192
  <button class="btn \${s.enabled === false ? "btn-enable" : "btn-disable"}" id="togbtn-\${s.name}" onclick="toggleService('\${s.name}')">\${s.enabled === false ? "Enable" : "Disable"}</button>
1194
1193
  <button class="btn-rotate" id="rotbtn-\${s.name}" style="display:none;background:#0e7490;border:1px solid #06b6d4;color:#cffafe;font-size:.75rem;padding:3px 8px;border-radius:4px;cursor:pointer" onclick="rotateKey('\${s.name}')">↻ Rotate</button>
1194
+ <button class="btn-rename" onclick="toggleRename('\${s.name}')" title="Rename service">✎</button>
1195
+ <button class="btn-delete" onclick="deleteService('\${s.name}')" title="Delete service">✕</button>
1196
+ </div>
1197
+ <div class="rename-panel" id="rn-\${s.name}">
1198
+ <input class="rename-input" id="rn-input-\${s.name}" value="\${s.name}" spellcheck="false" autocomplete="off" placeholder="New name…">
1199
+ <button class="btn" onclick="saveRename('\${s.name}')" style="padding:4px 10px;font-size:.8rem">Save</button>
1200
+ <button class="btn" onclick="toggleRename('\${s.name}')" style="padding:4px 10px;font-size:.8rem;background:#1e293b">Cancel</button>
1201
+ <span class="rename-msg" id="rn-msg-\${s.name}"></span>
1195
1202
  </div>
1196
1203
  <div class="project-edit" id="pe-\${s.name}">
1197
1204
  <input type="text" id="pe-input-\${s.name}" value="\${s.project || ""}" placeholder="Project name…" spellcheck="false" autocomplete="off">
@@ -1397,6 +1404,53 @@ async function clearProject(name) {
1397
1404
  await saveProject(name);
1398
1405
  }
1399
1406
 
1407
+ // ── Rename service ───────────────────────────
1408
+ function toggleRename(name) {
1409
+ const panel = document.getElementById("rn-" + name);
1410
+ const open = panel.style.display === "block";
1411
+ panel.style.display = open ? "none" : "block";
1412
+ if (!open) {
1413
+ const inp = document.getElementById("rn-input-" + name);
1414
+ inp.value = name;
1415
+ inp.focus(); inp.select();
1416
+ document.getElementById("rn-msg-" + name).textContent = "";
1417
+ }
1418
+ }
1419
+
1420
+ async function saveRename(oldName) {
1421
+ const inp = document.getElementById("rn-input-" + oldName);
1422
+ const msg = document.getElementById("rn-msg-" + oldName);
1423
+ const newName = inp.value.trim();
1424
+ if (!newName || newName === oldName) { toggleRename(oldName); return; }
1425
+ msg.style.color = "#94a3b8"; msg.textContent = "Saving…";
1426
+ try {
1427
+ const r = await fetch(BASE + "/rename/" + oldName, {
1428
+ method: "POST",
1429
+ headers: { "Content-Type": "application/json" },
1430
+ body: JSON.stringify({ new_name: newName })
1431
+ }).then(r => r.json());
1432
+ if (r.locked) { showLockScreen(false); return; }
1433
+ if (r.error) throw new Error(r.error);
1434
+ msg.style.color = "#4ade80"; msg.textContent = "✓ Renamed";
1435
+ setTimeout(() => loadServices(), 800);
1436
+ } catch (e) {
1437
+ msg.style.color = "#f87171"; msg.textContent = "✗ " + e.message;
1438
+ }
1439
+ }
1440
+
1441
+ // ── Delete service ───────────────────────────
1442
+ async function deleteService(name) {
1443
+ if (!confirm(\`Delete service "\${name}"? This cannot be undone.\`)) return;
1444
+ try {
1445
+ const r = await fetch(BASE + "/delete/" + name, { method: "POST" }).then(r => r.json());
1446
+ if (r.locked) { showLockScreen(false); return; }
1447
+ if (r.error) throw new Error(r.error);
1448
+ loadServices();
1449
+ } catch (e) {
1450
+ alert("Delete failed: " + e.message);
1451
+ }
1452
+ }
1453
+
1400
1454
  // ── Set key ─────────────────────────────────
1401
1455
  function toggleSet(name) {
1402
1456
  const panel = document.getElementById("set-panel-" + name);
@@ -3417,6 +3471,46 @@ function createServer(initPassword, whitelist, port, tunnelHostnameInit = null,
3417
3471
  return ok(res, { ok: true, locked: true, hard_locked: authHardLocked });
3418
3472
  }
3419
3473
 
3474
+ // POST /rename/:service — rename a service
3475
+ const renameMatch = reqPath.match(/^\/rename\/([a-zA-Z0-9_-]+)$/);
3476
+ if (method === "POST" && renameMatch) {
3477
+ if (lockedGuard(res)) return;
3478
+ const service = renameMatch[1].toLowerCase();
3479
+ let body;
3480
+ try { body = await readBody(req); } catch {
3481
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3482
+ return res.end(JSON.stringify({ error: "Invalid JSON body" }));
3483
+ }
3484
+ const newName = (body.new_name || "").trim().toLowerCase();
3485
+ if (!newName || !/^[a-z0-9_-]+$/.test(newName)) {
3486
+ res.writeHead(400, { "Content-Type": "application/json", ...CORS });
3487
+ return res.end(JSON.stringify({ error: "Invalid name — use lowercase letters, numbers, hyphens, underscores" }));
3488
+ }
3489
+ try {
3490
+ const { token, timestamp } = deriveToken(password, machineHash);
3491
+ const result = await api.updateService(password, machineHash, token, timestamp, service, { name: newName, label: newName });
3492
+ if (result.error) return strike(res, 502, result.error);
3493
+ return ok(res, { ok: true, old_name: service, new_name: newName });
3494
+ } catch (err) {
3495
+ return strike(res, 502, err.message);
3496
+ }
3497
+ }
3498
+
3499
+ // POST /delete/:service — remove a service entirely
3500
+ const deleteMatch = reqPath.match(/^\/delete\/([a-zA-Z0-9_-]+)$/);
3501
+ if (method === "POST" && deleteMatch) {
3502
+ if (lockedGuard(res)) return;
3503
+ const service = deleteMatch[1].toLowerCase();
3504
+ try {
3505
+ const { token, timestamp } = deriveToken(password, machineHash);
3506
+ const result = await api.removeService(password, machineHash, token, timestamp, service, true);
3507
+ if (result.error) return strike(res, 502, result.error);
3508
+ return ok(res, { ok: true, deleted: service });
3509
+ } catch (err) {
3510
+ return strike(res, 502, err.message);
3511
+ }
3512
+ }
3513
+
3420
3514
  // POST /toggle/:service — enable or disable a service
3421
3515
  const toggleMatch = reqPath.match(/^\/toggle\/([a-zA-Z0-9_-]+)$/);
3422
3516
  if (method === "POST" && toggleMatch) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "1.4.4",
3
+ "version": "1.4.6",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {