From abfbaf8918bcb1942c5dd8c5f0156e36ff2a8747 Mon Sep 17 00:00:00 2001 From: Radical Date: Thu, 29 May 2025 18:35:13 +0200 Subject: [PATCH] feat: add global email verification check --- src/api/v1/invites/id.rs | 2 ++ src/api/v1/me/mod.rs | 12 +++++--- src/api/v1/me/servers.rs | 4 ++- src/api/v1/servers/mod.rs | 10 +++---- src/api/v1/servers/uuid/channels/mod.rs | 6 +++- .../v1/servers/uuid/channels/uuid/messages.rs | 8 ++---- src/api/v1/servers/uuid/channels/uuid/mod.rs | 10 +++---- .../v1/servers/uuid/channels/uuid/socket.rs | 7 ++--- src/api/v1/servers/uuid/icon.rs | 8 ++---- src/api/v1/servers/uuid/invites/mod.rs | 10 +++---- src/api/v1/servers/uuid/mod.rs | 8 ++---- src/api/v1/servers/uuid/roles/mod.rs | 4 ++- src/api/v1/servers/uuid/roles/uuid.rs | 8 ++---- src/api/v1/users/mod.rs | 10 +++---- src/api/v1/users/uuid.rs | 10 ++++--- src/config.rs | 28 +++++++++++++++---- src/structs.rs | 4 ++- src/utils.rs | 20 +++++++++++++ 18 files changed, 106 insertions(+), 63 deletions(-) diff --git a/src/api/v1/invites/id.rs b/src/api/v1/invites/id.rs index 14b444f..f7e4a08 100644 --- a/src/api/v1/invites/id.rs +++ b/src/api/v1/invites/id.rs @@ -36,6 +36,8 @@ pub async fn join( let uuid = check_access_token(auth_header, &mut conn).await?; + global_checks(&data, uuid).await?; + let invite = Invite::fetch_one(&mut conn, invite_id).await?; let guild = Guild::fetch_one(&mut conn, invite.guild_uuid).await?; diff --git a/src/api/v1/me/mod.rs b/src/api/v1/me/mod.rs index 605eb1c..cd86e43 100644 --- a/src/api/v1/me/mod.rs +++ b/src/api/v1/me/mod.rs @@ -3,7 +3,7 @@ use actix_web::{HttpRequest, HttpResponse, Scope, get, patch, web}; use serde::Deserialize; use crate::{ - Data, api::v1::auth::check_access_token, error::Error, structs::Me, utils::get_auth_header, + api::v1::auth::check_access_token, error::Error, structs::Me, utils::{get_auth_header, global_checks}, Data }; mod servers; @@ -27,7 +27,7 @@ pub async fn get(req: HttpRequest, data: web::Data) -> Result, display_name: Option, @@ -39,7 +39,7 @@ struct NewInfo { struct UploadForm { #[multipart(limit = "100MB")] avatar: Option, - json: Option>, + json: MpJson>, } #[patch("")] @@ -56,6 +56,10 @@ pub async fn update( let uuid = check_access_token(auth_header, &mut conn).await?; + if form.avatar.is_some() || form.json.0.clone().is_some_and(|ni| ni.username.is_some() || ni.display_name.is_some()) { + global_checks(&data, uuid).await?; + } + let mut me = Me::get(&mut conn, uuid).await?; if let Some(avatar) = form.avatar { @@ -72,7 +76,7 @@ pub async fn update( .await?; } - if let Some(new_info) = form.json { + if let Some(new_info) = form.json.0 { if let Some(username) = &new_info.username { me.set_username(&mut conn, username.clone()).await?; } diff --git a/src/api/v1/me/servers.rs b/src/api/v1/me/servers.rs index 390c31e..ae026a7 100644 --- a/src/api/v1/me/servers.rs +++ b/src/api/v1/me/servers.rs @@ -2,7 +2,7 @@ use actix_web::{get, web, HttpRequest, HttpResponse}; -use crate::{api::v1::auth::check_access_token, error::Error, structs::Me, utils::get_auth_header, Data}; +use crate::{api::v1::auth::check_access_token, error::Error, structs::Me, utils::{get_auth_header, global_checks}, Data}; /// `GET /api/v1/me/servers` Returns all guild memberships in a list @@ -37,6 +37,8 @@ pub async fn get(req: HttpRequest, data: web::Data) -> Result Scope { @@ -87,6 +83,8 @@ pub async fn get( let uuid = check_access_token(auth_header, &mut conn).await?; + global_checks(&data, uuid).await?; + Member::fetch_one(&mut conn, uuid, guild_uuid).await?; let guild = Guild::fetch_one(&mut conn, guild_uuid).await?; diff --git a/src/api/v1/servers/uuid/roles/mod.rs b/src/api/v1/servers/uuid/roles/mod.rs index c0ebaa2..3ae9c5b 100644 --- a/src/api/v1/servers/uuid/roles/mod.rs +++ b/src/api/v1/servers/uuid/roles/mod.rs @@ -3,7 +3,7 @@ use actix_web::{HttpRequest, HttpResponse, get, post, web}; use serde::Deserialize; use crate::{ - api::v1::auth::check_access_token, error::Error, structs::{Member, Role}, utils::{get_auth_header, order_by_is_above}, Data + api::v1::auth::check_access_token, error::Error, structs::{Member, Role}, utils::{get_auth_header, global_checks, order_by_is_above}, Data }; pub mod uuid; @@ -64,6 +64,8 @@ pub async fn create( let uuid = check_access_token(auth_header, &mut conn).await?; + global_checks(&data, uuid).await?; + Member::fetch_one(&mut conn, uuid, guild_uuid).await?; // FIXME: Logic to check permissions, should probably be done in utils.rs diff --git a/src/api/v1/servers/uuid/roles/uuid.rs b/src/api/v1/servers/uuid/roles/uuid.rs index 8ca3cc5..21ab748 100644 --- a/src/api/v1/servers/uuid/roles/uuid.rs +++ b/src/api/v1/servers/uuid/roles/uuid.rs @@ -1,9 +1,5 @@ use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - structs::{Member, Role}, - utils::get_auth_header, + api::v1::auth::check_access_token, error::Error, structs::{Member, Role}, utils::{get_auth_header, global_checks}, Data }; use ::uuid::Uuid; use actix_web::{HttpRequest, HttpResponse, get, web}; @@ -24,6 +20,8 @@ pub async fn get( let uuid = check_access_token(auth_header, &mut conn).await?; + global_checks(&data, uuid).await?; + Member::fetch_one(&mut conn, uuid, guild_uuid).await?; if let Ok(cache_hit) = data.get_cache_key(format!("{}", role_uuid)).await { diff --git a/src/api/v1/users/mod.rs b/src/api/v1/users/mod.rs index 0b80fd1..b3d853b 100644 --- a/src/api/v1/users/mod.rs +++ b/src/api/v1/users/mod.rs @@ -3,11 +3,7 @@ use actix_web::{HttpRequest, HttpResponse, Scope, get, web}; use crate::{ - Data, - api::v1::auth::check_access_token, - error::Error, - structs::{StartAmountQuery, User}, - utils::get_auth_header, + api::v1::auth::check_access_token, error::Error, structs::{StartAmountQuery, User}, utils::{get_auth_header, global_checks}, Data }; mod uuid; @@ -68,7 +64,9 @@ pub async fn get( let mut conn = data.pool.get().await?; - check_access_token(auth_header, &mut conn).await?; + let uuid = check_access_token(auth_header, &mut conn).await?; + + global_checks(&data, uuid).await?; let users = User::fetch_amount(&mut conn, start, amount).await?; diff --git a/src/api/v1/users/uuid.rs b/src/api/v1/users/uuid.rs index 1e4b9e5..337019b 100644 --- a/src/api/v1/users/uuid.rs +++ b/src/api/v1/users/uuid.rs @@ -4,7 +4,7 @@ use actix_web::{HttpRequest, HttpResponse, get, web}; use uuid::Uuid; use crate::{ - Data, api::v1::auth::check_access_token, error::Error, structs::User, utils::get_auth_header, + api::v1::auth::check_access_token, error::Error, structs::User, utils::{get_auth_header, global_checks}, Data }; /// `GET /api/v1/users/{uuid}` Returns user with the given UUID @@ -31,15 +31,17 @@ pub async fn get( ) -> Result { let headers = req.headers(); - let uuid = path.into_inner().0; + let user_uuid = path.into_inner().0; let auth_header = get_auth_header(headers)?; let mut conn = data.pool.get().await?; - check_access_token(auth_header, &mut conn).await?; + let uuid = check_access_token(auth_header, &mut conn).await?; - let user = User::fetch_one(&data, uuid).await?; + global_checks(&data, uuid).await?; + + let user = User::fetch_one(&data, user_uuid).await?; Ok(HttpResponse::Ok().json(user)) } diff --git a/src/config.rs b/src/config.rs index 9ffd9c2..6db0483 100644 --- a/src/config.rs +++ b/src/config.rs @@ -11,7 +11,7 @@ pub struct ConfigBuilder { database: Database, cache_database: CacheDatabase, web: WebBuilder, - instance: Option, + instance: Option, bunny: BunnyBuilder, mail: Mail, } @@ -42,9 +42,10 @@ struct WebBuilder { _ssl: Option, } -#[derive(Debug, Deserialize, Clone)] -pub struct Instance { - pub registration: bool, +#[derive(Debug, Deserialize)] +struct InstanceBuilder { + registration: Option, + require_email_verification: Option, } #[derive(Debug, Deserialize)] @@ -106,11 +107,22 @@ impl ConfigBuilder { cdn_url: self.bunny.cdn_url, }; + let instance = match self.instance { + Some(instance) => Instance { + registration: instance.registration.unwrap_or(true), + require_email_verification: instance.require_email_verification.unwrap_or(false), + }, + None => Instance { + registration: true, + require_email_verification: false, + }, + }; + Config { database: self.database, cache_database: self.cache_database, web, - instance: self.instance.unwrap_or(Instance { registration: true }), + instance, bunny, mail: self.mail, } @@ -134,6 +146,12 @@ pub struct Web { pub url: Url, } +#[derive(Debug, Clone)] +pub struct Instance { + pub registration: bool, + pub require_email_verification: bool, +} + #[derive(Debug, Clone)] pub struct Bunny { pub api_key: String, diff --git a/src/structs.rs b/src/structs.rs index eb89980..4ece87a 100644 --- a/src/structs.rs +++ b/src/structs.rs @@ -16,7 +16,7 @@ use argon2::{ }; use crate::{ - error::Error, schema::*, utils::{generate_refresh_token, image_check, order_by_is_above, user_uuid_from_identifier, EMAIL_REGEX, PASSWORD_REGEX, USERNAME_REGEX}, Conn, Data + error::Error, schema::*, utils::{generate_refresh_token, global_checks, image_check, order_by_is_above, user_uuid_from_identifier, EMAIL_REGEX, PASSWORD_REGEX, USERNAME_REGEX}, Conn, Data }; pub trait HasUuid { @@ -1070,6 +1070,8 @@ impl PasswordResetToken { let user_uuid = user_uuid_from_identifier(&mut conn, &identifier).await?; + global_checks(&data, user_uuid).await?; + use users::dsl as udsl; let (username, email_address): (String, String) = udsl::users .filter(udsl::uuid.eq(user_uuid)) diff --git a/src/utils.rs b/src/utils.rs index 1d39fdb..d5d4480 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -161,6 +161,26 @@ pub async fn user_uuid_from_identifier(conn: &mut Conn, identifier: &String) -> } } +pub async fn global_checks(data: &Data, user_uuid: Uuid) -> Result<(), Error> { + if data.config.instance.require_email_verification { + let mut conn = data.pool.get().await?; + + use users::dsl; + let email_verified: bool = dsl::users + .filter(dsl::uuid.eq(user_uuid)) + .select(dsl::email_verified) + .get_result(&mut conn) + .await?; + + if !email_verified { + return Err(Error::Forbidden("server requires email verification".to_string())) + } + } + + + Ok(()) +} + pub async fn order_by_is_above(mut items: Vec) -> Result, Error> where T: HasUuid + HasIsAbove,