@kodycodes/cli 1.26.2 → 1.27.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 +175 -167
- package/dist/add-mcp-install.d.ts +1 -1
- package/dist/api-token.d.ts +1 -1
- package/dist/auth-6ZE6QQXA.js +31 -0
- package/dist/auth-6ZE6QQXA.js.map +6 -0
- package/dist/auth-bootstrap.d.ts +1 -1
- package/dist/auth.d.ts +27 -3
- package/dist/bin.js +27 -3
- package/dist/bin.js.map +6 -1
- package/dist/capability-proxy.d.ts +1 -1
- package/dist/chunk-2IHISXHN.js +1413 -0
- package/dist/chunk-2IHISXHN.js.map +6 -0
- package/dist/chunk-2XPH7L2P.js +23 -0
- package/dist/chunk-2XPH7L2P.js.map +6 -0
- package/dist/chunk-3H72XY5R.js +535 -0
- package/dist/chunk-3H72XY5R.js.map +6 -0
- package/dist/chunk-46IJPEID.js +30 -0
- package/dist/chunk-46IJPEID.js.map +6 -0
- package/dist/chunk-4GIFJVGT.js +14 -0
- package/dist/chunk-4GIFJVGT.js.map +6 -0
- package/dist/chunk-5JIRHJQP.js +1016 -0
- package/dist/chunk-5JIRHJQP.js.map +6 -0
- package/dist/chunk-5ZBJAP6G.js +2005 -0
- package/dist/chunk-5ZBJAP6G.js.map +6 -0
- package/dist/chunk-6ZYUGXVW.js +26 -0
- package/dist/chunk-6ZYUGXVW.js.map +6 -0
- package/dist/chunk-7PRJ36I5.js +23 -0
- package/dist/chunk-7PRJ36I5.js.map +6 -0
- package/dist/chunk-BTL3PJ2H.js +38 -0
- package/dist/chunk-BTL3PJ2H.js.map +6 -0
- package/dist/chunk-CASGRYOF.js +21 -0
- package/dist/chunk-CASGRYOF.js.map +6 -0
- package/dist/chunk-CPS7HW4T.js +56 -0
- package/dist/chunk-CPS7HW4T.js.map +6 -0
- package/dist/chunk-CWWHR4FH.js +19 -0
- package/dist/chunk-CWWHR4FH.js.map +6 -0
- package/dist/chunk-D366STO5.js +2051 -0
- package/dist/chunk-D366STO5.js.map +6 -0
- package/dist/chunk-DW5UTJ6T.js +115 -0
- package/dist/chunk-DW5UTJ6T.js.map +6 -0
- package/dist/chunk-EJXEVGWF.js +47 -0
- package/dist/chunk-EJXEVGWF.js.map +6 -0
- package/dist/chunk-FBEZ6PZ7.js +746 -0
- package/dist/chunk-FBEZ6PZ7.js.map +6 -0
- package/dist/chunk-JOMBXQOS.js +435 -0
- package/dist/chunk-JOMBXQOS.js.map +6 -0
- package/dist/{package-local-runtime-source.js → chunk-LU7I7CQ4.js} +395 -52
- package/dist/chunk-LU7I7CQ4.js.map +6 -0
- package/dist/chunk-OZDWOZLV.js +964 -0
- package/dist/chunk-OZDWOZLV.js.map +6 -0
- package/dist/chunk-SLNAWWQK.js +64 -0
- package/dist/chunk-SLNAWWQK.js.map +6 -0
- package/dist/chunk-TUUFQYWP.js +348 -0
- package/dist/chunk-TUUFQYWP.js.map +6 -0
- package/dist/chunk-UW6AWLXX.js +43 -0
- package/dist/chunk-UW6AWLXX.js.map +6 -0
- package/dist/chunk-W2SAFAT4.js +1563 -0
- package/dist/chunk-W2SAFAT4.js.map +6 -0
- package/dist/chunk-XV6D6HSG.js +57 -0
- package/dist/chunk-XV6D6HSG.js.map +6 -0
- package/dist/chunk-Y4FOJETX.js +616 -0
- package/dist/chunk-Y4FOJETX.js.map +6 -0
- package/dist/chunk-YDWTXEKQ.js +469 -0
- package/dist/chunk-YDWTXEKQ.js.map +6 -0
- package/dist/chunk-ZILB2HWE.js +24 -0
- package/dist/chunk-ZILB2HWE.js.map +6 -0
- package/dist/cli.js +74 -1500
- package/dist/cli.js.map +6 -1
- package/dist/defaults.d.ts +1 -1
- package/dist/detect-running-hosts.d.ts +1 -1
- package/dist/dev/dev-assets.d.ts +1 -1
- package/dist/dev/dev-registry.d.ts +1 -1
- package/dist/dev/error-page.d.ts +1 -1
- package/dist/dev/live-reload.d.ts +2 -2
- package/dist/dev-command-5PDEIWOC.js +5025 -0
- package/dist/dev-command-5PDEIWOC.js.map +6 -0
- package/dist/dev-command.d.ts +1 -1
- package/dist/discover-login-identity.d.ts +6 -7
- package/dist/discover-org.d.ts +2 -3
- package/dist/generated/openapi-routes.d.ts +6 -0
- package/dist/git-credential-INB2UV4C.js +170 -0
- package/dist/git-credential-INB2UV4C.js.map +6 -0
- package/dist/install-ONEJRYZ3.js +10728 -0
- package/dist/install-ONEJRYZ3.js.map +6 -0
- package/dist/install.d.ts +1 -1
- package/dist/local-execute-I3BP5NEQ.js +395 -0
- package/dist/local-execute-I3BP5NEQ.js.map +6 -0
- package/dist/local-execute-auth.d.ts +2 -2
- package/dist/local-execute.d.ts +1 -1
- package/dist/local-gateway-fetch-forward.d.ts +2 -2
- package/dist/local-package-run-JFKNSNWM.js +23 -0
- package/dist/local-package-run-JFKNSNWM.js.map +6 -0
- package/dist/local-package-run.d.ts +2 -2
- package/dist/mock/mock-fetch-gateway.d.ts +2 -2
- package/dist/mock/mock-supported.d.ts +1 -1
- package/dist/mock-dep-resolve-B5ZH4U3H.js +60 -0
- package/dist/mock-dep-resolve-B5ZH4U3H.js.map +6 -0
- package/dist/mock-fetch-gateway-S2ULVFAI.js +35 -0
- package/dist/mock-fetch-gateway-S2ULVFAI.js.map +6 -0
- package/dist/mock-local-deps-2VVNJLFF.js +200 -0
- package/dist/mock-local-deps-2VVNJLFF.js.map +6 -0
- package/dist/mock-local-deps.d.ts +1 -1
- package/dist/mock-server-TFO7Q26H.js +22 -0
- package/dist/mock-server-TFO7Q26H.js.map +6 -0
- package/dist/oauth-browser.d.ts +42 -0
- package/dist/oauth-provider.d.ts +14 -19
- package/dist/open-api-client-SR4B7265.js +43 -0
- package/dist/open-api-client-SR4B7265.js.map +6 -0
- package/dist/open-api-client.d.ts +2 -1
- package/dist/org-535LB4PD.js +134 -0
- package/dist/org-535LB4PD.js.map +6 -0
- package/dist/org-resolve.d.ts +2 -0
- package/dist/package-app-dev.d.ts +2 -2
- package/dist/package-manifest-DFF6XCPV.js +15 -0
- package/dist/package-manifest-DFF6XCPV.js.map +6 -0
- package/dist/remote-execute-JN6DRFEU.js +129 -0
- package/dist/remote-execute-JN6DRFEU.js.map +6 -0
- package/dist/remote-execute.d.ts +3 -3
- package/dist/setup-git-MHK563LB.js +87 -0
- package/dist/setup-git-MHK563LB.js.map +6 -0
- package/dist/setup-git.d.ts +1 -1
- package/dist/skill-D74XVI6T.js +61 -0
- package/dist/skill-D74XVI6T.js.map +6 -0
- package/dist/store-27J4CQZ2.js +48 -0
- package/dist/store-27J4CQZ2.js.map +6 -0
- package/dist/structured-error-5MLWXYR3.js +14 -0
- package/dist/structured-error-5MLWXYR3.js.map +6 -0
- package/dist/structured-error.d.ts +1 -1
- package/dist/target-33ATHDUF.js +17 -0
- package/dist/target-33ATHDUF.js.map +6 -0
- package/package.json +12 -11
- package/skills/kody/SKILL.md +53 -50
- package/dist/add-mcp-install.js +0 -59
- package/dist/add-mcp-install.js.map +0 -1
- package/dist/api-token-store.js +0 -120
- package/dist/api-token-store.js.map +0 -1
- package/dist/api-token.js +0 -238
- package/dist/api-token.js.map +0 -1
- package/dist/auth-bootstrap.js +0 -373
- package/dist/auth-bootstrap.js.map +0 -1
- package/dist/auth.js +0 -386
- package/dist/auth.js.map +0 -1
- package/dist/capability-proxy.js +0 -527
- package/dist/capability-proxy.js.map +0 -1
- package/dist/credential-call-map.js +0 -155
- package/dist/credential-call-map.js.map +0 -1
- package/dist/credential-client-runtime-source.js +0 -9
- package/dist/credential-client-runtime-source.js.map +0 -1
- package/dist/credentialed-fetch-scan.js +0 -98
- package/dist/credentialed-fetch-scan.js.map +0 -1
- package/dist/defaults.js +0 -34
- package/dist/defaults.js.map +0 -1
- package/dist/detect-running-hosts.js +0 -13
- package/dist/detect-running-hosts.js.map +0 -1
- package/dist/dev/dev-assets.js +0 -202
- package/dist/dev/dev-assets.js.map +0 -1
- package/dist/dev/dev-bundler.js +0 -65
- package/dist/dev/dev-bundler.js.map +0 -1
- package/dist/dev/dev-events.js +0 -94
- package/dist/dev/dev-events.js.map +0 -1
- package/dist/dev/dev-port.js +0 -80
- package/dist/dev/dev-port.js.map +0 -1
- package/dist/dev/dev-protocol.js +0 -14
- package/dist/dev/dev-protocol.js.map +0 -1
- package/dist/dev/dev-registry.js +0 -95
- package/dist/dev/dev-registry.js.map +0 -1
- package/dist/dev/dev-watcher.js +0 -162
- package/dist/dev/dev-watcher.js.map +0 -1
- package/dist/dev/error-page.js +0 -64
- package/dist/dev/error-page.js.map +0 -1
- package/dist/dev/live-reload.js +0 -151
- package/dist/dev/live-reload.js.map +0 -1
- package/dist/dev/prebuild.js +0 -102
- package/dist/dev/prebuild.js.map +0 -1
- package/dist/dev/process-reaper.js +0 -151
- package/dist/dev/process-reaper.js.map +0 -1
- package/dist/dev/stack-trace.js +0 -56
- package/dist/dev/stack-trace.js.map +0 -1
- package/dist/dev/workerd-process.js +0 -118
- package/dist/dev/workerd-process.js.map +0 -1
- package/dist/dev-command.js +0 -676
- package/dist/dev-command.js.map +0 -1
- package/dist/discover-login-identity.js +0 -77
- package/dist/discover-login-identity.js.map +0 -1
- package/dist/discover-org.js +0 -62
- package/dist/discover-org.js.map +0 -1
- package/dist/git-credential.js +0 -147
- package/dist/git-credential.js.map +0 -1
- package/dist/help.js +0 -333
- package/dist/help.js.map +0 -1
- package/dist/host-catalog.js +0 -427
- package/dist/host-catalog.js.map +0 -1
- package/dist/host-config.js +0 -80
- package/dist/host-config.js.map +0 -1
- package/dist/http-error-body.js +0 -35
- package/dist/http-error-body.js.map +0 -1
- package/dist/install-hosts.js +0 -328
- package/dist/install-hosts.js.map +0 -1
- package/dist/install.js +0 -120
- package/dist/install.js.map +0 -1
- package/dist/invoke-passthrough.js +0 -60
- package/dist/invoke-passthrough.js.map +0 -1
- package/dist/local-bundle.js +0 -200
- package/dist/local-bundle.js.map +0 -1
- package/dist/local-execute-auth.js +0 -53
- package/dist/local-execute-auth.js.map +0 -1
- package/dist/local-execute.js +0 -338
- package/dist/local-execute.js.map +0 -1
- package/dist/local-gateway-fetch-forward.js +0 -135
- package/dist/local-gateway-fetch-forward.js.map +0 -1
- package/dist/local-host-runtime-surface.js +0 -75
- package/dist/local-host-runtime-surface.js.map +0 -1
- package/dist/local-package-graph.js +0 -405
- package/dist/local-package-graph.js.map +0 -1
- package/dist/local-package-run.js +0 -307
- package/dist/local-package-run.js.map +0 -1
- package/dist/local-runtime-bridge.js +0 -132
- package/dist/local-runtime-bridge.js.map +0 -1
- package/dist/local-runtime-source.js +0 -541
- package/dist/local-runtime-source.js.map +0 -1
- package/dist/login-callback-assets.js +0 -10
- package/dist/login-callback-assets.js.map +0 -1
- package/dist/login-callback-page.js +0 -362
- package/dist/login-callback-page.js.map +0 -1
- package/dist/mcp.d.ts +0 -34
- package/dist/mcp.js +0 -134
- package/dist/mcp.js.map +0 -1
- package/dist/mock/ensure-msw.js +0 -215
- package/dist/mock/ensure-msw.js.map +0 -1
- package/dist/mock/install-mock-fetch-shim.js +0 -36
- package/dist/mock/install-mock-fetch-shim.js.map +0 -1
- package/dist/mock/load-package-mocks.js +0 -136
- package/dist/mock/load-package-mocks.js.map +0 -1
- package/dist/mock/mock-fetch-gateway.js +0 -343
- package/dist/mock/mock-fetch-gateway.js.map +0 -1
- package/dist/mock/mock-gateway-fetch-shim.js +0 -104
- package/dist/mock/mock-gateway-fetch-shim.js.map +0 -1
- package/dist/mock/mock-server.js +0 -499
- package/dist/mock/mock-server.js.map +0 -1
- package/dist/mock/mock-storage.js +0 -186
- package/dist/mock/mock-storage.js.map +0 -1
- package/dist/mock/mock-supported.js +0 -63
- package/dist/mock/mock-supported.js.map +0 -1
- package/dist/mock-dep-resolve.js +0 -47
- package/dist/mock-dep-resolve.js.map +0 -1
- package/dist/mock-local-deps.js +0 -207
- package/dist/mock-local-deps.js.map +0 -1
- package/dist/network-error.js +0 -11
- package/dist/network-error.js.map +0 -1
- package/dist/node-engine.js +0 -21
- package/dist/node-engine.js.map +0 -1
- package/dist/oauth-provider.js +0 -107
- package/dist/oauth-provider.js.map +0 -1
- package/dist/onboarding-prompt.js +0 -15
- package/dist/onboarding-prompt.js.map +0 -1
- package/dist/open-api-client.js +0 -432
- package/dist/open-api-client.js.map +0 -1
- package/dist/open-url.js +0 -21
- package/dist/open-url.js.map +0 -1
- package/dist/org-resolve.js +0 -16
- package/dist/org-resolve.js.map +0 -1
- package/dist/org-slug.js +0 -11
- package/dist/org-slug.js.map +0 -1
- package/dist/org.js +0 -100
- package/dist/org.js.map +0 -1
- package/dist/package-app-dev.js +0 -922
- package/dist/package-app-dev.js.map +0 -1
- package/dist/package-collaborators.js +0 -179
- package/dist/package-collaborators.js.map +0 -1
- package/dist/package-info.js +0 -19
- package/dist/package-info.js.map +0 -1
- package/dist/package-local-runtime-source.js.map +0 -1
- package/dist/package-manifest.js +0 -364
- package/dist/package-manifest.js.map +0 -1
- package/dist/process-info.js +0 -14
- package/dist/process-info.js.map +0 -1
- package/dist/redact.js +0 -16
- package/dist/redact.js.map +0 -1
- package/dist/remote-execute.js +0 -108
- package/dist/remote-execute.js.map +0 -1
- package/dist/setup-git.js +0 -676
- package/dist/setup-git.js.map +0 -1
- package/dist/skill.js +0 -35
- package/dist/skill.js.map +0 -1
- package/dist/store.js +0 -597
- package/dist/store.js.map +0 -1
- package/dist/structured-error.js +0 -57
- package/dist/structured-error.js.map +0 -1
- package/dist/target.js +0 -52
- package/dist/target.js.map +0 -1
- package/dist/tool-result.js +0 -19
- package/dist/tool-result.js.map +0 -1
- package/dist/which.js +0 -22
- package/dist/which.js.map +0 -1
- package/dist/workerd-binary.js +0 -98
- package/dist/workerd-binary.js.map +0 -1
|
@@ -0,0 +1,2005 @@
|
|
|
1
|
+
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
openUrl
|
|
4
|
+
} from "./chunk-ZILB2HWE.js";
|
|
5
|
+
import {
|
|
6
|
+
redactError
|
|
7
|
+
} from "./chunk-CASGRYOF.js";
|
|
8
|
+
import {
|
|
9
|
+
OpenApiError,
|
|
10
|
+
openApiGet,
|
|
11
|
+
openApiRequest
|
|
12
|
+
} from "./chunk-W2SAFAT4.js";
|
|
13
|
+
import {
|
|
14
|
+
isScopedApiToken,
|
|
15
|
+
missingCliAuthMessage
|
|
16
|
+
} from "./chunk-5JIRHJQP.js";
|
|
17
|
+
import {
|
|
18
|
+
orgSlugFromFlag
|
|
19
|
+
} from "./chunk-CWWHR4FH.js";
|
|
20
|
+
import {
|
|
21
|
+
loadCredentialStore,
|
|
22
|
+
loadCredentials,
|
|
23
|
+
loadPendingCredentials,
|
|
24
|
+
promotePendingOrg,
|
|
25
|
+
saveCredentials
|
|
26
|
+
} from "./chunk-3H72XY5R.js";
|
|
27
|
+
import {
|
|
28
|
+
accessTokenSkewMs,
|
|
29
|
+
cliClientMetadataUrl,
|
|
30
|
+
cliRedirectUrl,
|
|
31
|
+
defaultApiUrl,
|
|
32
|
+
defaultMcpUrl,
|
|
33
|
+
defaultScopes,
|
|
34
|
+
loginTimeoutMs
|
|
35
|
+
} from "./chunk-BTL3PJ2H.js";
|
|
36
|
+
|
|
37
|
+
// src/auth.ts
|
|
38
|
+
import {
|
|
39
|
+
createServer
|
|
40
|
+
} from "node:http";
|
|
41
|
+
|
|
42
|
+
// src/login-callback-assets.ts
|
|
43
|
+
var kodyMarkPngDataUri = "data:image/png;base64,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";
|
|
44
|
+
var kodyPatternWebpDataUri = "data:image/webp;base64,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";
|
|
45
|
+
|
|
46
|
+
// src/login-callback-page.ts
|
|
47
|
+
var errorCopy = {
|
|
48
|
+
denied: {
|
|
49
|
+
title: "Sign-in cancelled",
|
|
50
|
+
body: "You denied access in the browser. Nothing was saved."
|
|
51
|
+
},
|
|
52
|
+
state_mismatch: {
|
|
53
|
+
title: "Sign-in session mismatch",
|
|
54
|
+
body: "This login link did not match the request from your terminal (or it was reused)."
|
|
55
|
+
},
|
|
56
|
+
expired: {
|
|
57
|
+
title: "Sign-in expired",
|
|
58
|
+
body: "This login session timed out before it finished."
|
|
59
|
+
},
|
|
60
|
+
failed: {
|
|
61
|
+
title: "Sign-in failed",
|
|
62
|
+
body: "Kody could not finish signing the CLI in."
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function escapeHtml(value) {
|
|
66
|
+
return value.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
67
|
+
}
|
|
68
|
+
function accountLabel(account) {
|
|
69
|
+
const display = account.displayName?.trim();
|
|
70
|
+
const email = account.email?.trim();
|
|
71
|
+
const username = account.username?.trim();
|
|
72
|
+
if (display && email && display !== email) return `${display} (${email})`;
|
|
73
|
+
if (display) return display;
|
|
74
|
+
if (email) return email;
|
|
75
|
+
if (username) return username;
|
|
76
|
+
return void 0;
|
|
77
|
+
}
|
|
78
|
+
function brandMarkSvg() {
|
|
79
|
+
return `<svg class="mark" xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40" role="img" aria-label="Kody">
|
|
80
|
+
<image href="${kodyMarkPngDataUri}" width="40" height="40" preserveAspectRatio="xMidYMid meet"/>
|
|
81
|
+
</svg>`;
|
|
82
|
+
}
|
|
83
|
+
function statusIconSvg(kind) {
|
|
84
|
+
if (kind === "success") {
|
|
85
|
+
return `<svg class="status-icon" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" aria-hidden="true">
|
|
86
|
+
<circle cx="14" cy="14" r="14" fill="var(--color-primary)"/>
|
|
87
|
+
<path d="M8.2 14.2l3.4 3.4 8.2-8.2" fill="none" stroke="var(--color-on-primary)" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
88
|
+
</svg>`;
|
|
89
|
+
}
|
|
90
|
+
return `<svg class="status-icon" xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 28 28" aria-hidden="true">
|
|
91
|
+
<circle cx="14" cy="14" r="14" fill="var(--color-danger)"/>
|
|
92
|
+
<path d="M9.5 9.5l9 9M18.5 9.5l-9 9" fill="none" stroke="var(--color-on-danger)" stroke-width="2.4" stroke-linecap="round"/>
|
|
93
|
+
</svg>`;
|
|
94
|
+
}
|
|
95
|
+
function pageCss() {
|
|
96
|
+
return `:root {
|
|
97
|
+
color-scheme: light dark;
|
|
98
|
+
--color-primary: oklch(0.54 0.18 145);
|
|
99
|
+
--color-primary-hover: oklch(0.48 0.17 145);
|
|
100
|
+
--color-on-primary: oklch(1 0 0);
|
|
101
|
+
--color-primary-text: oklch(0.455 0.17 145);
|
|
102
|
+
--color-background: oklch(0.93 0.004 250);
|
|
103
|
+
--color-surface: oklch(0.975 0.002 250);
|
|
104
|
+
--color-text: oklch(0.22 0.012 260);
|
|
105
|
+
--color-text-muted: oklch(0.44 0.014 260);
|
|
106
|
+
--color-border: oklch(0.84 0.008 250);
|
|
107
|
+
--color-danger: oklch(0.535 0.19 25);
|
|
108
|
+
--color-on-danger: oklch(0.985 0.005 25);
|
|
109
|
+
--color-primary-dark: oklch(0.72 0.205 145);
|
|
110
|
+
--color-primary-hover-dark: oklch(0.78 0.21 145);
|
|
111
|
+
--color-on-primary-dark: oklch(0.2 0.05 148);
|
|
112
|
+
--color-primary-text-dark: oklch(0.75 0.2 145);
|
|
113
|
+
--color-background-dark: oklch(0.19 0.008 255);
|
|
114
|
+
--color-surface-dark: oklch(0.235 0.01 255);
|
|
115
|
+
--color-text-dark: oklch(0.945 0.005 250);
|
|
116
|
+
--color-text-muted-dark: oklch(0.72 0.01 255);
|
|
117
|
+
--color-border-dark: oklch(0.33 0.01 255);
|
|
118
|
+
--color-danger-dark: oklch(0.75 0.16 25);
|
|
119
|
+
--color-on-danger-dark: oklch(0.2 0.05 25);
|
|
120
|
+
--font-display: 'Bricolage Grotesque', system-ui, sans-serif;
|
|
121
|
+
--font-body: 'Wix Madefor Text', system-ui, sans-serif;
|
|
122
|
+
--radius-card: 14px;
|
|
123
|
+
--ease-out: cubic-bezier(0.22, 1, 0.36, 1);
|
|
124
|
+
--kody-pattern: url('${kodyPatternWebpDataUri}');
|
|
125
|
+
}
|
|
126
|
+
@media (prefers-color-scheme: dark) {
|
|
127
|
+
:root {
|
|
128
|
+
color-scheme: dark;
|
|
129
|
+
--color-primary: var(--color-primary-dark);
|
|
130
|
+
--color-primary-hover: var(--color-primary-hover-dark);
|
|
131
|
+
--color-on-primary: var(--color-on-primary-dark);
|
|
132
|
+
--color-primary-text: var(--color-primary-text-dark);
|
|
133
|
+
--color-background: var(--color-background-dark);
|
|
134
|
+
--color-surface: var(--color-surface-dark);
|
|
135
|
+
--color-text: var(--color-text-dark);
|
|
136
|
+
--color-text-muted: var(--color-text-muted-dark);
|
|
137
|
+
--color-border: var(--color-border-dark);
|
|
138
|
+
--color-danger: var(--color-danger-dark);
|
|
139
|
+
--color-on-danger: var(--color-on-danger-dark);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
*, *::before, *::after { box-sizing: border-box; }
|
|
143
|
+
html, body { height: 100%; }
|
|
144
|
+
body {
|
|
145
|
+
margin: 0;
|
|
146
|
+
min-height: 100%;
|
|
147
|
+
font-family: var(--font-body);
|
|
148
|
+
font-size: 1.0625rem;
|
|
149
|
+
line-height: 1.62;
|
|
150
|
+
color: var(--color-text);
|
|
151
|
+
background: var(--color-background);
|
|
152
|
+
-webkit-font-smoothing: antialiased;
|
|
153
|
+
text-rendering: optimizeLegibility;
|
|
154
|
+
}
|
|
155
|
+
.page {
|
|
156
|
+
position: relative;
|
|
157
|
+
isolation: isolate;
|
|
158
|
+
min-height: 100%;
|
|
159
|
+
display: grid;
|
|
160
|
+
place-items: center;
|
|
161
|
+
padding: clamp(1.5rem, 4vw, 3rem);
|
|
162
|
+
}
|
|
163
|
+
.page::before {
|
|
164
|
+
content: '';
|
|
165
|
+
position: absolute;
|
|
166
|
+
z-index: -1;
|
|
167
|
+
inset: 0;
|
|
168
|
+
background: radial-gradient(
|
|
169
|
+
ellipse 55% 50% at 50% 42%,
|
|
170
|
+
oklch(from var(--color-text) l c h / 0.055),
|
|
171
|
+
transparent 72%
|
|
172
|
+
);
|
|
173
|
+
mask-image: var(--kody-pattern);
|
|
174
|
+
mask-position: center;
|
|
175
|
+
mask-size: 220px;
|
|
176
|
+
mask-repeat: repeat;
|
|
177
|
+
-webkit-mask-image: var(--kody-pattern);
|
|
178
|
+
-webkit-mask-position: center;
|
|
179
|
+
-webkit-mask-size: 220px;
|
|
180
|
+
-webkit-mask-repeat: repeat;
|
|
181
|
+
pointer-events: none;
|
|
182
|
+
}
|
|
183
|
+
.panel {
|
|
184
|
+
width: min(100%, 28rem);
|
|
185
|
+
background: var(--color-surface);
|
|
186
|
+
border: 1px solid var(--color-border);
|
|
187
|
+
border-radius: var(--radius-card);
|
|
188
|
+
padding: 1.75rem 1.6rem 1.55rem;
|
|
189
|
+
box-shadow: 0 10px 28px oklch(from var(--color-text) l c h / 0.06);
|
|
190
|
+
}
|
|
191
|
+
.brand {
|
|
192
|
+
display: inline-flex;
|
|
193
|
+
align-items: center;
|
|
194
|
+
gap: 0.6rem;
|
|
195
|
+
font: 700 1.2rem/1 var(--font-display);
|
|
196
|
+
letter-spacing: -0.01em;
|
|
197
|
+
color: var(--color-text);
|
|
198
|
+
text-decoration: none;
|
|
199
|
+
}
|
|
200
|
+
.mark { display: block; border-radius: 10px; background: #111; }
|
|
201
|
+
.status {
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: flex-start;
|
|
204
|
+
gap: 0.85rem;
|
|
205
|
+
margin-top: 1.45rem;
|
|
206
|
+
}
|
|
207
|
+
.status-icon { flex: 0 0 auto; margin-top: 0.15rem; }
|
|
208
|
+
h1 {
|
|
209
|
+
margin: 0;
|
|
210
|
+
font: 800 1.55rem/1.15 var(--font-display);
|
|
211
|
+
letter-spacing: -0.02em;
|
|
212
|
+
text-wrap: balance;
|
|
213
|
+
}
|
|
214
|
+
.lead {
|
|
215
|
+
margin: 0.55rem 0 0;
|
|
216
|
+
color: var(--color-text-muted);
|
|
217
|
+
text-wrap: pretty;
|
|
218
|
+
}
|
|
219
|
+
.facts {
|
|
220
|
+
list-style: none;
|
|
221
|
+
margin: 1.15rem 0 0;
|
|
222
|
+
padding: 0;
|
|
223
|
+
display: grid;
|
|
224
|
+
gap: 0.55rem;
|
|
225
|
+
}
|
|
226
|
+
.facts li {
|
|
227
|
+
display: flex;
|
|
228
|
+
flex-direction: column;
|
|
229
|
+
gap: 0.2rem;
|
|
230
|
+
padding: 0.55rem 0.75rem;
|
|
231
|
+
border-radius: 10px;
|
|
232
|
+
background: color-mix(in oklch, var(--color-primary) 7%, transparent);
|
|
233
|
+
}
|
|
234
|
+
.facts .label {
|
|
235
|
+
font-size: 0.72rem;
|
|
236
|
+
font-weight: 600;
|
|
237
|
+
letter-spacing: 0.04em;
|
|
238
|
+
text-transform: uppercase;
|
|
239
|
+
color: var(--color-text-muted);
|
|
240
|
+
}
|
|
241
|
+
.facts .value {
|
|
242
|
+
font-family: var(--font-display);
|
|
243
|
+
font-weight: 650;
|
|
244
|
+
color: var(--color-text);
|
|
245
|
+
overflow-wrap: anywhere;
|
|
246
|
+
}
|
|
247
|
+
.next {
|
|
248
|
+
margin: 1.2rem 0 0;
|
|
249
|
+
padding-top: 1rem;
|
|
250
|
+
border-top: 1px solid var(--color-border);
|
|
251
|
+
color: var(--color-text-muted);
|
|
252
|
+
font-size: 0.95rem;
|
|
253
|
+
}
|
|
254
|
+
.next code {
|
|
255
|
+
font-family: ui-monospace, 'SF Mono', Menlo, monospace;
|
|
256
|
+
font-size: 0.9em;
|
|
257
|
+
color: var(--color-primary-text);
|
|
258
|
+
background: color-mix(in oklch, var(--color-primary) 10%, transparent);
|
|
259
|
+
padding: 0.12rem 0.35rem;
|
|
260
|
+
border-radius: 0.35rem;
|
|
261
|
+
}
|
|
262
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
263
|
+
.panel {
|
|
264
|
+
animation: rise 420ms var(--ease-out) both;
|
|
265
|
+
}
|
|
266
|
+
.status-icon {
|
|
267
|
+
animation: pop 480ms var(--ease-out) 80ms both;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
@keyframes rise {
|
|
271
|
+
from { opacity: 0; translate: 0 12px; }
|
|
272
|
+
to { opacity: 1; translate: 0 0; }
|
|
273
|
+
}
|
|
274
|
+
@keyframes pop {
|
|
275
|
+
from { opacity: 0; scale: 0.84; }
|
|
276
|
+
to { opacity: 1; scale: 1; }
|
|
277
|
+
}`;
|
|
278
|
+
}
|
|
279
|
+
function closeScript() {
|
|
280
|
+
return `<script>
|
|
281
|
+
(function () {
|
|
282
|
+
function attempt() {
|
|
283
|
+
try { window.close(); } catch (_) {}
|
|
284
|
+
}
|
|
285
|
+
attempt();
|
|
286
|
+
setTimeout(attempt, 120);
|
|
287
|
+
})();
|
|
288
|
+
</script>`;
|
|
289
|
+
}
|
|
290
|
+
function renderSuccess(page) {
|
|
291
|
+
const org = page.org?.trim();
|
|
292
|
+
const account = page.account ? accountLabel(page.account) : void 0;
|
|
293
|
+
const facts = [];
|
|
294
|
+
if (org) {
|
|
295
|
+
facts.push(
|
|
296
|
+
`<li><span class="label">Org</span><span class="value">${escapeHtml(org)}</span></li>`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
if (account) {
|
|
300
|
+
facts.push(
|
|
301
|
+
`<li><span class="label">Account</span><span class="value">${escapeHtml(account)}</span></li>`
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
const factsHtml = facts.length > 0 ? `<ul class="facts">${facts.join("")}</ul>` : "";
|
|
305
|
+
const lead = org ? `Kody CLI is signed in to <strong>${escapeHtml(org)}</strong>.` : "Kody CLI is signed in.";
|
|
306
|
+
return `<!doctype html>
|
|
307
|
+
<html lang="en">
|
|
308
|
+
<head>
|
|
309
|
+
<meta charset="utf-8"/>
|
|
310
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
311
|
+
<meta name="color-scheme" content="light dark"/>
|
|
312
|
+
<title>Signed in \xB7 Kody CLI</title>
|
|
313
|
+
<style>${pageCss()}</style>
|
|
314
|
+
</head>
|
|
315
|
+
<body>
|
|
316
|
+
<main class="page">
|
|
317
|
+
<section class="panel" data-state="success">
|
|
318
|
+
<div class="brand">${brandMarkSvg()}<span>Kody</span></div>
|
|
319
|
+
<div class="status">
|
|
320
|
+
${statusIconSvg("success")}
|
|
321
|
+
<div>
|
|
322
|
+
<h1>You're signed in</h1>
|
|
323
|
+
<p class="lead">${lead}</p>
|
|
324
|
+
</div>
|
|
325
|
+
</div>
|
|
326
|
+
${factsHtml}
|
|
327
|
+
<p class="next">You can close this tab and return to your terminal.</p>
|
|
328
|
+
</section>
|
|
329
|
+
</main>
|
|
330
|
+
${closeScript()}
|
|
331
|
+
</body>
|
|
332
|
+
</html>`;
|
|
333
|
+
}
|
|
334
|
+
function renderError(page) {
|
|
335
|
+
const copy = errorCopy[page.error];
|
|
336
|
+
return `<!doctype html>
|
|
337
|
+
<html lang="en">
|
|
338
|
+
<head>
|
|
339
|
+
<meta charset="utf-8"/>
|
|
340
|
+
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
|
341
|
+
<meta name="color-scheme" content="light dark"/>
|
|
342
|
+
<title>${escapeHtml(copy.title)} \xB7 Kody CLI</title>
|
|
343
|
+
<style>${pageCss()}</style>
|
|
344
|
+
</head>
|
|
345
|
+
<body>
|
|
346
|
+
<main class="page">
|
|
347
|
+
<section class="panel" data-state="error" data-error="${escapeHtml(page.error)}">
|
|
348
|
+
<div class="brand">${brandMarkSvg()}<span>Kody</span></div>
|
|
349
|
+
<div class="status">
|
|
350
|
+
${statusIconSvg("error")}
|
|
351
|
+
<div>
|
|
352
|
+
<h1>${escapeHtml(copy.title)}</h1>
|
|
353
|
+
<p class="lead">${escapeHtml(copy.body)}</p>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
<p class="next">Return to your terminal and run <code>kody login</code> to try again.</p>
|
|
357
|
+
</section>
|
|
358
|
+
</main>
|
|
359
|
+
${closeScript()}
|
|
360
|
+
</body>
|
|
361
|
+
</html>`;
|
|
362
|
+
}
|
|
363
|
+
function renderLoginCallbackPage(page) {
|
|
364
|
+
if (page.kind === "success") return renderSuccess(page);
|
|
365
|
+
return renderError(page);
|
|
366
|
+
}
|
|
367
|
+
function loginCallbackErrorFromOAuthError(error) {
|
|
368
|
+
const normalized = error.trim().toLowerCase();
|
|
369
|
+
if (normalized === "access_denied" || normalized === "denied" || normalized === "consent_required") {
|
|
370
|
+
return "denied";
|
|
371
|
+
}
|
|
372
|
+
if (normalized === "expired" || normalized === "expired_token" || normalized === "invalid_grant" || normalized.includes("expired")) {
|
|
373
|
+
return "expired";
|
|
374
|
+
}
|
|
375
|
+
if (normalized === "state_mismatch" || normalized === "invalid_request" || normalized === "invalid_state") {
|
|
376
|
+
return "state_mismatch";
|
|
377
|
+
}
|
|
378
|
+
return "failed";
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// src/discover-login-identity.ts
|
|
382
|
+
async function discoverLoginIdentity(input) {
|
|
383
|
+
const payload = await openApiGet({
|
|
384
|
+
token: input.accessToken,
|
|
385
|
+
apiUrl: input.apiUrl ?? defaultApiUrl,
|
|
386
|
+
path: "v1/me",
|
|
387
|
+
fetchFn: input.fetchFn,
|
|
388
|
+
...input.org ? { org: input.org } : {}
|
|
389
|
+
});
|
|
390
|
+
if (!payload || typeof payload !== "object" || Array.isArray(payload)) {
|
|
391
|
+
throw new Error(
|
|
392
|
+
"Could not determine the organization for this login (metaGetCurrentUser returned no result)."
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
const record = payload;
|
|
396
|
+
const orgSlug = typeof record.org?.slug === "string" ? record.org.slug : null;
|
|
397
|
+
if (!orgSlug) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
"Could not determine the organization for this login (metaGetCurrentUser returned no org.slug)."
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
const account = {};
|
|
403
|
+
if (typeof record.display_name === "string" && record.display_name.trim()) {
|
|
404
|
+
account.displayName = record.display_name.trim();
|
|
405
|
+
}
|
|
406
|
+
if (typeof record.email === "string" && record.email.trim()) {
|
|
407
|
+
account.email = record.email.trim();
|
|
408
|
+
}
|
|
409
|
+
if (typeof record.username === "string" && record.username.trim()) {
|
|
410
|
+
account.username = record.username.trim();
|
|
411
|
+
}
|
|
412
|
+
return {
|
|
413
|
+
org: orgSlugFromFlag(orgSlug),
|
|
414
|
+
account
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/discover-org.ts
|
|
419
|
+
async function discoverOrgSlugFromAccessToken(input) {
|
|
420
|
+
try {
|
|
421
|
+
const identity = await discoverLoginIdentity(input);
|
|
422
|
+
return orgSlugFromFlag(identity.org);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
const message = error instanceof Error ? error.message : "metaGetCurrentUser failed";
|
|
425
|
+
if (message.startsWith("Could not determine the organization")) throw error;
|
|
426
|
+
throw new Error(
|
|
427
|
+
`Could not determine the organization for this login (${message}).`
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ../../node_modules/oauth4webapi/build/index.js
|
|
433
|
+
var USER_AGENT;
|
|
434
|
+
if (typeof navigator === "undefined" || !navigator.userAgent?.startsWith?.("Mozilla/5.0 ")) {
|
|
435
|
+
const NAME = "oauth4webapi";
|
|
436
|
+
const VERSION = "v3.8.8";
|
|
437
|
+
USER_AGENT = `${NAME}/${VERSION}`;
|
|
438
|
+
}
|
|
439
|
+
function looseInstanceOf(input, expected) {
|
|
440
|
+
if (input == null) {
|
|
441
|
+
return false;
|
|
442
|
+
}
|
|
443
|
+
try {
|
|
444
|
+
return input instanceof expected || Object.getPrototypeOf(input)[Symbol.toStringTag] === expected.prototype[Symbol.toStringTag];
|
|
445
|
+
} catch {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
var ERR_INVALID_ARG_VALUE = "ERR_INVALID_ARG_VALUE";
|
|
450
|
+
var ERR_INVALID_ARG_TYPE = "ERR_INVALID_ARG_TYPE";
|
|
451
|
+
function CodedTypeError(message, code, cause) {
|
|
452
|
+
const err = new TypeError(message, { cause });
|
|
453
|
+
Object.assign(err, { code });
|
|
454
|
+
return err;
|
|
455
|
+
}
|
|
456
|
+
var allowInsecureRequests = /* @__PURE__ */ Symbol();
|
|
457
|
+
var clockSkew = /* @__PURE__ */ Symbol();
|
|
458
|
+
var clockTolerance = /* @__PURE__ */ Symbol();
|
|
459
|
+
var customFetch = /* @__PURE__ */ Symbol();
|
|
460
|
+
var jweDecrypt = /* @__PURE__ */ Symbol();
|
|
461
|
+
var encoder = new TextEncoder();
|
|
462
|
+
var decoder = new TextDecoder();
|
|
463
|
+
function buf(input) {
|
|
464
|
+
if (typeof input === "string") {
|
|
465
|
+
return encoder.encode(input);
|
|
466
|
+
}
|
|
467
|
+
return decoder.decode(input);
|
|
468
|
+
}
|
|
469
|
+
var encodeBase64Url;
|
|
470
|
+
if (Uint8Array.prototype.toBase64) {
|
|
471
|
+
encodeBase64Url = (input) => {
|
|
472
|
+
if (input instanceof ArrayBuffer) {
|
|
473
|
+
input = new Uint8Array(input);
|
|
474
|
+
}
|
|
475
|
+
return input.toBase64({ alphabet: "base64url", omitPadding: true });
|
|
476
|
+
};
|
|
477
|
+
} else {
|
|
478
|
+
const CHUNK_SIZE = 32768;
|
|
479
|
+
encodeBase64Url = (input) => {
|
|
480
|
+
if (input instanceof ArrayBuffer) {
|
|
481
|
+
input = new Uint8Array(input);
|
|
482
|
+
}
|
|
483
|
+
const arr = [];
|
|
484
|
+
for (let i = 0; i < input.byteLength; i += CHUNK_SIZE) {
|
|
485
|
+
arr.push(String.fromCharCode.apply(null, input.subarray(i, i + CHUNK_SIZE)));
|
|
486
|
+
}
|
|
487
|
+
return btoa(arr.join("")).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
var decodeBase64Url;
|
|
491
|
+
if (Uint8Array.fromBase64) {
|
|
492
|
+
decodeBase64Url = (input) => {
|
|
493
|
+
try {
|
|
494
|
+
return Uint8Array.fromBase64(input, { alphabet: "base64url" });
|
|
495
|
+
} catch (cause) {
|
|
496
|
+
throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
} else {
|
|
500
|
+
decodeBase64Url = (input) => {
|
|
501
|
+
try {
|
|
502
|
+
const binary = atob(input.replace(/-/g, "+").replace(/_/g, "/").replace(/\s/g, ""));
|
|
503
|
+
const bytes = new Uint8Array(binary.length);
|
|
504
|
+
for (let i = 0; i < binary.length; i++) {
|
|
505
|
+
bytes[i] = binary.charCodeAt(i);
|
|
506
|
+
}
|
|
507
|
+
return bytes;
|
|
508
|
+
} catch (cause) {
|
|
509
|
+
throw CodedTypeError("The input to be decoded is not correctly encoded.", ERR_INVALID_ARG_VALUE, cause);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
function b64u(input) {
|
|
514
|
+
if (typeof input === "string") {
|
|
515
|
+
return decodeBase64Url(input);
|
|
516
|
+
}
|
|
517
|
+
return encodeBase64Url(input);
|
|
518
|
+
}
|
|
519
|
+
var UnsupportedOperationError = class extends Error {
|
|
520
|
+
code;
|
|
521
|
+
constructor(message, options) {
|
|
522
|
+
super(message, options);
|
|
523
|
+
this.name = this.constructor.name;
|
|
524
|
+
this.code = UNSUPPORTED_OPERATION;
|
|
525
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
var OperationProcessingError = class extends Error {
|
|
529
|
+
code;
|
|
530
|
+
constructor(message, options) {
|
|
531
|
+
super(message, options);
|
|
532
|
+
this.name = this.constructor.name;
|
|
533
|
+
if (options?.code) {
|
|
534
|
+
this.code = options?.code;
|
|
535
|
+
}
|
|
536
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
function OPE(message, code, cause) {
|
|
540
|
+
return new OperationProcessingError(message, { code, cause });
|
|
541
|
+
}
|
|
542
|
+
function isJsonObject(input) {
|
|
543
|
+
if (input === null || typeof input !== "object" || Array.isArray(input)) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
return true;
|
|
547
|
+
}
|
|
548
|
+
function prepareHeaders(input) {
|
|
549
|
+
if (looseInstanceOf(input, Headers)) {
|
|
550
|
+
input = Object.fromEntries(input.entries());
|
|
551
|
+
}
|
|
552
|
+
const headers = new Headers(input ?? {});
|
|
553
|
+
if (USER_AGENT && !headers.has("user-agent")) {
|
|
554
|
+
headers.set("user-agent", USER_AGENT);
|
|
555
|
+
}
|
|
556
|
+
if (headers.has("authorization")) {
|
|
557
|
+
throw CodedTypeError('"options.headers" must not include the "authorization" header name', ERR_INVALID_ARG_VALUE);
|
|
558
|
+
}
|
|
559
|
+
return headers;
|
|
560
|
+
}
|
|
561
|
+
function signal(url, value) {
|
|
562
|
+
if (value !== void 0) {
|
|
563
|
+
if (typeof value === "function") {
|
|
564
|
+
value = value(url.href);
|
|
565
|
+
}
|
|
566
|
+
if (!(value instanceof AbortSignal)) {
|
|
567
|
+
throw CodedTypeError('"options.signal" must return or be an instance of AbortSignal', ERR_INVALID_ARG_TYPE);
|
|
568
|
+
}
|
|
569
|
+
return value;
|
|
570
|
+
}
|
|
571
|
+
return void 0;
|
|
572
|
+
}
|
|
573
|
+
function replaceDoubleSlash(pathname) {
|
|
574
|
+
if (pathname.includes("//")) {
|
|
575
|
+
return pathname.replace("//", "/");
|
|
576
|
+
}
|
|
577
|
+
return pathname;
|
|
578
|
+
}
|
|
579
|
+
function prependWellKnown(url, wellKnown, allowTerminatingSlash = false) {
|
|
580
|
+
if (url.pathname === "/") {
|
|
581
|
+
url.pathname = wellKnown;
|
|
582
|
+
} else {
|
|
583
|
+
url.pathname = replaceDoubleSlash(`${wellKnown}/${allowTerminatingSlash ? url.pathname : url.pathname.replace(/(\/)$/, "")}`);
|
|
584
|
+
}
|
|
585
|
+
return url;
|
|
586
|
+
}
|
|
587
|
+
function appendWellKnown(url, wellKnown) {
|
|
588
|
+
url.pathname = replaceDoubleSlash(`${url.pathname}/${wellKnown}`);
|
|
589
|
+
return url;
|
|
590
|
+
}
|
|
591
|
+
async function performDiscovery(input, urlName, transform, options) {
|
|
592
|
+
if (!(input instanceof URL)) {
|
|
593
|
+
throw CodedTypeError(`"${urlName}" must be an instance of URL`, ERR_INVALID_ARG_TYPE);
|
|
594
|
+
}
|
|
595
|
+
checkProtocol(input, options?.[allowInsecureRequests] !== true);
|
|
596
|
+
const url = transform(new URL(input.href));
|
|
597
|
+
const headers = prepareHeaders(options?.headers);
|
|
598
|
+
headers.set("accept", "application/json");
|
|
599
|
+
return (options?.[customFetch] || fetch)(url.href, {
|
|
600
|
+
body: void 0,
|
|
601
|
+
headers: Object.fromEntries(headers.entries()),
|
|
602
|
+
method: "GET",
|
|
603
|
+
redirect: "manual",
|
|
604
|
+
signal: signal(url, options?.signal)
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
async function discoveryRequest(issuerIdentifier, options) {
|
|
608
|
+
return performDiscovery(issuerIdentifier, "issuerIdentifier", (url) => {
|
|
609
|
+
switch (options?.algorithm) {
|
|
610
|
+
case void 0:
|
|
611
|
+
case "oidc":
|
|
612
|
+
appendWellKnown(url, ".well-known/openid-configuration");
|
|
613
|
+
break;
|
|
614
|
+
case "oauth2":
|
|
615
|
+
prependWellKnown(url, ".well-known/oauth-authorization-server");
|
|
616
|
+
break;
|
|
617
|
+
default:
|
|
618
|
+
throw CodedTypeError('"options.algorithm" must be "oidc" (default), or "oauth2"', ERR_INVALID_ARG_VALUE);
|
|
619
|
+
}
|
|
620
|
+
return url;
|
|
621
|
+
}, options);
|
|
622
|
+
}
|
|
623
|
+
function assertNumber(input, allow0, it, code, cause) {
|
|
624
|
+
try {
|
|
625
|
+
if (typeof input !== "number" || !Number.isFinite(input)) {
|
|
626
|
+
throw CodedTypeError(`${it} must be a number`, ERR_INVALID_ARG_TYPE, cause);
|
|
627
|
+
}
|
|
628
|
+
if (input > 0)
|
|
629
|
+
return;
|
|
630
|
+
if (allow0) {
|
|
631
|
+
if (input !== 0) {
|
|
632
|
+
throw CodedTypeError(`${it} must be a non-negative number`, ERR_INVALID_ARG_VALUE, cause);
|
|
633
|
+
}
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
throw CodedTypeError(`${it} must be a positive number`, ERR_INVALID_ARG_VALUE, cause);
|
|
637
|
+
} catch (err) {
|
|
638
|
+
if (code) {
|
|
639
|
+
throw OPE(err.message, code, cause);
|
|
640
|
+
}
|
|
641
|
+
throw err;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
function assertString(input, it, code, cause) {
|
|
645
|
+
try {
|
|
646
|
+
if (typeof input !== "string") {
|
|
647
|
+
throw CodedTypeError(`${it} must be a string`, ERR_INVALID_ARG_TYPE, cause);
|
|
648
|
+
}
|
|
649
|
+
if (input.length === 0) {
|
|
650
|
+
throw CodedTypeError(`${it} must not be empty`, ERR_INVALID_ARG_VALUE, cause);
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
if (code) {
|
|
654
|
+
throw OPE(err.message, code, cause);
|
|
655
|
+
}
|
|
656
|
+
throw err;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
async function processDiscoveryResponse(expectedIssuerIdentifier, response) {
|
|
660
|
+
const expected = expectedIssuerIdentifier;
|
|
661
|
+
if (!(expected instanceof URL) && expected !== _nodiscoverycheck) {
|
|
662
|
+
throw CodedTypeError('"expectedIssuerIdentifier" must be an instance of URL', ERR_INVALID_ARG_TYPE);
|
|
663
|
+
}
|
|
664
|
+
if (!looseInstanceOf(response, Response)) {
|
|
665
|
+
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
666
|
+
}
|
|
667
|
+
if (response.status !== 200) {
|
|
668
|
+
throw OPE('"response" is not a conform Authorization Server Metadata response (unexpected HTTP status code)', RESPONSE_IS_NOT_CONFORM, response);
|
|
669
|
+
}
|
|
670
|
+
assertReadableResponse(response);
|
|
671
|
+
const json = await getResponseJsonBody(response);
|
|
672
|
+
assertString(json.issuer, '"response" body "issuer" property', INVALID_RESPONSE, { body: json });
|
|
673
|
+
if (expected !== _nodiscoverycheck && new URL(json.issuer).href !== expected.href) {
|
|
674
|
+
throw OPE('"response" body "issuer" property does not match the expected value', JSON_ATTRIBUTE_COMPARISON, { expected: expected.href, body: json, attribute: "issuer" });
|
|
675
|
+
}
|
|
676
|
+
return json;
|
|
677
|
+
}
|
|
678
|
+
function assertApplicationJson(response) {
|
|
679
|
+
assertContentType(response, "application/json");
|
|
680
|
+
}
|
|
681
|
+
function notJson(response, ...types) {
|
|
682
|
+
let msg = '"response" content-type must be ';
|
|
683
|
+
if (types.length > 2) {
|
|
684
|
+
const last = types.pop();
|
|
685
|
+
msg += `${types.join(", ")}, or ${last}`;
|
|
686
|
+
} else if (types.length === 2) {
|
|
687
|
+
msg += `${types[0]} or ${types[1]}`;
|
|
688
|
+
} else {
|
|
689
|
+
msg += types[0];
|
|
690
|
+
}
|
|
691
|
+
return OPE(msg, RESPONSE_IS_NOT_JSON, response);
|
|
692
|
+
}
|
|
693
|
+
function assertContentType(response, contentType) {
|
|
694
|
+
if (getContentType(response) !== contentType) {
|
|
695
|
+
throw notJson(response, contentType);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
function randomBytes() {
|
|
699
|
+
return b64u(crypto.getRandomValues(new Uint8Array(32)));
|
|
700
|
+
}
|
|
701
|
+
function generateRandomCodeVerifier() {
|
|
702
|
+
return randomBytes();
|
|
703
|
+
}
|
|
704
|
+
async function calculatePKCECodeChallenge(codeVerifier) {
|
|
705
|
+
assertString(codeVerifier, "codeVerifier");
|
|
706
|
+
return b64u(await crypto.subtle.digest("SHA-256", buf(codeVerifier)));
|
|
707
|
+
}
|
|
708
|
+
function getClockSkew(client) {
|
|
709
|
+
const skew = client?.[clockSkew];
|
|
710
|
+
return typeof skew === "number" && Number.isFinite(skew) ? skew : 0;
|
|
711
|
+
}
|
|
712
|
+
function getClockTolerance(client) {
|
|
713
|
+
const tolerance = client?.[clockTolerance];
|
|
714
|
+
return typeof tolerance === "number" && Number.isFinite(tolerance) && Math.sign(tolerance) !== -1 ? tolerance : 30;
|
|
715
|
+
}
|
|
716
|
+
function epochTime() {
|
|
717
|
+
return Math.floor(Date.now() / 1e3);
|
|
718
|
+
}
|
|
719
|
+
function assertAs(as) {
|
|
720
|
+
if (typeof as !== "object" || as === null) {
|
|
721
|
+
throw CodedTypeError('"as" must be an object', ERR_INVALID_ARG_TYPE);
|
|
722
|
+
}
|
|
723
|
+
assertString(as.issuer, '"as.issuer"');
|
|
724
|
+
}
|
|
725
|
+
function assertClient(client) {
|
|
726
|
+
if (typeof client !== "object" || client === null) {
|
|
727
|
+
throw CodedTypeError('"client" must be an object', ERR_INVALID_ARG_TYPE);
|
|
728
|
+
}
|
|
729
|
+
assertString(client.client_id, '"client.client_id"');
|
|
730
|
+
}
|
|
731
|
+
function None() {
|
|
732
|
+
return (_as, client, body, _headers) => {
|
|
733
|
+
body.set("client_id", client.client_id);
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
var URLParse = URL.parse ? (url, base) => URL.parse(url, base) : (url, base) => {
|
|
737
|
+
try {
|
|
738
|
+
return new URL(url, base);
|
|
739
|
+
} catch {
|
|
740
|
+
return null;
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
function checkProtocol(url, enforceHttps) {
|
|
744
|
+
if (enforceHttps && url.protocol !== "https:") {
|
|
745
|
+
throw OPE("only requests to HTTPS are allowed", HTTP_REQUEST_FORBIDDEN, url);
|
|
746
|
+
}
|
|
747
|
+
if (url.protocol !== "https:" && url.protocol !== "http:") {
|
|
748
|
+
throw OPE("only HTTP and HTTPS requests are allowed", REQUEST_PROTOCOL_FORBIDDEN, url);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function validateEndpoint(value, endpoint, useMtlsAlias, enforceHttps) {
|
|
752
|
+
let url;
|
|
753
|
+
if (typeof value !== "string" || !(url = URLParse(value))) {
|
|
754
|
+
throw OPE(`authorization server metadata does not contain a valid ${useMtlsAlias ? `"as.mtls_endpoint_aliases.${endpoint}"` : `"as.${endpoint}"`}`, value === void 0 ? MISSING_SERVER_METADATA : INVALID_SERVER_METADATA, { attribute: useMtlsAlias ? `mtls_endpoint_aliases.${endpoint}` : endpoint });
|
|
755
|
+
}
|
|
756
|
+
checkProtocol(url, enforceHttps);
|
|
757
|
+
return url;
|
|
758
|
+
}
|
|
759
|
+
function resolveEndpoint(as, endpoint, useMtlsAlias, enforceHttps) {
|
|
760
|
+
if (useMtlsAlias && as.mtls_endpoint_aliases && endpoint in as.mtls_endpoint_aliases) {
|
|
761
|
+
return validateEndpoint(as.mtls_endpoint_aliases[endpoint], endpoint, useMtlsAlias, enforceHttps);
|
|
762
|
+
}
|
|
763
|
+
return validateEndpoint(as[endpoint], endpoint, useMtlsAlias, enforceHttps);
|
|
764
|
+
}
|
|
765
|
+
var ResponseBodyError = class extends Error {
|
|
766
|
+
cause;
|
|
767
|
+
code;
|
|
768
|
+
error;
|
|
769
|
+
status;
|
|
770
|
+
error_description;
|
|
771
|
+
response;
|
|
772
|
+
constructor(message, options) {
|
|
773
|
+
super(message, options);
|
|
774
|
+
this.name = this.constructor.name;
|
|
775
|
+
this.code = RESPONSE_BODY_ERROR;
|
|
776
|
+
this.cause = options.cause;
|
|
777
|
+
this.error = options.cause.error;
|
|
778
|
+
this.status = options.response.status;
|
|
779
|
+
this.error_description = options.cause.error_description;
|
|
780
|
+
Object.defineProperty(this, "response", { enumerable: false, value: options.response });
|
|
781
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
var AuthorizationResponseError = class extends Error {
|
|
785
|
+
cause;
|
|
786
|
+
code;
|
|
787
|
+
error;
|
|
788
|
+
error_description;
|
|
789
|
+
constructor(message, options) {
|
|
790
|
+
super(message, options);
|
|
791
|
+
this.name = this.constructor.name;
|
|
792
|
+
this.code = AUTHORIZATION_RESPONSE_ERROR;
|
|
793
|
+
this.cause = options.cause;
|
|
794
|
+
this.error = options.cause.get("error");
|
|
795
|
+
this.error_description = options.cause.get("error_description") ?? void 0;
|
|
796
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
797
|
+
}
|
|
798
|
+
};
|
|
799
|
+
var WWWAuthenticateChallengeError = class extends Error {
|
|
800
|
+
cause;
|
|
801
|
+
code;
|
|
802
|
+
response;
|
|
803
|
+
status;
|
|
804
|
+
constructor(message, options) {
|
|
805
|
+
super(message, options);
|
|
806
|
+
this.name = this.constructor.name;
|
|
807
|
+
this.code = WWW_AUTHENTICATE_CHALLENGE;
|
|
808
|
+
this.cause = options.cause;
|
|
809
|
+
this.status = options.response.status;
|
|
810
|
+
this.response = options.response;
|
|
811
|
+
Object.defineProperty(this, "response", { enumerable: false });
|
|
812
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
813
|
+
}
|
|
814
|
+
};
|
|
815
|
+
var tokenMatch = "[a-zA-Z0-9!#$%&\\'\\*\\+\\-\\.\\^_`\\|~]+";
|
|
816
|
+
var token68Match = "[a-zA-Z0-9\\-\\._\\~\\+\\/]+={0,2}";
|
|
817
|
+
var quotedMatch = '"((?:[^"\\\\]|\\\\[\\s\\S])*)"';
|
|
818
|
+
var quotedParamMatcher = "(" + tokenMatch + ")\\s*=\\s*" + quotedMatch;
|
|
819
|
+
var paramMatcher = "(" + tokenMatch + ")\\s*=\\s*(" + tokenMatch + ")";
|
|
820
|
+
var schemeRE = new RegExp("^[,\\s]*(" + tokenMatch + ")");
|
|
821
|
+
var quotedParamRE = new RegExp("^[,\\s]*" + quotedParamMatcher + "[,\\s]*(.*)");
|
|
822
|
+
var unquotedParamRE = new RegExp("^[,\\s]*" + paramMatcher + "[,\\s]*(.*)");
|
|
823
|
+
var token68ParamRE = new RegExp("^(" + token68Match + ")(?:$|[,\\s])(.*)");
|
|
824
|
+
function parseWwwAuthenticateChallenges(response) {
|
|
825
|
+
if (!looseInstanceOf(response, Response)) {
|
|
826
|
+
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
827
|
+
}
|
|
828
|
+
const header = response.headers.get("www-authenticate");
|
|
829
|
+
if (header === null) {
|
|
830
|
+
return void 0;
|
|
831
|
+
}
|
|
832
|
+
const challenges = [];
|
|
833
|
+
let rest = header;
|
|
834
|
+
while (rest) {
|
|
835
|
+
let match = rest.match(schemeRE);
|
|
836
|
+
const scheme = match?.["1"].toLowerCase();
|
|
837
|
+
if (!scheme) {
|
|
838
|
+
return void 0;
|
|
839
|
+
}
|
|
840
|
+
const afterScheme = rest.substring(match[0].length);
|
|
841
|
+
if (afterScheme && !afterScheme.match(/^[\s,]/)) {
|
|
842
|
+
return void 0;
|
|
843
|
+
}
|
|
844
|
+
const spaceMatch = afterScheme.match(/^\s+(.*)$/);
|
|
845
|
+
const hasParameters = !!spaceMatch;
|
|
846
|
+
rest = spaceMatch ? spaceMatch[1] : void 0;
|
|
847
|
+
const parameters = {};
|
|
848
|
+
let token68;
|
|
849
|
+
if (hasParameters) {
|
|
850
|
+
while (rest) {
|
|
851
|
+
let key;
|
|
852
|
+
let value;
|
|
853
|
+
if (match = rest.match(quotedParamRE)) {
|
|
854
|
+
;
|
|
855
|
+
[, key, value, rest] = match;
|
|
856
|
+
if (value.includes("\\")) {
|
|
857
|
+
value = value.replace(/\\([\s\S])/g, "$1");
|
|
858
|
+
}
|
|
859
|
+
parameters[key.toLowerCase()] = value;
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
if (match = rest.match(unquotedParamRE)) {
|
|
863
|
+
;
|
|
864
|
+
[, key, value, rest] = match;
|
|
865
|
+
parameters[key.toLowerCase()] = value;
|
|
866
|
+
continue;
|
|
867
|
+
}
|
|
868
|
+
if (match = rest.match(token68ParamRE)) {
|
|
869
|
+
if (Object.keys(parameters).length) {
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
;
|
|
873
|
+
[, token68, rest] = match;
|
|
874
|
+
break;
|
|
875
|
+
}
|
|
876
|
+
return void 0;
|
|
877
|
+
}
|
|
878
|
+
} else {
|
|
879
|
+
rest = afterScheme || void 0;
|
|
880
|
+
}
|
|
881
|
+
const challenge = { scheme, parameters };
|
|
882
|
+
if (token68) {
|
|
883
|
+
challenge.token68 = token68;
|
|
884
|
+
}
|
|
885
|
+
challenges.push(challenge);
|
|
886
|
+
}
|
|
887
|
+
if (!challenges.length) {
|
|
888
|
+
return void 0;
|
|
889
|
+
}
|
|
890
|
+
return challenges;
|
|
891
|
+
}
|
|
892
|
+
async function parseOAuthResponseErrorBody(response) {
|
|
893
|
+
if (response.status > 399 && response.status < 500) {
|
|
894
|
+
assertReadableResponse(response);
|
|
895
|
+
assertApplicationJson(response);
|
|
896
|
+
try {
|
|
897
|
+
const json = await response.clone().json();
|
|
898
|
+
if (isJsonObject(json) && typeof json.error === "string" && json.error.length) {
|
|
899
|
+
return json;
|
|
900
|
+
}
|
|
901
|
+
} catch {
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
async function checkOAuthBodyError(response, expected, label) {
|
|
907
|
+
if (response.status !== expected) {
|
|
908
|
+
checkAuthenticationChallenges(response);
|
|
909
|
+
let err;
|
|
910
|
+
if (err = await parseOAuthResponseErrorBody(response)) {
|
|
911
|
+
await response.body?.cancel();
|
|
912
|
+
throw new ResponseBodyError("server responded with an error in the response body", {
|
|
913
|
+
cause: err,
|
|
914
|
+
response
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
throw OPE(`"response" is not a conform ${label} response (unexpected HTTP status code)`, RESPONSE_IS_NOT_CONFORM, response);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
function assertDPoP(option) {
|
|
921
|
+
if (!branded.has(option)) {
|
|
922
|
+
throw CodedTypeError('"options.DPoP" is not a valid DPoPHandle', ERR_INVALID_ARG_VALUE);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function getContentType(input) {
|
|
926
|
+
return input.headers.get("content-type")?.split(";")[0];
|
|
927
|
+
}
|
|
928
|
+
async function authenticatedRequest(as, client, clientAuthentication, url, body, headers, options) {
|
|
929
|
+
await clientAuthentication(as, client, body, headers);
|
|
930
|
+
headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
931
|
+
return (options?.[customFetch] || fetch)(url.href, {
|
|
932
|
+
body,
|
|
933
|
+
headers: Object.fromEntries(headers.entries()),
|
|
934
|
+
method: "POST",
|
|
935
|
+
redirect: "manual",
|
|
936
|
+
signal: signal(url, options?.signal)
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
async function tokenEndpointRequest(as, client, clientAuthentication, grantType, parameters, options) {
|
|
940
|
+
const url = resolveEndpoint(as, "token_endpoint", client.use_mtls_endpoint_aliases, options?.[allowInsecureRequests] !== true);
|
|
941
|
+
parameters.set("grant_type", grantType);
|
|
942
|
+
const headers = prepareHeaders(options?.headers);
|
|
943
|
+
headers.set("accept", "application/json");
|
|
944
|
+
if (options?.DPoP !== void 0) {
|
|
945
|
+
assertDPoP(options.DPoP);
|
|
946
|
+
await options.DPoP.addProof(url, headers, "POST");
|
|
947
|
+
}
|
|
948
|
+
const response = await authenticatedRequest(as, client, clientAuthentication, url, parameters, headers, options);
|
|
949
|
+
options?.DPoP?.cacheNonce(response, url);
|
|
950
|
+
return response;
|
|
951
|
+
}
|
|
952
|
+
async function refreshTokenGrantRequest(as, client, clientAuthentication, refreshToken, options) {
|
|
953
|
+
assertAs(as);
|
|
954
|
+
assertClient(client);
|
|
955
|
+
assertString(refreshToken, '"refreshToken"');
|
|
956
|
+
const parameters = new URLSearchParams(options?.additionalParameters);
|
|
957
|
+
parameters.set("refresh_token", refreshToken);
|
|
958
|
+
return tokenEndpointRequest(as, client, clientAuthentication, "refresh_token", parameters, options);
|
|
959
|
+
}
|
|
960
|
+
var idTokenClaims = /* @__PURE__ */ new WeakMap();
|
|
961
|
+
var jwtRefs = /* @__PURE__ */ new WeakMap();
|
|
962
|
+
function getValidatedIdTokenClaims(ref) {
|
|
963
|
+
if (!ref.id_token) {
|
|
964
|
+
return void 0;
|
|
965
|
+
}
|
|
966
|
+
const claims = idTokenClaims.get(ref);
|
|
967
|
+
if (!claims) {
|
|
968
|
+
throw CodedTypeError('"ref" was already garbage collected or did not resolve from the proper sources', ERR_INVALID_ARG_VALUE);
|
|
969
|
+
}
|
|
970
|
+
return claims;
|
|
971
|
+
}
|
|
972
|
+
async function processGenericAccessTokenResponse(as, client, response, additionalRequiredIdTokenClaims, decryptFn, recognizedTokenTypes) {
|
|
973
|
+
assertAs(as);
|
|
974
|
+
assertClient(client);
|
|
975
|
+
if (!looseInstanceOf(response, Response)) {
|
|
976
|
+
throw CodedTypeError('"response" must be an instance of Response', ERR_INVALID_ARG_TYPE);
|
|
977
|
+
}
|
|
978
|
+
await checkOAuthBodyError(response, 200, "Token Endpoint");
|
|
979
|
+
assertReadableResponse(response);
|
|
980
|
+
const json = await getResponseJsonBody(response);
|
|
981
|
+
assertString(json.access_token, '"response" body "access_token" property', INVALID_RESPONSE, {
|
|
982
|
+
body: json
|
|
983
|
+
});
|
|
984
|
+
assertString(json.token_type, '"response" body "token_type" property', INVALID_RESPONSE, {
|
|
985
|
+
body: json
|
|
986
|
+
});
|
|
987
|
+
json.token_type = json.token_type.toLowerCase();
|
|
988
|
+
if (json.expires_in !== void 0) {
|
|
989
|
+
let expiresIn = typeof json.expires_in !== "number" ? parseFloat(json.expires_in) : json.expires_in;
|
|
990
|
+
assertNumber(expiresIn, true, '"response" body "expires_in" property', INVALID_RESPONSE, {
|
|
991
|
+
body: json
|
|
992
|
+
});
|
|
993
|
+
json.expires_in = expiresIn;
|
|
994
|
+
}
|
|
995
|
+
if (json.refresh_token !== void 0) {
|
|
996
|
+
assertString(json.refresh_token, '"response" body "refresh_token" property', INVALID_RESPONSE, {
|
|
997
|
+
body: json
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
if (json.scope !== void 0 && typeof json.scope !== "string") {
|
|
1001
|
+
throw OPE('"response" body "scope" property must be a string', INVALID_RESPONSE, { body: json });
|
|
1002
|
+
}
|
|
1003
|
+
if (json.id_token !== void 0) {
|
|
1004
|
+
assertString(json.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
|
|
1005
|
+
body: json
|
|
1006
|
+
});
|
|
1007
|
+
const requiredClaims = [];
|
|
1008
|
+
if (client.require_auth_time === true) {
|
|
1009
|
+
requiredClaims.push("auth_time");
|
|
1010
|
+
}
|
|
1011
|
+
if (client.default_max_age !== void 0) {
|
|
1012
|
+
assertNumber(client.default_max_age, true, '"client.default_max_age"');
|
|
1013
|
+
requiredClaims.push("auth_time");
|
|
1014
|
+
}
|
|
1015
|
+
if (additionalRequiredIdTokenClaims?.length) {
|
|
1016
|
+
requiredClaims.push(...additionalRequiredIdTokenClaims);
|
|
1017
|
+
}
|
|
1018
|
+
const { claims, jwt } = await validateIdTokenClaims(as, client, json.id_token, requiredClaims, decryptFn);
|
|
1019
|
+
validateIdTokenAuthorizedParty(client, claims);
|
|
1020
|
+
validateIdTokenAuthTimeClaim(claims);
|
|
1021
|
+
jwtRefs.set(response, jwt);
|
|
1022
|
+
idTokenClaims.set(json, claims);
|
|
1023
|
+
}
|
|
1024
|
+
if (recognizedTokenTypes?.[json.token_type] !== void 0) {
|
|
1025
|
+
recognizedTokenTypes[json.token_type](response, json);
|
|
1026
|
+
} else if (json.token_type !== "dpop" && json.token_type !== "bearer") {
|
|
1027
|
+
throw new UnsupportedOperationError("unsupported `token_type` value", { cause: { body: json } });
|
|
1028
|
+
}
|
|
1029
|
+
return json;
|
|
1030
|
+
}
|
|
1031
|
+
function checkAuthenticationChallenges(response) {
|
|
1032
|
+
let challenges;
|
|
1033
|
+
if (challenges = parseWwwAuthenticateChallenges(response)) {
|
|
1034
|
+
throw new WWWAuthenticateChallengeError("server responded with a challenge in the WWW-Authenticate HTTP Header", { cause: challenges, response });
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
async function processRefreshTokenResponse(as, client, response, options) {
|
|
1038
|
+
return processGenericAccessTokenResponse(as, client, response, void 0, options?.[jweDecrypt], options?.recognizedTokenTypes);
|
|
1039
|
+
}
|
|
1040
|
+
function validateAudience(expected, result) {
|
|
1041
|
+
if (Array.isArray(result.claims.aud)) {
|
|
1042
|
+
if (!result.claims.aud.includes(expected)) {
|
|
1043
|
+
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
1044
|
+
expected,
|
|
1045
|
+
claims: result.claims,
|
|
1046
|
+
claim: "aud"
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
} else if (result.claims.aud !== expected) {
|
|
1050
|
+
throw OPE('unexpected JWT "aud" (audience) claim value', JWT_CLAIM_COMPARISON, {
|
|
1051
|
+
expected,
|
|
1052
|
+
claims: result.claims,
|
|
1053
|
+
claim: "aud"
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
return result;
|
|
1057
|
+
}
|
|
1058
|
+
function validateIssuer(as, result) {
|
|
1059
|
+
const expected = as[_expectedIssuer]?.(result) ?? as.issuer;
|
|
1060
|
+
if (result.claims.iss !== expected) {
|
|
1061
|
+
throw OPE('unexpected JWT "iss" (issuer) claim value', JWT_CLAIM_COMPARISON, {
|
|
1062
|
+
expected,
|
|
1063
|
+
claims: result.claims,
|
|
1064
|
+
claim: "iss"
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
return result;
|
|
1068
|
+
}
|
|
1069
|
+
var branded = /* @__PURE__ */ new WeakSet();
|
|
1070
|
+
function brand(searchParams) {
|
|
1071
|
+
branded.add(searchParams);
|
|
1072
|
+
return searchParams;
|
|
1073
|
+
}
|
|
1074
|
+
var nopkce = /* @__PURE__ */ Symbol();
|
|
1075
|
+
async function authorizationCodeGrantRequest(as, client, clientAuthentication, callbackParameters, redirectUri, codeVerifier, options) {
|
|
1076
|
+
assertAs(as);
|
|
1077
|
+
assertClient(client);
|
|
1078
|
+
if (!branded.has(callbackParameters)) {
|
|
1079
|
+
throw CodedTypeError('"callbackParameters" must be an instance of URLSearchParams obtained from "validateAuthResponse()", or "validateJwtAuthResponse()', ERR_INVALID_ARG_VALUE);
|
|
1080
|
+
}
|
|
1081
|
+
assertString(redirectUri, '"redirectUri"');
|
|
1082
|
+
const code = getURLSearchParameter(callbackParameters, "code");
|
|
1083
|
+
if (!code) {
|
|
1084
|
+
throw OPE('no authorization code in "callbackParameters"', INVALID_RESPONSE);
|
|
1085
|
+
}
|
|
1086
|
+
const parameters = new URLSearchParams(options?.additionalParameters);
|
|
1087
|
+
parameters.set("redirect_uri", redirectUri);
|
|
1088
|
+
parameters.set("code", code);
|
|
1089
|
+
if (codeVerifier !== nopkce) {
|
|
1090
|
+
assertString(codeVerifier, '"codeVerifier"');
|
|
1091
|
+
parameters.set("code_verifier", codeVerifier);
|
|
1092
|
+
}
|
|
1093
|
+
return tokenEndpointRequest(as, client, clientAuthentication, "authorization_code", parameters, options);
|
|
1094
|
+
}
|
|
1095
|
+
var jwtClaimNames = {
|
|
1096
|
+
aud: "audience",
|
|
1097
|
+
c_hash: "code hash",
|
|
1098
|
+
client_id: "client id",
|
|
1099
|
+
exp: "expiration time",
|
|
1100
|
+
iat: "issued at",
|
|
1101
|
+
iss: "issuer",
|
|
1102
|
+
jti: "jwt id",
|
|
1103
|
+
nonce: "nonce",
|
|
1104
|
+
s_hash: "state hash",
|
|
1105
|
+
sub: "subject",
|
|
1106
|
+
ath: "access token hash",
|
|
1107
|
+
htm: "http method",
|
|
1108
|
+
htu: "http uri",
|
|
1109
|
+
cnf: "confirmation",
|
|
1110
|
+
auth_time: "authentication time"
|
|
1111
|
+
};
|
|
1112
|
+
function validatePresence(required, result) {
|
|
1113
|
+
for (const claim of required) {
|
|
1114
|
+
if (result.claims[claim] === void 0) {
|
|
1115
|
+
throw OPE(`JWT "${claim}" (${jwtClaimNames[claim]}) claim missing`, INVALID_RESPONSE, {
|
|
1116
|
+
claims: result.claims
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return result;
|
|
1121
|
+
}
|
|
1122
|
+
function validateStringClaim(claim, result) {
|
|
1123
|
+
if (typeof result.claims[claim] !== "string") {
|
|
1124
|
+
throw OPE(`unexpected JWT "${claim}" (${jwtClaimNames[claim]}) claim type`, INVALID_RESPONSE, {
|
|
1125
|
+
claims: result.claims
|
|
1126
|
+
});
|
|
1127
|
+
}
|
|
1128
|
+
return result;
|
|
1129
|
+
}
|
|
1130
|
+
function validateIdTokenClaims(as, client, idToken, requiredClaims, decryptFn) {
|
|
1131
|
+
return validateJwt(idToken, checkSigningAlgorithm.bind(void 0, client.id_token_signed_response_alg, as.id_token_signing_alg_values_supported, "RS256"), getClockSkew(client), getClockTolerance(client), decryptFn).then(validatePresence.bind(void 0, ["aud", "exp", "iat", "iss", "sub", ...requiredClaims])).then(validateIssuer.bind(void 0, as)).then(validateAudience.bind(void 0, client.client_id)).then(validateStringClaim.bind(void 0, "sub"));
|
|
1132
|
+
}
|
|
1133
|
+
function resolveIdTokenMaxAge(client, maxAge) {
|
|
1134
|
+
if (maxAge === skipAuthTimeCheck) {
|
|
1135
|
+
return maxAge;
|
|
1136
|
+
}
|
|
1137
|
+
const fromClient = maxAge === void 0;
|
|
1138
|
+
if (fromClient) {
|
|
1139
|
+
maxAge = client.default_max_age;
|
|
1140
|
+
}
|
|
1141
|
+
if (maxAge === void 0) {
|
|
1142
|
+
return skipAuthTimeCheck;
|
|
1143
|
+
}
|
|
1144
|
+
assertNumber(maxAge, true, fromClient ? '"client.default_max_age"' : '"maxAge" argument');
|
|
1145
|
+
return maxAge;
|
|
1146
|
+
}
|
|
1147
|
+
function validateIdTokenAuthTimeClaim(claims) {
|
|
1148
|
+
if (claims.auth_time !== void 0) {
|
|
1149
|
+
assertNumber(claims.auth_time, true, 'ID Token "auth_time" (authentication time)', INVALID_RESPONSE, { claims });
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
function validateIdTokenAuthTime(client, claims, maxAge) {
|
|
1153
|
+
if (maxAge === skipAuthTimeCheck) {
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
const now = epochTime() + getClockSkew(client);
|
|
1157
|
+
const tolerance = getClockTolerance(client);
|
|
1158
|
+
if (claims.auth_time + maxAge < now - tolerance) {
|
|
1159
|
+
throw OPE("too much time has elapsed since the last End-User authentication", JWT_TIMESTAMP_CHECK, { claims, now, tolerance, claim: "auth_time" });
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
function validateIdTokenNonce(claims, expectedNonce) {
|
|
1163
|
+
const expected = expectedNonce === expectNoNonce ? void 0 : expectedNonce;
|
|
1164
|
+
if (claims.nonce !== expected) {
|
|
1165
|
+
throw OPE('unexpected ID Token "nonce" claim value', JWT_CLAIM_COMPARISON, {
|
|
1166
|
+
expected,
|
|
1167
|
+
claims,
|
|
1168
|
+
claim: "nonce"
|
|
1169
|
+
});
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
function validateIdTokenAuthorizedParty(client, claims) {
|
|
1173
|
+
if (Array.isArray(claims.aud) && claims.aud.length !== 1) {
|
|
1174
|
+
if (claims.azp === void 0) {
|
|
1175
|
+
throw OPE('ID Token "aud" (audience) claim includes additional untrusted audiences', JWT_CLAIM_COMPARISON, { claims, claim: "aud" });
|
|
1176
|
+
}
|
|
1177
|
+
if (claims.azp !== client.client_id) {
|
|
1178
|
+
throw OPE('unexpected ID Token "azp" (authorized party) claim value', JWT_CLAIM_COMPARISON, {
|
|
1179
|
+
expected: client.client_id,
|
|
1180
|
+
claims,
|
|
1181
|
+
claim: "azp"
|
|
1182
|
+
});
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
var expectNoNonce = /* @__PURE__ */ Symbol();
|
|
1187
|
+
var skipAuthTimeCheck = /* @__PURE__ */ Symbol();
|
|
1188
|
+
async function processAuthorizationCodeResponse(as, client, response, options) {
|
|
1189
|
+
if (typeof options?.expectedNonce === "string" || typeof options?.maxAge === "number" || options?.requireIdToken) {
|
|
1190
|
+
return processAuthorizationCodeOpenIDResponse(as, client, response, options.expectedNonce, options.maxAge, options[jweDecrypt], options.recognizedTokenTypes);
|
|
1191
|
+
}
|
|
1192
|
+
return processAuthorizationCodeOAuth2Response(as, client, response, options?.maxAge, options?.[jweDecrypt], options?.recognizedTokenTypes);
|
|
1193
|
+
}
|
|
1194
|
+
async function processAuthorizationCodeOpenIDResponse(as, client, response, expectedNonce, maxAge, decryptFn, recognizedTokenTypes) {
|
|
1195
|
+
const additionalRequiredClaims = [];
|
|
1196
|
+
switch (expectedNonce) {
|
|
1197
|
+
case void 0:
|
|
1198
|
+
expectedNonce = expectNoNonce;
|
|
1199
|
+
break;
|
|
1200
|
+
case expectNoNonce:
|
|
1201
|
+
break;
|
|
1202
|
+
default:
|
|
1203
|
+
assertString(expectedNonce, '"expectedNonce" argument');
|
|
1204
|
+
additionalRequiredClaims.push("nonce");
|
|
1205
|
+
}
|
|
1206
|
+
maxAge = resolveIdTokenMaxAge(client, maxAge);
|
|
1207
|
+
if (maxAge !== skipAuthTimeCheck) {
|
|
1208
|
+
additionalRequiredClaims.push("auth_time");
|
|
1209
|
+
}
|
|
1210
|
+
const result = await processGenericAccessTokenResponse(as, client, response, additionalRequiredClaims, decryptFn, recognizedTokenTypes);
|
|
1211
|
+
assertString(result.id_token, '"response" body "id_token" property', INVALID_RESPONSE, {
|
|
1212
|
+
body: result
|
|
1213
|
+
});
|
|
1214
|
+
const claims = getValidatedIdTokenClaims(result);
|
|
1215
|
+
validateIdTokenAuthTime(client, claims, maxAge);
|
|
1216
|
+
validateIdTokenNonce(claims, expectedNonce);
|
|
1217
|
+
return result;
|
|
1218
|
+
}
|
|
1219
|
+
async function processAuthorizationCodeOAuth2Response(as, client, response, maxAge, decryptFn, recognizedTokenTypes) {
|
|
1220
|
+
const result = await processGenericAccessTokenResponse(as, client, response, void 0, decryptFn, recognizedTokenTypes);
|
|
1221
|
+
const claims = getValidatedIdTokenClaims(result);
|
|
1222
|
+
if (claims) {
|
|
1223
|
+
validateIdTokenAuthTime(client, claims, resolveIdTokenMaxAge(client, maxAge));
|
|
1224
|
+
validateIdTokenNonce(claims, expectNoNonce);
|
|
1225
|
+
}
|
|
1226
|
+
return result;
|
|
1227
|
+
}
|
|
1228
|
+
var WWW_AUTHENTICATE_CHALLENGE = "OAUTH_WWW_AUTHENTICATE_CHALLENGE";
|
|
1229
|
+
var RESPONSE_BODY_ERROR = "OAUTH_RESPONSE_BODY_ERROR";
|
|
1230
|
+
var UNSUPPORTED_OPERATION = "OAUTH_UNSUPPORTED_OPERATION";
|
|
1231
|
+
var AUTHORIZATION_RESPONSE_ERROR = "OAUTH_AUTHORIZATION_RESPONSE_ERROR";
|
|
1232
|
+
var PARSE_ERROR = "OAUTH_PARSE_ERROR";
|
|
1233
|
+
var INVALID_RESPONSE = "OAUTH_INVALID_RESPONSE";
|
|
1234
|
+
var RESPONSE_IS_NOT_JSON = "OAUTH_RESPONSE_IS_NOT_JSON";
|
|
1235
|
+
var RESPONSE_IS_NOT_CONFORM = "OAUTH_RESPONSE_IS_NOT_CONFORM";
|
|
1236
|
+
var HTTP_REQUEST_FORBIDDEN = "OAUTH_HTTP_REQUEST_FORBIDDEN";
|
|
1237
|
+
var REQUEST_PROTOCOL_FORBIDDEN = "OAUTH_REQUEST_PROTOCOL_FORBIDDEN";
|
|
1238
|
+
var JWT_TIMESTAMP_CHECK = "OAUTH_JWT_TIMESTAMP_CHECK_FAILED";
|
|
1239
|
+
var JWT_CLAIM_COMPARISON = "OAUTH_JWT_CLAIM_COMPARISON_FAILED";
|
|
1240
|
+
var JSON_ATTRIBUTE_COMPARISON = "OAUTH_JSON_ATTRIBUTE_COMPARISON_FAILED";
|
|
1241
|
+
var MISSING_SERVER_METADATA = "OAUTH_MISSING_SERVER_METADATA";
|
|
1242
|
+
var INVALID_SERVER_METADATA = "OAUTH_INVALID_SERVER_METADATA";
|
|
1243
|
+
function assertReadableResponse(response) {
|
|
1244
|
+
if (response.bodyUsed) {
|
|
1245
|
+
throw CodedTypeError('"response" body has been used already', ERR_INVALID_ARG_VALUE);
|
|
1246
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
async function validateJwt(jws, checkAlg, clockSkew2, clockTolerance2, decryptJwt) {
|
|
1249
|
+
let { 0: protectedHeader, 1: payload, length } = jws.split(".");
|
|
1250
|
+
if (length === 5) {
|
|
1251
|
+
if (decryptJwt !== void 0) {
|
|
1252
|
+
jws = await decryptJwt(jws);
|
|
1253
|
+
({ 0: protectedHeader, 1: payload, length } = jws.split("."));
|
|
1254
|
+
} else {
|
|
1255
|
+
throw new UnsupportedOperationError("JWE decryption is not configured", { cause: jws });
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (length !== 3) {
|
|
1259
|
+
throw OPE("Invalid JWT", INVALID_RESPONSE, jws);
|
|
1260
|
+
}
|
|
1261
|
+
let header;
|
|
1262
|
+
try {
|
|
1263
|
+
header = JSON.parse(buf(b64u(protectedHeader)));
|
|
1264
|
+
} catch (cause) {
|
|
1265
|
+
throw OPE("failed to parse JWT Header body as base64url encoded JSON", PARSE_ERROR, cause);
|
|
1266
|
+
}
|
|
1267
|
+
if (!isJsonObject(header)) {
|
|
1268
|
+
throw OPE("JWT Header must be a top level object", INVALID_RESPONSE, jws);
|
|
1269
|
+
}
|
|
1270
|
+
checkAlg(header);
|
|
1271
|
+
if (header.crit !== void 0) {
|
|
1272
|
+
throw new UnsupportedOperationError('no JWT "crit" header parameter extensions are supported', {
|
|
1273
|
+
cause: { header }
|
|
1274
|
+
});
|
|
1275
|
+
}
|
|
1276
|
+
let claims;
|
|
1277
|
+
try {
|
|
1278
|
+
claims = JSON.parse(buf(b64u(payload)));
|
|
1279
|
+
} catch (cause) {
|
|
1280
|
+
throw OPE("failed to parse JWT Payload body as base64url encoded JSON", PARSE_ERROR, cause);
|
|
1281
|
+
}
|
|
1282
|
+
if (!isJsonObject(claims)) {
|
|
1283
|
+
throw OPE("JWT Payload must be a top level object", INVALID_RESPONSE, jws);
|
|
1284
|
+
}
|
|
1285
|
+
const now = epochTime() + clockSkew2;
|
|
1286
|
+
if (claims.exp !== void 0) {
|
|
1287
|
+
if (typeof claims.exp !== "number") {
|
|
1288
|
+
throw OPE('unexpected JWT "exp" (expiration time) claim type', INVALID_RESPONSE, { claims });
|
|
1289
|
+
}
|
|
1290
|
+
if (claims.exp <= now - clockTolerance2) {
|
|
1291
|
+
throw OPE('unexpected JWT "exp" (expiration time) claim value, expiration is past current timestamp', JWT_TIMESTAMP_CHECK, { claims, now, tolerance: clockTolerance2, claim: "exp" });
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
if (claims.iat !== void 0) {
|
|
1295
|
+
if (typeof claims.iat !== "number") {
|
|
1296
|
+
throw OPE('unexpected JWT "iat" (issued at) claim type', INVALID_RESPONSE, { claims });
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
if (claims.iss !== void 0) {
|
|
1300
|
+
if (typeof claims.iss !== "string") {
|
|
1301
|
+
throw OPE('unexpected JWT "iss" (issuer) claim type', INVALID_RESPONSE, { claims });
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
if (claims.nbf !== void 0) {
|
|
1305
|
+
if (typeof claims.nbf !== "number") {
|
|
1306
|
+
throw OPE('unexpected JWT "nbf" (not before) claim type', INVALID_RESPONSE, { claims });
|
|
1307
|
+
}
|
|
1308
|
+
if (claims.nbf > now + clockTolerance2) {
|
|
1309
|
+
throw OPE('unexpected JWT "nbf" (not before) claim value', JWT_TIMESTAMP_CHECK, {
|
|
1310
|
+
claims,
|
|
1311
|
+
now,
|
|
1312
|
+
tolerance: clockTolerance2,
|
|
1313
|
+
claim: "nbf"
|
|
1314
|
+
});
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
if (claims.aud !== void 0) {
|
|
1318
|
+
if (typeof claims.aud !== "string" && !Array.isArray(claims.aud)) {
|
|
1319
|
+
throw OPE('unexpected JWT "aud" (audience) claim type', INVALID_RESPONSE, { claims });
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return { header, claims, jwt: jws };
|
|
1323
|
+
}
|
|
1324
|
+
function checkSigningAlgorithm(client, issuer, fallback, header) {
|
|
1325
|
+
if (client !== void 0) {
|
|
1326
|
+
if (typeof client === "string" ? header.alg !== client : !client.includes(header.alg)) {
|
|
1327
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
1328
|
+
header,
|
|
1329
|
+
expected: client,
|
|
1330
|
+
reason: "client configuration"
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
return;
|
|
1334
|
+
}
|
|
1335
|
+
if (Array.isArray(issuer)) {
|
|
1336
|
+
if (!issuer.includes(header.alg)) {
|
|
1337
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
1338
|
+
header,
|
|
1339
|
+
expected: issuer,
|
|
1340
|
+
reason: "authorization server metadata"
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
return;
|
|
1344
|
+
}
|
|
1345
|
+
if (fallback !== void 0) {
|
|
1346
|
+
if (typeof fallback === "string" ? header.alg !== fallback : typeof fallback === "function" ? !fallback(header.alg) : !fallback.includes(header.alg)) {
|
|
1347
|
+
throw OPE('unexpected JWT "alg" header parameter', INVALID_RESPONSE, {
|
|
1348
|
+
header,
|
|
1349
|
+
expected: fallback,
|
|
1350
|
+
reason: "default value"
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
return;
|
|
1354
|
+
}
|
|
1355
|
+
throw OPE('missing client or server configuration to verify used JWT "alg" header parameter', void 0, { client, issuer, fallback });
|
|
1356
|
+
}
|
|
1357
|
+
function getURLSearchParameter(parameters, name) {
|
|
1358
|
+
const { 0: value, length } = parameters.getAll(name);
|
|
1359
|
+
if (length > 1) {
|
|
1360
|
+
throw OPE(`"${name}" parameter must be provided only once`, INVALID_RESPONSE);
|
|
1361
|
+
}
|
|
1362
|
+
return value;
|
|
1363
|
+
}
|
|
1364
|
+
var skipStateCheck = /* @__PURE__ */ Symbol();
|
|
1365
|
+
var expectNoState = /* @__PURE__ */ Symbol();
|
|
1366
|
+
function validateAuthResponse(as, client, parameters, expectedState) {
|
|
1367
|
+
assertAs(as);
|
|
1368
|
+
assertClient(client);
|
|
1369
|
+
if (parameters instanceof URL) {
|
|
1370
|
+
parameters = parameters.searchParams;
|
|
1371
|
+
}
|
|
1372
|
+
if (!(parameters instanceof URLSearchParams)) {
|
|
1373
|
+
throw CodedTypeError('"parameters" must be an instance of URLSearchParams, or URL', ERR_INVALID_ARG_TYPE);
|
|
1374
|
+
}
|
|
1375
|
+
if (getURLSearchParameter(parameters, "response")) {
|
|
1376
|
+
throw OPE('"parameters" contains a JARM response, use validateJwtAuthResponse() instead of validateAuthResponse()', INVALID_RESPONSE, { parameters });
|
|
1377
|
+
}
|
|
1378
|
+
const iss = getURLSearchParameter(parameters, "iss");
|
|
1379
|
+
const state = getURLSearchParameter(parameters, "state");
|
|
1380
|
+
if (!iss && as.authorization_response_iss_parameter_supported) {
|
|
1381
|
+
throw OPE('response parameter "iss" (issuer) missing', INVALID_RESPONSE, { parameters });
|
|
1382
|
+
}
|
|
1383
|
+
if (iss && iss !== as.issuer) {
|
|
1384
|
+
throw OPE('unexpected "iss" (issuer) response parameter value', INVALID_RESPONSE, {
|
|
1385
|
+
expected: as.issuer,
|
|
1386
|
+
parameters
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
switch (expectedState) {
|
|
1390
|
+
case void 0:
|
|
1391
|
+
case expectNoState:
|
|
1392
|
+
if (state !== void 0) {
|
|
1393
|
+
throw OPE('unexpected "state" response parameter encountered', INVALID_RESPONSE, {
|
|
1394
|
+
expected: void 0,
|
|
1395
|
+
parameters
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
break;
|
|
1399
|
+
case skipStateCheck:
|
|
1400
|
+
break;
|
|
1401
|
+
default:
|
|
1402
|
+
assertString(expectedState, '"expectedState" argument');
|
|
1403
|
+
if (state !== expectedState) {
|
|
1404
|
+
throw OPE(state === void 0 ? 'response parameter "state" missing' : 'unexpected "state" response parameter value', INVALID_RESPONSE, { expected: expectedState, parameters });
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
const error = getURLSearchParameter(parameters, "error");
|
|
1408
|
+
if (error) {
|
|
1409
|
+
throw new AuthorizationResponseError("authorization response from the server is an error", {
|
|
1410
|
+
cause: parameters
|
|
1411
|
+
});
|
|
1412
|
+
}
|
|
1413
|
+
const id_token = getURLSearchParameter(parameters, "id_token");
|
|
1414
|
+
const token = getURLSearchParameter(parameters, "token");
|
|
1415
|
+
if (id_token !== void 0 || token !== void 0) {
|
|
1416
|
+
throw new UnsupportedOperationError("implicit and hybrid flows are not supported");
|
|
1417
|
+
}
|
|
1418
|
+
return brand(new URLSearchParams(parameters));
|
|
1419
|
+
}
|
|
1420
|
+
async function getResponseJsonBody(response, check = assertApplicationJson) {
|
|
1421
|
+
let json;
|
|
1422
|
+
try {
|
|
1423
|
+
json = await response.json();
|
|
1424
|
+
} catch (cause) {
|
|
1425
|
+
check(response);
|
|
1426
|
+
throw OPE('failed to parse "response" body as JSON', PARSE_ERROR, cause);
|
|
1427
|
+
}
|
|
1428
|
+
if (!isJsonObject(json)) {
|
|
1429
|
+
throw OPE('"response" body must be a top level object', INVALID_RESPONSE, { body: json });
|
|
1430
|
+
}
|
|
1431
|
+
return json;
|
|
1432
|
+
}
|
|
1433
|
+
var _nodiscoverycheck = /* @__PURE__ */ Symbol();
|
|
1434
|
+
var _expectedIssuer = /* @__PURE__ */ Symbol();
|
|
1435
|
+
|
|
1436
|
+
// src/oauth-browser.ts
|
|
1437
|
+
function fetchFrom(fetchFn) {
|
|
1438
|
+
return fetchFn ?? fetch;
|
|
1439
|
+
}
|
|
1440
|
+
function discoveryOptions(issuer, fetchFn) {
|
|
1441
|
+
return {
|
|
1442
|
+
algorithm: "oauth2",
|
|
1443
|
+
[customFetch]: fetchFrom(fetchFn),
|
|
1444
|
+
...issuer.protocol === "http:" ? { [allowInsecureRequests]: true } : {}
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
async function discoverCliAuthorizationServer(input) {
|
|
1448
|
+
const options = discoveryOptions(input.issuer, input.fetchFn);
|
|
1449
|
+
const response = await discoveryRequest(input.issuer, options);
|
|
1450
|
+
return processDiscoveryResponse(input.issuer, response);
|
|
1451
|
+
}
|
|
1452
|
+
function cliOAuthClient(mcpUrl) {
|
|
1453
|
+
return {
|
|
1454
|
+
client_id: cliClientMetadataUrl(mcpUrl),
|
|
1455
|
+
token_endpoint_auth_method: "none"
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
async function buildCliAuthorizationUrl(input) {
|
|
1459
|
+
if (!input.authorizationServer.authorization_endpoint) {
|
|
1460
|
+
throw new Error("OAuth authorization server has no authorization_endpoint.");
|
|
1461
|
+
}
|
|
1462
|
+
const url = new URL(input.authorizationServer.authorization_endpoint);
|
|
1463
|
+
url.searchParams.set("client_id", cliOAuthClient(input.mcpUrl).client_id);
|
|
1464
|
+
url.searchParams.set(
|
|
1465
|
+
"redirect_uri",
|
|
1466
|
+
(input.redirectUri ?? cliRedirectUrl()).href
|
|
1467
|
+
);
|
|
1468
|
+
url.searchParams.set("response_type", "code");
|
|
1469
|
+
url.searchParams.set("scope", input.scope ?? defaultScopes.join(" "));
|
|
1470
|
+
url.searchParams.set("code_challenge", input.codeChallenge);
|
|
1471
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
1472
|
+
url.searchParams.set("state", input.state);
|
|
1473
|
+
url.searchParams.set("resource", input.mcpUrl);
|
|
1474
|
+
if (input.org !== void 0) {
|
|
1475
|
+
url.searchParams.set("org", orgSlugFromFlag(input.org));
|
|
1476
|
+
}
|
|
1477
|
+
return url;
|
|
1478
|
+
}
|
|
1479
|
+
function insecure(issuer) {
|
|
1480
|
+
return new URL(issuer).protocol === "http:" ? { [allowInsecureRequests]: true } : {};
|
|
1481
|
+
}
|
|
1482
|
+
async function exchangeAuthorizationCode(input) {
|
|
1483
|
+
const client = cliOAuthClient(input.mcpUrl);
|
|
1484
|
+
const params = validateAuthResponse(
|
|
1485
|
+
input.authorizationServer,
|
|
1486
|
+
client,
|
|
1487
|
+
input.callback,
|
|
1488
|
+
input.state
|
|
1489
|
+
);
|
|
1490
|
+
const resource = new URLSearchParams();
|
|
1491
|
+
resource.set("resource", input.mcpUrl);
|
|
1492
|
+
const response = await authorizationCodeGrantRequest(
|
|
1493
|
+
input.authorizationServer,
|
|
1494
|
+
client,
|
|
1495
|
+
None(),
|
|
1496
|
+
params,
|
|
1497
|
+
input.redirectUri.href,
|
|
1498
|
+
input.codeVerifier,
|
|
1499
|
+
{
|
|
1500
|
+
...insecure(input.authorizationServer.issuer),
|
|
1501
|
+
[customFetch]: fetchFrom(input.fetchFn),
|
|
1502
|
+
additionalParameters: resource
|
|
1503
|
+
}
|
|
1504
|
+
);
|
|
1505
|
+
const tokens = await processAuthorizationCodeResponse(
|
|
1506
|
+
input.authorizationServer,
|
|
1507
|
+
client,
|
|
1508
|
+
response
|
|
1509
|
+
);
|
|
1510
|
+
return tokenSetFromResult(input.authorizationServer.issuer, tokens);
|
|
1511
|
+
}
|
|
1512
|
+
async function refreshOAuthTokens(input) {
|
|
1513
|
+
const issuer = new URL(input.issuer);
|
|
1514
|
+
const authorizationServer = await discoverCliAuthorizationServer({
|
|
1515
|
+
issuer,
|
|
1516
|
+
fetchFn: input.fetchFn
|
|
1517
|
+
});
|
|
1518
|
+
const client = cliOAuthClient(input.mcpUrl);
|
|
1519
|
+
const response = await refreshTokenGrantRequest(
|
|
1520
|
+
authorizationServer,
|
|
1521
|
+
client,
|
|
1522
|
+
None(),
|
|
1523
|
+
input.refreshToken,
|
|
1524
|
+
{
|
|
1525
|
+
...insecure(authorizationServer.issuer),
|
|
1526
|
+
[customFetch]: fetchFrom(input.fetchFn)
|
|
1527
|
+
}
|
|
1528
|
+
);
|
|
1529
|
+
const tokens = await processRefreshTokenResponse(
|
|
1530
|
+
authorizationServer,
|
|
1531
|
+
client,
|
|
1532
|
+
response
|
|
1533
|
+
);
|
|
1534
|
+
return tokenSetFromResult(authorizationServer.issuer, tokens);
|
|
1535
|
+
}
|
|
1536
|
+
function tokenSetFromResult(issuer, tokens) {
|
|
1537
|
+
return {
|
|
1538
|
+
accessToken: tokens.access_token,
|
|
1539
|
+
...tokens.refresh_token ? { refreshToken: tokens.refresh_token } : {},
|
|
1540
|
+
tokenType: tokens.token_type ?? "bearer",
|
|
1541
|
+
...typeof tokens.expires_in === "number" ? { expiresIn: tokens.expires_in } : {},
|
|
1542
|
+
...tokens.scope ? { scope: tokens.scope } : {},
|
|
1543
|
+
issuer
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
async function createPkcePair() {
|
|
1547
|
+
const verifier = generateRandomCodeVerifier();
|
|
1548
|
+
const challenge = await calculatePKCECodeChallenge(verifier);
|
|
1549
|
+
return { verifier, challenge };
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
// src/auth.ts
|
|
1553
|
+
function isAccessTokenExpired(credentials, now = Date.now()) {
|
|
1554
|
+
if (!credentials.expiresAt) return false;
|
|
1555
|
+
return now + accessTokenSkewMs >= credentials.expiresAt;
|
|
1556
|
+
}
|
|
1557
|
+
function credentialsFromTokens(input) {
|
|
1558
|
+
const now = input.now ?? Date.now();
|
|
1559
|
+
const expiresAt = typeof input.tokens.expires_in === "number" ? now + input.tokens.expires_in * 1e3 : input.previous?.expiresAt;
|
|
1560
|
+
const org = input.org ?? input.previous?.org;
|
|
1561
|
+
return {
|
|
1562
|
+
version: 1,
|
|
1563
|
+
mcpUrl: input.mcpUrl,
|
|
1564
|
+
...org ? { org } : {},
|
|
1565
|
+
resource: input.resource,
|
|
1566
|
+
authorizationServerUrl: input.authorizationServerUrl,
|
|
1567
|
+
clientId: input.client.client_id,
|
|
1568
|
+
clientSecret: "client_secret" in input.client ? input.client.client_secret : input.previous?.clientSecret,
|
|
1569
|
+
accessToken: input.tokens.access_token,
|
|
1570
|
+
refreshToken: input.tokens.refresh_token ?? input.previous?.refreshToken,
|
|
1571
|
+
tokenType: input.tokens.token_type ?? "bearer",
|
|
1572
|
+
expiresAt,
|
|
1573
|
+
scope: input.tokens.scope ?? input.previous?.scope
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
function deviceCredentials(input) {
|
|
1577
|
+
const org = input.org ?? input.previous?.org;
|
|
1578
|
+
return {
|
|
1579
|
+
version: 1,
|
|
1580
|
+
mcpUrl: input.mcpUrl,
|
|
1581
|
+
...org ? { org } : {},
|
|
1582
|
+
resource: input.mcpUrl,
|
|
1583
|
+
authorizationServerUrl: input.authorizationServerUrl,
|
|
1584
|
+
clientId: new URL(
|
|
1585
|
+
"/oauth/cli-client-metadata.json",
|
|
1586
|
+
input.authorizationServerUrl
|
|
1587
|
+
).href,
|
|
1588
|
+
accessToken: input.token,
|
|
1589
|
+
tokenType: "bearer",
|
|
1590
|
+
expiresAt: input.expiresAt,
|
|
1591
|
+
scope: input.scopes.join(" ")
|
|
1592
|
+
};
|
|
1593
|
+
}
|
|
1594
|
+
async function mintDeviceToken(input) {
|
|
1595
|
+
const minted = await openApiRequest({
|
|
1596
|
+
token: input.oauthAccessToken,
|
|
1597
|
+
apiUrl: input.apiUrl,
|
|
1598
|
+
fetchFn: input.fetchFn,
|
|
1599
|
+
method: "POST",
|
|
1600
|
+
path: "v1/tokens/device",
|
|
1601
|
+
body: { lifetime: input.lifetime, name: "kody-cli" }
|
|
1602
|
+
});
|
|
1603
|
+
if (!minted || typeof minted !== "object" || Array.isArray(minted)) {
|
|
1604
|
+
throw new Error("Device token mint returned no token.");
|
|
1605
|
+
}
|
|
1606
|
+
const record = minted;
|
|
1607
|
+
if (typeof record.token !== "string" || !record.token.startsWith("kody_at_")) {
|
|
1608
|
+
throw new Error("Device token mint did not return a kody_at_ token.");
|
|
1609
|
+
}
|
|
1610
|
+
const expiresAt = typeof record.expires_at === "string" ? Date.parse(record.expires_at) : void 0;
|
|
1611
|
+
const scopes = Array.isArray(record.scopes) ? record.scopes.filter(
|
|
1612
|
+
(scope) => typeof scope === "string"
|
|
1613
|
+
) : [];
|
|
1614
|
+
const loginOrg = typeof record.login_org?.slug === "string" && record.login_org.slug.trim() ? record.login_org.slug.trim() : void 0;
|
|
1615
|
+
return {
|
|
1616
|
+
token: record.token,
|
|
1617
|
+
...Number.isFinite(expiresAt) ? { expiresAt } : {},
|
|
1618
|
+
scopes,
|
|
1619
|
+
...loginOrg ? { loginOrg } : {}
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
async function refreshStoredCredentials(input) {
|
|
1623
|
+
const { credentials } = input;
|
|
1624
|
+
if (isScopedApiToken(credentials.accessToken)) {
|
|
1625
|
+
if (!isAccessTokenExpired(credentials, input.now)) return credentials;
|
|
1626
|
+
const slid = await adoptSlidingDeviceExpiry(input);
|
|
1627
|
+
if (slid) return slid;
|
|
1628
|
+
throw new Error("Stored API token expired. Run `kody login`.");
|
|
1629
|
+
}
|
|
1630
|
+
if (!credentials.refreshToken) {
|
|
1631
|
+
throw new Error("No refresh token is stored. Run `kody login`.");
|
|
1632
|
+
}
|
|
1633
|
+
const refreshed = await refreshOAuthTokens({
|
|
1634
|
+
issuer: credentials.authorizationServerUrl,
|
|
1635
|
+
mcpUrl: credentials.mcpUrl,
|
|
1636
|
+
refreshToken: credentials.refreshToken,
|
|
1637
|
+
fetchFn: input.fetchFn
|
|
1638
|
+
});
|
|
1639
|
+
const apiUrl = input.apiUrl ?? defaultApiUrl;
|
|
1640
|
+
const device = await mintDeviceToken({
|
|
1641
|
+
apiUrl,
|
|
1642
|
+
oauthAccessToken: refreshed.accessToken,
|
|
1643
|
+
lifetime: "long",
|
|
1644
|
+
fetchFn: input.fetchFn
|
|
1645
|
+
});
|
|
1646
|
+
const next = deviceCredentials({
|
|
1647
|
+
mcpUrl: credentials.mcpUrl,
|
|
1648
|
+
apiUrl,
|
|
1649
|
+
authorizationServerUrl: refreshed.issuer,
|
|
1650
|
+
token: device.token,
|
|
1651
|
+
expiresAt: device.expiresAt,
|
|
1652
|
+
scopes: device.scopes,
|
|
1653
|
+
previous: credentials,
|
|
1654
|
+
org: credentials.org ?? device.loginOrg
|
|
1655
|
+
});
|
|
1656
|
+
saveCredentials(next, input.backend, input.resolution, { activate: false });
|
|
1657
|
+
return next;
|
|
1658
|
+
}
|
|
1659
|
+
async function resolvePendingOrgSlug(input) {
|
|
1660
|
+
const mcpUrl = input.mcpUrl ?? defaultMcpUrl;
|
|
1661
|
+
const snapshot = loadCredentialStore(mcpUrl, input.backend, input.resolution);
|
|
1662
|
+
if (!snapshot?.pending) return snapshot?.activeOrg ?? null;
|
|
1663
|
+
let pending = loadPendingCredentials(mcpUrl, input.backend, input.resolution);
|
|
1664
|
+
if (!pending) return snapshot.activeOrg ?? null;
|
|
1665
|
+
if (isAccessTokenExpired(pending, input.now)) {
|
|
1666
|
+
if (!pending.refreshToken) return null;
|
|
1667
|
+
try {
|
|
1668
|
+
pending = await refreshStoredCredentials({
|
|
1669
|
+
credentials: pending,
|
|
1670
|
+
backend: input.backend,
|
|
1671
|
+
resolution: input.resolution,
|
|
1672
|
+
now: input.now,
|
|
1673
|
+
fetchFn: input.fetchFn,
|
|
1674
|
+
apiUrl: input.apiUrl
|
|
1675
|
+
});
|
|
1676
|
+
} catch {
|
|
1677
|
+
return null;
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
const discover = input.discoverOrg ?? discoverOrgSlugFromAccessToken;
|
|
1681
|
+
try {
|
|
1682
|
+
const slug = await discover({
|
|
1683
|
+
mcpUrl,
|
|
1684
|
+
accessToken: pending.accessToken,
|
|
1685
|
+
fetchFn: input.fetchFn
|
|
1686
|
+
});
|
|
1687
|
+
promotePendingOrg(mcpUrl, slug, input.backend, input.resolution);
|
|
1688
|
+
return slug;
|
|
1689
|
+
} catch {
|
|
1690
|
+
return null;
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
async function ensureFreshCredentials(input) {
|
|
1694
|
+
const mcpUrl = input.mcpUrl ?? defaultMcpUrl;
|
|
1695
|
+
await resolvePendingOrgSlug({
|
|
1696
|
+
mcpUrl,
|
|
1697
|
+
apiUrl: input.apiUrl,
|
|
1698
|
+
backend: input.backend,
|
|
1699
|
+
resolution: input.resolution,
|
|
1700
|
+
now: input.now,
|
|
1701
|
+
fetchFn: input.fetchFn,
|
|
1702
|
+
discoverOrg: input.discoverOrg
|
|
1703
|
+
});
|
|
1704
|
+
const credentials = loadCredentials(mcpUrl, input.backend, input.resolution, {
|
|
1705
|
+
org: input.org
|
|
1706
|
+
});
|
|
1707
|
+
if (!credentials) {
|
|
1708
|
+
if (input.org) {
|
|
1709
|
+
throw new Error(
|
|
1710
|
+
`No login stored for org "${input.org}". Run \`kody login --org ${input.org}\` (or \`kody org use ${input.org}\` after logging in).`
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
throw new Error(missingCliAuthMessage());
|
|
1714
|
+
}
|
|
1715
|
+
if (!isAccessTokenExpired(credentials, input.now)) {
|
|
1716
|
+
return credentials;
|
|
1717
|
+
}
|
|
1718
|
+
return await refreshStoredCredentials({
|
|
1719
|
+
credentials,
|
|
1720
|
+
backend: input.backend,
|
|
1721
|
+
resolution: input.resolution,
|
|
1722
|
+
now: input.now,
|
|
1723
|
+
fetchFn: input.fetchFn,
|
|
1724
|
+
apiUrl: input.apiUrl
|
|
1725
|
+
});
|
|
1726
|
+
}
|
|
1727
|
+
async function ensureOpenApiCredentials(input) {
|
|
1728
|
+
const credentials = await ensureFreshCredentials(input);
|
|
1729
|
+
if (isScopedApiToken(credentials.accessToken)) return credentials;
|
|
1730
|
+
return refreshStoredCredentials({
|
|
1731
|
+
credentials,
|
|
1732
|
+
backend: input.backend,
|
|
1733
|
+
resolution: input.resolution,
|
|
1734
|
+
now: input.now,
|
|
1735
|
+
fetchFn: input.fetchFn,
|
|
1736
|
+
apiUrl: input.apiUrl
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
async function adoptSlidingDeviceExpiry(input) {
|
|
1740
|
+
const { credentials } = input;
|
|
1741
|
+
try {
|
|
1742
|
+
const current = await openApiGet({
|
|
1743
|
+
token: credentials.accessToken,
|
|
1744
|
+
apiUrl: input.apiUrl ?? defaultApiUrl,
|
|
1745
|
+
path: "v1/tokens/current",
|
|
1746
|
+
fetchFn: input.fetchFn,
|
|
1747
|
+
...credentials.org ? { org: credentials.org } : {}
|
|
1748
|
+
});
|
|
1749
|
+
if (!current || typeof current !== "object" || Array.isArray(current)) {
|
|
1750
|
+
return null;
|
|
1751
|
+
}
|
|
1752
|
+
const expiresAt = Date.parse(
|
|
1753
|
+
typeof current.expires_at === "string" ? current.expires_at : ""
|
|
1754
|
+
);
|
|
1755
|
+
const now = input.now ?? Date.now();
|
|
1756
|
+
if (!Number.isFinite(expiresAt) || expiresAt <= now) return null;
|
|
1757
|
+
const next = { ...credentials, expiresAt };
|
|
1758
|
+
saveCredentials(next, input.backend, input.resolution, { activate: false });
|
|
1759
|
+
return next;
|
|
1760
|
+
} catch (error) {
|
|
1761
|
+
if (error instanceof OpenApiError && (error.status === 401 || error.status === 403)) {
|
|
1762
|
+
return null;
|
|
1763
|
+
}
|
|
1764
|
+
throw error;
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
function startCallbackServer(redirectUri) {
|
|
1768
|
+
return new Promise((resolve, reject) => {
|
|
1769
|
+
const server = createServer();
|
|
1770
|
+
const port = Number(redirectUri.port);
|
|
1771
|
+
server.listen(port, redirectUri.hostname, () => resolve(server));
|
|
1772
|
+
server.on("error", (error) => {
|
|
1773
|
+
if ("code" in error && error.code === "EADDRINUSE") {
|
|
1774
|
+
reject(
|
|
1775
|
+
new Error(
|
|
1776
|
+
`Login callback port ${port} is in use. Stop the other listener and retry.`
|
|
1777
|
+
)
|
|
1778
|
+
);
|
|
1779
|
+
return;
|
|
1780
|
+
}
|
|
1781
|
+
reject(error);
|
|
1782
|
+
});
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
function sendCallbackHtml(response, status, page) {
|
|
1786
|
+
if (response.headersSent) return;
|
|
1787
|
+
response.writeHead(status, {
|
|
1788
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1789
|
+
"Cache-Control": "no-store"
|
|
1790
|
+
});
|
|
1791
|
+
response.end(renderLoginCallbackPage(page));
|
|
1792
|
+
}
|
|
1793
|
+
function waitForCallback(input) {
|
|
1794
|
+
return new Promise((resolve, reject) => {
|
|
1795
|
+
let settled = false;
|
|
1796
|
+
let pendingResponse;
|
|
1797
|
+
const closeServer = () => {
|
|
1798
|
+
input.server.close();
|
|
1799
|
+
};
|
|
1800
|
+
const rejectOnce = (error) => {
|
|
1801
|
+
if (settled) return;
|
|
1802
|
+
settled = true;
|
|
1803
|
+
clearTimeout(timer);
|
|
1804
|
+
closeServer();
|
|
1805
|
+
reject(error);
|
|
1806
|
+
};
|
|
1807
|
+
const resolveOnce = (result) => {
|
|
1808
|
+
if (settled) return;
|
|
1809
|
+
settled = true;
|
|
1810
|
+
clearTimeout(timer);
|
|
1811
|
+
resolve(result);
|
|
1812
|
+
};
|
|
1813
|
+
const timer = setTimeout(() => {
|
|
1814
|
+
if (pendingResponse && !pendingResponse.headersSent) {
|
|
1815
|
+
sendCallbackHtml(pendingResponse, 408, {
|
|
1816
|
+
kind: "error",
|
|
1817
|
+
error: "expired"
|
|
1818
|
+
});
|
|
1819
|
+
}
|
|
1820
|
+
rejectOnce(new Error("Timed out waiting for the browser login."));
|
|
1821
|
+
}, input.timeoutMs);
|
|
1822
|
+
input.server.on(
|
|
1823
|
+
"request",
|
|
1824
|
+
(request, response) => {
|
|
1825
|
+
try {
|
|
1826
|
+
const url = new URL(request.url ?? "/", input.redirectUri);
|
|
1827
|
+
if (url.pathname !== input.redirectUri.pathname) {
|
|
1828
|
+
response.writeHead(404).end("Not found");
|
|
1829
|
+
return;
|
|
1830
|
+
}
|
|
1831
|
+
const error = url.searchParams.get("error");
|
|
1832
|
+
if (error) {
|
|
1833
|
+
const description = url.searchParams.get("error_description") ?? "";
|
|
1834
|
+
const kind = loginCallbackErrorFromOAuthError(error);
|
|
1835
|
+
sendCallbackHtml(response, 400, { kind: "error", error: kind });
|
|
1836
|
+
rejectOnce(new Error(description || error));
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
const state = url.searchParams.get("state");
|
|
1840
|
+
const code = url.searchParams.get("code");
|
|
1841
|
+
const iss = url.searchParams.get("iss") ?? void 0;
|
|
1842
|
+
if (state !== input.expectedState || !code) {
|
|
1843
|
+
const kind = state && state !== input.expectedState ? "state_mismatch" : "failed";
|
|
1844
|
+
sendCallbackHtml(response, 400, { kind: "error", error: kind });
|
|
1845
|
+
rejectOnce(new Error("OAuth callback missing code or state."));
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
pendingResponse = response;
|
|
1849
|
+
resolveOnce({
|
|
1850
|
+
code,
|
|
1851
|
+
...iss ? { iss } : {},
|
|
1852
|
+
complete(page) {
|
|
1853
|
+
sendCallbackHtml(
|
|
1854
|
+
response,
|
|
1855
|
+
page.kind === "success" ? 200 : 400,
|
|
1856
|
+
page
|
|
1857
|
+
);
|
|
1858
|
+
closeServer();
|
|
1859
|
+
}
|
|
1860
|
+
});
|
|
1861
|
+
} catch (error) {
|
|
1862
|
+
rejectOnce(redactError(error));
|
|
1863
|
+
}
|
|
1864
|
+
}
|
|
1865
|
+
);
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1868
|
+
async function login(options = {}) {
|
|
1869
|
+
const mcpUrl = options.mcpUrl ?? defaultMcpUrl;
|
|
1870
|
+
const apiUrl = options.apiUrl ?? defaultApiUrl;
|
|
1871
|
+
const orgHint = options.org === void 0 ? void 0 : orgSlugFromFlag(options.org);
|
|
1872
|
+
const redirectUri = cliRedirectUrl();
|
|
1873
|
+
const expectedState = crypto.randomUUID();
|
|
1874
|
+
const issuer = new URL(mcpUrl).origin;
|
|
1875
|
+
const authorizationServer = await discoverCliAuthorizationServer({
|
|
1876
|
+
issuer: new URL(issuer),
|
|
1877
|
+
fetchFn: options.fetchFn
|
|
1878
|
+
});
|
|
1879
|
+
const pkce = await createPkcePair();
|
|
1880
|
+
const authorizationUrl = await buildCliAuthorizationUrl({
|
|
1881
|
+
authorizationServer,
|
|
1882
|
+
mcpUrl,
|
|
1883
|
+
redirectUri,
|
|
1884
|
+
state: expectedState,
|
|
1885
|
+
codeChallenge: pkce.challenge,
|
|
1886
|
+
...orgHint ? { org: orgHint } : {}
|
|
1887
|
+
});
|
|
1888
|
+
const server = await startCallbackServer(redirectUri);
|
|
1889
|
+
options.onAuthorizationUrl?.(authorizationUrl);
|
|
1890
|
+
if (options.openBrowser !== false) {
|
|
1891
|
+
await openUrl(authorizationUrl.href);
|
|
1892
|
+
}
|
|
1893
|
+
let completeCallback;
|
|
1894
|
+
try {
|
|
1895
|
+
const callback = await waitForCallback({
|
|
1896
|
+
server,
|
|
1897
|
+
redirectUri,
|
|
1898
|
+
expectedState,
|
|
1899
|
+
timeoutMs: options.timeoutMs ?? loginTimeoutMs
|
|
1900
|
+
});
|
|
1901
|
+
completeCallback = callback.complete;
|
|
1902
|
+
const callbackUrl = new URL(redirectUri.href);
|
|
1903
|
+
callbackUrl.searchParams.set("code", callback.code);
|
|
1904
|
+
callbackUrl.searchParams.set("state", expectedState);
|
|
1905
|
+
if (callback.iss) callbackUrl.searchParams.set("iss", callback.iss);
|
|
1906
|
+
const tokens = await exchangeAuthorizationCode({
|
|
1907
|
+
authorizationServer,
|
|
1908
|
+
mcpUrl,
|
|
1909
|
+
redirectUri,
|
|
1910
|
+
callback: callbackUrl,
|
|
1911
|
+
state: expectedState,
|
|
1912
|
+
codeVerifier: pkce.verifier,
|
|
1913
|
+
fetchFn: options.fetchFn
|
|
1914
|
+
});
|
|
1915
|
+
const device = await mintDeviceToken({
|
|
1916
|
+
apiUrl,
|
|
1917
|
+
oauthAccessToken: tokens.accessToken,
|
|
1918
|
+
lifetime: options.lifetime ?? "long",
|
|
1919
|
+
fetchFn: options.fetchFn
|
|
1920
|
+
});
|
|
1921
|
+
let org = device.loginOrg ?? orgHint;
|
|
1922
|
+
let account;
|
|
1923
|
+
const discover = options.discoverIdentity ?? discoverLoginIdentity;
|
|
1924
|
+
try {
|
|
1925
|
+
const boundOrg = org;
|
|
1926
|
+
const identity = await discover({
|
|
1927
|
+
mcpUrl,
|
|
1928
|
+
apiUrl,
|
|
1929
|
+
accessToken: device.token,
|
|
1930
|
+
fetchFn: options.fetchFn,
|
|
1931
|
+
...boundOrg ? { org: boundOrg } : {}
|
|
1932
|
+
});
|
|
1933
|
+
if (orgHint && identity.org !== orgHint) {
|
|
1934
|
+
completeCallback?.({ kind: "error", error: "failed" });
|
|
1935
|
+
completeCallback = void 0;
|
|
1936
|
+
throw new Error(
|
|
1937
|
+
`Login was bound to org "${identity.org}" but \`--org ${orgHint}\` was requested.`
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
org = identity.org;
|
|
1941
|
+
account = identity.account;
|
|
1942
|
+
} catch (error) {
|
|
1943
|
+
if (error instanceof Error && error.message.startsWith("Login was bound to org ")) {
|
|
1944
|
+
throw error;
|
|
1945
|
+
}
|
|
1946
|
+
if (!org) {
|
|
1947
|
+
const existing = loadCredentialStore(
|
|
1948
|
+
mcpUrl,
|
|
1949
|
+
options.backend,
|
|
1950
|
+
options.resolution
|
|
1951
|
+
);
|
|
1952
|
+
if (existing && Object.keys(existing.orgs).length > 0) {
|
|
1953
|
+
completeCallback?.({ kind: "error", error: "failed" });
|
|
1954
|
+
completeCallback = void 0;
|
|
1955
|
+
throw new Error(
|
|
1956
|
+
"Could not determine the organization for this login. Re-run with `kody login --org <slug>`."
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
const credentials = deviceCredentials({
|
|
1962
|
+
mcpUrl,
|
|
1963
|
+
apiUrl,
|
|
1964
|
+
authorizationServerUrl: tokens.issuer,
|
|
1965
|
+
token: device.token,
|
|
1966
|
+
expiresAt: device.expiresAt,
|
|
1967
|
+
scopes: device.scopes,
|
|
1968
|
+
org
|
|
1969
|
+
});
|
|
1970
|
+
const saved = saveCredentials(
|
|
1971
|
+
credentials,
|
|
1972
|
+
options.backend,
|
|
1973
|
+
options.resolution
|
|
1974
|
+
);
|
|
1975
|
+
completeCallback?.({
|
|
1976
|
+
kind: "success",
|
|
1977
|
+
...org ? { org } : {},
|
|
1978
|
+
...account ? { account } : {}
|
|
1979
|
+
});
|
|
1980
|
+
completeCallback = void 0;
|
|
1981
|
+
return {
|
|
1982
|
+
credentials,
|
|
1983
|
+
authorizationUrl,
|
|
1984
|
+
backendKind: saved.backend.kind,
|
|
1985
|
+
backendPath: saved.backend.path,
|
|
1986
|
+
...org ? { org } : {},
|
|
1987
|
+
...account ? { account } : {}
|
|
1988
|
+
};
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
completeCallback?.({ kind: "error", error: "failed" });
|
|
1991
|
+
server.close();
|
|
1992
|
+
throw redactError(error);
|
|
1993
|
+
}
|
|
1994
|
+
}
|
|
1995
|
+
|
|
1996
|
+
export {
|
|
1997
|
+
isAccessTokenExpired,
|
|
1998
|
+
credentialsFromTokens,
|
|
1999
|
+
refreshStoredCredentials,
|
|
2000
|
+
resolvePendingOrgSlug,
|
|
2001
|
+
ensureFreshCredentials,
|
|
2002
|
+
ensureOpenApiCredentials,
|
|
2003
|
+
login
|
|
2004
|
+
};
|
|
2005
|
+
//# sourceMappingURL=chunk-5ZBJAP6G.js.map
|