Skip to content

Commit

Permalink
fix: cookies issue for SSO (#3244)
Browse files Browse the repository at this point in the history
Co-authored-by: Bhargav <BJP232004@GMAIL.COM>
  • Loading branch information
oasisk and bjp232004 committed Apr 16, 2024
1 parent d654dd2 commit 1c1b5c5
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 89 deletions.
6 changes: 6 additions & 0 deletions src/common/meta/user.rs
Expand Up @@ -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,
}
18 changes: 13 additions & 5 deletions src/common/utils/auth.rs
Expand Up @@ -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};
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
148 changes: 81 additions & 67 deletions src/handler/http/request/status/mod.rs
Expand Up @@ -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,
Expand Down Expand Up @@ -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},
};
Expand Down Expand Up @@ -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<HttpResponse, Error> {
use crate::common::meta::user::AuthTokens;

let query = web::Query::<HashMap<String, String>>::from_query(req.query_string()).unwrap();
let code = match query.get("code") {
Some(code) => code,
Expand All @@ -333,49 +341,38 @@ pub async fn redirect(req: HttpRequest) -> Result<HttpResponse, Error> {

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())),
Expand All @@ -397,65 +394,82 @@ pub async fn dex_login() -> Result<HttpResponse, Error> {
#[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()
}
}
}

#[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()
}

Expand Down
34 changes: 21 additions & 13 deletions src/handler/http/request/users/mod.rs
Expand Up @@ -16,16 +16,19 @@
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::{
common::{
meta::{
self,
user::{
RolesResponse, SignInResponse, SignInUser, UpdateUser, UserOrgRole, UserRequest,
UserRole,
AuthTokens, RolesResponse, SignInResponse, SignInUser, UpdateUser, UserOrgRole,
UserRequest, UserRole,
},
},
utils::auth::UserEmail,
Expand Down Expand Up @@ -238,24 +241,29 @@ pub async fn authentication(auth: web::Json<SignInUser>) -> Result<HttpResponse,
}
};
if resp.status {
let token = format!(
let access_token = format!(
"Basic {}",
base64::encode(&format!("{}:{}", auth.name, auth.password))
);
let mut access_cookie = cookie::Cookie::new("access_token", token);
access_cookie.set_expires(
let tokens = json::to_string(&AuthTokens {
access_token,
refresh_token: "".to_string(),
})
.unwrap();
let mut auth_cookie = cookie::Cookie::new("auth_tokens", tokens);
auth_cookie.set_expires(
cookie::time::OffsetDateTime::now_utc()
+ cookie::time::Duration::seconds(CONFIG.auth.cookie_max_age),
);
access_cookie.set_http_only(true);
access_cookie.set_secure(CONFIG.auth.cookie_secure_only);
access_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 {
access_cookie.set_same_site(cookie::SameSite::Lax)
auth_cookie.set_same_site(cookie::SameSite::Lax);
} else {
access_cookie.set_same_site(cookie::SameSite::None)
};
Ok(HttpResponse::Ok().cookie(access_cookie).json(resp))
auth_cookie.set_same_site(cookie::SameSite::None);
}
Ok(HttpResponse::Ok().cookie(auth_cookie).json(resp))
} else {
Ok(HttpResponse::Unauthorized().json(resp))
}
Expand Down
6 changes: 5 additions & 1 deletion src/router/http/mod.rs
Expand Up @@ -18,7 +18,7 @@ use actix_web::{http::Error, route, web, HttpRequest, HttpResponse};

use crate::common::infra::cluster;

const QUERIER_ROUTES: [&str; 12] = [
const QUERIER_ROUTES: [&str; 16] = [
"/summary",
"/schema",
"/streams",
Expand All @@ -31,6 +31,10 @@ const QUERIER_ROUTES: [&str; 12] = [
"/prometheus/api/v1/metadata",
"/prometheus/api/v1/labels",
"/prometheus/api/v1/label/",
"/config/dex_login",
"/config/logout",
"/config/redirect",
"/config/dex_refresh",
];

const FIXED_QUERIER_ROUTES: [&str; 3] = ["/summary", "/schema", "/streams"];
Expand Down
7 changes: 5 additions & 2 deletions web/src/services/http.ts
Expand Up @@ -73,8 +73,11 @@ const http = ({ headers } = {} as any) => {
.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) => {
Expand Down
1 change: 1 addition & 0 deletions web/src/utils/zincutils.ts
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion web/src/views/Login.vue
Expand Up @@ -15,7 +15,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
-->

<template>
<login />
<login v-if="user.email == ''" />
</template>

<script lang="ts">
Expand Down

0 comments on commit 1c1b5c5

Please sign in to comment.