feat: add global email verification check

This commit is contained in:
Radical 2025-05-29 18:35:13 +02:00
parent 29dbb085a2
commit abfbaf8918
18 changed files with 106 additions and 63 deletions

View file

@ -36,6 +36,8 @@ pub async fn join(
let uuid = check_access_token(auth_header, &mut conn).await?; 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 invite = Invite::fetch_one(&mut conn, invite_id).await?;
let guild = Guild::fetch_one(&mut conn, invite.guild_uuid).await?; let guild = Guild::fetch_one(&mut conn, invite.guild_uuid).await?;

View file

@ -3,7 +3,7 @@ use actix_web::{HttpRequest, HttpResponse, Scope, get, patch, web};
use serde::Deserialize; use serde::Deserialize;
use crate::{ 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; mod servers;
@ -27,7 +27,7 @@ pub async fn get(req: HttpRequest, data: web::Data<Data>) -> Result<HttpResponse
Ok(HttpResponse::Ok().json(me)) Ok(HttpResponse::Ok().json(me))
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
struct NewInfo { struct NewInfo {
username: Option<String>, username: Option<String>,
display_name: Option<String>, display_name: Option<String>,
@ -39,7 +39,7 @@ struct NewInfo {
struct UploadForm { struct UploadForm {
#[multipart(limit = "100MB")] #[multipart(limit = "100MB")]
avatar: Option<TempFile>, avatar: Option<TempFile>,
json: Option<MpJson<NewInfo>>, json: MpJson<Option<NewInfo>>,
} }
#[patch("")] #[patch("")]
@ -56,6 +56,10 @@ pub async fn update(
let uuid = check_access_token(auth_header, &mut conn).await?; 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?; let mut me = Me::get(&mut conn, uuid).await?;
if let Some(avatar) = form.avatar { if let Some(avatar) = form.avatar {
@ -72,7 +76,7 @@ pub async fn update(
.await?; .await?;
} }
if let Some(new_info) = form.json { if let Some(new_info) = form.json.0 {
if let Some(username) = &new_info.username { if let Some(username) = &new_info.username {
me.set_username(&mut conn, username.clone()).await?; me.set_username(&mut conn, username.clone()).await?;
} }

View file

@ -2,7 +2,7 @@
use actix_web::{get, web, HttpRequest, HttpResponse}; 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 /// `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<Data>) -> Result<HttpResponse
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
let me = Me::get(&mut conn, uuid).await?; let me = Me::get(&mut conn, uuid).await?;
let memberships = me.fetch_memberships(&mut conn).await?; let memberships = me.fetch_memberships(&mut conn).await?;

View file

@ -6,11 +6,7 @@ use serde::Deserialize;
mod uuid; mod uuid;
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Guild, StartAmountQuery}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Guild, StartAmountQuery},
utils::get_auth_header,
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
@ -134,7 +130,9 @@ pub async fn get(
let amount = request_query.amount.unwrap_or(10); let amount = request_query.amount.unwrap_or(10);
check_access_token(auth_header, &mut data.pool.get().await?).await?; let uuid = check_access_token(auth_header, &mut data.pool.get().await?).await?;
global_checks(&data, uuid).await?;
let guilds = Guild::fetch_amount(&data.pool, start, amount).await?; let guilds = Guild::fetch_amount(&data.pool, start, amount).await?;

View file

@ -1,5 +1,5 @@
use crate::{ use crate::{
api::v1::auth::check_access_token, error::Error, structs::{Channel, Member}, utils::{get_auth_header, order_by_is_above}, Data api::v1::auth::check_access_token, error::Error, structs::{Channel, Member}, utils::{get_auth_header, global_checks, order_by_is_above}, Data
}; };
use ::uuid::Uuid; use ::uuid::Uuid;
use actix_web::{HttpRequest, HttpResponse, get, post, web}; use actix_web::{HttpRequest, HttpResponse, get, post, web};
@ -29,6 +29,8 @@ pub async fn get(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
if let Ok(cache_hit) = data.get_cache_key(format!("{}_channels", guild_uuid)).await { if let Ok(cache_hit) = data.get_cache_key(format!("{}_channels", guild_uuid)).await {
@ -68,6 +70,8 @@ pub async fn create(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
// FIXME: Logic to check permissions, should probably be done in utils.rs // FIXME: Logic to check permissions, should probably be done in utils.rs

View file

@ -1,11 +1,7 @@
//! `/api/v1/servers/{uuid}/channels/{uuid}/messages` Endpoints related to channel messages //! `/api/v1/servers/{uuid}/channels/{uuid}/messages` Endpoints related to channel messages
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Channel, Member}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Channel, Member},
utils::get_auth_header,
}; };
use ::uuid::Uuid; use ::uuid::Uuid;
use actix_web::{HttpRequest, HttpResponse, get, web}; use actix_web::{HttpRequest, HttpResponse, get, web};
@ -64,6 +60,8 @@ pub async fn get(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
let channel: Channel; let channel: Channel;

View file

@ -2,11 +2,7 @@ pub mod messages;
pub mod socket; pub mod socket;
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Channel, Member}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Channel, Member},
utils::get_auth_header,
}; };
use actix_web::{HttpRequest, HttpResponse, delete, get, web}; use actix_web::{HttpRequest, HttpResponse, delete, get, web};
use uuid::Uuid; use uuid::Uuid;
@ -27,6 +23,8 @@ pub async fn get(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
if let Ok(cache_hit) = data.get_cache_key(format!("{}", channel_uuid)).await { if let Ok(cache_hit) = data.get_cache_key(format!("{}", channel_uuid)).await {
@ -59,6 +57,8 @@ pub async fn delete(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
let channel: Channel; let channel: Channel;

View file

@ -8,10 +8,7 @@ use futures_util::StreamExt as _;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, structs::{Channel, Member}, utils::{get_ws_protocol_header, global_checks}, Data
api::v1::auth::check_access_token,
structs::{Channel, Member},
utils::get_ws_protocol_header,
}; };
#[get("{uuid}/channels/{channel_uuid}/socket")] #[get("{uuid}/channels/{channel_uuid}/socket")]
@ -35,6 +32,8 @@ pub async fn ws(
// Authorize client using auth header // Authorize client using auth header
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
// Get server member from psql // Get server member from psql
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;

View file

@ -5,11 +5,7 @@ use futures_util::StreamExt as _;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Guild, Member}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Guild, Member},
utils::get_auth_header,
}; };
/// `PUT /api/v1/servers/{uuid}/icon` Icon upload /// `PUT /api/v1/servers/{uuid}/icon` Icon upload
@ -34,6 +30,8 @@ pub async fn upload(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
let mut guild = Guild::fetch_one(&mut conn, guild_uuid).await?; let mut guild = Guild::fetch_one(&mut conn, guild_uuid).await?;

View file

@ -3,11 +3,7 @@ use serde::Deserialize;
use uuid::Uuid; use uuid::Uuid;
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Guild, Member}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Guild, Member},
utils::get_auth_header,
}; };
#[derive(Deserialize)] #[derive(Deserialize)]
@ -31,6 +27,8 @@ pub async fn get(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
let guild = Guild::fetch_one(&mut conn, guild_uuid).await?; let guild = Guild::fetch_one(&mut conn, guild_uuid).await?;
@ -57,6 +55,8 @@ pub async fn create(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
let member = Member::fetch_one(&mut conn, uuid, guild_uuid).await?; let member = Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
let guild = Guild::fetch_one(&mut conn, guild_uuid).await?; let guild = Guild::fetch_one(&mut conn, guild_uuid).await?;

View file

@ -9,11 +9,7 @@ mod invites;
mod roles; mod roles;
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Guild, Member}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Guild, Member},
utils::get_auth_header,
}; };
pub fn web() -> Scope { pub fn web() -> Scope {
@ -87,6 +83,8 @@ pub async fn get(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
let guild = Guild::fetch_one(&mut conn, guild_uuid).await?; let guild = Guild::fetch_one(&mut conn, guild_uuid).await?;

View file

@ -3,7 +3,7 @@ use actix_web::{HttpRequest, HttpResponse, get, post, web};
use serde::Deserialize; use serde::Deserialize;
use crate::{ 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; pub mod uuid;
@ -64,6 +64,8 @@ pub async fn create(
let uuid = check_access_token(auth_header, &mut conn).await?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
// FIXME: Logic to check permissions, should probably be done in utils.rs // FIXME: Logic to check permissions, should probably be done in utils.rs

View file

@ -1,9 +1,5 @@
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{Member, Role}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{Member, Role},
utils::get_auth_header,
}; };
use ::uuid::Uuid; use ::uuid::Uuid;
use actix_web::{HttpRequest, HttpResponse, get, web}; 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?; let uuid = check_access_token(auth_header, &mut conn).await?;
global_checks(&data, uuid).await?;
Member::fetch_one(&mut conn, uuid, guild_uuid).await?; Member::fetch_one(&mut conn, uuid, guild_uuid).await?;
if let Ok(cache_hit) = data.get_cache_key(format!("{}", role_uuid)).await { if let Ok(cache_hit) = data.get_cache_key(format!("{}", role_uuid)).await {

View file

@ -3,11 +3,7 @@
use actix_web::{HttpRequest, HttpResponse, Scope, get, web}; use actix_web::{HttpRequest, HttpResponse, Scope, get, web};
use crate::{ use crate::{
Data, api::v1::auth::check_access_token, error::Error, structs::{StartAmountQuery, User}, utils::{get_auth_header, global_checks}, Data
api::v1::auth::check_access_token,
error::Error,
structs::{StartAmountQuery, User},
utils::get_auth_header,
}; };
mod uuid; mod uuid;
@ -68,7 +64,9 @@ pub async fn get(
let mut conn = data.pool.get().await?; 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?; let users = User::fetch_amount(&mut conn, start, amount).await?;

View file

@ -4,7 +4,7 @@ use actix_web::{HttpRequest, HttpResponse, get, web};
use uuid::Uuid; use uuid::Uuid;
use crate::{ 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 /// `GET /api/v1/users/{uuid}` Returns user with the given UUID
@ -31,15 +31,17 @@ pub async fn get(
) -> Result<HttpResponse, Error> { ) -> Result<HttpResponse, Error> {
let headers = req.headers(); 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 auth_header = get_auth_header(headers)?;
let mut conn = data.pool.get().await?; 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)) Ok(HttpResponse::Ok().json(user))
} }

View file

@ -11,7 +11,7 @@ pub struct ConfigBuilder {
database: Database, database: Database,
cache_database: CacheDatabase, cache_database: CacheDatabase,
web: WebBuilder, web: WebBuilder,
instance: Option<Instance>, instance: Option<InstanceBuilder>,
bunny: BunnyBuilder, bunny: BunnyBuilder,
mail: Mail, mail: Mail,
} }
@ -42,9 +42,10 @@ struct WebBuilder {
_ssl: Option<bool>, _ssl: Option<bool>,
} }
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize)]
pub struct Instance { struct InstanceBuilder {
pub registration: bool, registration: Option<bool>,
require_email_verification: Option<bool>,
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
@ -106,11 +107,22 @@ impl ConfigBuilder {
cdn_url: self.bunny.cdn_url, 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 { Config {
database: self.database, database: self.database,
cache_database: self.cache_database, cache_database: self.cache_database,
web, web,
instance: self.instance.unwrap_or(Instance { registration: true }), instance,
bunny, bunny,
mail: self.mail, mail: self.mail,
} }
@ -134,6 +146,12 @@ pub struct Web {
pub url: Url, pub url: Url,
} }
#[derive(Debug, Clone)]
pub struct Instance {
pub registration: bool,
pub require_email_verification: bool,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Bunny { pub struct Bunny {
pub api_key: String, pub api_key: String,

View file

@ -16,7 +16,7 @@ use argon2::{
}; };
use crate::{ 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 { pub trait HasUuid {
@ -1070,6 +1070,8 @@ impl PasswordResetToken {
let user_uuid = user_uuid_from_identifier(&mut conn, &identifier).await?; let user_uuid = user_uuid_from_identifier(&mut conn, &identifier).await?;
global_checks(&data, user_uuid).await?;
use users::dsl as udsl; use users::dsl as udsl;
let (username, email_address): (String, String) = udsl::users let (username, email_address): (String, String) = udsl::users
.filter(udsl::uuid.eq(user_uuid)) .filter(udsl::uuid.eq(user_uuid))

View file

@ -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<T>(mut items: Vec<T>) -> Result<Vec<T>, Error> pub async fn order_by_is_above<T>(mut items: Vec<T>) -> Result<Vec<T>, Error>
where where
T: HasUuid + HasIsAbove, T: HasUuid + HasIsAbove,