diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 7a424f4..9233dd6 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1931,6 +1931,7 @@ dependencies = [ "chrono", "clap", "clap_derive", + "cookie", "crc32fast", "figment", "jsonwebtoken", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index d0fe53f..f2df4f7 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -22,6 +22,7 @@ scrypt = "0.11" time = "0.3.36" chrono = { version = "0.4", features = ["serde"] } axum = { version = "0.7", features = ["macros", "multipart"] } +cookie = "0.18.1" axum-extra = { version = "0.9", features = ["typed-header", "cookie"] } tokio = { version = "1.36", features = ["full"] } figment = { version = "0.10", features = ["toml", "env"] } diff --git a/backend/src/api/admin.rs b/backend/src/api/admin.rs index 910aea9..e43547b 100644 --- a/backend/src/api/admin.rs +++ b/backend/src/api/admin.rs @@ -210,6 +210,6 @@ async fn admin_login( (status = 404, description = "User or password not found", body = ErrorResponse), ) )] -async fn admin_logout(cookies: CookieJar) -> Response { - auth::expire_cookies(cookies).into_response() +async fn admin_logout(State(app_state): State, cookies: CookieJar) -> Response { + auth::expire_cookies(&app_state, cookies).into_response() } diff --git a/backend/src/api/admin/auth.rs b/backend/src/api/admin/auth.rs index 1fd28b0..6b05c86 100644 --- a/backend/src/api/admin/auth.rs +++ b/backend/src/api/admin/auth.rs @@ -116,7 +116,7 @@ pub async fn authentication_middleware( ) else { // If the refresh token is invalid return an error - let purged_jar = expire_cookies(cookies); + let purged_jar = expire_cookies(&app_state, cookies); return Ok((purged_jar, AppError::Unauthorized).into_response()); }; @@ -128,7 +128,7 @@ pub async fn authentication_middleware( } // if the user is not found, clear the cookie jar Err(AppError::Database(sqlx::Error::RowNotFound)) => { - let purged_jar = expire_cookies(cookies); + let purged_jar = expire_cookies(&app_state, cookies); return Ok((purged_jar, AppError::Unauthorized).into_response()); } Err(err) => return Err(err.into_response()), @@ -155,10 +155,12 @@ pub async fn authentication_middleware( }) } -pub fn expire_cookies(cookies: CookieJar) -> CookieJar { - cookies - .remove(EPHEMERAL_TOKEN_COOKIE_NAME) - .remove(REFRESH_TOKEN_COOKIE_NAME) +pub fn expire_cookies(app_state: &AppState, cookies: CookieJar) -> CookieJar { + [EPHEMERAL_TOKEN_COOKIE_NAME, REFRESH_TOKEN_COOKIE_NAME] + .into_iter() + .fold(cookies, |jar, cookie_name| { + jar.remove(create_admin_cookie(cookie_name, app_state)) + }) } fn create_user_claim(user: &User) -> AdminEphemeralTokenClaims { @@ -171,18 +173,24 @@ fn create_user_claim(user: &User) -> AdminEphemeralTokenClaims { } } -fn create_ephemeral_cookie<'a>( - claims: AdminEphemeralTokenClaims, +// this helper is used to add and remove cookies +fn create_admin_cookie<'a>( + base: impl Into>, app_state: &AppState, -) -> Cookie<'a> { - let token = app_state.generate_refresh_token(claims); - - Cookie::build((EPHEMERAL_TOKEN_COOKIE_NAME, token)) +) -> cookie::CookieBuilder<'a> { + Cookie::build(base) .path("/api/admin/") .secure(app_state.config.secure_cookie) .http_only(true) .same_site(SameSite::Strict) - .build() +} + +fn create_ephemeral_cookie<'a>( + claims: AdminEphemeralTokenClaims, + app_state: &AppState, +) -> Cookie<'a> { + let token = app_state.generate_refresh_token(claims); + create_admin_cookie((EPHEMERAL_TOKEN_COOKIE_NAME, token), app_state).build() } fn create_refresh_cookie<'a>(user_id: Uuid, app_state: &AppState, remember_me: bool) -> Cookie<'a> { @@ -202,11 +210,7 @@ fn create_refresh_cookie<'a>(user_id: Uuid, app_state: &AppState, remember_me: b remember_me, }); - Cookie::build((REFRESH_TOKEN_COOKIE_NAME, token)) - .path("/api/admin/") - .secure(app_state.config.secure_cookie) - .http_only(true) - .same_site(SameSite::Strict) + create_admin_cookie((REFRESH_TOKEN_COOKIE_NAME, token), app_state) .expires(if remember_me { Expiration::DateTime(time_now + time::Duration::days(inactive_days)) } else {