@@ -802,14 +802,20 @@ const bootstrapBrowserSession = (
802802 } ;
803803 } ) ;
804804
805- const bootstrapBearerSession = ( credential = defaultDesktopBootstrapToken ) =>
805+ const bootstrapBearerSession = (
806+ credential = defaultDesktopBootstrapToken ,
807+ options ?: {
808+ readonly headers ?: Record < string , string > ;
809+ } ,
810+ ) =>
806811 Effect . gen ( function * ( ) {
807812 const bootstrapUrl = yield * getHttpServerUrl ( "/api/auth/bootstrap/bearer" ) ;
808813 const response = yield * Effect . promise ( ( ) =>
809814 fetch ( bootstrapUrl , {
810815 method : "POST" ,
811816 headers : {
812817 "content-type" : "application/json" ,
818+ ...options ?. headers ,
813819 } ,
814820 body : JSON . stringify ( {
815821 credential,
@@ -885,6 +891,22 @@ const splitHeaderTokens = (value: string | null) =>
885891 . filter ( ( token ) => token . length > 0 )
886892 . toSorted ( ) ;
887893
894+ const assertBrowserApiCorsHeaders = ( headers : Headers ) => {
895+ assert . equal ( headers . get ( "access-control-allow-origin" ) , "*" ) ;
896+ assert . deepEqual ( splitHeaderTokens ( headers . get ( "access-control-allow-methods" ) ) , [
897+ "GET" ,
898+ "OPTIONS" ,
899+ "POST" ,
900+ ] ) ;
901+ assert . deepEqual ( splitHeaderTokens ( headers . get ( "access-control-allow-headers" ) ) , [
902+ "authorization" ,
903+ "b3" ,
904+ "content-type" ,
905+ "traceparent" ,
906+ ] ) ;
907+ } ;
908+ const crossOriginClientOrigin = "http://remote-client.test:3773" ;
909+
888910const getWsServerUrl = (
889911 pathname = "" ,
890912 options ?: { authenticated ?: boolean ; credential ?: string } ,
@@ -1006,6 +1028,28 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
10061028 } ) . pipe ( Effect . provide ( NodeHttpServer . layerTest ) ) ,
10071029 ) ;
10081030
1031+ it . effect ( "includes CORS headers on public environment descriptor responses" , ( ) =>
1032+ Effect . gen ( function * ( ) {
1033+ yield * buildAppUnderTest ( ) ;
1034+
1035+ const url = yield * getHttpServerUrl ( "/.well-known/t3/environment" ) ;
1036+ const response = yield * Effect . promise ( ( ) =>
1037+ fetch ( url , {
1038+ headers : {
1039+ origin : crossOriginClientOrigin ,
1040+ } ,
1041+ } ) ,
1042+ ) ;
1043+ const body = ( yield * Effect . promise ( ( ) =>
1044+ response . json ( ) ,
1045+ ) ) as typeof testEnvironmentDescriptor ;
1046+
1047+ assert . equal ( response . status , 200 ) ;
1048+ assertBrowserApiCorsHeaders ( response . headers ) ;
1049+ assert . deepEqual ( body , testEnvironmentDescriptor ) ;
1050+ } ) . pipe ( Effect . provide ( NodeHttpServer . layerTest ) ) ,
1051+ ) ;
1052+
10091053 it . effect ( "reports unauthenticated session state without requiring auth" , ( ) =>
10101054 Effect . gen ( function * ( ) {
10111055 yield * buildAppUnderTest ( ) ;
@@ -1129,6 +1173,62 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
11291173 } ) . pipe ( Effect . provide ( NodeHttpServer . layerTest ) ) ,
11301174 ) ;
11311175
1176+ it . effect ( "includes CORS headers on remote auth success responses" , ( ) =>
1177+ Effect . gen ( function * ( ) {
1178+ yield * buildAppUnderTest ( ) ;
1179+
1180+ const origin = crossOriginClientOrigin ;
1181+ const { response : bootstrapResponse , body : bootstrapBody } = yield * bootstrapBearerSession (
1182+ defaultDesktopBootstrapToken ,
1183+ {
1184+ headers : { origin } ,
1185+ } ,
1186+ ) ;
1187+
1188+ assert . equal ( bootstrapResponse . status , 200 ) ;
1189+ assertBrowserApiCorsHeaders ( bootstrapResponse . headers ) ;
1190+ assert . equal ( bootstrapBody . authenticated , true ) ;
1191+ assert . equal ( typeof bootstrapBody . sessionToken , "string" ) ;
1192+
1193+ const sessionUrl = yield * getHttpServerUrl ( "/api/auth/session" ) ;
1194+ const sessionResponse = yield * Effect . promise ( ( ) =>
1195+ fetch ( sessionUrl , {
1196+ headers : {
1197+ authorization : `Bearer ${ bootstrapBody . sessionToken ?? "" } ` ,
1198+ origin,
1199+ } ,
1200+ } ) ,
1201+ ) ;
1202+ const sessionBody = ( yield * Effect . promise ( ( ) => sessionResponse . json ( ) ) ) as {
1203+ readonly authenticated : boolean ;
1204+ readonly sessionMethod ?: string ;
1205+ } ;
1206+
1207+ assert . equal ( sessionResponse . status , 200 ) ;
1208+ assertBrowserApiCorsHeaders ( sessionResponse . headers ) ;
1209+ assert . equal ( sessionBody . authenticated , true ) ;
1210+ assert . equal ( sessionBody . sessionMethod , "bearer-session-token" ) ;
1211+
1212+ const wsTokenUrl = yield * getHttpServerUrl ( "/api/auth/ws-token" ) ;
1213+ const wsTokenResponse = yield * Effect . promise ( ( ) =>
1214+ fetch ( wsTokenUrl , {
1215+ method : "POST" ,
1216+ headers : {
1217+ authorization : `Bearer ${ bootstrapBody . sessionToken ?? "" } ` ,
1218+ origin,
1219+ } ,
1220+ } ) ,
1221+ ) ;
1222+ const wsTokenBody = ( yield * Effect . promise ( ( ) => wsTokenResponse . json ( ) ) ) as {
1223+ readonly token : string ;
1224+ } ;
1225+
1226+ assert . equal ( wsTokenResponse . status , 200 ) ;
1227+ assertBrowserApiCorsHeaders ( wsTokenResponse . headers ) ;
1228+ assert . equal ( typeof wsTokenBody . token , "string" ) ;
1229+ } ) . pipe ( Effect . provide ( NodeHttpServer . layerTest ) ) ,
1230+ ) ;
1231+
11321232 it . effect (
11331233 "responds to remote auth websocket-token preflight requests with authorization CORS headers" ,
11341234 ( ) =>
@@ -1140,26 +1240,15 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
11401240 fetch ( wsTokenUrl , {
11411241 method : "OPTIONS" ,
11421242 headers : {
1143- origin : "http://192.168.86.35:3773" ,
1243+ origin : crossOriginClientOrigin ,
11441244 "access-control-request-method" : "POST" ,
11451245 "access-control-request-headers" : "authorization" ,
11461246 } ,
11471247 } ) ,
11481248 ) ;
11491249
11501250 assert . equal ( response . status , 204 ) ;
1151- assert . equal ( response . headers . get ( "access-control-allow-origin" ) , "*" ) ;
1152- assert . deepEqual ( splitHeaderTokens ( response . headers . get ( "access-control-allow-methods" ) ) , [
1153- "GET" ,
1154- "OPTIONS" ,
1155- "POST" ,
1156- ] ) ;
1157- assert . deepEqual ( splitHeaderTokens ( response . headers . get ( "access-control-allow-headers" ) ) , [
1158- "authorization" ,
1159- "b3" ,
1160- "content-type" ,
1161- "traceparent" ,
1162- ] ) ;
1251+ assertBrowserApiCorsHeaders ( response . headers ) ;
11631252 } ) . pipe ( Effect . provide ( NodeHttpServer . layerTest ) ) ,
11641253 ) ;
11651254
@@ -1172,7 +1261,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
11721261 fetch ( wsTokenUrl , {
11731262 method : "POST" ,
11741263 headers : {
1175- origin : "http://192.168.86.35:3773" ,
1264+ origin : crossOriginClientOrigin ,
11761265 } ,
11771266 } ) ,
11781267 ) ;
@@ -1181,7 +1270,7 @@ it.layer(NodeServices.layer)("server router seam", (it) => {
11811270 } ;
11821271
11831272 assert . equal ( response . status , 401 ) ;
1184- assert . equal ( response . headers . get ( "access-control-allow-origin" ) , "*" ) ;
1273+ assertBrowserApiCorsHeaders ( response . headers ) ;
11851274 assert . equal ( body . error , "Authentication required." ) ;
11861275 } ) . pipe ( Effect . provide ( NodeHttpServer . layerTest ) ) ,
11871276 ) ;
0 commit comments