fix: protect direct auth mutation routes
This commit is contained in:
@@ -968,10 +968,10 @@ async fn authorize_workspace_api_request(
|
|||||||
mut request: Request,
|
mut request: Request,
|
||||||
next: axum::middleware::Next,
|
next: axum::middleware::Next,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
if !request.uri().path().starts_with("/api/") || is_server_global_forward(request.uri().path())
|
if !request.uri().path().starts_with("/api/") {
|
||||||
{
|
|
||||||
return next.run(request).await;
|
return next.run(request).await;
|
||||||
}
|
}
|
||||||
|
let public_server_api = is_server_global_forward(request.uri().path());
|
||||||
let workspace_id = api.workspace_id().to_owned();
|
let workspace_id = api.workspace_id().to_owned();
|
||||||
let proof = request
|
let proof = request
|
||||||
.headers()
|
.headers()
|
||||||
@@ -1020,6 +1020,7 @@ async fn authorize_workspace_api_request(
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(Some(actor)) => actor,
|
Ok(Some(actor)) => actor,
|
||||||
|
Ok(None) if public_server_api => return next.run(request).await,
|
||||||
Ok(None) | Err(_) => return StatusCode::UNAUTHORIZED.into_response(),
|
Ok(None) | Err(_) => return StatusCode::UNAUTHORIZED.into_response(),
|
||||||
};
|
};
|
||||||
let cookie_authenticated = matches!(actor.auth_method, ActorAuthMethod::BrowserSession);
|
let cookie_authenticated = matches!(actor.auth_method, ActorAuthMethod::BrowserSession);
|
||||||
@@ -15881,6 +15882,61 @@ mod tests {
|
|||||||
assert_ne!(csrf_accepted.status(), StatusCode::FORBIDDEN);
|
assert_ne!(csrf_accepted.status(), StatusCode::FORBIDDEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn direct_workspace_router_enforces_origin_on_browser_auth_mutations() {
|
||||||
|
let workspace = tempfile::tempdir().unwrap();
|
||||||
|
let api = test_api(workspace.path()).await;
|
||||||
|
seed_test_api_token(api.store.as_ref(), "direct-cookie");
|
||||||
|
api.store
|
||||||
|
.create_browser_session(&BrowserSessionRecord {
|
||||||
|
token_hash: crate::auth::token_hash("direct-browser-session"),
|
||||||
|
session_id: "direct-session".to_owned(),
|
||||||
|
user_id: "user-direct-cookie".to_owned(),
|
||||||
|
created_at: "2026-01-01T00:00:00Z".to_owned(),
|
||||||
|
expires_at: "2099-01-01T00:00:00Z".to_owned(),
|
||||||
|
revoked_at: None,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let AuthConfig::Passkey {
|
||||||
|
origin,
|
||||||
|
cookie_name,
|
||||||
|
..
|
||||||
|
} = &api.config.auth;
|
||||||
|
let origin = origin.clone();
|
||||||
|
let cookie = format!("{cookie_name}=direct-browser-session");
|
||||||
|
let app = build_router(api.clone());
|
||||||
|
|
||||||
|
let cross_site = app
|
||||||
|
.clone()
|
||||||
|
.oneshot(
|
||||||
|
Request::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri("/api/auth/device-login/approve")
|
||||||
|
.header(axum::http::header::COOKIE, &cookie)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from("{}"))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(cross_site.status(), StatusCode::FORBIDDEN);
|
||||||
|
|
||||||
|
let same_origin = app
|
||||||
|
.oneshot(
|
||||||
|
Request::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri("/api/auth/device-login/approve")
|
||||||
|
.header(axum::http::header::COOKIE, cookie)
|
||||||
|
.header(ORIGIN, origin)
|
||||||
|
.header(CONTENT_TYPE, "application/json")
|
||||||
|
.body(Body::from("{}"))
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_ne!(same_origin.status(), StatusCode::FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn browser_session_set_and_clear_cookies_follow_public_https_scheme() {
|
async fn browser_session_set_and_clear_cookies_follow_public_https_scheme() {
|
||||||
for (scheme, secure) in [("http", false), ("https", true)] {
|
for (scheme, secure) in [("http", false), ("https", true)] {
|
||||||
|
|||||||
Reference in New Issue
Block a user