darkfire514 commited on
Commit
0c95196
·
verified ·
1 Parent(s): 18278d0

Upload 189 files

Browse files
src/gateway/auth.test.ts CHANGED
@@ -1,4 +1,5 @@
1
  import { describe, expect, it } from "vitest";
 
2
  import { authorizeGatewayConnect } from "./auth.js";
3
 
4
  describe("gateway auth", () => {
@@ -98,4 +99,48 @@ describe("gateway auth", () => {
98
  expect(res.method).toBe("tailscale");
99
  expect(res.user).toBe("peter");
100
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  });
 
1
  import { describe, expect, it } from "vitest";
2
+ import crypto from "node:crypto";
3
  import { authorizeGatewayConnect } from "./auth.js";
4
 
5
  describe("gateway auth", () => {
 
99
  expect(res.method).toBe("tailscale");
100
  expect(res.user).toBe("peter");
101
  });
102
+
103
+ it("allows Control UI oauth session cookie to satisfy token mode auth", async () => {
104
+ const prevEnv = {
105
+ OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_ID: process.env.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_ID,
106
+ OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_SECRET: process.env.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_SECRET,
107
+ OPENCLAW_CONTROL_UI_SESSION_SECRET: process.env.OPENCLAW_CONTROL_UI_SESSION_SECRET,
108
+ OPENCLAW_CONTROL_UI_ALLOWED_GOOGLE_EMAILS: process.env.OPENCLAW_CONTROL_UI_ALLOWED_GOOGLE_EMAILS,
109
+ };
110
+ try {
111
+ process.env.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_ID = "cid";
112
+ process.env.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_SECRET = "csecret";
113
+ process.env.OPENCLAW_CONTROL_UI_SESSION_SECRET = "session-secret";
114
+ process.env.OPENCLAW_CONTROL_UI_ALLOWED_GOOGLE_EMAILS = "admin@example.com";
115
+
116
+ const payload = {
117
+ v: 1,
118
+ exp: Math.floor(Date.now() / 1000) + 60,
119
+ provider: "google",
120
+ sub: "sub-1",
121
+ email: "admin@example.com",
122
+ };
123
+ const body = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
124
+ const sig = crypto.createHmac("sha256", "session-secret").update(body).digest("base64url");
125
+ const cookieToken = `${body}.${sig}`;
126
+
127
+ const res = await authorizeGatewayConnect({
128
+ auth: { mode: "token", token: "gateway-token", allowTailscale: false },
129
+ connectAuth: null,
130
+ client: { id: "openclaw-control-ui", version: "test", platform: "web", mode: "webchat" },
131
+ req: {
132
+ socket: { remoteAddress: "203.0.113.1" },
133
+ headers: { host: "example.com", cookie: `openclaw_ui_session=${encodeURIComponent(cookieToken)}` },
134
+ } as never,
135
+ });
136
+ expect(res.ok).toBe(true);
137
+ expect(res.method).toBe("control-ui-oauth");
138
+ expect(res.user).toBe("admin@example.com");
139
+ } finally {
140
+ process.env.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_ID = prevEnv.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_ID;
141
+ process.env.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_SECRET = prevEnv.OPENCLAW_CONTROL_UI_GOOGLE_CLIENT_SECRET;
142
+ process.env.OPENCLAW_CONTROL_UI_SESSION_SECRET = prevEnv.OPENCLAW_CONTROL_UI_SESSION_SECRET;
143
+ process.env.OPENCLAW_CONTROL_UI_ALLOWED_GOOGLE_EMAILS = prevEnv.OPENCLAW_CONTROL_UI_ALLOWED_GOOGLE_EMAILS;
144
+ }
145
+ });
146
  });
src/gateway/auth.ts CHANGED
@@ -2,6 +2,8 @@ import type { IncomingMessage } from "node:http";
2
  import { timingSafeEqual } from "node:crypto";
3
  import type { GatewayAuthConfig, GatewayTailscaleMode } from "../config/config.js";
4
  import { readTailscaleWhoisIdentity, type TailscaleWhoisIdentity } from "../infra/tailscale.js";
 
 
5
  import { isTrustedProxyAddress, parseForwardedForClientIp, resolveGatewayClientIp } from "./net.js";
6
  export type ResolvedGatewayAuthMode = "token" | "password";
7
 
@@ -14,7 +16,7 @@ export type ResolvedGatewayAuth = {
14
 
15
  export type GatewayAuthResult = {
16
  ok: boolean;
17
- method?: "token" | "password" | "tailscale" | "device-token";
18
  user?: string;
19
  reason?: string;
20
  };
@@ -241,11 +243,19 @@ export async function authorizeGatewayConnect(params: {
241
  req?: IncomingMessage;
242
  trustedProxies?: string[];
243
  tailscaleWhois?: TailscaleWhoisLookup;
 
244
  }): Promise<GatewayAuthResult> {
245
  const { auth, connectAuth, req, trustedProxies } = params;
246
  const tailscaleWhois = params.tailscaleWhois ?? readTailscaleWhoisIdentity;
247
  const localDirect = isLocalDirectRequest(req, trustedProxies);
248
 
 
 
 
 
 
 
 
249
  if (auth.allowTailscale && !localDirect) {
250
  const tailscaleCheck = await resolveVerifiedTailscaleUser({
251
  req,
 
2
  import { timingSafeEqual } from "node:crypto";
3
  import type { GatewayAuthConfig, GatewayTailscaleMode } from "../config/config.js";
4
  import { readTailscaleWhoisIdentity, type TailscaleWhoisIdentity } from "../infra/tailscale.js";
5
+ import { GATEWAY_CLIENT_IDS, type GatewayClientInfo } from "./protocol/client-info.js";
6
+ import { readControlUiOauthSessionIdentityFromRequest } from "./control-ui.js";
7
  import { isTrustedProxyAddress, parseForwardedForClientIp, resolveGatewayClientIp } from "./net.js";
8
  export type ResolvedGatewayAuthMode = "token" | "password";
9
 
 
16
 
17
  export type GatewayAuthResult = {
18
  ok: boolean;
19
+ method?: "token" | "password" | "tailscale" | "device-token" | "control-ui-oauth";
20
  user?: string;
21
  reason?: string;
22
  };
 
243
  req?: IncomingMessage;
244
  trustedProxies?: string[];
245
  tailscaleWhois?: TailscaleWhoisLookup;
246
+ client?: GatewayClientInfo | null;
247
  }): Promise<GatewayAuthResult> {
248
  const { auth, connectAuth, req, trustedProxies } = params;
249
  const tailscaleWhois = params.tailscaleWhois ?? readTailscaleWhoisIdentity;
250
  const localDirect = isLocalDirectRequest(req, trustedProxies);
251
 
252
+ if (auth.mode === "token" && params.client?.id === GATEWAY_CLIENT_IDS.CONTROL_UI && req) {
253
+ const session = readControlUiOauthSessionIdentityFromRequest(req);
254
+ if (session) {
255
+ return { ok: true, method: "control-ui-oauth", user: session.email ?? session.login ?? session.sub };
256
+ }
257
+ }
258
+
259
  if (auth.allowTailscale && !localDirect) {
260
  const tailscaleCheck = await resolveVerifiedTailscaleUser({
261
  req,
src/gateway/control-ui.ts CHANGED
@@ -327,7 +327,7 @@ function getRequestOrigin(req: IncomingMessage) {
327
  }
328
 
329
  function controlUiCookiePath(basePath: string) {
330
- return basePath || "/";
331
  }
332
 
333
  function buildBaseUrlPath(basePath: string, suffix: string) {
@@ -409,6 +409,30 @@ function readSessionFromRequest(req: IncomingMessage, cfg: ControlUiOauthConfig)
409
  return value;
410
  }
411
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  type OAuthStatePayload = { v: 1; exp: number; nonce: string; provider: ControlUiOauthProvider };
413
 
414
  function issueOAuthState(res: ServerResponse, cfg: ControlUiOauthConfig, basePath: string, secure: boolean, provider: ControlUiOauthProvider) {
 
327
  }
328
 
329
  function controlUiCookiePath(basePath: string) {
330
+ return "/";
331
  }
332
 
333
  function buildBaseUrlPath(basePath: string, suffix: string) {
 
409
  return value;
410
  }
411
 
412
+ export type ControlUiOauthSessionIdentity = {
413
+ provider: "google" | "github";
414
+ sub: string;
415
+ email?: string;
416
+ login?: string;
417
+ name?: string;
418
+ };
419
+
420
+ export function readControlUiOauthSessionIdentityFromRequest(
421
+ req: IncomingMessage,
422
+ ): ControlUiOauthSessionIdentity | null {
423
+ const cfg = resolveControlUiOauthConfig();
424
+ if (!isControlUiOauthEnabled(cfg)) return null;
425
+ const session = readSessionFromRequest(req, cfg);
426
+ if (!session) return null;
427
+ return {
428
+ provider: session.provider,
429
+ sub: session.sub,
430
+ email: session.email,
431
+ login: session.login,
432
+ name: session.name,
433
+ };
434
+ }
435
+
436
  type OAuthStatePayload = { v: 1; exp: number; nonce: string; provider: ControlUiOauthProvider };
437
 
438
  function issueOAuthState(res: ServerResponse, cfg: ControlUiOauthConfig, basePath: string, secure: boolean, provider: ControlUiOauthProvider) {
src/gateway/server/ws-connection/message-handler.ts CHANGED
@@ -572,6 +572,7 @@ export function attachGatewayWsMessageHandler(params: {
572
  connectAuth: connectParams.auth,
573
  req: upgradeReq,
574
  trustedProxies,
 
575
  });
576
  let authOk = authResult.ok;
577
  let authMethod =
 
572
  connectAuth: connectParams.auth,
573
  req: upgradeReq,
574
  trustedProxies,
575
+ client: connectParams.client,
576
  });
577
  let authOk = authResult.ok;
578
  let authMethod =