@kidd-cli/core 0.2.0 → 0.3.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/dist/{config-Db_sjFU-.js → config-D8e5qxLp.js} +5 -17
- package/dist/config-D8e5qxLp.js.map +1 -0
- package/dist/{create-store-D-fQpCql.js → create-store-OHdkm_Yt.js} +3 -4
- package/dist/{create-store-D-fQpCql.js.map → create-store-OHdkm_Yt.js.map} +1 -1
- package/dist/index.d.ts +31 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +75 -40
- package/dist/index.js.map +1 -1
- package/dist/lib/config.js +3 -4
- package/dist/lib/logger.d.ts +1 -1
- package/dist/lib/logger.js +1 -2
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/project.d.ts +1 -1
- package/dist/lib/project.d.ts.map +1 -1
- package/dist/lib/project.js +2 -3
- package/dist/lib/store.d.ts +1 -1
- package/dist/lib/store.js +3 -4
- package/dist/{logger-BkQQej8h.d.ts → logger-9j49T5da.d.ts} +1 -1
- package/dist/{logger-BkQQej8h.d.ts.map → logger-9j49T5da.d.ts.map} +1 -1
- package/dist/middleware/auth.d.ts +68 -40
- package/dist/middleware/auth.d.ts.map +1 -1
- package/dist/middleware/auth.js +245 -230
- package/dist/middleware/auth.js.map +1 -1
- package/dist/middleware/http.d.ts +1 -1
- package/dist/middleware/http.js +163 -4
- package/dist/middleware/http.js.map +1 -1
- package/dist/{middleware-BFBKNSPQ.js → middleware-BWnPSRWR.js} +2 -4
- package/dist/{middleware-BFBKNSPQ.js.map → middleware-BWnPSRWR.js.map} +1 -1
- package/dist/{project-DuXgjaa_.js → project-D0g84bZY.js} +4 -8
- package/dist/project-D0g84bZY.js.map +1 -0
- package/dist/{types-BaZ5WqVM.d.ts → types-CTvrsrnD.d.ts} +3 -3
- package/dist/types-CTvrsrnD.d.ts.map +1 -0
- package/dist/{types-C0CYivzY.d.ts → types-D-BxshYM.d.ts} +1 -1
- package/dist/{types-C0CYivzY.d.ts.map → types-D-BxshYM.d.ts.map} +1 -1
- package/package.json +7 -7
- package/dist/config-Db_sjFU-.js.map +0 -1
- package/dist/create-http-client-tZJWlWp1.js +0 -165
- package/dist/create-http-client-tZJWlWp1.js.map +0 -1
- package/dist/project-DuXgjaa_.js.map +0 -1
- package/dist/types-BaZ5WqVM.d.ts.map +0 -1
package/dist/middleware/auth.js
CHANGED
|
@@ -1,40 +1,18 @@
|
|
|
1
|
-
import { n as decorateContext, t as middleware } from "../middleware-
|
|
2
|
-
import "../project-
|
|
3
|
-
import { t as createStore } from "../create-store-
|
|
4
|
-
import { t as createHttpClient } from "../create-http-client-tZJWlWp1.js";
|
|
1
|
+
import { n as decorateContext, t as middleware } from "../middleware-BWnPSRWR.js";
|
|
2
|
+
import "../project-D0g84bZY.js";
|
|
3
|
+
import { t as createStore } from "../create-store-OHdkm_Yt.js";
|
|
5
4
|
import { join } from "node:path";
|
|
6
|
-
import { ok } from "@kidd-cli/utils/fp";
|
|
5
|
+
import { attempt, attemptAsync, isPlainObject, match, ok } from "@kidd-cli/utils/fp";
|
|
7
6
|
import { match as match$1 } from "ts-pattern";
|
|
8
7
|
import { platform } from "node:os";
|
|
9
8
|
import { readFileSync } from "node:fs";
|
|
10
|
-
import { Buffer } from "node:buffer";
|
|
11
9
|
import { execFile } from "node:child_process";
|
|
12
10
|
import { createServer } from "node:http";
|
|
13
11
|
import { parse } from "dotenv";
|
|
14
12
|
import { attempt as attempt$1 } from "es-toolkit";
|
|
15
13
|
import { z } from "zod";
|
|
16
14
|
import { createHash, randomBytes } from "node:crypto";
|
|
17
|
-
|
|
18
|
-
//#region src/middleware/http/build-auth-headers.ts
|
|
19
|
-
/**
|
|
20
|
-
* Convert auth credentials into HTTP headers.
|
|
21
|
-
*
|
|
22
|
-
* Uses exhaustive pattern matching to map each credential variant to
|
|
23
|
-
* the appropriate header format.
|
|
24
|
-
*
|
|
25
|
-
* @module
|
|
26
|
-
*/
|
|
27
|
-
/**
|
|
28
|
-
* Convert an auth credential into HTTP headers.
|
|
29
|
-
*
|
|
30
|
-
* @param credential - The credential to convert.
|
|
31
|
-
* @returns A record of header name to header value.
|
|
32
|
-
*/
|
|
33
|
-
function buildAuthHeaders(credential) {
|
|
34
|
-
return match$1(credential).with({ type: "bearer" }, (c) => ({ Authorization: `Bearer ${c.token}` })).with({ type: "basic" }, (c) => ({ Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString("base64")}` })).with({ type: "api-key" }, (c) => ({ [c.headerName]: c.key })).with({ type: "custom" }, (c) => ({ ...c.headers })).exhaustive();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
//#endregion
|
|
15
|
+
import { Buffer } from "node:buffer";
|
|
38
16
|
//#region src/middleware/auth/constants.ts
|
|
39
17
|
/**
|
|
40
18
|
* Default filename for file-based credential storage.
|
|
@@ -45,10 +23,6 @@ const DEFAULT_AUTH_FILENAME = "auth.json";
|
|
|
45
23
|
*/
|
|
46
24
|
const TOKEN_VAR_SUFFIX = "_TOKEN";
|
|
47
25
|
/**
|
|
48
|
-
* Default port for the local OAuth callback server (`0` = ephemeral).
|
|
49
|
-
*/
|
|
50
|
-
const DEFAULT_OAUTH_PORT = 0;
|
|
51
|
-
/**
|
|
52
26
|
* Default callback path for the local OAuth server.
|
|
53
27
|
*/
|
|
54
28
|
const DEFAULT_OAUTH_CALLBACK_PATH = "/callback";
|
|
@@ -76,7 +50,6 @@ const DEFAULT_DEVICE_CODE_TIMEOUT = 3e5;
|
|
|
76
50
|
function deriveTokenVar(cliName) {
|
|
77
51
|
return `${cliName.replaceAll("-", "_").toUpperCase()}${TOKEN_VAR_SUFFIX}`;
|
|
78
52
|
}
|
|
79
|
-
|
|
80
53
|
//#endregion
|
|
81
54
|
//#region src/middleware/auth/credential.ts
|
|
82
55
|
/**
|
|
@@ -119,18 +92,15 @@ function createBearerCredential(token) {
|
|
|
119
92
|
* @returns The fetch Response on success, null on failure.
|
|
120
93
|
*/
|
|
121
94
|
async function postFormEncoded(url, params, signal) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
95
|
+
const [fetchError, response] = await attemptAsync(() => fetch(url, {
|
|
96
|
+
body: params.toString(),
|
|
97
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
98
|
+
method: "POST",
|
|
99
|
+
signal
|
|
100
|
+
}));
|
|
101
|
+
if (fetchError) return null;
|
|
102
|
+
return response;
|
|
132
103
|
}
|
|
133
|
-
|
|
134
104
|
//#endregion
|
|
135
105
|
//#region src/middleware/auth/oauth-server.ts
|
|
136
106
|
/**
|
|
@@ -245,14 +215,11 @@ function sendSuccessPage(res) {
|
|
|
245
215
|
* @returns True when the URL uses HTTPS or HTTP on a loopback address.
|
|
246
216
|
*/
|
|
247
217
|
function isSecureAuthUrl(url) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
} catch {
|
|
254
|
-
return false;
|
|
255
|
-
}
|
|
218
|
+
const [error, parsed] = attempt(() => new URL(url));
|
|
219
|
+
if (error || !parsed) return false;
|
|
220
|
+
if (parsed.protocol === "https:") return true;
|
|
221
|
+
if (parsed.protocol !== "http:") return false;
|
|
222
|
+
return isLoopbackHost(parsed.hostname);
|
|
256
223
|
}
|
|
257
224
|
/**
|
|
258
225
|
* Open a URL in the user's default browser using a platform-specific command.
|
|
@@ -269,7 +236,7 @@ function isSecureAuthUrl(url) {
|
|
|
269
236
|
*/
|
|
270
237
|
function openBrowser(url) {
|
|
271
238
|
if (!isHttpUrl(url)) return;
|
|
272
|
-
const { command, args } = match
|
|
239
|
+
const { command, args } = match(platform()).with("darwin", () => ({
|
|
273
240
|
args: [url],
|
|
274
241
|
command: "open"
|
|
275
242
|
})).with("win32", () => ({
|
|
@@ -330,12 +297,9 @@ function startLocalServer(options) {
|
|
|
330
297
|
* @returns True when the URL uses http: or https: protocol.
|
|
331
298
|
*/
|
|
332
299
|
function isHttpUrl(url) {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
} catch {
|
|
337
|
-
return false;
|
|
338
|
-
}
|
|
300
|
+
const [error, parsed] = attempt(() => new URL(url));
|
|
301
|
+
if (error || !parsed) return false;
|
|
302
|
+
return parsed.protocol === "https:" || parsed.protocol === "http:";
|
|
339
303
|
}
|
|
340
304
|
/**
|
|
341
305
|
* Check whether a hostname is a loopback address.
|
|
@@ -364,7 +328,6 @@ function isLoopbackHost(hostname) {
|
|
|
364
328
|
function escapeCmdMeta(url) {
|
|
365
329
|
return url.replaceAll(/[&|<>^]/g, "^$&");
|
|
366
330
|
}
|
|
367
|
-
|
|
368
331
|
//#endregion
|
|
369
332
|
//#region src/middleware/auth/strategies/device-code.ts
|
|
370
333
|
/**
|
|
@@ -432,11 +395,9 @@ async function requestDeviceAuth(options) {
|
|
|
432
395
|
const response = await postFormEncoded(options.deviceAuthUrl, body, options.signal);
|
|
433
396
|
if (!response) return null;
|
|
434
397
|
if (!response.ok) return null;
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return null;
|
|
439
|
-
}
|
|
398
|
+
const [parseError, data] = await attemptAsync(() => response.json());
|
|
399
|
+
if (parseError) return null;
|
|
400
|
+
return parseDeviceAuthResponse(data);
|
|
440
401
|
}
|
|
441
402
|
/**
|
|
442
403
|
* Parse a device authorization response body.
|
|
@@ -446,17 +407,16 @@ async function requestDeviceAuth(options) {
|
|
|
446
407
|
* @returns The parsed response, or null if required fields are missing.
|
|
447
408
|
*/
|
|
448
409
|
function parseDeviceAuthResponse(data) {
|
|
449
|
-
if (
|
|
450
|
-
|
|
451
|
-
if (typeof
|
|
452
|
-
if (typeof
|
|
453
|
-
|
|
454
|
-
const interval = resolveServerInterval(record.interval);
|
|
410
|
+
if (!isPlainObject(data)) return null;
|
|
411
|
+
if (typeof data.device_code !== "string" || data.device_code === "") return null;
|
|
412
|
+
if (typeof data.user_code !== "string" || data.user_code === "") return null;
|
|
413
|
+
if (typeof data.verification_uri !== "string" || data.verification_uri === "") return null;
|
|
414
|
+
const interval = resolveServerInterval(data.interval);
|
|
455
415
|
return {
|
|
456
|
-
deviceCode:
|
|
416
|
+
deviceCode: data.device_code,
|
|
457
417
|
interval,
|
|
458
|
-
userCode:
|
|
459
|
-
verificationUri:
|
|
418
|
+
userCode: data.user_code,
|
|
419
|
+
verificationUri: data.verification_uri
|
|
460
420
|
};
|
|
461
421
|
}
|
|
462
422
|
/**
|
|
@@ -471,12 +431,10 @@ function parseDeviceAuthResponse(data) {
|
|
|
471
431
|
* @param userCode - The code the user should enter.
|
|
472
432
|
*/
|
|
473
433
|
async function displayUserCode(prompts, verificationUri, userCode) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
});
|
|
479
|
-
} catch {}
|
|
434
|
+
await attemptAsync(() => prompts.text({
|
|
435
|
+
defaultValue: "",
|
|
436
|
+
message: `Open ${verificationUri} and enter code: ${userCode} (press Enter to continue)`
|
|
437
|
+
}));
|
|
480
438
|
}
|
|
481
439
|
/**
|
|
482
440
|
* Resolve the poll interval, preferring server-provided value.
|
|
@@ -507,7 +465,7 @@ async function pollForToken(options) {
|
|
|
507
465
|
if (Date.now() >= options.deadline) return null;
|
|
508
466
|
await sleep(options.interval);
|
|
509
467
|
if (Date.now() >= options.deadline) return null;
|
|
510
|
-
return match
|
|
468
|
+
return match(await requestToken({
|
|
511
469
|
clientId: options.clientId,
|
|
512
470
|
deviceCode: options.deviceCode,
|
|
513
471
|
signal: options.signal,
|
|
@@ -555,24 +513,19 @@ async function requestToken(options) {
|
|
|
555
513
|
});
|
|
556
514
|
const response = await postFormEncoded(options.tokenUrl, body, options.signal);
|
|
557
515
|
if (!response) return { status: "error" };
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
if (
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
if (typeof record.error !== "string") return { status: "error" };
|
|
570
|
-
return match$1(record.error).with("authorization_pending", () => ({ status: "pending" })).with("slow_down", () => ({ status: "slow_down" })).with("expired_token", () => ({ status: "expired" })).with("access_denied", () => ({ status: "denied" })).otherwise(() => ({ status: "error" }));
|
|
571
|
-
} catch {
|
|
572
|
-
return { status: "error" };
|
|
516
|
+
const [parseError, data] = await attemptAsync(() => response.json());
|
|
517
|
+
if (parseError) return { status: "error" };
|
|
518
|
+
if (!isPlainObject(data)) return { status: "error" };
|
|
519
|
+
if (response.ok && typeof data.access_token === "string" && data.access_token !== "") {
|
|
520
|
+
if (typeof data.token_type === "string" && data.token_type.toLowerCase() !== "bearer") return { status: "error" };
|
|
521
|
+
return {
|
|
522
|
+
credential: createBearerCredential(data.access_token),
|
|
523
|
+
status: "success"
|
|
524
|
+
};
|
|
573
525
|
}
|
|
526
|
+
if (typeof data.error !== "string") return { status: "error" };
|
|
527
|
+
return match(data.error).with("authorization_pending", () => ({ status: "pending" })).with("slow_down", () => ({ status: "slow_down" })).with("expired_token", () => ({ status: "expired" })).with("access_denied", () => ({ status: "denied" })).otherwise(() => ({ status: "error" }));
|
|
574
528
|
}
|
|
575
|
-
|
|
576
529
|
//#endregion
|
|
577
530
|
//#region src/middleware/auth/strategies/dotenv.ts
|
|
578
531
|
/**
|
|
@@ -594,7 +547,6 @@ function resolveFromDotenv(options) {
|
|
|
594
547
|
if (!isValidToken(token)) return null;
|
|
595
548
|
return createBearerCredential(token);
|
|
596
549
|
}
|
|
597
|
-
|
|
598
550
|
//#endregion
|
|
599
551
|
//#region src/middleware/auth/strategies/env.ts
|
|
600
552
|
/**
|
|
@@ -608,7 +560,6 @@ function resolveFromEnv(options) {
|
|
|
608
560
|
if (!isValidToken(token)) return null;
|
|
609
561
|
return createBearerCredential(token);
|
|
610
562
|
}
|
|
611
|
-
|
|
612
563
|
//#endregion
|
|
613
564
|
//#region src/middleware/auth/schema.ts
|
|
614
565
|
/**
|
|
@@ -651,7 +602,6 @@ const authCredentialSchema = z.discriminatedUnion("type", [
|
|
|
651
602
|
apiKeyCredentialSchema,
|
|
652
603
|
customCredentialSchema
|
|
653
604
|
]);
|
|
654
|
-
|
|
655
605
|
//#endregion
|
|
656
606
|
//#region src/middleware/auth/strategies/file.ts
|
|
657
607
|
/**
|
|
@@ -671,7 +621,6 @@ function resolveFromFile(options) {
|
|
|
671
621
|
if (!result.success) return null;
|
|
672
622
|
return result.data;
|
|
673
623
|
}
|
|
674
|
-
|
|
675
624
|
//#endregion
|
|
676
625
|
//#region src/middleware/auth/strategies/oauth.ts
|
|
677
626
|
/**
|
|
@@ -867,18 +816,13 @@ async function exchangeCodeForToken(options) {
|
|
|
867
816
|
const response = await postFormEncoded(options.tokenUrl, body);
|
|
868
817
|
if (!response) return null;
|
|
869
818
|
if (!response.ok) return null;
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
return createBearerCredential(record.access_token);
|
|
877
|
-
} catch {
|
|
878
|
-
return null;
|
|
879
|
-
}
|
|
819
|
+
const [parseError, data] = await attemptAsync(() => response.json());
|
|
820
|
+
if (parseError) return null;
|
|
821
|
+
if (!isPlainObject(data)) return null;
|
|
822
|
+
if (typeof data.access_token !== "string" || data.access_token === "") return null;
|
|
823
|
+
if (typeof data.token_type === "string" && data.token_type.toLowerCase() !== "bearer") return null;
|
|
824
|
+
return createBearerCredential(data.access_token);
|
|
880
825
|
}
|
|
881
|
-
|
|
882
826
|
//#endregion
|
|
883
827
|
//#region src/middleware/auth/strategies/token.ts
|
|
884
828
|
/**
|
|
@@ -893,31 +837,27 @@ async function exchangeCodeForToken(options) {
|
|
|
893
837
|
* @returns A bearer credential on input, null on cancellation.
|
|
894
838
|
*/
|
|
895
839
|
async function resolveFromToken(options) {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
} catch {
|
|
901
|
-
return null;
|
|
902
|
-
}
|
|
840
|
+
const [promptError, token] = await attemptAsync(() => options.prompts.password({ message: options.message }));
|
|
841
|
+
if (promptError) return null;
|
|
842
|
+
if (!isValidToken(token)) return null;
|
|
843
|
+
return createBearerCredential(token);
|
|
903
844
|
}
|
|
904
|
-
|
|
905
845
|
//#endregion
|
|
906
846
|
//#region src/middleware/auth/chain.ts
|
|
907
847
|
const DEFAULT_PROMPT_MESSAGE = "Enter your API key";
|
|
908
848
|
/**
|
|
909
|
-
* Chain credential
|
|
849
|
+
* Chain credential strategies, returning the first non-null result.
|
|
910
850
|
*
|
|
911
|
-
* Walks the
|
|
912
|
-
* appropriate
|
|
851
|
+
* Walks the strategy list in order, dispatching each config to the
|
|
852
|
+
* appropriate strategy function via pattern matching. Short-circuits
|
|
913
853
|
* on the first successful resolution.
|
|
914
854
|
*
|
|
915
|
-
* @param options - Options with
|
|
916
|
-
* @returns The first resolved credential, or null if all
|
|
855
|
+
* @param options - Options with strategies, CLI name, and prompts instance.
|
|
856
|
+
* @returns The first resolved credential, or null if all strategies fail.
|
|
917
857
|
*/
|
|
918
858
|
async function runStrategyChain(options) {
|
|
919
859
|
const defaultTokenVar = deriveTokenVar(options.cliName);
|
|
920
|
-
return
|
|
860
|
+
return tryStrategies(options.strategies, 0, defaultTokenVar, options);
|
|
921
861
|
}
|
|
922
862
|
/**
|
|
923
863
|
* Return the given value when defined, otherwise the fallback.
|
|
@@ -931,33 +871,33 @@ function withDefault(value, fallback) {
|
|
|
931
871
|
return fallback;
|
|
932
872
|
}
|
|
933
873
|
/**
|
|
934
|
-
* Recursively try
|
|
874
|
+
* Recursively try strategies until one returns a credential or the list is exhausted.
|
|
935
875
|
*
|
|
936
876
|
* @private
|
|
937
|
-
* @param configs - The
|
|
877
|
+
* @param configs - The strategy configs.
|
|
938
878
|
* @param index - The current index.
|
|
939
879
|
* @param defaultTokenVar - The derived default token env var name.
|
|
940
880
|
* @param context - The resolve options for prompts access.
|
|
941
881
|
* @returns The first resolved credential, or null.
|
|
942
882
|
*/
|
|
943
|
-
async function
|
|
883
|
+
async function tryStrategies(configs, index, defaultTokenVar, context) {
|
|
944
884
|
if (index >= configs.length) return null;
|
|
945
885
|
const config = configs[index];
|
|
946
886
|
if (config === void 0) return null;
|
|
947
|
-
const credential = await
|
|
887
|
+
const credential = await dispatchStrategy(config, defaultTokenVar, context);
|
|
948
888
|
if (credential) return credential;
|
|
949
|
-
return
|
|
889
|
+
return tryStrategies(configs, index + 1, defaultTokenVar, context);
|
|
950
890
|
}
|
|
951
891
|
/**
|
|
952
|
-
* Dispatch a single
|
|
892
|
+
* Dispatch a single strategy config to its implementation.
|
|
953
893
|
*
|
|
954
894
|
* @private
|
|
955
|
-
* @param config - The
|
|
895
|
+
* @param config - The strategy config to dispatch.
|
|
956
896
|
* @param defaultTokenVar - The derived default token env var name.
|
|
957
897
|
* @param context - The resolve options for prompts access.
|
|
958
898
|
* @returns The resolved credential, or null.
|
|
959
899
|
*/
|
|
960
|
-
async function
|
|
900
|
+
async function dispatchStrategy(config, defaultTokenVar, context) {
|
|
961
901
|
return match$1(config).with({ source: "env" }, (c) => resolveFromEnv({ tokenVar: withDefault(c.tokenVar, defaultTokenVar) })).with({ source: "dotenv" }, (c) => resolveFromDotenv({
|
|
962
902
|
path: withDefault(c.path, join(process.cwd(), ".env")),
|
|
963
903
|
tokenVar: withDefault(c.tokenVar, defaultTokenVar)
|
|
@@ -968,7 +908,7 @@ async function dispatchResolver(config, defaultTokenVar, context) {
|
|
|
968
908
|
authUrl: c.authUrl,
|
|
969
909
|
callbackPath: withDefault(c.callbackPath, DEFAULT_OAUTH_CALLBACK_PATH),
|
|
970
910
|
clientId: c.clientId,
|
|
971
|
-
port: withDefault(c.port,
|
|
911
|
+
port: withDefault(c.port, 0),
|
|
972
912
|
scopes: withDefault(c.scopes, []),
|
|
973
913
|
timeout: withDefault(c.timeout, DEFAULT_OAUTH_TIMEOUT),
|
|
974
914
|
tokenUrl: c.tokenUrl
|
|
@@ -986,7 +926,6 @@ async function dispatchResolver(config, defaultTokenVar, context) {
|
|
|
986
926
|
prompts: context.prompts
|
|
987
927
|
})).with({ source: "custom" }, (c) => c.resolver()).exhaustive();
|
|
988
928
|
}
|
|
989
|
-
|
|
990
929
|
//#endregion
|
|
991
930
|
//#region src/middleware/auth/context.ts
|
|
992
931
|
/**
|
|
@@ -994,14 +933,14 @@ async function dispatchResolver(config, defaultTokenVar, context) {
|
|
|
994
933
|
*
|
|
995
934
|
* No credential data is stored on the returned object. `credential()`
|
|
996
935
|
* resolves passively on every call, `authenticated()` checks existence,
|
|
997
|
-
* `login()` runs the configured interactive
|
|
936
|
+
* `login()` runs the configured interactive strategies, saves the
|
|
998
937
|
* credential to the global file store, and `logout()` removes it.
|
|
999
938
|
*
|
|
1000
939
|
* @param options - Factory options.
|
|
1001
940
|
* @returns An AuthContext instance.
|
|
1002
941
|
*/
|
|
1003
942
|
function createAuthContext(options) {
|
|
1004
|
-
const {
|
|
943
|
+
const { strategies, cliName, prompts, resolveCredential } = options;
|
|
1005
944
|
/**
|
|
1006
945
|
* Resolve the current credential from passive sources (file, env).
|
|
1007
946
|
*
|
|
@@ -1021,16 +960,20 @@ function createAuthContext(options) {
|
|
|
1021
960
|
return resolveCredential() !== null;
|
|
1022
961
|
}
|
|
1023
962
|
/**
|
|
1024
|
-
* Run configured
|
|
963
|
+
* Run configured strategies interactively and persist the credential.
|
|
964
|
+
*
|
|
965
|
+
* When `loginOptions.strategies` is provided, those strategies are used
|
|
966
|
+
* instead of the default configured list.
|
|
1025
967
|
*
|
|
1026
968
|
* @private
|
|
969
|
+
* @param loginOptions - Optional overrides for the login attempt.
|
|
1027
970
|
* @returns A Result with the credential on success or an AuthError on failure.
|
|
1028
971
|
*/
|
|
1029
|
-
async function login() {
|
|
972
|
+
async function login(loginOptions) {
|
|
1030
973
|
const resolved = await runStrategyChain({
|
|
1031
974
|
cliName,
|
|
1032
975
|
prompts,
|
|
1033
|
-
|
|
976
|
+
strategies: resolveLoginStrategies(loginOptions, strategies)
|
|
1034
977
|
});
|
|
1035
978
|
if (resolved === null) return authError({
|
|
1036
979
|
message: "No credential resolved from any source",
|
|
@@ -1074,15 +1017,121 @@ function createAuthContext(options) {
|
|
|
1074
1017
|
function authError(error) {
|
|
1075
1018
|
return [error, null];
|
|
1076
1019
|
}
|
|
1077
|
-
|
|
1020
|
+
/**
|
|
1021
|
+
* Resolve the active strategies for a login attempt.
|
|
1022
|
+
*
|
|
1023
|
+
* Returns the override strategies from login options when provided,
|
|
1024
|
+
* otherwise falls back to the configured strategies.
|
|
1025
|
+
*
|
|
1026
|
+
* @private
|
|
1027
|
+
* @param loginOptions - Optional login overrides.
|
|
1028
|
+
* @param configured - The default configured strategies.
|
|
1029
|
+
* @returns The strategies to use for the login attempt.
|
|
1030
|
+
*/
|
|
1031
|
+
function resolveLoginStrategies(loginOptions, configured) {
|
|
1032
|
+
if (loginOptions !== void 0 && loginOptions.strategies !== void 0) return loginOptions.strategies;
|
|
1033
|
+
return configured;
|
|
1034
|
+
}
|
|
1035
|
+
//#endregion
|
|
1036
|
+
//#region src/middleware/http/build-auth-headers.ts
|
|
1037
|
+
/**
|
|
1038
|
+
* Convert auth credentials into HTTP headers.
|
|
1039
|
+
*
|
|
1040
|
+
* Uses exhaustive pattern matching to map each credential variant to
|
|
1041
|
+
* the appropriate header format.
|
|
1042
|
+
*
|
|
1043
|
+
* @module
|
|
1044
|
+
*/
|
|
1045
|
+
/**
|
|
1046
|
+
* Convert an auth credential into HTTP headers.
|
|
1047
|
+
*
|
|
1048
|
+
* @param credential - The credential to convert.
|
|
1049
|
+
* @returns A record of header name to header value.
|
|
1050
|
+
*/
|
|
1051
|
+
function buildAuthHeaders(credential) {
|
|
1052
|
+
return match$1(credential).with({ type: "bearer" }, (c) => ({ Authorization: `Bearer ${c.token}` })).with({ type: "basic" }, (c) => ({ Authorization: `Basic ${Buffer.from(`${c.username}:${c.password}`).toString("base64")}` })).with({ type: "api-key" }, (c) => ({ [c.headerName]: c.key })).with({ type: "custom" }, (c) => ({ ...c.headers })).exhaustive();
|
|
1053
|
+
}
|
|
1054
|
+
//#endregion
|
|
1055
|
+
//#region src/middleware/auth/headers.ts
|
|
1056
|
+
/**
|
|
1057
|
+
* Create a function that resolves auth credentials from `ctx.auth` into HTTP headers.
|
|
1058
|
+
*
|
|
1059
|
+
* The returned function reads `ctx.auth.credential()` and converts the credential
|
|
1060
|
+
* into the appropriate header format using `buildAuthHeaders()`. Returns an empty
|
|
1061
|
+
* record when no auth middleware is present or no credential exists.
|
|
1062
|
+
*
|
|
1063
|
+
* @returns A function that takes a Context and returns auth headers.
|
|
1064
|
+
*/
|
|
1065
|
+
function createAuthHeaders() {
|
|
1066
|
+
return function resolveHeaders(ctx) {
|
|
1067
|
+
if (!("auth" in ctx)) return {};
|
|
1068
|
+
const credential = ctx.auth.credential();
|
|
1069
|
+
if (credential === null) return {};
|
|
1070
|
+
return buildAuthHeaders(credential);
|
|
1071
|
+
};
|
|
1072
|
+
}
|
|
1073
|
+
//#endregion
|
|
1074
|
+
//#region src/middleware/auth/require.ts
|
|
1075
|
+
/**
|
|
1076
|
+
* Enforcement gate middleware that requires authentication.
|
|
1077
|
+
*
|
|
1078
|
+
* @module
|
|
1079
|
+
*/
|
|
1080
|
+
const DEFAULT_MESSAGE = "Authentication required.";
|
|
1081
|
+
/**
|
|
1082
|
+
* Create an enforcement middleware that gates on authentication.
|
|
1083
|
+
*
|
|
1084
|
+
* When `ctx.auth.authenticated()` returns true, the middleware calls
|
|
1085
|
+
* `next()`. When not authenticated, it calls `ctx.fail()` with the
|
|
1086
|
+
* provided (or default) message. When `ctx.auth` is absent (auth
|
|
1087
|
+
* middleware not configured), it calls `ctx.fail()` with an
|
|
1088
|
+
* `AUTH_MIDDLEWARE_MISSING` code.
|
|
1089
|
+
*
|
|
1090
|
+
* @param options - Optional configuration for the require gate.
|
|
1091
|
+
* @returns A Middleware that enforces authentication.
|
|
1092
|
+
*/
|
|
1093
|
+
function createAuthRequire(options) {
|
|
1094
|
+
const message = resolveMessage(options);
|
|
1095
|
+
return middleware((ctx, next) => {
|
|
1096
|
+
if (!hasProperty(ctx, "auth")) ctx.fail("auth.require() must run after auth() middleware", { code: "AUTH_MIDDLEWARE_MISSING" });
|
|
1097
|
+
if (!ctx.auth.authenticated()) ctx.fail(message, { code: "AUTH_REQUIRED" });
|
|
1098
|
+
return next();
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Runtime property check that avoids TypeScript's `in` narrowing.
|
|
1103
|
+
*
|
|
1104
|
+
* The `Context` interface declares `auth` via module augmentation,
|
|
1105
|
+
* so `!('auth' in ctx)` narrows to `never`. This function uses an
|
|
1106
|
+
* `unknown` cast to bypass the narrowing and perform a pure runtime
|
|
1107
|
+
* check.
|
|
1108
|
+
*
|
|
1109
|
+
* @private
|
|
1110
|
+
* @param obj - The object to inspect.
|
|
1111
|
+
* @param key - The property name to check.
|
|
1112
|
+
* @returns True when the property exists on the object.
|
|
1113
|
+
*/
|
|
1114
|
+
function hasProperty(obj, key) {
|
|
1115
|
+
return typeof obj === "object" && obj !== null && key in obj;
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Resolve the failure message from optional require options.
|
|
1119
|
+
*
|
|
1120
|
+
* @private
|
|
1121
|
+
* @param options - Optional require gate options.
|
|
1122
|
+
* @returns The resolved message string.
|
|
1123
|
+
*/
|
|
1124
|
+
function resolveMessage(options) {
|
|
1125
|
+
if (options !== void 0 && options.message !== void 0) return options.message;
|
|
1126
|
+
return DEFAULT_MESSAGE;
|
|
1127
|
+
}
|
|
1078
1128
|
//#endregion
|
|
1079
1129
|
//#region src/middleware/auth/auth.ts
|
|
1080
1130
|
/**
|
|
1081
|
-
* Auth middleware factory with
|
|
1131
|
+
* Auth middleware factory with strategy builder functions.
|
|
1082
1132
|
*
|
|
1083
1133
|
* Decorates `ctx.auth` with functions to resolve credentials on demand
|
|
1084
|
-
* and run interactive authentication.
|
|
1085
|
-
* HTTP clients via the `http` option.
|
|
1134
|
+
* and run interactive authentication.
|
|
1086
1135
|
*
|
|
1087
1136
|
* @module
|
|
1088
1137
|
*/
|
|
@@ -1095,44 +1144,31 @@ function authError(error) {
|
|
|
1095
1144
|
* 2. Dotenv — `.env` file (when configured)
|
|
1096
1145
|
* 3. Env — `CLI_NAME_TOKEN`
|
|
1097
1146
|
*
|
|
1098
|
-
* Interactive
|
|
1147
|
+
* Interactive strategies (OAuth, device-code, token, custom) only run when the
|
|
1099
1148
|
* command handler explicitly calls `ctx.auth.login()`.
|
|
1100
1149
|
*
|
|
1101
|
-
* When `options.http` is provided, the middleware also creates HTTP
|
|
1102
|
-
* client(s) with automatic credential header injection and decorates
|
|
1103
|
-
* them onto `ctx[namespace]`.
|
|
1104
|
-
*
|
|
1105
1150
|
* @param options - Auth middleware configuration.
|
|
1106
|
-
* @returns A Middleware that decorates ctx.auth
|
|
1151
|
+
* @returns A Middleware that decorates ctx.auth.
|
|
1107
1152
|
*/
|
|
1108
1153
|
function createAuth(options) {
|
|
1109
|
-
const {
|
|
1154
|
+
const { strategies } = options;
|
|
1110
1155
|
return middleware((ctx, next) => {
|
|
1111
1156
|
const cliName = ctx.meta.name;
|
|
1112
|
-
|
|
1157
|
+
decorateContext(ctx, "auth", createAuthContext({
|
|
1113
1158
|
cliName,
|
|
1114
1159
|
prompts: ctx.prompts,
|
|
1115
|
-
resolveCredential: () => resolveStoredCredential(cliName,
|
|
1116
|
-
|
|
1117
|
-
});
|
|
1118
|
-
decorateContext(ctx, "auth", authContext);
|
|
1119
|
-
if (options.http !== void 0) normalizeHttpOptions(options.http).reduce((context, httpConfig) => {
|
|
1120
|
-
const client = createHttpClient({
|
|
1121
|
-
baseUrl: httpConfig.baseUrl,
|
|
1122
|
-
defaultHeaders: httpConfig.headers,
|
|
1123
|
-
resolveHeaders: () => credentialToHeaders(authContext.credential())
|
|
1124
|
-
});
|
|
1125
|
-
return decorateContext(context, httpConfig.namespace, client);
|
|
1126
|
-
}, ctx);
|
|
1160
|
+
resolveCredential: () => resolveStoredCredential(cliName, strategies),
|
|
1161
|
+
strategies
|
|
1162
|
+
}));
|
|
1127
1163
|
return next();
|
|
1128
1164
|
});
|
|
1129
1165
|
}
|
|
1130
1166
|
/**
|
|
1131
|
-
* Auth middleware factory with
|
|
1167
|
+
* Auth middleware factory with strategy builder methods.
|
|
1132
1168
|
*
|
|
1133
|
-
* Use as `auth({
|
|
1169
|
+
* Use as `auth({ strategies: [...] })` to create middleware, or use
|
|
1134
1170
|
* the builder methods (`auth.env()`, `auth.oauth()`, etc.) to construct
|
|
1135
|
-
*
|
|
1171
|
+
* strategy configs with a cleaner API.
|
|
1136
1172
|
*/
|
|
1137
1173
|
const auth = Object.assign(createAuth, {
|
|
1138
1174
|
apiKey: buildToken,
|
|
@@ -1141,142 +1177,121 @@ const auth = Object.assign(createAuth, {
|
|
|
1141
1177
|
dotenv: buildDotenv,
|
|
1142
1178
|
env: buildEnv,
|
|
1143
1179
|
file: buildFile,
|
|
1180
|
+
headers: createAuthHeaders,
|
|
1144
1181
|
oauth: buildOAuth,
|
|
1182
|
+
require: createAuthRequire,
|
|
1145
1183
|
token: buildToken
|
|
1146
1184
|
});
|
|
1147
1185
|
/**
|
|
1148
|
-
* Build an env
|
|
1186
|
+
* Build an env strategy config.
|
|
1149
1187
|
*
|
|
1150
1188
|
* @private
|
|
1151
|
-
* @param options - Optional env
|
|
1189
|
+
* @param options - Optional env strategy options.
|
|
1152
1190
|
* @returns An EnvSourceConfig with `source: 'env'`.
|
|
1153
1191
|
*/
|
|
1154
1192
|
function buildEnv(options) {
|
|
1155
1193
|
return {
|
|
1156
|
-
|
|
1157
|
-
|
|
1194
|
+
...options,
|
|
1195
|
+
source: "env"
|
|
1158
1196
|
};
|
|
1159
1197
|
}
|
|
1160
1198
|
/**
|
|
1161
|
-
* Build a dotenv
|
|
1199
|
+
* Build a dotenv strategy config.
|
|
1162
1200
|
*
|
|
1163
1201
|
* @private
|
|
1164
|
-
* @param options - Optional dotenv
|
|
1202
|
+
* @param options - Optional dotenv strategy options.
|
|
1165
1203
|
* @returns A DotenvSourceConfig with `source: 'dotenv'`.
|
|
1166
1204
|
*/
|
|
1167
1205
|
function buildDotenv(options) {
|
|
1168
1206
|
return {
|
|
1169
|
-
|
|
1170
|
-
|
|
1207
|
+
...options,
|
|
1208
|
+
source: "dotenv"
|
|
1171
1209
|
};
|
|
1172
1210
|
}
|
|
1173
1211
|
/**
|
|
1174
|
-
* Build a file
|
|
1212
|
+
* Build a file strategy config.
|
|
1175
1213
|
*
|
|
1176
1214
|
* @private
|
|
1177
|
-
* @param options - Optional file
|
|
1215
|
+
* @param options - Optional file strategy options.
|
|
1178
1216
|
* @returns A FileSourceConfig with `source: 'file'`.
|
|
1179
1217
|
*/
|
|
1180
1218
|
function buildFile(options) {
|
|
1181
1219
|
return {
|
|
1182
|
-
|
|
1183
|
-
|
|
1220
|
+
...options,
|
|
1221
|
+
source: "file"
|
|
1184
1222
|
};
|
|
1185
1223
|
}
|
|
1186
1224
|
/**
|
|
1187
|
-
* Build an OAuth
|
|
1225
|
+
* Build an OAuth strategy config.
|
|
1188
1226
|
*
|
|
1189
1227
|
* @private
|
|
1190
|
-
* @param options - OAuth
|
|
1228
|
+
* @param options - OAuth strategy options (clientId, authUrl, tokenUrl required).
|
|
1191
1229
|
* @returns An OAuthSourceConfig with `source: 'oauth'`.
|
|
1192
1230
|
*/
|
|
1193
1231
|
function buildOAuth(options) {
|
|
1194
1232
|
return {
|
|
1195
|
-
|
|
1196
|
-
|
|
1233
|
+
...options,
|
|
1234
|
+
source: "oauth"
|
|
1197
1235
|
};
|
|
1198
1236
|
}
|
|
1199
1237
|
/**
|
|
1200
|
-
* Build a device code
|
|
1238
|
+
* Build a device code strategy config.
|
|
1201
1239
|
*
|
|
1202
1240
|
* @private
|
|
1203
|
-
* @param options - Device code
|
|
1241
|
+
* @param options - Device code strategy options (clientId, deviceAuthUrl, tokenUrl required).
|
|
1204
1242
|
* @returns A DeviceCodeSourceConfig with `source: 'device-code'`.
|
|
1205
1243
|
*/
|
|
1206
1244
|
function buildDeviceCode(options) {
|
|
1207
1245
|
return {
|
|
1208
|
-
|
|
1209
|
-
|
|
1246
|
+
...options,
|
|
1247
|
+
source: "device-code"
|
|
1210
1248
|
};
|
|
1211
1249
|
}
|
|
1212
1250
|
/**
|
|
1213
|
-
* Build a token
|
|
1251
|
+
* Build a token strategy config.
|
|
1214
1252
|
*
|
|
1215
1253
|
* Prompts the user for a token interactively. Aliased as `auth.apiKey()`.
|
|
1216
1254
|
*
|
|
1217
1255
|
* @private
|
|
1218
|
-
* @param options - Optional token
|
|
1256
|
+
* @param options - Optional token strategy options.
|
|
1219
1257
|
* @returns A TokenSourceConfig with `source: 'token'`.
|
|
1220
1258
|
*/
|
|
1221
1259
|
function buildToken(options) {
|
|
1222
1260
|
return {
|
|
1223
|
-
|
|
1224
|
-
|
|
1261
|
+
...options,
|
|
1262
|
+
source: "token"
|
|
1225
1263
|
};
|
|
1226
1264
|
}
|
|
1227
1265
|
/**
|
|
1228
|
-
* Build a custom
|
|
1266
|
+
* Build a custom strategy config from a strategy function.
|
|
1229
1267
|
*
|
|
1230
1268
|
* @private
|
|
1231
|
-
* @param
|
|
1269
|
+
* @param fn - The custom strategy function.
|
|
1232
1270
|
* @returns A CustomSourceConfig with `source: 'custom'`.
|
|
1233
1271
|
*/
|
|
1234
|
-
function buildCustom(
|
|
1272
|
+
function buildCustom(fn) {
|
|
1235
1273
|
return {
|
|
1236
|
-
resolver,
|
|
1274
|
+
resolver: fn,
|
|
1237
1275
|
source: "custom"
|
|
1238
1276
|
};
|
|
1239
1277
|
}
|
|
1240
1278
|
/**
|
|
1241
|
-
* Normalize the `http` option into an array of configs.
|
|
1242
|
-
*
|
|
1243
|
-
* @private
|
|
1244
|
-
* @param http - A single config or array of configs.
|
|
1245
|
-
* @returns An array of AuthHttpOptions.
|
|
1246
|
-
*/
|
|
1247
|
-
function normalizeHttpOptions(http) {
|
|
1248
|
-
if ("baseUrl" in http) return [http];
|
|
1249
|
-
return http;
|
|
1250
|
-
}
|
|
1251
|
-
/**
|
|
1252
|
-
* Convert a credential into auth headers, returning an empty record
|
|
1253
|
-
* when no credential is available.
|
|
1254
|
-
*
|
|
1255
|
-
* @private
|
|
1256
|
-
* @param credential - The credential or null.
|
|
1257
|
-
* @returns A record of auth headers.
|
|
1258
|
-
*/
|
|
1259
|
-
function credentialToHeaders(credential) {
|
|
1260
|
-
if (credential === null) return {};
|
|
1261
|
-
return buildAuthHeaders(credential);
|
|
1262
|
-
}
|
|
1263
|
-
/**
|
|
1264
1279
|
* Attempt to resolve a credential from stored (non-interactive) sources.
|
|
1265
1280
|
*
|
|
1266
1281
|
* Checks the file store first, then dotenv, then falls back to the
|
|
1267
|
-
* environment variable. Scans the
|
|
1282
|
+
* environment variable. Scans the strategy list for `file`, `dotenv`,
|
|
1268
1283
|
* and `env` source configs to respect user-configured overrides
|
|
1269
1284
|
* (e.g. a custom `tokenVar`, `dirName`, or dotenv `path`).
|
|
1270
1285
|
*
|
|
1271
1286
|
* @private
|
|
1272
1287
|
* @param cliName - The CLI name, used to derive paths and env var names.
|
|
1273
|
-
* @param
|
|
1288
|
+
* @param strategies - The configured strategy list for extracting overrides.
|
|
1274
1289
|
* @returns The resolved credential, or null.
|
|
1275
1290
|
*/
|
|
1276
|
-
function resolveStoredCredential(cliName,
|
|
1277
|
-
const fileConfig =
|
|
1278
|
-
const dotenvConfig =
|
|
1279
|
-
const envConfig =
|
|
1291
|
+
function resolveStoredCredential(cliName, strategies) {
|
|
1292
|
+
const fileConfig = findStrategyBySource(strategies, "file");
|
|
1293
|
+
const dotenvConfig = findStrategyBySource(strategies, "dotenv");
|
|
1294
|
+
const envConfig = findStrategyBySource(strategies, "env");
|
|
1280
1295
|
const defaultTokenVar = deriveTokenVar(cliName);
|
|
1281
1296
|
const fromFile = resolveFromFile({
|
|
1282
1297
|
dirName: withDefault(extractProp(fileConfig, "dirName"), `.${cliName}`),
|
|
@@ -1293,15 +1308,15 @@ function resolveStoredCredential(cliName, resolvers) {
|
|
|
1293
1308
|
return resolveFromEnv({ tokenVar: withDefault(extractProp(envConfig, "tokenVar"), defaultTokenVar) });
|
|
1294
1309
|
}
|
|
1295
1310
|
/**
|
|
1296
|
-
* Find the first
|
|
1311
|
+
* Find the first strategy config matching a given source type.
|
|
1297
1312
|
*
|
|
1298
1313
|
* @private
|
|
1299
|
-
* @param
|
|
1314
|
+
* @param strategies - The strategy config list.
|
|
1300
1315
|
* @param source - The source type to find.
|
|
1301
1316
|
* @returns The matching config, or undefined.
|
|
1302
1317
|
*/
|
|
1303
|
-
function
|
|
1304
|
-
return
|
|
1318
|
+
function findStrategyBySource(strategies, source) {
|
|
1319
|
+
return strategies.find((r) => r.source === source);
|
|
1305
1320
|
}
|
|
1306
1321
|
/**
|
|
1307
1322
|
* Safely extract a property from an optional config object.
|
|
@@ -1318,7 +1333,7 @@ function extractProp(config, key) {
|
|
|
1318
1333
|
if (config === void 0) return;
|
|
1319
1334
|
return config[key];
|
|
1320
1335
|
}
|
|
1321
|
-
|
|
1322
1336
|
//#endregion
|
|
1323
|
-
export { auth };
|
|
1337
|
+
export { auth, createAuthHeaders, createAuthRequire };
|
|
1338
|
+
|
|
1324
1339
|
//# sourceMappingURL=auth.js.map
|