@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.
- package/cli/commands/serve.js +76 -70
- package/cli/index.js +1 -1
- package/package.json +1 -1
package/cli/commands/serve.js
CHANGED
|
@@ -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
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
231
|
-
if (
|
|
232
|
-
const
|
|
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
|
|
247
|
+
<label>Set <strong>\${name}</strong> credentials β paste directly from source, never in chat</label>
|
|
242
248
|
<div class="oauth-fields">
|
|
243
|
-
|
|
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
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
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
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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.
|
|
14
|
+
const VERSION = "0.3.11";
|
|
15
15
|
|
|
16
16
|
// ============================================================
|
|
17
17
|
// Password prompt helper
|