From b7c123490a1dad28c84960f00fe206fd8b43f0d4 Mon Sep 17 00:00:00 2001 From: rtkay123 Date: Sat, 11 Apr 2026 11:08:14 +0200 Subject: refactor: reuse structure --- crates/api-auth/src/util.rs | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 crates/api-auth/src/util.rs (limited to 'crates/api-auth/src/util.rs') diff --git a/crates/api-auth/src/util.rs b/crates/api-auth/src/util.rs new file mode 100644 index 0000000..0893bd5 --- /dev/null +++ b/crates/api-auth/src/util.rs @@ -0,0 +1,103 @@ +use api_core::models::user::User; +use async_session::{Session, serde_json}; +use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse}; +use redis::AsyncCommands; +use serde::{Deserialize, de::DeserializeOwned}; +use sh_util::cache::{CacheKey, RedisManager}; + +use crate::{BasicClient, CSRF_TOKEN, SessionResponse, client::AuthHttpClient, error::AuthError}; + +pub async fn create_oauth_session( + client: &BasicClient, + cache: &RedisManager, + scopes: &[&str], +) -> Result { + let mut builder = client.authorize_url(CsrfToken::new_random); + + for pat in scopes { + builder = builder.add_scope(Scope::new(pat.to_string())); + } + let (auth_url, csrf_token) = builder.url(); + + let mut session = Session::new(); + session.insert(CSRF_TOKEN, &csrf_token).unwrap(); + + let cache_key = CacheKey::Session(session.id()); + let mut cache = cache.get().await.unwrap(); + cache + .set::<_, _, ()>( + cache_key, + serde_json::to_string(&session).or(Err(AuthError::InvalidSession))?, + ) + .await?; + let cookie = session + .into_cookie_value() + .ok_or(AuthError::MissingSession)?; + + Ok(SessionResponse { + cookie_value: cookie, + auth_url, + }) +} + +pub async fn get_user( + c: &BasicClient, + client: &AuthHttpClient, + code: &str, + endpoint: &str, +) -> Result +where + User: TryFrom, + T: DeserializeOwned, +{ + // Get an auth token + let token = c + .exchange_code(AuthorizationCode::new(code.to_owned())) + .request_async(client) + .await + .map_err(|_e| AuthError::UserToken)?; + // Fetch user data from discord + let user_data: T = client + // https://discord.com/developers/docs/resources/user#get-current-user + .get("https://discordapp.com/api/users/@me") + .bearer_auth(token.access_token().secret()) + .send() + .await + .map_err(|_e| AuthError::UserRetrieval)? + .json::() + .await + .map_err(|_e| AuthError::UserDeserialisation)?; + + User::try_from(user_data).map_err(|_e| AuthError::UserDeserialisation) +} + + pub async fn validate_session(cache: &RedisManager, cookie: &str, state: &str) -> Result<(), AuthError> { + let id = Session::id_from_cookie_value(cookie)?; + let cache_key = CacheKey::Session(&id); + let mut cache = cache.get().await.unwrap(); + let session = cache.get::<_, String>(&cache_key).await?; + let session: Session = + serde_json::from_str(&session).map_err(|_e| AuthError::InvalidSession)?; + + match session.validate() { + Some(session) => { + // Extract the CSRF token from the session + let stored_csrf_token = session.get::(CSRF_TOKEN); + + if let Some(stored) = stored_csrf_token { + // Cleanup the CSRF token session + cache.del::<_, ()>(cache_key).await?; + + // Validate CSRF token is the same as the one in the auth request + if *stored.secret() != state { + Err(AuthError::TokenMismatch) + } else { + Ok(()) + } + } else { + Err(AuthError::NoCSRFToken) + } + } + None => Err(AuthError::MissingSession), + } + } -- cgit v1.2.3