Skip to content

Commit 0dc9b7a

Browse files
committed
Replace local OAuth with the shared oauth-core package
xAI login and refresh go through @corbits/xai-provider. Callback copy is injected from branding so auth does not import it.
1 parent fa85931 commit 0dc9b7a

49 files changed

Lines changed: 926 additions & 1731 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎bun.lock‎

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
"tar": "^7.5.1"
8181
},
8282
"dependencies": {
83+
"@corbits/oauth-core": "github:corbitsdev/corbits-oauth-core",
8384
"@corbits/openai-responses": "github:corbitsdev/corbits-openai-responses",
8485
"@corbits/xai-provider": "github:corbitsdev/corbits-xai-provider",
8586
"@intx/agent": "workspace:*",

‎src/auth/callback-page.test.ts‎

Lines changed: 34 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
import { describe, expect, test } from "bun:test";
22

33
import {
4-
PRODUCT_GITHUB_LABEL,
5-
PRODUCT_GITHUB_URL,
6-
PRODUCT_SITE_LABEL,
7-
PRODUCT_SITE_URL,
8-
} from "../branding.js";
9-
import { callbackPageHtml, humanizeIdentifier } from "./callback-page.js";
10-
import { authorizationDoneHtml } from "./oauth/callback-server.js";
4+
authorizationDoneHtml,
5+
callbackPageHtml,
6+
humanizeIdentifier,
7+
type CallbackPageCopy,
8+
} from "./callback-page.js";
9+
10+
const copy: CallbackPageCopy = {
11+
productName: "Fixture Product",
12+
siteUrl: "https://fixture.example",
13+
siteLabel: "fixture.example",
14+
githubUrl: "https://github.com/fixture",
15+
githubLabel: "github.com/fixture",
16+
};
1117

1218
describe("humanizeIdentifier", () => {
1319
test("machine identifiers lose their separators and lead with a capital", () => {
@@ -24,59 +30,57 @@ describe("humanizeIdentifier", () => {
2430

2531
describe("callbackPageHtml", () => {
2632
test("success names the server that connected", () => {
27-
const html = callbackPageHtml({ subject: "linear" });
33+
const html = callbackPageHtml({ subject: "linear" }, copy);
2834
expect(html).toContain("Linear connected successfully");
2935
expect(html).not.toContain("access_denied");
3036
});
3137

3238
test("provider authorization waits for native setup before claiming connection", () => {
33-
const html = authorizationDoneHtml("Codex");
39+
const html = authorizationDoneHtml("Codex", copy);
3440
expect(html).toContain("Codex authorization received");
3541
expect(html).toContain("finish setup");
3642
expect(html).not.toContain("connected successfully");
3743
});
3844

3945
test("failure names the server and the humanized reason", () => {
40-
const html = callbackPageHtml({
41-
subject: "granola",
42-
error: "access_denied",
43-
});
46+
const html = callbackPageHtml(
47+
{
48+
subject: "granola",
49+
error: "access_denied",
50+
},
51+
copy,
52+
);
4453
expect(html).toContain("Granola failed to connect");
4554
expect(html).toContain("Access denied.");
4655
expect(html).not.toContain("access_denied");
4756
});
4857

4958
test("an unnamed authorization still renders both outcomes", () => {
50-
expect(callbackPageHtml()).toContain("Authorization complete");
51-
expect(callbackPageHtml({ error: "server_error" })).toContain(
59+
expect(callbackPageHtml({}, copy)).toContain("Authorization complete");
60+
expect(callbackPageHtml({ error: "server_error" }, copy)).toContain(
5261
"Authorization did not complete",
5362
);
5463
});
5564

5665
test("the subject is escaped rather than pasted into markup", () => {
57-
expect(callbackPageHtml({ subject: "<script>x</script>" })).not.toContain(
58-
"<script>x",
59-
);
66+
expect(
67+
callbackPageHtml({ subject: "<script>x</script>" }, copy),
68+
).not.toContain("<script>x");
6069
});
6170

62-
test("the footer links to the product site and the GitHub org", () => {
63-
const html = callbackPageHtml({ subject: "linear" });
71+
test("the footer links to the injected site and GitHub copy", () => {
72+
const html = callbackPageHtml({ subject: "linear" }, copy);
6473
const link = (url: string, label: string) =>
6574
`<a href="${url}" target="_blank" rel="noopener noreferrer">${label}</a>`;
66-
expect(html).toContain(link(PRODUCT_SITE_URL, PRODUCT_SITE_LABEL));
67-
expect(html).toContain(link(PRODUCT_GITHUB_URL, PRODUCT_GITHUB_LABEL));
68-
});
69-
70-
test("each footer label names the destination its URL actually points at", () => {
71-
expect(PRODUCT_SITE_URL).toContain(PRODUCT_SITE_LABEL);
72-
expect(PRODUCT_GITHUB_URL).toContain(PRODUCT_GITHUB_LABEL);
75+
expect(html).toContain(link(copy.siteUrl, copy.siteLabel));
76+
expect(html).toContain(link(copy.githubUrl, copy.githubLabel));
7377
});
7478

7579
// An allowlist rather than a shape match: an unexpected origin fails loudly
7680
// instead of passing because it happened to be wrapped in an anchor tag.
7781
const allowedOrigins = new Set([
78-
PRODUCT_SITE_URL,
79-
PRODUCT_GITHUB_URL,
82+
copy.siteUrl,
83+
copy.githubUrl,
8084
// The SVG namespace the wordmark declares; a URI, never fetched.
8185
"http://www.w3.org/2000/svg",
8286
]);
@@ -97,7 +101,7 @@ describe("callbackPageHtml", () => {
97101
["failure", { subject: "linear", error: "access_denied" }],
98102
] as const) {
99103
test(`the ${outcome} page names no off-machine origin beyond the footer links`, () => {
100-
const html = callbackPageHtml(page);
104+
const html = callbackPageHtml(page, copy);
101105
expect(offMachineOrigins(html)).toEqual([]);
102106
expect(html).not.toMatch(
103107
/\b(?:fetch|XMLHttpRequest|WebSocket|EventSource|sendBeacon|importScripts)\s*\(/,

‎src/auth/callback-page.ts‎

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
export interface CallbackPageCopy {
2+
readonly productName: string;
3+
readonly siteUrl: string;
4+
readonly siteLabel: string;
5+
readonly githubUrl: string;
6+
readonly githubLabel: string;
7+
}
8+
19
/**
210
* The page an OAuth provider redirects back to, for every authorization this
311
* product runs (MCP servers and inference providers alike).
@@ -14,14 +22,6 @@
1422
* authorization callback.
1523
*/
1624

17-
import {
18-
PRODUCT_GITHUB_LABEL,
19-
PRODUCT_GITHUB_URL,
20-
PRODUCT_NAME,
21-
PRODUCT_SITE_LABEL,
22-
PRODUCT_SITE_URL,
23-
} from "../branding.js";
24-
2525
/** Corbits wordmark, background layers stripped so it inherits `currentColor`. */
2626
const WORDMARK = `<svg class="wordmark" viewBox="0 0 1000 400" role="img" aria-label="Corbits" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M195.057 192.125L160.182 225.625C157.016 214.042 153.099 205.042 148.432 198.625C143.849 192.208 138.974 189 133.807 189C129.724 189 126.557 190.75 124.307 194.25C122.141 197.75 121.057 202.75 121.057 209.25C121.057 224.583 124.307 237.167 130.807 247C137.307 256.833 145.641 261.75 155.807 261.75C161.391 261.75 166.682 260.333 171.682 257.5C176.766 254.667 181.141 250.708 184.807 245.625L196.557 254.25C189.807 268.667 181.266 279.667 170.932 287.25C160.599 294.833 149.016 298.625 136.182 298.625C120.016 298.625 106.724 293.083 96.3072 282C85.9739 270.833 80.8072 256.667 80.8072 239.5C80.8072 219.583 87.7239 202.333 101.557 187.75C115.391 173.083 131.349 165.75 149.432 165.75C158.682 165.75 167.182 168 174.932 172.5C182.682 177 189.391 183.542 195.057 192.125ZM303.705 248.125C303.705 231.292 300.288 216.542 293.455 203.875C286.705 191.208 279.371 184.875 271.455 184.875C266.371 184.875 262.413 187.458 259.58 192.625C256.746 197.792 255.33 205.125 255.33 214.625C255.33 232.208 258.621 247.417 265.205 260.25C271.788 273 279.08 279.375 287.08 279.375C292.496 279.375 296.621 276.75 299.455 271.5C302.288 266.167 303.705 258.375 303.705 248.125ZM279.705 165.75C300.038 165.75 316.913 172.042 330.33 184.625C343.83 197.208 350.58 213.083 350.58 232.25C350.58 251.417 343.871 267.292 330.455 279.875C317.121 292.375 300.205 298.625 279.705 298.625C259.121 298.625 242.08 292.375 228.58 279.875C215.163 267.375 208.455 251.5 208.455 232.25C208.455 213 215.163 197.125 228.58 184.625C242.08 172.042 259.121 165.75 279.705 165.75ZM464.727 164.5L478.352 169.25L463.227 212C458.311 210.083 454.144 208.667 450.727 207.75C447.394 206.833 444.561 206.375 442.227 206.375C437.144 206.375 433.144 208.125 430.227 211.625C427.311 215.042 425.852 219.792 425.852 225.875V279.125H438.852V295H371.352V279.125H380.227V214.875C380.227 211.542 379.936 209.292 379.352 208.125C378.769 206.958 377.686 205.875 376.102 204.875L371.352 208.75L362.102 198.5L389.727 165.75C397.477 169.333 403.602 173.208 408.102 177.375C412.686 181.542 415.936 186.292 417.852 191.625C425.102 180.542 430.477 173.458 433.977 170.375C437.561 167.292 441.311 165.75 445.227 165.75C447.394 165.75 449.894 166.167 452.727 167C455.644 167.75 458.727 168.875 461.977 170.375L464.727 164.5ZM540.875 248.875C540.875 257.292 542.417 263.75 545.5 268.25C548.583 272.75 552.958 275 558.625 275C564.625 275 569.375 272.25 572.875 266.75C576.458 261.25 578.25 253.667 578.25 244C578.25 229.75 575.75 218.125 570.75 209.125C565.833 200.125 559.542 195.625 551.875 195.625C550.125 195.625 548.417 195.917 546.75 196.5C545.083 197 543.125 197.917 540.875 199.25V248.875ZM550.375 118.375V134.5L540.875 137.5V181.875C548.792 176.208 555.75 172.125 561.75 169.625C567.75 167.042 573.417 165.75 578.75 165.75C590.667 165.75 600.542 170.417 608.375 179.75C616.292 189.083 620.25 200.792 620.25 214.875C620.25 226.458 617.958 237.417 613.375 247.75C608.875 258 602.208 267.25 593.375 275.5C585.458 283.083 576.958 288.833 567.875 292.75C558.792 296.667 549.375 298.625 539.625 298.625C532.208 298.625 524.458 297.333 516.375 294.75C508.292 292.083 501.208 288.583 495.125 284.25V152.125L486.375 155V138.875L550.375 118.375ZM694.898 165.75H696.648C696.231 171.167 695.898 175.625 695.648 179.125C695.481 182.542 695.398 185.417 695.398 187.75V279.125H704.273V295H640.898V279.125H649.773V193.375L640.898 194.875V180.625L694.898 165.75ZM677.148 126C683.231 126 688.106 127.417 691.773 130.25C695.523 133.083 697.398 136.708 697.398 141.125C697.398 147.125 694.314 152.25 688.148 156.5C682.064 160.75 674.731 162.875 666.148 162.875C660.898 162.875 656.689 161.542 653.523 158.875C650.356 156.208 648.773 152.667 648.773 148.25C648.773 142.5 651.648 137.375 657.398 132.875C663.148 128.292 669.731 126 677.148 126ZM771.67 137.25L784.545 140.125V170.625H806.795L804.17 185.375C803.837 187.875 803.42 189.417 802.92 190C802.42 190.5 801.587 190.75 800.42 190.75H784.545V255.375C784.545 261.792 785.378 266.333 787.045 269C788.712 271.667 791.545 273 795.545 273C797.045 273 798.628 272.708 800.295 272.125C802.045 271.542 803.962 270.625 806.045 269.375L812.67 279.125C806.503 285.458 799.92 290.292 792.92 293.625C785.92 296.958 778.92 298.625 771.92 298.625C760.253 298.625 751.795 295.875 746.545 290.375C741.295 284.792 738.67 275.75 738.67 263.25V190.75H726.92V176.25C738.253 172.917 747.628 168 755.045 161.5C762.545 155 768.087 146.917 771.67 137.25ZM916.693 179.75L898.068 209.25C890.818 200.667 884.401 194.333 878.818 190.25C873.318 186.167 868.401 184.125 864.068 184.125C861.651 184.125 859.693 184.833 858.193 186.25C856.776 187.583 856.068 189.375 856.068 191.625C856.068 194.292 857.443 196.875 860.193 199.375C863.026 201.875 868.651 205.25 877.068 209.5C895.651 218.833 907.484 226.417 912.568 232.25C917.734 238.083 920.318 245.083 920.318 253.25C920.318 265.833 914.984 276.542 904.318 285.375C893.734 294.208 880.568 298.625 864.818 298.625C855.484 298.625 846.568 297.125 838.068 294.125C829.651 291.042 821.651 286.458 814.068 280.375L835.068 247.875C844.151 257.875 852.276 265.417 859.443 270.5C866.609 275.583 872.651 278.125 877.568 278.125C880.401 278.125 882.568 277.458 884.068 276.125C885.568 274.708 886.318 272.708 886.318 270.125C886.318 265.708 878.693 259.458 863.443 251.375L863.318 251.25C862.401 250.75 861.151 250.083 859.568 249.25C836.734 236.917 825.318 223.333 825.318 208.5C825.318 195.833 829.734 185.542 838.568 177.625C847.484 169.708 859.193 165.75 873.693 165.75C881.276 165.75 888.609 166.917 895.693 169.25C902.776 171.583 909.776 175.083 916.693 179.75Z" fill="currentColor"/> </svg>`;
2727

@@ -274,7 +274,10 @@ export interface CallbackPage {
274274
* open, and the one thing each has to answer is which server it is and
275275
* whether that one worked.
276276
*/
277-
export function callbackPageHtml(page: CallbackPage = {}): string {
277+
export function callbackPageHtml(
278+
page: CallbackPage = {},
279+
copy: CallbackPageCopy,
280+
): string {
278281
const failed = page.error !== undefined;
279282
const subject =
280283
page.subject === undefined
@@ -300,16 +303,16 @@ export function callbackPageHtml(page: CallbackPage = {}): string {
300303
: `${subject} connected successfully`;
301304
const reason = escapeHtml(humanizeIdentifier(page.error ?? ""));
302305
const body = failed
303-
? `${reason}. Close this tab and try again from ${PRODUCT_NAME}.`
306+
? `${reason}. Close this tab and try again from ${copy.productName}.`
304307
: pendingSetup
305-
? `Return to ${PRODUCT_NAME} to finish setup.`
306-
: `You can close this tab and return to ${PRODUCT_NAME}.`;
308+
? `Return to ${copy.productName} to finish setup.`
309+
: `You can close this tab and return to ${copy.productName}.`;
307310
return [
308311
"<!doctype html>",
309312
'<html lang="en">',
310313
'<meta charset="utf-8">',
311314
'<meta name="viewport" content="width=device-width, initial-scale=1">',
312-
`<title>${escapeHtml(heading)} \u00b7 ${PRODUCT_NAME}</title>`,
315+
`<title>${escapeHtml(heading)} \u00b7 ${copy.productName}</title>`,
313316
`<style>${STYLE}</style>`,
314317
"<body><main>",
315318
`<canvas aria-hidden="true" data-path="${MARK_PATH}"></canvas>`,
@@ -319,10 +322,17 @@ export function callbackPageHtml(page: CallbackPage = {}): string {
319322
`<h1>${heading}</h1>`,
320323
`<p class="body">${body}</p>`,
321324
"<hr>",
322-
`<footer>${PRODUCT_NAME}${footerLink(PRODUCT_SITE_URL, PRODUCT_SITE_LABEL)}${footerLink(PRODUCT_GITHUB_URL, PRODUCT_GITHUB_LABEL)}</footer>`,
325+
`<footer>${copy.productName}${footerLink(copy.siteUrl, copy.siteLabel)}${footerLink(copy.githubUrl, copy.githubLabel)}</footer>`,
323326
"</div>",
324327
"</main></body>",
325328
`<script>${SCRIPT}</script>`,
326329
"</html>",
327330
].join("");
328331
}
332+
333+
export function authorizationDoneHtml(
334+
providerName: string,
335+
copy: CallbackPageCopy,
336+
): string {
337+
return callbackPageHtml({ subject: providerName, pendingSetup: true }, copy);
338+
}

‎src/auth/codex/callback-server.ts‎

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { startCallbackServer, type CallbackServer } from "@corbits/oauth-core";
2+
13
import {
24
authorizationDoneHtml,
3-
startCallbackServer,
4-
type CallbackServer,
5-
} from "../oauth/callback-server.js";
5+
callbackPageHtml,
6+
type CallbackPageCopy,
7+
} from "../callback-page.js";
68
import { CODEX_CALLBACK_PATH, CODEX_CALLBACK_PORT } from "./constants.js";
79

810
export type CodexCallbackServer = CallbackServer;
@@ -11,13 +13,15 @@ export type CodexCallbackServer = CallbackServer;
1113
// server only accepts this exact redirect_uri for this client.
1214
export async function startCodexCallbackServer(
1315
expectedState: string,
16+
copy: CallbackPageCopy,
1417
): Promise<CodexCallbackServer> {
1518
return startCallbackServer(expectedState, {
1619
port: CODEX_CALLBACK_PORT,
17-
path: CODEX_CALLBACK_PATH,
1820
// Codex's registered redirect_uri uses localhost (not 127.0.0.1).
19-
publicHost: "localhost",
20-
doneHtml: authorizationDoneHtml("Codex"),
21-
label: "Codex",
21+
host: "localhost",
22+
path: CODEX_CALLBACK_PATH,
23+
doneHtml: authorizationDoneHtml("Codex", copy),
24+
failedHtml: (reason) =>
25+
callbackPageHtml({ subject: "Codex", error: reason }, copy),
2226
});
2327
}

‎src/auth/codex/index.ts‎

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,13 @@ export {
88
CODEX_DEFAULT_MODELS,
99
CODEX_REDIRECT_URI,
1010
} from "./constants.js";
11+
export type { CodexProfile, CodexTokens } from "./store.js";
1112
export {
1213
listCodexProfiles,
1314
loadCodexProfile,
1415
removeCodexProfile,
1516
saveCodexProfile,
16-
type CodexProfile,
17-
type CodexTokens,
18-
} from "./store.js";
17+
} from "../../config/oauth-stores.js";
1918
export {
2019
getValidCodexToken,
2120
isCodexTokenExpired,

‎src/auth/codex/login.ts‎

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,35 @@
1-
import { openInBrowser } from "../oauth/browser.js";
21
import {
2+
openInBrowser,
33
startOAuthLogin,
44
type OAuthLoginHandle,
55
type StartOAuthLoginOptions,
6-
} from "../oauth/login.js";
6+
} from "@corbits/oauth-core";
7+
8+
import type { CallbackPageCopy } from "../callback-page.js";
9+
import { saveCodexProfile } from "../../config/oauth-stores.js";
710
import { CODEX_BASE_URL, CODEX_DEFAULT_MODELS } from "./constants.js";
811
import { startCodexCallbackServer } from "./callback-server.js";
912
import { buildAuthorizeUrl, exchangeCode } from "./oauth.js";
10-
import { saveCodexProfile, type CodexTokens } from "./store.js";
13+
import type { CodexTokens } from "./store.js";
1114

1215
export { openInBrowser };
1316

1417
export type CodexLoginHandle = OAuthLoginHandle<CodexTokens>;
15-
export type StartCodexLoginOptions = StartOAuthLoginOptions;
18+
export type StartCodexLoginOptions = StartOAuthLoginOptions & {
19+
home?: string;
20+
copy: CallbackPageCopy;
21+
};
1622

1723
// Drive the loopback PKCE login for a Codex profile.
1824
export async function startCodexLogin(
1925
opts: StartCodexLoginOptions,
2026
): Promise<CodexLoginHandle> {
21-
return startOAuthLogin(opts, {
22-
startCallbackServer: startCodexCallbackServer,
27+
const { home, copy, ...loginOpts } = opts;
28+
return startOAuthLogin(loginOpts, {
29+
startCallbackServer: (state) => startCodexCallbackServer(state, copy),
2330
buildAuthorizeUrl,
2431
exchangeCode,
25-
saveProfile: saveCodexProfile,
32+
saveProfile: (profile) => saveCodexProfile(profile, home),
2633
});
2734
}
2835

0 commit comments

Comments
 (0)