@cartanova/qgrid-cli 2.7.7 → 2.7.9
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/bundle/dist/application/qgrid/qgrid.frame.js +63 -28
- package/bundle/dist/application/qgrid/qgrid.types.js +6 -3
- package/bundle/dist/application/request-log/request-log.model.js +2 -2
- package/bundle/dist/sonamu.config.js +3 -1
- package/bundle/dist/utils/providers/openai/openai-callback-relay.js +86 -0
- package/bundle/src/application/qgrid/qgrid-oauth-redirect.test.ts +71 -8
- package/bundle/src/application/qgrid/qgrid.frame.test.ts +43 -0
- package/bundle/src/application/qgrid/qgrid.frame.ts +72 -26
- package/bundle/src/application/qgrid/qgrid.types.ts +4 -1
- package/bundle/src/application/request-log/request-log.model.test.ts +25 -0
- package/bundle/src/application/request-log/request-log.model.ts +3 -1
- package/bundle/src/application/sonamu.generated.http +7 -3
- package/bundle/src/sonamu.config.ts +2 -0
- package/bundle/src/utils/providers/openai/openai-callback-relay.test.ts +109 -0
- package/bundle/src/utils/providers/openai/openai-callback-relay.ts +105 -0
- package/bundle/web-dist/client/assets/{index-CokxkK2G.js → index-D59Q_9Z4.js} +3 -3
- package/bundle/web-dist/client/assets/{logs-Ckqp98Y3.js → logs-BHVfI8ut.js} +1 -1
- package/bundle/web-dist/client/assets/{monit-u0pMHbyf.js → monit-EkLxH1oN.js} +1 -1
- package/bundle/web-dist/client/assets/{routes-DobxbCAV.js → routes-Kbvuclip.js} +2 -2
- package/bundle/web-dist/client/assets/{settings-C8rfyi9z.js → settings-B4uTFP3J.js} +1 -1
- package/bundle/web-dist/client/assets/{show-FbUQYs4L.js → show-BjIdSPTd.js} +1 -1
- package/bundle/web-dist/client/assets/tokens-BbCSxbFu.js +1 -0
- package/bundle/web-dist/client/assets/use-oauth-login-flow-4OltfVAd.js +1 -0
- package/bundle/web-dist/client/index.html +1 -1
- package/bundle/web-dist/server/assets/{routes-B9X0lvjQ.js → routes-Ct3I9263.js} +3 -2
- package/bundle/web-dist/server/assets/{tokens-CyItE-L-.js → tokens-PR42jF1J.js} +3 -2
- package/bundle/web-dist/server/assets/{use-oauth-login-flow-CItVKngs.js → use-oauth-login-flow-C-33Z5HA.js} +16 -10
- package/bundle/web-dist/server/entry-server.generated.js +3 -3
- package/dist/cli.js +10 -11
- package/package.json +1 -1
- package/skills/qgrid/references/ai-sdk-provider-contract.md +2 -0
- package/skills/qgrid/references/cli-env-and-server-boot.md +1 -1
- package/skills/qgrid/references/openai-codex-runtime.md +12 -5
- package/skills/qgrid/references/request-log-run-lifecycle.md +2 -0
- package/skills/qgrid/references/token-auth-quota-lifecycle.md +2 -0
- package/bundle/web-dist/client/assets/tokens-DFwEavFD.js +0 -1
- package/bundle/web-dist/client/assets/use-oauth-login-flow-D-9h3WUt.js +0 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import http from "node:http";
|
|
2
|
+
|
|
3
|
+
import { getLogger } from "@logtape/logtape";
|
|
4
|
+
|
|
5
|
+
const logger = getLogger(["qgrid", "oauth"]);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* OpenAI matches the Codex CLI client's redirect URIs by exact string, and only these two loopback
|
|
9
|
+
* callbacks are registered. Any other host, port, or path makes /oauth/authorize fail with
|
|
10
|
+
* `invalid_authorize_request` before the login screen renders, so qgrid cannot point the callback
|
|
11
|
+
* at its own configurable server port. Keep in sync with the Codex CLI allow-list (default 1455,
|
|
12
|
+
* fallback 1457).
|
|
13
|
+
*/
|
|
14
|
+
export const OPENAI_CALLBACK_PORTS = [1455, 1457] as const;
|
|
15
|
+
export const OPENAI_CALLBACK_PATH = "/auth/callback";
|
|
16
|
+
|
|
17
|
+
export function openAICallbackRedirectUri(port: number): string {
|
|
18
|
+
return `http://localhost:${port}${OPENAI_CALLBACK_PATH}`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type Relay = {
|
|
22
|
+
server: http.Server;
|
|
23
|
+
redirectUri: string;
|
|
24
|
+
dashboardBase: string;
|
|
25
|
+
expiryTimer: NodeJS.Timeout;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
let relay: Relay | null = null;
|
|
29
|
+
|
|
30
|
+
function forward(request: http.IncomingMessage, response: http.ServerResponse): void {
|
|
31
|
+
const current = relay;
|
|
32
|
+
const incoming = new URL(request.url ?? "/", "http://localhost");
|
|
33
|
+
if (!current || incoming.pathname !== OPENAI_CALLBACK_PATH) {
|
|
34
|
+
response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
|
35
|
+
response.end("not found");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Only the two OAuth result fields cross over, onto a fixed loopback base. The request never
|
|
40
|
+
// contributes scheme, authority, or path bytes to the redirect target.
|
|
41
|
+
const target = new URL(OPENAI_CALLBACK_PATH, current.dashboardBase);
|
|
42
|
+
for (const field of ["code", "state"] as const) {
|
|
43
|
+
const value = incoming.searchParams.get(field);
|
|
44
|
+
if (value) target.searchParams.set(field, value);
|
|
45
|
+
}
|
|
46
|
+
response.writeHead(302, { Location: target.toString(), "Cache-Control": "no-store" });
|
|
47
|
+
response.end();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function listenOn(port: number): Promise<http.Server | null> {
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const server = http.createServer(forward);
|
|
53
|
+
server.once("error", (error: NodeJS.ErrnoException) => {
|
|
54
|
+
server.close();
|
|
55
|
+
if (error.code === "EADDRINUSE" || error.code === "EACCES") resolve(null);
|
|
56
|
+
else reject(error);
|
|
57
|
+
});
|
|
58
|
+
// Bind loopback only, like the Codex CLI: the relay must not be reachable off-host.
|
|
59
|
+
server.listen(port, "127.0.0.1", () => resolve(server));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Opens a short-lived loopback listener on a registered Codex callback port and forwards the
|
|
65
|
+
* authorization code to qgrid own callback route. Returns the redirect URI to put in the authorize
|
|
66
|
+
* request. Repeated logins reuse the running listener and refresh its lifetime.
|
|
67
|
+
*/
|
|
68
|
+
export async function startOpenAICallbackRelay(
|
|
69
|
+
dashboardBase: string,
|
|
70
|
+
ttlMs: number,
|
|
71
|
+
ports: readonly number[] = OPENAI_CALLBACK_PORTS,
|
|
72
|
+
): Promise<string> {
|
|
73
|
+
if (relay) {
|
|
74
|
+
relay.dashboardBase = dashboardBase;
|
|
75
|
+
relay.expiryTimer.refresh();
|
|
76
|
+
return relay.redirectUri;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const port of ports) {
|
|
80
|
+
const server = await listenOn(port);
|
|
81
|
+
if (!server) continue;
|
|
82
|
+
|
|
83
|
+
const expiryTimer = setTimeout(() => stopOpenAICallbackRelay(), ttlMs);
|
|
84
|
+
expiryTimer.unref();
|
|
85
|
+
const address = server.address();
|
|
86
|
+
const boundPort = typeof address === "object" && address ? address.port : port;
|
|
87
|
+
relay = {
|
|
88
|
+
server,
|
|
89
|
+
redirectUri: openAICallbackRedirectUri(boundPort),
|
|
90
|
+
dashboardBase,
|
|
91
|
+
expiryTimer,
|
|
92
|
+
};
|
|
93
|
+
logger.info(`openai oauth callback relay listening on 127.0.0.1:${boundPort}`);
|
|
94
|
+
return relay.redirectUri;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
throw new Error(`OPENAI_CALLBACK_PORTS_UNAVAILABLE_${ports.join("_")}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function stopOpenAICallbackRelay(): void {
|
|
101
|
+
if (!relay) return;
|
|
102
|
+
clearTimeout(relay.expiryTimer);
|
|
103
|
+
relay.server.close();
|
|
104
|
+
relay = null;
|
|
105
|
+
}
|