11use axum:: {
22 BoxError , Router ,
33 extract:: Path ,
4+ http:: { HeaderValue , Method } ,
45 response:: {
56 IntoResponse , Result ,
67 sse:: { Event , KeepAlive , Sse } ,
@@ -14,7 +15,7 @@ use serde::{Deserialize, Serialize};
1415use slatedb:: Db ;
1516use std:: pin:: Pin ;
1617use std:: sync:: Arc ;
17- use tower_http:: cors:: { self , CorsLayer } ;
18+ use tower_http:: cors:: CorsLayer ;
1819use uuid:: Uuid ;
1920
2021use crate :: Log ;
@@ -32,17 +33,47 @@ struct LogWithLine {
3233 line : u64 ,
3334}
3435
35- pub fn create_app ( state : Arc < AppState > ) -> Router {
36+ /// Creates the CORS layer with appropriate security settings.
37+ ///
38+ /// The allowed origin is determined by the CORS_ALLOWED_ORIGIN environment variable.
39+ /// If not set, defaults to allowing all origins for local development.
40+ /// In production, this should be set to the specific frontend origin.
41+ fn create_cors_layer ( ) -> CorsLayer {
42+ let allowed_origin = std:: env:: var ( "CORS_ALLOWED_ORIGIN" ) . ok ( ) ;
43+
3644 let cors = CorsLayer :: new ( )
37- . allow_origin ( cors:: Any )
38- . allow_methods ( cors:: Any )
39- . allow_headers ( cors:: Any )
45+ . allow_methods ( [ Method :: GET , Method :: POST , Method :: OPTIONS ] )
46+ . allow_headers ( [ axum:: http:: header:: CONTENT_TYPE ] )
4047 . expose_headers ( [
4148 axum:: http:: header:: CONTENT_TYPE ,
4249 axum:: http:: header:: CACHE_CONTROL ,
4350 ] )
4451 . max_age ( std:: time:: Duration :: from_secs ( 3600 ) ) ;
4552
53+ match allowed_origin {
54+ Some ( origin) => {
55+ // Use specific origin for production security
56+ cors. allow_origin (
57+ origin
58+ . parse :: < HeaderValue > ( )
59+ . expect ( "CORS_ALLOWED_ORIGIN must be a valid header value" ) ,
60+ )
61+ }
62+ None => {
63+ // Fall back to permissive for local development only
64+ // Log a warning to remind operators to set CORS_ALLOWED_ORIGIN in production
65+ eprintln ! (
66+ "WARNING: CORS_ALLOWED_ORIGIN not set, allowing all origins. \
67+ Set this environment variable in production."
68+ ) ;
69+ cors. allow_origin ( tower_http:: cors:: Any )
70+ }
71+ }
72+ }
73+
74+ pub fn create_app ( state : Arc < AppState > ) -> Router {
75+ let cors = create_cors_layer ( ) ;
76+
4677 Router :: new ( )
4778 . route ( "/{uuid}" , post ( post_logs) )
4879 . route ( "/{uuid}" , get ( get_logs) )
@@ -134,19 +165,17 @@ async fn get_logs(
134165 let sse = Sse :: new ( stream) . keep_alive ( keep_alive) ;
135166 let mut response = sse. into_response ( ) ;
136167
168+ // Set cache and content-type headers for SSE
169+ // Note: CORS headers are handled by the CorsLayer middleware
137170 let headers = response. headers_mut ( ) ;
138- headers. insert ( "Access-Control-Allow-Origin" , "*" . parse ( ) . unwrap ( ) ) ;
139171 headers. insert (
140- "Access-Control-Allow-Methods" ,
141- "GET, POST, OPTIONS " . parse ( ) . unwrap ( ) ,
172+ axum :: http :: header :: CACHE_CONTROL ,
173+ "no-cache " . parse ( ) . expect ( "valid header value" ) ,
142174 ) ;
143- headers. insert ( "Access-Control-Allow-Headers" , "*" . parse ( ) . unwrap ( ) ) ;
144175 headers. insert (
145- "Access-Control-Expose-Headers" ,
146- "Content-Type, Cache-Control " . parse ( ) . unwrap ( ) ,
176+ axum :: http :: header :: CONTENT_TYPE ,
177+ "text/event-stream " . parse ( ) . expect ( "valid header value" ) ,
147178 ) ;
148- headers. insert ( "Cache-Control" , "no-cache" . parse ( ) . unwrap ( ) ) ;
149- headers. insert ( "Content-Type" , "text/event-stream" . parse ( ) . unwrap ( ) ) ;
150179
151180 response
152181}
0 commit comments