Merge remote-tracking branch 'upstream/master' into ratelimits
This commit is contained in:
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
ko_fi: insertish
|
||||||
|
custom: https://insrt.uk/donate
|
||||||
103
.github/workflows/docker.yml
vendored
Normal file
103
.github/workflows/docker.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
name: Docker
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
paths-ignore:
|
||||||
|
- ".github/**"
|
||||||
|
- "!.github/workflows/docker.yml"
|
||||||
|
- ".vscode/**"
|
||||||
|
- ".gitignore"
|
||||||
|
- ".gitlab-ci.yml"
|
||||||
|
- "LICENSE"
|
||||||
|
- "README"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "master"
|
||||||
|
paths:
|
||||||
|
- "Dockerfile"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
architecture: [linux/amd64]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Cache Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
key: ${{ runner.os }}-buildx-${{ matrix.architecture }}-${{ github.sha }}
|
||||||
|
- name: Build
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
platforms: ${{ matrix.architecture }}
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-new/${{ matrix.architecture }},mode=max
|
||||||
|
- name: Move cache
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
mv /tmp/.buildx-cache-new/${{ matrix.architecture }} /tmp/.buildx-cache/${{ matrix.architecture }}
|
||||||
|
|
||||||
|
publish:
|
||||||
|
needs: [test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: "recursive"
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Cache amd64 Docker layers
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: /tmp/.buildx-cache/linux/amd64
|
||||||
|
key: ${{ runner.os }}-buildx-linux/amd64-${{ github.sha }}
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v3
|
||||||
|
with:
|
||||||
|
images: revoltchat/server, ghcr.io/revoltchat/server
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Login to Github Container Registry
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Build and publish
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: true
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=local,src=/tmp/.buildx-cache/linux/amd64
|
||||||
|
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
|
||||||
|
- name: Move cache
|
||||||
|
run: |
|
||||||
|
rm -rf /tmp/.buildx-cache
|
||||||
|
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||||
33
.github/workflows/rust.yaml
vendored
Normal file
33
.github/workflows/rust.yaml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
name: Rust build and test
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check:
|
||||||
|
name: Rust project
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Install latest nightly
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
components: rustfmt, clippy
|
||||||
|
|
||||||
|
- name: Run cargo build
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: build
|
||||||
|
|
||||||
|
- name: Run cargo test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
1023
Cargo.lock
generated
1023
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -11,6 +11,7 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# Utility
|
# Utility
|
||||||
|
url = "2.2.2"
|
||||||
log = "0.4.11"
|
log = "0.4.11"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
linkify = "0.6.0"
|
linkify = "0.6.0"
|
||||||
@@ -18,7 +19,6 @@ once_cell = "1.4.1"
|
|||||||
env_logger = "0.7.1"
|
env_logger = "0.7.1"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
ctrlc = { version = "3.0", features = ["termination"] }
|
ctrlc = { version = "3.0", features = ["termination"] }
|
||||||
url = "2.2.2"
|
|
||||||
|
|
||||||
# Lang. Utilities
|
# Lang. Utilities
|
||||||
regex = "1"
|
regex = "1"
|
||||||
@@ -48,7 +48,7 @@ async-std = { version = "1.8.0", features = ["tokio1", "tokio02", "attributes"]
|
|||||||
web-push = "0.7.2"
|
web-push = "0.7.2"
|
||||||
many-to-many = "0.1.2"
|
many-to-many = "0.1.2"
|
||||||
lettre = "0.10.0-alpha.4"
|
lettre = "0.10.0-alpha.4"
|
||||||
rauth = { git = "https://gitlab.insrt.uk/insert/rauth", rev = "19bef300ecc269c028e3d746f0a2f09842d3b37b" }
|
rauth = { git = "https://github.com/insertish/rauth", rev = "ad2f5ad1657f4b20c8e483cc1d6c513ff9b22ae1" }
|
||||||
hive_pubsub = { git = "https://gitlab.insrt.uk/insert/hive", rev = "a89826df2b30166220e68a6ed01a58b751456604", features = ["mongo"] }
|
hive_pubsub = { git = "https://gitlab.insrt.uk/insert/hive", rev = "a89826df2b30166220e68a6ed01a58b751456604", features = ["mongo"] }
|
||||||
|
|
||||||
# web
|
# web
|
||||||
|
|||||||
5
assets/templates/reset.txt
Normal file
5
assets/templates/reset.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
You requested a password reset, if you did not perform this action you can safely ignore this email.
|
||||||
|
|
||||||
|
Reset your password here: {{url}}
|
||||||
|
|
||||||
|
Sent by Revolt.
|
||||||
6
assets/templates/verify.txt
Normal file
6
assets/templates/verify.txt
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
You're almost there!
|
||||||
|
If you did not perform this action you can safely ignore this email.
|
||||||
|
|
||||||
|
Please verify your account here: {{url}}
|
||||||
|
|
||||||
|
Sent by Revolt.
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
export version=0.5.2-alpha.1
|
export version=0.5.3-alpha.1
|
||||||
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
echo "pub const VERSION: &str = \"${version}\";" > src/version.rs
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ use mongodb::{
|
|||||||
use rocket::serde::json::Value;
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
pub struct LastMessage {
|
|
||||||
#[serde(rename = "_id")]
|
|
||||||
id: String,
|
|
||||||
author: String,
|
|
||||||
short: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "channel_type")]
|
#[serde(tag = "channel_type")]
|
||||||
pub enum Channel {
|
pub enum Channel {
|
||||||
@@ -36,7 +28,7 @@ pub enum Channel {
|
|||||||
active: bool,
|
active: bool,
|
||||||
recipients: Vec<String>,
|
recipients: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<LastMessage>,
|
last_message_id: Option<String>,
|
||||||
},
|
},
|
||||||
Group {
|
Group {
|
||||||
#[serde(rename = "_id")]
|
#[serde(rename = "_id")]
|
||||||
@@ -53,7 +45,7 @@ pub enum Channel {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
icon: Option<File>,
|
icon: Option<File>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<LastMessage>,
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
permissions: Option<i32>,
|
permissions: Option<i32>,
|
||||||
@@ -75,7 +67,7 @@ pub enum Channel {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
icon: Option<File>,
|
icon: Option<File>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
last_message: Option<String>,
|
last_message_id: Option<String>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
default_permissions: Option<i32>,
|
default_permissions: Option<i32>,
|
||||||
|
|||||||
@@ -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::{
|
use crate::{
|
||||||
database::*,
|
database::*,
|
||||||
notifications::{events::ClientboundNotification, websocket::is_online},
|
notifications::{events::ClientboundNotification, websocket::is_online},
|
||||||
@@ -8,15 +8,70 @@ use crate::{
|
|||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use mongodb::options::UpdateOptions;
|
use mongodb::options::UpdateOptions;
|
||||||
use mongodb::{
|
use mongodb::{
|
||||||
bson::{doc, to_bson, DateTime},
|
bson::{doc, to_bson, DateTime, Document},
|
||||||
options::FindOptions,
|
|
||||||
};
|
};
|
||||||
|
use rauth::entities::{Model, Session};
|
||||||
use rocket::serde::json::Value;
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use ulid::Ulid;
|
use ulid::Ulid;
|
||||||
use web_push::{
|
use web_push::{ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient, WebPushMessageBuilder};
|
||||||
ContentEncoding, SubscriptionInfo, 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)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@@ -43,6 +98,23 @@ pub enum SystemMessage {
|
|||||||
ChannelIconChanged { by: String },
|
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)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum Content {
|
pub enum Content {
|
||||||
@@ -99,7 +171,6 @@ impl Message {
|
|||||||
nonce: None,
|
nonce: None,
|
||||||
channel,
|
channel,
|
||||||
author,
|
author,
|
||||||
|
|
||||||
content,
|
content,
|
||||||
attachments: None,
|
attachments: None,
|
||||||
edited: None,
|
edited: None,
|
||||||
@@ -124,76 +195,19 @@ impl Message {
|
|||||||
let ss = self.clone();
|
let ss = self.clone();
|
||||||
let c_clone = channel.clone();
|
let c_clone = channel.clone();
|
||||||
async_std::task::spawn(async move {
|
async_std::task::spawn(async move {
|
||||||
let mut set = if let Content::Text(text) = &ss.content {
|
|
||||||
doc! {
|
|
||||||
"last_message": {
|
|
||||||
"_id": ss.id.clone(),
|
|
||||||
"author": ss.author.clone(),
|
|
||||||
"short": text.chars().take(128).collect::<String>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
doc! {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// ! MARK AS ACTIVE
|
let last_message_id = ss.id.clone();
|
||||||
// ! FIXME: temp code
|
let mut set = doc! { "last_message_id": last_message_id };
|
||||||
|
|
||||||
let channels = get_collection("channels");
|
let channels = get_collection("channels");
|
||||||
match &c_clone {
|
match &c_clone {
|
||||||
Channel::DirectMessage { id, .. } => {
|
Channel::DirectMessage { id, .. } => {
|
||||||
|
// ! MARK AS ACTIVE
|
||||||
set.insert("active", true);
|
set.insert("active", true);
|
||||||
channels
|
update_channels_last_message(&channels, id, &set).await;
|
||||||
.update_one(
|
},
|
||||||
doc! { "_id": id },
|
Channel::Group { id, .. } | Channel::TextChannel { id, .. } => {
|
||||||
doc! {
|
update_channels_last_message(&channels, id, &set).await;
|
||||||
"$set": set
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
/*.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;*/
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
Channel::Group { id, .. } => {
|
|
||||||
if let Content::Text(_) = &ss.content {
|
|
||||||
channels
|
|
||||||
.update_one(
|
|
||||||
doc! { "_id": id },
|
|
||||||
doc! {
|
|
||||||
"$set": set
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
/*.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;*/
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Channel::TextChannel { id, .. } => {
|
|
||||||
if let Content::Text(_) = &ss.content {
|
|
||||||
channels
|
|
||||||
.update_one(
|
|
||||||
doc! { "_id": id },
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"last_message": &ss.id
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
/*.map_err(|_| Error::DatabaseError {
|
|
||||||
operation: "update_one",
|
|
||||||
with: "channel",
|
|
||||||
})?;*/
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -235,8 +249,7 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mentions = self.mentions.clone();
|
let mentions = self.mentions.clone();
|
||||||
let enc = serde_json::to_string(&self).unwrap();
|
ClientboundNotification::Message(self.clone()).publish(channel.id().to_string());
|
||||||
ClientboundNotification::Message(self).publish(channel.id().to_string());
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Web Push Test Code
|
Web Push Test Code
|
||||||
@@ -262,59 +275,47 @@ impl Message {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch their corresponding sessions.
|
// Fetch their corresponding sessions.
|
||||||
if let Ok(mut cursor) = get_collection("accounts")
|
if target_ids.len() > 0 {
|
||||||
.find(
|
if let Ok(mut cursor) = Session::find(
|
||||||
doc! {
|
&get_db(),
|
||||||
"_id": {
|
doc! {
|
||||||
"$in": target_ids
|
"_id": {
|
||||||
},
|
"$in": target_ids
|
||||||
"sessions.subscription": {
|
},
|
||||||
"$exists": true
|
"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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
None
|
||||||
}
|
)
|
||||||
|
.await {
|
||||||
if subscriptions.len() > 0 {
|
let enc = serde_json::to_string(&PushNotification::new(self, &c_clone).await).unwrap();
|
||||||
let client = WebPushClient::new();
|
let client = WebPushClient::new();
|
||||||
let key =
|
let key =
|
||||||
base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap();
|
base64::decode_config(VAPID_PRIVATE_KEY.clone(), base64::URL_SAFE).unwrap();
|
||||||
|
|
||||||
for subscription in subscriptions {
|
while let Some(Ok(session)) = cursor.next().await {
|
||||||
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
|
if let Some(sub) = session.subscription {
|
||||||
let sig_builder = VapidSignatureBuilder::from_pem(
|
let subscription = SubscriptionInfo {
|
||||||
std::io::Cursor::new(&key),
|
endpoint: sub.endpoint,
|
||||||
&subscription,
|
keys: SubscriptionKeys {
|
||||||
)
|
auth: sub.auth,
|
||||||
.unwrap();
|
p256dh: sub.p256dh
|
||||||
let signature = sig_builder.build().unwrap();
|
}
|
||||||
builder.set_vapid_signature(signature);
|
};
|
||||||
builder.set_payload(ContentEncoding::AesGcm, enc.as_bytes());
|
|
||||||
let m = builder.build().unwrap();
|
let mut builder = WebPushMessageBuilder::new(&subscription).unwrap();
|
||||||
client.send(m).await.ok();
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -408,7 +409,7 @@ impl Message {
|
|||||||
let attachment_ids: Vec<String> =
|
let attachment_ids: Vec<String> =
|
||||||
attachments.iter().map(|f| f.id.to_string()).collect();
|
attachments.iter().map(|f| f.id.to_string()).collect();
|
||||||
get_collection("attachments")
|
get_collection("attachments")
|
||||||
.update_one(
|
.update_many(
|
||||||
doc! {
|
doc! {
|
||||||
"_id": {
|
"_id": {
|
||||||
"$in": attachment_ids
|
"$in": attachment_ids
|
||||||
@@ -423,7 +424,7 @@ impl Message {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "update_one",
|
operation: "update_many",
|
||||||
with: "attachment",
|
with: "attachment",
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
@@ -431,3 +432,15 @@ impl Message {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn update_channels_last_message(channels: &Collection, channel_id: &String, set: &Document) {
|
||||||
|
channels
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": channel_id },
|
||||||
|
doc! { "$set": set },
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.expect("Server should not run with no, or a corrupted db");
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::AUTUMN_URL;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
@@ -112,4 +113,8 @@ impl File {
|
|||||||
with: "attachment",
|
with: "attachment",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_autumn_url(&self) -> String {
|
||||||
|
format!("{}/{}/{}", AUTUMN_URL.as_str(), self.tag, self.id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::util::{
|
use crate::util::{
|
||||||
result::{Error, Result},
|
result::{Error, Result},
|
||||||
variables::JANUARY_URL,
|
variables::JANUARY_URL,
|
||||||
|
variables::MAX_EMBED_COUNT,
|
||||||
};
|
};
|
||||||
use linkify::{LinkFinder, LinkKind};
|
use linkify::{LinkFinder, LinkKind};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
@@ -46,6 +47,9 @@ pub enum Special {
|
|||||||
None,
|
None,
|
||||||
YouTube {
|
YouTube {
|
||||||
id: String,
|
id: String,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
timestamp: Option<String>,
|
||||||
},
|
},
|
||||||
Twitch {
|
Twitch {
|
||||||
content_type: TwitchType,
|
content_type: TwitchType,
|
||||||
@@ -100,11 +104,15 @@ impl Embed {
|
|||||||
pub async fn generate(content: String) -> Result<Vec<Embed>> {
|
pub async fn generate(content: String) -> Result<Vec<Embed>> {
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
|
static ref RE_CODE: Regex = Regex::new("```(?:.|\n)+?```|`(?:.|\n)+?`").unwrap();
|
||||||
|
static ref RE_IGNORED: Regex = Regex::new("(<http.+>)").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore code blocks.
|
// Ignore code blocks.
|
||||||
let content = RE_CODE.replace_all(&content, "");
|
let content = RE_CODE.replace_all(&content, "");
|
||||||
|
|
||||||
|
// Ignore all content between angle brackets starting with http.
|
||||||
|
let content = RE_IGNORED.replace_all(&content, "");
|
||||||
|
|
||||||
let content = content
|
let content = content
|
||||||
// Ignore quoted lines.
|
// Ignore quoted lines.
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@@ -120,35 +128,55 @@ impl Embed {
|
|||||||
.collect::<Vec<&str>>()
|
.collect::<Vec<&str>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
// ! FIXME: allow multiple links
|
|
||||||
// ! FIXME: prevent generation if link is surrounded with < >
|
|
||||||
let mut finder = LinkFinder::new();
|
let mut finder = LinkFinder::new();
|
||||||
finder.kinds(&[LinkKind::Url]);
|
finder.kinds(&[LinkKind::Url]);
|
||||||
let links: Vec<_> = finder.links(&content).collect();
|
let links: Vec<_> = finder.links(&content).take(*MAX_EMBED_COUNT).collect();
|
||||||
|
|
||||||
if links.len() == 0 {
|
if links.len() == 0 {
|
||||||
return Err(Error::LabelMe);
|
return Err(Error::LabelMe);
|
||||||
}
|
}
|
||||||
|
|
||||||
let link = &links[0];
|
let mut embeds: Vec<Embed> = Vec::new();
|
||||||
|
|
||||||
let client = reqwest::Client::new();
|
let mut link_index = 0;
|
||||||
let result = client
|
|
||||||
.get(&format!("{}/embed", *JANUARY_URL))
|
|
||||||
.query(&[("url", link.as_str())])
|
|
||||||
.send()
|
|
||||||
.await;
|
|
||||||
|
|
||||||
match result {
|
// ! FIXME: batch request to january?
|
||||||
Err(_) => return Err(Error::LabelMe),
|
while link_index < links.len() {
|
||||||
Ok(result) => match result.status() {
|
let link = &links[link_index];
|
||||||
reqwest::StatusCode::OK => {
|
|
||||||
let res: Embed = result.json().await.map_err(|_| Error::InvalidOperation)?;
|
|
||||||
|
|
||||||
Ok(vec![res])
|
// Check if we already processed this link.
|
||||||
}
|
if link_index != 0 && links.iter().take(link_index).any(|x| x.as_str() == link.as_str()) {
|
||||||
_ => return Err(Error::LabelMe),
|
link_index = link_index + 1;
|
||||||
},
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
let result = client
|
||||||
|
.get(&format!("{}/embed", *JANUARY_URL))
|
||||||
|
.query(&[("url", link.as_str())])
|
||||||
|
.send()
|
||||||
|
.await;
|
||||||
|
|
||||||
|
if result.is_err() {
|
||||||
|
link_index = link_index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let response = result.unwrap();
|
||||||
|
if response.status().is_success() {
|
||||||
|
let res: Embed = response.json().await.map_err(|_| Error::InvalidOperation)?;
|
||||||
|
|
||||||
|
embeds.push(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
link_index = link_index + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent database update when no embeds are found.
|
||||||
|
if embeds.len() > 0 {
|
||||||
|
Ok(embeds)
|
||||||
|
} else {
|
||||||
|
Err(Error::LabelMe)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ pub struct Server {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
|
|
||||||
pub channels: Vec<String>,
|
pub channels: Vec<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub categories: Option<Vec<Category>>,
|
pub categories: Option<Vec<Category>>,
|
||||||
@@ -108,6 +108,9 @@ pub struct Server {
|
|||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub banner: Option<File>,
|
pub banner: Option<File>,
|
||||||
|
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub flags: Option<i32>,
|
||||||
|
|
||||||
#[serde(skip_serializing_if = "if_false", default)]
|
#[serde(skip_serializing_if = "if_false", default)]
|
||||||
pub nsfw: bool
|
pub nsfw: bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use crate::database::*;
|
|||||||
use crate::notifications::websocket::is_online;
|
use crate::notifications::websocket::is_online;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
use crate::util::variables::EARLY_ADOPTER_BADGE;
|
use crate::util::variables::EARLY_ADOPTER_BADGE;
|
||||||
|
use crate::util::variables::MAX_SERVER_COUNT;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
|
||||||
pub enum RelationshipStatus {
|
pub enum RelationshipStatus {
|
||||||
@@ -323,4 +324,10 @@ impl User {
|
|||||||
.collect::<Vec<Document>>()
|
.collect::<Vec<Document>>()
|
||||||
.await)
|
.await)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check if this user can acquire another server.
|
||||||
|
pub async fn can_acquire_server(id: &str) -> Result<bool> {
|
||||||
|
let server_ids = User::fetch_server_ids(&id).await?;
|
||||||
|
Ok(server_ids.len() < *MAX_SERVER_COUNT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
|
||||||
use mongodb::bson::{doc, from_document};
|
use mongodb::bson::{doc, from_document};
|
||||||
use rauth::auth::Session;
|
use rauth::entities::Session;
|
||||||
use rocket::http::Status;
|
use rocket::http::Status;
|
||||||
use rocket::request::{self, FromRequest, Outcome, Request};
|
use rocket::request::{self, FromRequest, Outcome, Request};
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ impl<'r> FromRequest<'r> for User {
|
|||||||
.get("x-bot-token")
|
.get("x-bot-token")
|
||||||
.next()
|
.next()
|
||||||
.map(|x| x.to_string());
|
.map(|x| x.to_string());
|
||||||
|
|
||||||
if let Some(bot_token) = header_bot_token {
|
if let Some(bot_token) = header_bot_token {
|
||||||
return if let Ok(result) = get_collection("bots")
|
return if let Ok(result) = get_collection("bots")
|
||||||
.find_one(
|
.find_one(
|
||||||
@@ -40,7 +40,10 @@ impl<'r> FromRequest<'r> for User {
|
|||||||
if let Some(doc) = result {
|
if let Some(doc) = result {
|
||||||
Outcome::Success(from_document(doc).unwrap())
|
Outcome::Success(from_document(doc).unwrap())
|
||||||
} else {
|
} else {
|
||||||
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
|
Outcome::Failure((
|
||||||
|
Status::Forbidden,
|
||||||
|
rauth::util::Error::InvalidSession,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Outcome::Failure((
|
Outcome::Failure((
|
||||||
@@ -62,32 +65,34 @@ impl<'r> FromRequest<'r> for User {
|
|||||||
with: "bot",
|
with: "bot",
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
};
|
||||||
} else {
|
} else {
|
||||||
let session: Session = request.guard::<Session>().await.unwrap();
|
if let Outcome::Success(session) = request.guard::<Session>().await {
|
||||||
|
if let Ok(result) = get_collection("users")
|
||||||
if let Ok(result) = get_collection("users")
|
.find_one(
|
||||||
.find_one(
|
doc! {
|
||||||
doc! {
|
"_id": &session.user_id
|
||||||
"_id": &session.user_id
|
},
|
||||||
},
|
None,
|
||||||
None,
|
)
|
||||||
)
|
.await
|
||||||
.await
|
{
|
||||||
{
|
if let Some(doc) = result {
|
||||||
if let Some(doc) = result {
|
Outcome::Success(from_document(doc).unwrap())
|
||||||
Outcome::Success(from_document(doc).unwrap())
|
} else {
|
||||||
|
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
|
Outcome::Failure((
|
||||||
|
Status::InternalServerError,
|
||||||
|
rauth::util::Error::DatabaseError {
|
||||||
|
operation: "find_one",
|
||||||
|
with: "user",
|
||||||
|
},
|
||||||
|
))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Outcome::Failure((
|
Outcome::Failure((Status::Forbidden, rauth::util::Error::InvalidSession))
|
||||||
Status::InternalServerError,
|
|
||||||
rauth::util::Error::DatabaseError {
|
|
||||||
operation: "find_one",
|
|
||||||
with: "user",
|
|
||||||
},
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,39 +71,6 @@ pub async fn create_database() {
|
|||||||
.await
|
.await
|
||||||
.expect("Failed to create pubsub collection.");
|
.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(
|
db.run_command(
|
||||||
doc! {
|
doc! {
|
||||||
"createIndexes": "users",
|
"createIndexes": "users",
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ pub async fn run_migrations() {
|
|||||||
.list_database_names(None, None)
|
.list_database_names(None, None)
|
||||||
.await
|
.await
|
||||||
.expect("Failed to fetch database names.");
|
.expect("Failed to fetch database names.");
|
||||||
|
|
||||||
if list.iter().position(|x| x == "revolt").is_none() {
|
if list.iter().position(|x| x == "revolt").is_none() {
|
||||||
init::create_database().await;
|
init::create_database().await;
|
||||||
} else {
|
} else {
|
||||||
scripts::migrate_database().await;
|
scripts::migrate_database().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// panic!("https://pbs.twimg.com/media/EDTpB5JWwAUvyxd.jpg");
|
||||||
|
rauth::entities::sync_models(&super::get_db()).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::database::{permissions, get_collection, get_db, PermissionTuple};
|
|||||||
|
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use log::info;
|
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};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
@@ -11,7 +11,7 @@ struct MigrationInfo {
|
|||||||
revision: i32,
|
revision: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const LATEST_REVISION: i32 = 8;
|
pub const LATEST_REVISION: i32 = 10;
|
||||||
|
|
||||||
pub async fn migrate_database() {
|
pub async fn migrate_database() {
|
||||||
let migrations = get_collection("migrations");
|
let migrations = get_collection("migrations");
|
||||||
@@ -212,6 +212,138 @@ pub async fn run_migrations(revision: i32) -> i32 {
|
|||||||
.expect("Failed to create bots collection.");
|
.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();
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision <= 9 {
|
||||||
|
info!("Running migration [revision 9 / 2021-09-14]: Switch from last_message to last_message_id.");
|
||||||
|
|
||||||
|
let mut cursor = get_collection("channels")
|
||||||
|
.find(doc! { }, None)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Some(doc) = cursor.next().await {
|
||||||
|
if let Ok(channel) = doc {
|
||||||
|
let channel_id = channel.get_str("_id").unwrap();
|
||||||
|
if let Some(last_message) = channel.get("last_message") {
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct Obj {
|
||||||
|
#[serde(rename = "_id")]
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum LastMessage {
|
||||||
|
Obj(Obj),
|
||||||
|
Id(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
let lm = from_bson::<LastMessage>(last_message.clone()).unwrap();
|
||||||
|
let id = match lm {
|
||||||
|
LastMessage::Obj(Obj { id }) => id,
|
||||||
|
LastMessage::Id(id) => id
|
||||||
|
};
|
||||||
|
|
||||||
|
info!("Converting session {} to new format.", &channel_id);
|
||||||
|
get_collection("channels")
|
||||||
|
.update_one(
|
||||||
|
doc! {
|
||||||
|
"_id": channel_id
|
||||||
|
},
|
||||||
|
doc! {
|
||||||
|
"$set": {
|
||||||
|
"last_message_id": id
|
||||||
|
},
|
||||||
|
"$unset": {
|
||||||
|
"last_message": 1,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
info!("{} has no last_message.", &channel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Need to migrate fields on attachments, change `user_id`, `object_id`, etc to `parent`.
|
||||||
|
|
||||||
// Reminder to update LATEST_REVISION when adding new migrations.
|
// Reminder to update LATEST_REVISION when adding new migrations.
|
||||||
LATEST_REVISION
|
LATEST_REVISION
|
||||||
}
|
}
|
||||||
|
|||||||
94
src/main.rs
94
src/main.rs
@@ -21,19 +21,17 @@ pub mod util;
|
|||||||
pub mod version;
|
pub mod version;
|
||||||
|
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use chrono::Duration;
|
|
||||||
use futures::join;
|
use futures::join;
|
||||||
use log::info;
|
use log::info;
|
||||||
use rauth::options::{EmailVerification, Options, SMTP};
|
|
||||||
use rauth::{
|
use rauth::{
|
||||||
auth::Auth,
|
config::{Captcha, Config, EmailVerification, SMTPSettings, Template, Templates},
|
||||||
options::{Template, Templates},
|
logic::Auth,
|
||||||
};
|
};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use rocket_cors::AllowedOrigins;
|
use rocket_cors::AllowedOrigins;
|
||||||
use util::variables::{
|
use util::variables::{
|
||||||
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, PUBLIC_URL, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD,
|
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME,
|
||||||
SMTP_USERNAME, USE_EMAIL, USE_HCAPTCHA,
|
USE_EMAIL, USE_HCAPTCHA,
|
||||||
};
|
};
|
||||||
use crate::util::ratelimit::RatelimitState;
|
use crate::util::ratelimit::RatelimitState;
|
||||||
|
|
||||||
@@ -71,79 +69,67 @@ async fn launch_web() {
|
|||||||
let cors = rocket_cors::CorsOptions {
|
let cors = rocket_cors::CorsOptions {
|
||||||
allowed_origins: AllowedOrigins::All,
|
allowed_origins: AllowedOrigins::All,
|
||||||
allowed_methods: [
|
allowed_methods: [
|
||||||
"Get",
|
"Get", "Put", "Post", "Delete", "Options", "Head", "Trace", "Connect", "Patch",
|
||||||
"Put",
|
|
||||||
"Post",
|
|
||||||
"Delete",
|
|
||||||
"Options",
|
|
||||||
"Head",
|
|
||||||
"Trace",
|
|
||||||
"Connect",
|
|
||||||
"Patch",
|
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|s| FromStr::from_str(s).unwrap())
|
.map(|s| FromStr::from_str(s).unwrap())
|
||||||
.collect(),
|
.collect(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
.to_cors()
|
.to_cors()
|
||||||
.expect("Failed to create CORS.");
|
.expect("Failed to create CORS.");
|
||||||
|
|
||||||
let mut options = Options::new()
|
let mut config = Config {
|
||||||
.base_url(format!("{}/auth", *PUBLIC_URL))
|
email_verification: if *USE_EMAIL {
|
||||||
.email_verification(if *USE_EMAIL {
|
|
||||||
EmailVerification::Enabled {
|
EmailVerification::Enabled {
|
||||||
success_redirect_uri: format!("{}/login", *APP_URL),
|
smtp: SMTPSettings {
|
||||||
welcome_redirect_uri: format!("{}/welcome", *APP_URL),
|
|
||||||
password_reset_url: Some(format!("{}/login/reset", *APP_URL)),
|
|
||||||
|
|
||||||
verification_expiry: Duration::days(1),
|
|
||||||
password_reset_expiry: Duration::hours(1),
|
|
||||||
|
|
||||||
templates: Templates {
|
|
||||||
verify_email: Template {
|
|
||||||
title: "Verify your Revolt account.",
|
|
||||||
text: "You're almost there!
|
|
||||||
If you did not perform this action you can safely ignore this email.
|
|
||||||
|
|
||||||
Please verify your account here: {{url}}",
|
|
||||||
html: None,
|
|
||||||
},
|
|
||||||
reset_password: Template {
|
|
||||||
title: "Reset your Revolt password.",
|
|
||||||
text: "You requested a password reset, if you did not perform this action you can safely ignore this email.
|
|
||||||
|
|
||||||
Reset your password here: {{url}}",
|
|
||||||
html: None,
|
|
||||||
},
|
|
||||||
welcome: None,
|
|
||||||
},
|
|
||||||
|
|
||||||
smtp: SMTP {
|
|
||||||
from: (*SMTP_FROM).to_string(),
|
from: (*SMTP_FROM).to_string(),
|
||||||
host: (*SMTP_HOST).to_string(),
|
host: (*SMTP_HOST).to_string(),
|
||||||
username: (*SMTP_USERNAME).to_string(),
|
username: (*SMTP_USERNAME).to_string(),
|
||||||
password: (*SMTP_PASSWORD).to_string(),
|
password: (*SMTP_PASSWORD).to_string(),
|
||||||
|
reply_to: Some("support@revolt.chat".into()),
|
||||||
|
port: None,
|
||||||
|
use_tls: None,
|
||||||
|
},
|
||||||
|
expiry: Default::default(),
|
||||||
|
templates: Templates {
|
||||||
|
verify: Template {
|
||||||
|
title: "Verify your Revolt account.".into(),
|
||||||
|
text: include_str!("../assets/templates/verify.txt").into(),
|
||||||
|
url: format!("{}/login/verify/", *APP_URL),
|
||||||
|
html: None,
|
||||||
|
},
|
||||||
|
reset: Template {
|
||||||
|
title: "Reset your Revolt password.".into(),
|
||||||
|
text: include_str!("../assets/templates/reset.txt").into(),
|
||||||
|
url: format!("{}/login/reset/", *APP_URL),
|
||||||
|
html: None,
|
||||||
|
},
|
||||||
|
welcome: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
EmailVerification::Disabled
|
EmailVerification::Disabled
|
||||||
});
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
if *INVITE_ONLY {
|
if *INVITE_ONLY {
|
||||||
options = options.invite_only_collection(database::get_collection("invites"))
|
config.invite_only = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if *USE_HCAPTCHA {
|
if *USE_HCAPTCHA {
|
||||||
options = options.hcaptcha_secret(HCAPTCHA_KEY.clone());
|
config.captcha = Captcha::HCaptcha {
|
||||||
|
secret: HCAPTCHA_KEY.clone(),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let auth = Auth::new(database::get_collection("accounts"), options);
|
let auth = Auth::new(database::get_db(), config);
|
||||||
let rocket = rocket::build();
|
let rocket = rocket::build();
|
||||||
|
|
||||||
routes::mount(rocket)
|
routes::mount(rocket)
|
||||||
.mount("/", rocket_cors::catch_all_options_routes())
|
.mount("/", rocket_cors::catch_all_options_routes())
|
||||||
.mount("/auth", rauth::routes::routes())
|
.mount("/auth/account", rauth::web::account::routes())
|
||||||
|
.mount("/auth/session", rauth::web::session::routes())
|
||||||
.manage(auth)
|
.manage(auth)
|
||||||
.manage(cors.clone())
|
.manage(cors.clone())
|
||||||
.manage(RatelimitState::new())
|
.manage(RatelimitState::new())
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use hive_pubsub::PubSub;
|
use hive_pubsub::PubSub;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
|
||||||
use rocket::serde::json::Value;
|
use rocket::serde::json::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@@ -19,24 +18,24 @@ pub enum WebSocketError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
pub struct BotAuth {
|
pub struct Auth {
|
||||||
pub token: String
|
pub token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum AuthType {
|
pub enum Ping {
|
||||||
User(Session),
|
Binary(Vec<u8>),
|
||||||
Bot(BotAuth)
|
Number(usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ServerboundNotification {
|
pub enum ServerboundNotification {
|
||||||
Authenticate(AuthType),
|
Authenticate(Auth),
|
||||||
BeginTyping { channel: String },
|
BeginTyping { channel: String },
|
||||||
EndTyping { channel: String },
|
EndTyping { channel: String },
|
||||||
Ping { data: Vec<u8> }
|
Ping { data: Ping, responded: Option<()> },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -50,7 +49,7 @@ pub enum RemoveUserField {
|
|||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub enum RemoveChannelField {
|
pub enum RemoveChannelField {
|
||||||
Icon,
|
Icon,
|
||||||
Description
|
Description,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
@@ -85,8 +84,9 @@ pub enum ClientboundNotification {
|
|||||||
users: Vec<User>,
|
users: Vec<User>,
|
||||||
servers: Vec<Server>,
|
servers: Vec<Server>,
|
||||||
channels: Vec<Channel>,
|
channels: Vec<Channel>,
|
||||||
members: Vec<Member>
|
members: Vec<Member>,
|
||||||
},
|
},
|
||||||
|
Pong { data: Ping },
|
||||||
|
|
||||||
Message(Message),
|
Message(Message),
|
||||||
MessageUpdate {
|
MessageUpdate {
|
||||||
@@ -159,11 +159,11 @@ pub enum ClientboundNotification {
|
|||||||
role_id: String,
|
role_id: String,
|
||||||
data: Value,
|
data: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
clear: Option<RemoveRoleField>
|
clear: Option<RemoveRoleField>,
|
||||||
},
|
},
|
||||||
ServerRoleDelete {
|
ServerRoleDelete {
|
||||||
id: String,
|
id: String,
|
||||||
role_id: String
|
role_id: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
UserUpdate {
|
UserUpdate {
|
||||||
@@ -222,8 +222,7 @@ pub async fn prehandle_hook(notification: &ClientboundNotification) -> Result<()
|
|||||||
subscribe_if_exists(recipient.clone(), channel_id.to_string()).ok();
|
subscribe_if_exists(recipient.clone(), channel_id.to_string()).ok();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Channel::TextChannel { server, .. }
|
Channel::TextChannel { server, .. } | Channel::VoiceChannel { server, .. } => {
|
||||||
| Channel::VoiceChannel { server, .. } => {
|
|
||||||
// ! FIXME: write a better algorithm?
|
// ! FIXME: write a better algorithm?
|
||||||
let members = Server::fetch_member_ids(server).await?;
|
let members = Server::fetch_member_ids(server).await?;
|
||||||
for member in members {
|
for member in members {
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::{AuthType, BotAuth};
|
use crate::notifications::events::Ping;
|
||||||
use crate::util::variables::WS_HOST;
|
use crate::util::variables::WS_HOST;
|
||||||
|
|
||||||
use super::subscriptions;
|
use super::subscriptions;
|
||||||
|
|
||||||
use async_std::net::{TcpListener, TcpStream};
|
use async_std::net::{TcpListener, TcpStream};
|
||||||
use async_std::task;
|
use async_std::task;
|
||||||
use async_tungstenite::tungstenite::{Message, handshake::server};
|
use async_tungstenite::tungstenite::{handshake::server, Message};
|
||||||
use futures::channel::{oneshot, mpsc::{unbounded, UnboundedSender}};
|
use futures::channel::{
|
||||||
|
mpsc::{unbounded, UnboundedSender},
|
||||||
|
oneshot,
|
||||||
|
};
|
||||||
use futures::stream::TryStreamExt;
|
use futures::stream::TryStreamExt;
|
||||||
use futures::{pin_mut, prelude::*};
|
use futures::{pin_mut, prelude::*};
|
||||||
use hive_pubsub::PubSub;
|
use hive_pubsub::PubSub;
|
||||||
use log::{debug, info};
|
use log::{debug, info};
|
||||||
use many_to_many::ManyToMany;
|
use many_to_many::ManyToMany;
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::{
|
use rauth::entities::{Model, Session};
|
||||||
auth::{Auth},
|
|
||||||
options::Options,
|
|
||||||
};
|
|
||||||
use rmp_serde;
|
use rmp_serde;
|
||||||
use url::Url;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use std::sync::{Arc, Mutex, RwLock};
|
use std::sync::{Arc, Mutex, RwLock};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
events::{ClientboundNotification, ServerboundNotification, WebSocketError},
|
events::{ClientboundNotification, ServerboundNotification, WebSocketError},
|
||||||
@@ -51,28 +51,43 @@ pub async fn launch_server() {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum MSGFormat {
|
enum MSGFormat {
|
||||||
JSON,
|
JSON,
|
||||||
MSGPACK
|
MSGPACK,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HeaderCallback {
|
struct HeaderCallback {
|
||||||
sender: oneshot::Sender<MSGFormat>
|
sender: oneshot::Sender<MSGFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl server::Callback for HeaderCallback {
|
impl server::Callback for HeaderCallback {
|
||||||
fn on_request(self, request: &server::Request, response: server::Response) -> Result<server::Response, server::ErrorResponse> {
|
fn on_request(
|
||||||
|
self,
|
||||||
|
request: &server::Request,
|
||||||
|
response: server::Response,
|
||||||
|
) -> Result<server::Response, server::ErrorResponse> {
|
||||||
// we dont get some of the data sometimes so im generating a fake url with the only data we actually need
|
// we dont get some of the data sometimes so im generating a fake url with the only data we actually need
|
||||||
let url = format!("ws://example.com?{}", request.uri().query().unwrap_or("?format=json"));
|
let url = format!(
|
||||||
let mut query: HashMap<_, _> = url.parse::<Url>().unwrap().query_pairs().into_owned().collect(); // should be safe to use unwrap here as we just made the url ourself
|
"ws://example.com?{}",
|
||||||
|
request.uri().query().unwrap_or("?format=json")
|
||||||
|
);
|
||||||
|
let mut query: HashMap<_, _> = url
|
||||||
|
.parse::<Url>()
|
||||||
|
.unwrap()
|
||||||
|
.query_pairs()
|
||||||
|
.into_owned()
|
||||||
|
.collect(); // should be safe to use unwrap here as we just made the url ourself
|
||||||
let format_query: Option<String> = query.remove("format");
|
let format_query: Option<String> = query.remove("format");
|
||||||
|
|
||||||
let format = match format_query.as_deref().unwrap_or("json") {
|
let format = match format_query.as_deref().unwrap_or("json") {
|
||||||
"msgpack" => MSGFormat::MSGPACK,
|
"msgpack" => MSGFormat::MSGPACK,
|
||||||
"json" => MSGFormat::JSON,
|
"json" => MSGFormat::JSON,
|
||||||
_ => panic!("unknown format") // TODO: not use panic
|
_ => MSGFormat::JSON, // Fallback to JSON.
|
||||||
};
|
};
|
||||||
|
|
||||||
self.sender.send(format).unwrap(); // TODO: not use unwrap
|
if self.sender.send(format).is_ok() {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
|
} else {
|
||||||
|
Err(server::ErrorResponse::new(None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,12 +96,13 @@ async fn accept(stream: TcpStream) {
|
|||||||
.peer_addr()
|
.peer_addr()
|
||||||
.expect("Connected streams should have a peer address.");
|
.expect("Connected streams should have a peer address.");
|
||||||
let (sender, receiver) = oneshot::channel::<MSGFormat>();
|
let (sender, receiver) = oneshot::channel::<MSGFormat>();
|
||||||
|
|
||||||
let ws_stream = async_tungstenite::accept_hdr_async_with_config(stream, HeaderCallback { sender }, None)
|
|
||||||
.await
|
|
||||||
.expect("Error during websocket handshake.");
|
|
||||||
|
|
||||||
let msg_format = receiver.await.unwrap(); // TODO: not use unwrap
|
let ws_stream =
|
||||||
|
async_tungstenite::accept_hdr_async_with_config(stream, HeaderCallback { sender }, None)
|
||||||
|
.await
|
||||||
|
.expect("Error during websocket handshake.");
|
||||||
|
|
||||||
|
let msg_format = receiver.await.unwrap(); // TODO: not use unwrap
|
||||||
|
|
||||||
info!("User established WebSocket connection from {}.", &addr);
|
info!("User established WebSocket connection from {}.", &addr);
|
||||||
|
|
||||||
@@ -98,12 +114,12 @@ async fn accept(stream: TcpStream) {
|
|||||||
let res = match msg_format {
|
let res = match msg_format {
|
||||||
MSGFormat::JSON => match serde_json::to_string(¬ification) {
|
MSGFormat::JSON => match serde_json::to_string(¬ification) {
|
||||||
Ok(s) => Message::Text(s),
|
Ok(s) => Message::Text(s),
|
||||||
Err(_) => return
|
Err(_) => return,
|
||||||
}
|
},
|
||||||
MSGFormat::MSGPACK => match rmp_serde::to_vec(¬ification) {
|
MSGFormat::MSGPACK => match rmp_serde::to_vec(¬ification) {
|
||||||
Ok(v) => Message::Binary(v),
|
Ok(v) => Message::Binary(v),
|
||||||
Err(_) => return
|
Err(_) => return,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(_) = tx.unbounded_send(res) {
|
if let Err(_) = tx.unbounded_send(res) {
|
||||||
@@ -118,21 +134,27 @@ async fn accept(stream: TcpStream) {
|
|||||||
let mutex = mutex_generator();
|
let mutex = mutex_generator();
|
||||||
|
|
||||||
let maybe_decoded = match msg {
|
let maybe_decoded = match msg {
|
||||||
Message::Text(text) => serde_json::from_str::<ServerboundNotification>(&text).map_err(|e| e.to_string()),
|
Message::Text(text) => {
|
||||||
Message::Binary(vec) => rmp_serde::decode::from_read::<&[u8], ServerboundNotification>(vec.as_slice()).map_err(|e| e.to_string()),
|
serde_json::from_str::<ServerboundNotification>(&text).map_err(|e| e.to_string())
|
||||||
Message::Ping(vec) => Ok(ServerboundNotification::Ping { data: vec }),
|
}
|
||||||
_ => return Ok(())
|
Message::Binary(vec) => {
|
||||||
|
rmp_serde::decode::from_read::<&[u8], ServerboundNotification>(vec.as_slice())
|
||||||
|
.map_err(|e| e.to_string())
|
||||||
|
}
|
||||||
|
Message::Ping(vec) => Ok(ServerboundNotification::Ping { data: Ping::Binary(vec), responded: Some(()) }),
|
||||||
|
_ => return Ok(()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let notification = match maybe_decoded {
|
let notification = match maybe_decoded {
|
||||||
Err(why) => {
|
Err(why) => {
|
||||||
send(ClientboundNotification::Error(
|
send(ClientboundNotification::Error(
|
||||||
WebSocketError::MalformedData {
|
WebSocketError::MalformedData {
|
||||||
msg: why.to_string()
|
msg: why.to_string(),
|
||||||
}));
|
},
|
||||||
return Ok(())
|
));
|
||||||
},
|
return Ok(());
|
||||||
Ok(n) => n
|
}
|
||||||
|
Ok(n) => n,
|
||||||
};
|
};
|
||||||
|
|
||||||
match notification {
|
match notification {
|
||||||
@@ -147,34 +169,20 @@ async fn accept(stream: TcpStream) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(id) = match auth {
|
let id = if let Ok(Some(session)) =
|
||||||
AuthType::User(new_session) => {
|
Session::find_one(&get_db(), doc! { "token": &auth.token }, None).await
|
||||||
if let Ok(validated_session) =
|
{
|
||||||
Auth::new(get_collection("accounts"), Options::new())
|
Some(session.user_id)
|
||||||
.verify_session(new_session)
|
} else if let Ok(Some(bot)) = get_collection("bots")
|
||||||
.await
|
.find_one(doc! { "token": auth.token }, None)
|
||||||
{
|
.await
|
||||||
Some(validated_session.user_id.clone())
|
{
|
||||||
} else {
|
Some(bot.get_str("_id").unwrap().to_string())
|
||||||
None
|
} else {
|
||||||
}
|
None
|
||||||
}
|
};
|
||||||
AuthType::Bot(BotAuth { token }) => {
|
|
||||||
if let Ok(doc) = get_collection("bots")
|
if let Some(id) = id {
|
||||||
.find_one(
|
|
||||||
doc! { "token": token },
|
|
||||||
None
|
|
||||||
).await {
|
|
||||||
if let Some(doc) = doc {
|
|
||||||
Some(doc.get_str("_id").unwrap().to_string())
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} {
|
|
||||||
if let Ok(user) = (Ref { id: id.clone() }).fetch_user().await {
|
if let Ok(user) = (Ref { id: id.clone() }).fetch_user().await {
|
||||||
let is_invisible = if let Some(status) = &user.status {
|
let is_invisible = if let Some(status) = &user.status {
|
||||||
if let Some(presence) = &status.presence {
|
if let Some(presence) = &status.presence {
|
||||||
@@ -229,7 +237,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
data: json!({
|
data: json!({
|
||||||
"online": true
|
"online": true
|
||||||
}),
|
}),
|
||||||
clear: None
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish_as_user(id);
|
.publish_as_user(id);
|
||||||
}
|
}
|
||||||
@@ -297,8 +305,12 @@ async fn accept(stream: TcpStream) {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ServerboundNotification::Ping { data } => {
|
ServerboundNotification::Ping { data, responded } => {
|
||||||
info!("Ping received from User {}. Payload: {:?}", &addr, data);
|
debug!("Ping received from connection {}. Payload: {:?}", &addr, data);
|
||||||
|
|
||||||
|
if responded.is_none() {
|
||||||
|
send(ClientboundNotification::Pong { data });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -329,7 +341,7 @@ async fn accept(stream: TcpStream) {
|
|||||||
data: json!({
|
data: json!({
|
||||||
"online": false
|
"online": false
|
||||||
}),
|
}),
|
||||||
clear: None
|
clear: None,
|
||||||
}
|
}
|
||||||
.publish_as_user(id);
|
.publish_as_user(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ pub struct Data {
|
|||||||
|
|
||||||
#[post("/create", data = "<info>")]
|
#[post("/create", data = "<info>")]
|
||||||
pub async fn create_bot(user: User, info: Json<Data>) -> Result<Value> {
|
pub async fn create_bot(user: User, info: Json<Data>) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let info = info.into_inner();
|
let info = info.into_inner();
|
||||||
info.validate()
|
info.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ use mongodb::bson::doc;
|
|||||||
|
|
||||||
#[delete("/<target>")]
|
#[delete("/<target>")]
|
||||||
pub async fn delete_bot(user: User, target: Ref) -> Result<EmptyResponse> {
|
pub async fn delete_bot(user: User, target: Ref) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let bot = target.fetch_bot().await?;
|
let bot = target.fetch_bot().await?;
|
||||||
if bot.owner != user.id {
|
if bot.owner != user.id {
|
||||||
return Err(Error::MissingPermission);
|
return Err(Error::MissingPermission);
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ pub struct Data {
|
|||||||
|
|
||||||
#[patch("/<target>", data = "<data>")]
|
#[patch("/<target>", data = "<data>")]
|
||||||
pub async fn edit_bot(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
pub async fn edit_bot(user: User, target: Ref, data: Json<Data>) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ use serde_json::Value;
|
|||||||
|
|
||||||
#[get("/<target>")]
|
#[get("/<target>")]
|
||||||
pub async fn fetch_bot(user: User, target: Ref) -> Result<Value> {
|
pub async fn fetch_bot(user: User, target: Ref) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let bot = target.fetch_bot().await?;
|
let bot = target.fetch_bot().await?;
|
||||||
|
|
||||||
if !bot.public {
|
if !bot.public {
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ use serde_json::Value;
|
|||||||
|
|
||||||
#[get("/@me")]
|
#[get("/@me")]
|
||||||
pub async fn fetch_owned_bots(user: User) -> Result<Value> {
|
pub async fn fetch_owned_bots(user: User) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let bots = get_collection("bots")
|
let bots = get_collection("bots")
|
||||||
.find(
|
.find(
|
||||||
doc! {
|
doc! {
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ use serde_json::Value;
|
|||||||
|
|
||||||
#[get("/<target>/invite")]
|
#[get("/<target>/invite")]
|
||||||
pub async fn fetch_public_bot(user: User, target: Ref) -> Result<Value> {
|
pub async fn fetch_public_bot(user: User, target: Ref) -> Result<Value> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let bot = target.fetch_bot().await?;
|
let bot = target.fetch_bot().await?;
|
||||||
|
|
||||||
if !bot.public {
|
if !bot.public {
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ pub enum Destination {
|
|||||||
|
|
||||||
#[post("/<target>/invite", data = "<dest>")]
|
#[post("/<target>/invite", data = "<dest>")]
|
||||||
pub async fn invite_bot(user: User, target: Ref, dest: Json<Destination>) -> Result<EmptyResponse> {
|
pub async fn invite_bot(user: User, target: Ref, dest: Json<Destination>) -> Result<EmptyResponse> {
|
||||||
|
if user.bot.is_some() {
|
||||||
|
return Err(Error::IsBot)
|
||||||
|
}
|
||||||
|
|
||||||
let bot = target.fetch_bot().await?;
|
let bot = target.fetch_bot().await?;
|
||||||
|
|
||||||
if !bot.public {
|
if !bot.public {
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<Value> {
|
|||||||
owner: user.id,
|
owner: user.id,
|
||||||
recipients: set.into_iter().collect::<Vec<String>>(),
|
recipients: set.into_iter().collect::<Vec<String>>(),
|
||||||
icon: None,
|
icon: None,
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
permissions: None,
|
permissions: None,
|
||||||
nsfw: info.nsfw.unwrap_or_default()
|
nsfw: info.nsfw.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -30,7 +30,8 @@ pub struct Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref RE_ULID: Regex = Regex::new(r"<@([0123456789ABCDEFGHJKMNPQRSTVWXYZ]{26})>").unwrap();
|
// ignoring I L O and U is intentional
|
||||||
|
static ref RE_ULID: Regex = Regex::new(r"<@([0-9A-HJKMNP-TV-Z]{26})>").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<target>/messages", data = "<message>")]
|
#[post("/<target>/messages", data = "<message>")]
|
||||||
@@ -80,9 +81,10 @@ pub async fn message_send(_r: Ratelimiter, user: User, target: Ref, message: Jso
|
|||||||
let id = Ulid::new().to_string();
|
let id = Ulid::new().to_string();
|
||||||
|
|
||||||
let mut mentions = HashSet::new();
|
let mut mentions = HashSet::new();
|
||||||
if let Some(captures) = RE_ULID.captures_iter(&message.content).next() {
|
for capture in RE_ULID.captures_iter(&message.content) {
|
||||||
// ! FIXME: in the future, verify in group so we can send out push
|
if let Some(mention) = capture.get(1) {
|
||||||
mentions.insert(captures[1].to_string());
|
mentions.insert(mention.as_str().to_string());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut replies = HashSet::new();
|
let mut replies = HashSet::new();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::MAX_SERVER_COUNT;
|
||||||
|
|
||||||
use rocket::serde::json::Value;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
@@ -8,6 +9,12 @@ pub async fn req(user: User, target: Ref) -> Result<Value> {
|
|||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot)
|
return Err(Error::IsBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !User::can_acquire_server(&user.id).await? {
|
||||||
|
Err(Error::TooManyServers {
|
||||||
|
max: *MAX_SERVER_COUNT,
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
let target = target.fetch_invite().await?;
|
let target = target.fetch_invite().await?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
pub use rocket::response::Redirect;
|
|
||||||
pub use rocket::http::Status;
|
pub use rocket::http::Status;
|
||||||
|
pub use rocket::response::Redirect;
|
||||||
use rocket::{Build, Rocket};
|
use rocket::{Build, Rocket};
|
||||||
|
|
||||||
|
mod bots;
|
||||||
mod channels;
|
mod channels;
|
||||||
mod invites;
|
mod invites;
|
||||||
mod onboard;
|
mod onboard;
|
||||||
@@ -10,7 +11,6 @@ mod root;
|
|||||||
mod servers;
|
mod servers;
|
||||||
mod sync;
|
mod sync;
|
||||||
mod users;
|
mod users;
|
||||||
mod bots;
|
|
||||||
|
|
||||||
pub fn mount(rocket: Rocket<Build>) -> Rocket<Build> {
|
pub fn mount(rocket: Rocket<Build>) -> Rocket<Build> {
|
||||||
rocket
|
rocket
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::database::*;
|
|||||||
use crate::util::result::{Error, Result, EmptyResponse};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rauth::entities::Session;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
|
|
||||||
use rauth::auth::Session;
|
use rauth::entities::Session;
|
||||||
use rocket::serde::json::Value;
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
#[get("/hello")]
|
#[get("/hello")]
|
||||||
|
|||||||
@@ -1,37 +1,19 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{EmptyResponse, Error, Result};
|
use crate::util::result::{EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::{doc, to_document};
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rauth::entities::{Model, Session, WebPushSubscription};
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
pub struct Subscription {
|
|
||||||
endpoint: String,
|
|
||||||
p256dh: String,
|
|
||||||
auth: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/subscribe", data = "<data>")]
|
#[post("/subscribe", data = "<data>")]
|
||||||
pub async fn req(session: Session, data: Json<Subscription>) -> Result<EmptyResponse> {
|
pub async fn req(mut session: Session, data: Json<WebPushSubscription>) -> Result<EmptyResponse> {
|
||||||
let data = data.into_inner();
|
session.subscription = Some(data.into_inner());
|
||||||
get_collection("accounts")
|
session
|
||||||
.update_one(
|
.save(&get_db(), None)
|
||||||
doc! {
|
|
||||||
"_id": session.user_id,
|
|
||||||
"sessions.id": session.id.unwrap()
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$set": {
|
|
||||||
"sessions.$.subscription": to_document(&data)
|
|
||||||
.map_err(|_| Error::DatabaseError { operation: "to_document", with: "subscription" })?
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|_| Error::DatabaseError { operation: "update_one", with: "account" })?;
|
.map(|_| EmptyResponse)
|
||||||
|
.map_err(|_| Error::DatabaseError {
|
||||||
Ok(EmptyResponse {})
|
operation: "save",
|
||||||
|
with: "session",
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,18 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result, EmptyResponse};
|
use crate::util::result::{EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::Session;
|
use rauth::entities::{Model, Session};
|
||||||
|
|
||||||
#[post("/unsubscribe")]
|
#[post("/unsubscribe")]
|
||||||
pub async fn req(session: Session) -> Result<EmptyResponse> {
|
pub async fn req(mut session: Session) -> Result<EmptyResponse> {
|
||||||
get_collection("accounts")
|
session.subscription = None;
|
||||||
.update_one(
|
session
|
||||||
doc! {
|
.save(&get_db(), None)
|
||||||
"_id": session.user_id,
|
|
||||||
"sessions.id": session.id.unwrap()
|
|
||||||
},
|
|
||||||
doc! {
|
|
||||||
"$unset": {
|
|
||||||
"sessions.$.subscription": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
.await
|
||||||
|
.map(|_| EmptyResponse)
|
||||||
.map_err(|_| Error::DatabaseError {
|
.map_err(|_| Error::DatabaseError {
|
||||||
operation: "to_document",
|
operation: "save",
|
||||||
with: "subscription",
|
with: "session",
|
||||||
})?;
|
})
|
||||||
|
|
||||||
Ok(EmptyResponse {})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ pub async fn req(user: User, target: Ref, info: Json<Data>) -> Result<Value> {
|
|||||||
name: info.name,
|
name: info.name,
|
||||||
description: info.description,
|
description: info.description,
|
||||||
icon: None,
|
icon: None,
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
|
|
||||||
default_permissions: None,
|
default_permissions: None,
|
||||||
role_permissions: HashMap::new(),
|
role_permissions: HashMap::new(),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use std::collections::HashMap;
|
|||||||
|
|
||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::util::result::{Error, Result};
|
use crate::util::result::{Error, Result};
|
||||||
|
use crate::util::variables::MAX_SERVER_COUNT;
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rocket::serde::json::{Json, Value};
|
use rocket::serde::json::{Json, Value};
|
||||||
@@ -27,6 +28,12 @@ pub async fn req(user: User, info: Json<Data>) -> Result<Value> {
|
|||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot)
|
return Err(Error::IsBot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !User::can_acquire_server(&user.id).await? {
|
||||||
|
Err(Error::TooManyServers {
|
||||||
|
max: *MAX_SERVER_COUNT,
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
|
||||||
let info = info.into_inner();
|
let info = info.into_inner();
|
||||||
info.validate()
|
info.validate()
|
||||||
@@ -77,6 +84,8 @@ pub async fn req(user: User, info: Json<Data>) -> Result<Value> {
|
|||||||
|
|
||||||
icon: None,
|
icon: None,
|
||||||
banner: None,
|
banner: None,
|
||||||
|
|
||||||
|
flags: None,
|
||||||
nsfw: info.nsfw.unwrap_or_default()
|
nsfw: info.nsfw.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,7 +97,7 @@ pub async fn req(user: User, info: Json<Data>) -> Result<Value> {
|
|||||||
description: None,
|
description: None,
|
||||||
|
|
||||||
icon: None,
|
icon: None,
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
|
|
||||||
default_permissions: None,
|
default_permissions: None,
|
||||||
role_permissions: HashMap::new(),
|
role_permissions: HashMap::new(),
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ pub struct Options {
|
|||||||
#[post("/settings/fetch", data = "<options>")]
|
#[post("/settings/fetch", data = "<options>")]
|
||||||
pub async fn req(user: User, options: Json<Options>) -> Result<Value> {
|
pub async fn req(user: User, options: Json<Options>) -> Result<Value> {
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot)
|
return Err(Error::IsBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
let options = options.into_inner();
|
let options = options.into_inner();
|
||||||
let mut projection = doc! {
|
let mut projection = doc! {
|
||||||
"_id": 0,
|
"_id": 0,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ use rocket::serde::json::Value;
|
|||||||
#[get("/unreads")]
|
#[get("/unreads")]
|
||||||
pub async fn req(user: User) -> Result<Value> {
|
pub async fn req(user: User) -> Result<Value> {
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot)
|
return Err(Error::IsBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(json!(User::fetch_unreads(&user.id).await?))
|
Ok(json!(User::fetch_unreads(&user.id).await?))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::database::*;
|
use crate::database::*;
|
||||||
use crate::notifications::events::ClientboundNotification;
|
use crate::notifications::events::ClientboundNotification;
|
||||||
use crate::util::result::{Error, Result, EmptyResponse};
|
use crate::util::result::{EmptyResponse, Error, Result};
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::prelude::*;
|
||||||
use mongodb::bson::{doc, to_bson};
|
use mongodb::bson::{doc, to_bson};
|
||||||
@@ -20,7 +20,7 @@ pub struct Options {
|
|||||||
#[post("/settings/set?<options..>", data = "<data>")]
|
#[post("/settings/set?<options..>", data = "<data>")]
|
||||||
pub async fn req(user: User, data: Json<Data>, options: Options) -> Result<EmptyResponse> {
|
pub async fn req(user: User, data: Json<Data>, options: Options) -> Result<EmptyResponse> {
|
||||||
if user.bot.is_some() {
|
if user.bot.is_some() {
|
||||||
return Err(Error::IsBot)
|
return Err(Error::IsBot);
|
||||||
}
|
}
|
||||||
|
|
||||||
let data = data.into_inner();
|
let data = data.into_inner();
|
||||||
|
|||||||
@@ -3,9 +3,8 @@ use crate::notifications::events::ClientboundNotification;
|
|||||||
use crate::util::result::{Error, Result, EmptyResponse};
|
use crate::util::result::{Error, Result, EmptyResponse};
|
||||||
|
|
||||||
use mongodb::bson::doc;
|
use mongodb::bson::doc;
|
||||||
use rauth::auth::{Auth, Session};
|
use rauth::entities::Account;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rocket::State;
|
|
||||||
use rocket::serde::json::Json;
|
use rocket::serde::json::Json;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@@ -26,8 +25,7 @@ pub struct Data {
|
|||||||
|
|
||||||
#[patch("/<_ignore_id>/username", data = "<data>")]
|
#[patch("/<_ignore_id>/username", data = "<data>")]
|
||||||
pub async fn req(
|
pub async fn req(
|
||||||
auth: &State<Auth>,
|
account: Account,
|
||||||
session: Session,
|
|
||||||
user: User,
|
user: User,
|
||||||
data: Json<Data>,
|
data: Json<Data>,
|
||||||
_ignore_id: String,
|
_ignore_id: String,
|
||||||
@@ -39,8 +37,7 @@ pub async fn req(
|
|||||||
data.validate()
|
data.validate()
|
||||||
.map_err(|error| Error::FailedValidation { error })?;
|
.map_err(|error| Error::FailedValidation { error })?;
|
||||||
|
|
||||||
auth.verify_password(&session, data.password.clone())
|
account.verify_password(&data.password)
|
||||||
.await
|
|
||||||
.map_err(|_| Error::InvalidCredentials)?;
|
.map_err(|_| Error::InvalidCredentials)?;
|
||||||
|
|
||||||
let mut set = doc! {};
|
let mut set = doc! {};
|
||||||
|
|||||||
9
src/routes/users/fetch_self.rs
Executable file
9
src/routes/users/fetch_self.rs
Executable file
@@ -0,0 +1,9 @@
|
|||||||
|
use crate::database::*;
|
||||||
|
use crate::util::result::{Result};
|
||||||
|
|
||||||
|
use rocket::serde::json::Value;
|
||||||
|
|
||||||
|
#[get("/@me")]
|
||||||
|
pub async fn req(user: User) -> Result<Value> {
|
||||||
|
Ok(json!(user))
|
||||||
|
}
|
||||||
@@ -14,10 +14,12 @@ mod get_default_avatar;
|
|||||||
mod open_dm;
|
mod open_dm;
|
||||||
mod remove_friend;
|
mod remove_friend;
|
||||||
mod unblock_user;
|
mod unblock_user;
|
||||||
|
mod fetch_self;
|
||||||
|
|
||||||
pub fn routes() -> Vec<Route> {
|
pub fn routes() -> Vec<Route> {
|
||||||
routes![
|
routes![
|
||||||
// User Information
|
// User Information
|
||||||
|
fetch_self::req,
|
||||||
fetch_user::req,
|
fetch_user::req,
|
||||||
edit_user::req,
|
edit_user::req,
|
||||||
change_username::req,
|
change_username::req,
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ pub async fn req(user: User, target: Ref) -> Result<Value> {
|
|||||||
id,
|
id,
|
||||||
active: false,
|
active: false,
|
||||||
recipients: vec![user.id, target.id],
|
recipients: vec![user.id, target.id],
|
||||||
last_message: None,
|
last_message_id: None,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ pub enum Error {
|
|||||||
UnknownServer,
|
UnknownServer,
|
||||||
InvalidRole,
|
InvalidRole,
|
||||||
Banned,
|
Banned,
|
||||||
|
TooManyServers{
|
||||||
|
max: usize,
|
||||||
|
},
|
||||||
|
|
||||||
// ? Bot related errors.
|
// ? Bot related errors.
|
||||||
ReachedMaximumBots,
|
ReachedMaximumBots,
|
||||||
@@ -114,6 +117,7 @@ impl<'r> Responder<'r, 'static> for Error {
|
|||||||
Error::UnknownServer => Status::NotFound,
|
Error::UnknownServer => Status::NotFound,
|
||||||
Error::InvalidRole => Status::NotFound,
|
Error::InvalidRole => Status::NotFound,
|
||||||
Error::Banned => Status::Forbidden,
|
Error::Banned => Status::Forbidden,
|
||||||
|
Error::TooManyServers { .. } => Status::Forbidden,
|
||||||
|
|
||||||
Error::ReachedMaximumBots => Status::BadRequest,
|
Error::ReachedMaximumBots => Status::BadRequest,
|
||||||
Error::IsBot => Status::BadRequest,
|
Error::IsBot => Status::BadRequest,
|
||||||
|
|||||||
@@ -64,6 +64,10 @@ lazy_static! {
|
|||||||
env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap();
|
env::var("REVOLT_MAX_GROUP_SIZE").unwrap_or_else(|_| "50".to_string()).parse().unwrap();
|
||||||
pub static ref MAX_BOT_COUNT: usize =
|
pub static ref MAX_BOT_COUNT: usize =
|
||||||
env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
env::var("REVOLT_MAX_BOT_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
||||||
|
pub static ref MAX_EMBED_COUNT: usize =
|
||||||
|
env::var("REVOLT_MAX_EMBED_COUNT").unwrap_or_else(|_| "5".to_string()).parse().unwrap();
|
||||||
|
pub static ref MAX_SERVER_COUNT: usize =
|
||||||
|
env::var("REVOLT_MAX_SERVER_COUNT").unwrap_or_else(|_| "100".to_string()).parse().unwrap();
|
||||||
pub static ref EARLY_ADOPTER_BADGE: i64 =
|
pub static ref EARLY_ADOPTER_BADGE: i64 =
|
||||||
env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
|
env::var("REVOLT_EARLY_ADOPTER_BADGE").unwrap_or_else(|_| "0".to_string()).parse().unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
pub const VERSION: &str = "0.5.2-alpha.1";
|
pub const VERSION: &str = "0.5.3-alpha.1";
|
||||||
|
|||||||
Reference in New Issue
Block a user