From 1c1b5c5e7f628024de78fcc3d6511a8359e4da02 Mon Sep 17 00:00:00 2001 From: Ashish Kolhe <35160958+oasisk@users.noreply.github.com> Date: Tue, 16 Apr 2024 07:27:33 +0530 Subject: [PATCH] fix: cookies issue for SSO (#3244) Co-authored-by: Bhargav --- src/common/meta/user.rs | 6 + src/common/utils/auth.rs | 18 ++- src/handler/http/request/status/mod.rs | 148 ++++++++++++++----------- src/handler/http/request/users/mod.rs | 34 +++--- src/router/http/mod.rs | 6 +- web/src/services/http.ts | 7 +- web/src/utils/zincutils.ts | 1 + web/src/views/Login.vue | 2 +- 8 files changed, 133 insertions(+), 89 deletions(-) diff --git a/src/common/meta/user.rs b/src/common/meta/user.rs index b666c096ea..268b3684e7 100644 --- a/src/common/meta/user.rs +++ b/src/common/meta/user.rs @@ -339,3 +339,9 @@ pub struct RolesResponse { pub label: String, pub value: String, } + +#[derive(Clone, Debug, Default, Serialize, Deserialize, ToSchema)] +pub struct AuthTokens { + pub access_token: String, + pub refresh_token: String, +} diff --git a/src/common/utils/auth.rs b/src/common/utils/auth.rs index 7a2bb17c70..058527a5c4 100644 --- a/src/common/utils/auth.rs +++ b/src/common/utils/auth.rs @@ -15,6 +15,7 @@ use actix_web::{dev::Payload, Error, FromRequest, HttpRequest}; use argon2::{password_hash::SaltString, Algorithm, Argon2, Params, PasswordHasher, Version}; +use config::utils::json; #[cfg(feature = "enterprise")] use config::CONFIG; use futures::future::{ready, Ready}; @@ -23,7 +24,11 @@ use futures::future::{ready, Ready}; use crate::common::meta::ingestion::INGESTION_EP; use crate::common::{ infra::config::{PASSWORD_HASH, USERS}, - meta::{authz::Authz, organization::DEFAULT_ORG, user::UserRole}, + meta::{ + authz::Authz, + organization::DEFAULT_ORG, + user::{AuthTokens, UserRole}, + }, }; pub(crate) fn get_hash(pass: &str, salt: &str) -> String { @@ -300,8 +305,10 @@ impl FromRequest for AuthExtractor { ) }; - let auth_str = if let Some(cookie) = req.cookie("access_token") { - let access_token = cookie.value().to_string(); + let auth_str = if let Some(cookie) = req.cookie("auth_tokens") { + let auth_tokens: AuthTokens = json::from_str(cookie.value()).unwrap_or_default(); + let access_token = auth_tokens.access_token; + if access_token.starts_with("Basic") || access_token.starts_with("Bearer") { access_token } else { @@ -411,8 +418,9 @@ impl FromRequest for AuthExtractor { #[cfg(not(feature = "enterprise"))] fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { - let auth_str = if let Some(cookie) = req.cookie("access_token") { - let access_token = cookie.value().to_string(); + let auth_str = if let Some(cookie) = req.cookie("auth_tokens") { + let auth_tokens: AuthTokens = json::from_str(cookie.value()).unwrap_or_default(); + let access_token = auth_tokens.access_token; if access_token.starts_with("Basic") || access_token.starts_with("Bearer") { access_token } else { diff --git a/src/handler/http/request/status/mod.rs b/src/handler/http/request/status/mod.rs index 62c5906313..867040603f 100644 --- a/src/handler/http/request/status/mod.rs +++ b/src/handler/http/request/status/mod.rs @@ -15,7 +15,13 @@ use std::io::Error; -use actix_web::{cookie, get, http::header, put, web, HttpRequest, HttpResponse}; +use actix_web::{ + cookie, + cookie::{Cookie, SameSite}, + get, + http::header, + put, web, HttpRequest, HttpResponse, +}; use config::{ cluster::{is_ingester, LOCAL_NODE_ROLE, LOCAL_NODE_UUID}, meta::cluster::NodeStatus, @@ -47,7 +53,7 @@ use { use crate::{ common::{ infra::{cluster, config::*}, - meta::{functions::ZoFunction, http::HttpResponse as MetaHttpResponse}, + meta::{functions::ZoFunction, http::HttpResponse as MetaHttpResponse, user::AuthTokens}, }, service::{db, search::datafusion::DEFAULT_FUNCTIONS}, }; @@ -308,6 +314,8 @@ async fn get_stream_schema_status() -> (usize, usize, usize) { #[cfg(feature = "enterprise")] #[get("/redirect")] pub async fn redirect(req: HttpRequest) -> Result { + use crate::common::meta::user::AuthTokens; + let query = web::Query::>::from_query(req.query_string()).unwrap(); let code = match query.get("code") { Some(code) => code, @@ -333,49 +341,38 @@ pub async fn redirect(req: HttpRequest) -> Result { match exchange_code(code).await { Ok(login_data) => { - let token = login_data.access_token; + let access_token = login_data.access_token; let keys = get_jwks().await; let token_ver = - verify_decode_token(&token, &keys, &O2_CONFIG.dex.client_id, true).await; + verify_decode_token(&access_token, &keys, &O2_CONFIG.dex.client_id, true).await; match token_ver { Ok(res) => process_token(res).await, Err(e) => return Ok(HttpResponse::Unauthorized().json(e.to_string())), } - let mut access_token_cookie = cookie::Cookie::new("access_token", token); - access_token_cookie.set_expires( - cookie::time::OffsetDateTime::now_utc() - + cookie::time::Duration::seconds(CONFIG.auth.cookie_max_age), - ); - access_token_cookie.set_http_only(true); - access_token_cookie.set_secure(CONFIG.auth.cookie_secure_only); - access_token_cookie.set_path("/"); - if CONFIG.auth.cookie_same_site_lax { - access_token_cookie.set_same_site(cookie::SameSite::Lax) - } else { - access_token_cookie.set_same_site(cookie::SameSite::None) - }; + let tokens = json::to_string(&AuthTokens { + access_token, + refresh_token: login_data.refresh_token, + }) + .unwrap(); - let mut refresh_token_cookie = - cookie::Cookie::new("refresh_token", login_data.refresh_token); - refresh_token_cookie.set_expires( + let mut auth_cookie = Cookie::new("auth_tokens", tokens); + auth_cookie.set_expires( cookie::time::OffsetDateTime::now_utc() + cookie::time::Duration::seconds(CONFIG.auth.cookie_max_age), ); - refresh_token_cookie.set_http_only(true); - refresh_token_cookie.set_secure(CONFIG.auth.cookie_secure_only); - refresh_token_cookie.set_path("/"); + auth_cookie.set_http_only(true); + auth_cookie.set_secure(CONFIG.auth.cookie_secure_only); + auth_cookie.set_path("/"); if CONFIG.auth.cookie_same_site_lax { - refresh_token_cookie.set_same_site(cookie::SameSite::Lax) + auth_cookie.set_same_site(SameSite::Lax); } else { - refresh_token_cookie.set_same_site(cookie::SameSite::None) - }; - + auth_cookie.set_same_site(SameSite::None); + } Ok(HttpResponse::Found() .append_header((header::LOCATION, login_data.url)) - .cookie(access_token_cookie) - .cookie(refresh_token_cookie) + .cookie(auth_cookie) .finish()) } Err(e) => Ok(HttpResponse::Unauthorized().json(e.to_string())), @@ -397,44 +394,57 @@ pub async fn dex_login() -> Result { #[cfg(feature = "enterprise")] #[get("/dex_refresh")] async fn refresh_token_with_dex(req: actix_web::HttpRequest) -> HttpResponse { - let token = if req.headers().contains_key(header::AUTHORIZATION) { - req.headers() - .get(header::AUTHORIZATION) - .unwrap() - .to_str() - .unwrap() - .to_string() + let token = if let Some(cookie) = req.cookie("auth_tokens") { + let auth_tokens: AuthTokens = json::from_str(cookie.value()).unwrap_or_default(); + auth_tokens.refresh_token } else { return HttpResponse::Unauthorized().finish(); }; // Exchange the refresh token for a new access token match refresh_token(&token).await { - Ok(token_response) => HttpResponse::Ok().json(token_response), - Err(_) => { - let mut access_cookie = cookie::Cookie::new("access_token", ""); - access_cookie.set_http_only(true); - access_cookie.set_secure(CONFIG.auth.cookie_secure_only); - access_cookie.set_path("/"); - + Ok((access_token, refresh_token)) => { + let tokens = json::to_string(&AuthTokens { + access_token, + refresh_token, + }) + .unwrap(); + + let mut auth_cookie = Cookie::new("auth_tokens", tokens); + auth_cookie.set_expires( + cookie::time::OffsetDateTime::now_utc() + + cookie::time::Duration::seconds(CONFIG.auth.cookie_max_age), + ); + auth_cookie.set_http_only(true); + auth_cookie.set_secure(CONFIG.auth.cookie_secure_only); + auth_cookie.set_path("/"); if CONFIG.auth.cookie_same_site_lax { - access_cookie.set_same_site(cookie::SameSite::Lax) + auth_cookie.set_same_site(SameSite::Lax); } else { - access_cookie.set_same_site(cookie::SameSite::None) - }; - let mut refresh_cookie = cookie::Cookie::new("refresh_token", ""); - refresh_cookie.set_http_only(true); - refresh_cookie.set_secure(CONFIG.auth.cookie_secure_only); - refresh_cookie.set_path("/"); + auth_cookie.set_same_site(SameSite::None); + } + + HttpResponse::Ok().cookie(auth_cookie).finish() + } + Err(_) => { + let tokens = json::to_string(&AuthTokens::default()).unwrap(); + let mut auth_cookie = Cookie::new("auth_tokens", tokens); + auth_cookie.set_expires( + cookie::time::OffsetDateTime::now_utc() + + cookie::time::Duration::seconds(CONFIG.auth.cookie_max_age), + ); + auth_cookie.set_http_only(true); + auth_cookie.set_secure(CONFIG.auth.cookie_secure_only); + auth_cookie.set_path("/"); if CONFIG.auth.cookie_same_site_lax { - refresh_cookie.set_same_site(cookie::SameSite::Lax) + auth_cookie.set_same_site(SameSite::Lax); } else { - refresh_cookie.set_same_site(cookie::SameSite::None) - }; + auth_cookie.set_same_site(SameSite::None); + } + HttpResponse::Unauthorized() .append_header((header::LOCATION, "/")) - .cookie(access_cookie) - .cookie(refresh_cookie) + .cookie(auth_cookie) .finish() } } @@ -442,20 +452,24 @@ async fn refresh_token_with_dex(req: actix_web::HttpRequest) -> HttpResponse { #[get("/logout")] async fn logout(_req: actix_web::HttpRequest) -> HttpResponse { - let mut access_cookie = cookie::Cookie::new("access_token", ""); - access_cookie.set_http_only(true); - access_cookie.set_secure(CONFIG.auth.cookie_secure_only); - access_cookie.set_path("/"); - access_cookie.set_same_site(cookie::SameSite::Lax); - let mut refresh_cookie = cookie::Cookie::new("refresh_token", ""); - refresh_cookie.set_http_only(true); - refresh_cookie.set_secure(CONFIG.auth.cookie_secure_only); - refresh_cookie.set_path("/"); - refresh_cookie.set_same_site(cookie::SameSite::Lax); + let tokens = json::to_string(&AuthTokens::default()).unwrap(); + let mut auth_cookie = Cookie::new("auth_tokens", tokens); + auth_cookie.set_expires( + cookie::time::OffsetDateTime::now_utc() + + cookie::time::Duration::seconds(CONFIG.auth.cookie_max_age), + ); + auth_cookie.set_http_only(true); + auth_cookie.set_secure(CONFIG.auth.cookie_secure_only); + auth_cookie.set_path("/"); + if CONFIG.auth.cookie_same_site_lax { + auth_cookie.set_same_site(SameSite::Lax); + } else { + auth_cookie.set_same_site(SameSite::None); + } + HttpResponse::Ok() .append_header((header::LOCATION, "/")) - .cookie(access_cookie) - .cookie(refresh_cookie) + .cookie(auth_cookie) .finish() } diff --git a/src/handler/http/request/users/mod.rs b/src/handler/http/request/users/mod.rs index 836a2d99f7..9870fbaf24 100644 --- a/src/handler/http/request/users/mod.rs +++ b/src/handler/http/request/users/mod.rs @@ -16,7 +16,10 @@ use std::io::Error; use actix_web::{cookie, delete, get, http, post, put, web, HttpResponse}; -use config::{utils::base64, CONFIG}; +use config::{ + utils::{base64, json}, + CONFIG, +}; use strum::IntoEnumIterator; use crate::{ @@ -24,8 +27,8 @@ use crate::{ meta::{ self, user::{ - RolesResponse, SignInResponse, SignInUser, UpdateUser, UserOrgRole, UserRequest, - UserRole, + AuthTokens, RolesResponse, SignInResponse, SignInUser, UpdateUser, UserOrgRole, + UserRequest, UserRole, }, }, utils::auth::UserEmail, @@ -238,24 +241,29 @@ pub async fn authentication(auth: web::Json) -> Result { .get("/config/dex_refresh", { //headers: { Authorization: `${refreshToken}` }, }) - .then((res) => { - return instance(error.config); + .then((res) => { + if (res.status === 200) { + // Token refreshed successfully, retry the original request + return instance.request(error.config); + } }) .catch((refreshError) => { instance.get("/config/logout", {}).then((res) => { diff --git a/web/src/utils/zincutils.ts b/web/src/utils/zincutils.ts index 9f605ccbd7..96b8aa9c6c 100644 --- a/web/src/utils/zincutils.ts +++ b/web/src/utils/zincutils.ts @@ -109,6 +109,7 @@ export const getUserInfo = (loginString: string) => { JSON.stringify(payload) ); useLocalUserInfo(encodedSessionData); + decToken = payload; } catch (error) { // If parsing fails, it's not a valid JWT console.error("Invalid JWT token:", error); diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 7c662e4cdd..19548c43f8 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -15,7 +15,7 @@ along with this program. If not, see . -->