11import { Pty } from "@/pty"
22import { PtyID } from "@/pty/schema"
3+ import { PtyTicket } from "@/pty/ticket"
34import { handlePtyInput } from "@/pty/input"
45import { Shell } from "@/shell/shell"
56import { EffectBridge } from "@/effect/bridge"
7+ import { CorsConfig , isAllowedRequestOrigin , type CorsOptions } from "@/server/cors"
8+ import {
9+ PTY_CONNECT_TICKET_QUERY ,
10+ PTY_CONNECT_TOKEN_HEADER ,
11+ PTY_CONNECT_TOKEN_HEADER_VALUE ,
12+ } from "@/server/shared/pty-ticket"
613import { Effect } from "effect"
714import { HttpRouter , HttpServerRequest , HttpServerResponse } from "effect/unstable/http"
815import { HttpApiBuilder , HttpApiError } from "effect/unstable/httpapi"
@@ -11,9 +18,15 @@ import { InstanceHttpApi } from "../api"
1118import { CursorQuery , Params , PtyPaths } from "../groups/pty"
1219import { WebSocketTracker } from "../websocket-tracker"
1320
21+ function validOrigin ( request : HttpServerRequest . HttpServerRequest , opts : CorsOptions | undefined ) {
22+ return isAllowedRequestOrigin ( request . headers . origin , request . headers . host , opts )
23+ }
24+
1425export const ptyHandlers = HttpApiBuilder . group ( InstanceHttpApi , "pty" , ( handlers ) =>
1526 Effect . gen ( function * ( ) {
1627 const pty = yield * Pty . Service
28+ const tickets = yield * PtyTicket . Service
29+ const cors = yield * CorsConfig
1730
1831 const shells = Effect . fn ( "PtyHttpApi.shells" ) ( function * ( ) {
1932 return yield * Effect . promise ( ( ) => Shell . list ( ) )
@@ -54,19 +67,30 @@ export const ptyHandlers = HttpApiBuilder.group(InstanceHttpApi, "pty", (handler
5467 return true
5568 } )
5669
70+ const connectToken = Effect . fn ( "PtyHttpApi.connectToken" ) ( function * ( ctx : { params : { ptyID : PtyID } } ) {
71+ const request = yield * HttpServerRequest . HttpServerRequest
72+ if ( request . headers [ PTY_CONNECT_TOKEN_HEADER ] !== PTY_CONNECT_TOKEN_HEADER_VALUE || ! validOrigin ( request , cors ) )
73+ return yield * new HttpApiError . Forbidden ( { } )
74+ if ( ! ( yield * pty . get ( ctx . params . ptyID ) ) ) return yield * new HttpApiError . NotFound ( { } )
75+ return yield * tickets . issue ( { ptyID : ctx . params . ptyID , ...( yield * PtyTicket . scope ) } )
76+ } )
77+
5778 return handlers
5879 . handle ( "shells" , shells )
5980 . handle ( "list" , list )
6081 . handle ( "create" , create )
6182 . handle ( "get" , get )
6283 . handle ( "update" , update )
6384 . handle ( "remove" , remove )
85+ . handle ( "connectToken" , connectToken )
6486 } ) ,
6587)
6688
6789export const ptyConnectRoute = HttpRouter . use ( ( router ) =>
6890 Effect . gen ( function * ( ) {
6991 const pty = yield * Pty . Service
92+ const tickets = yield * PtyTicket . Service
93+ const cors = yield * CorsConfig
7094 yield * router . add (
7195 "GET" ,
7296 PtyPaths . connect ,
@@ -75,12 +99,20 @@ export const ptyConnectRoute = HttpRouter.use((router) =>
7599 if ( ! ( yield * pty . get ( params . ptyID ) ) ) return HttpServerResponse . empty ( { status : 404 } )
76100
77101 const query = yield * HttpServerRequest . schemaSearchParams ( CursorQuery )
102+ const request = yield * HttpServerRequest . HttpServerRequest
103+ const ticket = new URL ( request . url , "http://localhost" ) . searchParams . get ( PTY_CONNECT_TICKET_QUERY )
104+ if ( ticket ) {
105+ const valid = validOrigin ( request , cors )
106+ ? yield * tickets . consume ( { ticket, ptyID : params . ptyID , ...( yield * PtyTicket . scope ) } )
107+ : false
108+ if ( ! valid ) return HttpServerResponse . empty ( { status : 403 } )
109+ }
78110 const parsedCursor = query . cursor === undefined ? undefined : Number ( query . cursor )
79111 const cursor =
80112 parsedCursor !== undefined && Number . isSafeInteger ( parsedCursor ) && parsedCursor >= - 1
81113 ? parsedCursor
82114 : undefined
83- const socket = yield * Effect . orDie ( ( yield * HttpServerRequest . HttpServerRequest ) . upgrade )
115+ const socket = yield * Effect . orDie ( request . upgrade )
84116 const write = yield * socket . writer
85117 const closeAccepted = ( event : Socket . CloseEvent ) =>
86118 socket
0 commit comments