@lifeaitools/clauth 0.7.5 → 1.1.0
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/api.js +13 -5
- package/cli/commands/doctor.js +302 -0
- package/cli/commands/install.js +113 -0
- package/cli/commands/invite.js +175 -0
- package/cli/commands/join.js +179 -0
- package/cli/commands/serve.js +1153 -230
- package/cli/commands/tunnel.js +210 -0
- package/cli/index.js +140 -16
- package/install.ps1 +7 -0
- package/install.sh +11 -0
- package/package.json +7 -2
- package/scripts/bootstrap.cjs +78 -0
- package/scripts/postinstall.js +52 -0
- package/supabase/functions/auth-vault/index.ts +27 -7
- package/supabase/migrations/003_clauth_config.sql +13 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// cli/commands/join.js
|
|
2
|
+
// clauth join <invite-code>
|
|
3
|
+
// Redeems an invite and sets up the vault connection for a new user.
|
|
4
|
+
//
|
|
5
|
+
// Flow:
|
|
6
|
+
// 1. Validate invite code format
|
|
7
|
+
// 2. Choose a master password
|
|
8
|
+
// 3. Connect to vault (ask for URL + anon key, or use defaults)
|
|
9
|
+
// 4. Register machine via Edge Function with invite code
|
|
10
|
+
// 5. Optionally install as auto-start service
|
|
11
|
+
// 6. Print next-steps
|
|
12
|
+
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import ora from "ora";
|
|
15
|
+
import Conf from "conf";
|
|
16
|
+
import { getConfOptions } from "../conf-path.js";
|
|
17
|
+
import { getMachineHash, deriveSeedHash } from "../fingerprint.js";
|
|
18
|
+
import os from "os";
|
|
19
|
+
|
|
20
|
+
// Default vault — the LIFEAI Supabase project
|
|
21
|
+
const DEFAULT_VAULT_URL = "https://uvojezuorjgqzmhhgluu.supabase.co";
|
|
22
|
+
|
|
23
|
+
export async function runJoin(code) {
|
|
24
|
+
const config = new Conf(getConfOptions());
|
|
25
|
+
const inquirer = (await import("inquirer")).default;
|
|
26
|
+
|
|
27
|
+
// ── Validate code format ───────────────────
|
|
28
|
+
const normalized = code.toUpperCase().trim();
|
|
29
|
+
if (!/^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/.test(normalized)) {
|
|
30
|
+
console.log(chalk.red("\n Invalid invite code format. Expected: XXXX-XXXX-XXXX\n"));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(chalk.cyan(`\n Joining vault with invite: ${chalk.bold(normalized)}\n`));
|
|
35
|
+
|
|
36
|
+
// ── Check for existing installation ────────
|
|
37
|
+
if (config.get("supabase_url")) {
|
|
38
|
+
const { proceed } = await inquirer.prompt([{
|
|
39
|
+
type: "confirm",
|
|
40
|
+
name: "proceed",
|
|
41
|
+
message: "clauth is already configured. Overwrite with new vault connection?",
|
|
42
|
+
default: false,
|
|
43
|
+
}]);
|
|
44
|
+
if (!proceed) {
|
|
45
|
+
console.log(chalk.gray("\n Cancelled.\n"));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Step 1: Choose a password ──────────────
|
|
51
|
+
const { pw } = await inquirer.prompt([{
|
|
52
|
+
type: "password",
|
|
53
|
+
name: "pw",
|
|
54
|
+
message: "Choose a master password (min 8 chars):",
|
|
55
|
+
mask: "*",
|
|
56
|
+
validate: v => v.length >= 8 || "Password must be at least 8 characters",
|
|
57
|
+
}]);
|
|
58
|
+
const { pw2 } = await inquirer.prompt([{
|
|
59
|
+
type: "password",
|
|
60
|
+
name: "pw2",
|
|
61
|
+
message: "Confirm password:",
|
|
62
|
+
mask: "*",
|
|
63
|
+
}]);
|
|
64
|
+
if (pw !== pw2) {
|
|
65
|
+
console.log(chalk.red("\n Passwords don't match.\n"));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Step 2: Vault connection details ───────
|
|
70
|
+
// The person who invited you should share the vault URL + anon key.
|
|
71
|
+
// For the LIFEAI vault, the URL is well-known.
|
|
72
|
+
console.log("");
|
|
73
|
+
console.log(chalk.gray(" Your inviter should have shared the vault URL and anon key."));
|
|
74
|
+
console.log(chalk.gray(" Press Enter to use the LIFEAI default vault.\n"));
|
|
75
|
+
|
|
76
|
+
const { vaultUrl } = await inquirer.prompt([{
|
|
77
|
+
type: "input",
|
|
78
|
+
name: "vaultUrl",
|
|
79
|
+
message: "Vault URL:",
|
|
80
|
+
default: DEFAULT_VAULT_URL,
|
|
81
|
+
}]);
|
|
82
|
+
|
|
83
|
+
const { anonKey } = await inquirer.prompt([{
|
|
84
|
+
type: "input",
|
|
85
|
+
name: "anonKey",
|
|
86
|
+
message: "Vault anon key:",
|
|
87
|
+
validate: v => v.length > 0 || "Anon key is required (ask the person who invited you)",
|
|
88
|
+
}]);
|
|
89
|
+
|
|
90
|
+
// ── Step 3: Machine label ─────────────────
|
|
91
|
+
const { label } = await inquirer.prompt([{
|
|
92
|
+
type: "input",
|
|
93
|
+
name: "label",
|
|
94
|
+
message: "Label for this machine:",
|
|
95
|
+
default: os.hostname(),
|
|
96
|
+
}]);
|
|
97
|
+
|
|
98
|
+
// ── Step 4: Register machine ───────────────
|
|
99
|
+
const spinner = ora("Registering machine with vault...").start();
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const machineHash = getMachineHash();
|
|
103
|
+
const seedHash = deriveSeedHash(machineHash, pw);
|
|
104
|
+
|
|
105
|
+
const res = await fetch(`${vaultUrl}/functions/v1/auth-vault/register-machine`, {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Authorization": `Bearer ${anonKey}`,
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify({
|
|
112
|
+
machine_hash: machineHash,
|
|
113
|
+
hmac_seed_hash: seedHash,
|
|
114
|
+
label,
|
|
115
|
+
invite_code: normalized,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const body = await res.json().catch(() => ({}));
|
|
120
|
+
|
|
121
|
+
if (!res.ok || body.error) {
|
|
122
|
+
const errMsg = body.error || body.message || `HTTP ${res.status}`;
|
|
123
|
+
spinner.fail(chalk.red(`Registration failed: ${errMsg}`));
|
|
124
|
+
console.log("");
|
|
125
|
+
console.log(chalk.yellow(" Possible causes:"));
|
|
126
|
+
console.log(chalk.gray(" - Invite code expired or already used"));
|
|
127
|
+
console.log(chalk.gray(" - Vault URL or anon key incorrect"));
|
|
128
|
+
console.log(chalk.gray(" - Edge Function doesn't support invite-based registration yet"));
|
|
129
|
+
console.log("");
|
|
130
|
+
console.log(chalk.cyan(" Alternative: ask the admin for a bootstrap token, then run:"));
|
|
131
|
+
console.log(chalk.white(" clauth setup --admin-token <token>"));
|
|
132
|
+
console.log("");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
spinner.succeed(chalk.green(`Machine registered: ${machineHash.slice(0, 12)}...`));
|
|
137
|
+
|
|
138
|
+
// Save config
|
|
139
|
+
config.set("supabase_url", vaultUrl);
|
|
140
|
+
config.set("supabase_anon_key", anonKey);
|
|
141
|
+
|
|
142
|
+
} catch (err) {
|
|
143
|
+
spinner.fail(chalk.red(`Connection failed: ${err.message}`));
|
|
144
|
+
console.log(chalk.gray("\n Check your network and vault URL.\n"));
|
|
145
|
+
process.exit(1);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Step 5: Offer to install as service ────
|
|
149
|
+
const { installService } = await inquirer.prompt([{
|
|
150
|
+
type: "confirm",
|
|
151
|
+
name: "installService",
|
|
152
|
+
message: "Install as auto-start service? (recommended)",
|
|
153
|
+
default: true,
|
|
154
|
+
}]);
|
|
155
|
+
|
|
156
|
+
if (installService) {
|
|
157
|
+
try {
|
|
158
|
+
const { runServe } = await import("./serve.js");
|
|
159
|
+
await runServe({ action: "install", pw });
|
|
160
|
+
} catch (err) {
|
|
161
|
+
console.log(chalk.yellow(`\n Service install skipped: ${err.message}`));
|
|
162
|
+
console.log(chalk.gray(" You can install it later: clauth serve install\n"));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// ── Step 6: Done ───────────────────────────
|
|
167
|
+
console.log("");
|
|
168
|
+
console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
169
|
+
console.log(chalk.green(" clauth is ready!"));
|
|
170
|
+
console.log(chalk.cyan(" ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
|
|
171
|
+
console.log("");
|
|
172
|
+
console.log(chalk.gray(" Commands:"));
|
|
173
|
+
console.log(chalk.gray(" clauth test verify connection"));
|
|
174
|
+
console.log(chalk.gray(" clauth status list services"));
|
|
175
|
+
console.log(chalk.gray(" clauth get <svc> retrieve a credential"));
|
|
176
|
+
console.log(chalk.gray(" clauth serve start daemon"));
|
|
177
|
+
console.log(chalk.gray(" clauth doctor check health"));
|
|
178
|
+
console.log("");
|
|
179
|
+
}
|