aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrtkay123 <dev@kanjala.com>2026-04-11 11:08:14 +0200
committerrtkay123 <dev@kanjala.com>2026-04-11 11:08:14 +0200
commitb7c123490a1dad28c84960f00fe206fd8b43f0d4 (patch)
tree5e5a34a6ab63f77f88b75a20abea8ecac07744b0
parenta4e60c9cce8831a12c7ad9c2d4855a8ebf95d64c (diff)
downloadsellershut-b7c123490a1dad28c84960f00fe206fd8b43f0d4.tar.bz2
sellershut-b7c123490a1dad28c84960f00fe206fd8b43f0d4.zip
refactor: reuse structure
-rw-r--r--crates/api-auth/src/discord/mod.rs101
-rw-r--r--crates/api-auth/src/error.rs10
-rw-r--r--crates/api-auth/src/lib.rs2
-rw-r--r--crates/api-auth/src/util.rs103
4 files changed, 139 insertions, 77 deletions
diff --git a/crates/api-auth/src/discord/mod.rs b/crates/api-auth/src/discord/mod.rs
index 0844f58..43a62bf 100644
--- a/crates/api-auth/src/discord/mod.rs
+++ b/crates/api-auth/src/discord/mod.rs
@@ -1,7 +1,7 @@
use api_core::models::user::User;
use async_session::{Session, serde_json};
use async_trait::async_trait;
-use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse};
+use oauth2::{AuthorizationCode, CsrfToken, TokenResponse};
use redis::AsyncCommands;
use serde::{Deserialize, Serialize};
use sh_util::cache::{CacheKey, RedisManager};
@@ -19,11 +19,19 @@ struct DiscordUser {
avatar: Option<String>,
username: String,
discriminator: String,
+ email: Option<String>,
+ verified: bool,
}
-impl From<DiscordUser> for User {
- fn from(value: DiscordUser) -> Self {
- todo!()
+impl TryFrom<DiscordUser> for User {
+ type Error = AuthError;
+
+ fn try_from(user_data: DiscordUser) -> Result<Self, Self::Error> {
+ match (&user_data.email, user_data.verified) {
+ (None, _) => Err(AuthError::MissingEmail),
+ (_, false) => Err(AuthError::EmailNotVerified),
+ (Some(_), true) => Ok(Self {}),
+ }
}
}
@@ -47,84 +55,23 @@ impl AuthServiceDiscord {
#[async_trait]
impl OauthDriver for AuthServiceDiscord {
async fn get_user(&self, client: &AuthHttpClient, code: &str) -> Result<User, AuthError> {
- // Get an auth token
- let token = self
- .client
- .exchange_code(AuthorizationCode::new(code.to_owned()))
- .request_async(client)
- .await
- .unwrap();
- // Fetch user data from discord
- let user_data: DiscordUser = 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
- .unwrap()
- .json::<DiscordUser>()
- .await
- .unwrap();
-
- Ok(user_data.into())
+ crate::util::get_user::<DiscordUser>(
+ &self.client,
+ client,
+ code,
+ "https://discordapp.com/api/users/@me",
+ )
+ .await
}
- async fn validate_session(&self, cookie: &str, state: &str) -> Result<(), AuthError> {
- let id = Session::id_from_cookie_value(cookie)?;
- let cache_key = CacheKey::Session(&id);
- let mut cache = self.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::<CsrfToken>(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 {
- return Err(AuthError::TokenMismatch);
- } else {
- return Ok(());
- }
- } else {
- return Err(AuthError::NoCSRFToken);
- }
- }
- None => return Err(AuthError::MissingSession),
- }
+ async fn validate_session(&self, cookie: &str, state: &str) -> Result<(), AuthError> {
+ crate::util::validate_session(&self.cache, cookie, state).await
}
- async fn create_oauth_session(&self) -> Result<SessionResponse, AuthError> {
- let (auth_url, csrf_token) = self
- .client
- .authorize_url(CsrfToken::new_random)
- .add_scope(Scope::new("identify".to_string()))
- .url();
-
- let mut session = Session::new();
- session.insert(CSRF_TOKEN, &csrf_token).unwrap();
- let cache_key = CacheKey::Session(session.id());
- let mut cache = self.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,
- })
+ async fn create_oauth_session(&self) -> Result<SessionResponse, AuthError> {
+ crate::util::create_oauth_session(&self.client, &self.cache, &["identify", "email"]).await
}
+
async fn save_session(&self, user: &User) -> Result<(), AuthError> {
todo!()
}
diff --git a/crates/api-auth/src/error.rs b/crates/api-auth/src/error.rs
index 2db3281..86da20c 100644
--- a/crates/api-auth/src/error.rs
+++ b/crates/api-auth/src/error.rs
@@ -35,4 +35,14 @@ pub enum AuthError {
TokenMismatch,
#[error("CSRF token missing")]
NoCSRFToken,
+ #[error("No email available for this user")]
+ MissingEmail,
+ #[error("Email is not verified")]
+ EmailNotVerified,
+ #[error("oauth token for user")]
+ UserToken,
+ #[error("could not get user through http")]
+ UserRetrieval,
+ #[error("remote user mismatch")]
+ UserDeserialisation,
}
diff --git a/crates/api-auth/src/lib.rs b/crates/api-auth/src/lib.rs
index 815b170..24b966c 100644
--- a/crates/api-auth/src/lib.rs
+++ b/crates/api-auth/src/lib.rs
@@ -2,6 +2,7 @@
pub mod discord;
pub mod client;
+pub(crate) mod util;
mod error;
use api_core::auth::AuthClientConfig;
@@ -39,6 +40,7 @@ use url::Url;
use crate::error::AuthError;
+#[allow(dead_code)]
static CSRF_TOKEN: &str = "csrf_token";
pub struct SessionResponse {
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<SessionResponse, AuthError> {
+ 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<T>(
+ c: &BasicClient,
+ client: &AuthHttpClient,
+ code: &str,
+ endpoint: &str,
+) -> Result<User, AuthError>
+where
+ User: TryFrom<T>,
+ 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::<T>()
+ .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::<CsrfToken>(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),
+ }
+ }