Merge branch 'master' into unify-last-message

This commit is contained in:
Yannick Funk
2021-09-13 20:19:54 +02:00
committed by GitHub
36 changed files with 1115 additions and 802 deletions

View File

@@ -1,4 +1,4 @@
use crate::util::variables::{USE_JANUARY, VAPID_PRIVATE_KEY};
use crate::util::variables::{USE_JANUARY, VAPID_PRIVATE_KEY, PUBLIC_URL};
use crate::{
database::*,
notifications::{events::ClientboundNotification, websocket::is_online},
@@ -11,12 +11,68 @@ use mongodb::{
bson::{doc, to_bson, DateTime, Document},
options::FindOptions,
};
use rauth::entities::{Model, Session};
use rocket::serde::json::Value;
use serde::{Deserialize, Serialize};
use ulid::Ulid;
use web_push::{
ContentEncoding, SubscriptionInfo, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder,
};
use web_push::{ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder};
use std::time::SystemTime;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PushNotification {
pub author: String,
pub icon: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub image: Option<String>,
pub body: String,
pub tag: String,
pub timestamp: u64,
}
impl PushNotification {
pub async fn new(msg: Message, channel: &Channel) -> Self {
let author = Ref::from_unchecked(msg.author.clone())
.fetch_user()
.await;
let (author, avatar) = if let Ok(author) = author {
(Some(author.username), author.avatar)
} else {
(None, None)
};
let icon = if let Some(avatar) = avatar {
avatar.get_autumn_url()
} else {
format!("{}/users/{}/default_avatar", PUBLIC_URL.as_str(), msg.author)
};
let image = msg.attachments.map_or(None, |attachments| {
attachments
.first()
.map_or(None, |v| Some(v.get_autumn_url()))
});
let body = match msg.content {
Content::Text(body) => body,
Content::SystemMessage(sys_msg) => sys_msg.into()
};
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time should be valid")
.as_secs();
Self {
author: author.unwrap_or_else(|| "Unknown".into()),
icon,
image,
body,
tag: channel.id().to_string(),
timestamp,
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
@@ -43,6 +99,23 @@ pub enum SystemMessage {
ChannelIconChanged { by: String },
}
impl Into<String> for SystemMessage {
fn into(self) -> String {
match self {
SystemMessage::Text { content } => content,
SystemMessage::UserAdded { .. } => "User added to the channel.".to_string(),
SystemMessage::UserRemove { .. } => "User removed from the channel.".to_string(),
SystemMessage::UserJoined { .. } => "User joined the channel.".to_string(),
SystemMessage::UserLeft { .. } => "User left the channel.".to_string(),
SystemMessage::UserKicked { .. } => "User kicked from the channel.".to_string(),
SystemMessage::UserBanned { .. } => "User banned from the channel.".to_string(),
SystemMessage::ChannelRenamed { .. } => "Channel renamed.".to_string(),
SystemMessage::ChannelDescriptionChanged { .. } => "Channel description changed.".to_string(),
SystemMessage::ChannelIconChanged { .. } => "Channel icon changed.".to_string()
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(untagged)]
pub enum Content {
@@ -177,8 +250,7 @@ impl Message {
}
let mentions = self.mentions.clone();
let enc = serde_json::to_string(&self).unwrap();
ClientboundNotification::Message(self).publish(channel.id().to_string());
ClientboundNotification::Message(self.clone()).publish(channel.id().to_string());
/*
Web Push Test Code
@@ -204,59 +276,47 @@ impl Message {
}
// Fetch their corresponding sessions.
if let Ok(mut cursor) = get_collection("accounts")
.find(
doc! {
"_id": {
"$in": target_ids
},
"sessions.subscription": {
"$exists": true
}
},
FindOptions::builder()
.projection(doc! { "sessions": 1 })
.build(),
)
.await
{
let mut subscriptions = vec![];
while let Some(result) = cursor.next().await {
if let Ok(doc) = result {
if let Ok(sessions) = doc.get_array("sessions") {
for session in sessions {
if let Some(doc) = session.as_document() {
if let Ok(sub) = doc.get_document("subscription") {
let endpoint = sub.get_str("endpoint").unwrap().to_string();
let p256dh = sub.get_str("p256dh").unwrap().to_string();
let auth = sub.get_str("auth").unwrap().to_string();
subscriptions
.push(SubscriptionInfo::new(endpoint, p256dh, auth));
}
}
if target_ids.len() > 0 {
if let Ok(mut cursor) = Session::find(
&get_db(),
doc! {
"_id": {
"$in": target_ids
},
"subscription": {
"$exists": true
}
}
}
}
if subscriptions.len() > 0 {
},
None
)
.await {
let enc = serde_json::to_string(&PushNotification::new(self, &c_clone).await).unwrap();
let client = WebPushClient::new();
let key =
base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap();
for subscription in subscriptions {
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
let sig_builder = VapidSignatureBuilder::from_pem(
std::io::Cursor::new(&key),
&subscription,
)
.unwrap();
let signature = sig_builder.build().unwrap();
builder.set_vapid_signature(signature);
builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes());
let m = builder.build().unwrap();
client.send(m).await.ok();
while let Some(Ok(session)) = cursor.next().await {
if let Some(sub) = session.subscription {
let subscription = SubscriptionInfo {
endpoint: sub.endpoint,
keys: SubscriptionKeys {
auth: sub.auth,
p256dh: sub.p256dh
}
};
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
let sig_builder = VapidSignatureBuilder::from_pem(
std::io::Cursor::new(&key),
&subscription,
)
.unwrap();
let signature = sig_builder.build().unwrap();
builder.set_vapid_signature(signature);
builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes());
let m = builder.build().unwrap();
client.send(m).await.ok();
}
}
}
}
@@ -350,7 +410,7 @@ impl Message {
let attachment_ids: Vec<String> =
attachments.iter().map(|f| f.id.to_string()).collect();
get_collection("attachments")
.update_one(
.update_many(
doc! {
"_id": {
"$in": attachment_ids
@@ -365,7 +425,7 @@ impl Message {
)
.await
.map_err(|_| Error::DatabaseError {
operation: "update_one",
operation: "update_many",
with: "attachment",
})?;
}

View File

@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
use crate::database::*;
use crate::util::result::{Error, Result};
use crate::util::variables::AUTUMN_URL;
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(tag = "type")]
@@ -112,4 +113,8 @@ impl File {
with: "attachment",
})
}
pub fn get_autumn_url(&self) -> String {
format!("{}/{}/{}", AUTUMN_URL.as_str(), self.tag, self.id)
}
}

View File

@@ -46,6 +46,9 @@ pub enum Special {
None,
YouTube {
id: String,
#[serde(skip_serializing_if = "Option::is_none")]
timestamp: Option<String>,
},
Twitch {
content_type: TwitchType,

View File

@@ -1,7 +1,7 @@
use crate::database::*;
use mongodb::bson::{doc, from_document};
use rauth::auth::Session;
use rauth::entities::Session;
use rocket::http::Status;
use rocket::request::{self, FromRequest, Outcome, Request};
@@ -15,7 +15,7 @@ impl<'r> FromRequest<'r> for User {
.get("x-bot-token")
.next()
.map(|x| x.to_string());
if let Some(bot_token) = header_bot_token {
return if let Ok(result) = get_collection("bots")
.find_one(
@@ -40,7 +40,10 @@ impl<'r> FromRequest<'r> for User {
if let Some(doc) = result {
Outcome::Success(from_document(doc).unwrap())
} else {
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
Outcome::Failure((
Status::Forbidden,
rauth::util::Error::InvalidSession,
))
}
} else {
Outcome::Failure((
@@ -62,32 +65,34 @@ impl<'r> FromRequest<'r> for User {
with: "bot",
},
))
}
};
} else {
let session: Session = request.guard::<Session>().await.unwrap();
if let Ok(result) = get_collection("users")
.find_one(
doc! {
"_id": &session.user_id
},
None,
)
.await
{
if let Some(doc) = result {
Outcome::Success(from_document(doc).unwrap())
if let Outcome::Success(session) = request.guard::<Session>().await {
if let Ok(result) = get_collection("users")
.find_one(
doc! {
"_id": &session.user_id
},
None,
)
.await
{
if let Some(doc) = result {
Outcome::Success(from_document(doc).unwrap())
} else {
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
}
} else {
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
Outcome::Failure((
Status::InternalServerError,
rauth::util::Error::DatabaseError {
operation: "find_one",
with: "user",
},
))
}
} else {
Outcome::Failure((
Status::InternalServerError,
rauth::util::Error::DatabaseError {
operation: "find_one",
with: "user",
},
))
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
}
}
}

View File

@@ -71,39 +71,6 @@ pub async fn create_database() {
.await
.expect("Failed to create pubsub collection.");
db.run_command(
doc! {
"createIndexes": "accounts",
"indexes": [
{
"key": {
"email": 1
},
"name": "email",
"unique": true,
"collation": {
"locale": "en",
"strength": 2
}
},
{
"key": {
"email_normalised": 1
},
"name": "email_normalised",
"unique": true,
"collation": {
"locale": "en",
"strength": 2
}
}
]
},
None,
)
.await
.expect("Failed to create account index.");
db.run_command(
doc! {
"createIndexes": "users",

View File

@@ -10,10 +10,13 @@ pub async fn run_migrations() {
.list_database_names(None, None)
.await
.expect("Failed to fetch database names.");
if list.iter().position(|x| x == "revolt").is_none() {
init::create_database().await;
} else {
scripts::migrate_database().await;
}
// panic!("https://pbs.twimg.com/media/EDTpB5JWwAUvyxd.jpg");
rauth::entities::sync_models(&super::get_db()).await;
}

View File

@@ -2,7 +2,7 @@ use crate::database::{permissions, get_collection, get_db, PermissionTuple};
use futures::StreamExt;
use log::info;
use mongodb::{bson::{doc, from_document, to_document}, options::FindOptions};
use mongodb::{bson::{Document, doc, from_bson, from_document, to_document}, options::FindOptions};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
@@ -11,7 +11,7 @@ struct MigrationInfo {
revision: i32,
}
pub const LATEST_REVISION: i32 = 8;
pub const LATEST_REVISION: i32 = 9;
pub async fn migrate_database() {
let migrations = get_collection("migrations");
@@ -212,6 +212,82 @@ pub async fn run_migrations(revision: i32) -> i32 {
.expect("Failed to create bots collection.");
}
if revision <= 8 {
info!("Running migration [revision 8 / 2021-09-10]: Update to rAuth version 1.");
get_db()
.run_command(
doc! {
"dropIndexes": "accounts",
"index": ["email", "email_normalised"]
},
None,
)
.await
.expect("Failed to delete legacy account indexes.");
let col = get_collection("sessions");
let mut cursor = get_collection("accounts")
.find(doc! { }, None)
.await
.unwrap();
while let Some(doc) = cursor.next().await {
if let Ok(account) = doc {
let id = account.get_str("_id").unwrap();
if let Some(sessions) = account.get("sessions") {
#[derive(Deserialize)]
struct Session {
id: String,
token: String,
friendly_name: String,
subscription: Option<Document>,
}
let sessions = from_bson::<Vec<Session>>(sessions.clone()).unwrap();
for session in sessions {
info!("Converting session {} to new format.", &session.id);
let mut doc = doc! {
"_id": session.id,
"token": session.token,
"user_id": id.clone(),
"name": session.friendly_name,
};
if let Some(sub) = session.subscription {
doc.insert("subscription", sub);
}
col.insert_one(doc, None).await.ok();
}
} else {
info!("Account doesn't have any sessions!");
}
}
}
get_collection("accounts")
.update_many(
doc! { },
doc! {
"$unset": {
"sessions": 1,
},
"$set": {
"mfa": {
"recovery_codes": []
}
}
},
None
)
.await
.unwrap();
}
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
// Reminder to update LATEST_REVISION when adding new migrations.
LATEST_REVISION
}