zeuslock-dlp-cli 0.2.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/README.md +570 -0
- package/bin/zeuslock.js +9 -0
- package/package.json +33 -0
- package/src/cli.js +78 -0
- package/src/commands/agents.js +211 -0
- package/src/commands/anonymize.js +83 -0
- package/src/commands/auth.js +337 -0
- package/src/commands/deploy.js +515 -0
- package/src/commands/extensions.js +73 -0
- package/src/commands/hook.js +221 -0
- package/src/commands/incidents.js +436 -0
- package/src/commands/keys.js +211 -0
- package/src/commands/mcp.js +322 -0
- package/src/commands/rules.js +432 -0
- package/src/commands/scan.js +178 -0
- package/src/commands/shadow-ai.js +255 -0
- package/src/commands/siem.js +241 -0
- package/src/commands/status.js +170 -0
- package/src/commands/tokens.js +293 -0
- package/src/commands/users.js +255 -0
- package/src/commands/whoami.js +43 -0
- package/src/lib/api-client.js +308 -0
- package/src/lib/api-key-store.js +84 -0
- package/src/lib/auth-store.js +123 -0
- package/src/lib/cli-token.js +22 -0
- package/src/lib/command-token.js +15 -0
- package/src/lib/config.js +27 -0
- package/src/lib/dlp-scan.js +146 -0
- package/src/lib/package-info.js +11 -0
- package/src/lib/prompt.js +55 -0
- package/src/lib/siem-cursors.js +64 -0
- package/src/lib/table.js +30 -0
- package/src/lib/time.js +33 -0
- package/src/lib/version.js +24 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAuthRecord,
|
|
3
|
+
deleteAuthStore,
|
|
4
|
+
readAuthStore,
|
|
5
|
+
writeAuthStore
|
|
6
|
+
} from "./auth-store.js";
|
|
7
|
+
import { readApiKeyStore } from "./api-key-store.js";
|
|
8
|
+
import { assertCliAccessToken } from "./cli-token.js";
|
|
9
|
+
import { getCommandToken } from "./command-token.js";
|
|
10
|
+
import { DEFAULT_API_URL, resolveApiUrl } from "./config.js";
|
|
11
|
+
|
|
12
|
+
const CLI_TOKEN_401_MESSAGE = "CLI access token expired, revoked, or invalid. Generate a new token on the dashboard CLI page and run `zeuslock auth login --token <token>`.";
|
|
13
|
+
|
|
14
|
+
export class ApiError extends Error {
|
|
15
|
+
constructor(message, { status = null, data = null, responseText = "" } = {}) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = "ApiError";
|
|
18
|
+
this.status = status;
|
|
19
|
+
this.data = data;
|
|
20
|
+
this.responseText = responseText;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function login({ apiUrl, email, password, recaptchaToken = null }) {
|
|
25
|
+
const body = { email, password };
|
|
26
|
+
if (recaptchaToken) {
|
|
27
|
+
body.recaptcha_token = recaptchaToken;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return requestJson(apiUrl, "/api/auth/login", {
|
|
31
|
+
method: "POST",
|
|
32
|
+
body
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function verifyMfa({ apiUrl, session, code }) {
|
|
37
|
+
return requestJson(apiUrl, "/api/auth/verify-mfa", {
|
|
38
|
+
method: "POST",
|
|
39
|
+
body: { session, code }
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function refreshAuth({ apiUrl, refreshToken }) {
|
|
44
|
+
return requestJson(apiUrl, "/api/auth/refresh", {
|
|
45
|
+
method: "POST",
|
|
46
|
+
body: { refresh_token: refreshToken }
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getCurrentUser({ apiUrl, accessToken }) {
|
|
51
|
+
return requestJson(apiUrl, "/api/user/me", {
|
|
52
|
+
headers: {
|
|
53
|
+
Authorization: `Bearer ${accessToken}`
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getCliTokenIdentity({ apiUrl, accessToken }) {
|
|
59
|
+
return requestJson(apiUrl, "/api/auth/me", {
|
|
60
|
+
headers: {
|
|
61
|
+
Authorization: `Bearer ${accessToken}`
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function resolveApiUrlForRequest(env = process.env) {
|
|
67
|
+
const auth = await readAuthStore(env);
|
|
68
|
+
const apiKey = await readApiKeyStore(env);
|
|
69
|
+
return resolveApiUrl({ env, auth: auth || apiKey }) || DEFAULT_API_URL;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function requestApiKeyJson(endpoint, {
|
|
73
|
+
method = "GET",
|
|
74
|
+
body,
|
|
75
|
+
headers = {},
|
|
76
|
+
apiKey,
|
|
77
|
+
apiUrl,
|
|
78
|
+
signal,
|
|
79
|
+
env = process.env
|
|
80
|
+
} = {}) {
|
|
81
|
+
const stored = await readApiKeyStore(env);
|
|
82
|
+
const resolvedApiKey = String(apiKey || env.ZEUSLOCK_API_KEY || stored?.apiKey || "").trim();
|
|
83
|
+
if (!resolvedApiKey) {
|
|
84
|
+
throw new Error("API key is required. Provide --api-key, set ZEUSLOCK_API_KEY, or run `zeuslock auth api-key set`.");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const resolvedApiUrl = apiUrl || stored?.apiUrl || await resolveApiUrlForRequest(env);
|
|
88
|
+
return requestJson(resolvedApiUrl, endpoint, {
|
|
89
|
+
method,
|
|
90
|
+
body,
|
|
91
|
+
signal,
|
|
92
|
+
headers: {
|
|
93
|
+
...headers,
|
|
94
|
+
"X-API-Key": resolvedApiKey
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function requestApiKeyFormData(endpoint, formData, {
|
|
100
|
+
headers = {},
|
|
101
|
+
apiKey,
|
|
102
|
+
apiUrl,
|
|
103
|
+
signal,
|
|
104
|
+
env = process.env
|
|
105
|
+
} = {}) {
|
|
106
|
+
const stored = await readApiKeyStore(env);
|
|
107
|
+
const resolvedApiKey = String(apiKey || env.ZEUSLOCK_API_KEY || stored?.apiKey || "").trim();
|
|
108
|
+
if (!resolvedApiKey) {
|
|
109
|
+
throw new Error("API key is required. Provide --api-key, set ZEUSLOCK_API_KEY, or run `zeuslock auth api-key set`.");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const resolvedApiUrl = apiUrl || stored?.apiUrl || await resolveApiUrlForRequest(env);
|
|
113
|
+
return requestFormData(resolvedApiUrl, endpoint, formData, {
|
|
114
|
+
signal,
|
|
115
|
+
headers: {
|
|
116
|
+
...headers,
|
|
117
|
+
"X-API-Key": resolvedApiKey
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export async function requestAuthenticatedJson(endpoint, {
|
|
123
|
+
method = "GET",
|
|
124
|
+
body,
|
|
125
|
+
headers = {},
|
|
126
|
+
env = process.env
|
|
127
|
+
} = {}) {
|
|
128
|
+
const result = await requestAuthenticatedJsonWithMeta(endpoint, {
|
|
129
|
+
method,
|
|
130
|
+
body,
|
|
131
|
+
headers,
|
|
132
|
+
env
|
|
133
|
+
});
|
|
134
|
+
return result.data;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function requestAuthenticatedJsonWithMeta(endpoint, {
|
|
138
|
+
method = "GET",
|
|
139
|
+
body,
|
|
140
|
+
headers = {},
|
|
141
|
+
env = process.env
|
|
142
|
+
} = {}) {
|
|
143
|
+
const context = await resolveAuthenticatedContext(env);
|
|
144
|
+
const requestWithToken = (token) => requestJson(context.apiUrl, endpoint, {
|
|
145
|
+
method,
|
|
146
|
+
body,
|
|
147
|
+
headers: {
|
|
148
|
+
...headers,
|
|
149
|
+
Authorization: `Bearer ${token}`
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
return {
|
|
155
|
+
data: await requestWithToken(context.accessToken),
|
|
156
|
+
apiUrl: context.apiUrl,
|
|
157
|
+
authMode: context.mode,
|
|
158
|
+
authSource: context.source
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (context.mode === "token" && error instanceof ApiError && error.status === 401) {
|
|
162
|
+
throw new Error(CLI_TOKEN_401_MESSAGE);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (context.mode === "token" || !(error instanceof ApiError) || error.status !== 401) {
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!context.auth?.refreshToken) {
|
|
171
|
+
await deleteAuthStore(env);
|
|
172
|
+
throw new Error("Session expired. Run `zeuslock auth login` again.");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
try {
|
|
176
|
+
const tokenResponse = await refreshAuth({ apiUrl: context.apiUrl, refreshToken: context.auth.refreshToken });
|
|
177
|
+
const refreshedAuth = createAuthRecord({
|
|
178
|
+
apiUrl: context.apiUrl,
|
|
179
|
+
email: context.auth.email,
|
|
180
|
+
tokenResponse,
|
|
181
|
+
previous: context.auth
|
|
182
|
+
});
|
|
183
|
+
await writeAuthStore(refreshedAuth, env);
|
|
184
|
+
return {
|
|
185
|
+
data: await requestWithToken(refreshedAuth.accessToken),
|
|
186
|
+
apiUrl: context.apiUrl,
|
|
187
|
+
authMode: refreshedAuth.mode,
|
|
188
|
+
authSource: context.source
|
|
189
|
+
};
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (error instanceof ApiError && error.status === 401) {
|
|
192
|
+
await deleteAuthStore(env);
|
|
193
|
+
throw new Error("Session expired. Run `zeuslock auth login` again.");
|
|
194
|
+
}
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async function resolveAuthenticatedContext(env) {
|
|
200
|
+
const auth = await readAuthStore(env);
|
|
201
|
+
const commandToken = getCommandToken(env);
|
|
202
|
+
const envToken = String(env.ZEUSLOCK_API_TOKEN || "").trim();
|
|
203
|
+
const apiUrl = resolveApiUrl({ env, auth }) || DEFAULT_API_URL;
|
|
204
|
+
|
|
205
|
+
if (commandToken) {
|
|
206
|
+
assertCliAccessToken(commandToken, "--token");
|
|
207
|
+
return {
|
|
208
|
+
auth,
|
|
209
|
+
apiUrl,
|
|
210
|
+
accessToken: commandToken,
|
|
211
|
+
mode: "token",
|
|
212
|
+
source: "flag"
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (envToken) {
|
|
217
|
+
return {
|
|
218
|
+
auth,
|
|
219
|
+
apiUrl,
|
|
220
|
+
accessToken: envToken,
|
|
221
|
+
mode: "token",
|
|
222
|
+
source: "env"
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (auth?.accessToken) {
|
|
227
|
+
return {
|
|
228
|
+
auth,
|
|
229
|
+
apiUrl,
|
|
230
|
+
accessToken: auth.accessToken,
|
|
231
|
+
mode: auth.mode === "token" ? "token" : "session",
|
|
232
|
+
source: "store"
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
throw new Error("Not logged in. Run `zeuslock auth login` first.");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function requestJson(apiUrl, endpoint, { method = "GET", body, headers = {}, signal } = {}) {
|
|
240
|
+
const url = new URL(endpoint, ensureTrailingSlash(apiUrl));
|
|
241
|
+
const requestHeaders = {
|
|
242
|
+
Accept: "application/json",
|
|
243
|
+
...headers
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
const request = {
|
|
247
|
+
method,
|
|
248
|
+
headers: requestHeaders,
|
|
249
|
+
signal
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
if (body !== undefined) {
|
|
253
|
+
requestHeaders["Content-Type"] = "application/json";
|
|
254
|
+
request.body = JSON.stringify(body);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return fetchJson(url, request);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export async function requestFormData(apiUrl, endpoint, formData, { headers = {}, signal } = {}) {
|
|
261
|
+
const url = new URL(endpoint, ensureTrailingSlash(apiUrl));
|
|
262
|
+
const request = {
|
|
263
|
+
method: "POST",
|
|
264
|
+
headers: {
|
|
265
|
+
Accept: "application/json",
|
|
266
|
+
...headers
|
|
267
|
+
},
|
|
268
|
+
body: formData,
|
|
269
|
+
signal
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
return fetchJson(url, request);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function fetchJson(url, request) {
|
|
276
|
+
let response;
|
|
277
|
+
try {
|
|
278
|
+
response = await fetch(url, request);
|
|
279
|
+
} catch (error) {
|
|
280
|
+
throw new ApiError(error?.message || "Network request failed");
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const contentType = response.headers.get("content-type") || "";
|
|
284
|
+
let data = null;
|
|
285
|
+
let responseText = "";
|
|
286
|
+
|
|
287
|
+
if (contentType.includes("application/json")) {
|
|
288
|
+
data = await response.json().catch(() => null);
|
|
289
|
+
} else {
|
|
290
|
+
responseText = await response.text().catch(() => "");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (!response.ok) {
|
|
294
|
+
const message =
|
|
295
|
+
data?.error ||
|
|
296
|
+
data?.detail ||
|
|
297
|
+
data?.message ||
|
|
298
|
+
responseText ||
|
|
299
|
+
`Request failed with status ${response.status}`;
|
|
300
|
+
throw new ApiError(message, { status: response.status, data, responseText });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
return data ?? {};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function ensureTrailingSlash(apiUrl) {
|
|
307
|
+
return apiUrl.endsWith("/") ? apiUrl : `${apiUrl}/`;
|
|
308
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { getConfigDir } from "./auth-store.js";
|
|
5
|
+
import { normalizeApiUrl } from "./config.js";
|
|
6
|
+
|
|
7
|
+
const API_KEY_FILE_NAME = "api-key.json";
|
|
8
|
+
|
|
9
|
+
export function getApiKeyFilePath(env = process.env) {
|
|
10
|
+
return path.join(getConfigDir(env), API_KEY_FILE_NAME);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function readApiKeyStore(env = process.env) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = await readFile(getApiKeyFilePath(env), "utf8");
|
|
16
|
+
return normalizeApiKeyRecord(JSON.parse(raw));
|
|
17
|
+
} catch (error) {
|
|
18
|
+
if (error?.code === "ENOENT") {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
throw error;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function writeApiKeyStore(record, env = process.env) {
|
|
26
|
+
const apiKeyRecord = normalizeApiKeyRecord(record);
|
|
27
|
+
if (!apiKeyRecord?.apiKey) {
|
|
28
|
+
throw new Error("Cannot save API key state without an API key");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const dir = getConfigDir(env);
|
|
32
|
+
const filePath = getApiKeyFilePath(env);
|
|
33
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
34
|
+
|
|
35
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
36
|
+
await chmod(dir, 0o700).catch(() => {});
|
|
37
|
+
await writeFile(tempPath, `${JSON.stringify(apiKeyRecord, null, 2)}\n`, {
|
|
38
|
+
mode: 0o600
|
|
39
|
+
});
|
|
40
|
+
await chmod(tempPath, 0o600).catch(() => {});
|
|
41
|
+
await rename(tempPath, filePath);
|
|
42
|
+
await chmod(filePath, 0o600).catch(() => {});
|
|
43
|
+
|
|
44
|
+
return apiKeyRecord;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function deleteApiKeyStore(env = process.env) {
|
|
48
|
+
await rm(getApiKeyFilePath(env), { force: true });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createApiKeyRecord({ apiUrl, apiKey, verified = null }) {
|
|
52
|
+
return normalizeApiKeyRecord({
|
|
53
|
+
apiUrl,
|
|
54
|
+
apiKey,
|
|
55
|
+
apiKeyMasked: maskApiKey(apiKey),
|
|
56
|
+
verified,
|
|
57
|
+
savedAt: new Date().toISOString()
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function maskApiKey(apiKey) {
|
|
62
|
+
const value = String(apiKey || "").trim();
|
|
63
|
+
if (!value) {
|
|
64
|
+
return "";
|
|
65
|
+
}
|
|
66
|
+
if (value.length <= 12) {
|
|
67
|
+
return `${value.slice(0, 4)}***`;
|
|
68
|
+
}
|
|
69
|
+
return `${value.slice(0, 8)}***${value.slice(-4)}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function normalizeApiKeyRecord(record) {
|
|
73
|
+
if (!record || typeof record !== "object") {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
apiUrl: normalizeApiUrl(record.apiUrl),
|
|
79
|
+
apiKey: typeof record.apiKey === "string" ? record.apiKey : null,
|
|
80
|
+
apiKeyMasked: typeof record.apiKeyMasked === "string" ? record.apiKeyMasked : maskApiKey(record.apiKey),
|
|
81
|
+
verified: record.verified && typeof record.verified === "object" ? record.verified : null,
|
|
82
|
+
savedAt: typeof record.savedAt === "string" ? record.savedAt : null
|
|
83
|
+
};
|
|
84
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, rm, chmod, writeFile } from "node:fs/promises";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
const AUTH_FILE_NAME = "auth.json";
|
|
6
|
+
|
|
7
|
+
export function getConfigDir(env = process.env, platform = process.platform) {
|
|
8
|
+
if (env.ZEUSLOCK_CONFIG_DIR?.trim()) {
|
|
9
|
+
return path.resolve(env.ZEUSLOCK_CONFIG_DIR.trim());
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (platform === "darwin") {
|
|
13
|
+
return path.join(os.homedir(), "Library", "Application Support", "zeuslock");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (platform === "win32") {
|
|
17
|
+
const appData = env.APPDATA?.trim() || path.join(os.homedir(), "AppData", "Roaming");
|
|
18
|
+
return path.join(appData, "ZeusLock");
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const xdgConfigHome = env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), ".config");
|
|
22
|
+
return path.join(xdgConfigHome, "zeuslock");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getAuthFilePath(env = process.env) {
|
|
26
|
+
return path.join(getConfigDir(env), AUTH_FILE_NAME);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function readAuthStore(env = process.env) {
|
|
30
|
+
const filePath = getAuthFilePath(env);
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const raw = await readFile(filePath, "utf8");
|
|
34
|
+
const parsed = JSON.parse(raw);
|
|
35
|
+
return normalizeAuthRecord(parsed);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error?.code === "ENOENT") {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function writeAuthStore(record, env = process.env) {
|
|
45
|
+
const authRecord = normalizeAuthRecord(record);
|
|
46
|
+
if (!authRecord?.accessToken) {
|
|
47
|
+
throw new Error("Cannot save auth state without an access token");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const dir = getConfigDir(env);
|
|
51
|
+
const filePath = getAuthFilePath(env);
|
|
52
|
+
const tempPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
53
|
+
|
|
54
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
55
|
+
await chmod(dir, 0o700).catch(() => {});
|
|
56
|
+
await writeFile(tempPath, `${JSON.stringify(authRecord, null, 2)}\n`, {
|
|
57
|
+
mode: 0o600
|
|
58
|
+
});
|
|
59
|
+
await chmod(tempPath, 0o600).catch(() => {});
|
|
60
|
+
await rename(tempPath, filePath);
|
|
61
|
+
await chmod(filePath, 0o600).catch(() => {});
|
|
62
|
+
|
|
63
|
+
return authRecord;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function deleteAuthStore(env = process.env) {
|
|
67
|
+
await rm(getAuthFilePath(env), { force: true });
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createAuthRecord({ apiUrl, email, tokenResponse, previous = null }) {
|
|
71
|
+
const now = new Date();
|
|
72
|
+
const expiresIn = Number(tokenResponse.expires_in);
|
|
73
|
+
const expiresAt = Number.isFinite(expiresIn)
|
|
74
|
+
? new Date(now.getTime() + expiresIn * 1000).toISOString()
|
|
75
|
+
: (previous?.expiresAt ?? null);
|
|
76
|
+
|
|
77
|
+
return normalizeAuthRecord({
|
|
78
|
+
...previous,
|
|
79
|
+
mode: "session",
|
|
80
|
+
apiUrl,
|
|
81
|
+
email: email ?? previous?.email ?? null,
|
|
82
|
+
tokenType: tokenResponse.token_type || previous?.tokenType || "Bearer",
|
|
83
|
+
accessToken: tokenResponse.access_token || previous?.accessToken,
|
|
84
|
+
refreshToken: tokenResponse.refresh_token || previous?.refreshToken || null,
|
|
85
|
+
idToken: tokenResponse.id_token || previous?.idToken || null,
|
|
86
|
+
expiresAt,
|
|
87
|
+
savedAt: now.toISOString()
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function createCliTokenAuthRecord({ apiUrl, accessToken, email = null }) {
|
|
92
|
+
return normalizeAuthRecord({
|
|
93
|
+
mode: "token",
|
|
94
|
+
apiUrl,
|
|
95
|
+
email,
|
|
96
|
+
tokenType: "Bearer",
|
|
97
|
+
accessToken,
|
|
98
|
+
refreshToken: null,
|
|
99
|
+
idToken: null,
|
|
100
|
+
expiresAt: null,
|
|
101
|
+
savedAt: new Date().toISOString()
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function normalizeAuthRecord(record) {
|
|
106
|
+
if (!record || typeof record !== "object") {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const mode = record.mode === "token" ? "token" : "session";
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
mode,
|
|
114
|
+
apiUrl: typeof record.apiUrl === "string" ? record.apiUrl : null,
|
|
115
|
+
email: typeof record.email === "string" ? record.email : null,
|
|
116
|
+
tokenType: typeof record.tokenType === "string" ? record.tokenType : "Bearer",
|
|
117
|
+
accessToken: typeof record.accessToken === "string" ? record.accessToken : null,
|
|
118
|
+
refreshToken: mode === "token" ? null : typeof record.refreshToken === "string" ? record.refreshToken : null,
|
|
119
|
+
idToken: mode === "token" ? null : typeof record.idToken === "string" ? record.idToken : null,
|
|
120
|
+
expiresAt: mode === "token" ? null : typeof record.expiresAt === "string" ? record.expiresAt : null,
|
|
121
|
+
savedAt: typeof record.savedAt === "string" ? record.savedAt : null
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const CLI_TOKEN_PATTERN = /^zlu_[A-Za-z0-9_-]{43}$/;
|
|
2
|
+
|
|
3
|
+
export function isCliAccessToken(value) {
|
|
4
|
+
return CLI_TOKEN_PATTERN.test(String(value || "").trim());
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function assertCliAccessToken(value, optionName = "--token") {
|
|
8
|
+
if (!isCliAccessToken(value)) {
|
|
9
|
+
throw new Error(`Invalid ${optionName}. Expected zlu_ followed by 43 URL-safe characters.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function maskCliToken(token) {
|
|
14
|
+
const value = String(token || "").trim();
|
|
15
|
+
if (!value) {
|
|
16
|
+
return "";
|
|
17
|
+
}
|
|
18
|
+
if (value.length <= 12) {
|
|
19
|
+
return `${value.slice(0, 4)}***`;
|
|
20
|
+
}
|
|
21
|
+
return `${value.slice(0, 8)}***...${value.slice(-4)}`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const commandTokens = new WeakMap();
|
|
2
|
+
|
|
3
|
+
export function setCommandToken(env, token) {
|
|
4
|
+
if (!env || typeof env !== "object") {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
commandTokens.set(env, String(token || "").trim());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getCommandToken(env) {
|
|
11
|
+
if (!env || typeof env !== "object") {
|
|
12
|
+
return "";
|
|
13
|
+
}
|
|
14
|
+
return commandTokens.get(env) || "";
|
|
15
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getCommandToken } from "./command-token.js";
|
|
2
|
+
|
|
3
|
+
export const DEFAULT_API_URL = "https://api.zeuslock.ai";
|
|
4
|
+
|
|
5
|
+
export function readEnvConfig(env = process.env) {
|
|
6
|
+
const apiUrl = normalizeApiUrl(env.ZEUSLOCK_API_URL);
|
|
7
|
+
const hasApiToken = Boolean(getCommandToken(env) || env.ZEUSLOCK_API_TOKEN?.trim());
|
|
8
|
+
const hasApiKey = Boolean(env.ZEUSLOCK_API_KEY?.trim());
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
apiUrl,
|
|
12
|
+
hasApiToken,
|
|
13
|
+
hasApiKey
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function resolveApiUrl({ env = process.env, auth = null } = {}) {
|
|
18
|
+
return normalizeApiUrl(env.ZEUSLOCK_API_URL) || normalizeApiUrl(auth?.apiUrl) || DEFAULT_API_URL;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function normalizeApiUrl(value) {
|
|
22
|
+
const candidate = typeof value === "string" ? value.trim() : "";
|
|
23
|
+
if (!candidate) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return candidate.replace(/\/+$/, "");
|
|
27
|
+
}
|