@lifeaitools/clauth 0.3.9 β†’ 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.
@@ -8,10 +8,15 @@ import http from "http";
8
8
  import fs from "fs";
9
9
  import os from "os";
10
10
  import path from "path";
11
+ import { fileURLToPath } from "url";
11
12
  import { getMachineHash, deriveToken, deriveSeedHash } from "../fingerprint.js";
12
13
  import * as api from "../api.js";
13
14
  import chalk from "chalk";
14
15
 
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
18
+ const VERSION = pkg.version;
19
+
15
20
  const PID_FILE = path.join(os.tmpdir(), "clauth-serve.pid");
16
21
  const LOG_FILE = path.join(os.tmpdir(), "clauth-serve.log");
17
22
 
@@ -43,7 +48,7 @@ function dashboardHtml(port, whitelist) {
43
48
  <head>
44
49
  <meta charset="utf-8">
45
50
  <meta name="viewport" content="width=device-width,initial-scale=1">
46
- <title>clauth vault</title>
51
+ <title>clauth vault v${VERSION}</title>
47
52
  <style>
48
53
  *{margin:0;padding:0;box-sizing:border-box}
49
54
  body{background:#0a0f1a;color:#e2e8f0;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;min-height:100vh}
@@ -149,7 +154,7 @@ function dashboardHtml(port, whitelist) {
149
154
  <div id="main-view">
150
155
  <div class="header">
151
156
  <div class="dot" id="dot"></div>
152
- <h1>πŸ” clauth vault</h1>
157
+ <h1>πŸ” clauth vault <span style="font-size:0.55em;opacity:0.45;font-weight:400">v${VERSION}</span></h1>
153
158
  </div>
154
159
  <div id="error-bar" class="error-bar"></div>
155
160
  <div class="status-bar">
@@ -216,36 +221,32 @@ const KEY_URLS = {
216
221
  };
217
222
 
218
223
  // ── OAuth import config ─────────────────────
219
- // Services where Google downloads a JSON file. jsonFields = keys to extract
220
- // from the downloaded JSON (top-level or under an "installed"/"web" wrapper).
221
- // extra = additional fields the user must provide separately.
222
- const OAUTH_IMPORT = {
223
- "gmail": {
224
- jsonFields: ["client_id", "client_secret"],
225
- extra: [{ key: "refresh_token", label: "Refresh Token", hint: "From Google OAuth Playground or your app's auth callback" }]
226
- }
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
+ ]
227
233
  };
228
234
 
229
235
  function renderSetPanel(name) {
230
- const imp = OAUTH_IMPORT[name];
231
- if (imp) {
232
- const extraHtml = imp.extra.map(f => \`
236
+ const fields = OAUTH_FIELDS[name];
237
+ if (fields) {
238
+ const fieldsHtml = fields.map(f => \`
233
239
  <div class="oauth-field">
234
- <label class="oauth-label">\${f.label}</label>
240
+ <label class="oauth-label">\${f.label}\${f.required ? "" : " <span style='color:#475569'>(optional)</span>"}</label>
235
241
  \${f.hint ? \`<div class="oauth-hint">\${f.hint}</div>\` : ""}
236
242
  <input type="text" class="oauth-input" id="ofield-\${name}-\${f.key}" placeholder="Paste \${f.label}…" spellcheck="false" autocomplete="off">
237
243
  </div>
238
244
  \`).join("");
239
245
  return \`
240
246
  <div class="set-panel" id="set-panel-\${name}">
241
- <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>
242
248
  <div class="oauth-fields">
243
- <div class="oauth-field">
244
- <label class="oauth-label">OAuth JSON from Google Cloud Console</label>
245
- <div class="oauth-hint">Download from APIs & Services β†’ Credentials β†’ your OAuth client β†’ ↓ Download JSON</div>
246
- <textarea class="set-input" id="ofield-\${name}-json" placeholder='{"installed":{"client_id":"…","client_secret":"…",...}}' spellcheck="false" rows="3"></textarea>
247
- </div>
248
- \${extraHtml}
249
+ \${fieldsHtml}
249
250
  </div>
250
251
  <div class="set-foot">
251
252
  <button class="btn btn-save" onclick="saveKey('\${name}')">Save</button>
@@ -448,63 +449,68 @@ function toggleSet(name) {
448
449
 
449
450
  async function saveKey(name) {
450
451
  const msg = document.getElementById("set-msg-" + name);
451
- const imp = OAUTH_IMPORT[name];
452
- let value;
453
-
454
- if (imp) {
455
- const jsonEl = document.getElementById("ofield-" + name + "-json");
456
- const raw = jsonEl ? jsonEl.value.trim() : "";
457
- if (!raw) { msg.className = "set-msg fail"; msg.textContent = "Paste the OAuth JSON first."; return; }
458
- let parsed;
459
- try { parsed = JSON.parse(raw); } catch { msg.className = "set-msg fail"; msg.textContent = "Invalid JSON β€” copy the full file content."; return; }
460
- // Google wraps fields under "installed" or "web"
461
- const src = parsed.installed || parsed.web || parsed;
462
- const obj = {};
463
- for (const k of imp.jsonFields) {
464
- if (!src[k]) { msg.className = "set-msg fail"; msg.textContent = k + " not found in JSON."; return; }
465
- obj[k] = src[k];
466
- }
467
- 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;
468
458
  const el = document.getElementById("ofield-" + name + "-" + f.key);
469
- const v = el ? el.value.trim() : "";
470
- if (!v) { msg.className = "set-msg fail"; msg.textContent = f.label + " is required."; return; }
471
- 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;
472
483
  }
473
- value = JSON.stringify(obj);
474
484
  } else {
475
485
  const input = document.getElementById("set-input-" + name);
476
- value = input ? input.value.trim() : "";
486
+ const value = input ? input.value.trim() : "";
477
487
  if (!value) { msg.className = "set-msg fail"; msg.textContent = "Value is empty."; return; }
478
- }
479
-
480
- msg.className = "set-msg"; msg.textContent = "Saving…";
481
- try {
482
- const r = await fetch(BASE + "/set/" + name, {
483
- method: "POST",
484
- headers: { "Content-Type": "application/json" },
485
- body: JSON.stringify({ value })
486
- }).then(r => r.json());
487
488
 
488
- if (r.locked) { showLockScreen(); return; }
489
- if (r.error) throw new Error(r.error);
490
- msg.className = "set-msg ok"; msg.textContent = "βœ“ Saved";
491
- if (imp) {
492
- const jsonEl = document.getElementById("ofield-" + name + "-json");
493
- if (jsonEl) jsonEl.value = "";
494
- imp.extra.forEach(f => { const el = document.getElementById("ofield-" + name + "-" + f.key); if (el) el.value = ""; });
495
- } 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";
496
500
  const inp = document.getElementById("set-input-" + name);
497
501
  if (inp) inp.value = "";
502
+ } catch (e) {
503
+ msg.className = "set-msg fail"; msg.textContent = e.message || "Save failed";
504
+ return;
498
505
  }
499
- const dot = document.getElementById("sdot-" + name);
500
- if (dot) { dot.className = "status-dot"; dot.title = ""; }
501
- setTimeout(() => {
502
- document.getElementById("set-panel-" + name).style.display = "none";
503
- msg.textContent = "";
504
- }, 1800);
505
- } catch (e) {
506
- msg.className = "set-msg fail"; msg.textContent = "βœ— " + e.message;
507
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);
508
514
  }
509
515
 
510
516
  // ── Enable / Disable service ────────────────
@@ -766,7 +772,7 @@ function createServer(initPassword, whitelist, port) {
766
772
  }
767
773
 
768
774
  // GET /get/:service
769
- const getMatch = reqPath.match(/^\/get\/([a-zA-Z0-9_-]+)$/);
775
+ const getMatch = reqPath.match(/^\/get\/([a-zA-Z0-9_.-]+)$/);
770
776
  if (method === "GET" && getMatch) {
771
777
  if (lockedGuard(res)) return;
772
778
  const service = getMatch[1].toLowerCase();
@@ -912,7 +918,7 @@ function createServer(initPassword, whitelist, port) {
912
918
  }
913
919
 
914
920
  // POST /set/:service β€” write a new key value into vault
915
- const setMatch = reqPath.match(/^\/set\/([a-zA-Z0-9_-]+)$/);
921
+ const setMatch = reqPath.match(/^\/set\/([a-zA-Z0-9_.-]+)$/);
916
922
  if (method === "POST" && setMatch) {
917
923
  if (lockedGuard(res)) return;
918
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.9";
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.9",
3
+ "version": "0.3.11",
4
4
  "description": "Hardware-bound credential vault for the LIFEAI infrastructure stack",
5
5
  "type": "module",
6
6
  "bin": {