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.send_debug_payload" = "Send debug payload"
|
||||
"mod_panel:label.ban_reason" = "Ban reason"
|
||||
"mod_panel:label.ban_expiration" = "Ban expiration"
|
||||
|
||||
"requests:label.requests" = "Requests"
|
||||
"requests:label.community_join_request" = "Community join request"
|
||||
|
|
|
@ -87,10 +87,31 @@ macro_rules! get_user_from_token {
|
|||
{
|
||||
Ok(ua) => {
|
||||
if ua.permissions.check_banned() {
|
||||
// check expiration
|
||||
let now = tetratto_shared::unix_epoch_timestamp();
|
||||
let expired = ua.ban_expire <= now;
|
||||
|
||||
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 {
|
||||
Some(ua)
|
||||
}
|
||||
|
|
|
@ -298,7 +298,7 @@
|
|||
(div
|
||||
("class" "flex flex_col gap_1")
|
||||
(label
|
||||
("for" "title")
|
||||
("for" "reason")
|
||||
(str (text "mod_panel:label.ban_reason")))
|
||||
(textarea
|
||||
("type" "text")
|
||||
|
@ -309,6 +309,37 @@
|
|||
(text "{{ profile.ban_reason|remove_script_tags|safe }}")))
|
||||
(button
|
||||
(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
|
||||
("class" "card_nest w_full")
|
||||
(div
|
||||
|
|
|
@ -76,7 +76,17 @@
|
|||
(span ("class" "fade") (text "The following reason was provided by a moderator:"))
|
||||
(div
|
||||
("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
|
||||
(text "{% elif user and user.awaiting_purchase %}")
|
||||
; account waiting for payment message
|
||||
|
|
|
@ -179,7 +179,7 @@ pub async fn stripe_webhook(
|
|||
let new_user_permissions = user.permissions | FinePermission::SUPPORTER;
|
||||
|
||||
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
|
||||
{
|
||||
return Json(e.into());
|
||||
|
@ -225,7 +225,7 @@ pub async fn stripe_webhook(
|
|||
user.secondary_permissions | SecondaryPermission::DEVELOPER_PASS;
|
||||
|
||||
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
|
||||
{
|
||||
return Json(e.into());
|
||||
|
@ -284,7 +284,7 @@ pub async fn stripe_webhook(
|
|||
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
||||
|
||||
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
|
||||
{
|
||||
return Json(e.into());
|
||||
|
@ -310,7 +310,7 @@ pub async fn stripe_webhook(
|
|||
user.secondary_permissions - SecondaryPermission::DEVELOPER_PASS;
|
||||
|
||||
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
|
||||
{
|
||||
return Json(e.into());
|
||||
|
@ -396,7 +396,7 @@ pub async fn stripe_webhook(
|
|||
let new_user_permissions = user.permissions - FinePermission::SUPPORTER;
|
||||
|
||||
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
|
||||
{
|
||||
return Json(e.into());
|
||||
|
@ -437,7 +437,7 @@ pub async fn stripe_webhook(
|
|||
user.secondary_permissions - SecondaryPermission::DEVELOPER_PASS;
|
||||
|
||||
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
|
||||
{
|
||||
return Json(e.into());
|
||||
|
|
|
@ -4,9 +4,9 @@ use crate::{
|
|||
model::{ApiReturn, Error},
|
||||
routes::api::v1::{
|
||||
AppendAssociations, AwardAchievement, DeleteUser, DisableTotp, RefreshGrantToken,
|
||||
UpdateSecondaryUserRole, UpdateUserAwaitingPurchase, UpdateUserBanReason,
|
||||
UpdateUserInviteCode, UpdateUserIsDeactivated, UpdateUserIsVerified, UpdateUserPassword,
|
||||
UpdateUserRole, UpdateUserUsername,
|
||||
UpdateSecondaryUserRole, UpdateUserAwaitingPurchase, UpdateUserBanExpire,
|
||||
UpdateUserBanReason, UpdateUserInviteCode, UpdateUserIsDeactivated, UpdateUserIsVerified,
|
||||
UpdateUserPassword, UpdateUserRole, UpdateUserUsername,
|
||||
},
|
||||
State,
|
||||
};
|
||||
|
@ -423,7 +423,7 @@ pub async fn update_user_role_request(
|
|||
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: true,
|
||||
message: "User updated".to_string(),
|
||||
|
@ -449,7 +449,7 @@ pub async fn update_user_secondary_role_request(
|
|||
};
|
||||
|
||||
match data
|
||||
.update_user_secondary_role(id, req.role, user, false)
|
||||
.update_user_secondary_role(id, req.role, &user, false)
|
||||
.await
|
||||
{
|
||||
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.
|
||||
pub async fn seen_request(jar: CookieJar, Extension(data): Extension<State>) -> impl IntoResponse {
|
||||
let data = &(data.read().await).0;
|
||||
|
|
|
@ -340,6 +340,10 @@ pub fn routes() -> Router {
|
|||
"/auth/user/{id}/ban_reason",
|
||||
post(auth::profile::update_user_ban_reason_request),
|
||||
)
|
||||
.route(
|
||||
"/auth/user/{id}/ban_expire",
|
||||
post(auth::profile::update_user_ban_expire_request),
|
||||
)
|
||||
.route(
|
||||
"/auth/user/{id}",
|
||||
delete(auth::profile::delete_user_request),
|
||||
|
@ -916,6 +920,11 @@ pub struct UpdateUserBanReason {
|
|||
pub reason: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateUserBanExpire {
|
||||
pub expire: usize,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct UpdateUserInviteCode {
|
||||
pub invite_code: String,
|
||||
|
|
|
@ -27,7 +27,7 @@ macro_rules! update_role_fn {
|
|||
&self,
|
||||
id: usize,
|
||||
role: $role_ty,
|
||||
user: User,
|
||||
user: &User,
|
||||
force: bool,
|
||||
) -> Result<()> {
|
||||
let other_user = self.get_user_by_id(id).await?;
|
||||
|
@ -129,6 +129,7 @@ impl DataManager {
|
|||
ban_reason: get!(x->28(String)),
|
||||
channel_mutes: serde_json::from_str(&get!(x->29(String)).to_string()).unwrap(),
|
||||
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!(
|
||||
&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![
|
||||
&(data.id as i64),
|
||||
&(data.created as i64),
|
||||
|
@ -318,6 +319,7 @@ impl DataManager {
|
|||
&data.ban_reason,
|
||||
&serde_json::to_string(&data.channel_mutes).unwrap(),
|
||||
&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_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_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!(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,
|
||||
ban_reason 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
|
||||
ALTER TABLE posts
|
||||
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.
|
||||
#[serde(default)]
|
||||
pub is_deactivated: bool,
|
||||
/// The time at which the user's ban will automatically expire.
|
||||
#[serde(default)]
|
||||
pub ban_expire: usize,
|
||||
}
|
||||
|
||||
pub type UserConnections =
|
||||
|
@ -408,6 +411,7 @@ impl User {
|
|||
ban_reason: String::new(),
|
||||
channel_mutes: Vec::new(),
|
||||
is_deactivated: false,
|
||||
ban_expire: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue