fix(http): use manual cookie headers with SameSite attribute and dev flag for cross-origin support

This commit is contained in:
2026-07-19 01:02:55 +01:00
parent fd1e59267d
commit 4bb103e9b2
+75 -11
View File
@@ -207,6 +207,22 @@ struct CreateStreamKeyBody {
label: String, label: String,
} }
struct SessionCookie(Option<String>);
impl<S> FromRequestParts<S> for SessionCookie
where
S: Send + Sync,
{
type Rejection = HttpError;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let jar = CookieJar::from_request_parts(parts, _state).await.unwrap();
Ok(SessionCookie(
jar.get("session").map(|c| c.value().to_string()),
))
}
}
struct AuthUser(entity::users::Model); struct AuthUser(entity::users::Model);
impl FromRequestParts<Arc<HttpServer>> for AuthUser { impl FromRequestParts<Arc<HttpServer>> for AuthUser {
@@ -216,12 +232,18 @@ impl FromRequestParts<Arc<HttpServer>> for AuthUser {
parts: &mut Parts, parts: &mut Parts,
state: &Arc<HttpServer>, state: &Arc<HttpServer>,
) -> Result<Self, HttpError> { ) -> Result<Self, HttpError> {
let jar = CookieJar::from_request_parts(parts, state).await.unwrap(); let jar = CookieJar::from_headers(&parts.headers);
let session = jar.get("session").ok_or(HttpError::Unauthorized)?; let session = jar.get("session").ok_or(HttpError::Unauthorized)?;
let session_val = session.value().to_string();
tracing::debug!(?session_val, "AuthUser: extracted session cookie");
let user = users::Entity::find_by_auth_session(&state.db, session.to_string()) let user = match users::Entity::find_by_auth_session(&state.db, session_val.clone()).await {
.await? Ok(u) => u.ok_or(HttpError::Unauthorized)?,
.ok_or(HttpError::Unauthorized)?; Err(e) => {
tracing::error!(?session_val, error = %e, "AuthUser: db query failed");
return Err(HttpError::DbErr(e));
}
};
Ok(AuthUser(user)) Ok(AuthUser(user))
} }
@@ -289,11 +311,45 @@ struct LoginResponse {
session_token: String, session_token: String,
} }
struct DevFlag(bool);
impl<S> FromRequestParts<S> for DevFlag
where
S: Send + Sync,
{
type Rejection = std::convert::Infallible;
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
let query = parts.uri.query().unwrap_or("");
Ok(DevFlag(query.contains("dev=1")))
}
}
fn cookie_for_token(token: &str, dev: bool) -> Cookie<'static> {
let token = token.to_owned();
match dev {
true => Cookie::build(("session", token))
.path("/")
.http_only(true)
.same_site(axum_extra::extract::cookie::SameSite::None)
.build(),
false => Cookie::build(("session", token))
.path("/")
.http_only(true)
.same_site(axum_extra::extract::cookie::SameSite::Lax)
.build(),
}
}
#[axum::debug_handler]
async fn login_handler( async fn login_handler(
session: SessionCookie,
State(state): State<Arc<HttpServer>>, State(state): State<Arc<HttpServer>>,
DevFlag(dev): DevFlag,
Json(payload): Json<LoginForm>, Json(payload): Json<LoginForm>,
jar: CookieJar,
) -> Result<impl IntoResponse, HttpError> { ) -> Result<impl IntoResponse, HttpError> {
tracing::debug!(session = ?session.0, "login: existing session cookie");
let user = users::Entity::find_by_username(&state.db, payload.username.clone()) let user = users::Entity::find_by_username(&state.db, payload.username.clone())
.await? .await?
.ok_or_else(|| { .ok_or_else(|| {
@@ -310,8 +366,11 @@ async fn login_handler(
let auth = auth_session::Entity::create(&state.db, user.id).await?; let auth = auth_session::Entity::create(&state.db, user.id).await?;
let token = auth.value; let token = auth.value;
let jar = jar.add(Cookie::new("session", token)); let cookie = cookie_for_token(&token, dev);
Ok((StatusCode::OK, jar)) Ok((
StatusCode::OK,
[(axum::http::header::SET_COOKIE, cookie.to_string())],
))
} }
#[derive(Deserialize)] #[derive(Deserialize)]
@@ -322,10 +381,12 @@ struct CreateUserForm {
} }
async fn create_user_handler( async fn create_user_handler(
session: SessionCookie,
State(state): State<Arc<HttpServer>>, State(state): State<Arc<HttpServer>>,
DevFlag(dev): DevFlag,
Json(payload): Json<CreateUserForm>, Json(payload): Json<CreateUserForm>,
jar: CookieJar,
) -> Result<impl IntoResponse, HttpError> { ) -> Result<impl IntoResponse, HttpError> {
tracing::debug!(session = ?session.0, "create_user: existing session cookie");
if state.config.signup_code.is_empty() || payload.ref_token != state.config.signup_code { if state.config.signup_code.is_empty() || payload.ref_token != state.config.signup_code {
warn!(username = %payload.username, "signup rejected: invalid signup code"); warn!(username = %payload.username, "signup rejected: invalid signup code");
return Err(HttpError::Unauthorized); return Err(HttpError::Unauthorized);
@@ -352,8 +413,11 @@ async fn create_user_handler(
let session = auth_session::Entity::create(&state.db, user.id).await?; let session = auth_session::Entity::create(&state.db, user.id).await?;
let token = session.value; let token = session.value;
let jar = jar.add(Cookie::new("session", token)); let cookie = cookie_for_token(&token, dev);
Ok((jar, StatusCode::OK)) Ok((
StatusCode::OK,
[(axum::http::header::SET_COOKIE, cookie.to_string())],
))
} }
async fn stream_handler( async fn stream_handler(
@@ -372,7 +436,7 @@ async fn stream_handler(
}; };
let stream_key_id = stream_key_id.ok_or_else(|| { let stream_key_id = stream_key_id.ok_or_else(|| {
warn!(slug = %slug, "WHEP request for unknown or inactive stream"); // warn!(slug = %slug, "WHEP request for unknown or inactive stream");
HttpError::NotFound HttpError::NotFound
})?; })?;