@lifeaitools/clauth 0.3.10 → 0.3.11

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.
@@ -221,36 +221,32 @@ const KEY_URLS = {
221
221
  };
222
222
 
223
223
  // ── OAuth import config ─────────────────────
224
- // Services where Google downloads a JSON file. jsonFields = keys to extract
225
- // from the downloaded JSON (top-level or under an "installed"/"web" wrapper).
226
- // extra = additional fields the user must provide separately.
227
- const OAUTH_IMPORT = {
228
- "gmail": {
229
- jsonFields: ["client_id", "client_secret"],
230
- extra: [{ key: "refresh_token", label: "Refresh Token", hint: "From Google OAuth Playground or your app's auth callback" }]
231
- }
224
+ // OAuth services with atomic fields each field saved as clauth.<service>.<key>
225
+ // No JSON blob parsing. Each field is its own vault secret.
226
+ const OAUTH_FIELDS = {
227
+ "gmail": [
228
+ { key: "client_id", label: "Client ID", hint: "From Google Cloud Console → APIs & Services → Credentials → OAuth client", required: true },
229
+ { key: "client_secret", label: "Client Secret", hint: "From Google Cloud Console OAuth client", required: true },
230
+ { key: "refresh_token", label: "Refresh Token", hint: "From Google OAuth Playground or your app's auth callback", required: true },
231
+ { key: "from_address", label: "From Address", hint: "Gmail address to send from (e.g. dave@life.ai)", required: false },
232
+ ]
232
233
  };
233
234
 
234
235
  function renderSetPanel(name) {
235
- const imp = OAUTH_IMPORT[name];
236
- if (imp) {
237
- const extraHtml = imp.extra.map(f => \`
236
+ const fields = OAUTH_FIELDS[name];
237
+ if (fields) {
238
+ const fieldsHtml = fields.map(f => \`
238
239
  <div class="oauth-field">
239
- <label class="oauth-label">\${f.label}</label>
240
+ <label class="oauth-label">\${f.label}\${f.required ? "" : " <span style='color:#475569'>(optional)</span>"}</label>
240
241
  \${f.hint ? \`<div class="oauth-hint">\${f.hint}</div>\` : ""}
241
242
  <input type="text" class="oauth-input" id="ofield-\${name}-\${f.key}" placeholder="Paste \${f.label}…" spellcheck="false" autocomplete="off">
242
243
  </div>
243
244
  \`).join("");
244
245
  return \`
245
246
  <div class="set-panel" id="set-panel-\${name}">
246
- <label>Set <strong>\${name}</strong> credentials — paste directly from Google, never in chat</label>
247
+ <label>Set <strong>\${name}</strong> credentials — paste directly from source, never in chat</label>
247
248
  <div class="oauth-fields">
248
- <div class="oauth-field">
249
- <label class="oauth-label">OAuth JSON from Google Cloud Console</label>
250
- <div class="oauth-hint">Download from APIs & Services → Credentials → your OAuth client → ↓ Download JSON</div>
251
- <textarea class="set-input" id="ofield-\${name}-json" placeholder='{"installed":{"client_id":"…","client_secret":"…",...}}' spellcheck="false" rows="3"></textarea>
252
- </div>
253
- \${extraHtml}
249
+ \${fieldsHtml}
254
250
  </div>
255
251
  <div class="set-foot">
256
252
  <button class="btn btn-save" onclick="saveKey('\${name}')">Save</button>
@@ -453,63 +449,68 @@ function toggleSet(name) {
453
449
 
454
450
  async function saveKey(name) {
455
451
  const msg = document.getElementById("set-msg-" + name);
456
- const imp = OAUTH_IMPORT[name];
457
- let value;
458
-
459
- if (imp) {
460
- const jsonEl = document.getElementById("ofield-" + name + "-json");
461
- const raw = jsonEl ? jsonEl.value.trim() : "";
462
- if (!raw) { msg.className = "set-msg fail"; msg.textContent = "Paste the OAuth JSON first."; return; }
463
- let parsed;
464
- try { parsed = JSON.parse(raw); } catch { msg.className = "set-msg fail"; msg.textContent = "Invalid JSON — copy the full file content."; return; }
465
- // Google wraps fields under "installed" or "web"
466
- const src = parsed.installed || parsed.web || parsed;
467
- const obj = {};
468
- for (const k of imp.jsonFields) {
469
- if (!src[k]) { msg.className = "set-msg fail"; msg.textContent = k + " not found in JSON."; return; }
470
- obj[k] = src[k];
471
- }
472
- for (const f of imp.extra) {
452
+ const fields = OAUTH_FIELDS[name];
453
+
454
+ if (fields) {
455
+ // Validate required fields first
456
+ for (const f of fields) {
457
+ if (!f.required) continue;
473
458
  const el = document.getElementById("ofield-" + name + "-" + f.key);
474
- const v = el ? el.value.trim() : "";
475
- if (!v) { msg.className = "set-msg fail"; msg.textContent = f.label + " is required."; return; }
476
- obj[f.key] = v;
459
+ if (!el || !el.value.trim()) {
460
+ msg.className = "set-msg fail"; msg.textContent = f.label + " is required."; return;
461
+ }
462
+ }
463
+ // Save each field as an atomic vault key: clauth.<service>.<key>
464
+ msg.className = "set-msg"; msg.textContent = "Saving…";
465
+ try {
466
+ for (const f of fields) {
467
+ const el = document.getElementById("ofield-" + name + "-" + f.key);
468
+ const v = el ? el.value.trim() : "";
469
+ if (!v) continue; // skip optional empty fields
470
+ const r = await fetch(BASE + "/set/" + name + "." + f.key, {
471
+ method: "POST",
472
+ headers: { "Content-Type": "application/json" },
473
+ body: JSON.stringify({ value: v })
474
+ }).then(r => r.json());
475
+ if (r.locked) { showLockScreen(); return; }
476
+ if (r.error) throw new Error(r.error);
477
+ }
478
+ msg.className = "set-msg ok"; msg.textContent = "✓ Saved";
479
+ fields.forEach(f => { const el = document.getElementById("ofield-" + name + "-" + f.key); if (el) el.value = ""; });
480
+ } catch (e) {
481
+ msg.className = "set-msg fail"; msg.textContent = e.message || "Save failed";
482
+ return;
477
483
  }
478
- value = JSON.stringify(obj);
479
484
  } else {
480
485
  const input = document.getElementById("set-input-" + name);
481
- value = input ? input.value.trim() : "";
486
+ const value = input ? input.value.trim() : "";
482
487
  if (!value) { msg.className = "set-msg fail"; msg.textContent = "Value is empty."; return; }
483
- }
484
488
 
485
- msg.className = "set-msg"; msg.textContent = "Saving…";
486
- try {
487
- const r = await fetch(BASE + "/set/" + name, {
488
- method: "POST",
489
- headers: { "Content-Type": "application/json" },
490
- body: JSON.stringify({ value })
491
- }).then(r => r.json());
492
-
493
- if (r.locked) { showLockScreen(); return; }
494
- if (r.error) throw new Error(r.error);
495
- msg.className = "set-msg ok"; msg.textContent = "✓ Saved";
496
- if (imp) {
497
- const jsonEl = document.getElementById("ofield-" + name + "-json");
498
- if (jsonEl) jsonEl.value = "";
499
- imp.extra.forEach(f => { const el = document.getElementById("ofield-" + name + "-" + f.key); if (el) el.value = ""; });
500
- } else {
489
+ msg.className = "set-msg"; msg.textContent = "Saving…";
490
+ try {
491
+ const r = await fetch(BASE + "/set/" + name, {
492
+ method: "POST",
493
+ headers: { "Content-Type": "application/json" },
494
+ body: JSON.stringify({ value })
495
+ }).then(r => r.json());
496
+
497
+ if (r.locked) { showLockScreen(); return; }
498
+ if (r.error) throw new Error(r.error);
499
+ msg.className = "set-msg ok"; msg.textContent = "✓ Saved";
501
500
  const inp = document.getElementById("set-input-" + name);
502
501
  if (inp) inp.value = "";
502
+ } catch (e) {
503
+ msg.className = "set-msg fail"; msg.textContent = e.message || "Save failed";
504
+ return;
503
505
  }
504
- const dot = document.getElementById("sdot-" + name);
505
- if (dot) { dot.className = "status-dot"; dot.title = ""; }
506
- setTimeout(() => {
507
- document.getElementById("set-panel-" + name).style.display = "none";
508
- msg.textContent = "";
509
- }, 1800);
510
- } catch (e) {
511
- msg.className = "set-msg fail"; msg.textContent = "✗ " + e.message;
512
506
  }
507
+
508
+ const dot = document.getElementById("sdot-" + name);
509
+ if (dot) { dot.className = "status-dot"; dot.title = ""; }
510
+ setTimeout(() => {
511
+ document.getElementById("set-panel-" + name).style.display = "none";
512
+ msg.textContent = "";
513
+ }, 1800);
513
514
  }
514
515
 
515
516
  // ── Enable / Disable service ────────────────
@@ -771,7 +772,7 @@ function createServer(initPassword, whitelist, port) {
771
772
  }
772
773
 
773
774
  // GET /get/:service
774
- const getMatch = reqPath.match(/^\/get\/([a-zA-Z0-9_-]+)$/);
775
+ const getMatch = reqPath.match(/^\/get\/([a-zA-Z0-9_.-]+)$/);
775
776
  if (method === "GET" && getMatch) {
776
777
  if (lockedGuard(res)) return;
777
778
  const service = getMatch[1].toLowerCase();
@@ -917,7 +918,7 @@ function createServer(initPassword, whitelist, port) {
917
918
  }
918
919
 
919
920
  // POST /set/:service — write a new key value into vault
920
- const setMatch = reqPath.match(/^\/set\/([a-zA-Z0-9_-]+)$/);
921
+ const setMatch = reqPath.match(/^\/set\/([a-zA-Z0-9_.-]+)$/);
921
922
  if (method === "POST" && setMatch) {
922
923
  if (lockedGuard(res)) return;
923
924
  const service = setMatch[1].toLowerCase();
package/cli/index.js CHANGED
@@ -11,7 +11,7 @@ import * as api from "./api.js";
11
11
  import os from "os";
12
12
 
13
13
  const config = new Conf({ projectName: "clauth" });
14
- const VERSION = "0.3.10";
14
+ const VERSION = "0.3.11";
15
15
 
16
16
  // ============================================================
17
17
  // Password prompt helper
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lifeaitools/clauth",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {