@devdogsuga/devtools 0.1.26 → 0.1.28
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 +72 -11
- package/bin/devtools.mjs +4 -4
- package/dist/cli-CZKkNHK5.js +6619 -0
- package/dist/failure-log-BBPGejQv.js +256 -0
- package/dist/launch.js +267 -281
- package/dist/local-env-D8HC2JK_.js +351 -0
- package/dist/local-env-DrBCXqD8.js +2 -0
- package/dist/menu-CQBsQRgH.js +1684 -0
- package/dist/peer-redirect-hooks.js +15 -0
- package/dist/peers-RBplwovA.js +229 -0
- package/dist/root-C2OmZDwD.js +94 -0
- package/dist/stack-Bx-55oi_.js +2 -0
- package/dist/stack-BynCgRVS.js +181 -0
- package/dist/supabase-project-0knM0mqd.js +426 -0
- package/dist/telemetry-C4foD8MD.js +249 -0
- package/dist/telemetry.js +2 -148
- package/package.json +10 -29
- package/bin/devtools-ci-bare.mjs +0 -48
- package/bin/devtools-ci.mjs +0 -31
- package/dist/args.d.ts +0 -26
- package/dist/args.js +0 -78
- package/dist/bws/bw.d.ts +0 -18
- package/dist/bws/bw.js +0 -70
- package/dist/bws/client.d.ts +0 -43
- package/dist/bws/client.js +0 -363
- package/dist/bws/environments.d.ts +0 -96
- package/dist/bws/environments.js +0 -111
- package/dist/bws/pace.d.ts +0 -25
- package/dist/bws/pace.js +0 -43
- package/dist/bws/retry.d.ts +0 -12
- package/dist/bws/retry.js +0 -39
- package/dist/bws/store.d.ts +0 -1
- package/dist/bws/store.js +0 -32
- package/dist/bws/token.d.ts +0 -31
- package/dist/bws/token.js +0 -88
- package/dist/bws/vault.d.ts +0 -29
- package/dist/bws/vault.js +0 -200
- package/dist/catalog.d.ts +0 -57
- package/dist/catalog.js +0 -57
- package/dist/cf/build.d.ts +0 -13
- package/dist/cf/build.js +0 -65
- package/dist/cf/commands.d.ts +0 -1
- package/dist/cf/commands.js +0 -172
- package/dist/cf/local-env.d.ts +0 -58
- package/dist/cf/local-env.js +0 -177
- package/dist/ci.d.ts +0 -1
- package/dist/ci.js +0 -328
- package/dist/cli.d.ts +0 -11
- package/dist/cli.js +0 -1094
- package/dist/commands.d.ts +0 -261
- package/dist/commands.js +0 -1318
- package/dist/completions.d.ts +0 -4
- package/dist/completions.js +0 -110
- package/dist/cron/commands.d.ts +0 -47
- package/dist/cron/commands.js +0 -372
- package/dist/cron/discovery.d.ts +0 -55
- package/dist/cron/discovery.js +0 -85
- package/dist/cron/schema.d.ts +0 -45
- package/dist/cron/schema.js +0 -35
- package/dist/db/config-push.d.ts +0 -8
- package/dist/db/config-push.js +0 -11
- package/dist/db/connection.d.ts +0 -79
- package/dist/db/connection.js +0 -114
- package/dist/db/exec.d.ts +0 -1
- package/dist/db/exec.js +0 -11
- package/dist/db/generate-types.d.ts +0 -1
- package/dist/db/generate-types.js +0 -4
- package/dist/db/generated-env.d.ts +0 -69
- package/dist/db/generated-env.js +0 -113
- package/dist/db/introspect.d.ts +0 -1
- package/dist/db/introspect.js +0 -122
- package/dist/db/local-env.d.ts +0 -65
- package/dist/db/local-env.js +0 -23
- package/dist/db/new-migration.d.ts +0 -8
- package/dist/db/new-migration.js +0 -69
- package/dist/db/pooler.d.ts +0 -23
- package/dist/db/pooler.js +0 -55
- package/dist/db/run.d.ts +0 -68
- package/dist/db/run.js +0 -156
- package/dist/db/seed-buckets.d.ts +0 -2
- package/dist/db/seed-buckets.js +0 -4
- package/dist/db/seed-production.d.ts +0 -8
- package/dist/db/seed-production.js +0 -79
- package/dist/db/seed-roles.d.ts +0 -7
- package/dist/db/seed-roles.js +0 -33
- package/dist/db/session-refresh.d.ts +0 -43
- package/dist/db/session-refresh.js +0 -40
- package/dist/deploy/migrations.d.ts +0 -20
- package/dist/deploy/migrations.js +0 -68
- package/dist/deploy/orphans.d.ts +0 -20
- package/dist/deploy/orphans.js +0 -177
- package/dist/deploy/preflight.d.ts +0 -53
- package/dist/deploy/preflight.js +0 -189
- package/dist/deploy/report.d.ts +0 -26
- package/dist/deploy/report.js +0 -70
- package/dist/deploy/require-planner.d.ts +0 -2
- package/dist/deploy/require-planner.js +0 -60
- package/dist/deploy/require-token.d.ts +0 -15
- package/dist/deploy/require-token.js +0 -78
- package/dist/deploy/secrets-file.d.ts +0 -17
- package/dist/deploy/secrets-file.js +0 -166
- package/dist/deploy/write-env.d.ts +0 -40
- package/dist/deploy/write-env.js +0 -395
- package/dist/docs/index-pages.d.ts +0 -38
- package/dist/docs/index-pages.js +0 -186
- package/dist/emails/commands.d.ts +0 -15
- package/dist/emails/commands.js +0 -130
- package/dist/emails/email-types.d.ts +0 -13
- package/dist/emails/email-types.js +0 -1
- package/dist/emails/fixtures.d.ts +0 -20
- package/dist/emails/fixtures.js +0 -20
- package/dist/env/audit.d.ts +0 -206
- package/dist/env/audit.js +0 -436
- package/dist/env/cloudflare.d.ts +0 -21
- package/dist/env/cloudflare.js +0 -61
- package/dist/env/commands.d.ts +0 -99
- package/dist/env/commands.js +0 -613
- package/dist/env/discovery.d.ts +0 -15
- package/dist/env/discovery.js +0 -239
- package/dist/env/document.d.ts +0 -119
- package/dist/env/document.js +0 -323
- package/dist/env/example.d.ts +0 -68
- package/dist/env/example.js +0 -605
- package/dist/env/selection.d.ts +0 -105
- package/dist/env/selection.js +0 -228
- package/dist/env-entry.d.ts +0 -49
- package/dist/env-entry.js +0 -204
- package/dist/environment-doctor.d.ts +0 -45
- package/dist/environment-doctor.js +0 -429
- package/dist/environment.d.ts +0 -92
- package/dist/environment.js +0 -185
- package/dist/fingerprint.d.ts +0 -10
- package/dist/fingerprint.js +0 -18
- package/dist/gen/campus-map.d.ts +0 -1
- package/dist/gen/campus-map.js +0 -473
- package/dist/gen/commands.d.ts +0 -1
- package/dist/gen/commands.js +0 -25
- package/dist/gen/hypno.d.ts +0 -1
- package/dist/gen/hypno.js +0 -134
- package/dist/gh/client.d.ts +0 -80
- package/dist/gh/client.js +0 -259
- package/dist/gh/environments.d.ts +0 -83
- package/dist/gh/environments.js +0 -178
- package/dist/gh/rulesets/actors.d.ts +0 -14
- package/dist/gh/rulesets/actors.js +0 -69
- package/dist/gh/rulesets/api.d.ts +0 -15
- package/dist/gh/rulesets/api.js +0 -104
- package/dist/gh/rulesets/commands.d.ts +0 -3
- package/dist/gh/rulesets/commands.js +0 -203
- package/dist/gh/rulesets/desired.d.ts +0 -112
- package/dist/gh/rulesets/desired.js +0 -271
- package/dist/gh/rulesets/diff.d.ts +0 -65
- package/dist/gh/rulesets/diff.js +0 -170
- package/dist/gh/rulesets/types.d.ts +0 -76
- package/dist/gh/rulesets/types.js +0 -13
- package/dist/gh/settings/api.d.ts +0 -40
- package/dist/gh/settings/api.js +0 -180
- package/dist/gh/settings/commands.d.ts +0 -1
- package/dist/gh/settings/commands.js +0 -254
- package/dist/gh/settings/desired.d.ts +0 -53
- package/dist/gh/settings/desired.js +0 -43
- package/dist/gh/settings/diff.d.ts +0 -57
- package/dist/gh/settings/diff.js +0 -161
- package/dist/gh/settings/types.d.ts +0 -92
- package/dist/gh/settings/types.js +0 -11
- package/dist/gh/settings/workflows.d.ts +0 -41
- package/dist/gh/settings/workflows.js +0 -128
- package/dist/grantRoot.d.ts +0 -42
- package/dist/grantRoot.js +0 -89
- package/dist/help.d.ts +0 -18
- package/dist/help.js +0 -169
- package/dist/images/commands.d.ts +0 -32
- package/dist/images/commands.js +0 -231
- package/dist/images/events.d.ts +0 -47
- package/dist/images/events.js +0 -116
- package/dist/images/graphics.d.ts +0 -70
- package/dist/images/graphics.js +0 -234
- package/dist/images/open-graph-types.d.ts +0 -75
- package/dist/images/open-graph-types.js +0 -1
- package/dist/images/prompts.d.ts +0 -34
- package/dist/images/prompts.js +0 -100
- package/dist/images/render.d.ts +0 -21
- package/dist/images/render.js +0 -61
- package/dist/images/select.d.ts +0 -64
- package/dist/images/select.js +0 -96
- package/dist/instance.d.ts +0 -69
- package/dist/instance.js +0 -102
- package/dist/invocation.d.ts +0 -49
- package/dist/invocation.js +0 -78
- package/dist/launch-ci.d.ts +0 -1
- package/dist/launch-ci.js +0 -90
- package/dist/launch.d.ts +0 -18
- package/dist/menu.d.ts +0 -43
- package/dist/menu.js +0 -300
- package/dist/moderation.d.ts +0 -70
- package/dist/moderation.js +0 -128
- package/dist/oauth/browser.d.ts +0 -1
- package/dist/oauth/browser.js +0 -13
- package/dist/oauth/config.d.ts +0 -24
- package/dist/oauth/config.js +0 -24
- package/dist/oauth/connect-url.d.ts +0 -17
- package/dist/oauth/connect-url.js +0 -23
- package/dist/oauth/db.d.ts +0 -37
- package/dist/oauth/db.js +0 -108
- package/dist/oauth/device.d.ts +0 -93
- package/dist/oauth/device.js +0 -198
- package/dist/oauth/discovery.d.ts +0 -28
- package/dist/oauth/discovery.js +0 -54
- package/dist/oauth/env-file.d.ts +0 -6
- package/dist/oauth/env-file.js +0 -35
- package/dist/oauth/exchange.d.ts +0 -43
- package/dist/oauth/exchange.js +0 -93
- package/dist/oauth/label.d.ts +0 -1
- package/dist/oauth/label.js +0 -25
- package/dist/oauth/loopback.d.ts +0 -52
- package/dist/oauth/loopback.js +0 -168
- package/dist/oauth/pkce.d.ts +0 -6
- package/dist/oauth/pkce.js +0 -34
- package/dist/oauth/target.d.ts +0 -54
- package/dist/oauth/target.js +0 -102
- package/dist/oauth/transport.d.ts +0 -41
- package/dist/oauth/transport.js +0 -32
- package/dist/oauth/wizard.d.ts +0 -40
- package/dist/oauth/wizard.js +0 -578
- package/dist/persona.d.ts +0 -18
- package/dist/persona.js +0 -221
- package/dist/pick.d.ts +0 -6
- package/dist/pick.js +0 -64
- package/dist/pipes.d.ts +0 -1
- package/dist/pipes.js +0 -25
- package/dist/planner/checks.d.ts +0 -26
- package/dist/planner/checks.js +0 -104
- package/dist/planner/commands.d.ts +0 -10
- package/dist/planner/commands.js +0 -367
- package/dist/planner/db.d.ts +0 -15
- package/dist/planner/db.js +0 -33
- package/dist/planner/role.d.ts +0 -33
- package/dist/planner/role.js +0 -68
- package/dist/repo/peer-redirect-hooks.d.ts +0 -16
- package/dist/repo/peer-redirect-hooks.js +0 -10
- package/dist/repo/peer-redirect.d.ts +0 -4
- package/dist/repo/peer-redirect.js +0 -26
- package/dist/repo/peers.d.ts +0 -39
- package/dist/repo/peers.js +0 -139
- package/dist/repo/resolve.d.ts +0 -41
- package/dist/repo/resolve.js +0 -152
- package/dist/repo/root.d.ts +0 -23
- package/dist/repo/root.js +0 -91
- package/dist/repo/source.d.ts +0 -14
- package/dist/repo/source.js +0 -54
- package/dist/repo/supabase-project.d.ts +0 -60
- package/dist/repo/supabase-project.js +0 -124
- package/dist/repo/tsx-loader.d.ts +0 -6
- package/dist/repo/tsx-loader.js +0 -41
- package/dist/run/pick.d.ts +0 -90
- package/dist/run/pick.js +0 -664
- package/dist/setup.d.ts +0 -1
- package/dist/setup.js +0 -280
- package/dist/stack.d.ts +0 -48
- package/dist/stack.js +0 -403
- package/dist/telemetry.d.ts +0 -59
- package/dist/tier.d.ts +0 -40
- package/dist/tier.js +0 -96
- package/dist/ui.d.ts +0 -40
- package/dist/ui.js +0 -71
- package/dist/version.d.ts +0 -7
- package/dist/version.js +0 -17
- package/dist/workers.d.ts +0 -5
- package/dist/workers.js +0 -33
- package/dist/workflows/commands.d.ts +0 -54
- package/dist/workflows/commands.js +0 -763
package/dist/oauth/db.js
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { createClient } from "@supabase/supabase-js";
|
|
2
|
-
import { execFileSync } from "node:child_process";
|
|
3
|
-
import { parse } from "dotenv";
|
|
4
|
-
import { PROVIDER_SCOPES } from "./config.js";
|
|
5
|
-
function runStatus(argv, cwd) {
|
|
6
|
-
return execFileSync(argv[0], argv.slice(1), {
|
|
7
|
-
cwd,
|
|
8
|
-
encoding: "utf-8",
|
|
9
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Runs `supabase status -o env` in `cwd` and extracts the local API URL and
|
|
14
|
-
* service role key. Throws a descriptive error if Supabase isn't running or
|
|
15
|
-
* the CLI isn't installed.
|
|
16
|
-
*
|
|
17
|
-
* Two invocations are tried, because this wizard runs in two different worlds:
|
|
18
|
-
* a sibling project, where the Supabase CLI is normally installed globally, and
|
|
19
|
-
* this monorepo, where it is a workspace devDependency and not on PATH at all.
|
|
20
|
-
* A global install wins, since `pnpm exec` in a non-pnpm project would fail for
|
|
21
|
-
* reasons that have nothing to do with Supabase.
|
|
22
|
-
*/
|
|
23
|
-
export function detectLocalSupabase(cwd) {
|
|
24
|
-
let output;
|
|
25
|
-
try {
|
|
26
|
-
output = runStatus(["supabase", "status", "-o", "env"], cwd);
|
|
27
|
-
}
|
|
28
|
-
catch (first) {
|
|
29
|
-
try {
|
|
30
|
-
output = runStatus(["pnpm", "exec", "supabase", "status", "-o", "env"], cwd);
|
|
31
|
-
}
|
|
32
|
-
catch {
|
|
33
|
-
// The first failure is the one worth reporting: it is the one that says
|
|
34
|
-
// "not installed" rather than "pnpm could not find a workspace".
|
|
35
|
-
if (first.code === "ENOENT") {
|
|
36
|
-
throw new Error("supabase CLI not found — install it from https://supabase.com/docs/guides/cli");
|
|
37
|
-
}
|
|
38
|
-
const stderr = (first.stderr ?? "").trim();
|
|
39
|
-
throw new Error(stderr || (first instanceof Error ? first.message : String(first)));
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const env = parse(output);
|
|
43
|
-
const apiUrl = env["API_URL"];
|
|
44
|
-
const serviceRoleKey = env["SERVICE_ROLE_KEY"];
|
|
45
|
-
if (!apiUrl || !serviceRoleKey) {
|
|
46
|
-
throw new Error("Could not find API_URL or SERVICE_ROLE_KEY in `supabase status` output");
|
|
47
|
-
}
|
|
48
|
-
return { apiUrl, serviceRoleKey };
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Looks up a custom auth provider on a local Supabase instance through the
|
|
52
|
-
* Admin SDK, and reports whether it exists along with its name and issuer.
|
|
53
|
-
*
|
|
54
|
-
* A 404 means it does not exist yet. Any other error is real and rethrown.
|
|
55
|
-
*/
|
|
56
|
-
export async function checkProvider({ apiUrl, serviceRoleKey }, identifier) {
|
|
57
|
-
const supabase = createClient(apiUrl, serviceRoleKey, {
|
|
58
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
59
|
-
});
|
|
60
|
-
const { data, error } = await supabase.auth.admin.customProviders.getProvider(identifier);
|
|
61
|
-
if (error && error.status !== 404)
|
|
62
|
-
throw error;
|
|
63
|
-
return {
|
|
64
|
-
exists: !!data,
|
|
65
|
-
name: data?.name ?? undefined,
|
|
66
|
-
issuer: data?.issuer ?? undefined,
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
export async function upsertDevDogsProvider({ apiUrl, serviceRoleKey }, { identifier, name, clientId, clientSecret, issuer, }) {
|
|
70
|
-
const supabase = createClient(apiUrl, serviceRoleKey, {
|
|
71
|
-
auth: { autoRefreshToken: false, persistSession: false },
|
|
72
|
-
});
|
|
73
|
-
const { data: existing, error: getError } = await supabase.auth.admin.customProviders.getProvider(identifier);
|
|
74
|
-
// 404 just means the provider doesn't exist yet. Any other error is real.
|
|
75
|
-
if (getError && getError.status !== 404) {
|
|
76
|
-
throw getError;
|
|
77
|
-
}
|
|
78
|
-
if (existing) {
|
|
79
|
-
const { data, error } = await supabase.auth.admin.customProviders.updateProvider(identifier, {
|
|
80
|
-
name,
|
|
81
|
-
client_id: clientId,
|
|
82
|
-
client_secret: clientSecret,
|
|
83
|
-
issuer,
|
|
84
|
-
scopes: PROVIDER_SCOPES,
|
|
85
|
-
enabled: true,
|
|
86
|
-
});
|
|
87
|
-
if (error)
|
|
88
|
-
throw error;
|
|
89
|
-
if (!data)
|
|
90
|
-
throw new Error("updateProvider returned no data");
|
|
91
|
-
return { identifier: data.identifier, issuer: data.issuer ?? issuer };
|
|
92
|
-
}
|
|
93
|
-
const { data, error } = await supabase.auth.admin.customProviders.createProvider({
|
|
94
|
-
provider_type: "oidc",
|
|
95
|
-
identifier,
|
|
96
|
-
name,
|
|
97
|
-
client_id: clientId,
|
|
98
|
-
client_secret: clientSecret,
|
|
99
|
-
issuer,
|
|
100
|
-
scopes: PROVIDER_SCOPES,
|
|
101
|
-
enabled: true,
|
|
102
|
-
});
|
|
103
|
-
if (error)
|
|
104
|
-
throw error;
|
|
105
|
-
if (!data)
|
|
106
|
-
throw new Error("createProvider returned no data");
|
|
107
|
-
return { identifier: data.identifier, issuer: data.issuer ?? issuer };
|
|
108
|
-
}
|
package/dist/oauth/device.d.ts
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The device-code fallback for the one-click connect flow (TASK-352): for a
|
|
3
|
-
* contributor whose terminal cannot open a browser tab that can complete a
|
|
4
|
-
* loopback redirect back to THIS machine — SSH, a Codespace, a dev container,
|
|
5
|
-
* or any environment where the loopback listener itself fails to start (see
|
|
6
|
-
* `transport.ts`) — this trades a short user code, approved in a browser
|
|
7
|
-
* running anywhere, for the same OAuth client credentials `exchange.ts`
|
|
8
|
-
* hands back. The server side of this (TASK-352) lives in the DevDogsUGA
|
|
9
|
-
* repo, at `apps/platform/src/app/(site)/tools/oauth/device/` — this is the
|
|
10
|
-
* fixed wire contract the two sides agreed on:
|
|
11
|
-
*
|
|
12
|
-
* POST <platform>/tools/oauth/device/code
|
|
13
|
-
* { "label", "callback_uri" }
|
|
14
|
-
*
|
|
15
|
-
* 200 { "device_code", "user_code", "verification_uri",
|
|
16
|
-
* "verification_uri_complete", "expires_in", "interval" }
|
|
17
|
-
* 400 { "error": "invalid_request", "error_description" }
|
|
18
|
-
* 429 { "error": "rate_limited" } (+ Retry-After header)
|
|
19
|
-
*
|
|
20
|
-
* POST <platform>/tools/oauth/device/token
|
|
21
|
-
* { "device_code" }
|
|
22
|
-
*
|
|
23
|
-
* 200 { "client_id", "client_secret", "issuer" }
|
|
24
|
-
* 400 { "error": "authorization_pending" | "slow_down" | "access_denied" |
|
|
25
|
-
* "expired_token" | "invalid_grant" | "invalid_request",
|
|
26
|
-
* "error_description" }
|
|
27
|
-
* 429 { "error": "rate_limited" } (+ Retry-After header)
|
|
28
|
-
*
|
|
29
|
-
* `requestDeviceCode` mints the code; `pollForToken` polls `.../token` on
|
|
30
|
-
* the server's own cadence until it settles one way or the other. Both take
|
|
31
|
-
* an injectable `fetchImpl` so tests never touch the network, and
|
|
32
|
-
* `pollForToken` additionally takes injectable `sleep`/`now` so tests never
|
|
33
|
-
* touch the wall clock either.
|
|
34
|
-
*/
|
|
35
|
-
import type { ExchangeResult } from "./exchange.js";
|
|
36
|
-
export type DeviceErrorKind = "invalid_request" | "rate_limited" | "network" | "malformed";
|
|
37
|
-
export declare class DeviceError extends Error {
|
|
38
|
-
readonly kind: DeviceErrorKind;
|
|
39
|
-
readonly retryAfterSeconds?: number | undefined;
|
|
40
|
-
constructor(kind: DeviceErrorKind, message: string, retryAfterSeconds?: number | undefined);
|
|
41
|
-
}
|
|
42
|
-
export interface DeviceCodeResult {
|
|
43
|
-
deviceCode: string;
|
|
44
|
-
userCode: string;
|
|
45
|
-
verificationUri: string;
|
|
46
|
-
verificationUriComplete: string;
|
|
47
|
-
expiresIn: number;
|
|
48
|
-
interval: number;
|
|
49
|
-
}
|
|
50
|
-
/** The `/token` endpoint's terminal (non-retryable) outcomes, plus `network`/`malformed`. */
|
|
51
|
-
export type PollErrorKind = "access_denied" | "expired_token" | "invalid_grant" | "invalid_request" | "network" | "malformed";
|
|
52
|
-
export declare class PollError extends Error {
|
|
53
|
-
readonly kind: PollErrorKind;
|
|
54
|
-
constructor(kind: PollErrorKind, message: string);
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Requests a device code. Mirrors `exchange.ts`'s `exchangeCode` in shape:
|
|
58
|
-
* every failure is a distinct `DeviceError` kind, a 429 carries
|
|
59
|
-
* `Retry-After` when the platform sent one, and a 200 whose body is not
|
|
60
|
-
* valid JSON or is missing a required field is `malformed` rather than
|
|
61
|
-
* handing the caller `undefined` fields.
|
|
62
|
-
*/
|
|
63
|
-
export declare function requestDeviceCode({ platformUrl, label, callbackUri, fetchImpl, }: {
|
|
64
|
-
platformUrl: string;
|
|
65
|
-
label: string;
|
|
66
|
-
callbackUri: string;
|
|
67
|
-
fetchImpl?: typeof fetch;
|
|
68
|
-
}): Promise<DeviceCodeResult>;
|
|
69
|
-
/**
|
|
70
|
-
* Polls `.../tools/oauth/device/token` on the server's cadence until the
|
|
71
|
-
* user approves (or denies) the request in their browser, the device code
|
|
72
|
-
* expires, or the server rejects it outright.
|
|
73
|
-
*
|
|
74
|
-
* `authorization_pending` keeps polling at the current interval;
|
|
75
|
-
* `slow_down` adds 5 seconds to it and keeps polling (per the wire
|
|
76
|
-
* contract); a 429 waits out `Retry-After` (or the current interval, if the
|
|
77
|
-
* platform sent none) and keeps polling; every other error is terminal.
|
|
78
|
-
* Local expiry (`now() - start >= expiresInSeconds`) is reported the same
|
|
79
|
-
* way as the server's own `expired_token` — the caller does not need to
|
|
80
|
-
* tell the two apart.
|
|
81
|
-
*
|
|
82
|
-
* A network failure while polling retries with backoff up to
|
|
83
|
-
* `MAX_NETWORK_RETRIES` times before it is reported as `PollError("network")`.
|
|
84
|
-
*/
|
|
85
|
-
export declare function pollForToken({ platformUrl, deviceCode, intervalSeconds, expiresInSeconds, fetchImpl, sleep, now, }: {
|
|
86
|
-
platformUrl: string;
|
|
87
|
-
deviceCode: string;
|
|
88
|
-
intervalSeconds: number;
|
|
89
|
-
expiresInSeconds: number;
|
|
90
|
-
fetchImpl?: typeof fetch;
|
|
91
|
-
sleep?: (ms: number) => Promise<void>;
|
|
92
|
-
now?: () => number;
|
|
93
|
-
}): Promise<ExchangeResult>;
|
package/dist/oauth/device.js
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
export class DeviceError extends Error {
|
|
2
|
-
kind;
|
|
3
|
-
retryAfterSeconds;
|
|
4
|
-
constructor(kind, message, retryAfterSeconds) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.kind = kind;
|
|
7
|
-
this.retryAfterSeconds = retryAfterSeconds;
|
|
8
|
-
this.name = "DeviceError";
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
export class PollError extends Error {
|
|
12
|
-
kind;
|
|
13
|
-
constructor(kind, message) {
|
|
14
|
-
super(message);
|
|
15
|
-
this.kind = kind;
|
|
16
|
-
this.name = "PollError";
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
function deviceCodeUrl(platformUrl) {
|
|
20
|
-
return `${platformUrl.replace(/\/+$/, "")}/tools/oauth/device/code`;
|
|
21
|
-
}
|
|
22
|
-
function deviceTokenUrl(platformUrl) {
|
|
23
|
-
return `${platformUrl.replace(/\/+$/, "")}/tools/oauth/device/token`;
|
|
24
|
-
}
|
|
25
|
-
function retryAfterSecondsFrom(response) {
|
|
26
|
-
const header = response.headers.get("retry-after");
|
|
27
|
-
const seconds = header ? Number(header) : undefined;
|
|
28
|
-
return Number.isFinite(seconds) ? seconds : undefined;
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Requests a device code. Mirrors `exchange.ts`'s `exchangeCode` in shape:
|
|
32
|
-
* every failure is a distinct `DeviceError` kind, a 429 carries
|
|
33
|
-
* `Retry-After` when the platform sent one, and a 200 whose body is not
|
|
34
|
-
* valid JSON or is missing a required field is `malformed` rather than
|
|
35
|
-
* handing the caller `undefined` fields.
|
|
36
|
-
*/
|
|
37
|
-
export async function requestDeviceCode({ platformUrl, label, callbackUri, fetchImpl = fetch, }) {
|
|
38
|
-
const url = deviceCodeUrl(platformUrl);
|
|
39
|
-
let response;
|
|
40
|
-
try {
|
|
41
|
-
response = await fetchImpl(url, {
|
|
42
|
-
method: "POST",
|
|
43
|
-
headers: { "content-type": "application/json" },
|
|
44
|
-
body: JSON.stringify({ label, callback_uri: callbackUri }),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
throw new DeviceError("network", `Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
49
|
-
}
|
|
50
|
-
if (response.status === 429) {
|
|
51
|
-
const retryAfterSeconds = retryAfterSecondsFrom(response);
|
|
52
|
-
throw new DeviceError("rate_limited", `${url} is rate-limited` +
|
|
53
|
-
(retryAfterSeconds !== undefined
|
|
54
|
-
? ` — retry after ${retryAfterSeconds}s.`
|
|
55
|
-
: "."), retryAfterSeconds);
|
|
56
|
-
}
|
|
57
|
-
let body;
|
|
58
|
-
try {
|
|
59
|
-
body = await response.json();
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
throw new DeviceError("malformed", `${url} did not return valid JSON (status ${response.status}).`);
|
|
63
|
-
}
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
const errBody = (body ?? {});
|
|
66
|
-
const description = typeof errBody.error_description === "string"
|
|
67
|
-
? errBody.error_description
|
|
68
|
-
: undefined;
|
|
69
|
-
const code = typeof errBody.error === "string"
|
|
70
|
-
? errBody.error
|
|
71
|
-
: `HTTP ${response.status}`;
|
|
72
|
-
throw new DeviceError("invalid_request", description ? `${code}: ${description}` : code);
|
|
73
|
-
}
|
|
74
|
-
const result = (body ?? {});
|
|
75
|
-
if (typeof result.device_code !== "string" ||
|
|
76
|
-
typeof result.user_code !== "string" ||
|
|
77
|
-
typeof result.verification_uri !== "string" ||
|
|
78
|
-
typeof result.verification_uri_complete !== "string" ||
|
|
79
|
-
typeof result.expires_in !== "number" ||
|
|
80
|
-
typeof result.interval !== "number") {
|
|
81
|
-
throw new DeviceError("malformed", `${url} returned 200 but was missing a required device-code field.`);
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
deviceCode: result.device_code,
|
|
85
|
-
userCode: result.user_code,
|
|
86
|
-
verificationUri: result.verification_uri,
|
|
87
|
-
verificationUriComplete: result.verification_uri_complete,
|
|
88
|
-
expiresIn: result.expires_in,
|
|
89
|
-
interval: result.interval,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/** Network failures while polling get this many retries before `pollForToken` gives up. */
|
|
93
|
-
const MAX_NETWORK_RETRIES = 3;
|
|
94
|
-
/** Exponential backoff for a network retry, capped well under the poll interval's own cadence. */
|
|
95
|
-
function networkBackoffSeconds(attempt) {
|
|
96
|
-
return Math.min(2 ** attempt, 30);
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Polls `.../tools/oauth/device/token` on the server's cadence until the
|
|
100
|
-
* user approves (or denies) the request in their browser, the device code
|
|
101
|
-
* expires, or the server rejects it outright.
|
|
102
|
-
*
|
|
103
|
-
* `authorization_pending` keeps polling at the current interval;
|
|
104
|
-
* `slow_down` adds 5 seconds to it and keeps polling (per the wire
|
|
105
|
-
* contract); a 429 waits out `Retry-After` (or the current interval, if the
|
|
106
|
-
* platform sent none) and keeps polling; every other error is terminal.
|
|
107
|
-
* Local expiry (`now() - start >= expiresInSeconds`) is reported the same
|
|
108
|
-
* way as the server's own `expired_token` — the caller does not need to
|
|
109
|
-
* tell the two apart.
|
|
110
|
-
*
|
|
111
|
-
* A network failure while polling retries with backoff up to
|
|
112
|
-
* `MAX_NETWORK_RETRIES` times before it is reported as `PollError("network")`.
|
|
113
|
-
*/
|
|
114
|
-
export async function pollForToken({ platformUrl, deviceCode, intervalSeconds, expiresInSeconds, fetchImpl = fetch, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), now = () => Date.now(), }) {
|
|
115
|
-
const url = deviceTokenUrl(platformUrl);
|
|
116
|
-
const deadline = now() + expiresInSeconds * 1000;
|
|
117
|
-
let interval = intervalSeconds;
|
|
118
|
-
let networkFailures = 0;
|
|
119
|
-
// Set only by a branch that already slept its own wait (a network retry's
|
|
120
|
-
// backoff, or a 429's Retry-After) — skips the ordinary top-of-loop
|
|
121
|
-
// interval sleep so those waits are not doubled up with it.
|
|
122
|
-
let skipIntervalSleep = false;
|
|
123
|
-
while (true) {
|
|
124
|
-
if (now() >= deadline) {
|
|
125
|
-
throw new PollError("expired_token", "The device code expired before it was approved. Run the command again.");
|
|
126
|
-
}
|
|
127
|
-
if (!skipIntervalSleep)
|
|
128
|
-
await sleep(interval * 1000);
|
|
129
|
-
skipIntervalSleep = false;
|
|
130
|
-
let response;
|
|
131
|
-
try {
|
|
132
|
-
response = await fetchImpl(url, {
|
|
133
|
-
method: "POST",
|
|
134
|
-
headers: { "content-type": "application/json" },
|
|
135
|
-
body: JSON.stringify({ device_code: deviceCode }),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
catch (err) {
|
|
139
|
-
networkFailures += 1;
|
|
140
|
-
if (networkFailures > MAX_NETWORK_RETRIES) {
|
|
141
|
-
throw new PollError("network", `Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
-
}
|
|
143
|
-
await sleep(networkBackoffSeconds(networkFailures) * 1000);
|
|
144
|
-
skipIntervalSleep = true;
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
networkFailures = 0;
|
|
148
|
-
if (response.status === 429) {
|
|
149
|
-
const retryAfterSeconds = retryAfterSecondsFrom(response);
|
|
150
|
-
await sleep((retryAfterSeconds ?? interval) * 1000);
|
|
151
|
-
skipIntervalSleep = true;
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
let body;
|
|
155
|
-
try {
|
|
156
|
-
body = await response.json();
|
|
157
|
-
}
|
|
158
|
-
catch {
|
|
159
|
-
throw new PollError("malformed", `${url} did not return valid JSON (status ${response.status}).`);
|
|
160
|
-
}
|
|
161
|
-
if (response.ok) {
|
|
162
|
-
const result = (body ?? {});
|
|
163
|
-
if (typeof result.client_id !== "string" ||
|
|
164
|
-
typeof result.client_secret !== "string" ||
|
|
165
|
-
typeof result.issuer !== "string") {
|
|
166
|
-
throw new PollError("malformed", `${url} returned 200 but was missing client_id, client_secret, or issuer.`);
|
|
167
|
-
}
|
|
168
|
-
return {
|
|
169
|
-
clientId: result.client_id,
|
|
170
|
-
clientSecret: result.client_secret,
|
|
171
|
-
issuer: result.issuer,
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
const errBody = (body ?? {});
|
|
175
|
-
const errorCode = typeof errBody.error === "string" ? errBody.error : undefined;
|
|
176
|
-
const description = typeof errBody.error_description === "string"
|
|
177
|
-
? errBody.error_description
|
|
178
|
-
: undefined;
|
|
179
|
-
switch (errorCode) {
|
|
180
|
-
case "authorization_pending":
|
|
181
|
-
continue;
|
|
182
|
-
case "slow_down":
|
|
183
|
-
interval += 5;
|
|
184
|
-
continue;
|
|
185
|
-
case "access_denied":
|
|
186
|
-
throw new PollError("access_denied", description ?? "Connection declined in the browser.");
|
|
187
|
-
case "expired_token":
|
|
188
|
-
throw new PollError("expired_token", description ?? "The device code expired. Run the command again.");
|
|
189
|
-
case "invalid_grant":
|
|
190
|
-
throw new PollError("invalid_grant", description ?? "invalid_grant");
|
|
191
|
-
case "invalid_request":
|
|
192
|
-
default: {
|
|
193
|
-
const code = errorCode ?? `HTTP ${response.status}`;
|
|
194
|
-
throw new PollError("invalid_request", description ? `${code}: ${description}` : code);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OIDC discovery for the DevDogs OAuth server.
|
|
3
|
-
*
|
|
4
|
-
* The wizard used to build the issuer it registered with Supabase as
|
|
5
|
-
* `${baseUrl}/auth/v1` — an assumption, not a fact. Supabase Auth's own
|
|
6
|
-
* discovery document at that same base advertises a DIFFERENT issuer (its
|
|
7
|
-
* project ref host, e.g. `https://<ref>.supabase.co/auth/v1`, rather than
|
|
8
|
-
* the custom domain a caller reaches it through), and Supabase's OIDC
|
|
9
|
-
* provider refuses to register a custom provider whose declared issuer
|
|
10
|
-
* disagrees with what discovery reports for it. Reading the issuer FROM
|
|
11
|
-
* discovery, rather than constructing it, keeps this correct regardless of
|
|
12
|
-
* which host the platform's own discovery document happens to advertise —
|
|
13
|
-
* including once the platform starts advertising its custom domain instead
|
|
14
|
-
* of the underlying project host.
|
|
15
|
-
*/
|
|
16
|
-
/** Thrown when discovery cannot be fetched or does not shape up as expected. */
|
|
17
|
-
export declare class DiscoveryError extends Error {
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Fetches `${baseUrl}/auth/v1/.well-known/openid-configuration` and returns
|
|
21
|
-
* the `issuer` it advertises.
|
|
22
|
-
*
|
|
23
|
-
* Every failure names the discovery URL it was trying to reach — a network
|
|
24
|
-
* failure, a non-2xx response, unparsable JSON, and a missing/non-string
|
|
25
|
-
* `issuer` field are each reported distinctly, so the wizard can print
|
|
26
|
-
* something more useful than "fetch failed".
|
|
27
|
-
*/
|
|
28
|
-
export declare function fetchIssuer(baseUrl: string): Promise<string>;
|
package/dist/oauth/discovery.js
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* OIDC discovery for the DevDogs OAuth server.
|
|
3
|
-
*
|
|
4
|
-
* The wizard used to build the issuer it registered with Supabase as
|
|
5
|
-
* `${baseUrl}/auth/v1` — an assumption, not a fact. Supabase Auth's own
|
|
6
|
-
* discovery document at that same base advertises a DIFFERENT issuer (its
|
|
7
|
-
* project ref host, e.g. `https://<ref>.supabase.co/auth/v1`, rather than
|
|
8
|
-
* the custom domain a caller reaches it through), and Supabase's OIDC
|
|
9
|
-
* provider refuses to register a custom provider whose declared issuer
|
|
10
|
-
* disagrees with what discovery reports for it. Reading the issuer FROM
|
|
11
|
-
* discovery, rather than constructing it, keeps this correct regardless of
|
|
12
|
-
* which host the platform's own discovery document happens to advertise —
|
|
13
|
-
* including once the platform starts advertising its custom domain instead
|
|
14
|
-
* of the underlying project host.
|
|
15
|
-
*/
|
|
16
|
-
/** Thrown when discovery cannot be fetched or does not shape up as expected. */
|
|
17
|
-
export class DiscoveryError extends Error {
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Fetches `${baseUrl}/auth/v1/.well-known/openid-configuration` and returns
|
|
21
|
-
* the `issuer` it advertises.
|
|
22
|
-
*
|
|
23
|
-
* Every failure names the discovery URL it was trying to reach — a network
|
|
24
|
-
* failure, a non-2xx response, unparsable JSON, and a missing/non-string
|
|
25
|
-
* `issuer` field are each reported distinctly, so the wizard can print
|
|
26
|
-
* something more useful than "fetch failed".
|
|
27
|
-
*/
|
|
28
|
-
export async function fetchIssuer(baseUrl) {
|
|
29
|
-
const discoveryUrl = `${baseUrl}/auth/v1/.well-known/openid-configuration`;
|
|
30
|
-
let response;
|
|
31
|
-
try {
|
|
32
|
-
response = await fetch(discoveryUrl);
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
throw new DiscoveryError(`Could not reach ${discoveryUrl}: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
-
}
|
|
37
|
-
if (!response.ok) {
|
|
38
|
-
throw new DiscoveryError(`${discoveryUrl} returned ${response.status} ${response.statusText}`);
|
|
39
|
-
}
|
|
40
|
-
let body;
|
|
41
|
-
try {
|
|
42
|
-
body = await response.json();
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
throw new DiscoveryError(`${discoveryUrl} did not return valid JSON`);
|
|
46
|
-
}
|
|
47
|
-
const issuer = body && typeof body === "object"
|
|
48
|
-
? body.issuer
|
|
49
|
-
: undefined;
|
|
50
|
-
if (typeof issuer !== "string" || issuer.length === 0) {
|
|
51
|
-
throw new DiscoveryError(`${discoveryUrl} response had no "issuer" field`);
|
|
52
|
-
}
|
|
53
|
-
return issuer;
|
|
54
|
-
}
|
package/dist/oauth/env-file.d.ts
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Writes `values` into `.env.local` in `cwd`, updating any existing
|
|
3
|
-
* `KEY=...` lines in place and appending keys that aren't present yet.
|
|
4
|
-
* Leaves all other lines (comments, unrelated vars) untouched.
|
|
5
|
-
*/
|
|
6
|
-
export declare function upsertEnvLocal(cwd: string, values: Record<string, string>): void;
|
package/dist/oauth/env-file.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
-
import { join } from "node:path";
|
|
3
|
-
/**
|
|
4
|
-
* Writes `values` into `.env.local` in `cwd`, updating any existing
|
|
5
|
-
* `KEY=...` lines in place and appending keys that aren't present yet.
|
|
6
|
-
* Leaves all other lines (comments, unrelated vars) untouched.
|
|
7
|
-
*/
|
|
8
|
-
export function upsertEnvLocal(cwd, values) {
|
|
9
|
-
const path = join(cwd, ".env.local");
|
|
10
|
-
const existing = existsSync(path) ? readFileSync(path, "utf-8") : "";
|
|
11
|
-
const lines = existing.length > 0 ? existing.split("\n") : [];
|
|
12
|
-
const remaining = new Map(Object.entries(values));
|
|
13
|
-
const updated = lines.map((line) => {
|
|
14
|
-
const match = /^([A-Za-z_][A-Za-z0-9_]*)=/.exec(line);
|
|
15
|
-
if (!match)
|
|
16
|
-
return line;
|
|
17
|
-
// The regex has exactly one capture group, so a match always fills it;
|
|
18
|
-
// noUncheckedIndexedAccess can't see that, hence the explicit guard.
|
|
19
|
-
const key = match[1];
|
|
20
|
-
if (key === undefined)
|
|
21
|
-
return line;
|
|
22
|
-
const value = remaining.get(key);
|
|
23
|
-
if (value === undefined)
|
|
24
|
-
return line;
|
|
25
|
-
remaining.delete(key);
|
|
26
|
-
return `${key}=${formatValue(value)}`;
|
|
27
|
-
});
|
|
28
|
-
for (const [key, value] of remaining) {
|
|
29
|
-
updated.push(`${key}=${formatValue(value)}`);
|
|
30
|
-
}
|
|
31
|
-
writeFileSync(path, `${updated.join("\n").replace(/\n*$/, "")}\n`);
|
|
32
|
-
}
|
|
33
|
-
function formatValue(value) {
|
|
34
|
-
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
35
|
-
}
|
package/dist/oauth/exchange.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The token exchange half of the one-click connect flow: trades the
|
|
3
|
-
* authorization `code` the loopback listener caught, plus the PKCE
|
|
4
|
-
* verifier that matches the challenge sent up front, for the OAuth client
|
|
5
|
-
* credentials the platform minted — see `commands.ts`'s `oauth` node and
|
|
6
|
-
* this repo's launch instructions for the full wire contract this speaks:
|
|
7
|
-
*
|
|
8
|
-
* POST <platform>/tools/oauth/connect/exchange
|
|
9
|
-
* { "code", "code_verifier" }
|
|
10
|
-
*
|
|
11
|
-
* 200 { "client_id", "client_secret", "issuer" }
|
|
12
|
-
* 400 { "error": "invalid_request" | "invalid_grant", "error_description" }
|
|
13
|
-
* 429 { "error": "rate_limited" } (+ Retry-After header)
|
|
14
|
-
*/
|
|
15
|
-
export type ExchangeErrorKind = "invalid_request" | "invalid_grant" | "rate_limited" | "network" | "malformed";
|
|
16
|
-
export declare class ExchangeError extends Error {
|
|
17
|
-
readonly kind: ExchangeErrorKind;
|
|
18
|
-
readonly retryAfterSeconds?: number | undefined;
|
|
19
|
-
constructor(kind: ExchangeErrorKind, message: string, retryAfterSeconds?: number | undefined);
|
|
20
|
-
}
|
|
21
|
-
export interface ExchangeResult {
|
|
22
|
-
clientId: string;
|
|
23
|
-
clientSecret: string;
|
|
24
|
-
issuer: string;
|
|
25
|
-
}
|
|
26
|
-
/**
|
|
27
|
-
* Exchanges `code` + `codeVerifier` for OAuth client credentials.
|
|
28
|
-
*
|
|
29
|
-
* Every failure is a distinct `ExchangeError` kind so the caller (the
|
|
30
|
-
* wizard) can print something specific rather than a bare "request
|
|
31
|
-
* failed": a network failure names the URL and underlying error, a 429
|
|
32
|
-
* carries `Retry-After` when the platform sent one, a 400 distinguishes
|
|
33
|
-
* `invalid_grant` (the code was wrong, expired, or already used — the
|
|
34
|
-
* ordinary "try the flow again" case) from any other `invalid_request`, and
|
|
35
|
-
* a 200 with a body that is not valid JSON or is missing a required field
|
|
36
|
-
* is reported as `malformed` rather than silently returning `undefined`
|
|
37
|
-
* fields to the caller.
|
|
38
|
-
*/
|
|
39
|
-
export declare function exchangeCode({ platformUrl, code, codeVerifier, }: {
|
|
40
|
-
platformUrl: string;
|
|
41
|
-
code: string;
|
|
42
|
-
codeVerifier: string;
|
|
43
|
-
}): Promise<ExchangeResult>;
|
package/dist/oauth/exchange.js
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The token exchange half of the one-click connect flow: trades the
|
|
3
|
-
* authorization `code` the loopback listener caught, plus the PKCE
|
|
4
|
-
* verifier that matches the challenge sent up front, for the OAuth client
|
|
5
|
-
* credentials the platform minted — see `commands.ts`'s `oauth` node and
|
|
6
|
-
* this repo's launch instructions for the full wire contract this speaks:
|
|
7
|
-
*
|
|
8
|
-
* POST <platform>/tools/oauth/connect/exchange
|
|
9
|
-
* { "code", "code_verifier" }
|
|
10
|
-
*
|
|
11
|
-
* 200 { "client_id", "client_secret", "issuer" }
|
|
12
|
-
* 400 { "error": "invalid_request" | "invalid_grant", "error_description" }
|
|
13
|
-
* 429 { "error": "rate_limited" } (+ Retry-After header)
|
|
14
|
-
*/
|
|
15
|
-
export class ExchangeError extends Error {
|
|
16
|
-
kind;
|
|
17
|
-
retryAfterSeconds;
|
|
18
|
-
constructor(kind, message, retryAfterSeconds) {
|
|
19
|
-
super(message);
|
|
20
|
-
this.kind = kind;
|
|
21
|
-
this.retryAfterSeconds = retryAfterSeconds;
|
|
22
|
-
this.name = "ExchangeError";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
function exchangeUrl(platformUrl) {
|
|
26
|
-
return `${platformUrl.replace(/\/+$/, "")}/tools/oauth/connect/exchange`;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Exchanges `code` + `codeVerifier` for OAuth client credentials.
|
|
30
|
-
*
|
|
31
|
-
* Every failure is a distinct `ExchangeError` kind so the caller (the
|
|
32
|
-
* wizard) can print something specific rather than a bare "request
|
|
33
|
-
* failed": a network failure names the URL and underlying error, a 429
|
|
34
|
-
* carries `Retry-After` when the platform sent one, a 400 distinguishes
|
|
35
|
-
* `invalid_grant` (the code was wrong, expired, or already used — the
|
|
36
|
-
* ordinary "try the flow again" case) from any other `invalid_request`, and
|
|
37
|
-
* a 200 with a body that is not valid JSON or is missing a required field
|
|
38
|
-
* is reported as `malformed` rather than silently returning `undefined`
|
|
39
|
-
* fields to the caller.
|
|
40
|
-
*/
|
|
41
|
-
export async function exchangeCode({ platformUrl, code, codeVerifier, }) {
|
|
42
|
-
const url = exchangeUrl(platformUrl);
|
|
43
|
-
let response;
|
|
44
|
-
try {
|
|
45
|
-
response = await fetch(url, {
|
|
46
|
-
method: "POST",
|
|
47
|
-
headers: { "content-type": "application/json" },
|
|
48
|
-
body: JSON.stringify({ code, code_verifier: codeVerifier }),
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
throw new ExchangeError("network", `Could not reach ${url}: ${err instanceof Error ? err.message : String(err)}`);
|
|
53
|
-
}
|
|
54
|
-
if (response.status === 429) {
|
|
55
|
-
const retryAfterHeader = response.headers.get("retry-after");
|
|
56
|
-
const retryAfterSeconds = retryAfterHeader
|
|
57
|
-
? Number(retryAfterHeader)
|
|
58
|
-
: undefined;
|
|
59
|
-
throw new ExchangeError("rate_limited", `${url} is rate-limited` +
|
|
60
|
-
(Number.isFinite(retryAfterSeconds)
|
|
61
|
-
? ` — retry after ${retryAfterSeconds}s.`
|
|
62
|
-
: "."), Number.isFinite(retryAfterSeconds) ? retryAfterSeconds : undefined);
|
|
63
|
-
}
|
|
64
|
-
let body;
|
|
65
|
-
try {
|
|
66
|
-
body = await response.json();
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
throw new ExchangeError("malformed", `${url} did not return valid JSON (status ${response.status}).`);
|
|
70
|
-
}
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
const errBody = (body ?? {});
|
|
73
|
-
const kind = errBody.error === "invalid_grant" ? "invalid_grant" : "invalid_request";
|
|
74
|
-
const description = typeof errBody.error_description === "string"
|
|
75
|
-
? errBody.error_description
|
|
76
|
-
: undefined;
|
|
77
|
-
const code = typeof errBody.error === "string"
|
|
78
|
-
? errBody.error
|
|
79
|
-
: `HTTP ${response.status}`;
|
|
80
|
-
throw new ExchangeError(kind, description ? `${code}: ${description}` : code);
|
|
81
|
-
}
|
|
82
|
-
const result = (body ?? {});
|
|
83
|
-
if (typeof result.client_id !== "string" ||
|
|
84
|
-
typeof result.client_secret !== "string" ||
|
|
85
|
-
typeof result.issuer !== "string") {
|
|
86
|
-
throw new ExchangeError("malformed", `${url} returned 200 but was missing client_id, client_secret, or issuer.`);
|
|
87
|
-
}
|
|
88
|
-
return {
|
|
89
|
-
clientId: result.client_id,
|
|
90
|
-
clientSecret: result.client_secret,
|
|
91
|
-
issuer: result.issuer,
|
|
92
|
-
};
|
|
93
|
-
}
|
package/dist/oauth/label.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function defaultLabel(cwd: string): string;
|