Compare commits

..
13 Commits
33 changed files with 665 additions and 350 deletions
-79
View File
@@ -1,79 +0,0 @@
# MongoDB URI
MONGODB=mongodb://localhost
# URL to where the Revolt app is publicly accessible
REVOLT_APP_URL=http://local.revolt.chat:5000
# URL to where the API is publicly accessible
REVOLT_PUBLIC_URL=http://local.revolt.chat:8000
VITE_API_URL=http://local.revolt.chat:8000
# URL to where the WebSocket server is publicly accessible
REVOLT_EXTERNAL_WS_URL=ws://local.revolt.chat:9000
# URL to where Autumn is publicly available
AUTUMN_PUBLIC_URL=http://local.revolt.chat:3000
# URL to where January is publicly available
JANUARY_PUBLIC_URL=http://local.revolt.chat:7000
# URL to where Vortex is publicly available
# VOSO_PUBLIC_URL=https://voso.revolt.chat
##
## hCaptcha Settings
##
# If you are sure that you don't want to use hCaptcha, set to 1.
REVOLT_UNSAFE_NO_CAPTCHA=1
# hCaptcha API key
# REVOLT_HCAPTCHA_KEY=0x0000000000000000000000000000000000000000
# hCaptcha site key
# REVOLT_HCAPTCHA_SITEKEY=10000000-ffff-ffff-ffff-000000000001
##
## Email Settings
##
# If you are sure that you don't want to use email verification, set to 1.
REVOLT_UNSAFE_NO_EMAIL=1
# SMTP host
# REVOLT_SMTP_HOST=smtp.example.com
# SMTP username
# REVOLT_SMTP_USERNAME=noreply@example.com
# SMTP password
# REVOLT_SMTP_PASSWORD=CHANGEME
# SMTP From header
# REVOLT_SMTP_FROM=Revolt <noreply@example.com>
##
## Application Settings
##
# Whether to only allow users to sign up if they have an invite code
REVOLT_INVITE_ONLY=0
# Maximum number of people that can be in a group chat
REVOLT_MAX_GROUP_SIZE=150
# VAPID keys for push notifications
# Generate using this guide: https://gitlab.insrt.uk/revolt/delta/-/wikis/vapid
# --> Please replace these keys before going into production! <--
REVOLT_VAPID_PRIVATE_KEY=LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJSUWpyTWxLRnBiVWhsUHpUbERvcEliYk1yeVNrNXpKYzVYVzIxSjJDS3hvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFWnkrQkg2TGJQZ2hEa3pEempXOG0rUXVPM3pCajRXT1phdkR6ZU00c0pqbmFwd1psTFE0WAp1ZDh2TzVodU94QWhMQlU3WWRldVovWHlBdFpWZmNyQi9BPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
REVOLT_VAPID_PUBLIC_KEY=BGcvgR-i2z4IQ5Mw841vJvkLjt8wY-FjmWrw83jOLCY52qcGZS0OF7nfLzuYbjsQISwVO2HXrmf18gLWVX3Kwfw=
##
## Vortex configuration
##
# VOSO_MANAGE_TOKEN=CHANGEME
+3
View File
@@ -1,3 +1,6 @@
Rocket.toml Rocket.toml
target target
.data .data
.env
.vercel
Generated
+7 -5
View File
@@ -1454,12 +1454,12 @@ checksum = "90f97a5f38dd3ccfbe7aa80f4a0c00930f21b922c74195be0201c51028f22dcf"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.8.2" version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [ dependencies = [
"autocfg 1.1.0", "autocfg 1.1.0",
"hashbrown 0.11.2", "hashbrown 0.12.1",
"serde", "serde",
] ]
@@ -2505,7 +2505,7 @@ dependencies = [
[[package]] [[package]]
name = "rauth" name = "rauth"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4" source = "git+https://github.com/insertish/rauth?rev=43a4081dd5943db39d27229d4792b0afe82c1674#43a4081dd5943db39d27229d4792b0afe82c1674"
dependencies = [ dependencies = [
"async-std", "async-std",
"async-trait", "async-trait",
@@ -2783,6 +2783,7 @@ dependencies = [
"dotenv", "dotenv",
"futures", "futures",
"impl_ops", "impl_ops",
"indexmap",
"iso8601-timestamp", "iso8601-timestamp",
"lazy_static", "lazy_static",
"linkify 0.8.1", "linkify 0.8.1",
@@ -2987,8 +2988,9 @@ dependencies = [
[[package]] [[package]]
name = "rocket_rauth" name = "rocket_rauth"
version = "1.0.0" version = "1.0.0"
source = "git+https://github.com/insertish/rauth?rev=9c3f9bb58a549a51546bd748274130ac4a667cb4#9c3f9bb58a549a51546bd748274130ac4a667cb4" source = "git+https://github.com/insertish/rauth?rev=43a4081dd5943db39d27229d4792b0afe82c1674#43a4081dd5943db39d27229d4792b0afe82c1674"
dependencies = [ dependencies = [
"iso8601-timestamp",
"okapi", "okapi",
"rauth", "rauth",
"rocket", "rocket",
+1 -1
View File
@@ -52,7 +52,7 @@ mobc-redis = { version = "0.7.0", default-features = false, features = ["async-s
# web # web
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] } rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" } rocket_empty = { git = "https://github.com/insertish/rocket_empty", branch = "master" }
rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4" } rocket_rauth = { git = "https://github.com/insertish/rauth", rev = "43a4081dd5943db39d27229d4792b0afe82c1674" }
# spec generation # spec generation
schemars = "0.8.8" schemars = "0.8.8"
@@ -2,13 +2,27 @@ use revolt_quark::{
models::{channel::PartialChannel, Channel, User}, models::{channel::PartialChannel, Channel, User},
perms, Db, EmptyResponse, Error, Permission, Ref, Result, perms, Db, EmptyResponse, Error, Permission, Ref, Result,
}; };
use serde::{Deserialize, Serialize};
use validator::Validate;
/// # Query Parameters
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsChannelDelete {
/// Whether to not send a leave message
leave_silently: Option<bool>,
}
/// # Close Channel /// # Close Channel
/// ///
/// Deletes a server channel, leaves a group or closes a group. /// Deletes a server channel, leaves a group or closes a group.
#[openapi(tag = "Channel Information")] #[openapi(tag = "Channel Information")]
#[delete("/<target>")] #[delete("/<target>?<options..>")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> { pub async fn req(
db: &Db,
user: User,
target: Ref,
options: OptionsChannelDelete,
) -> Result<EmptyResponse> {
let mut channel = target.as_channel(db).await?; let mut channel = target.as_channel(db).await?;
let mut perms = perms(&user).channel(&channel); let mut perms = perms(&user).channel(&channel);
perms.throw_permission(db, Permission::ViewChannel).await?; perms.throw_permission(db, Permission::ViewChannel).await?;
@@ -27,7 +41,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
.await .await
.map(|_| EmptyResponse), .map(|_| EmptyResponse),
Channel::Group { .. } => channel Channel::Group { .. } => channel
.remove_user_from_group(db, &user.id, None) .remove_user_from_group(
db,
&user.id,
None,
options.leave_silently.unwrap_or_default(),
)
.await .await
.map(|_| EmptyResponse), .map(|_| EmptyResponse),
Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => { Channel::TextChannel { .. } | Channel::VoiceChannel { .. } => {
@@ -29,6 +29,8 @@ pub struct DataEditChannel {
icon: Option<String>, icon: Option<String>,
/// Whether this channel is age-restricted /// Whether this channel is age-restricted
nsfw: Option<bool>, nsfw: Option<bool>,
/// Whether this channel is archived
archived: Option<bool>,
#[validate(length(min = 1))] #[validate(length(min = 1))]
remove: Option<Vec<FieldsChannel>>, remove: Option<Vec<FieldsChannel>>,
} }
@@ -83,6 +85,7 @@ pub async fn req(
} }
// Transfer ownership // Transfer ownership
partial.owner = Some(new_owner.to_string());
let old_owner = std::mem::replace(owner, new_owner.to_string()); let old_owner = std::mem::replace(owner, new_owner.to_string());
// Notify clients // Notify clients
@@ -33,7 +33,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
} }
channel channel
.remove_user_from_group(db, &member.id, Some(&user.id)) .remove_user_from_group(db, &member.id, Some(&user.id), false)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }
@@ -116,7 +116,7 @@ pub async fn message_send(
} }
// 3. Ensure interactions information is correct // 3. Ensure interactions information is correct
message.interactions.validate(db).await?; message.interactions.validate(db, &mut permissions).await?;
// 4. Verify replies are valid. // 4. Verify replies are valid.
let mut replies = HashSet::new(); let mut replies = HashSet::new();
@@ -55,7 +55,7 @@ pub async fn create_emoji(
// Check that there are no more than 100 emoji // Check that there are no more than 100 emoji
// ! FIXME: hardcoded upper limit // ! FIXME: hardcoded upper limit
let emojis = db.fetch_emoji_by_parent_id(&server.id).await?; let emojis = db.fetch_emoji_by_parent_id(&server.id).await?;
if emojis.len() > 100 { if emojis.len() > 99 {
return Err(Error::TooManyEmoji); return Err(Error::TooManyEmoji);
} }
} }
@@ -34,7 +34,7 @@ pub async fn req(db: &Db, user: User, target: Ref, member: Ref) -> Result<EmptyR
} }
server server
.remove_member(db, member, RemovalIntention::Kick) .remove_member(db, member, RemovalIntention::Kick, false)
.await .await
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
} }
@@ -2,13 +2,27 @@ use revolt_quark::{
models::{server_member::RemovalIntention, User}, models::{server_member::RemovalIntention, User},
Db, EmptyResponse, Ref, Result, Db, EmptyResponse, Ref, Result,
}; };
use serde::{Deserialize, Serialize};
use validator::Validate;
/// # Query Parameters
#[derive(Validate, Serialize, Deserialize, JsonSchema, FromForm)]
pub struct OptionsServerDelete {
/// Whether to not send a leave message
leave_silently: Option<bool>,
}
/// # Delete / Leave Server /// # Delete / Leave Server
/// ///
/// Deletes a server if owner otherwise leaves. /// Deletes a server if owner otherwise leaves.
#[openapi(tag = "Server Information")] #[openapi(tag = "Server Information")]
#[delete("/<target>")] #[delete("/<target>?<options..>")]
pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> { pub async fn req(
db: &Db,
user: User,
target: Ref,
options: OptionsServerDelete,
) -> Result<EmptyResponse> {
let server = target.as_server(db).await?; let server = target.as_server(db).await?;
let member = db.fetch_member(&target.id, &user.id).await?; let member = db.fetch_member(&target.id, &user.id).await?;
@@ -16,7 +30,12 @@ pub async fn req(db: &Db, user: User, target: Ref) -> Result<EmptyResponse> {
server.delete(db).await server.delete(db).await
} else { } else {
server server
.remove_member(db, member, RemovalIntention::Leave) .remove_member(
db,
member,
RemovalIntention::Leave,
options.leave_silently.unwrap_or_default(),
)
.await .await
} }
.map(|_| EmptyResponse) .map(|_| EmptyResponse)
+30 -13
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use revolt_quark::{ use revolt_quark::{
models::{ models::{
server::{Category, FieldsServer, PartialServer, SystemMessageChannels}, server::{Category, FieldsServer, PartialServer, SystemMessageChannels},
@@ -135,29 +137,44 @@ pub async fn req(
} }
} }
// 2. Apply new icon // 2. Validate changes
if let Some(system_messages) = &partial.system_messages {
for id in system_messages.clone().into_channel_ids() {
if !server.channels.contains(&id) {
return Err(Error::NotFound);
}
}
}
if let Some(categories) = &mut partial.categories {
let mut channel_ids = HashSet::new();
for category in categories {
for channel in &category.channels {
if channel_ids.contains(channel) {
return Err(Error::InvalidOperation);
}
channel_ids.insert(channel.to_string());
}
category
.channels
.retain(|item| server.channels.contains(item));
}
}
// 3. Apply new icon
if let Some(icon) = icon { if let Some(icon) = icon {
partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?); partial.icon = Some(File::use_server_icon(db, &icon, &server.id).await?);
server.icon = partial.icon.clone(); server.icon = partial.icon.clone();
} }
// 3. Apply new banner // 4. Apply new banner
if let Some(banner) = banner { if let Some(banner) = banner {
partial.banner = Some(File::use_banner(db, &banner, &server.id).await?); partial.banner = Some(File::use_banner(db, &banner, &server.id).await?);
server.banner = partial.banner.clone(); server.banner = partial.banner.clone();
} }
// 4. Validate changes
if let Some(system_messages) = &partial.system_messages {
let channels = system_messages.clone().into_channel_ids();
if !db
.check_channels_exist(&channels.into_iter().collect())
.await?
{
return Err(Error::NotFound);
}
}
server server
.update(db, partial, remove.unwrap_or_default()) .update(db, partial, remove.unwrap_or_default())
.await?; .await?;
+2 -1
View File
@@ -64,6 +64,7 @@ regex = "1.5.5"
nanoid = "0.4.0" nanoid = "0.4.0"
linkify = "0.8.1" linkify = "0.8.1"
dotenv = "0.15.0" dotenv = "0.15.0"
indexmap = "1.9.1"
impl_ops = "0.1.1" impl_ops = "0.1.1"
num_enum = "0.5.6" num_enum = "0.5.6"
reqwest = "0.11.10" reqwest = "0.11.10"
@@ -85,7 +86,7 @@ rocket_empty = { optional = true, git = "https://github.com/insertish/rocket_emp
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" } rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "5843861a88958c16bfaa0b40f0d8910772bcd2f6" }
# rAuth # rAuth
rauth = { git = "https://github.com/insertish/rauth", rev = "9c3f9bb58a549a51546bd748274130ac4a667cb4", features = [ "async-std-runtime" ] } rauth = { git = "https://github.com/insertish/rauth", rev = "43a4081dd5943db39d27229d4792b0afe82c1674", features = [ "async-std-runtime" ] }
# Sentry # Sentry
sentry = "0.25.0" sentry = "0.25.0"
+137
View File
@@ -0,0 +1,137 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<style type="text/css">
body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.w-24,.w-24>tbody>tr>td{width:96px !important}.p-lg-10:not(table),.p-lg-10:not(.btn)>tbody>tr>td,.p-lg-10.btn td a{padding:0 !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}.p-6:not(table),.p-6:not(.btn)>tbody>tr>td,.p-6.btn td a{padding:24px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-6>tbody>tr>td{font-size:24px !important;line-height:24px !important;height:24px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head>
<body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tbody>
<tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tbody>
<tr>
<td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
<!--[if (gte mso 9)|(IE)]>
<table align="center" role="presentation">
<tbody>
<tr>
<td width="600">
<![endif]-->
<table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
</td>
</tr>
</tbody>
</table>
<table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="card p-6 p-lg-10 space-y-4" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
<h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Account Deletion</h1>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately.</p>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary p-3 fw-700" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; font-weight: 700 !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Confirm</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="text-muted text-center" style="color: #718096;" align="center">
Sent from Revolt<br>
Made in the EU
</div>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,29 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
alt="Revolt Logo"
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Account Deletion</h1>
<p>
You requested to have your account deleted, if you did not perform
this action please take measures to secure your account immediately.
</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
</div>
<div class="text-muted text-center my-6">
Sent from Revolt<br />
Made in the EU
</div>
</div>
</body>
</html>
+3 -2
View File
@@ -1,5 +1,6 @@
You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately. You requested to have your account deleted, if you did not perform this action please take measures to secure your account immediately.
Confirm here: {{url}} Please navigate to: {{url}}
Sent by Revolt. Sent by Revolt.
Made in the EU.
+137 -63
View File
@@ -1,63 +1,137 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-GB"> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Reset your password.</title> <meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<style type="text/css"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
a[x-apple-data-detectors] {color: inherit !important;} <style type="text/css">
</style> body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.w-24,.w-24>tbody>tr>td{width:96px !important}.p-lg-10:not(table),.p-lg-10:not(.btn)>tbody>tr>td,.p-lg-10.btn td a{padding:0 !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}.p-6:not(table),.p-6:not(.btn)>tbody>tr>td,.p-6.btn td a{padding:24px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-6>tbody>tr>td{font-size:24px !important;line-height:24px !important;height:24px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head> </head>
<body style="margin: 0; padding: 0;"> <body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table border="0" cellpadding="0" cellspacing="0" width="100%"> <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tr> <tbody>
<td style="padding: 20px 0 30px 0;"> <tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;"> <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tr> <tbody>
<td align="center" bgcolor="#ff4654"> <tr>
<img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" /> <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
</td> <!--[if (gte mso 9)|(IE)]>
</tr> <table align="center" role="presentation">
<tr> <tbody>
<td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;"> <tr>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <td width="600">
<tr> <![endif]-->
<td style="color: #153643; font-family: Arial, sans-serif;"> <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<h1 style="font-size: 24px; margin: 0;">Reset your password!</h1> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;"> <tbody>
<p> <tr>
You requested a password reset, if you didn't perform this action you can safely ignore this email. <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</p> &#160;
<p style="margin: 0;"> </td>
Reset your password by navigating to <a href="{{url}}">{{url}}</a>. </tr>
</p> </tbody>
</td> </table>
</tr> <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
</table> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
<td bgcolor="#ff4654" style="padding: 30px 30px;"> </td>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> </tr>
<tr> </tbody>
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;"> </table>
<p style="margin: 0;">Sent by Revolt. &middot; Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<p>Revolt is a user-first chat platform built with modern web technologies.</p> <tbody>
</td> <tr>
</tr> <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</table> &#160;
</td> </td>
</tr> </tr>
</table> </tbody>
</table>
</td> <table class="card p-6 p-lg-10 space-y-4" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
</tr> <tbody>
</table> <tr>
</body> <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
</html> <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Password Reset</h1>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">You requested a password reset, click below to continue.</p>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary p-3 fw-700" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; font-weight: 700 !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Reset</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="text-muted text-center" style="color: #718096;" align="center">
Sent from Revolt<br>
Made in the EU
</div>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,25 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Password Reset</h1>
<p>You requested a password reset, click below to continue.</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Reset</a>
</div>
<div class="text-muted text-center my-6">
Sent from Revolt<br />
Made in the EU
</div>
</div>
</body>
</html>
+3 -2
View File
@@ -1,5 +1,6 @@
You requested a password reset, if you did not perform this action you can safely ignore this email. You requested a password reset, if you did not perform this action you can safely ignore this email.
Reset your password here: {{url}} Please navigate to: {{url}}
Sent by Revolt. Sent by Revolt.
Made in the EU.
@@ -1,14 +0,0 @@
<h2>Reset your password.</h2>
<p>
You requested a password reset, if you did not perform this action you can safely ignore this email.
</p>
<p>
Reset your password here: <a href="{{url}}">{{url}}</a>
</p>
<br/>
<p>
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
</p>
<p>
Revolt is a user-first chat platform built with modern web technologies.
</p>
+137 -63
View File
@@ -1,63 +1,137 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-GB"> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <!-- Compiled with Bootstrap Email version: 1.3.1 --><meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Verify your account.</title> <meta name="x-apple-disable-message-reformatting">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
<style type="text/css"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
a[x-apple-data-detectors] {color: inherit !important;} <style type="text/css">
</style> body,table,td{font-family:Helvetica,Arial,sans-serif !important}.ExternalClass{width:100%}.ExternalClass,.ExternalClass p,.ExternalClass span,.ExternalClass font,.ExternalClass td,.ExternalClass div{line-height:150%}a{text-decoration:none}*{color:inherit}a[x-apple-data-detectors],u+#body a,#MessageViewBody a{color:inherit;text-decoration:none;font-size:inherit;font-family:inherit;font-weight:inherit;line-height:inherit}img{-ms-interpolation-mode:bicubic}table:not([class^=s-]){font-family:Helvetica,Arial,sans-serif;mso-table-lspace:0pt;mso-table-rspace:0pt;border-spacing:0px;border-collapse:collapse}table:not([class^=s-]) td{border-spacing:0px;border-collapse:collapse}@media screen and (max-width: 600px){.w-full,.w-full>tbody>tr>td{width:100% !important}.w-24,.w-24>tbody>tr>td{width:96px !important}.p-lg-10:not(table),.p-lg-10:not(.btn)>tbody>tr>td,.p-lg-10.btn td a{padding:0 !important}.p-3:not(table),.p-3:not(.btn)>tbody>tr>td,.p-3.btn td a{padding:12px !important}.p-6:not(table),.p-6:not(.btn)>tbody>tr>td,.p-6.btn td a{padding:24px !important}*[class*=s-lg-]>tbody>tr>td{font-size:0 !important;line-height:0 !important;height:0 !important}.s-4>tbody>tr>td{font-size:16px !important;line-height:16px !important;height:16px !important}.s-6>tbody>tr>td{font-size:24px !important;line-height:24px !important;height:24px !important}.s-10>tbody>tr>td{font-size:40px !important;line-height:40px !important;height:40px !important}}
</style>
</head> </head>
<body style="margin: 0; padding: 0;"> <body class="bg-light" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<table border="0" cellpadding="0" cellspacing="0" width="100%"> <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0" style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;" bgcolor="#f7fafc">
<tr> <tbody>
<td style="padding: 20px 0 30px 0;"> <tr>
<td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
<table align="center" border="0" cellpadding="0" cellspacing="0" width="600" style="border-collapse: collapse; border: 1px solid #cccccc;"> <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
<tr> <tbody>
<td align="center" bgcolor="#ff4654"> <tr>
<img src="https://revolt.chat/header.png" alt="Revolt logo" width="600" height="168" style="display: block;" /> <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
</td> <!--[if (gte mso 9)|(IE)]>
</tr> <table align="center" role="presentation">
<tr> <tbody>
<td bgcolor="#ffffff" style="padding: 40px 30px 40px 30px;"> <tr>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> <td width="600">
<tr> <![endif]-->
<td style="color: #153643; font-family: Arial, sans-serif;"> <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%; max-width: 600px; margin: 0 auto;">
<h1 style="font-size: 24px; margin: 0;">You're almost there!</h1> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 24px; padding: 20px 0 0 0;"> <tbody>
<p> <tr>
Verify your account to be able to log into the platform. If you didn't perform this action you can safely ignore this email. <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</p> &#160;
<p style="margin: 0;"> </td>
Please verify your account by navigating to <a href="{{url}}">{{url}}</a>. </tr>
</p> </tbody>
</td> </table>
</tr> <table class="ax-center" role="presentation" align="center" border="0" cellpadding="0" cellspacing="0" style="margin: 0 auto;">
</table> <tbody>
</td> <tr>
</tr> <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
<tr> <img alt="Revolt Logo" class="w-24" src="https://app.revolt.chat/assets/logo_round.png" style="height: auto; line-height: 100%; outline: none; text-decoration: none; display: block; width: 96px; border-style: none; border-width: 0;" width="96">
<td bgcolor="#ff4654" style="padding: 30px 30px;"> </td>
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="border-collapse: collapse;"> </tr>
<tr> </tbody>
<td style="color: #ffffff; font-family: Arial, sans-serif; font-size: 14px;"> </table>
<p style="margin: 0;">Sent by Revolt. &middot; Website: <a style="color: white;" href="https://revolt.chat">https://revolt.chat</a></p> <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<p>Revolt is a user-first chat platform built with modern web technologies.</p> <tbody>
</td> <tr>
</tr> <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;" align="left" width="100%" height="40">
</table> &#160;
</td> </td>
</tr> </tr>
</table> </tbody>
</table>
</td> <table class="card p-6 p-lg-10 space-y-4" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;" bgcolor="#ffffff">
</tr> <tbody>
</table> <tr>
</body> <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 40px;" align="left" bgcolor="#ffffff">
</html> <h1 class="h3 fw-700" style="padding-top: 0; padding-bottom: 0; font-weight: 700 !important; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;" align="left">Almost there!</h1>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<p class="" style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left">To complete your sign up, we just need to verify your email.</p>
<table class="s-4 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 16px; font-size: 16px; width: 100%; height: 16px; margin: 0;" align="left" width="100%" height="16">
&#160;
</td>
</tr>
</tbody>
</table>
<table class="btn btn-primary p-3 fw-700" role="presentation" border="0" cellpadding="0" cellspacing="0" style="border-radius: 6px; border-collapse: separate !important; font-weight: 700 !important;">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 16px; border-radius: 6px; font-weight: 700 !important; margin: 0;" align="center" bgcolor="#0d6efd">
<a href="{{url}}" style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: 700 !important; white-space: nowrap; background-color: #0d6efd; padding: 12px; border: 1px solid #0d6efd;">Confirm</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
<div class="text-muted text-center" style="color: #718096;" align="center">
Sent from Revolt<br>
Made in the EU
</div>
<table class="s-6 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;" width="100%">
<tbody>
<tr>
<td style="line-height: 24px; font-size: 24px; width: 100%; height: 24px; margin: 0;" align="left" width="100%" height="24">
&#160;
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</tbody>
</table>
<![endif]-->
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,26 @@
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
/* Add custom classes and styles that you want inlined here */
</style>
</head>
<body class="bg-light">
<div class="container">
<img
alt="Revolt Logo"
class="ax-center my-10 w-24"
src="https://app.revolt.chat/assets/logo_round.png"
/>
<div class="card p-6 p-lg-10 space-y-4">
<h1 class="h3 fw-700">Almost there!</h1>
<p>To complete your sign up, we just need to verify your email.</p>
<a class="btn btn-primary p-3 fw-700" href="{{url}}">Confirm</a>
</div>
<div class="text-muted text-center my-6">
Sent from Revolt<br />
Made in the EU
</div>
</div>
</body>
</html>
+5 -4
View File
@@ -1,6 +1,7 @@
You're almost there! Almost there!
If you did not perform this action you can safely ignore this email. To complete your sign up, we just need to verify your email.
Please verify your account here: {{url}} Please navigate to: {{url}}
Sent by Revolt. Sent by Revolt.
Made in the EU.
@@ -1,15 +0,0 @@
<h2>You're almost there!</h2>
<p>
Verify your account to be able to log into the platform.<br/>
If you did not perform this action you can safely ignore this email.
</p>
<p>
Please verify your account here: <a href="{{url}}">{{url}}</a>
</p>
<br/>
<p>
Sent by Revolt. · Website: <a href="https://revolt.chat">https://revolt.chat</a>
</p>
<p>
Revolt is a user-first chat platform built with modern web technologies.
</p>
@@ -1,5 +1,3 @@
use std::collections::HashSet;
use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result}; use crate::{AbstractAttachment, AbstractChannel, Error, OverrideField, Result};
@@ -83,8 +81,4 @@ impl AbstractChannel for DummyDb {
info!("Updating permissions for role {role} in {channel} with {permissions:?}"); info!("Updating permissions for role {role} in {channel} with {permissions:?}");
Ok(()) Ok(())
} }
async fn check_channels_exist(&self, _channels: &HashSet<String>) -> Result<bool> {
Ok(true)
}
} }
@@ -286,6 +286,7 @@ impl Channel {
db: &Database, db: &Database,
user: &str, user: &str,
by: Option<&str>, by: Option<&str>,
silent: bool,
) -> Result<()> { ) -> Result<()> {
match &self { match &self {
Channel::Group { Channel::Group {
@@ -329,20 +330,22 @@ impl Channel {
.p(id.to_string()) .p(id.to_string())
.await; .await;
if let Some(by) = by { if !silent {
SystemMessage::UserRemove { if let Some(by) = by {
id: user.to_string(), SystemMessage::UserRemove {
by: by.to_string(), id: user.to_string(),
} by: by.to_string(),
} else { }
SystemMessage::UserLeft { } else {
id: user.to_string(), SystemMessage::UserLeft {
id: user.to_string(),
}
} }
.into_message(id.to_string())
.create(db, self, None)
.await
.ok();
} }
.into_message(id.to_string())
.create(db, self, None)
.await
.ok();
Ok(()) Ok(())
} }
@@ -13,13 +13,14 @@ use crate::{
}, },
Channel, Emoji, Message, User, Channel, Emoji, Message, User,
}, },
permissions::PermissionCalculator,
presence::presence_filter_online, presence::presence_filter_online,
tasks::ack::AckEvent, tasks::ack::AckEvent,
types::{ types::{
january::{Embed, Text}, january::{Embed, Text},
push::PushNotification, push::PushNotification,
}, },
Database, Error, Result, Database, Error, Permission, Result,
}; };
impl Message { impl Message {
@@ -195,6 +196,11 @@ impl Message {
/// Add a reaction to a message /// Add a reaction to a message
pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> { pub async fn add_reaction(&self, db: &Database, user: &User, emoji: &str) -> Result<()> {
// Check how many reactions are already on the message
if self.reactions.len() >= 20 {
return Err(Error::InvalidOperation);
}
// Check if the emoji is whitelisted // Check if the emoji is whitelisted
if !self.interactions.can_use(emoji) { if !self.interactions.can_use(emoji) {
return Err(Error::InvalidOperation); return Err(Error::InvalidOperation);
@@ -401,8 +407,14 @@ impl BulkMessageResponse {
impl Interactions { impl Interactions {
/// Validate interactions info is correct /// Validate interactions info is correct
pub async fn validate(&self, db: &Database) -> Result<()> { pub async fn validate(
&self,
db: &Database,
permissions: &mut PermissionCalculator<'_>,
) -> Result<()> {
if let Some(reactions) = &self.reactions { if let Some(reactions) = &self.reactions {
permissions.throw_permission(db, Permission::React).await?;
if reactions.len() > 20 { if reactions.len() > 20 {
return Err(Error::InvalidOperation); return Err(Error::InvalidOperation);
} }
+25 -20
View File
@@ -1,3 +1,5 @@
use std::collections::HashSet;
use iso8601_timestamp::Timestamp; use iso8601_timestamp::Timestamp;
use ulid::Ulid; use ulid::Ulid;
@@ -254,6 +256,7 @@ impl Server {
db: &Database, db: &Database,
member: Member, member: Member,
intention: RemovalIntention, intention: RemovalIntention,
silent: bool,
) -> Result<()> { ) -> Result<()> {
db.delete_member(&member.id).await?; db.delete_member(&member.id).await?;
@@ -264,20 +267,22 @@ impl Server {
.p(member.id.server) .p(member.id.server)
.await; .await;
if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention { if !silent {
RemovalIntention::Leave => x.user_left.as_ref(), if let Some(id) = self.system_messages.as_ref().and_then(|x| match intention {
RemovalIntention::Kick => x.user_kicked.as_ref(), RemovalIntention::Leave => x.user_left.as_ref(),
RemovalIntention::Ban => x.user_banned.as_ref(), RemovalIntention::Kick => x.user_kicked.as_ref(),
}) { RemovalIntention::Ban => x.user_banned.as_ref(),
match intention { }) {
RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user }, match intention {
RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user }, RemovalIntention::Leave => SystemMessage::UserLeft { id: member.id.user },
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user }, RemovalIntention::Kick => SystemMessage::UserKicked { id: member.id.user },
RemovalIntention::Ban => SystemMessage::UserBanned { id: member.id.user },
}
.into_message(id.to_string())
.create_no_web_push(db, id, false)
.await
.ok();
} }
.into_message(id.to_string())
.create_no_web_push(db, id, false)
.await
.ok();
} }
Ok(()) Ok(())
@@ -302,7 +307,7 @@ impl Server {
member: Member, member: Member,
reason: Option<String>, reason: Option<String>,
) -> Result<ServerBan> { ) -> Result<ServerBan> {
self.remove_member(db, member.clone(), RemovalIntention::Ban) self.remove_member(db, member.clone(), RemovalIntention::Ban, false)
.await?; .await?;
self.ban_user(db, member.id, reason).await self.ban_user(db, member.id, reason).await
@@ -310,23 +315,23 @@ impl Server {
} }
impl SystemMessageChannels { impl SystemMessageChannels {
pub fn into_channel_ids(self) -> Vec<String> { pub fn into_channel_ids(self) -> HashSet<String> {
let mut ids = vec![]; let mut ids = HashSet::new();
if let Some(id) = self.user_joined { if let Some(id) = self.user_joined {
ids.push(id); ids.insert(id);
} }
if let Some(id) = self.user_left { if let Some(id) = self.user_left {
ids.push(id); ids.insert(id);
} }
if let Some(id) = self.user_kicked { if let Some(id) = self.user_kicked {
ids.push(id); ids.insert(id);
} }
if let Some(id) = self.user_banned { if let Some(id) = self.user_banned {
ids.push(id); ids.insert(id);
} }
ids ids
@@ -1,5 +1,3 @@
use std::collections::HashSet;
use bson::{Bson, Document}; use bson::{Bson, Document};
use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
@@ -291,25 +289,6 @@ impl AbstractChannel for MongoDb {
with: "channel", with: "channel",
}) })
} }
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool> {
let count = channels.len() as u64;
self.col::<Document>(COL)
.count_documents(
doc! {
"_id": {
"$in": channels.iter().cloned().collect::<Vec<String>>()
}
},
None,
)
.await
.map(|x| x == count)
.map_err(|_| Error::DatabaseError {
operation: "count_documents",
with: "channel",
})
}
} }
impl IntoDocumentPath for FieldsChannel { impl IntoDocumentPath for FieldsChannel {
+6 -9
View File
@@ -1,11 +1,10 @@
use crate::util::regex::RE_COLOUR; use crate::util::regex::RE_COLOUR;
use std::collections::{HashMap, HashSet};
use indexmap::{IndexMap, IndexSet};
use iso8601_timestamp::Timestamp;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use validator::Validate; use validator::Validate;
use iso8601_timestamp::Timestamp;
#[cfg(feature = "rocket_impl")] #[cfg(feature = "rocket_impl")]
use rocket::FromFormField; use rocket::FromFormField;
@@ -87,10 +86,8 @@ pub struct Masquerade {
/// Replace the display role colour shown on this message /// Replace the display role colour shown on this message
/// ///
/// Must have `ManageRole` permission to use /// Must have `ManageRole` permission to use
///
/// This can be any valid CSS colour
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
#[validate(length(min = 1, max = 32))] #[validate(length(min = 1, max = 128), regex = "RE_COLOUR")]
pub colour: Option<String>, pub colour: Option<String>,
} }
@@ -99,7 +96,7 @@ pub struct Masquerade {
pub struct Interactions { pub struct Interactions {
/// Reactions which should always appear and be distinct /// Reactions which should always appear and be distinct
#[serde(skip_serializing_if = "Option::is_none", default)] #[serde(skip_serializing_if = "Option::is_none", default)]
pub reactions: Option<HashSet<String>>, pub reactions: Option<IndexSet<String>>,
/// Whether reactions should be restricted to the given list /// Whether reactions should be restricted to the given list
#[serde(skip_serializing_if = "if_false", default)] #[serde(skip_serializing_if = "if_false", default)]
pub restrict_reactions: bool, pub restrict_reactions: bool,
@@ -145,8 +142,8 @@ pub struct Message {
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub replies: Option<Vec<String>>, pub replies: Option<Vec<String>>,
/// Hashmap of emoji IDs to array of user IDs /// Hashmap of emoji IDs to array of user IDs
#[serde(skip_serializing_if = "HashMap::is_empty", default)] #[serde(skip_serializing_if = "IndexMap::is_empty", default)]
pub reactions: HashMap<String, HashSet<String>>, pub reactions: IndexMap<String, IndexSet<String>>,
/// Information about how this message should be interacted with /// Information about how this message should be interacted with
#[serde(skip_serializing_if = "Interactions::is_default", default)] #[serde(skip_serializing_if = "Interactions::is_default", default)]
pub interactions: Interactions, pub interactions: Interactions,
+6
View File
@@ -32,9 +32,15 @@ pub struct Relationship {
/// Presence status /// Presence status
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)] #[derive(Serialize, Deserialize, JsonSchema, Debug, Clone, PartialEq)]
pub enum Presence { pub enum Presence {
/// User is online
Online, Online,
/// User is not currently available
Idle, Idle,
/// User is focusing / will only receive mentions
Focus,
/// User is busy / will not receive any notifications
Busy, Busy,
/// User appears to be offline
Invisible, Invisible,
} }
@@ -1,5 +1,3 @@
use std::collections::HashSet;
use crate::models::channel::{Channel, FieldsChannel, PartialChannel}; use crate::models::channel::{Channel, FieldsChannel, PartialChannel};
use crate::{OverrideField, Result}; use crate::{OverrideField, Result};
@@ -55,7 +53,4 @@ pub trait AbstractChannel: Sync + Send {
role: &str, role: &str,
permissions: OverrideField, permissions: OverrideField,
) -> Result<()>; ) -> Result<()>;
/// Validate existence of channels
async fn check_channels_exist(&self, channels: &HashSet<String>) -> Result<bool>;
} }
+3 -3
View File
@@ -26,19 +26,19 @@ pub fn config() -> Config {
title: "Verify your Revolt account.".into(), title: "Verify your Revolt account.".into(),
text: include_str!(crate::asset!("templates/verify.txt")).into(), text: include_str!(crate::asset!("templates/verify.txt")).into(),
url: format!("{}/login/verify/", *APP_URL), url: format!("{}/login/verify/", *APP_URL),
html: None, html: Some(include_str!(crate::asset!("templates/verify.html")).into()),
}, },
reset: Template { reset: Template {
title: "Reset your Revolt password.".into(), title: "Reset your Revolt password.".into(),
text: include_str!(crate::asset!("templates/reset.txt")).into(), text: include_str!(crate::asset!("templates/reset.txt")).into(),
url: format!("{}/login/reset/", *APP_URL), url: format!("{}/login/reset/", *APP_URL),
html: None, html: Some(include_str!(crate::asset!("templates/reset.html")).into()),
}, },
deletion: Template { deletion: Template {
title: "Confirm account deletion.".into(), title: "Confirm account deletion.".into(),
text: include_str!(crate::asset!("templates/deletion.txt")).into(), text: include_str!(crate::asset!("templates/deletion.txt")).into(),
url: format!("{}/delete/", *APP_URL), url: format!("{}/delete/", *APP_URL),
html: None, html: Some(include_str!(crate::asset!("templates/deletion.html")).into()),
}, },
welcome: None, welcome: None,
}, },