Merge branch 'master' into webhooks

This commit is contained in:
Angelo Kontaxis
2023-02-23 17:26:59 +00:00
committed by GitHub
49 changed files with 558 additions and 539 deletions

2
.github/FUNDING.yml vendored
View File

@@ -1,2 +0,0 @@
ko_fi: insertish
custom: https://insrt.uk/donate

View File

@@ -15,10 +15,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install latest nightly
- name: Install latest stable
uses: actions-rs/toolchain@v1
with:
toolchain: nightly
toolchain: stable
override: true
components: rustfmt, clippy
@@ -59,6 +59,7 @@ jobs:
with:
repository: revoltchat/api
path: api
token: ${{ secrets.PAT }}
- name: Download OpenAPI specification
if: github.event_name != 'pull_request'
@@ -70,6 +71,6 @@ jobs:
with:
cwd: "api"
add: "*.json"
author_name: Revolt CI
author_email: revolt-ci@users.noreply.github.com
message: "chore: generate OpenAPI specification"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

108
Cargo.lock generated
View File

@@ -330,6 +330,40 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "authifier"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a84411393a4326726ead660991ce8047f1865fa89900fb6c18e1e6c513cf1f9b"
dependencies = [
"async-std",
"async-trait",
"base32",
"bson",
"chrono",
"futures",
"handlebars",
"iso8601-timestamp",
"lazy_static",
"lettre",
"log",
"mongodb",
"nanoid",
"rand 0.8.5",
"regex",
"reqwest",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"rust-argon2",
"schemars",
"serde",
"serde_json",
"totp-lite",
"ulid 0.5.0",
"validator 0.15.0",
]
[[package]]
name = "autocfg"
version = "0.1.8"
@@ -2579,39 +2613,6 @@ dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rauth"
version = "1.0.4"
source = "git+https://github.com/insertish/rauth?tag=1.0.4#2d3bc59623672e3ff57c49a90c4d3cd20780dbba"
dependencies = [
"async-std",
"async-trait",
"base32",
"bson",
"chrono",
"futures",
"handlebars",
"iso8601-timestamp",
"lazy_static",
"lettre",
"log",
"mongodb",
"nanoid",
"rand 0.8.5",
"regex",
"reqwest",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"rust-argon2",
"schemars",
"serde",
"serde_json",
"totp-lite",
"ulid 0.5.0",
"validator 0.15.0",
]
[[package]]
name = "rdrand"
version = "0.4.0"
@@ -2791,7 +2792,7 @@ dependencies = [
[[package]]
name = "revolt-bonfire"
version = "0.5.6"
version = "0.5.9"
dependencies = [
"async-std",
"async-tungstenite",
@@ -2807,7 +2808,7 @@ dependencies = [
[[package]]
name = "revolt-delta"
version = "0.5.6"
version = "0.5.9"
dependencies = [
"async-channel",
"async-std",
@@ -2834,8 +2835,8 @@ dependencies = [
"revolt-quark",
"revolt_rocket_okapi",
"rocket",
"rocket_authifier",
"rocket_empty",
"rocket_rauth",
"schemars",
"serde",
"serde_json",
@@ -2847,12 +2848,13 @@ dependencies = [
[[package]]
name = "revolt-quark"
version = "0.5.6"
version = "0.5.9"
dependencies = [
"async-lock",
"async-recursion",
"async-std",
"async-trait",
"authifier",
"base64 0.13.0",
"bincode",
"bitfield",
@@ -2874,7 +2876,6 @@ dependencies = [
"once_cell",
"optional_struct",
"pretty_env_logger",
"rauth",
"redis-kiss",
"regex",
"reqwest",
@@ -3010,6 +3011,22 @@ dependencies = [
"yansi",
]
[[package]]
name = "rocket_authifier"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4554a81937341d8c3cd3e43431ac2d23624444dc3a88e7ffd3ff66593779293"
dependencies = [
"authifier",
"iso8601-timestamp",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"rocket_empty",
"schemars",
"serde",
]
[[package]]
name = "rocket_codegen"
version = "0.5.0-rc.2"
@@ -3080,21 +3097,6 @@ dependencies = [
"uncased",
]
[[package]]
name = "rocket_rauth"
version = "1.0.4"
source = "git+https://github.com/insertish/rauth?tag=1.0.4#2d3bc59623672e3ff57c49a90c4d3cd20780dbba"
dependencies = [
"iso8601-timestamp",
"rauth",
"revolt_okapi",
"revolt_rocket_okapi",
"rocket",
"rocket_empty",
"schemars",
"serde",
]
[[package]]
name = "rust-argon2"
version = "1.0.0"

View File

@@ -1,7 +0,0 @@
#!/bin/bash
# Build base image
docker build -t revolt.chat/base:latest -f Dockerfile .
# Build crates
docker build -t revolt.chat/delta:latest -f crates/delta/Dockerfile .
docker build -t revolt.chat/bonfire:latest -f crates/bonfire/Dockerfile .

View File

@@ -1,2 +0,0 @@
ko_fi: insertish
custom: https://insrt.uk/donate

View File

@@ -1,98 +0,0 @@
name: Docker
on:
push:
branches:
- "master"
tags:
- "*"
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_amd64:
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: ghcr.io/revoltchat/bonfire
- 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
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

View File

@@ -1,33 +0,0 @@
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

View File

@@ -1,49 +0,0 @@
name: Add Issue to Board
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $issue:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV

View File

@@ -1,72 +0,0 @@
name: Add PR to Board
on:
pull_request_target:
types: [opened, synchronize, ready_for_review, review_requested]
jobs:
track_pr:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
- name: Add PR to project
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
PR_ID: ${{ github.event.pull_request.node_id }}
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $pr:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $pr}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Set fields
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
gh api graphql -f query='
mutation (
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
) {
set_status: updateProjectNextItemField(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: $status_value
}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-bonfire"
version = "0.5.6"
version = "0.5.9"
license = "AGPL-3.0-or-later"
edition = "2021"

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-delta"
version = "0.5.6"
version = "0.5.9"
license = "AGPL-3.0-or-later"
authors = ["Paul Makles <paulmakles@gmail.com>"]
edition = "2018"
@@ -52,7 +52,7 @@ mobc-redis = { version = "0.7.0", default-features = false, features = ["async-s
# web
rocket = { version = "0.5.0-rc.2", default-features = false, features = ["json"] }
rocket_empty = { version = "0.1.1", features = ["schema"] }
rocket_rauth = { git = "https://github.com/insertish/rauth", tag = "1.0.4" }
rocket_authifier = { version = "1.0.7" }
# spec generation
schemars = "0.8.8"

View File

@@ -11,8 +11,8 @@ pub mod routes;
pub mod util;
use async_std::channel::unbounded;
use revolt_quark::authifier::{Authifier, AuthifierEvent};
use revolt_quark::events::client::EventV1;
use revolt_quark::rauth::{RAuth, RAuthEvent};
use revolt_quark::DatabaseInfo;
use rocket::data::ToByteUnit;
@@ -28,25 +28,25 @@ async fn rocket() -> _ {
let db = DatabaseInfo::Auto.connect().await.unwrap();
db.migrate_database().await.unwrap();
// Setup rAuth event channel
// Setup Authifier event channel
let (sender, receiver) = unbounded();
// Setup rAuth
let rauth = RAuth {
// Setup Authifier
let authifier = Authifier {
database: db.clone().into(),
config: revolt_quark::util::rauth::config(),
config: revolt_quark::util::authifier::config(),
event_channel: Some(sender),
};
// Launch a listener for rAuth events
// Launch a listener for Authifier events
async_std::task::spawn(async move {
while let Ok(event) = receiver.recv().await {
match &event {
RAuthEvent::CreateSession { .. } | RAuthEvent::CreateAccount { .. } => {
AuthifierEvent::CreateSession { .. } | AuthifierEvent::CreateAccount { .. } => {
EventV1::Auth(event).global().await
}
RAuthEvent::DeleteSession { user_id, .. }
| RAuthEvent::DeleteAllSessions { user_id, .. } => {
AuthifierEvent::DeleteSession { user_id, .. }
| AuthifierEvent::DeleteAllSessions { user_id, .. } => {
let id = user_id.to_string();
EventV1::Auth(event).private(id).await
}
@@ -66,7 +66,7 @@ async fn rocket() -> _ {
.mount("/", revolt_quark::web::cors::catch_all_options_routes())
.mount("/", revolt_quark::web::ratelimiter::routes())
.mount("/swagger/", revolt_quark::web::swagger::routes())
.manage(rauth)
.manage(authifier)
.manage(db)
.manage(cors.clone())
.attach(revolt_quark::web::ratelimiter::RatelimitFairing)

View File

@@ -10,6 +10,7 @@ mod invites;
mod onboard;
mod push;
mod root;
mod safety;
mod servers;
mod sync;
mod users;
@@ -28,9 +29,10 @@ pub fn mount(mut rocket: Rocket<Build>) -> Rocket<Build> {
"/servers" => servers::routes(),
"/invites" => invites::routes(),
"/custom" => customisation::routes(),
"/auth/account" => rocket_rauth::routes::account::routes(),
"/auth/session" => rocket_rauth::routes::session::routes(),
"/auth/mfa" => rocket_rauth::routes::mfa::routes(),
"/safety" => safety::routes(),
"/auth/account" => rocket_authifier::routes::account::routes(),
"/auth/session" => rocket_authifier::routes::session::routes(),
"/auth/mfa" => rocket_authifier::routes::mfa::routes(),
"/onboard" => onboard::routes(),
"/push" => push::routes(),
"/sync" => sync::routes(),
@@ -108,6 +110,12 @@ fn custom_openapi_spec() -> OpenApi {
"Emojis"
]
},
{
"name": "Platform Moderation",
"tags": [
"User Safety"
]
},
{
"name": "Authentication",
"tags": [

View File

@@ -1,5 +1,7 @@
use crate::util::regex::RE_USERNAME;
use revolt_quark::{models::User, rauth::models::Session, Database, EmptyResponse, Error, Result};
use revolt_quark::{
authifier::models::Session, models::User, Database, EmptyResponse, Error, Result,
};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize};

View File

@@ -1,4 +1,4 @@
use revolt_quark::{models::User, rauth::models::Session};
use revolt_quark::{authifier::models::Session, models::User};
use rocket::serde::json::Json;
use serde::Serialize;

View File

@@ -1,7 +1,7 @@
use revolt_quark::{
rauth::{
authifier::{
models::{Session, WebPushSubscription},
RAuth,
Authifier,
},
EmptyResponse, Error, Result,
};
@@ -16,13 +16,13 @@ use rocket::{serde::json::Json, State};
#[openapi(tag = "Web Push")]
#[post("/subscribe", data = "<data>")]
pub async fn req(
rauth: &State<RAuth>,
authifier: &State<Authifier>,
mut session: Session,
data: Json<WebPushSubscription>,
) -> Result<EmptyResponse> {
session.subscription = Some(data.into_inner());
session
.save(rauth)
.save(authifier)
.await
.map(|_| EmptyResponse)
.map_err(|_| Error::DatabaseError {

View File

@@ -1,5 +1,5 @@
use revolt_quark::{
rauth::{models::Session, RAuth},
authifier::{models::Session, Authifier},
EmptyResponse, Error, Result,
};
@@ -10,10 +10,10 @@ use rocket::State;
/// Remove the Web Push subscription associated with the current session.
#[openapi(tag = "Web Push")]
#[post("/unsubscribe")]
pub async fn req(rauth: &State<RAuth>, mut session: Session) -> Result<EmptyResponse> {
pub async fn req(authifier: &State<Authifier>, mut session: Session) -> Result<EmptyResponse> {
session.subscription = None;
session
.save(rauth)
.save(authifier)
.await
.map(|_| EmptyResponse)
.map_err(|_| Error::DatabaseError {

View File

@@ -119,12 +119,21 @@ pub async fn root() -> Result<Json<RevoltConfig>> {
app: APP_URL.to_string(),
vapid: VAPID_PUBLIC_KEY.to_string(),
build: BuildInformation {
commit_sha: env!("VERGEN_GIT_SHA", "<failed to generate>").to_string(),
commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP", "<failed to generate>")
commit_sha: option_env!("VERGEN_GIT_SHA")
.unwrap_or_else(|| "<failed to generate>")
.to_string(),
commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP")
.unwrap_or_else(|| "<failed to generate>")
.to_string(),
semver: option_env!("VERGEN_GIT_SEMVER")
.unwrap_or_else(|| "<failed to generate>")
.to_string(),
origin_url: option_env!("GIT_ORIGIN_URL")
.unwrap_or_else(|| "<failed to generate>")
.to_string(),
timestamp: option_env!("VERGEN_BUILD_TIMESTAMP")
.unwrap_or_else(|| "<failed to generate>")
.to_string(),
semver: env!("VERGEN_GIT_SEMVER", "<failed to generate>").to_string(),
origin_url: env!("GIT_ORIGIN_URL", "<failed to generate>").to_string(),
timestamp: env!("VERGEN_BUILD_TIMESTAMP", "<failed to generate>").to_string(),
},
}))
}

View File

@@ -0,0 +1,11 @@
use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi;
use rocket::Route;
mod report_content;
pub fn routes() -> (Vec<Route>, OpenApi) {
openapi_get_routes_spec![
// Reports
report_content::report_content
]
}

View File

@@ -0,0 +1,159 @@
use revolt_quark::events::client::EventV1;
use revolt_quark::models::report::ReportedContent;
use revolt_quark::models::snapshot::{Snapshot, SnapshotContent};
use revolt_quark::models::{Report, User};
use revolt_quark::{Db, Error, Result};
use serde::Deserialize;
use ulid::Ulid;
use validator::Validate;
use rocket::serde::json::Json;
/// # Report Data
#[derive(Validate, Deserialize, JsonSchema)]
pub struct DataReportContent {
/// Content being reported
content: ReportedContent,
/// Additional report description
#[validate(length(min = 0, max = 1000))]
#[serde(default)]
additional_context: String,
}
/// # Report Content
///
/// Report a piece of content to the moderation team.
#[openapi(tag = "User Safety")]
#[post("/report", data = "<data>")]
pub async fn report_content(db: &Db, user: User, data: Json<DataReportContent>) -> Result<()> {
let data = data.into_inner();
data.validate()
.map_err(|error| Error::FailedValidation { error })?;
// Bots cannot create reports
if user.bot.is_some() {
return Err(Error::IsBot);
}
// Find the content and create a snapshot of it
// Also retrieve any references to Files
let (content, files): (SnapshotContent, Vec<String>) = match &data.content {
ReportedContent::Message { id, .. } => {
let message = db.fetch_message(id).await?;
// Users cannot report themselves
if message.author == user.id {
return Err(Error::CannotReportYourself);
}
// Collect message attachments
let files = message
.attachments
.as_ref()
.map(|attachments| attachments.iter().map(|x| x.id.to_string()).collect())
.unwrap_or_default();
// Collect prior context
let prior_context = db
.fetch_messages(
&message.channel,
Some(15),
Some(message.id.to_string()),
None,
None,
None,
)
.await?;
// Collect leading context
let leading_context = db
.fetch_messages(
&message.channel,
Some(15),
None,
Some(message.id.to_string()),
None,
None,
)
.await?;
(
SnapshotContent::Message {
message,
prior_context,
leading_context,
},
files,
)
}
ReportedContent::Server { id, .. } => {
let server = db.fetch_server(id).await?;
// Users cannot report their own server
if server.owner == user.id {
return Err(Error::CannotReportYourself);
}
// Collect server's icon and banner
let files = [&server.icon, &server.banner]
.iter()
.filter_map(|x| x.as_ref().map(|x| x.id.to_string()))
.collect();
(SnapshotContent::Server(server), files)
}
ReportedContent::User { id, .. } => {
let reported_user = db.fetch_user(id).await?;
// Users cannot report themselves
if reported_user.id == user.id {
return Err(Error::CannotReportYourself);
}
// Collect user's avatar and profile background
let files = [
reported_user.avatar.as_ref(),
reported_user
.profile
.as_ref()
.and_then(|profile| profile.background.as_ref()),
]
.iter()
.filter_map(|x| x.as_ref().map(|x| x.id.to_string()))
.collect();
(SnapshotContent::User(reported_user), files)
}
};
// Mark all the attachments as reported
for file in files {
db.mark_attachment_as_reported(&file).await?;
}
// Generate an id for the report
let id = Ulid::new().to_string();
// Save a snapshot of the content
let snapshot = Snapshot {
id: Ulid::new().to_string(),
report_id: id.to_string(),
content,
};
db.insert_snapshot(&snapshot).await?;
// Save the report
let report = Report {
id,
author_id: user.id,
content: data.content,
additional_context: data.additional_context,
};
db.insert_report(&report).await?;
EventV1::ReportCreate(report).global().await;
Ok(())
}

View File

@@ -1,5 +1,5 @@
use crate::util::regex::RE_USERNAME;
use revolt_quark::{models::User, rauth::models::Account, Database, Error, Result};
use revolt_quark::{authifier::models::Account, models::User, Database, Error, Result};
use rocket::{serde::json::Json, State};
use serde::{Deserialize, Serialize};
use validator::Validate;

View File

@@ -1,2 +0,0 @@
ko_fi: insertish
custom: https://insrt.uk/donate

View File

@@ -1,33 +0,0 @@
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

View File

@@ -1,49 +0,0 @@
name: Add Issue to Board
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'TODO_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Todo") |.id' project_data.json) >> $GITHUB_ENV
- name: Add issue to project
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $issue:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $issue}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV

View File

@@ -1,72 +0,0 @@
name: Add PR to Board
on:
pull_request_target:
types: [opened, synchronize, ready_for_review, review_requested]
jobs:
track_pr:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
gh api graphql -f query='
query {
organization(login: "revoltchat"){
projectNext(number: 3) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo 'INCOMING_OPTION_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") |.settings | fromjson.options[] | select(.name=="Incoming PRs") |.id' project_data.json) >> $GITHUB_ENV
- name: Add PR to project
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
PR_ID: ${{ github.event.pull_request.node_id }}
run: |
item_id="$( gh api graphql -f query='
mutation($project:ID!, $pr:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $pr}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f pr=$PR_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Set fields
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
run: |
gh api graphql -f query='
mutation (
$project: ID!
$item: ID!
$status_field: ID!
$status_value: String!
) {
set_status: updateProjectNextItemField(input: {
projectId: $project
itemId: $item
fieldId: $status_field
value: $status_value
}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f item=$ITEM_ID -f status_field=$STATUS_FIELD_ID -f status_value=${{ env.INCOMING_OPTION_ID }} --silent

View File

@@ -1,6 +1,6 @@
[package]
name = "revolt-quark"
version = "0.5.6"
version = "0.5.9"
license = "AGPL-3.0-or-later"
edition = "2021"
@@ -16,9 +16,9 @@ rocket_impl = [
"lru",
"dashmap",
"rauth/database-mongodb",
"rauth/rocket_impl",
"rauth/okapi_impl"
"authifier/database-mongodb",
"authifier/rocket_impl",
"authifier/okapi_impl"
]
test = [ "async-std", "mongo", "mongodb/async-std-runtime", "rocket_impl" ]
@@ -84,8 +84,8 @@ rocket = { optional = true, version = "0.5.0-rc.2", default-features = false, fe
rocket_empty = { version = "0.1.1", optional = true, features = [ "schema" ] }
rocket_cors = { optional = true, git = "https://github.com/lawliet89/rocket_cors", rev = "c17e8145baa4790319fdb6a473e465b960f55e7c" }
# rAuth
rauth = { git = "https://github.com/insertish/rauth", tag = "1.0.4", features = [ "async-std-runtime" ] }
# Authifier
authifier = { version = "1.0.7", features = [ "async-std-runtime" ] }
# Sentry
sentry = "0.25.0"

View File

@@ -61,13 +61,13 @@ impl Deref for Database {
}
}
impl From<Database> for rauth::Database {
impl From<Database> for authifier::Database {
fn from(val: Database) -> Self {
match val {
Database::Dummy(_) => rauth::Database::default(),
Database::MongoDb(MongoDb(client)) => {
rauth::Database::MongoDb(rauth::database::MongoDb(client.database("revolt")))
}
Database::Dummy(_) => authifier::Database::default(),
Database::MongoDb(MongoDb(client)) => authifier::Database::MongoDb(
authifier::database::MongoDb(client.database("revolt")),
),
}
}
}

View File

@@ -1,4 +1,4 @@
use rauth::RAuthEvent;
use authifier::AuthifierEvent;
use serde::{Deserialize, Serialize};
use crate::models::channel::{FieldsChannel, PartialChannel};
@@ -7,7 +7,7 @@ use crate::models::message::{AppendMessage, PartialMessage};
use crate::models::server::{FieldsRole, FieldsServer, PartialRole, PartialServer};
use crate::models::server_member::{FieldsMember, MemberCompositeKey, PartialMember};
use crate::models::user::{FieldsUser, PartialUser, RelationshipStatus};
use crate::models::{Channel, Emoji, Member, Message, Server, User, UserSettings, Webhook};
use crate::models::{Channel, Emoji, Member, Message, Server, User, UserSettings, Webhook, Report};
use crate::Error;
/// WebSocket Client Errors
@@ -229,6 +229,9 @@ pub enum EventV1 {
id: String
},
/// New report
ReportCreate(Report),
/// Auth events
Auth(RAuthEvent),
Auth(AuthifierEvent),
}

View File

@@ -29,6 +29,11 @@ pub mod users {
pub mod user_settings;
}
pub mod safety {
pub mod report;
pub mod snapshot;
}
#[derive(Debug, Clone)]
pub struct DummyDb;

View File

@@ -0,0 +1,12 @@
use crate::models::Report;
use crate::{AbstractReport, Result};
use super::super::DummyDb;
#[async_trait]
impl AbstractReport for DummyDb {
async fn insert_report(&self, report: &Report) -> Result<()> {
info!("Insert {:?}", report);
Ok(())
}
}

View File

@@ -0,0 +1,12 @@
use crate::models::Snapshot;
use crate::{AbstractSnapshot, Result};
use super::super::DummyDb;
#[async_trait]
impl AbstractSnapshot for DummyDb {
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
info!("Insert {:?}", snapshot);
Ok(())
}
}

View File

@@ -215,7 +215,7 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
if revision <= 8 {
info!("Running migration [revision 8 / 2021-09-10]: Update to rAuth version 1.");
info!("Running migration [revision 8 / 2021-09-10]: Update to Authifier version 1.");
db.db()
.run_command(
@@ -603,20 +603,20 @@ pub async fn run_migrations(db: &MongoDb, revision: i32) -> i32 {
}
if revision <= 15 {
info!("Running migration [revision 15 / 04-06-2022]: Migrate rAuth to latest version.");
info!("Running migration [revision 15 / 04-06-2022]: Migrate Authifier to latest version.");
let db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db()));
db.run_migration(rauth::Migration::M2022_06_03EnsureUpToSpec)
let db = authifier::Database::MongoDb(authifier::database::MongoDb(db.db()));
db.run_migration(authifier::Migration::M2022_06_03EnsureUpToSpec)
.await
.unwrap();
}
if revision <= 16 {
info!("Running migration [revision 16 / 07-07-2022]: Add `emojis` collection and rAuth migration.");
info!("Running migration [revision 16 / 07-07-2022]: Add `emojis` collection and Authifier migration.");
let rauth_db = rauth::Database::MongoDb(rauth::database::MongoDb(db.db()));
rauth_db
.run_migration(rauth::Migration::M2022_06_09AddIndexForDeletion)
let authifier_db = authifier::Database::MongoDb(authifier::database::MongoDb(db.db()));
authifier_db
.run_migration(authifier::Migration::M2022_06_09AddIndexForDeletion)
.await
.unwrap();

View File

@@ -40,6 +40,11 @@ pub mod users {
pub mod user_settings;
}
pub mod safety {
pub mod report;
pub mod snapshot;
}
#[derive(Debug, Clone)]
pub struct MongoDb(pub mongodb::Client);

View File

@@ -0,0 +1,13 @@
use crate::models::Report;
use crate::{AbstractReport, Result};
use super::super::MongoDb;
static COL: &str = "safety_reports";
#[async_trait]
impl AbstractReport for MongoDb {
async fn insert_report(&self, report: &Report) -> Result<()> {
self.insert_one(COL, report).await.map(|_| ())
}
}

View File

@@ -0,0 +1,13 @@
use crate::models::Snapshot;
use crate::{AbstractSnapshot, Result};
use super::super::MongoDb;
static COL: &str = "safety_snapshots";
#[async_trait]
impl AbstractSnapshot for MongoDb {
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()> {
self.insert_one(COL, snapshot).await.map(|_| ())
}
}

View File

@@ -1,4 +1,4 @@
use rauth::models::Session;
use authifier::models::Session;
use revolt_okapi::openapi3::{SecurityScheme, SecuritySchemeData};
use revolt_rocket_okapi::gen::OpenApiGenerator;
use revolt_rocket_okapi::request::{OpenApiFromRequest, RequestHeaderInput};
@@ -12,7 +12,7 @@ use crate::Database;
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = rauth::Error;
type Error = authifier::Error;
async fn from_request(request: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let user: &Option<User> = request
@@ -43,7 +43,7 @@ impl<'r> FromRequest<'r> for User {
if let Some(user) = user {
Outcome::Success(user.clone())
} else {
Outcome::Failure((Status::Unauthorized, rauth::Error::InvalidSession))
Outcome::Failure((Status::Unauthorized, authifier::Error::InvalidSession))
}
}
}

View File

@@ -17,8 +17,8 @@ extern crate bitfield;
#[macro_use]
extern crate bson;
pub use authifier;
pub use iso8601_timestamp::Timestamp;
pub use rauth;
pub use redis_kiss;
pub mod events;

View File

@@ -28,9 +28,15 @@ mod users {
pub mod user_settings;
}
mod safety {
pub mod report;
pub mod snapshot;
}
pub use admin::*;
pub use channels::*;
pub use media::*;
pub use safety::*;
pub use servers::*;
pub use users::*;
@@ -42,10 +48,12 @@ pub use channel_unread::ChannelUnread;
pub use emoji::Emoji;
pub use message::Message;
pub use migrations::MigrationInfo;
pub use report::Report;
pub use server::Server;
pub use server_ban::ServerBan;
pub use server_member::Member;
pub use simple::SimpleModel;
pub use snapshot::Snapshot;
pub use user::User;
pub use user_settings::UserSettings;
pub use webhook::Webhook;

View File

@@ -0,0 +1,85 @@
use serde::{Deserialize, Serialize};
/// Reason for reporting content (message or server)
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub enum ContentReportReason {
/// No reason has been specified
NoneSpecified,
/// Blatantly illegal content
Illegal,
/// Content that promotes harm to others / self
PromotesHarm,
/// Spam or platform abuse
SpamAbuse,
/// Distribution of malware
Malware,
/// Harassment or abuse targeted at another user
Harassment,
}
/// Reason for reporting a user
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub enum UserReportReason {
/// No reason has been specified
NoneSpecified,
/// User is sending spam or otherwise abusing the platform
SpamAbuse,
/// User's profile contains inappropriate content for a general audience
InappropriateProfile,
/// User is impersonating another user
Impersonation,
/// User is evading a ban
BanEvasion,
/// User is not of minimum age to use the platform
Underage,
}
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
#[serde(tag = "type")]
pub enum ReportedContent {
/// Report a message
Message {
/// ID of the message
id: String,
/// Reason for reporting message
report_reason: ContentReportReason,
},
/// Report a server
Server {
/// ID of the server
id: String,
/// Reason for reporting server
report_reason: ContentReportReason,
},
/// Report a user
User {
/// ID of the user
id: String,
/// Reason for reporting a user
report_reason: UserReportReason,
},
}
/// User-generated platform moderation report.
#[derive(Serialize, Deserialize, JsonSchema, Debug, Clone)]
pub struct Report {
/// Unique Id
#[serde(rename = "_id")]
pub id: String,
/// Id of the user creating this report
pub author_id: String,
/// Reported content
pub content: ReportedContent,
/// Additional report context
pub additional_context: String,
}

View File

@@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
use crate::models::{Message, Server, User};
/// Enum to map into different models
/// that can be saved in a snapshot
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
#[serde(tag = "_type")]
pub enum SnapshotContent {
Message {
/// Context before the message
#[serde(rename = "_prior_context")]
prior_context: Vec<Message>,
/// Context after the message
#[serde(rename = "_leading_context")]
leading_context: Vec<Message>,
/// Message
#[serde(flatten)]
message: Message,
},
Server(Server),
User(User),
}
/// Snapshot of some content
#[derive(Serialize, Deserialize, JsonSchema, Debug)]
pub struct Snapshot {
/// Unique Id
#[serde(rename = "_id")]
pub id: String,
/// Report parent Id
pub report_id: String,
/// Snapshot of content
pub content: SnapshotContent,
}

View File

@@ -1,8 +1,8 @@
use crate::bson::doc;
use crate::util::variables::delta::VAPID_PRIVATE_KEY;
use authifier::Database;
use deadqueue::limited::Queue;
use rauth::Database;
use web_push::{
ContentEncoding, SubscriptionInfo, SubscriptionKeys, VapidSignatureBuilder, WebPushClient,
WebPushMessageBuilder,

View File

@@ -27,6 +27,11 @@ mod users {
pub mod user_settings;
}
mod safety {
pub mod report;
pub mod snapshot;
}
pub use admin::migrations::AbstractMigrations;
pub use media::attachment::AbstractAttachment;
@@ -46,6 +51,9 @@ pub use users::bot::AbstractBot;
pub use users::user::AbstractUser;
pub use users::user_settings::AbstractUserSettings;
pub use safety::report::AbstractReport;
pub use safety::snapshot::AbstractSnapshot;
pub trait AbstractDatabase:
Sync
+ Send
@@ -63,5 +71,7 @@ pub trait AbstractDatabase:
+ AbstractUser
+ AbstractUserSettings
+ AbstractWebhook
+ AbstractReport
+ AbstractSnapshot
{
}

View File

@@ -0,0 +1,8 @@
use crate::models::Report;
use crate::Result;
#[async_trait]
pub trait AbstractReport: Sync + Send {
/// Insert a new report into the database
async fn insert_report(&self, report: &Report) -> Result<()>;
}

View File

@@ -0,0 +1,8 @@
use crate::models::Snapshot;
use crate::Result;
#[async_trait]
pub trait AbstractSnapshot: Sync + Send {
/// Insert a new snapshot into the database
async fn insert_snapshot(&self, snapshot: &Snapshot) -> Result<()>;
}

View File

@@ -1,9 +1,11 @@
use authifier::config::{ResolveIp, Shield};
use super::variables::delta::{
APP_URL, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_USERNAME,
USE_EMAIL, USE_HCAPTCHA,
APP_URL, AUTHIFIER_SHIELD_KEY, HCAPTCHA_KEY, INVITE_ONLY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD,
SMTP_USERNAME, USE_EMAIL, USE_HCAPTCHA,
};
use crate::rauth::config::{
use crate::authifier::config::{
Captcha, Config, EmailVerificationConfig, SMTPSettings, Template, Templates,
};
@@ -59,5 +61,19 @@ pub fn config() -> Config {
};
}
if let Some(api_key) = &*AUTHIFIER_SHIELD_KEY {
config.shield = Shield::Enabled {
api_key: api_key.to_string(),
strict: false,
};
}
if std::env::var("TRUST_CLOUDFLARE")
.map(|x| x == "1")
.unwrap_or_default()
{
config.resolve_ip = ResolveIp::Cloudflare;
}
config
}

View File

@@ -1,7 +1,7 @@
pub mod authifier;
pub mod log;
pub mod manipulation;
pub mod pfp;
pub mod rauth;
pub mod r#ref;
pub mod regex;
pub mod result;

View File

@@ -64,6 +64,9 @@ pub enum Error {
IsBot,
BotIsPrivate,
// ? User safety related errors
CannotReportYourself,
// ? Permission errors
MissingPermission {
permission: Permission,
@@ -166,6 +169,8 @@ impl<'r> Responder<'r, 'static> for Error {
Error::IsBot => Status::BadRequest,
Error::BotIsPrivate => Status::Forbidden,
Error::CannotReportYourself => Status::BadRequest,
Error::MissingPermission { .. } => Status::Forbidden,
Error::MissingUserPermission { .. } => Status::Forbidden,
Error::NotElevated => Status::Forbidden,

View File

@@ -30,6 +30,8 @@ lazy_static! {
env::var("REVOLT_VAPID_PRIVATE_KEY").expect("Missing REVOLT_VAPID_PRIVATE_KEY environment variable.");
pub static ref VAPID_PUBLIC_KEY: String =
env::var("REVOLT_VAPID_PUBLIC_KEY").expect("Missing REVOLT_VAPID_PUBLIC_KEY environment variable.");
pub static ref AUTHIFIER_SHIELD_KEY: Option<String> =
env::var("REVOLT_AUTHIFIER_SHIELD_KEY").ok();
// Application Flags
pub static ref INVITE_ONLY: bool = env::var("REVOLT_INVITE_ONLY").map_or(false, |v| v == "1");

View File

@@ -8,7 +8,7 @@ use std::hash::Hasher;
use std::ops::Add;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use crate::rauth::models::Session;
use crate::authifier::models::Session;
use rocket::fairing::{Fairing, Info, Kind};
use rocket::http::uri::Origin;
use rocket::http::{Method, Status};
@@ -129,6 +129,8 @@ fn resolve_bucket<'r>(request: &'r rocket::Request<'_>) -> (&'r str, Option<&'r
}
}
("swagger", _) => ("swagger", None),
("safety", Some("report")) => ("safety_report", Some("report")),
("safety", _) => ("safety", None),
_ => ("any", None),
}
} else {
@@ -148,6 +150,8 @@ fn resolve_bucket_limit(bucket: &str) -> u8 {
"auth_delete" => 255,
"default_avatar" => 255,
"swagger" => 100,
"safety" => 15,
"safety_report" => 3,
_ => 20,
}
}