@alchemy/cli 0.6.1 → 0.7.0-alpha.5
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 +49 -61
- package/dist/auth-I5WFLU46.js +16 -0
- package/dist/{auth-S4DTOWW3.js → auth-JGON2JU6.js} +10 -8
- package/dist/{chunk-7GD5HACA.js → chunk-5MXODL63.js} +3 -3
- package/dist/{chunk-NBDWF4ZQ.js → chunk-A6L3WCJN.js} +32 -22
- package/dist/{chunk-BAAQ7ELR.js → chunk-B3R6PRAL.js} +59 -4
- package/dist/{chunk-FFMNT74F.js → chunk-HSKKIATB.js} +125 -76
- package/dist/{chunk-KDMIWPZH.js → chunk-HYCRHNPX.js} +1 -1
- package/dist/chunk-MUT4TFQ5.js +64 -0
- package/dist/{chunk-UMKDYHMO.js → chunk-NT3G6BKD.js} +54 -99
- package/dist/{chunk-ATX65U7J.js → chunk-PKAN5FKD.js} +570 -41
- package/dist/{chunk-56ZVYB4G.js → chunk-QEDAULQ2.js} +263 -222
- package/dist/errors-3CNFGAXT.js +54 -0
- package/dist/index.js +6668 -2166
- package/dist/{interactive-QJ4REXWB.js → interactive-VXPD6N7Z.js} +56 -61
- package/dist/{onboarding-3WIM6PVV.js → onboarding-CEHXSNYD.js} +14 -11
- package/dist/resolve-X7HLVLGA.js +50 -0
- package/package.json +12 -3
- package/scripts/postinstall.cjs +69 -1
- package/dist/auth-QB3BA7AN.js +0 -17
- package/dist/chunk-JQRGILIS.js +0 -53
- package/dist/chunk-T5Z2GJUX.js +0 -331
- package/dist/credential-storage-T6FFW7DG.js +0 -14
- package/dist/resolve-HXKHDOJZ.js +0 -31
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
4
|
getBaseDomain
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QEDAULQ2.js";
|
|
6
6
|
|
|
7
7
|
// src/lib/auth.ts
|
|
8
8
|
import { createHash, randomBytes } from "crypto";
|
|
@@ -130,7 +130,7 @@ ${SHARED_STYLE}
|
|
|
130
130
|
// src/lib/auth.ts
|
|
131
131
|
var AUTH_PORT = 16424;
|
|
132
132
|
var AUTH_CALLBACK_PATH = "/callback";
|
|
133
|
-
var
|
|
133
|
+
var DEFAULT_EXPIRES_IN_SECONDS = 90 * 24 * 60 * 60;
|
|
134
134
|
function getAuthBaseUrl() {
|
|
135
135
|
return process.env.ALCHEMY_AUTH_URL || `https://auth.${getBaseDomain()}`;
|
|
136
136
|
}
|
|
@@ -140,40 +140,56 @@ function generateCodeVerifier() {
|
|
|
140
140
|
function deriveCodeChallenge(verifier) {
|
|
141
141
|
return createHash("sha256").update(verifier).digest("base64url");
|
|
142
142
|
}
|
|
143
|
-
function
|
|
144
|
-
return randomBytes(32).toString("base64url");
|
|
145
|
-
}
|
|
146
|
-
function getAuthorizeUrl(port, codeChallenge, state) {
|
|
143
|
+
function getLoginUrl(port, codeChallenge) {
|
|
147
144
|
const base = getAuthBaseUrl();
|
|
148
|
-
const
|
|
149
|
-
url.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
url
|
|
154
|
-
url.searchParams.set("state", state);
|
|
155
|
-
return url.toString();
|
|
156
|
-
}
|
|
157
|
-
function prepareBrowserLogin(port = AUTH_PORT) {
|
|
158
|
-
const codeVerifier = generateCodeVerifier();
|
|
159
|
-
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
160
|
-
const state = generateState();
|
|
161
|
-
return {
|
|
162
|
-
authorizeUrl: getAuthorizeUrl(port, codeChallenge, state),
|
|
163
|
-
codeVerifier,
|
|
164
|
-
state
|
|
165
|
-
};
|
|
145
|
+
const redirect = encodeURIComponent(`http://localhost:${port}${AUTH_CALLBACK_PATH}`);
|
|
146
|
+
let url = `${base}/login?redirectUrl=${redirect}&_t=${Date.now()}`;
|
|
147
|
+
if (codeChallenge) {
|
|
148
|
+
url += `&code_challenge=${encodeURIComponent(codeChallenge)}`;
|
|
149
|
+
}
|
|
150
|
+
return url;
|
|
166
151
|
}
|
|
167
152
|
function openBrowser(url) {
|
|
168
153
|
const cmd = platform() === "darwin" ? "open" : platform() === "win32" ? "start" : "xdg-open";
|
|
169
154
|
execFile(cmd, [url]);
|
|
170
155
|
}
|
|
156
|
+
function closeServer(args) {
|
|
157
|
+
const { server, sockets } = args;
|
|
158
|
+
return new Promise((resolve) => {
|
|
159
|
+
server.close(() => resolve());
|
|
160
|
+
for (const socket of sockets) {
|
|
161
|
+
socket.destroy();
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
async function sendCallbackResponse(args) {
|
|
166
|
+
const { server, sockets, req, res, statusCode, body } = args;
|
|
167
|
+
req.socket.setKeepAlive(false);
|
|
168
|
+
res.shouldKeepAlive = false;
|
|
169
|
+
res.writeHead(statusCode, {
|
|
170
|
+
"Content-Type": "text/html",
|
|
171
|
+
Connection: "close"
|
|
172
|
+
});
|
|
173
|
+
await new Promise((resolve) => {
|
|
174
|
+
res.end(body, () => resolve());
|
|
175
|
+
});
|
|
176
|
+
await closeServer({ server, sockets });
|
|
177
|
+
}
|
|
171
178
|
function waitForCallback(port, timeoutMs = 12e4) {
|
|
172
|
-
|
|
179
|
+
let cancelFn;
|
|
180
|
+
const promise = new Promise((resolve, reject) => {
|
|
181
|
+
const sockets = /* @__PURE__ */ new Set();
|
|
173
182
|
const timer = setTimeout(() => {
|
|
174
183
|
server.close();
|
|
184
|
+
for (const socket of sockets) {
|
|
185
|
+
socket.destroy();
|
|
186
|
+
}
|
|
175
187
|
reject(new Error("Authentication timed out. Please try again."));
|
|
176
188
|
}, timeoutMs);
|
|
189
|
+
cancelFn = () => {
|
|
190
|
+
clearTimeout(timer);
|
|
191
|
+
closeServer({ server, sockets });
|
|
192
|
+
};
|
|
177
193
|
const server = createServer((req, res) => {
|
|
178
194
|
const url = new URL(req.url || "/", `http://localhost:${port}`);
|
|
179
195
|
if (url.pathname !== AUTH_CALLBACK_PATH) {
|
|
@@ -185,35 +201,52 @@ function waitForCallback(port, timeoutMs = 12e4) {
|
|
|
185
201
|
if (error) {
|
|
186
202
|
const description = url.searchParams.get("error_description") || error;
|
|
187
203
|
clearTimeout(timer);
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
204
|
+
void sendCallbackResponse({
|
|
205
|
+
server,
|
|
206
|
+
sockets,
|
|
207
|
+
req,
|
|
208
|
+
res,
|
|
209
|
+
statusCode: 200,
|
|
210
|
+
body: authErrorHtml(description)
|
|
211
|
+
}).finally(() => {
|
|
212
|
+
reject(new Error(`Authentication failed: ${description}`));
|
|
213
|
+
});
|
|
192
214
|
return;
|
|
193
215
|
}
|
|
194
216
|
const code = url.searchParams.get("code");
|
|
195
217
|
if (!code) {
|
|
196
218
|
clearTimeout(timer);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
219
|
+
void sendCallbackResponse({
|
|
220
|
+
server,
|
|
221
|
+
sockets,
|
|
222
|
+
req,
|
|
223
|
+
res,
|
|
224
|
+
statusCode: 400,
|
|
225
|
+
body: "Missing auth code"
|
|
226
|
+
}).finally(() => {
|
|
227
|
+
reject(new Error("Authentication callback missing auth code."));
|
|
228
|
+
});
|
|
201
229
|
return;
|
|
202
230
|
}
|
|
203
231
|
clearTimeout(timer);
|
|
204
232
|
resolve({
|
|
205
233
|
code,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
server
|
|
216
|
-
|
|
234
|
+
sendSuccess: () => sendCallbackResponse({
|
|
235
|
+
server,
|
|
236
|
+
sockets,
|
|
237
|
+
req,
|
|
238
|
+
res,
|
|
239
|
+
statusCode: 200,
|
|
240
|
+
body: AUTH_SUCCESS_HTML
|
|
241
|
+
}),
|
|
242
|
+
sendError: (message) => sendCallbackResponse({
|
|
243
|
+
server,
|
|
244
|
+
sockets,
|
|
245
|
+
req,
|
|
246
|
+
res,
|
|
247
|
+
statusCode: 500,
|
|
248
|
+
body: authErrorHtml(message)
|
|
249
|
+
})
|
|
217
250
|
});
|
|
218
251
|
});
|
|
219
252
|
server.on("error", (err) => {
|
|
@@ -229,60 +262,75 @@ function waitForCallback(port, timeoutMs = 12e4) {
|
|
|
229
262
|
reject(err);
|
|
230
263
|
}
|
|
231
264
|
});
|
|
265
|
+
server.on("connection", (socket) => {
|
|
266
|
+
sockets.add(socket);
|
|
267
|
+
socket.on("close", () => {
|
|
268
|
+
sockets.delete(socket);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
232
271
|
server.listen(port, "127.0.0.1");
|
|
233
272
|
server.unref();
|
|
234
273
|
});
|
|
274
|
+
return { promise, cancel: () => cancelFn() };
|
|
235
275
|
}
|
|
236
276
|
async function exchangeCodeForToken(code, port, options) {
|
|
237
277
|
const baseUrl = getAuthBaseUrl();
|
|
238
278
|
const redirectUri = `http://localhost:${port}${AUTH_CALLBACK_PATH}`;
|
|
239
|
-
const body =
|
|
240
|
-
grant_type: "authorization_code",
|
|
279
|
+
const body = {
|
|
241
280
|
code,
|
|
242
|
-
redirect_uri: redirectUri
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
281
|
+
redirect_uri: redirectUri
|
|
282
|
+
};
|
|
283
|
+
if (options?.expiresInSeconds) {
|
|
284
|
+
body.expires_in_seconds = options.expiresInSeconds;
|
|
285
|
+
}
|
|
286
|
+
if (options?.codeVerifier) {
|
|
287
|
+
body.code_verifier = options.codeVerifier;
|
|
288
|
+
}
|
|
289
|
+
const response = await fetch(`${baseUrl}/api/cli/token`, {
|
|
247
290
|
method: "POST",
|
|
248
|
-
headers: { "Content-Type": "application/
|
|
249
|
-
body:
|
|
291
|
+
headers: { "Content-Type": "application/json" },
|
|
292
|
+
body: JSON.stringify(body)
|
|
250
293
|
});
|
|
251
294
|
if (!response.ok) {
|
|
252
295
|
const errBody = await response.json().catch(() => ({}));
|
|
253
|
-
|
|
254
|
-
|
|
296
|
+
throw new Error(
|
|
297
|
+
errBody.error || `Token exchange failed (HTTP ${response.status})`
|
|
298
|
+
);
|
|
255
299
|
}
|
|
256
300
|
const data = await response.json();
|
|
257
|
-
if (!data.
|
|
258
|
-
throw new Error("Token exchange response missing
|
|
301
|
+
if (!data.authToken) {
|
|
302
|
+
throw new Error("Token exchange response missing authToken");
|
|
259
303
|
}
|
|
260
|
-
|
|
261
|
-
|
|
304
|
+
return { token: data.authToken, expiresAt: data.expiresAt };
|
|
305
|
+
}
|
|
306
|
+
function prepareLogin(port = AUTH_PORT) {
|
|
307
|
+
const codeVerifier = generateCodeVerifier();
|
|
308
|
+
const codeChallenge = deriveCodeChallenge(codeVerifier);
|
|
309
|
+
const loginUrl = getLoginUrl(port, codeChallenge);
|
|
310
|
+
const handle = waitForCallback(port);
|
|
311
|
+
return { loginUrl, callbackPromise: handle.promise, codeVerifier, port, cancel: handle.cancel };
|
|
262
312
|
}
|
|
263
|
-
async function
|
|
264
|
-
const port = options?.port ?? AUTH_PORT;
|
|
265
|
-
const { authorizeUrl, codeVerifier, state } = prepared ?? prepareBrowserLogin(port);
|
|
266
|
-
const callbackPromise = waitForCallback(port);
|
|
313
|
+
async function completeLogin(prepared, options) {
|
|
267
314
|
if (!options?.skipBrowserOpen) {
|
|
268
|
-
openBrowser(
|
|
269
|
-
}
|
|
270
|
-
const callback = await callbackPromise;
|
|
271
|
-
if (callback.state !== state) {
|
|
272
|
-
callback.sendError("State mismatch \u2014 possible CSRF attack.");
|
|
273
|
-
throw new Error("OAuth state mismatch. Authentication aborted.");
|
|
315
|
+
openBrowser(prepared.loginUrl);
|
|
274
316
|
}
|
|
317
|
+
const callback = await prepared.callbackPromise;
|
|
275
318
|
try {
|
|
276
|
-
const result = await exchangeCodeForToken(callback.code, port, {
|
|
277
|
-
|
|
319
|
+
const result = await exchangeCodeForToken(callback.code, prepared.port, {
|
|
320
|
+
expiresInSeconds: options?.expiresInSeconds ?? DEFAULT_EXPIRES_IN_SECONDS,
|
|
321
|
+
codeVerifier: prepared.codeVerifier
|
|
278
322
|
});
|
|
279
|
-
callback.sendSuccess();
|
|
323
|
+
await callback.sendSuccess();
|
|
280
324
|
return result;
|
|
281
325
|
} catch (err) {
|
|
282
|
-
callback.sendError("Failed to complete authentication. Please try again.");
|
|
326
|
+
await callback.sendError("Failed to complete authentication. Please try again.");
|
|
283
327
|
throw err;
|
|
284
328
|
}
|
|
285
329
|
}
|
|
330
|
+
async function performBrowserLogin(port = AUTH_PORT, options) {
|
|
331
|
+
const prepared = prepareLogin(port);
|
|
332
|
+
return completeLogin(prepared, { expiresInSeconds: options?.expiresInSeconds });
|
|
333
|
+
}
|
|
286
334
|
async function revokeToken(token) {
|
|
287
335
|
const baseUrl = getAuthBaseUrl();
|
|
288
336
|
try {
|
|
@@ -307,12 +355,13 @@ async function revokeToken(token) {
|
|
|
307
355
|
|
|
308
356
|
export {
|
|
309
357
|
AUTH_PORT,
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
prepareBrowserLogin,
|
|
358
|
+
DEFAULT_EXPIRES_IN_SECONDS,
|
|
359
|
+
getLoginUrl,
|
|
313
360
|
openBrowser,
|
|
314
361
|
waitForCallback,
|
|
315
362
|
exchangeCodeForToken,
|
|
363
|
+
prepareLogin,
|
|
364
|
+
completeLogin,
|
|
316
365
|
performBrowserLogin,
|
|
317
366
|
revokeToken
|
|
318
367
|
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
|
+
import {
|
|
4
|
+
isInteractiveAllowed
|
|
5
|
+
} from "./chunk-HYCRHNPX.js";
|
|
6
|
+
import {
|
|
7
|
+
resolveAuthToken
|
|
8
|
+
} from "./chunk-PKAN5FKD.js";
|
|
9
|
+
|
|
10
|
+
// src/lib/onboarding.ts
|
|
11
|
+
function hasAPIKey(cfg) {
|
|
12
|
+
return Boolean(cfg.api_key?.trim());
|
|
13
|
+
}
|
|
14
|
+
function hasAccessKeyAndApp(cfg) {
|
|
15
|
+
return Boolean(cfg.access_key?.trim() && cfg.app?.id && cfg.app.apiKey);
|
|
16
|
+
}
|
|
17
|
+
function hasX402Wallet(cfg) {
|
|
18
|
+
return cfg.x402 === true && Boolean(cfg.wallet_key_file?.trim());
|
|
19
|
+
}
|
|
20
|
+
function hasAuthToken(cfg) {
|
|
21
|
+
return resolveAuthToken(cfg) !== void 0;
|
|
22
|
+
}
|
|
23
|
+
function getSetupMethod(cfg) {
|
|
24
|
+
if (hasAPIKey(cfg)) return "api_key";
|
|
25
|
+
if (hasAccessKeyAndApp(cfg)) return "access_key_app";
|
|
26
|
+
if (hasX402Wallet(cfg)) return "x402_wallet";
|
|
27
|
+
if (hasAuthToken(cfg)) return "auth_token";
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
function isSetupComplete(cfg) {
|
|
31
|
+
return getSetupMethod(cfg) !== null;
|
|
32
|
+
}
|
|
33
|
+
function getSetupStatus(cfg) {
|
|
34
|
+
const satisfiedBy = getSetupMethod(cfg);
|
|
35
|
+
if (satisfiedBy) {
|
|
36
|
+
return {
|
|
37
|
+
complete: true,
|
|
38
|
+
satisfiedBy,
|
|
39
|
+
missing: [],
|
|
40
|
+
nextCommands: []
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
complete: false,
|
|
45
|
+
satisfiedBy: null,
|
|
46
|
+
missing: ["Provide one auth path: alchemy auth OR api-key OR access-key+app OR SIWx wallet"],
|
|
47
|
+
nextCommands: [
|
|
48
|
+
"alchemy auth",
|
|
49
|
+
"alchemy config set app",
|
|
50
|
+
"alchemy config set access-key <key> && alchemy config set app <app-id>",
|
|
51
|
+
"alchemy wallet connect --mode local --chain evm && alchemy config set x402 true"
|
|
52
|
+
]
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function shouldRunOnboarding(program, cfg) {
|
|
56
|
+
return isInteractiveAllowed(program) && !isSetupComplete(cfg);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
getSetupMethod,
|
|
61
|
+
isSetupComplete,
|
|
62
|
+
getSetupStatus,
|
|
63
|
+
shouldRunOnboarding
|
|
64
|
+
};
|
|
@@ -1,26 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
revokeToken,
|
|
9
|
-
waitForCallback
|
|
10
|
-
} from "./chunk-FFMNT74F.js";
|
|
4
|
+
completeLogin,
|
|
5
|
+
prepareLogin,
|
|
6
|
+
revokeToken
|
|
7
|
+
} from "./chunk-HSKKIATB.js";
|
|
11
8
|
import {
|
|
12
9
|
isInteractiveAllowed
|
|
13
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HYCRHNPX.js";
|
|
14
11
|
import {
|
|
15
12
|
AdminClient,
|
|
16
13
|
resolveAuthToken
|
|
17
|
-
} from "./chunk-
|
|
18
|
-
import {
|
|
19
|
-
deleteCredentials,
|
|
20
|
-
getCredentials,
|
|
21
|
-
getStorageBackend,
|
|
22
|
-
saveCredentials
|
|
23
|
-
} from "./chunk-JQRGILIS.js";
|
|
14
|
+
} from "./chunk-PKAN5FKD.js";
|
|
24
15
|
import {
|
|
25
16
|
bold,
|
|
26
17
|
brand,
|
|
@@ -29,12 +20,13 @@ import {
|
|
|
29
20
|
promptAutocomplete,
|
|
30
21
|
promptText,
|
|
31
22
|
withSpinner
|
|
32
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-A6L3WCJN.js";
|
|
33
24
|
import {
|
|
25
|
+
configPath,
|
|
34
26
|
load,
|
|
35
27
|
maskIf,
|
|
36
28
|
save
|
|
37
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-B3R6PRAL.js";
|
|
38
30
|
import {
|
|
39
31
|
CLIError,
|
|
40
32
|
ErrorCode,
|
|
@@ -42,7 +34,7 @@ import {
|
|
|
42
34
|
exitWithError,
|
|
43
35
|
isJSONMode,
|
|
44
36
|
printHuman
|
|
45
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-QEDAULQ2.js";
|
|
46
38
|
|
|
47
39
|
// src/commands/auth.ts
|
|
48
40
|
function registerAuth(program) {
|
|
@@ -51,7 +43,7 @@ function registerAuth(program) {
|
|
|
51
43
|
const yes = opts.yes || cmd.opts().yes;
|
|
52
44
|
try {
|
|
53
45
|
if (!opts.force) {
|
|
54
|
-
const existing =
|
|
46
|
+
const existing = resolveAuthToken();
|
|
55
47
|
if (existing) {
|
|
56
48
|
printHuman(
|
|
57
49
|
` ${green("\u2713")} Already authenticated
|
|
@@ -64,108 +56,79 @@ function registerAuth(program) {
|
|
|
64
56
|
}
|
|
65
57
|
}
|
|
66
58
|
if (opts.force) {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
if (cfg2.auth_token) {
|
|
72
|
-
await revokeToken(cfg2.auth_token);
|
|
73
|
-
save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
|
|
74
|
-
}
|
|
75
|
-
} else {
|
|
76
|
-
await revokeToken(tokenToRevoke);
|
|
59
|
+
const cfg2 = load();
|
|
60
|
+
if (cfg2.auth_token) {
|
|
61
|
+
await revokeToken(cfg2.auth_token);
|
|
62
|
+
save({ ...cfg2, auth_token: void 0, auth_token_expires_at: void 0 });
|
|
77
63
|
}
|
|
78
|
-
await deleteCredentials();
|
|
79
64
|
}
|
|
80
|
-
const prepared =
|
|
81
|
-
const callbackPromise = waitForCallback(AUTH_PORT);
|
|
65
|
+
const prepared = prepareLogin();
|
|
82
66
|
if (!isJSONMode()) {
|
|
83
67
|
console.log("");
|
|
84
68
|
console.log(` ${brand("\u25C6")} ${bold("Alchemy Authentication")}`);
|
|
85
69
|
console.log(` ${dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`);
|
|
86
70
|
console.log("");
|
|
87
|
-
console.log(` ${dim(prepared.
|
|
71
|
+
console.log(` ${dim(prepared.loginUrl)}`);
|
|
88
72
|
console.log("");
|
|
89
73
|
}
|
|
90
|
-
|
|
74
|
+
const promptAbort = new AbortController();
|
|
75
|
+
let earlyAuth = false;
|
|
76
|
+
prepared.callbackPromise.then(() => {
|
|
77
|
+
earlyAuth = true;
|
|
78
|
+
promptAbort.abort();
|
|
79
|
+
}).catch(() => {
|
|
80
|
+
});
|
|
91
81
|
if (!yes && !isJSONMode() && isInteractiveAllowed(program)) {
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
if (promptResult !== "callback_received") {
|
|
101
|
-
if (!isJSONMode()) {
|
|
102
|
-
console.log(` Opening browser to log in...`);
|
|
103
|
-
console.log(` ${dim("Waiting for authentication...")}`);
|
|
104
|
-
}
|
|
105
|
-
openBrowser(prepared.authorizeUrl);
|
|
106
|
-
browserOpened = true;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (!browserOpened && yes) {
|
|
110
|
-
if (!isJSONMode()) {
|
|
111
|
-
console.log(` Opening browser to log in...`);
|
|
112
|
-
console.log(` ${dim("Waiting for authentication...")}`);
|
|
82
|
+
const answer = await promptText({
|
|
83
|
+
message: "Press Enter to open browser, or paste the link above to authenticate",
|
|
84
|
+
cancelMessage: "Login cancelled.",
|
|
85
|
+
abortSignal: promptAbort.signal
|
|
86
|
+
});
|
|
87
|
+
if (answer === null) {
|
|
88
|
+
prepared.cancel();
|
|
89
|
+
return;
|
|
113
90
|
}
|
|
114
|
-
openBrowser(prepared.authorizeUrl);
|
|
115
91
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
throw new Error("OAuth state mismatch. Authentication aborted.");
|
|
120
|
-
}
|
|
121
|
-
let result;
|
|
122
|
-
try {
|
|
123
|
-
result = await exchangeCodeForToken(callback.code, AUTH_PORT, {
|
|
124
|
-
codeVerifier: prepared.codeVerifier
|
|
125
|
-
});
|
|
126
|
-
callback.sendSuccess();
|
|
127
|
-
} catch (err) {
|
|
128
|
-
callback.sendError("Failed to complete authentication. Please try again.");
|
|
129
|
-
throw err;
|
|
92
|
+
if (!earlyAuth && !isJSONMode()) {
|
|
93
|
+
console.log(` Opening browser to log in...`);
|
|
94
|
+
console.log(` ${dim("Waiting for authentication...")}`);
|
|
130
95
|
}
|
|
131
|
-
await
|
|
96
|
+
const result = await completeLogin(prepared, {
|
|
97
|
+
skipBrowserOpen: earlyAuth
|
|
98
|
+
});
|
|
99
|
+
const cfg = load();
|
|
100
|
+
const hasConfiguredApp = Boolean(cfg.app);
|
|
101
|
+
save({
|
|
102
|
+
...cfg,
|
|
132
103
|
auth_token: result.token,
|
|
133
104
|
auth_token_expires_at: result.expiresAt
|
|
134
105
|
});
|
|
135
|
-
const cfg = load();
|
|
136
|
-
if (cfg.auth_token) {
|
|
137
|
-
save({ ...cfg, auth_token: void 0, auth_token_expires_at: void 0 });
|
|
138
|
-
}
|
|
139
106
|
const expiresAt = result.expiresAt;
|
|
140
|
-
const backend = await getStorageBackend();
|
|
141
107
|
printHuman(
|
|
142
108
|
` ${green("\u2713")} Logged in successfully
|
|
143
|
-
${dim("
|
|
109
|
+
${dim("Token saved to")} ${configPath()}
|
|
144
110
|
${dim("Expires:")} ${expiresAt}
|
|
145
111
|
`,
|
|
146
112
|
{
|
|
147
113
|
status: "authenticated",
|
|
148
114
|
expiresAt,
|
|
149
|
-
|
|
115
|
+
configPath: configPath()
|
|
150
116
|
}
|
|
151
117
|
);
|
|
152
|
-
if (isInteractiveAllowed(program)) {
|
|
118
|
+
if (isInteractiveAllowed(program) && !hasConfiguredApp) {
|
|
153
119
|
await selectAppAfterAuth(result.token);
|
|
154
120
|
}
|
|
155
|
-
process.exit(0);
|
|
156
121
|
} catch (err) {
|
|
157
122
|
exitWithError(
|
|
158
123
|
err instanceof CLIError ? err : new CLIError(ErrorCode.AUTH_REQUIRED, String(err.message))
|
|
159
124
|
);
|
|
160
125
|
}
|
|
161
126
|
});
|
|
162
|
-
cmd.command("status").description("Show current authentication status").action(
|
|
127
|
+
cmd.command("status").description("Show current authentication status").action(() => {
|
|
163
128
|
try {
|
|
164
|
-
const creds = await getCredentials();
|
|
165
129
|
const cfg = load();
|
|
166
|
-
const validToken =
|
|
167
|
-
|
|
168
|
-
if (!hasToken) {
|
|
130
|
+
const validToken = resolveAuthToken(cfg);
|
|
131
|
+
if (!cfg.auth_token) {
|
|
169
132
|
printHuman(
|
|
170
133
|
` ${dim("Not authenticated. Run")} alchemy auth ${dim("to log in.")}
|
|
171
134
|
`,
|
|
@@ -181,20 +144,15 @@ function registerAuth(program) {
|
|
|
181
144
|
);
|
|
182
145
|
return;
|
|
183
146
|
}
|
|
184
|
-
const expiresAt = creds?.auth_token_expires_at || cfg.auth_token_expires_at || "unknown";
|
|
185
|
-
const backend = await getStorageBackend();
|
|
186
|
-
const storedIn = creds?.auth_token ? backend : "config file (legacy)";
|
|
187
147
|
printHuman(
|
|
188
148
|
` ${green("\u2713")} Authenticated
|
|
189
149
|
${dim("Token:")} ${maskIf(validToken)}
|
|
190
|
-
${dim("
|
|
191
|
-
${dim("Expires:")} ${expiresAt}
|
|
150
|
+
${dim("Expires:")} ${cfg.auth_token_expires_at || "unknown"}
|
|
192
151
|
`,
|
|
193
152
|
{
|
|
194
153
|
authenticated: true,
|
|
195
154
|
expired: false,
|
|
196
|
-
expiresAt
|
|
197
|
-
storageBackend: storedIn
|
|
155
|
+
expiresAt: cfg.auth_token_expires_at
|
|
198
156
|
}
|
|
199
157
|
);
|
|
200
158
|
} catch (err) {
|
|
@@ -203,17 +161,14 @@ function registerAuth(program) {
|
|
|
203
161
|
});
|
|
204
162
|
cmd.command("logout").description("Clear saved authentication token").action(async () => {
|
|
205
163
|
try {
|
|
206
|
-
const creds = await getCredentials();
|
|
207
164
|
const cfg = load();
|
|
208
|
-
const activeToken = creds?.auth_token || cfg.auth_token;
|
|
209
165
|
let revokeResult;
|
|
210
|
-
if (
|
|
211
|
-
revokeResult = await revokeToken(
|
|
166
|
+
if (cfg.auth_token) {
|
|
167
|
+
revokeResult = await revokeToken(cfg.auth_token);
|
|
212
168
|
}
|
|
213
|
-
|
|
214
|
-
const { auth_token: _, auth_token_expires_at: __, app: ___, ...rest } = cfg;
|
|
169
|
+
const { auth_token: _, auth_token_expires_at: __, ...rest } = cfg;
|
|
215
170
|
save(rest);
|
|
216
|
-
if (!
|
|
171
|
+
if (!cfg.auth_token) {
|
|
217
172
|
printHuman(
|
|
218
173
|
` ${dim("No active session.")}
|
|
219
174
|
`,
|