@vibe-commit/capture 0.1.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/LICENSE +21 -0
- package/README.md +66 -0
- package/dist/agents/claude_code.d.ts +11 -0
- package/dist/agents/claude_code.js +162 -0
- package/dist/agents/claude_code.js.map +1 -0
- package/dist/agents/codex_cli.d.ts +15 -0
- package/dist/agents/codex_cli.js +67 -0
- package/dist/agents/codex_cli.js.map +1 -0
- package/dist/agents/cursor.d.ts +21 -0
- package/dist/agents/cursor.js +89 -0
- package/dist/agents/cursor.js.map +1 -0
- package/dist/agents/registry.d.ts +184 -0
- package/dist/agents/registry.js +212 -0
- package/dist/agents/registry.js.map +1 -0
- package/dist/agents/types.d.ts +279 -0
- package/dist/agents/types.js +67 -0
- package/dist/agents/types.js.map +1 -0
- package/dist/commands/connect.d.ts +36 -0
- package/dist/commands/connect.js +499 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/context.d.ts +45 -0
- package/dist/commands/context.js +14 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/report.d.ts +105 -0
- package/dist/commands/report.js +430 -0
- package/dist/commands/report.js.map +1 -0
- package/dist/commands/status.d.ts +29 -0
- package/dist/commands/status.js +236 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/why.d.ts +349 -0
- package/dist/commands/why.js +937 -0
- package/dist/commands/why.js.map +1 -0
- package/dist/consent.d.ts +59 -0
- package/dist/consent.js +111 -0
- package/dist/consent.js.map +1 -0
- package/dist/copy/claims.d.ts +79 -0
- package/dist/copy/claims.js +343 -0
- package/dist/copy/claims.js.map +1 -0
- package/dist/copy/grades.d.ts +117 -0
- package/dist/copy/grades.js +96 -0
- package/dist/copy/grades.js.map +1 -0
- package/dist/copy/index.d.ts +12 -0
- package/dist/copy/index.js +13 -0
- package/dist/copy/index.js.map +1 -0
- package/dist/copy/strings.d.ts +1328 -0
- package/dist/copy/strings.js +1243 -0
- package/dist/copy/strings.js.map +1 -0
- package/dist/copy/time.d.ts +27 -0
- package/dist/copy/time.js +43 -0
- package/dist/copy/time.js.map +1 -0
- package/dist/credential.d.ts +211 -0
- package/dist/credential.js +298 -0
- package/dist/credential.js.map +1 -0
- package/dist/exit.d.ts +59 -0
- package/dist/exit.js +60 -0
- package/dist/exit.js.map +1 -0
- package/dist/git.d.ts +299 -0
- package/dist/git.js +653 -0
- package/dist/git.js.map +1 -0
- package/dist/hooks/entry.d.ts +277 -0
- package/dist/hooks/entry.js +1092 -0
- package/dist/hooks/entry.js.map +1 -0
- package/dist/hooks/post_commit.d.ts +59 -0
- package/dist/hooks/post_commit.js +160 -0
- package/dist/hooks/post_commit.js.map +1 -0
- package/dist/hooks/post_rewrite.d.ts +59 -0
- package/dist/hooks/post_rewrite.js +161 -0
- package/dist/hooks/post_rewrite.js.map +1 -0
- package/dist/hooks/session_end.d.ts +101 -0
- package/dist/hooks/session_end.js +120 -0
- package/dist/hooks/session_end.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +360 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +153 -0
- package/dist/install.js +284 -0
- package/dist/install.js.map +1 -0
- package/dist/json.d.ts +100 -0
- package/dist/json.js +94 -0
- package/dist/json.js.map +1 -0
- package/dist/oauth/discovery.d.ts +96 -0
- package/dist/oauth/discovery.js +163 -0
- package/dist/oauth/discovery.js.map +1 -0
- package/dist/oauth/lock.d.ts +100 -0
- package/dist/oauth/lock.js +287 -0
- package/dist/oauth/lock.js.map +1 -0
- package/dist/oauth/loopback.d.ts +85 -0
- package/dist/oauth/loopback.js +124 -0
- package/dist/oauth/loopback.js.map +1 -0
- package/dist/oauth/pkce.d.ts +58 -0
- package/dist/oauth/pkce.js +77 -0
- package/dist/oauth/pkce.js.map +1 -0
- package/dist/oauth/read.d.ts +120 -0
- package/dist/oauth/read.js +144 -0
- package/dist/oauth/read.js.map +1 -0
- package/dist/oauth/session.d.ts +80 -0
- package/dist/oauth/session.js +112 -0
- package/dist/oauth/session.js.map +1 -0
- package/dist/oauth/signin.d.ts +178 -0
- package/dist/oauth/signin.js +225 -0
- package/dist/oauth/signin.js.map +1 -0
- package/dist/oauth/token.d.ts +139 -0
- package/dist/oauth/token.js +183 -0
- package/dist/oauth/token.js.map +1 -0
- package/dist/paths.d.ts +366 -0
- package/dist/paths.js +460 -0
- package/dist/paths.js.map +1 -0
- package/dist/policy.d.ts +195 -0
- package/dist/policy.js +244 -0
- package/dist/policy.js.map +1 -0
- package/dist/post.d.ts +302 -0
- package/dist/post.js +384 -0
- package/dist/post.js.map +1 -0
- package/dist/project.d.ts +99 -0
- package/dist/project.js +192 -0
- package/dist/project.js.map +1 -0
- package/dist/redact.d.ts +175 -0
- package/dist/redact.js +700 -0
- package/dist/redact.js.map +1 -0
- package/dist/runtime.d.ts +30 -0
- package/dist/runtime.js +37 -0
- package/dist/runtime.js.map +1 -0
- package/dist/spawn_budget.d.ts +101 -0
- package/dist/spawn_budget.js +124 -0
- package/dist/spawn_budget.js.map +1 -0
- package/dist/spool.d.ts +390 -0
- package/dist/spool.js +556 -0
- package/dist/spool.js.map +1 -0
- package/dist/state.d.ts +109 -0
- package/dist/state.js +217 -0
- package/dist/state.js.map +1 -0
- package/dist/system_message.d.ts +101 -0
- package/dist/system_message.js +141 -0
- package/dist/system_message.js.map +1 -0
- package/dist/term.d.ts +112 -0
- package/dist/term.js +134 -0
- package/dist/term.js.map +1 -0
- package/dist/version.d.ts +16 -0
- package/dist/version.js +17 -0
- package/dist/version.js.map +1 -0
- package/package.json +50 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/oauth/token` client — both grants.
|
|
3
|
+
*
|
|
4
|
+
* RFC 6749 §4.1.3 (`authorization_code`) and §6 (`refresh_token`), posted
|
|
5
|
+
* form-encoded, which is the shape the server prefers and parses first. No
|
|
6
|
+
* client secret: this is a PUBLIC client (the server advertises
|
|
7
|
+
* `token_endpoint_auth_methods_supported: ["none"]`), and a secret shipped
|
|
8
|
+
* inside a CLI on a developer's disk is not a secret.
|
|
9
|
+
*
|
|
10
|
+
* ## ⚠ THE CLIENT ID IS NOT REGISTERED SERVER-SIDE YET
|
|
11
|
+
*
|
|
12
|
+
* `vibecommit-mcp`'s `REGISTERED_CLIENT_IDS` is a hard-coded membership set
|
|
13
|
+
* (RFC 7591 dynamic registration is deferred) holding ten entries — `claude-ai`,
|
|
14
|
+
* `cursor-mcp`, `vscode-copilot`, `codex-cli`, `windsurf-mcp`, `cline`,
|
|
15
|
+
* `continue-dev`, `roo-code`, `kiro`, `mcp-inspector` — and **none of them is
|
|
16
|
+
* this CLI**. `isRegisteredClient` gates BOTH `/oauth/authorize` and
|
|
17
|
+
* `/oauth/token`, so against the live server every sign-in below returns
|
|
18
|
+
* `unauthorized_client` until `CLIENT_ID` below is added to that list.
|
|
19
|
+
*
|
|
20
|
+
* That is a one-line server-side change in another repo, and it is REPORTED
|
|
21
|
+
* rather than worked around: the two alternatives are both worse. Borrowing
|
|
22
|
+
* `mcp-inspector` would ship a dev/testing identity in a released binary, and
|
|
23
|
+
* borrowing `claude-ai` would make this client indistinguishable from Claude
|
|
24
|
+
* Code in the audit trail of a product whose entire value is an audit trail.
|
|
25
|
+
*
|
|
26
|
+
* One constant, named, so the registration is a one-line change on this side too.
|
|
27
|
+
*
|
|
28
|
+
* ⚠ AND THE NAME IS `capture-cli` RATHER THAN THE OBVIOUS PRODUCT-PREFIXED ONE.
|
|
29
|
+
* Every sibling repo in this project is named with that product prefix, and
|
|
30
|
+
* `test/provenance.test.ts`'s `REPO_TOKEN` matches exactly that shape to find
|
|
31
|
+
* closed-core references — so a client id built the obvious way is read by the
|
|
32
|
+
* licensing gate as a reference to a closed sibling repo that does not exist.
|
|
33
|
+
* The collision is a real signal rather than a tooling annoyance: an OAuth
|
|
34
|
+
* client id indistinguishable from a repository name is ambiguous to a human
|
|
35
|
+
* reader too. `capture-cli` names the package (`@vibe-commit/capture`) and
|
|
36
|
+
* matches the server's own convention for the ids beside it (`codex-cli`).
|
|
37
|
+
*
|
|
38
|
+
* @provenance vibecommit-mcp src/oauth/token.ts — both grants, request + response shapes, retyped
|
|
39
|
+
* @provenance vibecommit-mcp src/transport/server.ts — form-encoded body parse, retyped
|
|
40
|
+
* @provenance vibecommit-mcp src/db/supabase.ts — the registered-client gate, retyped
|
|
41
|
+
*/
|
|
42
|
+
import { AccessToken, RefreshToken } from "../credential.js";
|
|
43
|
+
/** This CLI's OAuth client identity. ⚠ Not yet in the server's set — see above. */
|
|
44
|
+
export declare const CLIENT_ID = "capture-cli";
|
|
45
|
+
/** The only scope the server registers. */
|
|
46
|
+
export declare const SCOPE = "mcp:tools";
|
|
47
|
+
/**
|
|
48
|
+
* Refresh this many milliseconds BEFORE the access token actually expires.
|
|
49
|
+
*
|
|
50
|
+
* Without a margin, a read starting at t=899s on a 900s token races its own
|
|
51
|
+
* round trip and fails with a 401 it could have avoided. 60s comfortably exceeds
|
|
52
|
+
* both the request budget and any plausible clock disagreement between this
|
|
53
|
+
* machine and the issuer, and costs one extra refresh per 15 minutes of
|
|
54
|
+
* continuous use.
|
|
55
|
+
*/
|
|
56
|
+
export declare const EXPIRY_SKEW_MS = 60000;
|
|
57
|
+
/** A live token pair, both halves opaque, with the expiry carried as DATA. */
|
|
58
|
+
export interface TokenSet {
|
|
59
|
+
readonly access: AccessToken;
|
|
60
|
+
readonly refresh: RefreshToken;
|
|
61
|
+
/** Epoch ms. From `expires_in`, never from parsing the JWT — see `AccessToken`. */
|
|
62
|
+
readonly expiresAtMs: number;
|
|
63
|
+
readonly scope: string;
|
|
64
|
+
}
|
|
65
|
+
export type TokenOutcome = {
|
|
66
|
+
readonly kind: "ok";
|
|
67
|
+
readonly tokens: TokenSet;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The server answered with an RFC 6749 §5.2 error. A FIRST-CLASS OUTCOME and
|
|
71
|
+
* not an exception: `invalid_grant` on the refresh path is the ordinary way a
|
|
72
|
+
* user learns their session ended, and `unauthorized_client` is how this
|
|
73
|
+
* client will learn its id was never registered.
|
|
74
|
+
*/
|
|
75
|
+
| {
|
|
76
|
+
readonly kind: "denied";
|
|
77
|
+
readonly error: string;
|
|
78
|
+
}
|
|
79
|
+
/** No response at all — DNS, refused, TLS, timeout. Not an HTTP status. */
|
|
80
|
+
| {
|
|
81
|
+
readonly kind: "unreachable";
|
|
82
|
+
}
|
|
83
|
+
/** A 2xx whose body is not a token response. Never treated as success. */
|
|
84
|
+
| {
|
|
85
|
+
readonly kind: "malformed";
|
|
86
|
+
};
|
|
87
|
+
export interface TokenDeps {
|
|
88
|
+
readonly fetch: typeof fetch;
|
|
89
|
+
readonly timeoutMs: number;
|
|
90
|
+
/** Epoch ms. Injected so the expiry arithmetic is reachable from a test. */
|
|
91
|
+
readonly nowMs: number;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Turn a token response into two opaque tokens, or refuse it.
|
|
95
|
+
*
|
|
96
|
+
* ⚠ Both halves are CLASS-CHECKED before they are wrapped, and this is the
|
|
97
|
+
* mirror of the ingest lane's `wrong-class` refusal rather than belt-and-braces.
|
|
98
|
+
* A response that put the JWT in `refresh_token` would otherwise be stored and
|
|
99
|
+
* then presented to the refresh grant on every subsequent read, and the symptom
|
|
100
|
+
* — `invalid_grant`, a signed-out user — names neither the cause nor the field.
|
|
101
|
+
* Refusing at the boundary makes the fault land where it happened.
|
|
102
|
+
*
|
|
103
|
+
* Exported so a test can drive known-bad bodies at it without a socket.
|
|
104
|
+
*/
|
|
105
|
+
export declare function parseTokenResponse(body: unknown, nowMs: number): TokenOutcome;
|
|
106
|
+
/**
|
|
107
|
+
* `grant_type=authorization_code`.
|
|
108
|
+
*
|
|
109
|
+
* `redirect_uri` is sent again and MUST be byte-identical to the one presented
|
|
110
|
+
* at authorize — the server compares them (`candidate.redirect_uri !==
|
|
111
|
+
* input.redirectUri` → `invalid_grant`). That is the second place the
|
|
112
|
+
* `127.0.0.1`-vs-`localhost` spelling decides whether this works.
|
|
113
|
+
*/
|
|
114
|
+
export declare function exchangeCode(tokenEndpoint: string, input: {
|
|
115
|
+
code: string;
|
|
116
|
+
codeVerifier: string;
|
|
117
|
+
redirectUri: string;
|
|
118
|
+
}, deps: TokenDeps): Promise<TokenOutcome>;
|
|
119
|
+
/**
|
|
120
|
+
* `grant_type=refresh_token` — rotation. The response carries a NEW refresh
|
|
121
|
+
* token and the old one is dead the instant this succeeds.
|
|
122
|
+
*
|
|
123
|
+
* ⚠ CALL THIS ONLY UNDER `withRefreshLock`. Presenting a token that has already
|
|
124
|
+
* been rotated revokes the whole family server-side. `lock.ts`'s module note is
|
|
125
|
+
* the long version; this is the line that stops a reader wiring it up directly.
|
|
126
|
+
*/
|
|
127
|
+
export declare function refreshGrant(tokenEndpoint: string, refresh: RefreshToken, deps: TokenDeps): Promise<TokenOutcome>;
|
|
128
|
+
/**
|
|
129
|
+
* The authorize URL, built from the DISCOVERED endpoint.
|
|
130
|
+
*
|
|
131
|
+
* Every parameter is appended to whatever query the discovered endpoint already
|
|
132
|
+
* carries rather than replacing it: the authorize UI lives in another repo on
|
|
133
|
+
* another host and may legitimately carry its own.
|
|
134
|
+
*/
|
|
135
|
+
export declare function buildAuthorizeUrl(authorizationEndpoint: string, input: {
|
|
136
|
+
challenge: string;
|
|
137
|
+
state: string;
|
|
138
|
+
redirectUri: string;
|
|
139
|
+
}): string;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `/oauth/token` client — both grants.
|
|
3
|
+
*
|
|
4
|
+
* RFC 6749 §4.1.3 (`authorization_code`) and §6 (`refresh_token`), posted
|
|
5
|
+
* form-encoded, which is the shape the server prefers and parses first. No
|
|
6
|
+
* client secret: this is a PUBLIC client (the server advertises
|
|
7
|
+
* `token_endpoint_auth_methods_supported: ["none"]`), and a secret shipped
|
|
8
|
+
* inside a CLI on a developer's disk is not a secret.
|
|
9
|
+
*
|
|
10
|
+
* ## ⚠ THE CLIENT ID IS NOT REGISTERED SERVER-SIDE YET
|
|
11
|
+
*
|
|
12
|
+
* `vibecommit-mcp`'s `REGISTERED_CLIENT_IDS` is a hard-coded membership set
|
|
13
|
+
* (RFC 7591 dynamic registration is deferred) holding ten entries — `claude-ai`,
|
|
14
|
+
* `cursor-mcp`, `vscode-copilot`, `codex-cli`, `windsurf-mcp`, `cline`,
|
|
15
|
+
* `continue-dev`, `roo-code`, `kiro`, `mcp-inspector` — and **none of them is
|
|
16
|
+
* this CLI**. `isRegisteredClient` gates BOTH `/oauth/authorize` and
|
|
17
|
+
* `/oauth/token`, so against the live server every sign-in below returns
|
|
18
|
+
* `unauthorized_client` until `CLIENT_ID` below is added to that list.
|
|
19
|
+
*
|
|
20
|
+
* That is a one-line server-side change in another repo, and it is REPORTED
|
|
21
|
+
* rather than worked around: the two alternatives are both worse. Borrowing
|
|
22
|
+
* `mcp-inspector` would ship a dev/testing identity in a released binary, and
|
|
23
|
+
* borrowing `claude-ai` would make this client indistinguishable from Claude
|
|
24
|
+
* Code in the audit trail of a product whose entire value is an audit trail.
|
|
25
|
+
*
|
|
26
|
+
* One constant, named, so the registration is a one-line change on this side too.
|
|
27
|
+
*
|
|
28
|
+
* ⚠ AND THE NAME IS `capture-cli` RATHER THAN THE OBVIOUS PRODUCT-PREFIXED ONE.
|
|
29
|
+
* Every sibling repo in this project is named with that product prefix, and
|
|
30
|
+
* `test/provenance.test.ts`'s `REPO_TOKEN` matches exactly that shape to find
|
|
31
|
+
* closed-core references — so a client id built the obvious way is read by the
|
|
32
|
+
* licensing gate as a reference to a closed sibling repo that does not exist.
|
|
33
|
+
* The collision is a real signal rather than a tooling annoyance: an OAuth
|
|
34
|
+
* client id indistinguishable from a repository name is ambiguous to a human
|
|
35
|
+
* reader too. `capture-cli` names the package (`@vibe-commit/capture`) and
|
|
36
|
+
* matches the server's own convention for the ids beside it (`codex-cli`).
|
|
37
|
+
*
|
|
38
|
+
* @provenance vibecommit-mcp src/oauth/token.ts — both grants, request + response shapes, retyped
|
|
39
|
+
* @provenance vibecommit-mcp src/transport/server.ts — form-encoded body parse, retyped
|
|
40
|
+
* @provenance vibecommit-mcp src/db/supabase.ts — the registered-client gate, retyped
|
|
41
|
+
*/
|
|
42
|
+
import { AccessToken, RefreshToken, isAccessTokenShape, isRefreshTokenShape } from "../credential.js";
|
|
43
|
+
import { CHALLENGE_METHOD } from "./pkce.js";
|
|
44
|
+
/** This CLI's OAuth client identity. ⚠ Not yet in the server's set — see above. */
|
|
45
|
+
export const CLIENT_ID = "capture-cli";
|
|
46
|
+
/** The only scope the server registers. */
|
|
47
|
+
export const SCOPE = "mcp:tools";
|
|
48
|
+
/**
|
|
49
|
+
* Refresh this many milliseconds BEFORE the access token actually expires.
|
|
50
|
+
*
|
|
51
|
+
* Without a margin, a read starting at t=899s on a 900s token races its own
|
|
52
|
+
* round trip and fails with a 401 it could have avoided. 60s comfortably exceeds
|
|
53
|
+
* both the request budget and any plausible clock disagreement between this
|
|
54
|
+
* machine and the issuer, and costs one extra refresh per 15 minutes of
|
|
55
|
+
* continuous use.
|
|
56
|
+
*/
|
|
57
|
+
export const EXPIRY_SKEW_MS = 60_000;
|
|
58
|
+
async function post(endpoint, form, deps) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timer = setTimeout(() => controller.abort(), deps.timeoutMs);
|
|
61
|
+
let res;
|
|
62
|
+
try {
|
|
63
|
+
res = await deps.fetch(endpoint, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
67
|
+
accept: "application/json",
|
|
68
|
+
},
|
|
69
|
+
body: new URLSearchParams(form).toString(),
|
|
70
|
+
signal: controller.signal,
|
|
71
|
+
// This request carries the code verifier or the refresh token in its
|
|
72
|
+
// BODY. undici does not strip a body on redirect the way it strips an
|
|
73
|
+
// Authorization header, so following one would hand the secret to a hop
|
|
74
|
+
// the operator never configured. `post.ts` refuses redirects on the same
|
|
75
|
+
// lane for the same reason.
|
|
76
|
+
redirect: "error",
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
return { kind: "unreachable" };
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
let body;
|
|
86
|
+
try {
|
|
87
|
+
body = (await res.json());
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return { kind: "malformed" };
|
|
91
|
+
}
|
|
92
|
+
if (!res.ok) {
|
|
93
|
+
const error = body.error;
|
|
94
|
+
return { kind: "denied", error: typeof error === "string" ? error : "invalid_request" };
|
|
95
|
+
}
|
|
96
|
+
return parseTokenResponse(body, deps.nowMs);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Turn a token response into two opaque tokens, or refuse it.
|
|
100
|
+
*
|
|
101
|
+
* ⚠ Both halves are CLASS-CHECKED before they are wrapped, and this is the
|
|
102
|
+
* mirror of the ingest lane's `wrong-class` refusal rather than belt-and-braces.
|
|
103
|
+
* A response that put the JWT in `refresh_token` would otherwise be stored and
|
|
104
|
+
* then presented to the refresh grant on every subsequent read, and the symptom
|
|
105
|
+
* — `invalid_grant`, a signed-out user — names neither the cause nor the field.
|
|
106
|
+
* Refusing at the boundary makes the fault land where it happened.
|
|
107
|
+
*
|
|
108
|
+
* Exported so a test can drive known-bad bodies at it without a socket.
|
|
109
|
+
*/
|
|
110
|
+
export function parseTokenResponse(body, nowMs) {
|
|
111
|
+
const doc = body;
|
|
112
|
+
if (typeof doc.access_token !== "string" || !isAccessTokenShape(doc.access_token)) {
|
|
113
|
+
return { kind: "malformed" };
|
|
114
|
+
}
|
|
115
|
+
if (typeof doc.refresh_token !== "string" || !isRefreshTokenShape(doc.refresh_token)) {
|
|
116
|
+
return { kind: "malformed" };
|
|
117
|
+
}
|
|
118
|
+
if (typeof doc.expires_in !== "number" || !Number.isFinite(doc.expires_in)) {
|
|
119
|
+
return { kind: "malformed" };
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
kind: "ok",
|
|
123
|
+
tokens: {
|
|
124
|
+
access: new AccessToken(doc.access_token),
|
|
125
|
+
refresh: new RefreshToken(doc.refresh_token),
|
|
126
|
+
expiresAtMs: nowMs + doc.expires_in * 1000,
|
|
127
|
+
scope: typeof doc.scope === "string" ? doc.scope : SCOPE,
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* `grant_type=authorization_code`.
|
|
133
|
+
*
|
|
134
|
+
* `redirect_uri` is sent again and MUST be byte-identical to the one presented
|
|
135
|
+
* at authorize — the server compares them (`candidate.redirect_uri !==
|
|
136
|
+
* input.redirectUri` → `invalid_grant`). That is the second place the
|
|
137
|
+
* `127.0.0.1`-vs-`localhost` spelling decides whether this works.
|
|
138
|
+
*/
|
|
139
|
+
export async function exchangeCode(tokenEndpoint, input, deps) {
|
|
140
|
+
return await post(tokenEndpoint, {
|
|
141
|
+
grant_type: "authorization_code",
|
|
142
|
+
client_id: CLIENT_ID,
|
|
143
|
+
code: input.code,
|
|
144
|
+
code_verifier: input.codeVerifier,
|
|
145
|
+
redirect_uri: input.redirectUri,
|
|
146
|
+
}, deps);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* `grant_type=refresh_token` — rotation. The response carries a NEW refresh
|
|
150
|
+
* token and the old one is dead the instant this succeeds.
|
|
151
|
+
*
|
|
152
|
+
* ⚠ CALL THIS ONLY UNDER `withRefreshLock`. Presenting a token that has already
|
|
153
|
+
* been rotated revokes the whole family server-side. `lock.ts`'s module note is
|
|
154
|
+
* the long version; this is the line that stops a reader wiring it up directly.
|
|
155
|
+
*/
|
|
156
|
+
export async function refreshGrant(tokenEndpoint, refresh, deps) {
|
|
157
|
+
return await post(tokenEndpoint, {
|
|
158
|
+
grant_type: "refresh_token",
|
|
159
|
+
client_id: CLIENT_ID,
|
|
160
|
+
// The one place this secret's plaintext is used, at the request boundary —
|
|
161
|
+
// the same discipline `post.ts` states for the ingest credential.
|
|
162
|
+
refresh_token: refresh.expose(),
|
|
163
|
+
}, deps);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* The authorize URL, built from the DISCOVERED endpoint.
|
|
167
|
+
*
|
|
168
|
+
* Every parameter is appended to whatever query the discovered endpoint already
|
|
169
|
+
* carries rather than replacing it: the authorize UI lives in another repo on
|
|
170
|
+
* another host and may legitimately carry its own.
|
|
171
|
+
*/
|
|
172
|
+
export function buildAuthorizeUrl(authorizationEndpoint, input) {
|
|
173
|
+
const url = new URL(authorizationEndpoint);
|
|
174
|
+
url.searchParams.set("response_type", "code");
|
|
175
|
+
url.searchParams.set("client_id", CLIENT_ID);
|
|
176
|
+
url.searchParams.set("redirect_uri", input.redirectUri);
|
|
177
|
+
url.searchParams.set("scope", SCOPE);
|
|
178
|
+
url.searchParams.set("state", input.state);
|
|
179
|
+
url.searchParams.set("code_challenge", input.challenge);
|
|
180
|
+
url.searchParams.set("code_challenge_method", CHALLENGE_METHOD);
|
|
181
|
+
return url.toString();
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/oauth/token.ts"],"names":[],"mappings":"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"}
|