diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index dcf3dd90..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -ko_fi: insertish -custom: https://insrt.uk/donate diff --git a/.github/workflows/rust.yaml b/.github/workflows/rust.yaml index 08abe127..c7f57608 100644 --- a/.github/workflows/rust.yaml +++ b/.github/workflows/rust.yaml @@ -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 }} diff --git a/Cargo.lock b/Cargo.lock index 78e2f244..7f795004 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/build.sh b/build.sh deleted file mode 100755 index 9c9a9162..00000000 --- a/build.sh +++ /dev/null @@ -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 . diff --git a/crates/bonfire/.github/FUNDING.yml b/crates/bonfire/.github/FUNDING.yml deleted file mode 100644 index dcf3dd90..00000000 --- a/crates/bonfire/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -ko_fi: insertish -custom: https://insrt.uk/donate diff --git a/crates/bonfire/.github/workflows/docker.yml b/crates/bonfire/.github/workflows/docker.yml deleted file mode 100644 index 77cdbc0b..00000000 --- a/crates/bonfire/.github/workflows/docker.yml +++ /dev/null @@ -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 diff --git a/crates/bonfire/.github/workflows/rust.yaml b/crates/bonfire/.github/workflows/rust.yaml deleted file mode 100644 index 4386b19f..00000000 --- a/crates/bonfire/.github/workflows/rust.yaml +++ /dev/null @@ -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 diff --git a/crates/bonfire/.github/workflows/triage_issue.yml b/crates/bonfire/.github/workflows/triage_issue.yml deleted file mode 100644 index b625a98b..00000000 --- a/crates/bonfire/.github/workflows/triage_issue.yml +++ /dev/null @@ -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 diff --git a/crates/bonfire/.github/workflows/triage_pr.yml b/crates/bonfire/.github/workflows/triage_pr.yml deleted file mode 100644 index df07fe88..00000000 --- a/crates/bonfire/.github/workflows/triage_pr.yml +++ /dev/null @@ -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 diff --git a/crates/bonfire/Cargo.toml b/crates/bonfire/Cargo.toml index bb7a05fd..2e36f6fc 100644 --- a/crates/bonfire/Cargo.toml +++ b/crates/bonfire/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "revolt-bonfire" -version = "0.5.6" +version = "0.5.9" license = "AGPL-3.0-or-later" edition = "2021" diff --git a/crates/delta/Cargo.toml b/crates/delta/Cargo.toml index f3d3ffa3..abd3923d 100644 --- a/crates/delta/Cargo.toml +++ b/crates/delta/Cargo.toml @@ -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 "] 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" diff --git a/crates/delta/src/main.rs b/crates/delta/src/main.rs index 5bb58261..255fbb0a 100644 --- a/crates/delta/src/main.rs +++ b/crates/delta/src/main.rs @@ -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) diff --git a/crates/delta/src/routes/mod.rs b/crates/delta/src/routes/mod.rs index 24abf5c4..6b957f5f 100644 --- a/crates/delta/src/routes/mod.rs +++ b/crates/delta/src/routes/mod.rs @@ -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) -> Rocket { "/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": [ diff --git a/crates/delta/src/routes/onboard/complete.rs b/crates/delta/src/routes/onboard/complete.rs index 1098091b..a96444f7 100644 --- a/crates/delta/src/routes/onboard/complete.rs +++ b/crates/delta/src/routes/onboard/complete.rs @@ -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}; diff --git a/crates/delta/src/routes/onboard/hello.rs b/crates/delta/src/routes/onboard/hello.rs index 55bf5cf2..e8caa430 100644 --- a/crates/delta/src/routes/onboard/hello.rs +++ b/crates/delta/src/routes/onboard/hello.rs @@ -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; diff --git a/crates/delta/src/routes/push/subscribe.rs b/crates/delta/src/routes/push/subscribe.rs index 30740449..dea9f412 100644 --- a/crates/delta/src/routes/push/subscribe.rs +++ b/crates/delta/src/routes/push/subscribe.rs @@ -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 = "")] pub async fn req( - rauth: &State, + authifier: &State, mut session: Session, data: Json, ) -> Result { session.subscription = Some(data.into_inner()); session - .save(rauth) + .save(authifier) .await .map(|_| EmptyResponse) .map_err(|_| Error::DatabaseError { diff --git a/crates/delta/src/routes/push/unsubscribe.rs b/crates/delta/src/routes/push/unsubscribe.rs index 440ace0f..cbbe70de 100644 --- a/crates/delta/src/routes/push/unsubscribe.rs +++ b/crates/delta/src/routes/push/unsubscribe.rs @@ -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, mut session: Session) -> Result { +pub async fn req(authifier: &State, mut session: Session) -> Result { session.subscription = None; session - .save(rauth) + .save(authifier) .await .map(|_| EmptyResponse) .map_err(|_| Error::DatabaseError { diff --git a/crates/delta/src/routes/root.rs b/crates/delta/src/routes/root.rs index 59bd8dc9..c6743142 100644 --- a/crates/delta/src/routes/root.rs +++ b/crates/delta/src/routes/root.rs @@ -119,12 +119,21 @@ pub async fn root() -> Result> { app: APP_URL.to_string(), vapid: VAPID_PUBLIC_KEY.to_string(), build: BuildInformation { - commit_sha: env!("VERGEN_GIT_SHA", "").to_string(), - commit_timestamp: env!("VERGEN_GIT_COMMIT_TIMESTAMP", "") + commit_sha: option_env!("VERGEN_GIT_SHA") + .unwrap_or_else(|| "") + .to_string(), + commit_timestamp: option_env!("VERGEN_GIT_COMMIT_TIMESTAMP") + .unwrap_or_else(|| "") + .to_string(), + semver: option_env!("VERGEN_GIT_SEMVER") + .unwrap_or_else(|| "") + .to_string(), + origin_url: option_env!("GIT_ORIGIN_URL") + .unwrap_or_else(|| "") + .to_string(), + timestamp: option_env!("VERGEN_BUILD_TIMESTAMP") + .unwrap_or_else(|| "") .to_string(), - semver: env!("VERGEN_GIT_SEMVER", "").to_string(), - origin_url: env!("GIT_ORIGIN_URL", "").to_string(), - timestamp: env!("VERGEN_BUILD_TIMESTAMP", "").to_string(), }, })) } diff --git a/crates/delta/src/routes/safety/mod.rs b/crates/delta/src/routes/safety/mod.rs new file mode 100644 index 00000000..29102aa6 --- /dev/null +++ b/crates/delta/src/routes/safety/mod.rs @@ -0,0 +1,11 @@ +use revolt_rocket_okapi::revolt_okapi::openapi3::OpenApi; +use rocket::Route; + +mod report_content; + +pub fn routes() -> (Vec, OpenApi) { + openapi_get_routes_spec![ + // Reports + report_content::report_content + ] +} diff --git a/crates/delta/src/routes/safety/report_content.rs b/crates/delta/src/routes/safety/report_content.rs new file mode 100644 index 00000000..295b4e2b --- /dev/null +++ b/crates/delta/src/routes/safety/report_content.rs @@ -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 = "")] +pub async fn report_content(db: &Db, user: User, data: Json) -> 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) = 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(()) +} diff --git a/crates/delta/src/routes/users/change_username.rs b/crates/delta/src/routes/users/change_username.rs index 512e00cf..2941a2eb 100644 --- a/crates/delta/src/routes/users/change_username.rs +++ b/crates/delta/src/routes/users/change_username.rs @@ -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; diff --git a/crates/quark/.github/FUNDING.yml b/crates/quark/.github/FUNDING.yml deleted file mode 100644 index dcf3dd90..00000000 --- a/crates/quark/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -ko_fi: insertish -custom: https://insrt.uk/donate diff --git a/crates/quark/.github/workflows/rust.yaml b/crates/quark/.github/workflows/rust.yaml deleted file mode 100644 index 4386b19f..00000000 --- a/crates/quark/.github/workflows/rust.yaml +++ /dev/null @@ -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 diff --git a/crates/quark/.github/workflows/triage_issue.yml b/crates/quark/.github/workflows/triage_issue.yml deleted file mode 100644 index b625a98b..00000000 --- a/crates/quark/.github/workflows/triage_issue.yml +++ /dev/null @@ -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 diff --git a/crates/quark/.github/workflows/triage_pr.yml b/crates/quark/.github/workflows/triage_pr.yml deleted file mode 100644 index df07fe88..00000000 --- a/crates/quark/.github/workflows/triage_pr.yml +++ /dev/null @@ -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 diff --git a/crates/quark/Cargo.toml b/crates/quark/Cargo.toml index 1f08b715..11b2ff1e 100644 --- a/crates/quark/Cargo.toml +++ b/crates/quark/Cargo.toml @@ -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" diff --git a/crates/quark/src/database.rs b/crates/quark/src/database.rs index 1a0b382a..1388c850 100644 --- a/crates/quark/src/database.rs +++ b/crates/quark/src/database.rs @@ -61,13 +61,13 @@ impl Deref for Database { } } -impl From for rauth::Database { +impl From 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")), + ), } } } diff --git a/crates/quark/src/events/client.rs b/crates/quark/src/events/client.rs index 617ecc64..b4706535 100644 --- a/crates/quark/src/events/client.rs +++ b/crates/quark/src/events/client.rs @@ -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), } diff --git a/crates/quark/src/impl/dummy/mod.rs b/crates/quark/src/impl/dummy/mod.rs index dcb41249..92b1f315 100644 --- a/crates/quark/src/impl/dummy/mod.rs +++ b/crates/quark/src/impl/dummy/mod.rs @@ -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; diff --git a/crates/quark/src/impl/dummy/safety/report.rs b/crates/quark/src/impl/dummy/safety/report.rs new file mode 100644 index 00000000..5286976c --- /dev/null +++ b/crates/quark/src/impl/dummy/safety/report.rs @@ -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(()) + } +} diff --git a/crates/quark/src/impl/dummy/safety/snapshot.rs b/crates/quark/src/impl/dummy/safety/snapshot.rs new file mode 100644 index 00000000..8d384059 --- /dev/null +++ b/crates/quark/src/impl/dummy/safety/snapshot.rs @@ -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(()) + } +} diff --git a/crates/quark/src/impl/mongo/admin/migrations/scripts.rs b/crates/quark/src/impl/mongo/admin/migrations/scripts.rs index e986ac02..5fbd4b9c 100644 --- a/crates/quark/src/impl/mongo/admin/migrations/scripts.rs +++ b/crates/quark/src/impl/mongo/admin/migrations/scripts.rs @@ -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(); diff --git a/crates/quark/src/impl/mongo/mod.rs b/crates/quark/src/impl/mongo/mod.rs index 8c766219..2efc84f8 100644 --- a/crates/quark/src/impl/mongo/mod.rs +++ b/crates/quark/src/impl/mongo/mod.rs @@ -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); diff --git a/crates/quark/src/impl/mongo/safety/report.rs b/crates/quark/src/impl/mongo/safety/report.rs new file mode 100644 index 00000000..6b364d11 --- /dev/null +++ b/crates/quark/src/impl/mongo/safety/report.rs @@ -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(|_| ()) + } +} diff --git a/crates/quark/src/impl/mongo/safety/snapshot.rs b/crates/quark/src/impl/mongo/safety/snapshot.rs new file mode 100644 index 00000000..d4dd29f6 --- /dev/null +++ b/crates/quark/src/impl/mongo/safety/snapshot.rs @@ -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(|_| ()) + } +} diff --git a/crates/quark/src/impl/rocket.rs b/crates/quark/src/impl/rocket.rs index 0756bd38..22c1466c 100644 --- a/crates/quark/src/impl/rocket.rs +++ b/crates/quark/src/impl/rocket.rs @@ -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 { let user: &Option = 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)) } } } diff --git a/crates/quark/src/lib.rs b/crates/quark/src/lib.rs index f3a77166..2d063639 100644 --- a/crates/quark/src/lib.rs +++ b/crates/quark/src/lib.rs @@ -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; diff --git a/crates/quark/src/models/mod.rs b/crates/quark/src/models/mod.rs index f6a4b629..1a3573d9 100644 --- a/crates/quark/src/models/mod.rs +++ b/crates/quark/src/models/mod.rs @@ -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; diff --git a/crates/quark/src/models/safety/report.rs b/crates/quark/src/models/safety/report.rs new file mode 100644 index 00000000..db4552e8 --- /dev/null +++ b/crates/quark/src/models/safety/report.rs @@ -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, +} diff --git a/crates/quark/src/models/safety/snapshot.rs b/crates/quark/src/models/safety/snapshot.rs new file mode 100644 index 00000000..f9674de8 --- /dev/null +++ b/crates/quark/src/models/safety/snapshot.rs @@ -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, + + /// Context after the message + #[serde(rename = "_leading_context")] + leading_context: Vec, + + /// 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, +} diff --git a/crates/quark/src/tasks/web_push.rs b/crates/quark/src/tasks/web_push.rs index 08a6d851..c9428f6f 100644 --- a/crates/quark/src/tasks/web_push.rs +++ b/crates/quark/src/tasks/web_push.rs @@ -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, diff --git a/crates/quark/src/traits/mod.rs b/crates/quark/src/traits/mod.rs index 4691a072..8671ffad 100644 --- a/crates/quark/src/traits/mod.rs +++ b/crates/quark/src/traits/mod.rs @@ -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 { } diff --git a/crates/quark/src/traits/safety/report.rs b/crates/quark/src/traits/safety/report.rs new file mode 100644 index 00000000..f5a15e39 --- /dev/null +++ b/crates/quark/src/traits/safety/report.rs @@ -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<()>; +} diff --git a/crates/quark/src/traits/safety/snapshot.rs b/crates/quark/src/traits/safety/snapshot.rs new file mode 100644 index 00000000..9f6ac84e --- /dev/null +++ b/crates/quark/src/traits/safety/snapshot.rs @@ -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<()>; +} diff --git a/crates/quark/src/util/rauth.rs b/crates/quark/src/util/authifier.rs similarity index 80% rename from crates/quark/src/util/rauth.rs rename to crates/quark/src/util/authifier.rs index 02d8b26b..e87803d5 100644 --- a/crates/quark/src/util/rauth.rs +++ b/crates/quark/src/util/authifier.rs @@ -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 } diff --git a/crates/quark/src/util/mod.rs b/crates/quark/src/util/mod.rs index bb83ded1..a4aa2814 100644 --- a/crates/quark/src/util/mod.rs +++ b/crates/quark/src/util/mod.rs @@ -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; diff --git a/crates/quark/src/util/result.rs b/crates/quark/src/util/result.rs index 628c8303..6efbd01f 100644 --- a/crates/quark/src/util/result.rs +++ b/crates/quark/src/util/result.rs @@ -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, diff --git a/crates/quark/src/util/variables/delta.rs b/crates/quark/src/util/variables/delta.rs index f81ff087..bb789987 100644 --- a/crates/quark/src/util/variables/delta.rs +++ b/crates/quark/src/util/variables/delta.rs @@ -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 = + 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"); diff --git a/crates/quark/src/web/ratelimiter.rs b/crates/quark/src/web/ratelimiter.rs index 853d9aa8..631fa368 100644 --- a/crates/quark/src/web/ratelimiter.rs +++ b/crates/quark/src/web/ratelimiter.rs @@ -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, } }