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), } }