opencode-auth-proxy 1.0.0 → 1.0.1

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.
@@ -0,0 +1,2 @@
1
+ export { opencodeAuthProxy, opencodeAuthProxy as default } from "./opencode-auth-proxy.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,IAAI,OAAO,EAAE,MAAM,0BAA0B,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { opencodeAuthProxy, opencodeAuthProxy as default } from "./opencode-auth-proxy.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,iBAAiB,IAAI,OAAO,EAAE,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,5 @@
1
+ import type { Plugin } from "@opencode-ai/plugin";
2
+ declare const opencodeAuthProxy: Plugin;
3
+ export { opencodeAuthProxy };
4
+ export default opencodeAuthProxy;
5
+ //# sourceMappingURL=opencode-auth-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-auth-proxy.d.ts","sourceRoot":"","sources":["../opencode-auth-proxy.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAoQjD,QAAA,MAAM,iBAAiB,EAAE,MAoUxB,CAAA;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA;AAC5B,eAAe,iBAAiB,CAAA"}
@@ -0,0 +1,508 @@
1
+ import http from "http";
2
+ import httpProxy from "http-proxy";
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import crypto from "crypto";
6
+ import os from "os";
7
+ const GOOGLE_SCOPES = [
8
+ "https://www.googleapis.com/auth/cloud-platform",
9
+ "https://www.googleapis.com/auth/userinfo.email",
10
+ "https://www.googleapis.com/auth/userinfo.profile",
11
+ ];
12
+ function base64url(buf) {
13
+ return buf
14
+ .toString("base64")
15
+ .replace(/\+/g, "-")
16
+ .replace(/\//g, "_")
17
+ .replace(/=+$/, "");
18
+ }
19
+ function encodeState(payload) {
20
+ return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
21
+ }
22
+ function decodeState(state) {
23
+ const normalized = state.replace(/-/g, "+").replace(/_/g, "/");
24
+ const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
25
+ const json = Buffer.from(padded, "base64").toString("utf8");
26
+ const parsed = JSON.parse(json);
27
+ return { verifier: parsed.verifier || "", projectId: parsed.projectId || "" };
28
+ }
29
+ function generatePkce() {
30
+ const verifier = base64url(crypto.randomBytes(32));
31
+ const challenge = base64url(crypto.createHash("sha256").update(verifier).digest());
32
+ return { verifier, challenge };
33
+ }
34
+ function parseCookies(req) {
35
+ const header = req.headers.cookie;
36
+ if (!header)
37
+ return {};
38
+ return header.split(";").reduce((acc, part) => {
39
+ const [key, ...valueParts] = part.trim().split("=");
40
+ if (!key)
41
+ return acc;
42
+ acc[key] = valueParts.join("=");
43
+ return acc;
44
+ }, {});
45
+ }
46
+ function signSession(sessionSecret) {
47
+ const sessionData = Buffer.from("authenticated", "utf8").toString("base64url");
48
+ const signature = crypto.createHmac("sha256", sessionSecret).update(sessionData).digest("hex");
49
+ return `${sessionData}.${signature}`;
50
+ }
51
+ function verifySession(token, sessionSecret) {
52
+ const [sessionPart, signature] = token.split(".");
53
+ if (!sessionPart || !signature)
54
+ return false;
55
+ const expected = crypto.createHmac("sha256", sessionSecret).update(sessionPart).digest("hex");
56
+ return crypto.timingSafeEqual(Buffer.from(signature, "utf8"), Buffer.from(expected, "utf8"));
57
+ }
58
+ function expandPath(filePath) {
59
+ if (filePath.startsWith("~/")) {
60
+ return path.join(os.homedir(), filePath.slice(2));
61
+ }
62
+ return filePath;
63
+ }
64
+ function loadToken(tokenPath) {
65
+ try {
66
+ const expandedPath = expandPath(tokenPath);
67
+ const raw = fs.readFileSync(expandedPath, "utf8");
68
+ const parsed = JSON.parse(raw);
69
+ if (typeof parsed !== "object" || !parsed)
70
+ return null;
71
+ if ("access" in parsed && "refresh" in parsed) {
72
+ return parsed;
73
+ }
74
+ return null;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
80
+ function persistToken(token, tokenPath) {
81
+ const expandedPath = expandPath(tokenPath);
82
+ const dir = path.dirname(expandedPath);
83
+ fs.mkdirSync(dir, { recursive: true });
84
+ fs.writeFileSync(expandedPath, JSON.stringify(token, null, 2));
85
+ }
86
+ async function fetchWithTimeout(url, options, timeoutMs = 10000) {
87
+ const controller = new AbortController();
88
+ const timeout = setTimeout(() => controller.abort(), timeoutMs);
89
+ try {
90
+ return await fetch(url, { ...options, signal: controller.signal });
91
+ }
92
+ finally {
93
+ clearTimeout(timeout);
94
+ }
95
+ }
96
+ async function authorizeGoogle(clientId, redirectUri, projectId = "") {
97
+ const pkce = generatePkce();
98
+ const url = new URL("https://accounts.google.com/o/oauth2/v2/auth");
99
+ url.searchParams.set("client_id", clientId);
100
+ url.searchParams.set("response_type", "code");
101
+ url.searchParams.set("redirect_uri", redirectUri);
102
+ url.searchParams.set("scope", GOOGLE_SCOPES.join(" "));
103
+ url.searchParams.set("code_challenge", pkce.challenge);
104
+ url.searchParams.set("code_challenge_method", "S256");
105
+ url.searchParams.set("state", encodeState({ verifier: pkce.verifier, projectId: projectId || "" }));
106
+ url.searchParams.set("access_type", "offline");
107
+ url.searchParams.set("prompt", "consent");
108
+ return { url: url.toString(), verifier: pkce.verifier, projectId: projectId || "" };
109
+ }
110
+ async function exchangeGoogle(code, state, clientId, clientSecret, redirectUri, log) {
111
+ try {
112
+ const { verifier } = decodeState(state || "");
113
+ const startTime = Date.now();
114
+ const requestBody = new URLSearchParams({
115
+ client_id: clientId,
116
+ client_secret: clientSecret,
117
+ code,
118
+ grant_type: "authorization_code",
119
+ redirect_uri: redirectUri,
120
+ code_verifier: verifier,
121
+ });
122
+ await log({
123
+ service: "opencode-auth-proxy",
124
+ level: "info",
125
+ message: "OAuth token exchange request",
126
+ extra: {
127
+ redirect_uri: redirectUri,
128
+ client_id: clientId,
129
+ client_secret_length: clientSecret?.length || 0,
130
+ code_length: code?.length || 0,
131
+ has_verifier: !!verifier,
132
+ },
133
+ });
134
+ const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
135
+ method: "POST",
136
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
137
+ body: requestBody,
138
+ });
139
+ if (!tokenResponse.ok) {
140
+ const errorText = await tokenResponse.text();
141
+ let parsedError = {};
142
+ try {
143
+ parsedError = JSON.parse(errorText);
144
+ }
145
+ catch {
146
+ parsedError = { raw: errorText };
147
+ }
148
+ await log({
149
+ service: "opencode-auth-proxy",
150
+ level: "error",
151
+ message: "OAuth token exchange failed",
152
+ extra: {
153
+ status: tokenResponse.status,
154
+ statusText: tokenResponse.statusText,
155
+ error: parsedError,
156
+ redirect_uri: redirectUri,
157
+ redirect_uri_length: redirectUri.length,
158
+ client_id: clientId,
159
+ client_id_length: clientId.length,
160
+ client_secret_set: !!clientSecret,
161
+ client_secret_length: clientSecret?.length || 0,
162
+ },
163
+ });
164
+ const errorMessage = parsedError.error_description || parsedError.error || errorText;
165
+ return { type: "failed", error: errorMessage };
166
+ }
167
+ const tokenPayload = (await tokenResponse.json());
168
+ const userInfoResponse = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
169
+ headers: { Authorization: `Bearer ${tokenPayload.access_token}` },
170
+ });
171
+ const userInfo = userInfoResponse.ok ? (await userInfoResponse.json()) : {};
172
+ const refreshToken = tokenPayload.refresh_token;
173
+ if (!refreshToken)
174
+ return { type: "failed", error: "Missing refresh token in response" };
175
+ return {
176
+ type: "success",
177
+ refresh: refreshToken,
178
+ access: tokenPayload.access_token,
179
+ expires: startTime + (tokenPayload.expires_in || 0) * 1000,
180
+ email: userInfo.email,
181
+ projectId: "",
182
+ };
183
+ }
184
+ catch (error) {
185
+ return { type: "failed", error: error instanceof Error ? error.message : "Unknown error" };
186
+ }
187
+ }
188
+ function normalizeRedirectUri(uri, defaultPort) {
189
+ const defaultUri = `http://localhost:${defaultPort}/auth/google/callback`;
190
+ const rawUri = uri?.trim() || defaultUri;
191
+ try {
192
+ const url = new URL(rawUri);
193
+ if (url.pathname.endsWith("/") && url.pathname !== "/") {
194
+ url.pathname = url.pathname.replace(/\/+$/, "");
195
+ return url.toString();
196
+ }
197
+ return rawUri;
198
+ }
199
+ catch (err) {
200
+ return rawUri;
201
+ }
202
+ }
203
+ function getCallbackPath(redirectUri) {
204
+ try {
205
+ const url = new URL(redirectUri);
206
+ return url.pathname || "/auth/google/callback";
207
+ }
208
+ catch {
209
+ return "/auth/google/callback";
210
+ }
211
+ }
212
+ const opencodeAuthProxy = async ({ client, directory }) => {
213
+ await client.app.log({
214
+ service: "opencode-auth-proxy",
215
+ level: "info",
216
+ message: "Plugin initializing",
217
+ extra: {
218
+ hasEnvPort: !!process.env.PORT,
219
+ hasEnvSessionSecret: !!process.env.SESSION_SECRET,
220
+ hasEnvGoogleId: !!process.env.GOOGLE_REDIRECT_CLIENT_ID,
221
+ directory,
222
+ },
223
+ });
224
+ const config = client.app.config?.authProxy;
225
+ const PORT = config?.port ?? Number(process.env.PORT ?? 4096);
226
+ const TARGET_HOST = config?.targetHost ?? (process.env.TARGET_HOST ?? "127.0.0.1");
227
+ const TARGET_PORT = config?.targetPort ?? Number(process.env.TARGET_PORT ?? 4097);
228
+ const GOOGLE_CLIENT_ID = process.env.GOOGLE_REDIRECT_CLIENT_ID?.trim() ?? config?.googleClientId?.trim() ?? "";
229
+ const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_REDIRECT_CLIENT_SECRET?.trim() ?? config?.googleClientSecret?.trim() ?? "";
230
+ const GOOGLE_REDIRECT_URI = normalizeRedirectUri(config?.googleRedirectUri ?? process.env.GOOGLE_REDIRECT_URI, PORT);
231
+ const providedSessionSecret = process.env.SESSION_SECRET?.trim() ?? config?.sessionSecret?.trim();
232
+ const SESSION_SECRET = providedSessionSecret ?? crypto.randomBytes(32).toString("hex");
233
+ if (!providedSessionSecret) {
234
+ await client.app.log({
235
+ service: "opencode-auth-proxy",
236
+ level: "warn",
237
+ message: "SESSION_SECRET not provided, auto-generating. Sessions will not persist across restarts.",
238
+ });
239
+ }
240
+ const SESSION_COOKIE_NAME = config?.sessionCookieName ?? process.env.SESSION_COOKIE_NAME ?? "opencode_session";
241
+ const TOKEN_PATH = config?.tokenPath || process.env.TOKEN_PATH || path.join(os.homedir(), ".opencode-proxy", "google-auth.json");
242
+ const COOKIE_SECURE = (() => {
243
+ if (config?.cookieSecure !== undefined)
244
+ return config.cookieSecure;
245
+ if (process.env.COOKIE_SECURE !== undefined)
246
+ return process.env.COOKIE_SECURE !== "false";
247
+ try {
248
+ const redirectUrl = new URL(GOOGLE_REDIRECT_URI);
249
+ return redirectUrl.protocol === "https:";
250
+ }
251
+ catch {
252
+ return false;
253
+ }
254
+ })();
255
+ if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
256
+ await client.app.log({
257
+ service: "opencode-auth-proxy",
258
+ level: "error",
259
+ message: "Google OAuth credentials are required (GOOGLE_REDIRECT_CLIENT_ID and GOOGLE_REDIRECT_CLIENT_SECRET)",
260
+ extra: {
261
+ hasClientId: !!GOOGLE_CLIENT_ID,
262
+ hasClientSecret: !!GOOGLE_CLIENT_SECRET,
263
+ hasEnvClientId: !!process.env.GOOGLE_REDIRECT_CLIENT_ID,
264
+ hasEnvClientSecret: !!process.env.GOOGLE_REDIRECT_CLIENT_SECRET,
265
+ },
266
+ });
267
+ return {};
268
+ }
269
+ const target = `http://${TARGET_HOST}:${TARGET_PORT}`;
270
+ const CALLBACK_PATH = getCallbackPath(GOOGLE_REDIRECT_URI);
271
+ const PUBLIC_PATHS = new Set([
272
+ "/health",
273
+ "/global/health",
274
+ "/auth/google/start",
275
+ "/auth/google/config",
276
+ CALLBACK_PATH,
277
+ "/auth/google/callback",
278
+ ]);
279
+ function isPublicPath(pathname) {
280
+ return PUBLIC_PATHS.has(pathname);
281
+ }
282
+ const proxy = httpProxy.createProxyServer({
283
+ target,
284
+ changeOrigin: true,
285
+ ws: true,
286
+ });
287
+ proxy.on("error", async (err, req, res) => {
288
+ const url = req.url || "/";
289
+ const method = req.method || "UNKNOWN";
290
+ await client.app.log({
291
+ service: "opencode-auth-proxy",
292
+ level: "error",
293
+ message: "Proxy error",
294
+ extra: {
295
+ message: err?.message,
296
+ code: err?.code,
297
+ errno: err?.errno,
298
+ syscall: err?.syscall,
299
+ target,
300
+ url,
301
+ method,
302
+ stack: err?.stack,
303
+ },
304
+ });
305
+ const response = res;
306
+ if (response && !response.headersSent) {
307
+ response.writeHead(502, { "Content-Type": "application/json" });
308
+ response.end(JSON.stringify({
309
+ error: "Proxy error",
310
+ message: err?.message,
311
+ code: err?.code,
312
+ target,
313
+ }));
314
+ }
315
+ });
316
+ const server = http.createServer(async (req, res) => {
317
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
318
+ const pathname = url.pathname || "/";
319
+ const accept = String(req.headers.accept || "");
320
+ const isSSE = accept.includes("text/event-stream") || pathname === "/event" || pathname === "/global/event";
321
+ const rejectRequest = async () => {
322
+ if (isSSE) {
323
+ await client.app.log({
324
+ service: "opencode-auth-proxy",
325
+ level: "warn",
326
+ message: "Unauthorized request",
327
+ extra: { method: req.method, path: pathname, kind: "sse" },
328
+ });
329
+ res.writeHead(401, { "Content-Type": "application/json" });
330
+ res.write(JSON.stringify({ error: "unauthorized" }));
331
+ }
332
+ else if (req.method && req.method.toUpperCase() === "GET") {
333
+ await client.app.log({
334
+ service: "opencode-auth-proxy",
335
+ level: "warn",
336
+ message: "Unauthorized request",
337
+ extra: { method: req.method, path: pathname, kind: "redirect" },
338
+ });
339
+ res.writeHead(302, { Location: "/auth/google/start" });
340
+ }
341
+ else {
342
+ await client.app.log({
343
+ service: "opencode-auth-proxy",
344
+ level: "warn",
345
+ message: "Unauthorized request",
346
+ extra: { method: req.method, path: pathname, kind: "api" },
347
+ });
348
+ res.writeHead(401, { "Content-Type": "application/json" });
349
+ res.write(JSON.stringify({ error: "unauthorized" }));
350
+ }
351
+ res.end();
352
+ };
353
+ if (!isPublicPath(pathname)) {
354
+ const cookies = parseCookies(req);
355
+ const isAuthenticated = cookies[SESSION_COOKIE_NAME]
356
+ ? verifySession(cookies[SESSION_COOKIE_NAME], SESSION_SECRET)
357
+ : false;
358
+ if (!isAuthenticated) {
359
+ await rejectRequest();
360
+ return;
361
+ }
362
+ }
363
+ if (pathname === "/health" || pathname === "/global/health") {
364
+ const upstream = await (async () => {
365
+ try {
366
+ const response = await fetchWithTimeout(`${target}/global/health`, { method: "GET" }, 2000);
367
+ const body = await response
368
+ .json()
369
+ .catch(async () => ({ text: await response.text().catch(() => "") }));
370
+ return { ok: response.ok, status: response.status, body };
371
+ }
372
+ catch (e) {
373
+ return { ok: false, error: e instanceof Error ? e.message : String(e) };
374
+ }
375
+ })();
376
+ res.writeHead(200, { "Content-Type": "application/json" });
377
+ res.end(JSON.stringify({ status: "ok", healthy: true, target, upstream }));
378
+ return;
379
+ }
380
+ if (pathname === "/auth/google/config") {
381
+ res.writeHead(200, { "Content-Type": "application/json" });
382
+ res.end(JSON.stringify({
383
+ redirect_uri: GOOGLE_REDIRECT_URI,
384
+ callback_path: CALLBACK_PATH,
385
+ client_id: GOOGLE_CLIENT_ID.substring(0, 20) + "...",
386
+ client_secret_set: !!GOOGLE_CLIENT_SECRET,
387
+ scopes: GOOGLE_SCOPES,
388
+ }));
389
+ return;
390
+ }
391
+ if (pathname === "/auth/google/start") {
392
+ try {
393
+ const projectId = url.searchParams.get("project") || "";
394
+ const result = await authorizeGoogle(GOOGLE_CLIENT_ID, GOOGLE_REDIRECT_URI, projectId);
395
+ res.writeHead(302, { Location: result.url });
396
+ res.end();
397
+ }
398
+ catch (err) {
399
+ await client.app.log({
400
+ service: "opencode-auth-proxy",
401
+ level: "error",
402
+ message: "Auth start error",
403
+ extra: { error: err instanceof Error ? err.message : String(err) },
404
+ });
405
+ res.writeHead(500, { "Content-Type": "application/json" });
406
+ res.end(JSON.stringify({ error: err?.message || "auth start failed" }));
407
+ }
408
+ return;
409
+ }
410
+ if (pathname === CALLBACK_PATH || pathname === "/auth/google/callback") {
411
+ const code = url.searchParams.get("code");
412
+ const state = url.searchParams.get("state") || "";
413
+ if (!code) {
414
+ res.writeHead(400, { "Content-Type": "application/json" });
415
+ res.end(JSON.stringify({ error: "missing code" }));
416
+ return;
417
+ }
418
+ try {
419
+ const result = await exchangeGoogle(code, state, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, client.app.log.bind(client.app));
420
+ if (result.type !== "success") {
421
+ res.writeHead(500, { "Content-Type": "application/json" });
422
+ res.end(JSON.stringify(result));
423
+ return;
424
+ }
425
+ const payload = { ...result, storedAt: new Date().toISOString() };
426
+ persistToken(payload, TOKEN_PATH);
427
+ const cookie = `${SESSION_COOKIE_NAME}=${signSession(SESSION_SECRET)}; Path=/; HttpOnly; SameSite=Lax${COOKIE_SECURE ? "; Secure" : ""}`;
428
+ const redirectTo = url.searchParams.get("redirect");
429
+ const targetPath = redirectTo && redirectTo.startsWith("/") ? redirectTo : "/";
430
+ res.writeHead(302, { Location: targetPath, "Set-Cookie": cookie });
431
+ res.end();
432
+ }
433
+ catch (err) {
434
+ await client.app.log({
435
+ service: "opencode-auth-proxy",
436
+ level: "error",
437
+ message: "Auth callback error",
438
+ extra: { error: err instanceof Error ? err.message : String(err) },
439
+ });
440
+ res.writeHead(500, { "Content-Type": "application/json" });
441
+ res.end(JSON.stringify({ error: err?.message || "auth callback failed" }));
442
+ }
443
+ return;
444
+ }
445
+ if (url.pathname === "/auth/google/token") {
446
+ const token = loadToken(TOKEN_PATH);
447
+ if (!token) {
448
+ res.writeHead(404, { "Content-Type": "application/json" });
449
+ res.end(JSON.stringify({ error: "token not found" }));
450
+ return;
451
+ }
452
+ res.writeHead(200, { "Content-Type": "application/json" });
453
+ res.end(JSON.stringify({ token }));
454
+ return;
455
+ }
456
+ proxy.web(req, res, { target });
457
+ });
458
+ server.on("upgrade", async (req, socket, head) => {
459
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
460
+ if (!isPublicPath(url.pathname || "/")) {
461
+ const cookies = parseCookies(req);
462
+ const isAuthenticated = cookies[SESSION_COOKIE_NAME]
463
+ ? verifySession(cookies[SESSION_COOKIE_NAME], SESSION_SECRET)
464
+ : false;
465
+ if (!isAuthenticated) {
466
+ await client.app.log({
467
+ service: "opencode-auth-proxy",
468
+ level: "warn",
469
+ message: "Unauthorized WebSocket upgrade",
470
+ extra: { method: "UPGRADE", path: url.pathname || "/" },
471
+ });
472
+ socket.destroy();
473
+ return;
474
+ }
475
+ }
476
+ proxy.ws(req, socket, head);
477
+ });
478
+ try {
479
+ server.listen(PORT, async () => {
480
+ await client.app.log({
481
+ service: "opencode-auth-proxy",
482
+ level: "info",
483
+ message: `Proxy server started`,
484
+ extra: {
485
+ port: PORT,
486
+ target,
487
+ },
488
+ });
489
+ });
490
+ }
491
+ catch (error) {
492
+ await client.app.log({
493
+ service: "opencode-auth-proxy",
494
+ level: "error",
495
+ message: "Failed to start proxy server",
496
+ extra: {
497
+ error: error instanceof Error ? error.message : String(error),
498
+ port: PORT,
499
+ },
500
+ });
501
+ }
502
+ return {
503
+ event: async () => { },
504
+ };
505
+ };
506
+ export { opencodeAuthProxy };
507
+ export default opencodeAuthProxy;
508
+ //# sourceMappingURL=opencode-auth-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-auth-proxy.js","sourceRoot":"","sources":["../opencode-auth-proxy.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,15 +1,18 @@
1
1
  {
2
2
  "name": "opencode-auth-proxy",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Google OAuth authentication proxy plugin for OpenCode - provides secure OAuth flow with session management",
5
5
  "type": "module",
6
- "module": "opencode-auth-proxy.ts",
7
- "main": "opencode-auth-proxy.ts",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
8
  "exports": {
9
- ".": "./opencode-auth-proxy.ts"
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
10
13
  },
11
14
  "files": [
12
- "opencode-auth-proxy.ts",
15
+ "dist/",
13
16
  "README.md",
14
17
  "LICENSE",
15
18
  "CHANGELOG.md"
@@ -32,6 +35,10 @@
32
35
  "engines": {
33
36
  "node": ">=20.0.0"
34
37
  },
38
+ "scripts": {
39
+ "build": "tsc -p tsconfig.build.json",
40
+ "prepublishOnly": "npm run build"
41
+ },
35
42
  "peerDependencies": {
36
43
  "typescript": "^5"
37
44
  },
@@ -40,6 +47,8 @@
40
47
  "http-proxy": "^1.18.1"
41
48
  },
42
49
  "devDependencies": {
43
- "@types/node": "^20.0.0"
50
+ "@types/http-proxy": "^1.17.17",
51
+ "@types/node": "^20.0.0",
52
+ "typescript": "^5.9.3"
44
53
  }
45
54
  }
@@ -1,588 +0,0 @@
1
- import type { Plugin } from "@opencode-ai/plugin"
2
- import http, { IncomingMessage, ServerResponse } from "http"
3
- import httpProxy from "http-proxy"
4
- import fs from "fs"
5
- import path from "path"
6
- import crypto from "crypto"
7
- import os from "os"
8
-
9
- type AuthProxyConfig = {
10
- port?: number
11
- targetPort?: number
12
- targetHost?: string
13
- googleClientId?: string
14
- googleClientSecret?: string
15
- googleRedirectUri?: string
16
- sessionSecret?: string
17
- sessionCookieName?: string
18
- cookieSecure?: boolean
19
- tokenPath?: string
20
- }
21
-
22
- type TokenSuccess = {
23
- type: "success"
24
- refresh: string
25
- access: string
26
- expires: number
27
- email?: string
28
- projectId: string
29
- }
30
-
31
- type TokenPayload = TokenSuccess | { type: "failed"; error: string }
32
-
33
- const GOOGLE_SCOPES = [
34
- "https://www.googleapis.com/auth/cloud-platform",
35
- "https://www.googleapis.com/auth/userinfo.email",
36
- "https://www.googleapis.com/auth/userinfo.profile",
37
- ]
38
-
39
- function base64url(buf: Buffer): string {
40
- return buf
41
- .toString("base64")
42
- .replace(/\+/g, "-")
43
- .replace(/\//g, "_")
44
- .replace(/=+$/, "")
45
- }
46
-
47
- function encodeState(payload: { verifier: string; projectId: string }): string {
48
- return Buffer.from(JSON.stringify(payload), "utf8").toString("base64url")
49
- }
50
-
51
- function decodeState(state: string): { verifier: string; projectId: string } {
52
- const normalized = state.replace(/-/g, "+").replace(/_/g, "/")
53
- const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=")
54
- const json = Buffer.from(padded, "base64").toString("utf8")
55
- const parsed = JSON.parse(json)
56
- return { verifier: parsed.verifier || "", projectId: parsed.projectId || "" }
57
- }
58
-
59
- function generatePkce() {
60
- const verifier = base64url(crypto.randomBytes(32))
61
- const challenge = base64url(crypto.createHash("sha256").update(verifier).digest())
62
- return { verifier, challenge }
63
- }
64
-
65
- function parseCookies(req: IncomingMessage): Record<string, string> {
66
- const header = req.headers.cookie
67
- if (!header) return {}
68
- return header.split(";").reduce<Record<string, string>>((acc, part) => {
69
- const [key, ...valueParts] = part.trim().split("=")
70
- if (!key) return acc
71
- acc[key] = valueParts.join("=")
72
- return acc
73
- }, {})
74
- }
75
-
76
- function signSession(sessionSecret: string): string {
77
- const sessionData = Buffer.from("authenticated", "utf8").toString("base64url")
78
- const signature = crypto.createHmac("sha256", sessionSecret).update(sessionData).digest("hex")
79
- return `${sessionData}.${signature}`
80
- }
81
-
82
- function verifySession(token: string, sessionSecret: string): boolean {
83
- const [sessionPart, signature] = token.split(".")
84
- if (!sessionPart || !signature) return false
85
- const expected = crypto.createHmac("sha256", sessionSecret).update(sessionPart).digest("hex")
86
- return crypto.timingSafeEqual(Buffer.from(signature, "utf8"), Buffer.from(expected, "utf8"))
87
- }
88
-
89
- function expandPath(filePath: string): string {
90
- if (filePath.startsWith("~/")) {
91
- return path.join(os.homedir(), filePath.slice(2))
92
- }
93
- return filePath
94
- }
95
-
96
- function loadToken(tokenPath: string): TokenSuccess | null {
97
- try {
98
- const expandedPath = expandPath(tokenPath)
99
- const raw = fs.readFileSync(expandedPath, "utf8")
100
- const parsed = JSON.parse(raw)
101
- if (typeof parsed !== "object" || !parsed) return null
102
- if ("access" in parsed && "refresh" in parsed) {
103
- return parsed as TokenSuccess
104
- }
105
- return null
106
- } catch {
107
- return null
108
- }
109
- }
110
-
111
- function persistToken(token: TokenSuccess, tokenPath: string) {
112
- const expandedPath = expandPath(tokenPath)
113
- const dir = path.dirname(expandedPath)
114
- fs.mkdirSync(dir, { recursive: true })
115
- fs.writeFileSync(expandedPath, JSON.stringify(token, null, 2))
116
- }
117
-
118
- async function fetchWithTimeout(url: string, options: RequestInit, timeoutMs = 10000): Promise<Response> {
119
- const controller = new AbortController()
120
- const timeout = setTimeout(() => controller.abort(), timeoutMs)
121
- try {
122
- return await fetch(url, { ...options, signal: controller.signal })
123
- } finally {
124
- clearTimeout(timeout)
125
- }
126
- }
127
-
128
- async function authorizeGoogle(
129
- clientId: string,
130
- redirectUri: string,
131
- projectId = "",
132
- ): Promise<{ url: string; verifier: string; projectId: string }> {
133
- const pkce = generatePkce()
134
- const url = new URL("https://accounts.google.com/o/oauth2/v2/auth")
135
- url.searchParams.set("client_id", clientId)
136
- url.searchParams.set("response_type", "code")
137
- url.searchParams.set("redirect_uri", redirectUri)
138
- url.searchParams.set("scope", GOOGLE_SCOPES.join(" "))
139
- url.searchParams.set("code_challenge", pkce.challenge)
140
- url.searchParams.set("code_challenge_method", "S256")
141
- url.searchParams.set("state", encodeState({ verifier: pkce.verifier, projectId: projectId || "" }))
142
- url.searchParams.set("access_type", "offline")
143
- url.searchParams.set("prompt", "consent")
144
- return { url: url.toString(), verifier: pkce.verifier, projectId: projectId || "" }
145
- }
146
-
147
- async function exchangeGoogle(
148
- code: string,
149
- state: string,
150
- clientId: string,
151
- clientSecret: string,
152
- redirectUri: string,
153
- log: (data: any) => Promise<void>,
154
- ): Promise<TokenPayload> {
155
- try {
156
- const { verifier } = decodeState(state || "")
157
- const startTime = Date.now()
158
- const requestBody = new URLSearchParams({
159
- client_id: clientId,
160
- client_secret: clientSecret,
161
- code,
162
- grant_type: "authorization_code",
163
- redirect_uri: redirectUri,
164
- code_verifier: verifier,
165
- })
166
- await log({
167
- service: "opencode-auth-proxy",
168
- level: "info",
169
- message: "OAuth token exchange request",
170
- extra: {
171
- redirect_uri: redirectUri,
172
- client_id: clientId,
173
- client_secret_length: clientSecret?.length || 0,
174
- code_length: code?.length || 0,
175
- has_verifier: !!verifier,
176
- },
177
- })
178
- const tokenResponse = await fetch("https://oauth2.googleapis.com/token", {
179
- method: "POST",
180
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
181
- body: requestBody,
182
- })
183
- if (!tokenResponse.ok) {
184
- const errorText = await tokenResponse.text()
185
- let parsedError: any = {}
186
- try {
187
- parsedError = JSON.parse(errorText)
188
- } catch {
189
- parsedError = { raw: errorText }
190
- }
191
- await log({
192
- service: "opencode-auth-proxy",
193
- level: "error",
194
- message: "OAuth token exchange failed",
195
- extra: {
196
- status: tokenResponse.status,
197
- statusText: tokenResponse.statusText,
198
- error: parsedError,
199
- redirect_uri: redirectUri,
200
- redirect_uri_length: redirectUri.length,
201
- client_id: clientId,
202
- client_id_length: clientId.length,
203
- client_secret_set: !!clientSecret,
204
- client_secret_length: clientSecret?.length || 0,
205
- },
206
- })
207
- const errorMessage = parsedError.error_description || parsedError.error || errorText
208
- return { type: "failed", error: errorMessage }
209
- }
210
-
211
- const tokenPayload = (await tokenResponse.json()) as {
212
- access_token: string
213
- expires_in: number
214
- refresh_token?: string
215
- }
216
- const userInfoResponse = await fetch("https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {
217
- headers: { Authorization: `Bearer ${tokenPayload.access_token}` },
218
- })
219
- const userInfo = userInfoResponse.ok ? ((await userInfoResponse.json()) as { email?: string }) : {}
220
-
221
- const refreshToken = tokenPayload.refresh_token
222
- if (!refreshToken) return { type: "failed", error: "Missing refresh token in response" }
223
-
224
- return {
225
- type: "success",
226
- refresh: refreshToken,
227
- access: tokenPayload.access_token,
228
- expires: startTime + (tokenPayload.expires_in || 0) * 1000,
229
- email: userInfo.email,
230
- projectId: "",
231
- }
232
- } catch (error) {
233
- return { type: "failed", error: error instanceof Error ? error.message : "Unknown error" }
234
- }
235
- }
236
-
237
- function normalizeRedirectUri(uri: string | undefined, defaultPort: number): string {
238
- const defaultUri = `http://localhost:${defaultPort}/auth/google/callback`
239
- const rawUri = uri?.trim() || defaultUri
240
- try {
241
- const url = new URL(rawUri)
242
- if (url.pathname.endsWith("/") && url.pathname !== "/") {
243
- url.pathname = url.pathname.replace(/\/+$/, "")
244
- return url.toString()
245
- }
246
- return rawUri
247
- } catch (err) {
248
- return rawUri
249
- }
250
- }
251
-
252
- function getCallbackPath(redirectUri: string): string {
253
- try {
254
- const url = new URL(redirectUri)
255
- return url.pathname || "/auth/google/callback"
256
- } catch {
257
- return "/auth/google/callback"
258
- }
259
- }
260
-
261
- const opencodeAuthProxy: Plugin = async ({ client, directory }) => {
262
- await client.app.log({
263
- service: "opencode-auth-proxy",
264
- level: "info",
265
- message: "Plugin initializing",
266
- extra: {
267
- hasEnvPort: !!process.env.PORT,
268
- hasEnvSessionSecret: !!process.env.SESSION_SECRET,
269
- hasEnvGoogleId: !!process.env.GOOGLE_REDIRECT_CLIENT_ID,
270
- directory,
271
- },
272
- })
273
-
274
- const config = (client.app.config as { authProxy?: AuthProxyConfig })?.authProxy
275
-
276
- const PORT = config?.port ?? Number(process.env.PORT ?? 4096)
277
- const TARGET_HOST = config?.targetHost ?? (process.env.TARGET_HOST ?? "127.0.0.1")
278
- const TARGET_PORT = config?.targetPort ?? Number(process.env.TARGET_PORT ?? 4097)
279
- const GOOGLE_CLIENT_ID =
280
- process.env.GOOGLE_REDIRECT_CLIENT_ID?.trim() ?? config?.googleClientId?.trim() ?? ""
281
- const GOOGLE_CLIENT_SECRET =
282
- process.env.GOOGLE_REDIRECT_CLIENT_SECRET?.trim() ?? config?.googleClientSecret?.trim() ?? ""
283
- const GOOGLE_REDIRECT_URI = normalizeRedirectUri(
284
- config?.googleRedirectUri ?? process.env.GOOGLE_REDIRECT_URI,
285
- PORT,
286
- )
287
- const providedSessionSecret = process.env.SESSION_SECRET?.trim() ?? config?.sessionSecret?.trim()
288
- const SESSION_SECRET = providedSessionSecret ?? crypto.randomBytes(32).toString("hex")
289
- if (!providedSessionSecret) {
290
- await client.app.log({
291
- service: "opencode-auth-proxy",
292
- level: "warn",
293
- message: "SESSION_SECRET not provided, auto-generating. Sessions will not persist across restarts.",
294
- })
295
- }
296
- const SESSION_COOKIE_NAME = config?.sessionCookieName ?? process.env.SESSION_COOKIE_NAME ?? "opencode_session"
297
- const TOKEN_PATH =
298
- config?.tokenPath || process.env.TOKEN_PATH || path.join(os.homedir(), ".opencode-proxy", "google-auth.json")
299
-
300
- const COOKIE_SECURE = (() => {
301
- if (config?.cookieSecure !== undefined) return config.cookieSecure
302
- if (process.env.COOKIE_SECURE !== undefined) return process.env.COOKIE_SECURE !== "false"
303
- try {
304
- const redirectUrl = new URL(GOOGLE_REDIRECT_URI)
305
- return redirectUrl.protocol === "https:"
306
- } catch {
307
- return false
308
- }
309
- })()
310
-
311
-
312
- if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
313
- await client.app.log({
314
- service: "opencode-auth-proxy",
315
- level: "error",
316
- message: "Google OAuth credentials are required (GOOGLE_REDIRECT_CLIENT_ID and GOOGLE_REDIRECT_CLIENT_SECRET)",
317
- extra: {
318
- hasClientId: !!GOOGLE_CLIENT_ID,
319
- hasClientSecret: !!GOOGLE_CLIENT_SECRET,
320
- hasEnvClientId: !!process.env.GOOGLE_REDIRECT_CLIENT_ID,
321
- hasEnvClientSecret: !!process.env.GOOGLE_REDIRECT_CLIENT_SECRET,
322
- },
323
- })
324
- return {}
325
- }
326
-
327
- const target = `http://${TARGET_HOST}:${TARGET_PORT}`
328
- const CALLBACK_PATH = getCallbackPath(GOOGLE_REDIRECT_URI)
329
- const PUBLIC_PATHS = new Set([
330
- "/health",
331
- "/global/health",
332
- "/auth/google/start",
333
- "/auth/google/config",
334
- CALLBACK_PATH,
335
- "/auth/google/callback",
336
- ])
337
-
338
- function isPublicPath(pathname: string): boolean {
339
- return PUBLIC_PATHS.has(pathname)
340
- }
341
-
342
- const proxy = httpProxy.createProxyServer({
343
- target,
344
- changeOrigin: true,
345
- ws: true,
346
- })
347
-
348
- proxy.on("error", async (err: any, req: IncomingMessage, res: any) => {
349
- const url = req.url || "/"
350
- const method = req.method || "UNKNOWN"
351
- await client.app.log({
352
- service: "opencode-auth-proxy",
353
- level: "error",
354
- message: "Proxy error",
355
- extra: {
356
- message: err?.message,
357
- code: err?.code,
358
- errno: err?.errno,
359
- syscall: err?.syscall,
360
- target,
361
- url,
362
- method,
363
- stack: err?.stack,
364
- },
365
- })
366
- const response = res as ServerResponse | undefined
367
- if (response && !response.headersSent) {
368
- response.writeHead(502, { "Content-Type": "application/json" })
369
- response.end(
370
- JSON.stringify({
371
- error: "Proxy error",
372
- message: err?.message,
373
- code: err?.code,
374
- target,
375
- }),
376
- )
377
- }
378
- })
379
-
380
- const server = http.createServer(async (req: IncomingMessage, res: ServerResponse) => {
381
- const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`)
382
- const pathname = url.pathname || "/"
383
- const accept = String(req.headers.accept || "")
384
- const isSSE = accept.includes("text/event-stream") || pathname === "/event" || pathname === "/global/event"
385
-
386
- const rejectRequest = async () => {
387
- if (isSSE) {
388
- await client.app.log({
389
- service: "opencode-auth-proxy",
390
- level: "warn",
391
- message: "Unauthorized request",
392
- extra: { method: req.method, path: pathname, kind: "sse" },
393
- })
394
- res.writeHead(401, { "Content-Type": "application/json" })
395
- res.write(JSON.stringify({ error: "unauthorized" }))
396
- } else if (req.method && req.method.toUpperCase() === "GET") {
397
- await client.app.log({
398
- service: "opencode-auth-proxy",
399
- level: "warn",
400
- message: "Unauthorized request",
401
- extra: { method: req.method, path: pathname, kind: "redirect" },
402
- })
403
- res.writeHead(302, { Location: "/auth/google/start" })
404
- } else {
405
- await client.app.log({
406
- service: "opencode-auth-proxy",
407
- level: "warn",
408
- message: "Unauthorized request",
409
- extra: { method: req.method, path: pathname, kind: "api" },
410
- })
411
- res.writeHead(401, { "Content-Type": "application/json" })
412
- res.write(JSON.stringify({ error: "unauthorized" }))
413
- }
414
- res.end()
415
- }
416
-
417
- if (!isPublicPath(pathname)) {
418
- const cookies = parseCookies(req)
419
- const isAuthenticated = cookies[SESSION_COOKIE_NAME]
420
- ? verifySession(cookies[SESSION_COOKIE_NAME], SESSION_SECRET)
421
- : false
422
- if (!isAuthenticated) {
423
- await rejectRequest()
424
- return
425
- }
426
- }
427
-
428
- if (pathname === "/health" || pathname === "/global/health") {
429
- const upstream = await (async () => {
430
- try {
431
- const response = await fetchWithTimeout(`${target}/global/health`, { method: "GET" }, 2000)
432
- const body = await response
433
- .json()
434
- .catch(async () => ({ text: await response.text().catch(() => "") }))
435
- return { ok: response.ok, status: response.status, body }
436
- } catch (e) {
437
- return { ok: false, error: e instanceof Error ? e.message : String(e) }
438
- }
439
- })()
440
- res.writeHead(200, { "Content-Type": "application/json" })
441
- res.end(JSON.stringify({ status: "ok", healthy: true, target, upstream }))
442
- return
443
- }
444
-
445
- if (pathname === "/auth/google/config") {
446
- res.writeHead(200, { "Content-Type": "application/json" })
447
- res.end(
448
- JSON.stringify({
449
- redirect_uri: GOOGLE_REDIRECT_URI,
450
- callback_path: CALLBACK_PATH,
451
- client_id: GOOGLE_CLIENT_ID.substring(0, 20) + "...",
452
- client_secret_set: !!GOOGLE_CLIENT_SECRET,
453
- scopes: GOOGLE_SCOPES,
454
- }),
455
- )
456
- return
457
- }
458
-
459
- if (pathname === "/auth/google/start") {
460
- try {
461
- const projectId = url.searchParams.get("project") || ""
462
- const result = await authorizeGoogle(GOOGLE_CLIENT_ID, GOOGLE_REDIRECT_URI, projectId)
463
- res.writeHead(302, { Location: result.url })
464
- res.end()
465
- } catch (err) {
466
- await client.app.log({
467
- service: "opencode-auth-proxy",
468
- level: "error",
469
- message: "Auth start error",
470
- extra: { error: err instanceof Error ? err.message : String(err) },
471
- })
472
- res.writeHead(500, { "Content-Type": "application/json" })
473
- res.end(JSON.stringify({ error: (err as Error)?.message || "auth start failed" }))
474
- }
475
- return
476
- }
477
-
478
- if (pathname === CALLBACK_PATH || pathname === "/auth/google/callback") {
479
- const code = url.searchParams.get("code")
480
- const state = url.searchParams.get("state") || ""
481
- if (!code) {
482
- res.writeHead(400, { "Content-Type": "application/json" })
483
- res.end(JSON.stringify({ error: "missing code" }))
484
- return
485
- }
486
- try {
487
- const result = await exchangeGoogle(
488
- code,
489
- state,
490
- GOOGLE_CLIENT_ID,
491
- GOOGLE_CLIENT_SECRET,
492
- GOOGLE_REDIRECT_URI,
493
- client.app.log.bind(client.app),
494
- )
495
- if (result.type !== "success") {
496
- res.writeHead(500, { "Content-Type": "application/json" })
497
- res.end(JSON.stringify(result))
498
- return
499
- }
500
- const payload = { ...result, storedAt: new Date().toISOString() }
501
- persistToken(payload, TOKEN_PATH)
502
-
503
- const cookie = `${SESSION_COOKIE_NAME}=${signSession(SESSION_SECRET)}; Path=/; HttpOnly; SameSite=Lax${COOKIE_SECURE ? "; Secure" : ""}`
504
- const redirectTo = url.searchParams.get("redirect")
505
- const targetPath = redirectTo && redirectTo.startsWith("/") ? redirectTo : "/"
506
-
507
- res.writeHead(302, { Location: targetPath, "Set-Cookie": cookie })
508
- res.end()
509
- } catch (err) {
510
- await client.app.log({
511
- service: "opencode-auth-proxy",
512
- level: "error",
513
- message: "Auth callback error",
514
- extra: { error: err instanceof Error ? err.message : String(err) },
515
- })
516
- res.writeHead(500, { "Content-Type": "application/json" })
517
- res.end(JSON.stringify({ error: (err as Error)?.message || "auth callback failed" }))
518
- }
519
- return
520
- }
521
-
522
- if (url.pathname === "/auth/google/token") {
523
- const token = loadToken(TOKEN_PATH)
524
- if (!token) {
525
- res.writeHead(404, { "Content-Type": "application/json" })
526
- res.end(JSON.stringify({ error: "token not found" }))
527
- return
528
- }
529
- res.writeHead(200, { "Content-Type": "application/json" })
530
- res.end(JSON.stringify({ token }))
531
- return
532
- }
533
-
534
- proxy.web(req, res, { target })
535
- })
536
-
537
- server.on("upgrade", async (req, socket, head) => {
538
- const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`)
539
- if (!isPublicPath(url.pathname || "/")) {
540
- const cookies = parseCookies(req)
541
- const isAuthenticated = cookies[SESSION_COOKIE_NAME]
542
- ? verifySession(cookies[SESSION_COOKIE_NAME], SESSION_SECRET)
543
- : false
544
- if (!isAuthenticated) {
545
- await client.app.log({
546
- service: "opencode-auth-proxy",
547
- level: "warn",
548
- message: "Unauthorized WebSocket upgrade",
549
- extra: { method: "UPGRADE", path: url.pathname || "/" },
550
- })
551
- socket.destroy()
552
- return
553
- }
554
- }
555
- proxy.ws(req, socket, head)
556
- })
557
-
558
- try {
559
- server.listen(PORT, async () => {
560
- await client.app.log({
561
- service: "opencode-auth-proxy",
562
- level: "info",
563
- message: `Proxy server started`,
564
- extra: {
565
- port: PORT,
566
- target,
567
- },
568
- })
569
- })
570
- } catch (error) {
571
- await client.app.log({
572
- service: "opencode-auth-proxy",
573
- level: "error",
574
- message: "Failed to start proxy server",
575
- extra: {
576
- error: error instanceof Error ? error.message : String(error),
577
- port: PORT,
578
- },
579
- })
580
- }
581
-
582
- return {
583
- event: async () => {},
584
- }
585
- }
586
-
587
- export { opencodeAuthProxy }
588
- export default opencodeAuthProxy