add: temporary bans
This commit is contained in:
parent
9650c0177e
commit
155fe34c6e
11 changed files with 132 additions and 19 deletions
|
@ -214,6 +214,7 @@ version = "1.0.0"
|
||||||
"mod_panel:label.invited_by" = "Invited by"
|
"mod_panel:label.invited_by" = "Invited by"
|
||||||
"mod_panel:label.send_debug_payload" = "Send debug payload"
|
"mod_panel:label.send_debug_payload" = "Send debug payload"
|
||||||
"mod_panel:label.ban_reason" = "Ban reason"
|
"mod_panel:label.ban_reason" = "Ban reason"
|
||||||
|
"mod_panel:label.ban_expiration" = "Ban expiration"
|
||||||
|
|
||||||
"requests:label.requests" = "Requests"
|
"requests:label.requests" = "Requests"
|
||||||
"requests:label.community_join_request" = "Community join request"
|
"requests:label.community_join_request" = "Community join request"
|
||||||
|
|
|
@ -87,10 +87,31 @@ macro_rules! get_user_from_token {
|
||||||
{
|
{
|
||||||
Ok(ua) => {
|
Ok(ua) => {
|
||||||
if ua.permissions.check_banned() {
|
if ua.permissions.check_banned() {
|
||||||
let mut banned_user = tetratto_core::model::auth::User::banned();
|
// check expiration
|
||||||
banned_user.ban_reason = ua.ban_reason;
|
let now = tetratto_shared::unix_epoch_timestamp();
|
||||||
|
let expired = ua.ban_expire <= now;
|
||||||
|
|
||||||
Some(banned_user)
|
if expired && ua.ban_expire != 0 {
|
||||||
|
$db.update_user_role(
|
||||||
|
ua.id,
|
||||||
|
ua.permissions
|
||||||
|
- tetratto_core::model::permissions::FinePermission::BANNED,
|
||||||
|
&ua,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("failed to auto unban user");
|
||||||
|
|
||||||
|
Some(ua)
|
||||||
|
} else {
|
||||||
|
// banned
|
||||||
|
let mut banned_user = tetratto_core::model::auth::User::banned();
|
||||||
|
|
||||||
|
banned_user.ban_reason = ua.ban_reason;
|
||||||
|
banned_user.ban_expire = ua.ban_expire;
|
||||||
|
|
||||||
|
Some(banned_user)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Some(ua)
|
Some(ua)
|
||||||
}
|
}
|
||||||
|
|
|
@ -298,7 +298,7 @@
|
||||||
(div
|
(div
|
||||||
("class" "flex flex_col gap_1")
|
("class" "flex flex_col gap_1")
|
||||||
(label
|
(label
|
||||||
("for" "title")
|
("for" "reason")
|
||||||
(str (text "mod_panel:label.ban_reason")))
|
(str (text "mod_panel:label.ban_reason")))
|
||||||
(textarea
|
(textarea
|
||||||
("type" "text")
|
("type" "text")
|
||||||
|
@ -309,6 +309,37 @@
|
||||||
(text "{{ profile.ban_reason|remove_script_tags|safe }}")))
|
(text "{{ profile.ban_reason|remove_script_tags|safe }}")))
|
||||||
(button
|
(button
|
||||||
(str (text "general:action.save")))))
|
(str (text "general:action.save")))))
|
||||||
|
(div
|
||||||
|
("class" "card_nest w_full")
|
||||||
|
(div
|
||||||
|
("class" "card small flex items_center justify_between gap_2")
|
||||||
|
(div
|
||||||
|
("class" "flex items_center gap_2")
|
||||||
|
(icon (text "scale"))
|
||||||
|
(span
|
||||||
|
(str (text "mod_panel:label.ban_expiration")))))
|
||||||
|
(form
|
||||||
|
("class" "card flex flex_col gap_2")
|
||||||
|
("onsubmit" "event.preventDefault(); profile_request(false, 'ban_expire', { expire: new Date(event.target.expire.value).getTime() || 0 })")
|
||||||
|
(div
|
||||||
|
("class" "flex flex_col gap_1")
|
||||||
|
(label
|
||||||
|
("for" "expire")
|
||||||
|
(str (text "mod_panel:label.ban_expiration")))
|
||||||
|
(input
|
||||||
|
("type" "datetime-local")
|
||||||
|
("name" "expire")
|
||||||
|
("id" "expire")
|
||||||
|
("value" "{{ profile.ban_expire }}")))
|
||||||
|
(div
|
||||||
|
("class" "flex gap_2")
|
||||||
|
(button
|
||||||
|
(str (text "general:action.save")))
|
||||||
|
(button
|
||||||
|
("type" "button")
|
||||||
|
("class" "lowered red")
|
||||||
|
("onclick" "profile_request(false, 'ban_expire', { expire: 0 })")
|
||||||
|
(str (text "notifs:action.clear"))))))
|
||||||
(div
|
(div
|
||||||
("class" "card_nest w_full")
|
("class" "card_nest w_full")
|
||||||
(div
|
(div
|
||||||
|
|
|
@ -76,7 +76,17 @@
|
||||||
(span ("class" "fade") (text "The following reason was provided by a moderator:"))
|
(span ("class" "fade") (text "The following reason was provided by a moderator:"))
|
||||||
(div
|
(div
|
||||||
("class" "card lowered w_full")
|
("class" "card lowered w_full")
|
||||||
(text "{{ user.ban_reason|markdown|safe }}"))))))
|
(text "{{ user.ban_reason|markdown|safe }}"))
|
||||||
|
(text "{% if user.ban_expire != 0 -%}")
|
||||||
|
(hr)
|
||||||
|
(span
|
||||||
|
(text "Your ban will expire on: ")
|
||||||
|
(span ("id" "ban_expire")))
|
||||||
|
(script
|
||||||
|
(text "document.getElementById(\"ban_expire\").innerText = new Date({{ user.ban_expire }}).toLocaleString();"))
|
||||||
|
(text "{% else %}")
|
||||||
|
(span (text "This ban is marked as permanent."))
|
||||||
|
(text "{%- endif %}")))))
|
||||||
; if we aren't banned, just show the page body
|
; if we aren't banned, just show the page body
|
||||||
(text "{% elif user and user.awaiting_purchase %}")
|
(text "{% elif user and user.awaiting_purchase %}")
|
||||||
; account waiting for payment message
|
; account waiting for payment message
|
||||||
|
|
|
@ -179,7 +179,7 @@ pub async fn stripe_webhook(
|
||||||
let new_user_permissions = user.permissions | FinePermission::SUPPORTER;
|
let new_user_permissions = user.permissions | FinePermission::SUPPORTER;
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.update_user_role(user.id, new_user_permissions, user.clone(), true)
|
.update_user_role(user.id, new_user_permissions, &user, true)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
|
@ -225,7 +225,7 @@ pub async fn stripe_webhook(
|
||||||
user.secondary_permissions | SecondaryPermission::DEVELOPER_PASS;
|
user.secondary_permissions | SecondaryPermission::DEVELOPER_PASS;
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.update_user_secondary_role(user.id, new_user_permissions, user.clone(), true)
|
.update_user_secondary_role(user.id, new_user_permissions, &user, true)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
|
@ -284,7 +284,7 @@ pub async fn stripe_webhook(
|
||||||
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.update_user_role(user.id, new_user_permissions, user.clone(), true)
|
.update_user_role(user.id, new_user_permissions, &user, true)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
|
@ -310,7 +310,7 @@ pub async fn stripe_webhook(
|
||||||
user.secondary_permissions - SecondaryPermission::DEVELOPER_PASS;
|
user.secondary_permissions - SecondaryPermission::DEVELOPER_PASS;
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.update_user_secondary_role(user.id, new_user_permissions, user.clone(), true)
|
.update_user_secondary_role(user.id, new_user_permissions, &user, true)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
|
@ -396,7 +396,7 @@ pub async fn stripe_webhook(
|
||||||
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.update_user_role(user.id, new_user_permissions, user.clone(), true)
|
.update_user_role(user.id, new_user_permissions, &user, true)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
|
@ -437,7 +437,7 @@ pub async fn stripe_webhook(
|
||||||
user.secondary_permissions - SecondaryPermission::DEVELOPER_PASS;
|
user.secondary_permissions - SecondaryPermission::DEVELOPER_PASS;
|
||||||
|
|
||||||
if let Err(e) = data
|
if let Err(e) = data
|
||||||
.update_user_secondary_role(user.id, new_user_permissions, user.clone(), true)
|
.update_user_secondary_role(user.id, new_user_permissions, &user, true)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
return Json(e.into());
|
return Json(e.into());
|
||||||
|
|
|
@ -4,9 +4,9 @@ use crate::{
|
||||||
model::{ApiReturn, Error},
|
model::{ApiReturn, Error},
|
||||||
routes::api::v1::{
|
routes::api::v1::{
|
||||||
AppendAssociations, AwardAchievement, DeleteUser, DisableTotp, RefreshGrantToken,
|
AppendAssociations, AwardAchievement, DeleteUser, DisableTotp, RefreshGrantToken,
|
||||||
UpdateSecondaryUserRole, UpdateUserAwaitingPurchase, UpdateUserBanReason,
|
UpdateSecondaryUserRole, UpdateUserAwaitingPurchase, UpdateUserBanExpire,
|
||||||
UpdateUserInviteCode, UpdateUserIsDeactivated, UpdateUserIsVerified, UpdateUserPassword,
|
UpdateUserBanReason, UpdateUserInviteCode, UpdateUserIsDeactivated, UpdateUserIsVerified,
|
||||||
UpdateUserRole, UpdateUserUsername,
|
UpdateUserPassword, UpdateUserRole, UpdateUserUsername,
|
||||||
},
|
},
|
||||||
State,
|
State,
|
||||||
};
|
};
|
||||||
|
@ -423,7 +423,7 @@ pub async fn update_user_role_request(
|
||||||
None => return Json(Error::NotAllowed.into()),
|
None => return Json(Error::NotAllowed.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
match data.update_user_role(id, req.role, user, false).await {
|
match data.update_user_role(id, req.role, &user, false).await {
|
||||||
Ok(_) => Json(ApiReturn {
|
Ok(_) => Json(ApiReturn {
|
||||||
ok: true,
|
ok: true,
|
||||||
message: "User updated".to_string(),
|
message: "User updated".to_string(),
|
||||||
|
@ -449,7 +449,7 @@ pub async fn update_user_secondary_role_request(
|
||||||
};
|
};
|
||||||
|
|
||||||
match data
|
match data
|
||||||
.update_user_secondary_role(id, req.role, user, false)
|
.update_user_secondary_role(id, req.role, &user, false)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => Json(ApiReturn {
|
Ok(_) => Json(ApiReturn {
|
||||||
|
@ -490,6 +490,35 @@ pub async fn update_user_ban_reason_request(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Update the ban expiration date of the given user.
|
||||||
|
///
|
||||||
|
/// Does not support third-party grants.
|
||||||
|
pub async fn update_user_ban_expire_request(
|
||||||
|
jar: CookieJar,
|
||||||
|
Path(id): Path<usize>,
|
||||||
|
Extension(data): Extension<State>,
|
||||||
|
Json(req): Json<UpdateUserBanExpire>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
let data = &(data.read().await).0;
|
||||||
|
let user = match get_user_from_token!(jar, data) {
|
||||||
|
Some(ua) => ua,
|
||||||
|
None => return Json(Error::NotAllowed.into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if !user.permissions.check(FinePermission::MANAGE_USERS) {
|
||||||
|
return Json(Error::NotAllowed.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
match data.update_user_ban_expire(id, req.expire as i64).await {
|
||||||
|
Ok(_) => Json(ApiReturn {
|
||||||
|
ok: true,
|
||||||
|
message: "User updated".to_string(),
|
||||||
|
payload: (),
|
||||||
|
}),
|
||||||
|
Err(e) => Json(e.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Update the current user's last seen value.
|
/// Update the current user's last seen value.
|
||||||
pub async fn seen_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
pub async fn seen_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
||||||
let data = &(data.read().await).0;
|
let data = &(data.read().await).0;
|
||||||
|
|
|
@ -340,6 +340,10 @@ pub fn routes() -> Router {
|
||||||
"/auth/user/{id}/ban_reason",
|
"/auth/user/{id}/ban_reason",
|
||||||
post(auth::profile::update_user_ban_reason_request),
|
post(auth::profile::update_user_ban_reason_request),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/auth/user/{id}/ban_expire",
|
||||||
|
post(auth::profile::update_user_ban_expire_request),
|
||||||
|
)
|
||||||
.route(
|
.route(
|
||||||
"/auth/user/{id}",
|
"/auth/user/{id}",
|
||||||
delete(auth::profile::delete_user_request),
|
delete(auth::profile::delete_user_request),
|
||||||
|
@ -916,6 +920,11 @@ pub struct UpdateUserBanReason {
|
||||||
pub reason: String,
|
pub reason: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct UpdateUserBanExpire {
|
||||||
|
pub expire: usize,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
pub struct UpdateUserInviteCode {
|
pub struct UpdateUserInviteCode {
|
||||||
pub invite_code: String,
|
pub invite_code: String,
|
||||||
|
|
|
@ -27,7 +27,7 @@ macro_rules! update_role_fn {
|
||||||
&self,
|
&self,
|
||||||
id: usize,
|
id: usize,
|
||||||
role: $role_ty,
|
role: $role_ty,
|
||||||
user: User,
|
user: &User,
|
||||||
force: bool,
|
force: bool,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let other_user = self.get_user_by_id(id).await?;
|
let other_user = self.get_user_by_id(id).await?;
|
||||||
|
@ -129,6 +129,7 @@ impl DataManager {
|
||||||
ban_reason: get!(x->28(String)),
|
ban_reason: get!(x->28(String)),
|
||||||
channel_mutes: serde_json::from_str(&get!(x->29(String)).to_string()).unwrap(),
|
channel_mutes: serde_json::from_str(&get!(x->29(String)).to_string()).unwrap(),
|
||||||
is_deactivated: get!(x->30(i32)) as i8 == 1,
|
is_deactivated: get!(x->30(i32)) as i8 == 1,
|
||||||
|
ban_expire: get!(x->31(i64)) as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,7 +286,7 @@ impl DataManager {
|
||||||
|
|
||||||
let res = execute!(
|
let res = execute!(
|
||||||
&conn,
|
&conn,
|
||||||
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31)",
|
"INSERT INTO users VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30, $31, $32)",
|
||||||
params![
|
params![
|
||||||
&(data.id as i64),
|
&(data.id as i64),
|
||||||
&(data.created as i64),
|
&(data.created as i64),
|
||||||
|
@ -318,6 +319,7 @@ impl DataManager {
|
||||||
&data.ban_reason,
|
&data.ban_reason,
|
||||||
&serde_json::to_string(&data.channel_mutes).unwrap(),
|
&serde_json::to_string(&data.channel_mutes).unwrap(),
|
||||||
&if data.is_deactivated { 1_i32 } else { 0_i32 },
|
&if data.is_deactivated { 1_i32 } else { 0_i32 },
|
||||||
|
&(data.ban_expire as i64),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1059,6 +1061,7 @@ impl DataManager {
|
||||||
auto_method!(update_user_seller_data(StripeSellerData)@get_user_by_id -> "UPDATE users SET seller_data = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_seller_data(StripeSellerData)@get_user_by_id -> "UPDATE users SET seller_data = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
auto_method!(update_user_ban_reason(&str)@get_user_by_id -> "UPDATE users SET ban_reason = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_ban_reason(&str)@get_user_by_id -> "UPDATE users SET ban_reason = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||||
auto_method!(update_user_channel_mutes(Vec<usize>)@get_user_by_id -> "UPDATE users SET channel_mutes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_channel_mutes(Vec<usize>)@get_user_by_id -> "UPDATE users SET channel_mutes = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_user);
|
||||||
|
auto_method!(update_user_ban_expire(i64)@get_user_by_id -> "UPDATE users SET ban_expire = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||||
|
|
||||||
auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
|
auto_method!(get_user_by_stripe_id(&str)@get_user_from_row -> "SELECT * FROM users WHERE stripe_id = $1" --name="user" --returns=User);
|
||||||
auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
auto_method!(update_user_stripe_id(&str)@get_user_by_id -> "UPDATE users SET stripe_id = $1 WHERE id = $2" --cache-key-tmpl=cache_clear_user);
|
||||||
|
|
|
@ -29,5 +29,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||||
seller_data TEXT NOT NULL,
|
seller_data TEXT NOT NULL,
|
||||||
ban_reason TEXT NOT NULL,
|
ban_reason TEXT NOT NULL,
|
||||||
channel_mutes TEXT NOT NULL,
|
channel_mutes TEXT NOT NULL,
|
||||||
is_deactivated INT NOT NULL
|
is_deactivated INT NOT NULL,
|
||||||
|
ban_expire BIGINT NOT NULL
|
||||||
)
|
)
|
||||||
|
|
|
@ -25,3 +25,7 @@ ADD COLUMN IF NOT EXISTS topics TEXT DEFAULT '{}';
|
||||||
-- posts topic
|
-- posts topic
|
||||||
ALTER TABLE posts
|
ALTER TABLE posts
|
||||||
ADD COLUMN IF NOT EXISTS topic BIGINT DEFAULT 0;
|
ADD COLUMN IF NOT EXISTS topic BIGINT DEFAULT 0;
|
||||||
|
|
||||||
|
-- users ban_expire
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD COLUMN IF NOT EXISTS ban_expire BIGINT DEFAULT 0;
|
||||||
|
|
|
@ -92,6 +92,9 @@ pub struct User {
|
||||||
/// users, but their data is not wiped.
|
/// users, but their data is not wiped.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub is_deactivated: bool,
|
pub is_deactivated: bool,
|
||||||
|
/// The time at which the user's ban will automatically expire.
|
||||||
|
#[serde(default)]
|
||||||
|
pub ban_expire: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type UserConnections =
|
pub type UserConnections =
|
||||||
|
@ -408,6 +411,7 @@ impl User {
|
||||||
ban_reason: String::new(),
|
ban_reason: String::new(),
|
||||||
channel_mutes: Vec::new(),
|
channel_mutes: Vec::new(),
|
||||||
is_deactivated: false,
|
is_deactivated: false,
|
||||||
|
ban_expire: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue