Skip to content

Commit acb1893

Browse files
authored
Cap parallel OIDC login state cookies (#1238)
* Cap parallel OIDC login state cookies * Cap parallel OIDC login state cookies * simplify cookie eviction code
1 parent 357b5bb commit acb1893

File tree

1 file changed

+47
-8
lines changed

1 file changed

+47
-8
lines changed

src/webserver/oidc.rs

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ const OIDC_CLIENT_MIN_REFRESH_INTERVAL: Duration = Duration::from_secs(5);
5151
const OIDC_HTTP_BODY_TIMEOUT: Duration = OIDC_CLIENT_MIN_REFRESH_INTERVAL;
5252
const SQLPAGE_OIDC_REDIRECT_COUNT_COOKIE: &str = "sqlpage_oidc_redirect_count";
5353
const MAX_OIDC_REDIRECTS: u8 = 3;
54+
const MAX_OIDC_PARALLEL_LOGIN_FLOWS: usize = 8;
5455
const AUTH_COOKIE_EXPIRATION: awc::cookie::time::Duration =
5556
actix_web::cookie::time::Duration::days(7);
5657
const LOGIN_FLOW_STATE_COOKIE_EXPIRATION: awc::cookie::time::Duration =
@@ -455,7 +456,8 @@ fn handle_unauthenticated_request(
455456

456457
let initial_url = request.uri().to_string();
457458
let redirect_count = get_redirect_count(&request);
458-
let response = build_auth_provider_redirect_response(oidc_state, &initial_url, redirect_count);
459+
let response =
460+
build_auth_provider_redirect_response(oidc_state, &request, &initial_url, redirect_count);
459461
MiddlewareResponse::Respond(request.into_response(response))
460462
}
461463

@@ -487,7 +489,7 @@ fn handle_oidc_callback_error(
487489
if let Ok(http_client) = get_http_client_from_appdata(&request) {
488490
oidc_state.maybe_refresh(http_client, OIDC_CLIENT_MIN_REFRESH_INTERVAL);
489491
}
490-
let resp = build_auth_provider_redirect_response(oidc_state, "/", redirect_count);
492+
let resp = build_auth_provider_redirect_response(oidc_state, &request, "/", redirect_count);
491493
request.into_response(resp)
492494
}
493495

@@ -585,7 +587,6 @@ fn process_oidc_logout(
585587
.path("/")
586588
.finish(),
587589
)?;
588-
589590
log::debug!("User logged out successfully");
590591
Ok(response)
591592
}
@@ -736,6 +737,7 @@ fn set_auth_cookie(response: &mut HttpResponse, id_token: &OidcToken) {
736737

737738
fn build_auth_provider_redirect_response(
738739
oidc_state: &OidcState,
740+
request: &ServiceRequest,
739741
initial_url: &str,
740742
redirect_count: u8,
741743
) -> HttpResponse {
@@ -750,11 +752,17 @@ fn build_auth_provider_redirect_response(
750752
.same_site(actix_web::cookie::SameSite::Lax)
751753
.max_age(LOGIN_FLOW_STATE_COOKIE_EXPIRATION)
752754
.finish();
753-
HttpResponse::SeeOther()
754-
.append_header((header::LOCATION, url.to_string()))
755-
.cookie(tmp_login_flow_state_cookie)
756-
.cookie(redirect_count_cookie)
757-
.body("Redirecting...")
755+
let mut response = HttpResponse::SeeOther();
756+
response.append_header((header::LOCATION, url.to_string()));
757+
if let Ok(cookies) = request.cookies() {
758+
for mut cookie in get_tmp_login_flow_state_cookies_to_evict(&cookies).cloned() {
759+
cookie.make_removal();
760+
response.cookie(cookie);
761+
}
762+
}
763+
response.cookie(tmp_login_flow_state_cookie);
764+
response.cookie(redirect_count_cookie);
765+
response.body("Redirecting...")
758766
}
759767

760768
fn build_redirect_response(target_url: String) -> HttpResponse {
@@ -1078,6 +1086,15 @@ fn get_tmp_login_flow_state_cookie(
10781086
.with_context(|| format!("No {cookie_name} cookie found"))
10791087
}
10801088

1089+
fn get_tmp_login_flow_state_cookies_to_evict<'a>(
1090+
cookies: &'a [Cookie<'static>],
1091+
) -> impl Iterator<Item = &'a Cookie<'static>> + 'a {
1092+
let is_state = &|c: &Cookie<'_>| c.name().starts_with(SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX);
1093+
let login_state_count = cookies.iter().filter(|c| is_state(c)).count();
1094+
let to_evict = login_state_count.saturating_sub(MAX_OIDC_PARALLEL_LOGIN_FLOWS - 1);
1095+
cookies.iter().filter(|c| is_state(c)).take(to_evict)
1096+
}
1097+
10811098
#[derive(Debug, Serialize, Deserialize, Clone)]
10821099
struct LoginFlowState<'a> {
10831100
#[serde(rename = "n")]
@@ -1127,6 +1144,7 @@ fn validate_redirect_url(url: String, redirect_uri: &str) -> String {
11271144
mod tests {
11281145
use super::*;
11291146
use actix_web::http::StatusCode;
1147+
use actix_web::{cookie::Cookie, test::TestRequest};
11301148
use openidconnect::url::Url;
11311149

11321150
#[test]
@@ -1182,4 +1200,25 @@ mod tests {
11821200
.expect("generated URL should parse");
11831201
verify_logout_params(&params, secret).expect("generated URL should validate");
11841202
}
1203+
1204+
#[test]
1205+
fn evicts_excess_tmp_login_flow_state_cookies() {
1206+
let request = (0..MAX_OIDC_PARALLEL_LOGIN_FLOWS)
1207+
.fold(TestRequest::default(), |request, i| {
1208+
request.cookie(Cookie::new(
1209+
format!("{SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX}{i}"),
1210+
format!("value-{i}"),
1211+
))
1212+
})
1213+
.to_srv_request();
1214+
1215+
let cookies = request.cookies().unwrap();
1216+
let cookies_to_evict: Vec<_> =
1217+
get_tmp_login_flow_state_cookies_to_evict(&cookies).collect();
1218+
1219+
assert_eq!(cookies_to_evict.len(), 1);
1220+
assert!(cookies_to_evict[0]
1221+
.name()
1222+
.starts_with(SQLPAGE_TMP_LOGIN_STATE_COOKIE_PREFIX));
1223+
}
11851224
}

0 commit comments

Comments
 (0)