99import { createServer as createHttpsServer } from "node:https" ;
1010import type { CanvasHostHandler } from "../canvas-host/server.js" ;
1111import type { createSubsystemLogger } from "../logging/subsystem.js" ;
12+ import type { GatewayWsClient } from "./server/ws-types.js" ;
1213import { resolveAgentAvatar } from "../agents/identity-avatar.js" ;
1314import {
1415 A2UI_PATH ,
@@ -18,7 +19,7 @@ import {
1819} from "../canvas-host/a2ui.js" ;
1920import { loadConfig } from "../config/config.js" ;
2021import { handleSlackHttpRequest } from "../slack/http/index.js" ;
21- import { authorizeGatewayConnect } from "./auth.js" ;
22+ import { authorizeGatewayConnect , isLocalDirectRequest , type ResolvedGatewayAuth } from "./auth.js" ;
2223import {
2324 handleControlUiAvatarRequest ,
2425 handleControlUiHttpRequest ,
@@ -38,7 +39,8 @@ import {
3839 resolveHookDeliver ,
3940} from "./hooks.js" ;
4041import { sendUnauthorized } from "./http-common.js" ;
41- import { getBearerToken } from "./http-utils.js" ;
42+ import { getBearerToken , getHeader } from "./http-utils.js" ;
43+ import { resolveGatewayClientIp } from "./net.js" ;
4244import { handleOpenAiHttpRequest } from "./openai-http.js" ;
4345import { handleOpenResponsesHttpRequest } from "./openresponses-http.js" ;
4446import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js" ;
@@ -78,6 +80,51 @@ function isCanvasPath(pathname: string): boolean {
7880 ) ;
7981}
8082
83+ function hasAuthorizedWsClientForIp ( clients : Set < GatewayWsClient > , clientIp : string ) : boolean {
84+ for ( const client of clients ) {
85+ if ( client . clientIp && client . clientIp === clientIp ) {
86+ return true ;
87+ }
88+ }
89+ return false ;
90+ }
91+
92+ async function authorizeCanvasRequest ( params : {
93+ req : IncomingMessage ;
94+ auth : ResolvedGatewayAuth ;
95+ trustedProxies : string [ ] ;
96+ clients : Set < GatewayWsClient > ;
97+ } ) : Promise < boolean > {
98+ const { req, auth, trustedProxies, clients } = params ;
99+ if ( isLocalDirectRequest ( req , trustedProxies ) ) {
100+ return true ;
101+ }
102+
103+ const token = getBearerToken ( req ) ;
104+ if ( token ) {
105+ const authResult = await authorizeGatewayConnect ( {
106+ auth : { ...auth , allowTailscale : false } ,
107+ connectAuth : { token, password : token } ,
108+ req,
109+ trustedProxies,
110+ } ) ;
111+ if ( authResult . ok ) {
112+ return true ;
113+ }
114+ }
115+
116+ const clientIp = resolveGatewayClientIp ( {
117+ remoteAddr : req . socket ?. remoteAddress ?? "" ,
118+ forwardedFor : getHeader ( req , "x-forwarded-for" ) ,
119+ realIp : getHeader ( req , "x-real-ip" ) ,
120+ trustedProxies,
121+ } ) ;
122+ if ( ! clientIp ) {
123+ return false ;
124+ }
125+ return hasAuthorizedWsClientForIp ( clients , clientIp ) ;
126+ }
127+
81128export type HooksRequestHandler = ( req : IncomingMessage , res : ServerResponse ) => Promise < boolean > ;
82129
83130export function createHooksRequestHandler (
@@ -226,6 +273,7 @@ export function createHooksRequestHandler(
226273
227274export function createGatewayHttpServer ( opts : {
228275 canvasHost : CanvasHostHandler | null ;
276+ clients : Set < GatewayWsClient > ;
229277 controlUiEnabled : boolean ;
230278 controlUiBasePath : string ;
231279 controlUiRoot ?: ControlUiRootState ;
@@ -234,11 +282,12 @@ export function createGatewayHttpServer(opts: {
234282 openResponsesConfig ?: import ( "../config/types.gateway.js" ) . GatewayHttpResponsesConfig ;
235283 handleHooksRequest : HooksRequestHandler ;
236284 handlePluginRequest ?: HooksRequestHandler ;
237- resolvedAuth : import ( "./auth.js" ) . ResolvedGatewayAuth ;
285+ resolvedAuth : ResolvedGatewayAuth ;
238286 tlsOptions ?: TlsOptions ;
239287} ) : HttpServer {
240288 const {
241289 canvasHost,
290+ clients,
242291 controlUiEnabled,
243292 controlUiBasePath,
244293 controlUiRoot,
@@ -305,16 +354,15 @@ export function createGatewayHttpServer(opts: {
305354 }
306355 }
307356 if ( canvasHost ) {
308- const url = new URL ( req . url ?? "/" , ` http://${ req . headers . host ?? " localhost"} ` ) ;
357+ const url = new URL ( req . url ?? "/" , " http://localhost") ;
309358 if ( isCanvasPath ( url . pathname ) ) {
310- const token = getBearerToken ( req ) ;
311- const authResult = await authorizeGatewayConnect ( {
312- auth : resolvedAuth ,
313- connectAuth : token ? { token, password : token } : null ,
359+ const ok = await authorizeCanvasRequest ( {
314360 req,
361+ auth : resolvedAuth ,
315362 trustedProxies,
363+ clients,
316364 } ) ;
317- if ( ! authResult . ok ) {
365+ if ( ! ok ) {
318366 sendUnauthorized ( res ) ;
319367 return ;
320368 }
@@ -363,41 +411,38 @@ export function attachGatewayUpgradeHandler(opts: {
363411 httpServer : HttpServer ;
364412 wss : WebSocketServer ;
365413 canvasHost : CanvasHostHandler | null ;
366- resolvedAuth : import ( "./auth.js" ) . ResolvedGatewayAuth ;
414+ clients : Set < GatewayWsClient > ;
415+ resolvedAuth : ResolvedGatewayAuth ;
367416} ) {
368- const { httpServer, wss, canvasHost, resolvedAuth } = opts ;
417+ const { httpServer, wss, canvasHost, clients , resolvedAuth } = opts ;
369418 httpServer . on ( "upgrade" , ( req , socket , head ) => {
370419 void ( async ( ) => {
371420 if ( canvasHost ) {
372- const url = new URL ( req . url ?? "/" , ` http://${ req . headers . host ?? " localhost"} ` ) ;
421+ const url = new URL ( req . url ?? "/" , " http://localhost") ;
373422 if ( url . pathname === CANVAS_WS_PATH ) {
374423 const configSnapshot = loadConfig ( ) ;
375- const token = getBearerToken ( req ) ;
376- const authResult = await authorizeGatewayConnect ( {
377- auth : resolvedAuth ,
378- connectAuth : token ? { token, password : token } : null ,
424+ const trustedProxies = configSnapshot . gateway ?. trustedProxies ?? [ ] ;
425+ const ok = await authorizeCanvasRequest ( {
379426 req,
380- trustedProxies : configSnapshot . gateway ?. trustedProxies ?? [ ] ,
427+ auth : resolvedAuth ,
428+ trustedProxies,
429+ clients,
381430 } ) ;
382- if ( ! authResult . ok ) {
431+ if ( ! ok ) {
383432 socket . write ( "HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n" ) ;
384433 socket . destroy ( ) ;
385434 return ;
386435 }
387436 }
388- }
389- if ( canvasHost ?. handleUpgrade ( req , socket , head ) ) {
390- return ;
437+ if ( canvasHost . handleUpgrade ( req , socket , head ) ) {
438+ return ;
439+ }
391440 }
392441 wss . handleUpgrade ( req , socket , head , ( ws ) => {
393442 wss . emit ( "connection" , ws , req ) ;
394443 } ) ;
395444 } ) ( ) . catch ( ( ) => {
396- try {
397- socket . destroy ( ) ;
398- } catch {
399- // ignore
400- }
445+ socket . destroy ( ) ;
401446 } ) ;
402447 } ) ;
403448}
0 commit comments