├── .cargo └── config.toml ├── .github ├── dependabot.yml └── workflows │ ├── nix.yml │ └── rust.yml ├── .gitignore ├── .gitmodules ├── ARCHITECTURE.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── build.rs ├── default.nix ├── deny.toml ├── example_config.toml ├── flake.lock ├── flake.nix ├── resources ├── email-token-template.html ├── email-token-template.txt ├── lillies.svg └── lotus.svg ├── rust-toolchain.toml ├── scherzo.service ├── scherzo_derive ├── Cargo.toml └── src │ └── lib.rs ├── shell.nix └── src ├── bin ├── cmd.rs └── migrate.rs ├── config.rs ├── db ├── migration │ ├── add_account_kind.rs │ ├── add_next_msg_ids.rs │ ├── initial_db_version.rs │ ├── mod.rs │ ├── remove_log_chan_id_from_admin_keys.rs │ └── timestamps_are_milliseconds.rs ├── mod.rs ├── sled.rs └── sqlite.rs ├── error.rs ├── impls ├── admin_action.rs ├── against.rs ├── auth │ ├── begin_auth.rs │ ├── check_logged_in.rs │ ├── delete_user.rs │ ├── federate.rs │ ├── key.rs │ ├── login_federated.rs │ ├── mod.rs │ ├── next_step.rs │ ├── next_step │ │ ├── delete_user.rs │ │ ├── email.rs │ │ ├── login.rs │ │ ├── registration.rs │ │ └── reset_password.rs │ ├── step_back.rs │ └── stream_steps.rs ├── batch │ ├── batch.rs │ ├── batch_same.rs │ └── mod.rs ├── chat │ ├── channels │ │ ├── create_channel.rs │ │ ├── delete_channel.rs │ │ ├── get_guild_channels.rs │ │ ├── mod.rs │ │ ├── typing.rs │ │ ├── update_all_channel_order.rs │ │ ├── update_channel_information.rs │ │ └── update_channel_order.rs │ ├── guilds │ │ ├── create_direct_message.rs │ │ ├── create_guild.rs │ │ ├── create_room.rs │ │ ├── delete_guild.rs │ │ ├── get_guild.rs │ │ ├── get_guild_list.rs │ │ ├── get_guild_members.rs │ │ ├── join_guild.rs │ │ ├── leave_guild.rs │ │ ├── mod.rs │ │ ├── preview_guild.rs │ │ ├── update_guild_information.rs │ │ └── upgrade_room_to_guild.rs │ ├── invites │ │ ├── create_invite.rs │ │ ├── delete_invite.rs │ │ ├── get_guild_invites.rs │ │ ├── get_pending_invites.rs │ │ ├── ignore_pending_invite.rs │ │ ├── invite_user_to_guild.rs │ │ ├── mod.rs │ │ └── reject_pending_invite.rs │ ├── messages │ │ ├── add_reaction.rs │ │ ├── delete_message.rs │ │ ├── get_channel_messages.rs │ │ ├── get_message.rs │ │ ├── get_pinned_messages.rs │ │ ├── mod.rs │ │ ├── pin_message.rs │ │ ├── remove_reaction.rs │ │ ├── send_message.rs │ │ ├── unpin_message.rs │ │ └── update_message_text.rs │ ├── mod.rs │ ├── moderation │ │ ├── ban_user.rs │ │ ├── get_banned_users.rs │ │ ├── kick_user.rs │ │ ├── mod.rs │ │ └── unban_user.rs │ ├── permissions │ │ ├── add_guild_role.rs │ │ ├── delete_guild_role.rs │ │ ├── get_guild_roles.rs │ │ ├── get_permissions.rs │ │ ├── get_user_roles.rs │ │ ├── give_up_ownership.rs │ │ ├── grant_ownership.rs │ │ ├── manage_user_roles.rs │ │ ├── mod.rs │ │ ├── modify_guild_role.rs │ │ ├── move_role.rs │ │ ├── query_has_permission.rs │ │ └── set_permissions.rs │ ├── stream_events.rs │ └── trigger_action.rs ├── emote │ ├── add_emote_to_pack.rs │ ├── create_emote_pack.rs │ ├── delete_emote_from_pack.rs │ ├── delete_emote_pack.rs │ ├── dequip_emote_pack.rs │ ├── equip_emote_pack.rs │ ├── get_emote_pack_emotes.rs │ ├── get_emote_packs.rs │ └── mod.rs ├── mediaproxy │ ├── can_instant_view.rs │ ├── fetch_link_metadata.rs │ ├── instant_view.rs │ └── mod.rs ├── mod.rs ├── profile │ ├── get_app_data.rs │ ├── get_profile.rs │ ├── mod.rs │ ├── set_app_data.rs │ └── update_profile.rs ├── rest │ ├── about.rs │ ├── download.rs │ ├── mod.rs │ └── upload.rs ├── sync │ ├── mod.rs │ ├── notify_new_id.rs │ ├── pull.rs │ └── push.rs └── voice │ ├── mod.rs │ └── stream_message.rs ├── key.rs ├── lib.rs ├── main.rs └── utils ├── either.rs ├── evec.rs ├── http_ratelimit.rs ├── mod.rs ├── ratelimit.rs └── test.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-unknown-linux-gnu] 2 | linker = "clang" 3 | rustflags = ["-C", "link-arg=-fuse-ld=mold", "--cfg", "tokio_unstable"] 4 | 5 | [target.x86_64-unknown-linux-musl] 6 | linker = "clang" 7 | rustflags = ["-C", "link-arg=-fuse-ld=mold", "--cfg", "tokio_unstable"] -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | 8 | - package-ecosystem: "cargo" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" -------------------------------------------------------------------------------- /.github/workflows/nix.yml: -------------------------------------------------------------------------------- 1 | name: "Nix" 2 | on: 3 | push: 4 | branches: [ master ] 5 | paths-ignore: 6 | - 'README.md' 7 | - '**/*.nix' 8 | - 'nix/envrc' 9 | - 'flake.lock' 10 | 11 | jobs: 12 | nix-build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout repo 16 | uses: actions/checkout@v3 17 | - name: Install nix 18 | uses: cachix/install-nix-action@v16 19 | with: 20 | extra_nix_config: | 21 | experimental-features = nix-command flakes 22 | nix_path: nixpkgs=channel:nixos-unstable 23 | - name: Setup cachix 24 | uses: cachix/cachix-action@v10 25 | with: 26 | name: harmony 27 | authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' 28 | - name: Tests 29 | run: nix build .#scherzo 30 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: [ master ] 7 | paths-ignore: 8 | - 'README.md' 9 | - '**/*.nix' 10 | - 'nix/envrc' 11 | - 'flake.lock' 12 | pull_request: 13 | branches: [ master ] 14 | paths-ignore: 15 | - 'README.md' 16 | - '**/*.nix' 17 | - 'nix/envrc' 18 | - 'flake.lock' 19 | 20 | env: 21 | CARGO_TERM_COLOR: always 22 | CARGO_NET_RETRY: 10 23 | RUST_BACKTRACE: short 24 | 25 | jobs: 26 | check: 27 | runs-on: ubuntu-latest 28 | name: Build scherzo 29 | steps: 30 | - name: Checkout repo 31 | uses: actions/checkout@v3 32 | with: 33 | submodules: true 34 | 35 | - name: Install nix 36 | uses: cachix/install-nix-action@v16 37 | with: 38 | extra_nix_config: | 39 | experimental-features = nix-command flakes 40 | nix_path: nixpkgs=channel:nixos-unstable 41 | 42 | - name: Install dependencies 43 | run: | 44 | sudo apt update -yy 45 | sudo apt install -yy --no-install-recommends protobuf-compiler clang 46 | 47 | - name: Install rust 48 | run: rustup update && rustup component add rustfmt clippy 49 | 50 | - name: Cache rust 51 | uses: Swatinem/rust-cache@v1 52 | with: 53 | key: cache-debug 54 | 55 | - name: Install Cargo Deny 56 | run: cargo install --locked cargo-deny 57 | 58 | - name: Cargo Deny 59 | run: cargo deny check 60 | 61 | - name: Test (with sled DB) 62 | run: nix-shell -p mold --run "cargo test --no-default-features --features sled,voice" 63 | 64 | - name: Test (with sqlite DB) 65 | run: nix-shell -p mold --run "cargo test --no-default-features --features sqlite,voice" 66 | 67 | build-release: 68 | needs: check 69 | if: github.event_name == 'push' 70 | runs-on: ubuntu-latest 71 | name: Build release binaries 72 | steps: 73 | - name: Checkout repo 74 | uses: actions/checkout@v3 75 | with: 76 | submodules: true 77 | 78 | - name: Install nix 79 | uses: cachix/install-nix-action@v16 80 | with: 81 | extra_nix_config: | 82 | experimental-features = nix-command flakes 83 | nix_path: nixpkgs=channel:nixos-unstable 84 | 85 | - name: Install dependencies 86 | run: | 87 | sudo apt update -yy 88 | sudo apt install -yy --no-install-recommends protobuf-compiler clang 89 | 90 | - name: Install rust 91 | run: rustup update && rustup component add rustfmt clippy 92 | 93 | - name: Cache rust 94 | uses: Swatinem/rust-cache@v1 95 | with: 96 | key: cache-release-1 97 | 98 | - name: Build release 99 | run: nix-shell -p mold --run "cargo build --release --no-default-features --features sled,voice,jemalloc" 100 | 101 | - name: Build release (migrate binary) 102 | run: nix-shell -p mold --run "cargo build --release --no-default-features --features sled,sqlite,jemalloc --bin scherzo_migrate" 103 | 104 | #- name: UPX 105 | # run: | 106 | # ./upx target/x86_64-unknown-linux-gnu/release/scherzo 107 | # ./upx target/x86_64-unknown-linux-gnu/release/scherzo_cmd 108 | 109 | - name: Upload release 110 | env: 111 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 112 | run: | 113 | wget -q https://github.com/TheAssassin/pyuploadtool/releases/download/continuous/pyuploadtool-x86_64.AppImage 114 | chmod +x pyuploadtool-x86_64.AppImage 115 | ./pyuploadtool-x86_64.AppImage target/release/scherzo target/release/scherzo_cmd target/release/scherzo_migrate 116 | 117 | update: 118 | needs: build-release 119 | name: Update Scherzo at harmonyapp.io 120 | runs-on: ubuntu-latest 121 | if: github.event_name == 'push' 122 | steps: 123 | - name: Checkout repo 124 | uses: actions/checkout@v3 125 | with: 126 | repository: 'harmony-development/ansible' 127 | - run: 'echo "$SSH_KEY" > key && chmod 600 key' 128 | shell: bash 129 | env: 130 | SSH_KEY: ${{secrets.ACTIONS_SSH_KEY}} 131 | - run: 'echo "$KNOWN_HOSTS" > known_hosts && chmod 600 known_hosts' 132 | shell: bash 133 | env: 134 | KNOWN_HOSTS: ${{secrets.ACTIONS_SSH_KNOWN_HOSTS}} 135 | - run: 'ansible-playbook only-scherzo.yml --key-file key' 136 | shell: bash 137 | env: 138 | SSH_HOST: ${{secrets.ACTIONS_SSH_HOST}} 139 | - name: Trigger integration testing 140 | if: ${{ github.ref == 'refs/heads/master' }} 141 | uses: peter-evans/repository-dispatch@v1 142 | with: 143 | token: ${{ secrets.INTEGRATION_TEST_PAT }} 144 | repository: harmony-development/integration-testing 145 | event-type: dep-updated 146 | 147 | docker: 148 | needs: build-release 149 | name: Update scherzo docker image 150 | runs-on: ubuntu-latest 151 | if: github.event_name == 'push' 152 | steps: 153 | - 154 | name: Set up QEMU 155 | uses: docker/setup-qemu-action@v1 156 | - 157 | name: Set up Docker Buildx 158 | uses: docker/setup-buildx-action@v1 159 | - 160 | name: Login to DockerHub 161 | uses: docker/login-action@v1 162 | with: 163 | username: ${{ secrets.DOCKERHUB_USERNAME }} 164 | password: ${{ secrets.DOCKERHUB_TOKEN }} 165 | - 166 | name: Build and push 167 | id: docker_build 168 | uses: docker/build-push-action@v2 169 | with: 170 | push: true 171 | tags: yusdacra/scherzo:latest 172 | - 173 | name: Image digest 174 | run: echo ${{ steps.docker_build.outputs.digest }} 175 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Rust 2 | /target/ 3 | **/*.rs.bk 4 | 5 | # Nix 6 | /result* 7 | /nix/result* 8 | 9 | # Direnv 10 | /.direnv 11 | /.envrc 12 | 13 | /.vscode 14 | /db* 15 | /media_root 16 | /logs 17 | /media 18 | /config.toml 19 | /key.pem 20 | /cert.pem 21 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "protocols/before_account_kind"] 2 | path = protocols/before_account_kind 3 | url = https://github.com/harmony-development/protocol.git 4 | -------------------------------------------------------------------------------- /ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # Architecture 2 | 3 | This file describes the high-level architecture of scherzo. 4 | 5 | ## `src/main.rs` 6 | 7 | This is the entrypoint for the `scherzo` server binary, and where everything is 8 | combined to work in harmony (pun intended). It mostly handles setup work like 9 | parsing config (`parse_config`); tracing, opentelemetry, tokio-console 10 | (all in `setup_tracing`); database (`setup_db`); hRPC server transport 11 | (HTTP, done in `setup_transport`) and then hands these over to `setup_server`. 12 | 13 | All of these are done in the `run` function, which combines all those together 14 | and serves the server. It also handles cleanup work (DB flush, tracing flush). 15 | 16 | ## `src/impls` 17 | 18 | This is where API implementations are put in. Each protocol package has it's own 19 | module, REST APIs have their own module. Other more miscellanous code (mainly 20 | `admin_action.rs` and `against.rs`) are also stored here. 21 | 22 | - Each endpoint has their own file in their respective protocol package module. 23 | - Protocol packages that utilize the database have structs named `InsertServerNameTree` 24 | which abstract over DB operations used in that package. It is recommended to put 25 | all DB logic for endpoint handlers in these structs as methods, with `endpoint_name_logic` name. 26 | 27 | ## `src/db` 28 | 29 | This module contains database implementations, keys used for storing certain 30 | type of values in various protocol package database trees, serialize / deserialize 31 | functions for data types and database migrations (`migration` module). 32 | 33 | - A macro for defining deserialization functions is provided here (`impl_deser`). 34 | - See the `mod.rs` file of the module for implementation details. 35 | 36 | ## `src/bin` 37 | 38 | Contains various binary utilities used to work with scherzo, mainly for database 39 | operations (database migration from one implementation to another, CLI for common 40 | database operations etc.). 41 | 42 | ## `src/config.rs` 43 | 44 | This module defines the config structure, and implements functions for utilizing 45 | the config values. See the file to see how to add new config options. 46 | 47 | ## `src/error.rs` 48 | 49 | This module defines the error type used by scherzo in various places. See the file 50 | itself on how to add new error variants. 51 | 52 | ## `src/key.rs` 53 | 54 | This module implements the key handling code for federation between servers. 55 | 56 | ## `src/utils` 57 | 58 | This module contains various utility code used by different places in code. 59 | 60 | ## `scherzo_derive` 61 | 62 | This crate is a proc macro crate that implements various macros used by `scherzo` 63 | to implement stuff more conveniently. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.12 as builder 2 | 3 | RUN apk add --no-cache curl 4 | 5 | RUN cd /root && curl -L https://github.com/harmony-development/scherzo/releases/download/continuous/scherzo > scherzo && chmod +x scherzo 6 | 7 | FROM alpine:3.12 8 | 9 | EXPOSE 2289 10 | 11 | RUN mkdir -p /srv/scherzo 12 | COPY --from=builder /root/scherzo /srv/scherzo/ 13 | 14 | RUN echo "listen_on_localhost = false" > /srv/scherzo/config.toml 15 | 16 | RUN set -x ; \ 17 | addgroup -Sg 82 www-data 2>/dev/null ; \ 18 | adduser -S -D -H -h /srv/scherzo -G www-data -g www-data www-data 2>/dev/null ; \ 19 | addgroup www-data www-data 2>/dev/null && exit 0 ; exit 1 20 | 21 | RUN chown -cR www-data:www-data /srv/scherzo 22 | 23 | RUN apk add --no-cache \ 24 | curl \ 25 | ca-certificates \ 26 | libgcc 27 | 28 | VOLUME ["/srv/scherzo/db", "/srv/scherzo/media", "/srv/scherzo/logs"] 29 | 30 | HEALTHCHECK --start-period=2s CMD curl --fail -s http://localhost:2289/_harmony/about || curl -k --fail -s https://localhost:2289/_harmony/about || exit 1 31 | 32 | USER www-data 33 | WORKDIR /srv/scherzo 34 | ENTRYPOINT [ "/srv/scherzo/scherzo" ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # scherzo 2 | 3 | Harmony server implemented in Rust. 4 | 5 | It uses [hyper] for serving HTTP via [hrpc-rs], and currently supports [sled] 6 | and `sqlite` as a database backend. 7 | 8 | ## Deploy 9 | 10 | Note: the CI builds are currently compiled with `sled` DB backend. 11 | 12 | With docker (or podman): 13 | ``` 14 | docker pull yusdacra/scherzo:latest 15 | docker run -d -p 2289:2289 -v db:/srv/scherzo/db -v media:/srv/scherzo/media yusdacra/scherzo:latest 16 | ``` 17 | 18 | One liner to start the latest master CI artifact: 19 | ``` 20 | mkdir scherzo && cd scherzo && curl -L https://github.com/harmony-development/scherzo/releases/download/continuous/scherzo > scherzo && chmod +x scherzo && ./scherzo 21 | ``` 22 | 23 | ## Configuration 24 | 25 | See the [example config](./example_config.toml) for a commented config file 26 | with all options available. 27 | 28 | ## Roadmap 29 | 30 | - Auth service: (implemented) 31 | - dynamic auth is implemented (login and register by email) 32 | - Chat service: (implemented) 33 | - Mediaproxy service: (implemented) 34 | - Voice service: (implemented) 35 | - Rest APIs: (implemented) 36 | - Federation: (implemented) 37 | 38 | ## Build 39 | 40 | - Clone this repo 41 | - Make sure you have the toolchain described in `rust-toolchain.toml` installed 42 | - This will be installed automatically for you if you have rustup setup! 43 | - Make sure you have `mold`, `clang` and `protoc` installed 44 | - Run `cargo build` 45 | 46 | If you have Nix, you can just do: 47 | - Flakes: `nix build` to build, `nix develop` for development shell 48 | - Non-flakes: `nix-build` to build, `nix-shell shell.nix` for development shell 49 | 50 | You can also get an executable binary from the latest `Continous build` release. 51 | 52 | [hyper]: https://github.com/tokio-rs/hyper 53 | [hrpc-rs]: https://github.com/harmony-development/hrpc-rs 54 | [sled]: https://github.com/spacejam/sled -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use harmony_build::{Builder, Protocol, Result}; 2 | 3 | fn main() -> Result<()> { 4 | build_protocol( 5 | "before_account_kind", 6 | &["profile.v1", "harmonytypes.v1"], 7 | &[], 8 | |builder| { 9 | builder.modify_hrpc_config(|cfg| { 10 | cfg.type_attribute( 11 | ".protocol.profile.v1.Profile", 12 | "#[archive_attr(derive(::bytecheck::CheckBytes))]", 13 | ) 14 | }) 15 | }, 16 | )?; 17 | Ok(()) 18 | } 19 | 20 | fn build_protocol( 21 | version: &str, 22 | stable_svcs: &[&str], 23 | staging_svcs: &[&str], 24 | f: impl FnOnce(Builder) -> Builder, 25 | ) -> Result<()> { 26 | let protocol_path = format!("protocols/{}", version); 27 | let out_dir = { 28 | let mut dir = std::env::var("OUT_DIR").expect("no out dir, how"); 29 | dir.push('/'); 30 | dir.push_str(version); 31 | dir 32 | }; 33 | std::fs::create_dir_all(&out_dir)?; 34 | 35 | let all_services = stable_svcs.iter().chain(staging_svcs.iter()); 36 | let protocol = Protocol::from_path(protocol_path, stable_svcs, staging_svcs)?; 37 | 38 | let mut builder = Builder::new() 39 | .modify_hrpc_config(|cfg| cfg.build_client(false).build_server(false)) 40 | .modify_prost_config(|mut cfg| { 41 | cfg.bytes(&[".protocol.batch.v1"]); 42 | cfg 43 | }); 44 | 45 | for service in all_services.filter(|a| "batch.v1".ne(**a)) { 46 | builder = builder.modify_hrpc_config(|cfg| { 47 | cfg.type_attribute( 48 | format!(".protocol.{}", service), 49 | "#[derive(::rkyv::Archive, ::rkyv::Serialize, ::rkyv::Deserialize)]", 50 | ) 51 | }); 52 | } 53 | 54 | let builder = f(builder); 55 | 56 | builder.generate(protocol, out_dir)?; 57 | 58 | Ok(()) 59 | } 60 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | # Flake's default package for non-flake-enabled nix instances 2 | (import 3 | ( 4 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); 5 | in 6 | fetchTarball { 7 | url = 8 | "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flakeCompat.locked.rev}.tar.gz"; 9 | sha256 = lock.nodes.flakeCompat.locked.narHash; 10 | } 11 | ) 12 | { src = ./.; }).defaultNix.default 13 | -------------------------------------------------------------------------------- /example_config.toml: -------------------------------------------------------------------------------- 1 | # The host where this server is hosted. Eg. `harmonyapp.io:2289` or `harmonyapp.io:4444`. 2 | # A port must always be specified. 3 | host = "" 4 | 5 | # Whether to listen on `localhost` or `0.0.0.0` (useful for docker). 6 | listen_on_localhost = true 7 | 8 | # The port to listen on. 9 | port = 2289 10 | 11 | # Why this server is hosted or info about the server. 12 | server_description = "" 13 | 14 | # Message of the day. Can also be changed at runtime, so it is suitable for 15 | # putting recent information (maintanance etc.). 16 | motd = "" 17 | 18 | # Whether to support CORS requests. Should only be used for 19 | # development purposes, since this allows everything. 20 | cors_dev = false 21 | 22 | # Whether to log headers in traces. Authorization information 23 | # will be marked as "sensitive" and won't be logged. 24 | log_headers = false 25 | 26 | [policy] 27 | 28 | # Whether to disable registration and only allow it using admin generated tokens. 29 | disable_registration = false 30 | 31 | # Whether to disable email validation for registration. 32 | disable_registration_email_validation = false 33 | 34 | # Maximum amount of requests that should be processed concurrently. 35 | # If set to 0, it will be disabled. 36 | # Note: you'll want to increase this if your server has 100+ members. 37 | max_concurrent_requests = 512 38 | 39 | [policy.ratelimit] 40 | 41 | # Whether to disable ratelimits or not (useful when testing / benching). 42 | disable = false 43 | 44 | # The header name to look at for client IPs. This is useful if scherzo is 45 | # running behind a reverse proxy, where the requests will be made from one 46 | # local address, and as such rate limiting will not work properly. By setting 47 | # this to a header name such as "X-Forwarded-For", scherzo can use it to get 48 | # client IP and use it for rate limiting. 49 | # 50 | # By default this is not set. If the header can't be found in the request, 51 | # scherzo will silently fallback to using connection IPs. 52 | # 53 | # client_ip_header_name = "" 54 | 55 | # A list of allowed IP addresses that can bypass the rate limiter. 56 | # 57 | # Invalid IP addresses will be silently ignored. Note that these are 58 | # *IP addresses* and as such they don't take a port. 59 | # 60 | # allowed_ips = ["127.0.0.1", "0.0.0.0", "::1"] 61 | 62 | [db] 63 | 64 | # Path to a directory to put db backups in. 65 | db_backup_path = "." 66 | 67 | # Database in-memory cache limit in MiB. 68 | db_cache_limit = 1024 69 | 70 | # (sled only) whether to increase throughput at the cost of more storage usage. 71 | sled_throughput_at_storage_cost = false 72 | 73 | # HTTPS settings 74 | [tls] 75 | 76 | # Path to the certificate file. 77 | cert_file = "./cert" 78 | 79 | # Path to the (private) key file. 80 | key_file = "./key" 81 | 82 | # Media settings 83 | [media] 84 | 85 | # Max upload length for files in MiB. 86 | max_upload_length = 50 87 | 88 | # Where to store media files. 89 | media_root = "./media" 90 | 91 | # Federation settings 92 | [federation] 93 | 94 | # Path to the federation key. This will be generated by the server automatically. 95 | key = "./federation_key" 96 | 97 | # Which hosts to allow. This takes priority over `host_block_list`. 98 | host_allow_list = [] 99 | 100 | # Which hosts to block. 101 | host_block_list = [] 102 | 103 | # (optional) email mailserver settings 104 | # [email] 105 | 106 | # Server address of the mailserver. 107 | # server = "localhost" 108 | 109 | # Port the mailserver is on on the specified address. 110 | # port = 25 111 | 112 | # Name and email adress to use for sending the emails. 113 | # from = "Harmony " 114 | 115 | # (optional) Credentials file for the mailserver. 116 | # 117 | # Example: 118 | # ``` 119 | # username = "username" 120 | # password = "password" 121 | # ``` 122 | # credentials_file = "./email_creds.toml" 123 | 124 | # Whether to use TLS or not while connecting to the mailserver. 125 | # tls = false -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "devshell": { 4 | "locked": { 5 | "lastModified": 1642188268, 6 | "narHash": "sha256-DNz4xScpXIn7rSDohdayBpPR9H9OWCMDOgTYegX081k=", 7 | "owner": "numtide", 8 | "repo": "devshell", 9 | "rev": "696acc29668b644df1740b69e1601119bf6da83b", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "numtide", 14 | "repo": "devshell", 15 | "type": "github" 16 | } 17 | }, 18 | "flakeCompat": { 19 | "flake": false, 20 | "locked": { 21 | "lastModified": 1641205782, 22 | "narHash": "sha256-4jY7RCWUoZ9cKD8co0/4tFARpWB+57+r1bLLvXNJliY=", 23 | "owner": "edolstra", 24 | "repo": "flake-compat", 25 | "rev": "b7547d3eed6f32d06102ead8991ec52ab0a4f1a7", 26 | "type": "github" 27 | }, 28 | "original": { 29 | "owner": "edolstra", 30 | "repo": "flake-compat", 31 | "type": "github" 32 | } 33 | }, 34 | "nixCargoIntegration": { 35 | "inputs": { 36 | "devshell": "devshell", 37 | "nixpkgs": [ 38 | "nixpkgs" 39 | ], 40 | "rustOverlay": "rustOverlay" 41 | }, 42 | "locked": { 43 | "lastModified": 1642745416, 44 | "narHash": "sha256-i87+cNS0raIgHEhNdvBS9OhWm8Dam+PiWe7lKxPMZ9g=", 45 | "owner": "yusdacra", 46 | "repo": "nix-cargo-integration", 47 | "rev": "20c4403b45b86f33a35b55569544e35cd7772927", 48 | "type": "github" 49 | }, 50 | "original": { 51 | "owner": "yusdacra", 52 | "ref": "master", 53 | "repo": "nix-cargo-integration", 54 | "type": "github" 55 | } 56 | }, 57 | "nixpkgs": { 58 | "locked": { 59 | "lastModified": 1642635915, 60 | "narHash": "sha256-vabPA32j81xBO5m3+qXndWp5aqepe+vu96Wkd9UnngM=", 61 | "owner": "NixOS", 62 | "repo": "nixpkgs", 63 | "rev": "6d8215281b2f87a5af9ed7425a26ac575da0438f", 64 | "type": "github" 65 | }, 66 | "original": { 67 | "owner": "NixOS", 68 | "ref": "nixos-unstable", 69 | "repo": "nixpkgs", 70 | "type": "github" 71 | } 72 | }, 73 | "root": { 74 | "inputs": { 75 | "flakeCompat": "flakeCompat", 76 | "nixCargoIntegration": "nixCargoIntegration", 77 | "nixpkgs": "nixpkgs" 78 | } 79 | }, 80 | "rustOverlay": { 81 | "flake": false, 82 | "locked": { 83 | "lastModified": 1642646417, 84 | "narHash": "sha256-1PN44kOjxk6fYeeE8qTo8k+oa4Fa4Mg5UVLPVzuhBpA=", 85 | "owner": "oxalica", 86 | "repo": "rust-overlay", 87 | "rev": "85dcf1a4e4897db4420f2c0a3eaf7bb4693914bc", 88 | "type": "github" 89 | }, 90 | "original": { 91 | "owner": "oxalica", 92 | "repo": "rust-overlay", 93 | "type": "github" 94 | } 95 | } 96 | }, 97 | "root": "root", 98 | "version": 7 99 | } 100 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 | nixCargoIntegration = { 5 | url = "github:yusdacra/nix-cargo-integration/master"; 6 | inputs.nixpkgs.follows = "nixpkgs"; 7 | }; 8 | flakeCompat = { 9 | url = "github:edolstra/flake-compat"; 10 | flake = false; 11 | }; 12 | }; 13 | 14 | outputs = inputs: inputs.nixCargoIntegration.lib.makeOutputs { 15 | root = ./.; 16 | buildPlatform = "crate2nix"; 17 | overrides = { 18 | crateOverrides = common: _: { 19 | mediasoup-sys = prev: 20 | let 21 | pkgs = common.pkgs; 22 | pythonPkgs = pkgs: with pkgs; [ 23 | pip 24 | ]; 25 | pythonWithPkgs = pkgs.python3.withPackages pythonPkgs; 26 | all = (with pkgs; [ cmake gnumake nodejs meson ninja ]) ++ [ pythonWithPkgs ]; 27 | in 28 | { 29 | buildInputs = (prev.buildInputs or [ ]) ++ all; 30 | nativeBuildInputs = (prev.nativeBuildInputs or [ ]) ++ all; 31 | }; 32 | scherzo = prev: { 33 | crateBin = common.lib.filter (bin: bin.name != "scherzo_migrate" && bin.name != "scherzo_cmd") prev.crateBin; 34 | }; 35 | }; 36 | shell = common: prev: { 37 | packages = prev.packages ++ (with common.pkgs; [ 38 | musl.dev 39 | mold 40 | mkcert 41 | cargo-deny 42 | /*(common.lib.buildCrate { 43 | memberName = "tokio-console"; 44 | 45 | root = builtins.fetchGit { 46 | url = "https://github.com/tokio-rs/console.git"; 47 | rev = "a30264e0b5469ea596430b846b05e6e3541915d1"; 48 | ref = "main"; 49 | }; 50 | 51 | inherit (common) nativeBuildInputs buildInputs; 52 | CARGO_PKG_REPOSITORY = "https://github.com/tokio-rs/console"; 53 | })*/ 54 | ]); 55 | commands = prev.commands ++ [ 56 | { 57 | name = "generate-cert"; 58 | command = '' 59 | mkcert localhost 127.0.0.1 ::1 60 | mv localhost+2.pem cert.pem 61 | mv localhost+2-key.pem key.pem 62 | ''; 63 | } 64 | ]; 65 | }; 66 | }; 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /resources/email-token-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Harmony Token 8 | 9 | 10 | 12 |
14 |

Welcome Aboard!

15 |

Please use the following code in order to {{action}}:

16 |
18 | {{token}}
19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /resources/email-token-template.txt: -------------------------------------------------------------------------------- 1 | Please use the following code in order to {{action}}: {{token}} -------------------------------------------------------------------------------- /resources/lotus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly-2022-01-20" 3 | targets = ["x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl"] -------------------------------------------------------------------------------- /scherzo.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Scherzo Harmony Homeserver 3 | After=network.target 4 | 5 | [Service] 6 | Type=simple 7 | Restart=on-failure 8 | RestartSec=5 9 | User=scherzo 10 | WorkingDirectory=/var/lib/scherzo 11 | ExecStart=/opt/scherzo -d 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /scherzo_derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "scherzo_derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | license = "GPL-3.0" 6 | 7 | [lib] 8 | proc-macro = true 9 | 10 | [dependencies] 11 | quote = "1.0" 12 | syn = "1.0" 13 | proc-macro2 = "1.0" -------------------------------------------------------------------------------- /scherzo_derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use proc_macro2::{Ident, Span}; 3 | use quote::quote; 4 | use syn::{parse_macro_input, AttributeArgs, ItemFn}; 5 | 6 | #[proc_macro] 7 | pub fn define_proto_mod(input: TokenStream) -> TokenStream { 8 | let input = input.to_string(); 9 | let mut split = input.split(',').collect::>(); 10 | let (proto_name, svc) = if split.len() == 1 { 11 | ("main", split.pop().unwrap()) 12 | } else { 13 | let svc = split.pop().unwrap().trim(); 14 | let proto_name = split.pop().unwrap().trim(); 15 | (proto_name, svc) 16 | }; 17 | let path = format!("/{}/protocol.{}.v1.rs", proto_name, svc); 18 | 19 | let svc = Ident::new(svc, Span::call_site()); 20 | 21 | (quote! { 22 | pub mod #svc { 23 | pub mod v1 { 24 | include!(concat!(env!("OUT_DIR"), #path)); 25 | } 26 | pub use v1::*; 27 | } 28 | }) 29 | .into() 30 | } 31 | 32 | #[proc_macro] 33 | pub fn impl_db_methods(input: TokenStream) -> TokenStream { 34 | let input = proc_macro2::TokenStream::from(input); 35 | (quote! { 36 | pub async fn apply_batch(&self, batch: Batch) -> Result<(), ServerError> { 37 | self. #input .apply_batch(batch).await.map_err(ServerError::DbError) 38 | } 39 | 40 | pub async fn insert(&self, key: impl AsRef<[u8]>, value: impl AsRef<[u8]>) -> Result, ServerError> { 41 | self. #input .insert(key.as_ref(), value.as_ref()).await.map_err(ServerError::DbError) 42 | } 43 | 44 | pub async fn remove(&self, key: impl AsRef<[u8]>) -> Result, ServerError> { 45 | self. #input .remove(key.as_ref()).await.map_err(ServerError::DbError) 46 | } 47 | 48 | pub async fn get(&self, key: impl AsRef<[u8]>) -> Result, ServerError> { 49 | self. #input .get(key.as_ref()).await.map_err(ServerError::DbError) 50 | } 51 | 52 | pub async fn contains_key(&self, key: impl AsRef<[u8]>) -> Result { 53 | self. #input .contains_key(key.as_ref()).await.map_err(ServerError::DbError) 54 | } 55 | 56 | pub async fn scan_prefix<'a>(&'a self, prefix: impl AsRef<[u8]>) -> impl Iterator> + 'a { 57 | self. #input .scan_prefix(prefix.as_ref()).await.map(|res| res.map_err(ServerError::DbError)) 58 | } 59 | }).into() 60 | } 61 | 62 | // TODO: move this to hrpc, add error reporting for invalid inputs 63 | /// Apply a rate limit to this endpoint. 64 | #[proc_macro_attribute] 65 | pub fn rate(args: TokenStream, input: TokenStream) -> TokenStream { 66 | let mut args = parse_macro_input!(args as AttributeArgs); 67 | let func = parse_macro_input!(input as ItemFn); 68 | 69 | let dur = args.pop().unwrap(); 70 | let num = args.pop().unwrap(); 71 | 72 | let func_name = quote::format_ident!("{}_middleware", func.sig.ident); 73 | 74 | (quote! { 75 | fn #func_name (&self) -> Option { 76 | use hrpc::server::HrpcLayer; 77 | 78 | (!self.disable_ratelimits) 79 | .then(|| HrpcLayer::new(crate::utils::rate_limit( 80 | #num, 81 | std::time::Duration::from_secs(#dur), 82 | self.deps.config.policy.ratelimit.client_ip_header_name.clone(), 83 | self.deps.config.policy.ratelimit.allowed_ips.clone(), 84 | ))) 85 | } 86 | 87 | #func 88 | }) 89 | .into() 90 | } 91 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | # Flake's devShell for non-flake-enabled nix instances 2 | (import 3 | ( 4 | let lock = builtins.fromJSON (builtins.readFile ./flake.lock); 5 | in 6 | fetchTarball { 7 | url = 8 | "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flakeCompat.locked.rev}.tar.gz"; 9 | sha256 = lock.nodes.flakeCompat.locked.narHash; 10 | } 11 | ) 12 | { src = ./.; }).shellNix.default 13 | -------------------------------------------------------------------------------- /src/bin/cmd.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt::Display, io::prelude::*, mem::size_of}; 2 | 3 | use scherzo::{ 4 | config::DbConfig, 5 | db::deser_guild, 6 | impls::{auth::AuthTree, chat::ChatTree}, 7 | }; 8 | 9 | #[tokio::main] 10 | async fn main() -> Result<(), Box> { 11 | let args = std::env::args().skip(1).collect::>(); 12 | let db_path = std::env::var("SCHERZO_DB").unwrap_or_else(|_| "./db".to_string()); 13 | 14 | let db = scherzo::db::open_db(db_path, DbConfig::default()).await; 15 | 16 | let auth_tree = AuthTree::new(&db).await?; 17 | let chat_tree = ChatTree::new(&db).await?; 18 | 19 | match args.first().map(String::as_str).ok_or("no command")? { 20 | "list" => match args.get(1).map(String::as_str).ok_or("need list name")? { 21 | "accounts" => { 22 | let mut stdout = std::io::stdout(); 23 | for res in auth_tree.inner.iter().await { 24 | let (key, _) = res?; 25 | 26 | if let Ok(parsed) = std::str::from_utf8(key.as_ref()) { 27 | if parsed.contains('@') { 28 | writeln!(stdout, "email: {}", parsed)?; 29 | } 30 | } 31 | } 32 | } 33 | "guilds" => { 34 | let mut stdout = std::io::stdout(); 35 | for res in chat_tree.chat_tree.iter().await { 36 | let (key, val) = res?; 37 | 38 | if key.len() == size_of::() { 39 | let guild = deser_guild(val); 40 | let id = 41 | u64::from_be_bytes(key.try_into().expect("failed to convert to id")); 42 | writeln!(stdout, "{}: {:#?}", id, guild)?; 43 | } 44 | } 45 | } 46 | "channels" => { 47 | let guild_id = args 48 | .get(2) 49 | .map(String::as_str) 50 | .ok_or("need guild id")? 51 | .parse::()?; 52 | let channels = chat_tree.get_guild_channels_logic(guild_id, 0).await?; 53 | 54 | let mut stdout = std::io::stdout(); 55 | for channel in channels.channels { 56 | writeln!( 57 | stdout, 58 | "{}: {:#?}", 59 | channel.channel_id, 60 | channel 61 | .channel 62 | .ok_or("channel doesnt have channel object")? 63 | )?; 64 | } 65 | } 66 | _ => exit_with_msg("no such list", 1), 67 | }, 68 | _ => exit_with_msg("no such command", 1), 69 | } 70 | 71 | Ok(()) 72 | } 73 | 74 | fn exit_with_msg(err: impl Display, code: i32) -> ! { 75 | eprintln!("error: {}", err); 76 | std::process::exit(code) 77 | } 78 | -------------------------------------------------------------------------------- /src/bin/migrate.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, path::PathBuf}; 2 | 3 | use hrpc::BoxError; 4 | use itertools::Itertools; 5 | use scherzo::{ 6 | db::{self, sled::shared as sled, sqlite::shared as sqlite, Batch}, 7 | utils::evec::EVec, 8 | }; 9 | 10 | fn main() -> Result<(), BoxError> { 11 | let rt = tokio::runtime::Runtime::new()?; 12 | let _rt_guard = rt.enter(); 13 | 14 | let mut args = std::env::args().collect_vec(); 15 | 16 | if args.len() < 4 { 17 | println!( 18 | "usage: {} \nexample: {} ./db sled sqlite", 19 | args[0], args[0] 20 | ); 21 | std::process::exit(1); 22 | } 23 | 24 | args.reverse(); 25 | args.pop(); 26 | 27 | let db_path = args.pop().expect("expected db path"); 28 | let db_type = args.pop().expect("expected db type to migrate to"); 29 | let db_target = args.pop().expect("expected db type to migrate to"); 30 | let db_config = scherzo::config::DbConfig::default(); 31 | 32 | let mut db_backup_path = PathBuf::from(db_path.clone()); 33 | db_backup_path.set_file_name(format!( 34 | "{}_migrate_backup", 35 | db_backup_path.file_name().unwrap().to_string_lossy() 36 | )); 37 | 38 | std::fs::rename(&db_path, &db_backup_path)?; 39 | 40 | let db_backup_path = db_backup_path.into_os_string().into_string().unwrap(); 41 | 42 | rt.block_on(async move { 43 | let src_db = match db_type.as_str() { 44 | "sled" => Db::Sled(sled::open_database(db_backup_path, db_config.clone()).await?), 45 | "sqlite" => Db::Sqlite(sqlite::open_database(db_backup_path, db_config.clone()).await?), 46 | _ => panic!("no such db"), 47 | }; 48 | 49 | let target_db = match db_target.as_str() { 50 | "sled" => Db::Sled(sled::open_database(db_path, db_config).await?), 51 | "sqlite" => Db::Sqlite(sqlite::open_database(db_path, db_config).await?), 52 | _ => panic!("no such db"), 53 | }; 54 | 55 | let vals = src_db.iter().await?; 56 | target_db.insert(vals).await?; 57 | target_db.flush().await?; 58 | 59 | Ok(()) 60 | }) 61 | } 62 | 63 | type ValueMap = HashMap<&'static [u8], Vec<(EVec, EVec)>, ahash::RandomState>; 64 | 65 | enum Db { 66 | Sled(sled::Db), 67 | Sqlite(sqlite::Db), 68 | } 69 | 70 | impl Db { 71 | async fn iter(&self) -> Result { 72 | let mut treemap = HashMap::with_hasher(ahash::RandomState::new()); 73 | for name in db::TREES { 74 | match self { 75 | Self::Sled(db) => { 76 | let tree = db.open_tree(name).await?; 77 | treemap.insert( 78 | name, 79 | tree.iter().await.fold_ok(Vec::new(), |mut all, item| { 80 | all.push(item); 81 | all 82 | })?, 83 | ); 84 | } 85 | Self::Sqlite(db) => { 86 | let tree = db.open_tree(name).await?; 87 | treemap.insert( 88 | name, 89 | tree.iter().await.fold_ok(Vec::new(), |mut all, item| { 90 | all.push(item); 91 | all 92 | })?, 93 | ); 94 | } 95 | } 96 | } 97 | Ok(treemap) 98 | } 99 | 100 | async fn insert(&self, mut values: ValueMap) -> Result<(), BoxError> { 101 | for name in db::TREES { 102 | match self { 103 | Self::Sled(db) => { 104 | let tree = db.open_tree(name).await?; 105 | let mut batch = Batch::default(); 106 | for (k, v) in values.remove(name).expect("no such tree") { 107 | batch.insert(k, v); 108 | } 109 | tree.apply_batch(batch).await?; 110 | } 111 | Self::Sqlite(db) => { 112 | let tree = db.open_tree(name).await?; 113 | let mut batch = Batch::default(); 114 | for (k, v) in values.remove(name).expect("no such tree") { 115 | batch.insert(k, v); 116 | } 117 | tree.apply_batch(batch).await?; 118 | } 119 | } 120 | } 121 | Ok(()) 122 | } 123 | 124 | async fn flush(&self) -> Result<(), BoxError> { 125 | match self { 126 | Self::Sled(db) => db.flush().await?, 127 | Self::Sqlite(db) => db.flush().await?, 128 | } 129 | Ok(()) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/db/migration/add_account_kind.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use super::*; 4 | 5 | use db::{ 6 | profile::{make_user_profile_key, USER_PREFIX}, 7 | rkyv_ser, Batch, DbError, 8 | }; 9 | use harmony_rust_sdk::api::profile::AccountKind; 10 | use hrpc::box_error; 11 | 12 | use crate::api::profile::Profile as NewProfile; 13 | use profile::Profile as OldProfile; 14 | 15 | pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { 16 | let fut = async move { 17 | let profile_tree = db.open_tree(b"profile").await?; 18 | let mut batch = Batch::default(); 19 | for res in profile_tree.scan_prefix(USER_PREFIX).await { 20 | let (key, val) = res?; 21 | if key.len() == make_user_profile_key(0).len() { 22 | let old_profile = rkyv::from_bytes::(&val); 23 | if let Ok(old_profile) = old_profile { 24 | let new_val = rkyv_ser(&NewProfile { 25 | user_avatar: old_profile.user_avatar, 26 | account_kind: AccountKind::FullUnspecified.into(), 27 | is_bot: old_profile.is_bot, 28 | user_name: old_profile.user_name, 29 | user_status: old_profile.user_status, 30 | }); 31 | batch.insert(key, new_val); 32 | } else if rkyv::check_archived_root::(&val).is_ok() { 33 | // if it's new, then its already fine 34 | continue; 35 | } else { 36 | old_profile.map_err(|err| DbError { 37 | inner: box_error(AnyhowError(anyhow::anyhow!( 38 | "profile with key {} has invalid state: {}", 39 | String::from_utf8_lossy(key.as_ref()), 40 | err 41 | ))), 42 | })?; 43 | } 44 | } 45 | } 46 | profile_tree.apply_batch(batch).await?; 47 | Ok(()) 48 | }; 49 | 50 | Box::pin(fut) 51 | } 52 | 53 | scherzo_derive::define_proto_mod!(before_account_kind, profile); 54 | scherzo_derive::define_proto_mod!(before_account_kind, harmonytypes); 55 | 56 | #[derive(Debug)] 57 | struct AnyhowError(anyhow::Error); 58 | 59 | impl Display for AnyhowError { 60 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 61 | Display::fmt(&self.0, f) 62 | } 63 | } 64 | 65 | impl std::error::Error for AnyhowError {} 66 | -------------------------------------------------------------------------------- /src/db/migration/add_next_msg_ids.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use db::{chat::make_chan_key, deser_chan, Batch}; 4 | 5 | pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { 6 | const CHAN_KEY_LEN: usize = make_chan_key(0, 0).len(); 7 | 8 | let fut = async move { 9 | let chat_tree = db.open_tree(b"chat").await?; 10 | 11 | let mut batch = Batch::default(); 12 | for res in chat_tree.iter().await { 13 | let (key, val) = res?; 14 | let mut key: Vec = key.into(); 15 | 16 | if key.len() == CHAN_KEY_LEN && key[8] == 8 { 17 | deser_chan(val); 18 | key.push(9); 19 | 20 | let id = chat_tree 21 | .scan_prefix(&key) 22 | .await 23 | .last() 24 | // Ensure that the first message ID is always 1! 25 | // otherwise get message id 26 | .map_or(Ok(1), |res| { 27 | res.map(|res| { 28 | u64::from_be_bytes( 29 | res.0 30 | .split_at(key.len()) 31 | .1 32 | .try_into() 33 | .expect("failed to convert to u64 id"), 34 | ) 35 | }) 36 | })?; 37 | 38 | key.pop(); 39 | key.push(7); 40 | 41 | batch.insert(key, id.to_be_bytes()); 42 | } 43 | } 44 | chat_tree.apply_batch(batch).await 45 | }; 46 | 47 | Box::pin(fut) 48 | } 49 | -------------------------------------------------------------------------------- /src/db/migration/initial_db_version.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { 4 | let fut = async move { 5 | let version_tree = db.open_tree(b"version").await?; 6 | if !version_tree.contains_key(b"version").await? { 7 | version_tree 8 | .insert(b"version", &MIGRATIONS.len().to_be_bytes()) 9 | .await?; 10 | } 11 | Ok(()) 12 | }; 13 | 14 | Box::pin(fut) 15 | } 16 | -------------------------------------------------------------------------------- /src/db/migration/mod.rs: -------------------------------------------------------------------------------- 1 | use std::future::Future; 2 | 3 | use hrpc::server::gen_prelude::BoxFuture; 4 | use tracing::Instrument; 5 | 6 | use crate::db; 7 | 8 | use super::{Db, DbResult}; 9 | 10 | mod add_account_kind; 11 | mod add_next_msg_ids; 12 | mod initial_db_version; 13 | mod remove_log_chan_id_from_admin_keys; 14 | mod timestamps_are_milliseconds; 15 | 16 | type Migration = for<'a> fn(&'a Db) -> BoxFuture<'a, DbResult<()>>; 17 | 18 | pub const MIGRATIONS: [Migration; 5] = [ 19 | initial_db_version::migrate, 20 | add_next_msg_ids::migrate, 21 | remove_log_chan_id_from_admin_keys::migrate, 22 | add_account_kind::migrate, 23 | timestamps_are_milliseconds::migrate, 24 | ]; 25 | 26 | pub async fn get_db_version(db: &Db) -> DbResult<(usize, bool)> { 27 | let version_tree = db.open_tree(b"version").await?; 28 | let version = version_tree 29 | .get(b"version") 30 | .await? 31 | .and_then(|raw| Some(usize::from_be_bytes(raw.try_into().ok()?))) 32 | .unwrap_or(0); 33 | Ok((version, version < MIGRATIONS.len())) 34 | } 35 | 36 | pub fn apply_migrations( 37 | db: &Db, 38 | current_version: usize, 39 | ) -> impl Future> + '_ { 40 | let fut = async move { 41 | if current_version == 0 { 42 | initial_db_version::migrate(db).await 43 | } else { 44 | for (version, migration) in MIGRATIONS.into_iter().enumerate().skip(current_version) { 45 | tracing::warn!( 46 | "migrating database from version {} to {}", 47 | version, 48 | version + 1 49 | ); 50 | migration(db).await?; 51 | increment_db_version(db).await?; 52 | } 53 | Ok(()) 54 | } 55 | }; 56 | fut.instrument( 57 | tracing::info_span!("apply_migrations", before_migration_version = %current_version), 58 | ) 59 | } 60 | 61 | async fn increment_db_version(db: &Db) -> DbResult<()> { 62 | let version_tree = db.open_tree(b"version").await?; 63 | if let Some(version) = version_tree 64 | .get(b"version") 65 | .await? 66 | .and_then(|raw| Some(usize::from_be_bytes(raw.try_into().ok()?))) 67 | { 68 | let new_version = version + 1; 69 | tracing::info!( 70 | "migrated database from version {} to {}", 71 | version, 72 | new_version 73 | ); 74 | version_tree 75 | .insert(b"version", &new_version.to_be_bytes()) 76 | .await?; 77 | } 78 | Ok(()) 79 | } 80 | -------------------------------------------------------------------------------- /src/db/migration/remove_log_chan_id_from_admin_keys.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of; 2 | 3 | use crate::db::chat::ADMIN_GUILD_KEY; 4 | 5 | use super::*; 6 | 7 | pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { 8 | let fut = Box::pin(async move { 9 | let chat_tree = db.open_tree(b"chat").await?; 10 | 11 | if let Some(raw) = chat_tree.get(ADMIN_GUILD_KEY).await? { 12 | let (gid_raw, rest) = raw.split_at(size_of::()); 13 | let guild_id = unsafe { u64::from_be_bytes(gid_raw.try_into().unwrap_unchecked()) }; 14 | let (_, cmd_raw) = rest.split_at(size_of::()); 15 | let cmd_id = unsafe { u64::from_be_bytes(cmd_raw.try_into().unwrap_unchecked()) }; 16 | 17 | let new_raw = [guild_id.to_be_bytes(), cmd_id.to_be_bytes()].concat(); 18 | 19 | chat_tree.insert(ADMIN_GUILD_KEY, new_raw).await?; 20 | } 21 | 22 | Ok(()) 23 | }); 24 | 25 | Box::pin(fut) 26 | } 27 | -------------------------------------------------------------------------------- /src/db/migration/timestamps_are_milliseconds.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | use db::{ 4 | rkyv_ser, Batch 5 | }; 6 | use harmony_rust_sdk::api::chat::Message as HarmonyMessage; 7 | 8 | pub(super) fn migrate(db: &Db) -> BoxFuture<'_, DbResult<()>> { 9 | let fut = async move { 10 | let chat_tree = db.open_tree(b"chat").await?; 11 | let mut batch = Batch::default(); 12 | let pfix = []; 13 | 14 | for res in chat_tree.scan_prefix(&pfix).await { 15 | let (key, val) = res?; 16 | 17 | let message = rkyv::from_bytes::(&val); 18 | let Ok(mut msg) = message else { 19 | continue; 20 | }; 21 | 22 | msg.created_at *= 1000; 23 | msg.edited_at = msg.edited_at.map(|x| x * 1000); 24 | 25 | batch.insert(key, rkyv_ser(&msg)); 26 | } 27 | 28 | chat_tree.apply_batch(batch).await?; 29 | Ok(()) 30 | }; 31 | 32 | Box::pin(fut) 33 | } 34 | -------------------------------------------------------------------------------- /src/db/sled.rs: -------------------------------------------------------------------------------- 1 | use std::ops::RangeInclusive; 2 | 3 | use hrpc::common::future::Ready; 4 | 5 | use crate::{config::DbConfig, utils::evec::EVec}; 6 | 7 | use super::{Batch, DbError, DbResult}; 8 | 9 | type SledFut = Ready>; 10 | 11 | pub mod shared { 12 | use hrpc::common::future::ready; 13 | 14 | use super::*; 15 | 16 | pub async fn open_database(db_path: String, db_config: DbConfig) -> DbResult { 17 | tokio::task::spawn_blocking(move || -> DbResult { 18 | let db = sled::Config::new() 19 | .use_compression(true) 20 | .path(db_path) 21 | .cache_capacity(db_config.db_cache_limit * 1024 * 1024) 22 | .mode( 23 | db_config 24 | .sled_throughput_at_storage_cost 25 | .then(|| sled::Mode::HighThroughput) 26 | .unwrap_or(sled::Mode::LowSpace), 27 | ) 28 | .open() 29 | .and_then(|db| db.verify_integrity().map(|_| db))?; 30 | 31 | if db_config.sled_load_to_cache_on_startup { 32 | for tree_name in db.tree_names() { 33 | let tree = db.open_tree(tree_name)?; 34 | for res in tree.iter() { 35 | res?; 36 | } 37 | } 38 | } 39 | 40 | Ok(Db { inner: db }) 41 | }) 42 | .await 43 | .unwrap() 44 | } 45 | 46 | pub fn open_temp() -> Db { 47 | let inner = sled::Config::new() 48 | .temporary(true) 49 | .open() 50 | .expect("failed to create temp db"); 51 | 52 | Db { inner } 53 | } 54 | 55 | #[derive(Debug, Clone)] 56 | pub struct Db { 57 | inner: sled::Db, 58 | } 59 | 60 | impl Db { 61 | pub fn open_tree(&self, name: &[u8]) -> SledFut { 62 | ready( 63 | self.inner 64 | .open_tree(name) 65 | .map_err(Into::into) 66 | .map(|tree| Tree { inner: tree }), 67 | ) 68 | } 69 | 70 | pub async fn flush(&self) -> DbResult<()> { 71 | self.inner 72 | .flush_async() 73 | .await 74 | .map(|_| ()) 75 | .map_err(Into::into) 76 | } 77 | } 78 | 79 | #[derive(Debug, Clone)] 80 | pub struct Tree { 81 | inner: sled::Tree, 82 | } 83 | 84 | impl Tree { 85 | pub fn get(&self, key: &[u8]) -> SledFut> { 86 | ready( 87 | self.inner 88 | .get(key) 89 | .map_err(Into::into) 90 | .map(|opt| opt.map(|i| i.into())), 91 | ) 92 | } 93 | 94 | pub fn insert(&self, key: &[u8], value: impl Into) -> SledFut> { 95 | ready( 96 | self.inner 97 | .insert(key, value) 98 | .map_err(Into::into) 99 | .map(|opt| opt.map(|i| i.into())), 100 | ) 101 | } 102 | 103 | pub fn remove(&self, key: &[u8]) -> SledFut> { 104 | ready( 105 | self.inner 106 | .remove(key) 107 | .map_err(Into::into) 108 | .map(|opt| opt.map(|i| i.into())), 109 | ) 110 | } 111 | 112 | pub fn scan_prefix<'a>( 113 | &'a self, 114 | prefix: &[u8], 115 | ) -> Ready> + 'a> { 116 | ready( 117 | self.inner 118 | .scan_prefix(prefix) 119 | .map(|res| res.map(|(a, b)| (a.into(), b.into())).map_err(Into::into)), 120 | ) 121 | } 122 | 123 | pub fn iter(&self) -> Ready> + '_> { 124 | ready( 125 | self.inner 126 | .iter() 127 | .map(|res| res.map(|(a, b)| (a.into(), b.into())).map_err(Into::into)), 128 | ) 129 | } 130 | 131 | pub fn apply_batch(&self, batch: Batch) -> SledFut<()> { 132 | ready(self.inner.apply_batch(batch.into()).map_err(Into::into)) 133 | } 134 | 135 | pub fn contains_key(&self, key: &[u8]) -> SledFut { 136 | ready(self.inner.contains_key(key).map_err(Into::into)) 137 | } 138 | 139 | pub fn range<'a>( 140 | &'a self, 141 | range: RangeInclusive<&[u8]>, 142 | ) -> Ready> + DoubleEndedIterator + 'a> 143 | { 144 | ready( 145 | self.inner 146 | .range(range) 147 | .map(|res| res.map(|(a, b)| (a.into(), b.into())).map_err(Into::into)), 148 | ) 149 | } 150 | 151 | pub fn verify_integrity(&self) -> SledFut<()> { 152 | ready(self.inner.verify_integrity().map_err(Into::into)) 153 | } 154 | } 155 | } 156 | 157 | impl From for DbError { 158 | fn from(inner: sled::Error) -> Self { 159 | DbError { 160 | inner: Box::new(inner), 161 | } 162 | } 163 | } 164 | 165 | impl From for sled::Batch { 166 | fn from(batch: Batch) -> Self { 167 | let mut sled = sled::Batch::default(); 168 | for (key, value) in batch.inserts { 169 | match value { 170 | Some(value) => sled.insert(key, value), 171 | None => sled.remove(key), 172 | } 173 | } 174 | sled 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/impls/admin_action.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub const HELP_TEXT: &str = r#" 4 | all commands should be prefixed with `/`. 5 | 6 | commands are: 7 | `generate token` -> generates a single use token 8 | `check token ` -> checks if a token is valid without using it 9 | `delete user ` -> deletes a user from the server 10 | `set motd ` -> sets the server MOTD 11 | `help` -> shows help 12 | "#; 13 | 14 | pub struct AdminActionError; 15 | 16 | #[derive(Debug, Clone)] 17 | pub enum AdminAction { 18 | SetMotd(String), 19 | DeleteUser(u64), 20 | GenerateToken, 21 | CheckToken(String), 22 | Help, 23 | } 24 | 25 | impl FromStr for AdminAction { 26 | type Err = AdminActionError; 27 | 28 | fn from_str(s: &str) -> Result { 29 | let act = match s.strip_prefix('/').ok_or(AdminActionError)? { 30 | "generate token" => AdminAction::GenerateToken, 31 | "help" => AdminAction::Help, 32 | s => { 33 | if let Some(s) = s.strip_prefix("delete user") { 34 | let user_id = s.trim().parse::().map_err(|_| AdminActionError)?; 35 | AdminAction::DeleteUser(user_id) 36 | } else if let Some(s) = s.strip_prefix("set motd") { 37 | let new_motd = s.trim().to_string(); 38 | AdminAction::SetMotd(new_motd) 39 | } else if let Some(s) = s.strip_prefix("check token") { 40 | let token = s.trim(); 41 | AdminAction::CheckToken(token.to_string()) 42 | } else { 43 | return Err(AdminActionError); 44 | } 45 | } 46 | }; 47 | Ok(act) 48 | } 49 | } 50 | 51 | pub async fn run_str(deps: &Dependencies, action: &str) -> ServerResult { 52 | let maybe_action = AdminAction::from_str(action); 53 | match maybe_action { 54 | Ok(action) => run(deps, action).await, 55 | Err(_) => Ok(format!("invalid command: `{}`", action)), 56 | } 57 | } 58 | 59 | pub async fn run(deps: &Dependencies, action: AdminAction) -> ServerResult { 60 | match action { 61 | AdminAction::GenerateToken => { 62 | let token = deps.auth_tree.generate_single_use_token([]).await?; 63 | Ok(token.into()) 64 | } 65 | AdminAction::DeleteUser(user_id) => { 66 | auth::delete_user::logic(deps, user_id).await?; 67 | Ok(format!("deleted user {}", user_id)) 68 | } 69 | AdminAction::CheckToken(token) => { 70 | let token_valid = deps.auth_tree.check_single_use_token(token).await.is_ok(); 71 | let msg = token_valid 72 | .then(|| "token is valid") 73 | .unwrap_or("token is invalid"); 74 | Ok(msg.to_string()) 75 | } 76 | AdminAction::SetMotd(new_motd) => { 77 | deps.runtime_config.lock().motd = new_motd; 78 | Ok("new MOTD set".to_string()) 79 | } 80 | AdminAction::Help => Ok(HELP_TEXT.to_string()), 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/impls/against.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::Infallible, 3 | future::Future, 4 | pin::Pin, 5 | task::{Context, Poll}, 6 | }; 7 | 8 | use hrpc::exports::futures_util::ready; 9 | use hrpc::{ 10 | client::transport::http::hyper::{http_client, HttpClient}, 11 | server::transport::http::{box_body, HttpRequest, HttpResponse}, 12 | }; 13 | use hyper::{client::ResponseFuture, header::HeaderName}; 14 | use pin_project::pin_project; 15 | use tower::{Layer, Service}; 16 | 17 | use super::*; 18 | 19 | #[derive(Clone)] 20 | pub struct AgainstLayer; 21 | 22 | impl Layer for AgainstLayer 23 | where 24 | S: tower::Service + Send + 'static, 25 | { 26 | type Service = AgainstService; 27 | 28 | fn layer(&self, inner: S) -> Self::Service { 29 | AgainstService { 30 | http: http_client(&mut hyper::Client::builder()), 31 | header_name: HeaderName::from_static("against"), 32 | inner, 33 | } 34 | } 35 | } 36 | 37 | pub struct AgainstService { 38 | http: HttpClient, 39 | header_name: HeaderName, 40 | inner: S, 41 | } 42 | 43 | impl Service for AgainstService 44 | where 45 | S: tower::Service + Send + 'static, 46 | { 47 | type Response = HttpResponse; 48 | 49 | type Error = Infallible; 50 | 51 | type Future = AgainstFuture; 52 | 53 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 54 | Service::poll_ready(&mut self.inner, cx) 55 | } 56 | 57 | fn call(&mut self, request: HttpRequest) -> Self::Future { 58 | let maybe_host_id = request.headers().get(&self.header_name).and_then(|header| { 59 | header 60 | .to_str() 61 | .ok() 62 | .and_then(|v| HomeserverIdentifier::from_str(v).ok()) 63 | }); 64 | if let Some(host_id) = maybe_host_id { 65 | let url = { 66 | let mut url_parts = host_id.to_url().into_parts(); 67 | url_parts.path_and_query = Some(request.uri().path().parse().unwrap()); 68 | Uri::from_parts(url_parts).unwrap() 69 | }; 70 | let (parts, body) = request.into_parts(); 71 | 72 | let mut request = http::Request::builder() 73 | .uri(url) 74 | .method(parts.method) 75 | .body(body) 76 | .unwrap(); 77 | *request.headers_mut() = parts.headers; 78 | *request.extensions_mut() = parts.extensions; 79 | 80 | AgainstFuture::Remote(self.http.request(request)) 81 | } else { 82 | AgainstFuture::Local(tower::Service::call(&mut self.inner, request)) 83 | } 84 | } 85 | } 86 | 87 | #[pin_project(project = EnumProj)] 88 | pub enum AgainstFuture { 89 | Remote(#[pin] ResponseFuture), 90 | Local(#[pin] Fut), 91 | } 92 | 93 | impl Future for AgainstFuture 94 | where 95 | Fut: Future>, 96 | { 97 | type Output = Result; 98 | 99 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 100 | let this = self.project(); 101 | 102 | match this { 103 | EnumProj::Local(fut) => fut.poll(cx), 104 | EnumProj::Remote(fut) => { 105 | let res = ready!(fut.poll(cx)); 106 | Poll::Ready(Ok(res.map(|r| r.map(box_body)).unwrap_or_else(|err| { 107 | ServerError::HttpError(err).into_http_response() 108 | }))) 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/impls/auth/begin_auth.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &AuthServer, 5 | _: Request, 6 | ) -> ServerResult> { 7 | let auth_id = gen_rand_inline_str(); 8 | 9 | // [tag:step_stack_non_empty] 10 | svc.step_map 11 | .entry(auth_id.clone()) 12 | .and_modify(|s| *s = vec![initial_auth_step()]) 13 | .or_insert_with(|| vec![initial_auth_step()]); 14 | 15 | tracing::debug!("new auth session {}", auth_id); 16 | 17 | Ok((BeginAuthResponse { 18 | auth_id: auth_id.into(), 19 | }) 20 | .into_response()) 21 | } 22 | -------------------------------------------------------------------------------- /src/impls/auth/check_logged_in.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &AuthServer, 5 | request: Request, 6 | ) -> Result, HrpcServerError> { 7 | svc.deps.auth(&request).await?; 8 | Ok((CheckLoggedInResponse {}).into_response()) 9 | } 10 | -------------------------------------------------------------------------------- /src/impls/auth/delete_user.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn logic(deps: &Dependencies, user_id: u64) -> ServerResult<()> { 4 | // delete from auth first 5 | let mut batch = Batch::default(); 6 | batch.remove(user_id.to_be_bytes()); 7 | batch.remove(token_key(user_id)); 8 | batch.remove(atime_key(user_id)); 9 | 10 | deps.auth_tree.apply_batch(batch).await?; 11 | 12 | // set profile to deleted 13 | deps.profile_tree 14 | .update_profile_logic( 15 | user_id, 16 | Some("Deleted User".to_string()), 17 | Some("".to_string()), 18 | Some(UserStatus::OfflineUnspecified.into()), 19 | Some(false), 20 | ) 21 | .await?; 22 | 23 | // remove metadata 24 | db::batch_delete_prefix( 25 | &deps.profile_tree.inner, 26 | db::profile::make_user_metadata_prefix(user_id), 27 | ) 28 | .await?; 29 | 30 | // remove guild list 31 | db::batch_delete_prefix( 32 | &deps.chat_tree.chat_tree, 33 | db::chat::make_guild_list_key_prefix(user_id), 34 | ) 35 | .await?; 36 | 37 | // end stream event 38 | let _ = deps.chat_event_canceller.send(user_id); 39 | 40 | Ok(()) 41 | } 42 | -------------------------------------------------------------------------------- /src/impls/auth/federate.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &AuthServer, 5 | request: Request, 6 | ) -> Result, HrpcServerError> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let keys_manager = svc.keys_manager()?; 10 | 11 | let profile = svc.deps.profile_tree.get_profile_logic(user_id).await?; 12 | let server_id = request.into_message().await?.server_id; 13 | 14 | svc.is_host_allowed(&server_id)?; 15 | 16 | let data = TokenData { 17 | user_id, 18 | server_id, 19 | username: profile.user_name, 20 | avatar: profile.user_avatar, 21 | }; 22 | 23 | let token = keys_manager.generate_token(data).await?; 24 | 25 | Ok((FederateResponse { token: Some(token) }).into_response()) 26 | } 27 | -------------------------------------------------------------------------------- /src/impls/auth/key.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &AuthServer, 5 | _: Request, 6 | ) -> ServerResult> { 7 | let keys_manager = svc.keys_manager()?; 8 | let key = keys_manager.get_own_key().await?; 9 | 10 | Ok((KeyResponse { 11 | key: key.pk.to_vec(), 12 | }) 13 | .into_response()) 14 | } 15 | -------------------------------------------------------------------------------- /src/impls/auth/login_federated.rs: -------------------------------------------------------------------------------- 1 | use crate::api::profile::AccountKind; 2 | 3 | use super::*; 4 | 5 | pub async fn handler( 6 | svc: &AuthServer, 7 | request: Request, 8 | ) -> Result, HrpcServerError> { 9 | let LoginFederatedRequest { 10 | auth_token, 11 | server_id, 12 | } = request.into_message().await?; 13 | 14 | svc.is_host_allowed(&server_id)?; 15 | 16 | if let Some(token) = auth_token { 17 | let keys_manager = svc.keys_manager()?; 18 | let pubkey = keys_manager.get_key(server_id.into()).await?; 19 | keys::verify_token(&token, &pubkey)?; 20 | let TokenData { 21 | user_id: foreign_id, 22 | server_id, 23 | username, 24 | avatar, 25 | } = TokenData::decode(token.data.as_slice()).map_err(|_| ServerError::InvalidTokenData)?; 26 | 27 | let local_user_id = if let Some(id) = svc 28 | .deps 29 | .profile_tree 30 | .foreign_to_local_id(foreign_id, &server_id) 31 | .await? 32 | { 33 | id 34 | } else { 35 | let local_id = gen_rand_u64(); 36 | 37 | let mut batch = Batch::default(); 38 | // Add the local to foreign user key entry 39 | batch.insert( 40 | make_local_to_foreign_user_key(local_id).to_vec(), 41 | [&foreign_id.to_be_bytes(), server_id.as_bytes()].concat(), 42 | ); 43 | // Add the foreign to local user key entry 44 | batch.insert( 45 | make_foreign_to_local_user_key(foreign_id, &server_id), 46 | local_id.to_be_bytes().to_vec(), 47 | ); 48 | // Add the profile entry 49 | let profile = Profile { 50 | is_bot: false, 51 | user_status: UserStatus::OfflineUnspecified.into(), 52 | user_avatar: avatar, 53 | user_name: username, 54 | account_kind: AccountKind::FullUnspecified.into(), 55 | }; 56 | let buf = rkyv_ser(&profile); 57 | batch.insert(make_user_profile_key(local_id).to_vec(), buf); 58 | svc.deps 59 | .profile_tree 60 | .inner 61 | .apply_batch(batch) 62 | .await 63 | .map_err(ServerError::DbError)?; 64 | 65 | local_id 66 | }; 67 | 68 | let session_token = svc.gen_auth_token().await?; 69 | let session = Session { 70 | session_token: session_token.to_string(), 71 | user_id: local_user_id, 72 | guest_token: None, 73 | }; 74 | svc.deps 75 | .auth_tree 76 | .insert( 77 | auth_key(session_token.as_str()), 78 | local_user_id.to_be_bytes(), 79 | ) 80 | .await?; 81 | 82 | return Ok((LoginFederatedResponse { 83 | session: Some(session), 84 | }) 85 | .into_response()); 86 | } 87 | 88 | Err(ServerError::InvalidToken.into()) 89 | } 90 | -------------------------------------------------------------------------------- /src/impls/auth/next_step/delete_user.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | // "delete-user-input-token" 4 | pub async fn handle_input_token( 5 | svc: &AuthServer, 6 | values: &mut Vec, 7 | ) -> ServerResult { 8 | let token = try_get_token(values)?; 9 | 10 | let raw_user_id = svc.deps.auth_tree.validate_single_use_token(token).await?; 11 | let user_id = db::deser_id(raw_user_id); 12 | 13 | crate::impls::auth::delete_user::logic(svc.deps.as_ref(), user_id).await?; 14 | 15 | Ok(back_to_inital_step()) 16 | } 17 | 18 | // "delete-user-send-token" 19 | pub async fn handle_send_token( 20 | svc: &AuthServer, 21 | values: &mut Vec, 22 | ) -> ServerResult { 23 | let auth_tree = &svc.deps.auth_tree; 24 | 25 | let user_email = try_get_email(values)?; 26 | 27 | let user_id = auth_tree.get_user_id(&user_email).await?; 28 | 29 | let token = auth_tree 30 | .generate_single_use_token(user_id.to_be_bytes()) 31 | .await?; 32 | 33 | email::send_token_email( 34 | svc.deps.as_ref(), 35 | &user_email, 36 | token.as_ref(), 37 | "delete account", 38 | ) 39 | .await?; 40 | 41 | Ok(AuthStep { 42 | can_go_back: false, 43 | fallback_url: String::default(), 44 | step: form("delete-user-input-token", [("token", "password")]), 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /src/impls/auth/next_step/email.rs: -------------------------------------------------------------------------------- 1 | use crate::impls::send_email; 2 | 3 | use super::*; 4 | 5 | const EMAIL_BODY_TEMPLATE_PLAIN: &str = 6 | include_str!("../../../../resources/email-token-template.txt"); 7 | //const EMAIL_BODY_TEMPLATE_HTML: &str = 8 | // include_str!("../../../../resources/email-token-template.html"); 9 | 10 | //const LILLIES_SVG: &[u8] = include_bytes!("../../../../resources/lillies.svg"); 11 | //const LOTUS_SVG: &[u8] = include_bytes!("../../../../resources/lotus.svg"); 12 | 13 | pub async fn send_token_email( 14 | deps: &Dependencies, 15 | to: &str, 16 | token: &str, 17 | action: &str, 18 | ) -> ServerResult<()> { 19 | /*let html_body = EMAIL_BODY_TEMPLATE_HTML 20 | .replace("{{action}}", action) 21 | .replace("{{token}}", token);*/ 22 | let plain_body = EMAIL_BODY_TEMPLATE_PLAIN 23 | .replace("{{action}}", action) 24 | .replace("{{token}}", token); 25 | 26 | /*let files = vec![ 27 | ( 28 | LILLIES_SVG.to_vec(), 29 | "lillies".to_string(), 30 | "image/svg+xml".to_string(), 31 | true, 32 | ), 33 | ( 34 | LOTUS_SVG.to_vec(), 35 | "lotus".to_string(), 36 | "image/svg+xml".to_string(), 37 | true, 38 | ), 39 | ];*/ 40 | 41 | let subject = format!("Harmony - {} for {}", action, &deps.config.host); 42 | 43 | send_email(deps, to, subject, plain_body, None, Vec::new()).await?; 44 | 45 | Ok(()) 46 | } 47 | -------------------------------------------------------------------------------- /src/impls/auth/next_step/login.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handle(svc: &AuthServer, values: &mut Vec) -> ServerResult { 4 | let auth_tree = &svc.deps.auth_tree; 5 | 6 | let password_raw = try_get_password(values)?; 7 | let email = try_get_email(values)?; 8 | 9 | let user_id = auth_tree.get_user_id(&email).await?; 10 | 11 | // check password 12 | let is_password_correct = 13 | auth_tree 14 | .get(user_id.to_be_bytes().as_ref()) 15 | .await? 16 | .map_or(false, |pass_hash| { 17 | let pass_hash = unsafe { std::str::from_utf8_unchecked(pass_hash.as_ref()) }; 18 | verify_password(password_raw, pass_hash) 19 | }); 20 | if !is_password_correct { 21 | bail!(ServerError::WrongEmailOrPassword { 22 | email: email.into(), 23 | }); 24 | } 25 | 26 | let session_token = svc.gen_auth_token().await?; // [ref:alphanumeric_auth_token_gen] [ref:auth_token_length] 27 | let mut batch = Batch::default(); 28 | // [ref:token_u64_key] 29 | batch.insert(token_key(user_id), session_token.as_str().as_bytes()); 30 | batch.insert( 31 | // [ref:atime_u64_key] 32 | atime_key(user_id), 33 | // [ref:atime_u64_value] 34 | get_time_secs().to_be_bytes(), 35 | ); 36 | auth_tree.apply_batch(batch).await?; 37 | 38 | tracing::debug!("user {} logged in with email {}", user_id, email); 39 | 40 | auth_tree 41 | .insert(auth_key(session_token.as_str()), user_id.to_be_bytes()) 42 | .await?; 43 | 44 | Ok(AuthStep { 45 | can_go_back: false, 46 | fallback_url: String::default(), 47 | step: Some(auth_step::Step::Session(Session { 48 | user_id, 49 | session_token: session_token.into(), 50 | guest_token: None, 51 | })), 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/impls/auth/next_step/registration.rs: -------------------------------------------------------------------------------- 1 | use rkyv::{de::deserializers::SharedDeserializeMap, Archive, Deserialize, Serialize}; 2 | 3 | use super::*; 4 | 5 | #[derive(Debug, Archive, Serialize, Deserialize)] 6 | #[archive_attr(derive(bytecheck::CheckBytes))] 7 | struct RegInfo { 8 | email: String, 9 | username: String, 10 | password_raw: Vec, 11 | } 12 | 13 | pub async fn handle(svc: &AuthServer, values: &mut Vec) -> ServerResult { 14 | let auth_tree = &svc.deps.auth_tree; 15 | let config = &svc.deps.config; 16 | 17 | if config.policy.disable_registration { 18 | let token_raw = try_get_token(values)?; 19 | auth_tree.validate_single_use_token(token_raw).await?; 20 | } 21 | 22 | let password_raw = try_get_password(values)?; 23 | let username = try_get_username(values)?; 24 | let email = try_get_email(values)?; 25 | 26 | if svc.deps.email.is_some() 27 | && config.email.is_some() 28 | && !config.policy.disable_registration_email_validation 29 | { 30 | let reg_info = RegInfo { 31 | email, 32 | username, 33 | password_raw, 34 | }; 35 | let reg_info_serialized = rkyv_ser(®_info); 36 | 37 | let token = auth_tree 38 | .generate_single_use_token(reg_info_serialized) 39 | .await?; 40 | 41 | email::send_token_email( 42 | svc.deps.as_ref(), 43 | ®_info.email, 44 | token.as_ref(), 45 | "register", 46 | ) 47 | .await?; 48 | 49 | return Ok(AuthStep { 50 | can_go_back: false, 51 | fallback_url: String::default(), 52 | step: form("register-input-token", [("token", "password")]), 53 | }); 54 | } 55 | 56 | logic(svc, password_raw, username, email).await 57 | } 58 | 59 | pub async fn handle_input_token( 60 | svc: &AuthServer, 61 | values: &mut Vec, 62 | ) -> ServerResult { 63 | let token = try_get_token(values)?; 64 | 65 | let reg_info_raw = svc.deps.auth_tree.validate_single_use_token(token).await?; 66 | let reg_info: RegInfo = rkyv_arch::(®_info_raw) 67 | .deserialize(&mut SharedDeserializeMap::default()) 68 | .expect("must be correct"); 69 | 70 | logic( 71 | svc, 72 | reg_info.password_raw, 73 | reg_info.username, 74 | reg_info.email, 75 | ) 76 | .await 77 | } 78 | 79 | pub async fn logic( 80 | svc: &AuthServer, 81 | password_raw: Vec, 82 | username: String, 83 | email: String, 84 | ) -> ServerResult { 85 | let auth_tree = &svc.deps.auth_tree; 86 | 87 | if password_raw.is_empty() { 88 | bail!(("h.invalid-password", "password can't be empty")); 89 | } 90 | let password_hashed = hash_password(password_raw); 91 | 92 | if username.is_empty() { 93 | bail!(("h.invalid-username", "username can't be empty")); 94 | } 95 | 96 | if email.is_empty() { 97 | bail!(("h.invalid-email", "email can't be empty")); 98 | } 99 | 100 | if auth_tree.get(email.as_bytes()).await?.is_some() { 101 | bail!(ServerError::UserAlreadyExists); 102 | } 103 | 104 | if svc.deps.profile_tree.does_username_exist(&username).await? { 105 | bail!(ServerError::UserAlreadyExists); 106 | } 107 | 108 | let user_id = svc.gen_user_id().await?; 109 | let session_token = svc.gen_auth_token().await?; // [ref:alphanumeric_auth_token_gen] [ref:auth_token_length] 110 | 111 | let mut batch = Batch::default(); 112 | batch.insert(email.into_bytes(), user_id.to_be_bytes()); 113 | batch.insert(user_id.to_be_bytes(), password_hashed.into_bytes()); 114 | // [ref:token_u64_key] 115 | batch.insert(token_key(user_id), session_token.as_str().as_bytes()); 116 | batch.insert( 117 | // [ref:atime_u64_key] 118 | atime_key(user_id), 119 | // [ref:atime_u64_value] 120 | get_time_secs().to_be_bytes(), 121 | ); 122 | auth_tree.apply_batch(batch).await?; 123 | 124 | let buf = rkyv_ser(&Profile { 125 | user_name: username, 126 | ..Default::default() 127 | }); 128 | svc.deps 129 | .profile_tree 130 | .insert(make_user_profile_key(user_id), buf) 131 | .await?; 132 | 133 | tracing::debug!("new user {} registered", user_id); 134 | 135 | auth_tree 136 | .insert(auth_key(session_token.as_str()), user_id.to_be_bytes()) 137 | .await?; 138 | 139 | Ok(AuthStep { 140 | can_go_back: false, 141 | fallback_url: String::default(), 142 | step: Some(auth_step::Step::Session(Session { 143 | user_id, 144 | session_token: session_token.into(), 145 | guest_token: None, 146 | })), 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /src/impls/auth/next_step/reset_password.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | // "reset-password-input-token" 4 | pub async fn handle_input_token( 5 | svc: &AuthServer, 6 | values: &mut Vec, 7 | ) -> ServerResult { 8 | let new_password_raw = try_get_password(values)?; 9 | let token = try_get_token(values)?; 10 | 11 | let raw_user_id = svc.deps.auth_tree.validate_single_use_token(token).await?; 12 | 13 | let hashed_password = hash_password(new_password_raw); 14 | svc.deps 15 | .auth_tree 16 | .insert(raw_user_id, hashed_password) 17 | .await?; 18 | 19 | Ok(back_to_inital_step()) 20 | } 21 | 22 | // "reset-password-send-token" 23 | pub async fn handle_send_token( 24 | svc: &AuthServer, 25 | values: &mut Vec, 26 | ) -> ServerResult { 27 | let auth_tree = &svc.deps.auth_tree; 28 | 29 | let user_email = try_get_email(values)?; 30 | 31 | let user_id = auth_tree.get_user_id(&user_email).await?; 32 | 33 | let token = auth_tree 34 | .generate_single_use_token(user_id.to_be_bytes()) 35 | .await?; 36 | 37 | email::send_token_email( 38 | svc.deps.as_ref(), 39 | &user_email, 40 | token.as_ref(), 41 | "reset password", 42 | ) 43 | .await?; 44 | 45 | Ok(AuthStep { 46 | can_go_back: false, 47 | fallback_url: String::default(), 48 | step: form( 49 | "reset-password-input-token", 50 | [("token", "password"), ("new-password", "password")], 51 | ), 52 | }) 53 | } 54 | -------------------------------------------------------------------------------- /src/impls/auth/step_back.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &AuthServer, 5 | req: Request, 6 | ) -> ServerResult> { 7 | let req = req.into_message().await?; 8 | let auth_id = req.auth_id; 9 | 10 | let prev_step; 11 | let Some(mut step_stack) = svc.step_map.get_mut(auth_id.as_str()) else { 12 | bail!(ServerError::InvalidAuthId); 13 | }; 14 | 15 | // Safety: step stack can never be empty [ref:step_stack_non_empty] 16 | if unsafe { step_stack.last().unwrap_unchecked().can_go_back } { 17 | step_stack.pop(); 18 | tracing::debug!("auth session {} went to previous step", auth_id); 19 | } else { 20 | tracing::debug!( 21 | "auth session {} wanted prev step, but we can't go back", 22 | auth_id 23 | ); 24 | } 25 | 26 | // Safety: step stack can never be empty [ref:step_stack_non_empty] 27 | prev_step = unsafe { step_stack.last().unwrap_unchecked().clone() }; 28 | 29 | drop(step_stack); 30 | 31 | if let Some(chan) = svc.send_step.get(auth_id.as_str()) { 32 | tracing::debug!("sending prev step to {} stream", auth_id); 33 | if let Err(err) = chan.send(prev_step.clone()).await { 34 | tracing::error!("failed to send auth step to {}: {}", auth_id, err); 35 | } 36 | } else { 37 | tracing::debug!("no stream found for auth id {}, pushing to queue", auth_id); 38 | svc.queued_steps 39 | .entry(auth_id.into()) 40 | .and_modify(|s| s.push(prev_step.clone())) 41 | .or_insert_with(|| vec![prev_step.clone()]); 42 | } 43 | 44 | Ok((StepBackResponse { 45 | step: Some(prev_step), 46 | }) 47 | .into_response()) 48 | } 49 | -------------------------------------------------------------------------------- /src/impls/auth/stream_steps.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &AuthServer, 5 | _request: Request<()>, 6 | mut socket: Socket, 7 | ) -> Result<(), HrpcServerError> { 8 | let msg = socket.receive_message().await?; 9 | 10 | let auth_id: SmolStr = msg.auth_id.into(); 11 | 12 | if svc.step_map.contains_key(auth_id.as_str()) { 13 | tracing::debug!("auth id {} validated", auth_id); 14 | } else { 15 | tracing::error!("auth id {} is not valid", auth_id); 16 | return Err(ServerError::InvalidAuthId.into()); 17 | } 18 | 19 | tracing::debug!("creating stream for id {}", auth_id); 20 | 21 | if let Some(mut queued_steps) = svc.queued_steps.get_mut(auth_id.as_str()) { 22 | for step in queued_steps.drain(..) { 23 | if let Err(err) = socket 24 | .send_message(StreamStepsResponse { step: Some(step) }) 25 | .await 26 | { 27 | tracing::error!( 28 | "error occured while sending step to id {}: {}", 29 | auth_id, 30 | err 31 | ); 32 | 33 | // Return from func since we errored 34 | return Err(err.into()); 35 | } 36 | } 37 | } 38 | 39 | let (tx, mut rx) = mpsc::channel(64); 40 | svc.send_step.insert(auth_id.clone(), tx); 41 | tracing::debug!("pushed stream tx for id {}", auth_id); 42 | 43 | let mut error = None; 44 | loop { 45 | tokio::select! { 46 | biased; 47 | Some(step) = rx.recv() => { 48 | tracing::debug!("received auth step to send to id {}", auth_id); 49 | let end_stream = matches!( 50 | step, 51 | AuthStep { 52 | step: Some(auth_step::Step::Session(_)), 53 | .. 54 | } 55 | ); 56 | 57 | if let Err(err) = socket 58 | .send_message(StreamStepsResponse { step: Some(step) }) 59 | .await 60 | { 61 | tracing::error!( 62 | "error occured while sending step to id {}: {}", 63 | auth_id, 64 | err 65 | ); 66 | 67 | // Break from loop since we errored 68 | break; 69 | } 70 | 71 | // Break if we authed 72 | if end_stream { 73 | // Close the socket 74 | socket.close().await?; 75 | break; 76 | } 77 | } 78 | Err(err) = socket.receive_message() => { 79 | error = Some(err); 80 | break; 81 | } 82 | else => tokio::task::yield_now().await, 83 | } 84 | } 85 | 86 | svc.send_step.remove(&auth_id); 87 | tracing::debug!("removing stream for id {}", auth_id); 88 | 89 | error.map_or(Ok(()), |err| Err(err.into())) 90 | } 91 | -------------------------------------------------------------------------------- /src/impls/batch/batch.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &BatchServer, 5 | mut request: Request, 6 | ) -> ServerResult> { 7 | let auth_header = request 8 | .header_map_mut() 9 | .and_then(|h| h.remove(header::AUTHORIZATION)); 10 | let BatchRequest { requests } = request.into_message().await?; 11 | 12 | let request_len = requests.len(); 13 | let (bodies, endpoints) = requests.into_iter().fold( 14 | ( 15 | Vec::with_capacity(request_len), 16 | Vec::with_capacity(request_len), 17 | ), 18 | |(mut bodies, mut endpoints), request| { 19 | bodies.push(request.request); 20 | endpoints.push(request.endpoint); 21 | (bodies, endpoints) 22 | }, 23 | ); 24 | let responses = svc 25 | .make_req(bodies, Endpoint::Different(endpoints), auth_header) 26 | .await?; 27 | 28 | Ok((BatchResponse { responses }).into_response()) 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/batch/batch_same.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &BatchServer, 5 | mut request: Request, 6 | ) -> ServerResult> { 7 | let auth_header = request 8 | .header_map_mut() 9 | .and_then(|h| h.remove(header::AUTHORIZATION)); 10 | let BatchSameRequest { endpoint, requests } = request.into_message().await?; 11 | 12 | let responses = svc 13 | .make_req(requests, Endpoint::Same(endpoint), auth_header) 14 | .await?; 15 | 16 | Ok((BatchSameResponse { responses }).into_response()) 17 | } 18 | -------------------------------------------------------------------------------- /src/impls/chat/channels/create_channel.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let CreateChannelRequest { 10 | guild_id, 11 | channel_name, 12 | kind, 13 | position, 14 | metadata, 15 | } = request.into_message().await?; 16 | 17 | let chat_tree = &svc.deps.chat_tree; 18 | 19 | chat_tree.check_guild_user(guild_id, user_id).await?; 20 | chat_tree 21 | .check_perms(guild_id, None, user_id, "channels.manage.create", false) 22 | .await?; 23 | 24 | if channel_name.is_empty() { 25 | bail!(("h.bad-channel-name", "channel name can't be empty")); 26 | } 27 | 28 | if position.as_ref().map_or(false, |pos| pos.item_id == 0) { 29 | bail!(("h.bad-item-position", "channel position can't be empty")); 30 | } 31 | 32 | let channel_id = chat_tree 33 | .create_channel_logic( 34 | guild_id, 35 | channel_name.clone(), 36 | ChannelKind::from_i32(kind).unwrap_or_default(), 37 | metadata.clone(), 38 | position.clone(), 39 | ) 40 | .await?; 41 | 42 | svc.send_event_through_chan( 43 | EventSub::Guild(guild_id), 44 | stream_event::Event::CreatedChannel(stream_event::ChannelCreated { 45 | guild_id, 46 | channel_id, 47 | name: channel_name, 48 | position, 49 | kind, 50 | metadata, 51 | }), 52 | Some(PermCheck::new( 53 | guild_id, 54 | Some(channel_id), 55 | "messages.view", 56 | false, 57 | )), 58 | EventContext::empty(), 59 | ); 60 | 61 | Ok((CreateChannelResponse { channel_id }).into_response()) 62 | } 63 | -------------------------------------------------------------------------------- /src/impls/chat/channels/delete_channel.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteChannelRequest { 10 | guild_id, 11 | channel_id, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree 17 | .check_guild_user_channel(guild_id, user_id, channel_id) 18 | .await?; 19 | chat_tree 20 | .check_perms( 21 | guild_id, 22 | Some(channel_id), 23 | user_id, 24 | "channels.manage.delete", 25 | false, 26 | ) 27 | .await?; 28 | 29 | let channel_data = chat_tree 30 | .scan_prefix(&make_chan_key(guild_id, channel_id)) 31 | .await 32 | .try_fold(Vec::new(), |mut all, res| { 33 | all.push(res?.0); 34 | ServerResult::Ok(all) 35 | })?; 36 | 37 | // Remove from ordering list 38 | let key = make_guild_chan_ordering_key(guild_id); 39 | let mut ordering = chat_tree.get_list_u64_logic(&key).await?; 40 | if let Some(index) = ordering.iter().position(|oid| channel_id.eq(oid)) { 41 | ordering.remove(index); 42 | } 43 | let serialized_ordering = chat_tree.serialize_list_u64_logic(ordering); 44 | 45 | let mut batch = Batch::default(); 46 | for key in channel_data { 47 | batch.remove(key); 48 | } 49 | batch.insert(key, serialized_ordering); 50 | chat_tree 51 | .chat_tree 52 | .apply_batch(batch) 53 | .await 54 | .map_err(ServerError::DbError)?; 55 | 56 | svc.send_event_through_chan( 57 | EventSub::Guild(guild_id), 58 | stream_event::Event::DeletedChannel(stream_event::ChannelDeleted { 59 | guild_id, 60 | channel_id, 61 | }), 62 | None, 63 | EventContext::empty(), 64 | ); 65 | 66 | Ok((DeleteChannelResponse {}).into_response()) 67 | } 68 | -------------------------------------------------------------------------------- /src/impls/chat/channels/get_guild_channels.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetGuildChannelsRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | 15 | chat_tree 16 | .get_guild_channels_logic(guild_id, user_id) 17 | .await 18 | .map(IntoResponse::into_response) 19 | .map_err(Into::into) 20 | } 21 | -------------------------------------------------------------------------------- /src/impls/chat/channels/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod create_channel; 4 | pub mod delete_channel; 5 | pub mod get_guild_channels; 6 | pub mod typing; 7 | pub mod update_all_channel_order; 8 | pub mod update_channel_information; 9 | pub mod update_channel_order; 10 | -------------------------------------------------------------------------------- /src/impls/chat/channels/typing.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let TypingRequest { 10 | guild_id, 11 | channel_id, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree 17 | .check_guild_user_channel(guild_id, user_id, channel_id) 18 | .await?; 19 | chat_tree 20 | .check_perms(guild_id, Some(channel_id), user_id, "messages.send", false) 21 | .await?; 22 | 23 | svc.send_event_through_chan( 24 | EventSub::Guild(guild_id), 25 | stream_event::Event::Typing(stream_event::Typing { 26 | user_id, 27 | guild_id, 28 | channel_id, 29 | }), 30 | Some(PermCheck::new( 31 | guild_id, 32 | Some(channel_id), 33 | "messages.view", 34 | false, 35 | )), 36 | EventContext::empty(), 37 | ); 38 | 39 | Ok((TypingResponse {}).into_response()) 40 | } 41 | -------------------------------------------------------------------------------- /src/impls/chat/channels/update_all_channel_order.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let UpdateAllChannelOrderRequest { 10 | guild_id, 11 | channel_ids, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree.check_guild_user(guild_id, user_id).await?; 17 | chat_tree 18 | .check_perms(guild_id, None, user_id, "channels.manage.move", false) 19 | .await?; 20 | 21 | for channel_id in &channel_ids { 22 | chat_tree.does_channel_exist(guild_id, *channel_id).await?; 23 | } 24 | 25 | let prefix = make_guild_chan_prefix(guild_id); 26 | let channels = chat_tree 27 | .scan_prefix(&prefix) 28 | .await 29 | .try_fold(Vec::new(), |mut all, res| { 30 | let (key, _) = res?; 31 | if key.len() == prefix.len() + size_of::() { 32 | all.push(deser_id(key.split_at(prefix.len()).1)); 33 | } 34 | ServerResult::Ok(all) 35 | })?; 36 | 37 | for channel_id in channels { 38 | if !channel_ids.contains(&channel_id) { 39 | return Err(ServerError::UnderSpecifiedChannels.into()); 40 | } 41 | } 42 | 43 | let key = make_guild_chan_ordering_key(guild_id); 44 | let serialized_ordering = chat_tree.serialize_list_u64_logic(channel_ids.clone()); 45 | chat_tree.insert(key, serialized_ordering).await?; 46 | 47 | svc.send_event_through_chan( 48 | EventSub::Guild(guild_id), 49 | stream_event::Event::ChannelsReordered(stream_event::ChannelsReordered { 50 | guild_id, 51 | channel_ids, 52 | }), 53 | None, 54 | EventContext::empty(), 55 | ); 56 | 57 | Ok((UpdateAllChannelOrderResponse {}).into_response()) 58 | } 59 | -------------------------------------------------------------------------------- /src/impls/chat/channels/update_channel_information.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let UpdateChannelInformationRequest { 10 | guild_id, 11 | channel_id, 12 | new_name, 13 | new_metadata, 14 | } = request.into_message().await?; 15 | 16 | let chat_tree = &svc.deps.chat_tree; 17 | 18 | chat_tree.check_guild_user(guild_id, user_id).await?; 19 | chat_tree 20 | .check_perms( 21 | guild_id, 22 | Some(channel_id), 23 | user_id, 24 | "channels.manage.change-information", 25 | false, 26 | ) 27 | .await?; 28 | 29 | let key = make_chan_key(guild_id, channel_id); 30 | let mut chan_info = if let Some(raw) = chat_tree.get(key).await? { 31 | db::deser_chan(raw) 32 | } else { 33 | return Err(ServerError::NoSuchChannel { 34 | guild_id, 35 | channel_id, 36 | } 37 | .into()); 38 | }; 39 | 40 | if let Some(new_name) = new_name.clone() { 41 | if new_name.is_empty() { 42 | bail!(("h.bad-channel-name", "channel name can't be empty")); 43 | } 44 | chan_info.channel_name = new_name; 45 | } 46 | if let Some(new_metadata) = new_metadata.clone() { 47 | chan_info.metadata = Some(new_metadata); 48 | } 49 | 50 | let buf = rkyv_ser(&chan_info); 51 | chat_tree.insert(key, buf).await?; 52 | 53 | svc.send_event_through_chan( 54 | EventSub::Guild(guild_id), 55 | stream_event::Event::EditedChannel(stream_event::ChannelUpdated { 56 | guild_id, 57 | channel_id, 58 | new_name, 59 | new_metadata, 60 | }), 61 | Some(PermCheck::new( 62 | guild_id, 63 | Some(channel_id), 64 | "messages.view", 65 | false, 66 | )), 67 | EventContext::empty(), 68 | ); 69 | 70 | Ok((UpdateChannelInformationResponse {}).into_response()) 71 | } 72 | -------------------------------------------------------------------------------- /src/impls/chat/channels/update_channel_order.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let UpdateChannelOrderRequest { 10 | guild_id, 11 | channel_id, 12 | new_position, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree 18 | .check_guild_user_channel(guild_id, user_id, channel_id) 19 | .await?; 20 | chat_tree 21 | .check_perms( 22 | guild_id, 23 | Some(channel_id), 24 | user_id, 25 | "channels.manage.move", 26 | false, 27 | ) 28 | .await?; 29 | 30 | if let Some(position) = new_position { 31 | chat_tree 32 | .update_channel_order_logic(guild_id, channel_id, Some(position.clone())) 33 | .await?; 34 | 35 | svc.send_event_through_chan( 36 | EventSub::Guild(guild_id), 37 | stream_event::Event::EditedChannelPosition(stream_event::ChannelPositionUpdated { 38 | guild_id, 39 | channel_id, 40 | new_position: Some(position), 41 | }), 42 | None, 43 | EventContext::empty(), 44 | ); 45 | } 46 | 47 | Ok((UpdateChannelOrderResponse {}).into_response()) 48 | } 49 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/create_direct_message.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/create_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let CreateGuildRequest { 10 | metadata, 11 | name, 12 | picture, 13 | } = request.into_message().await?; 14 | 15 | if name.is_empty() { 16 | bail!(("h.bad-guild-name", "guild name can't be empty")); 17 | } 18 | 19 | if picture.as_ref().map_or(false, String::is_empty) { 20 | bail!(("h.bad-guild-picture", "guild picture can't be empty if set")); 21 | } 22 | 23 | let guild_id = svc 24 | .deps 25 | .chat_tree 26 | .create_guild_logic( 27 | user_id, 28 | name, 29 | picture, 30 | metadata, 31 | guild_kind::Kind::new_normal(guild_kind::Normal::new()), 32 | ) 33 | .await?; 34 | 35 | svc.dispatch_guild_join(guild_id, user_id).await?; 36 | 37 | Ok((CreateGuildResponse { guild_id }).into_response()) 38 | } 39 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/create_room.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/delete_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteGuildRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | chat_tree 15 | .check_perms(guild_id, None, user_id, "guild.manage.delete", false) 16 | .await?; 17 | 18 | let guild_members = chat_tree.get_guild_members_logic(guild_id).await?.members; 19 | 20 | let guild_data = chat_tree 21 | .scan_prefix(&guild_id.to_be_bytes()) 22 | .await 23 | .try_fold(Vec::new(), |mut all, res| { 24 | all.push(res?.0); 25 | ServerResult::Ok(all) 26 | })?; 27 | 28 | let mut batch = Batch::default(); 29 | for key in guild_data { 30 | batch.remove(key); 31 | } 32 | chat_tree 33 | .chat_tree 34 | .apply_batch(batch) 35 | .await 36 | .map_err(ServerError::DbError)?; 37 | 38 | svc.send_event_through_chan( 39 | EventSub::Guild(guild_id), 40 | stream_event::Event::DeletedGuild(stream_event::GuildDeleted { guild_id }), 41 | None, 42 | EventContext::empty(), 43 | ); 44 | 45 | let mut local_ids = Vec::new(); 46 | for member_id in guild_members { 47 | match svc.deps.profile_tree.local_to_foreign_id(member_id).await? { 48 | Some((foreign_id, target)) => svc.dispatch_event( 49 | target, 50 | DispatchKind::UserRemovedFromGuild(SyncUserRemovedFromGuild { 51 | user_id: foreign_id, 52 | guild_id, 53 | }), 54 | ), 55 | None => { 56 | chat_tree 57 | .remove_guild_from_guild_list(user_id, guild_id, "") 58 | .await?; 59 | local_ids.push(member_id); 60 | } 61 | } 62 | } 63 | svc.send_event_through_chan( 64 | EventSub::Homeserver, 65 | stream_event::Event::GuildRemovedFromList(stream_event::GuildRemovedFromList { 66 | guild_id, 67 | homeserver: String::new(), 68 | }), 69 | None, 70 | EventContext::new(local_ids), 71 | ); 72 | 73 | Ok((DeleteGuildResponse {}).into_response()) 74 | } 75 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/get_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetGuildRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | 15 | chat_tree 16 | .get_guild_logic(guild_id) 17 | .await 18 | .map(|g| (GetGuildResponse { guild: Some(g) }).into_response()) 19 | } 20 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/get_guild_list.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let prefix = make_guild_list_key_prefix(user_id); 10 | let guilds = 11 | svc.deps 12 | .chat_tree 13 | .scan_prefix(&prefix) 14 | .await 15 | .try_fold(Vec::new(), |mut all, res| { 16 | let (guild_id_raw, _) = res?; 17 | let (id_raw, host_raw) = guild_id_raw 18 | .split_at(prefix.len()) 19 | .1 20 | .split_at(size_of::()); 21 | 22 | // Safety: this unwrap can never cause UB since we split at u64 boundary 23 | let guild_id = deser_id(id_raw); 24 | // Safety: we never store non UTF-8 hosts, so this can't cause UB 25 | let host = unsafe { std::str::from_utf8_unchecked(host_raw) }; 26 | 27 | all.push(GuildListEntry { 28 | guild_id, 29 | server_id: host.to_string(), 30 | }); 31 | 32 | ServerResult::Ok(all) 33 | })?; 34 | 35 | Ok((GetGuildListResponse { guilds }).into_response()) 36 | } 37 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/get_guild_members.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetGuildMembersRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | 15 | chat_tree 16 | .get_guild_members_logic(guild_id) 17 | .await 18 | .map(IntoResponse::into_response) 19 | } 20 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/join_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let JoinGuildRequest { invite_id } = request.into_message().await?; 10 | let key = make_invite_key(invite_id.as_str()); 11 | 12 | let chat_tree = &svc.deps.chat_tree; 13 | 14 | let (guild_id, mut invite) = if let Some(raw) = chat_tree.get(&key).await? { 15 | db::deser_invite_entry(raw) 16 | } else { 17 | return Err(ServerError::NoSuchInvite(invite_id.into()).into()); 18 | }; 19 | 20 | if chat_tree.is_user_banned_in_guild(guild_id, user_id).await? { 21 | return Err(ServerError::UserBanned.into()); 22 | } 23 | 24 | chat_tree 25 | .is_user_in_guild(guild_id, user_id) 26 | .await 27 | .ok() 28 | .map_or(Ok(()), |_| Err(ServerError::UserAlreadyInGuild))?; 29 | 30 | let is_infinite = invite.possible_uses == 0; 31 | 32 | if is_infinite.not() && invite.use_count >= invite.possible_uses { 33 | return Err(ServerError::InviteExpired.into()); 34 | } 35 | 36 | chat_tree 37 | .insert(make_member_key(guild_id, user_id), []) 38 | .await?; 39 | chat_tree.add_default_role_to(guild_id, user_id).await?; 40 | invite.use_count += 1; 41 | 42 | let is_invite_consumed = is_infinite.not() && invite.use_count >= invite.possible_uses; 43 | if is_invite_consumed { 44 | chat_tree.delete_invite_logic(invite_id).await?; 45 | } 46 | 47 | svc.send_event_through_chan( 48 | EventSub::Guild(guild_id), 49 | stream_event::Event::JoinedMember(stream_event::MemberJoined { 50 | guild_id, 51 | member_id: user_id, 52 | }), 53 | None, 54 | EventContext::empty(), 55 | ); 56 | 57 | svc.dispatch_guild_join(guild_id, user_id).await?; 58 | 59 | if !is_invite_consumed { 60 | let buf = rkyv_ser(&invite); 61 | chat_tree 62 | .insert( 63 | key, 64 | [guild_id.to_be_bytes().as_ref(), buf.as_ref()].concat(), 65 | ) 66 | .await?; 67 | } 68 | 69 | Ok((JoinGuildResponse { guild_id }).into_response()) 70 | } 71 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/leave_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let LeaveGuildRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | 15 | if chat_tree.is_user_guild_owner(guild_id, user_id).await? { 16 | bail!(("h.owner-cant-leave", "guild owners cant leave their guild")); 17 | } 18 | 19 | chat_tree 20 | .chat_tree 21 | .remove(&make_member_key(guild_id, user_id)) 22 | .await 23 | .map_err(ServerError::DbError)?; 24 | 25 | svc.send_event_through_chan( 26 | EventSub::Guild(guild_id), 27 | stream_event::Event::LeftMember(stream_event::MemberLeft { 28 | guild_id, 29 | member_id: user_id, 30 | leave_reason: LeaveReason::WillinglyUnspecified.into(), 31 | }), 32 | None, 33 | EventContext::empty(), 34 | ); 35 | 36 | svc.dispatch_guild_leave(guild_id, user_id).await?; 37 | 38 | Ok((LeaveGuildResponse {}).into_response()) 39 | } 40 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod create_direct_message; 4 | pub mod create_guild; 5 | pub mod create_room; 6 | pub mod delete_guild; 7 | pub mod get_guild; 8 | pub mod get_guild_list; 9 | pub mod get_guild_members; 10 | pub mod join_guild; 11 | pub mod leave_guild; 12 | pub mod preview_guild; 13 | pub mod update_guild_information; 14 | pub mod upgrade_room_to_guild; 15 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/preview_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let PreviewGuildRequest { invite_id } = request.into_message().await?; 8 | 9 | let chat_tree = &svc.deps.chat_tree; 10 | 11 | let key = make_invite_key(&invite_id); 12 | let guild_id = chat_tree 13 | .get(&key) 14 | .await? 15 | .ok_or_else(|| ServerError::NoSuchInvite(invite_id.into())) 16 | .map(|raw| db::deser_invite_entry_guild_id(&raw))?; 17 | let guild = chat_tree.get_guild_logic(guild_id).await?; 18 | // TODO(yusdacra): don't count members by iterating through scan prefix result 19 | // instead keep a member count entry in db 20 | let member_count = chat_tree 21 | .scan_prefix(&make_guild_mem_prefix(guild_id)) 22 | .await 23 | .try_fold(0, |all, res| res.map(|_| all + 1))?; 24 | 25 | Ok((PreviewGuildResponse { 26 | name: guild.name, 27 | picture: guild.picture, 28 | member_count, 29 | }) 30 | .into_response()) 31 | } 32 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/update_guild_information.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let UpdateGuildInformationRequest { 10 | guild_id, 11 | new_name, 12 | new_picture, 13 | new_metadata, 14 | } = request.into_message().await?; 15 | 16 | let chat_tree = &svc.deps.chat_tree; 17 | 18 | chat_tree.check_guild_user(guild_id, user_id).await?; 19 | 20 | let mut guild_info = chat_tree.get_guild_logic(guild_id).await?; 21 | 22 | chat_tree 23 | .check_perms( 24 | guild_id, 25 | None, 26 | user_id, 27 | "guild.manage.change-information", 28 | false, 29 | ) 30 | .await?; 31 | 32 | if let Some(new_name) = new_name.clone() { 33 | if new_name.is_empty() { 34 | bail!(("h.bad-guild-name", "guild name cant be empty")); 35 | } 36 | guild_info.name = new_name; 37 | } 38 | if let Some(new_picture) = new_picture.clone() { 39 | guild_info.picture = new_picture.is_empty().not().then(|| new_picture); 40 | } 41 | if let Some(new_metadata) = new_metadata.clone() { 42 | guild_info.metadata = Some(new_metadata); 43 | } 44 | 45 | chat_tree.put_guild_logic(guild_id, guild_info).await?; 46 | 47 | svc.send_event_through_chan( 48 | EventSub::Guild(guild_id), 49 | stream_event::Event::EditedGuild(stream_event::GuildUpdated { 50 | guild_id, 51 | new_name, 52 | new_picture, 53 | new_metadata, 54 | }), 55 | None, 56 | EventContext::empty(), 57 | ); 58 | 59 | Ok((UpdateGuildInformationResponse {}).into_response()) 60 | } 61 | -------------------------------------------------------------------------------- /src/impls/chat/guilds/upgrade_room_to_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/invites/create_invite.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let CreateInviteRequest { 10 | guild_id, 11 | name, 12 | possible_uses, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree.check_guild_user(guild_id, user_id).await?; 18 | chat_tree 19 | .check_perms(guild_id, None, user_id, "invites.manage.create", false) 20 | .await?; 21 | 22 | chat_tree 23 | .create_invite_logic(guild_id, name.as_str(), possible_uses) 24 | .await?; 25 | 26 | Ok((CreateInviteResponse { invite_id: name }).into_response()) 27 | } 28 | -------------------------------------------------------------------------------- /src/impls/chat/invites/delete_invite.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteInviteRequest { 10 | guild_id, 11 | invite_id, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree.check_guild_user(guild_id, user_id).await?; 17 | chat_tree 18 | .check_perms(guild_id, None, user_id, "invites.manage.delete", false) 19 | .await?; 20 | 21 | chat_tree.delete_invite_logic(invite_id).await?; 22 | 23 | Ok((DeleteInviteResponse {}).into_response()) 24 | } 25 | -------------------------------------------------------------------------------- /src/impls/chat/invites/get_guild_invites.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetGuildInvitesRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | chat_tree 15 | .check_perms(guild_id, None, user_id, "invites.view", false) 16 | .await?; 17 | 18 | chat_tree 19 | .get_guild_invites_logic(guild_id) 20 | .await 21 | .map(IntoResponse::into_response) 22 | } 23 | -------------------------------------------------------------------------------- /src/impls/chat/invites/get_pending_invites.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/invites/ignore_pending_invite.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/invites/invite_user_to_guild.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/invites/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod create_invite; 4 | pub mod delete_invite; 5 | pub mod get_guild_invites; 6 | pub mod get_pending_invites; 7 | pub mod ignore_pending_invite; 8 | pub mod invite_user_to_guild; 9 | pub mod reject_pending_invite; 10 | -------------------------------------------------------------------------------- /src/impls/chat/invites/reject_pending_invite.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/chat/messages/add_reaction.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let AddReactionRequest { 10 | guild_id, 11 | channel_id, 12 | message_id, 13 | emote, 14 | } = request.into_message().await?; 15 | 16 | if let Some(emote) = emote { 17 | let chat_tree = &svc.deps.chat_tree; 18 | 19 | chat_tree 20 | .check_perms( 21 | guild_id, 22 | Some(channel_id), 23 | user_id, 24 | all_permissions::MESSAGES_REACTIONS_ADD, 25 | false, 26 | ) 27 | .await?; 28 | 29 | let reaction = chat_tree 30 | .update_reaction(user_id, guild_id, channel_id, message_id, emote, true) 31 | .await?; 32 | svc.send_reaction_event(guild_id, channel_id, message_id, reaction); 33 | } 34 | 35 | Ok((AddReactionResponse {}).into_response()) 36 | } 37 | -------------------------------------------------------------------------------- /src/impls/chat/messages/delete_message.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteMessageRequest { 10 | guild_id, 11 | channel_id, 12 | message_id, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree 18 | .check_guild_user_channel(guild_id, user_id, channel_id) 19 | .await?; 20 | if chat_tree 21 | .get_message_logic(guild_id, channel_id, message_id) 22 | .await? 23 | .0 24 | .author_id 25 | != user_id 26 | { 27 | chat_tree 28 | .check_perms( 29 | guild_id, 30 | Some(channel_id), 31 | user_id, 32 | "messages.manage.delete", 33 | false, 34 | ) 35 | .await?; 36 | } 37 | 38 | chat_tree 39 | .chat_tree 40 | .remove(&make_msg_key(guild_id, channel_id, message_id)) 41 | .await 42 | .map_err(ServerError::DbError)?; 43 | 44 | svc.send_event_through_chan( 45 | EventSub::Guild(guild_id), 46 | stream_event::Event::DeletedMessage(stream_event::MessageDeleted { 47 | guild_id, 48 | channel_id, 49 | message_id, 50 | }), 51 | Some(PermCheck::new( 52 | guild_id, 53 | Some(channel_id), 54 | "messages.view", 55 | false, 56 | )), 57 | EventContext::empty(), 58 | ); 59 | 60 | Ok((DeleteMessageResponse {}).into_response()) 61 | } 62 | -------------------------------------------------------------------------------- /src/impls/chat/messages/get_channel_messages.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetChannelMessagesRequest { 10 | guild_id, 11 | channel_id, 12 | message_id, 13 | direction, 14 | count, 15 | } = request.into_message().await?; 16 | 17 | let chat_tree = &svc.deps.chat_tree; 18 | 19 | chat_tree 20 | .check_guild_user_channel(guild_id, user_id, channel_id) 21 | .await?; 22 | chat_tree 23 | .check_perms(guild_id, Some(channel_id), user_id, "messages.view", false) 24 | .await?; 25 | 26 | chat_tree 27 | .get_channel_messages_logic( 28 | guild_id, 29 | channel_id, 30 | message_id, 31 | direction.map(|val| Direction::from_i32(val).unwrap_or_default()), 32 | count, 33 | ) 34 | .await 35 | .map(IntoResponse::into_response) 36 | } 37 | -------------------------------------------------------------------------------- /src/impls/chat/messages/get_message.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let request = request.into_message().await?; 10 | 11 | let GetMessageRequest { 12 | guild_id, 13 | channel_id, 14 | message_id, 15 | } = request; 16 | 17 | let chat_tree = &svc.deps.chat_tree; 18 | 19 | chat_tree 20 | .check_guild_user_channel(guild_id, user_id, channel_id) 21 | .await?; 22 | chat_tree 23 | .check_perms(guild_id, Some(channel_id), user_id, "messages.view", false) 24 | .await?; 25 | 26 | let message = Some( 27 | chat_tree 28 | .get_message_logic(guild_id, channel_id, message_id) 29 | .await? 30 | .0, 31 | ); 32 | 33 | Ok((GetMessageResponse { message }).into_response()) 34 | } 35 | -------------------------------------------------------------------------------- /src/impls/chat/messages/get_pinned_messages.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetPinnedMessagesRequest { 10 | guild_id, 11 | channel_id, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree 17 | .check_guild_user_channel(guild_id, user_id, channel_id) 18 | .await?; 19 | 20 | chat_tree 21 | .check_perms( 22 | guild_id, 23 | Some(channel_id), 24 | user_id, 25 | all_permissions::MESSAGES_VIEW, 26 | false, 27 | ) 28 | .await?; 29 | 30 | let pinned_message_ids = chat_tree 31 | .get_pinned_messages_logic(guild_id, channel_id) 32 | .await?; 33 | 34 | Ok((GetPinnedMessagesResponse { pinned_message_ids }).into_response()) 35 | } 36 | -------------------------------------------------------------------------------- /src/impls/chat/messages/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod add_reaction; 4 | pub mod delete_message; 5 | pub mod get_channel_messages; 6 | pub mod get_message; 7 | pub mod get_pinned_messages; 8 | pub mod pin_message; 9 | pub mod remove_reaction; 10 | pub mod send_message; 11 | pub mod unpin_message; 12 | pub mod update_message_text; 13 | -------------------------------------------------------------------------------- /src/impls/chat/messages/pin_message.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let PinMessageRequest { 10 | guild_id, 11 | channel_id, 12 | message_id, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree 18 | .check_guild_user_channel(guild_id, user_id, channel_id) 19 | .await?; 20 | 21 | chat_tree 22 | .check_perms( 23 | guild_id, 24 | Some(channel_id), 25 | user_id, 26 | all_permissions::MESSAGES_PINS_ADD, 27 | false, 28 | ) 29 | .await?; 30 | 31 | let key = make_pinned_msgs_key(guild_id, channel_id); 32 | let mut pinned_msgs_raw = chat_tree.get(key).await?.map_or_else(Vec::new, EVec::into); 33 | pinned_msgs_raw.extend_from_slice(&message_id.to_be_bytes()); 34 | chat_tree.insert(key, pinned_msgs_raw).await?; 35 | 36 | svc.send_event_through_chan( 37 | EventSub::Guild(guild_id), 38 | stream_event::Event::MessagePinned(stream_event::MessagePinned { 39 | guild_id, 40 | channel_id, 41 | message_id, 42 | }), 43 | Some(PermCheck::new( 44 | guild_id, 45 | Some(channel_id), 46 | all_permissions::MESSAGES_VIEW, 47 | false, 48 | )), 49 | EventContext::empty(), 50 | ); 51 | 52 | Ok(PinMessageResponse::new().into_response()) 53 | } 54 | -------------------------------------------------------------------------------- /src/impls/chat/messages/remove_reaction.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let RemoveReactionRequest { 10 | guild_id, 11 | channel_id, 12 | message_id, 13 | emote, 14 | } = request.into_message().await?; 15 | 16 | if let Some(emote) = emote { 17 | let chat_tree = &svc.deps.chat_tree; 18 | 19 | chat_tree 20 | .check_perms( 21 | guild_id, 22 | Some(channel_id), 23 | user_id, 24 | all_permissions::MESSAGES_REACTIONS_REMOVE, 25 | false, 26 | ) 27 | .await?; 28 | 29 | let reaction = chat_tree 30 | .update_reaction(user_id, guild_id, channel_id, message_id, emote, false) 31 | .await?; 32 | if reaction.is_some() { 33 | svc.send_reaction_event(guild_id, channel_id, message_id, reaction); 34 | } 35 | } 36 | 37 | Ok((RemoveReactionResponse {}).into_response()) 38 | } 39 | -------------------------------------------------------------------------------- /src/impls/chat/messages/send_message.rs: -------------------------------------------------------------------------------- 1 | use crate::impls::admin_action; 2 | 3 | use super::*; 4 | 5 | pub async fn handler( 6 | svc: &ChatServer, 7 | request: Request, 8 | ) -> ServerResult> { 9 | let user_id = svc.deps.auth(&request).await?; 10 | 11 | let mut request = request.into_message().await?; 12 | let guild_id = request.guild_id; 13 | let channel_id = request.channel_id; 14 | let echo_id = request.echo_id; 15 | 16 | let chat_tree = &svc.deps.chat_tree; 17 | 18 | chat_tree 19 | .check_guild_user_channel(guild_id, user_id, channel_id) 20 | .await?; 21 | chat_tree 22 | .check_perms(guild_id, Some(channel_id), user_id, "messages.send", false) 23 | .await?; 24 | 25 | chat_tree.process_message_overrides(request.overrides.as_ref())?; 26 | let content = chat_tree 27 | .process_message_content( 28 | request.content.take(), 29 | svc.deps.config.media.media_root.as_path(), 30 | &svc.deps.config.host, 31 | ) 32 | .await?; 33 | request.content = Some(content); 34 | let (message_id, message) = chat_tree.send_message_logic(user_id, request).await?; 35 | 36 | let is_cmd_channel = chat_tree 37 | .admin_guild_keys 38 | .get() 39 | .map_or(false, |keys| keys.check_if_cmd(guild_id, channel_id)); 40 | 41 | let action_content = if is_cmd_channel { 42 | if let Some(content::Content::TextMessage(content::TextContent { 43 | content: Some(FormattedText { text, .. }), 44 | })) = message.content.as_ref().and_then(|c| c.content.as_ref()) 45 | { 46 | let msg = admin_action::run_str(svc.deps.as_ref(), text) 47 | .await 48 | .unwrap_or_else(|err| format!("error: {}", err)); 49 | Some(msg) 50 | } else { 51 | None 52 | } 53 | } else { 54 | None 55 | }; 56 | 57 | svc.send_event_through_chan( 58 | EventSub::Guild(guild_id), 59 | stream_event::Event::SentMessage(stream_event::MessageSent { 60 | echo_id, 61 | guild_id, 62 | channel_id, 63 | message_id, 64 | message: Some(message), 65 | }), 66 | Some(PermCheck::new( 67 | guild_id, 68 | Some(channel_id), 69 | "messages.view", 70 | false, 71 | )), 72 | EventContext::empty(), 73 | ); 74 | 75 | if let Some(msg) = action_content { 76 | let content = content::Content::TextMessage(content::TextContent { 77 | content: Some(FormattedText::new(msg, Vec::new())), 78 | }); 79 | let (message_id, message) = chat_tree 80 | .send_with_system(guild_id, channel_id, content) 81 | .await?; 82 | svc.send_event_through_chan( 83 | EventSub::Guild(guild_id), 84 | stream_event::Event::SentMessage(stream_event::MessageSent { 85 | echo_id, 86 | guild_id, 87 | channel_id, 88 | message_id, 89 | message: Some(message), 90 | }), 91 | Some(PermCheck::new( 92 | guild_id, 93 | Some(channel_id), 94 | "messages.view", 95 | false, 96 | )), 97 | EventContext::empty(), 98 | ); 99 | } 100 | 101 | Ok((SendMessageResponse { message_id }).into_response()) 102 | } 103 | -------------------------------------------------------------------------------- /src/impls/chat/messages/unpin_message.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let UnpinMessageRequest { 10 | guild_id, 11 | channel_id, 12 | message_id, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree 18 | .check_guild_user_channel(guild_id, user_id, channel_id) 19 | .await?; 20 | 21 | chat_tree 22 | .check_perms( 23 | guild_id, 24 | Some(channel_id), 25 | user_id, 26 | all_permissions::MESSAGES_PINS_REMOVE, 27 | false, 28 | ) 29 | .await?; 30 | 31 | let mut pinned_message_ids = chat_tree 32 | .get_pinned_messages_logic(guild_id, channel_id) 33 | .await?; 34 | if let Some(pos) = pinned_message_ids.iter().position(|id| message_id.eq(id)) { 35 | pinned_message_ids.remove(pos); 36 | } 37 | let pinned_msgs_raw = chat_tree.serialize_list_u64_logic(pinned_message_ids); 38 | chat_tree 39 | .insert(make_pinned_msgs_key(guild_id, channel_id), pinned_msgs_raw) 40 | .await?; 41 | 42 | svc.send_event_through_chan( 43 | EventSub::Guild(guild_id), 44 | stream_event::Event::MessageUnpinned(stream_event::MessageUnpinned { 45 | guild_id, 46 | channel_id, 47 | message_id, 48 | }), 49 | Some(PermCheck::new( 50 | guild_id, 51 | Some(channel_id), 52 | all_permissions::MESSAGES_VIEW, 53 | false, 54 | )), 55 | EventContext::empty(), 56 | ); 57 | 58 | Ok(UnpinMessageResponse::new().into_response()) 59 | } 60 | -------------------------------------------------------------------------------- /src/impls/chat/messages/update_message_text.rs: -------------------------------------------------------------------------------- 1 | use rkyv::de::deserializers::SharedDeserializeMap; 2 | 3 | use super::*; 4 | 5 | pub async fn handler( 6 | svc: &ChatServer, 7 | request: Request, 8 | ) -> ServerResult> { 9 | let user_id = svc.deps.auth(&request).await?; 10 | 11 | let request = request.into_message().await?; 12 | 13 | let UpdateMessageTextRequest { 14 | guild_id, 15 | channel_id, 16 | message_id, 17 | new_content, 18 | } = request; 19 | 20 | let chat_tree = &svc.deps.chat_tree; 21 | 22 | chat_tree 23 | .check_guild_user_channel(guild_id, user_id, channel_id) 24 | .await?; 25 | chat_tree 26 | .check_perms(guild_id, Some(channel_id), user_id, "messages.send", false) 27 | .await?; 28 | 29 | if new_content.as_ref().map_or(true, |f| f.text.is_empty()) { 30 | return Err(ServerError::MessageContentCantBeEmpty.into()); 31 | } 32 | 33 | let key = make_msg_key(guild_id, channel_id, message_id); 34 | let Some(message_raw) = chat_tree.get(key).await? else { 35 | bail!(ServerError::NoSuchMessage { guild_id, channel_id, message_id }); 36 | }; 37 | let message_archived = rkyv_arch::(&message_raw); 38 | 39 | if message_archived.author_id != user_id { 40 | bail!(( 41 | "h.not-author", 42 | "you must be the author of a message to edit it" 43 | )); 44 | } 45 | 46 | let mut message: Message = message_archived 47 | .deserialize(&mut SharedDeserializeMap::default()) 48 | .unwrap(); 49 | 50 | let msg_content = if let Some(content) = &mut message.content { 51 | content 52 | } else { 53 | message.content = Some(Content::default()); 54 | message.content.as_mut().unwrap() 55 | }; 56 | msg_content.content = Some(content::Content::TextMessage(content::TextContent { 57 | content: new_content.clone(), 58 | })); 59 | 60 | let edited_at = get_time_secs(); 61 | message.edited_at = Some(edited_at); 62 | 63 | let buf = rkyv_ser(&message); 64 | chat_tree.insert(key, buf).await?; 65 | 66 | svc.send_event_through_chan( 67 | EventSub::Guild(guild_id), 68 | stream_event::Event::EditedMessage(stream_event::MessageUpdated { 69 | guild_id, 70 | channel_id, 71 | message_id, 72 | edited_at, 73 | new_content, 74 | }), 75 | Some(PermCheck::new( 76 | guild_id, 77 | Some(channel_id), 78 | "messages.view", 79 | false, 80 | )), 81 | EventContext::empty(), 82 | ); 83 | 84 | Ok((UpdateMessageTextResponse {}).into_response()) 85 | } 86 | -------------------------------------------------------------------------------- /src/impls/chat/moderation/ban_user.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let BanUserRequest { 10 | guild_id, 11 | user_id: user_to_ban, 12 | } = request.into_message().await?; 13 | 14 | if user_id == user_to_ban { 15 | return Err(ServerError::CantKickOrBanYourself.into()); 16 | } 17 | 18 | let chat_tree = &svc.deps.chat_tree; 19 | 20 | chat_tree.check_guild_user(guild_id, user_id).await?; 21 | chat_tree.is_user_in_guild(guild_id, user_to_ban).await?; 22 | chat_tree 23 | .check_perms(guild_id, None, user_id, "user.manage.ban", false) 24 | .await?; 25 | 26 | chat_tree.kick_user_logic(guild_id, user_to_ban).await?; 27 | 28 | chat_tree 29 | .insert( 30 | make_banned_member_key(guild_id, user_to_ban), 31 | get_time_secs().to_be_bytes(), 32 | ) 33 | .await?; 34 | 35 | svc.send_event_through_chan( 36 | EventSub::Guild(guild_id), 37 | stream_event::Event::LeftMember(stream_event::MemberLeft { 38 | guild_id, 39 | member_id: user_to_ban, 40 | leave_reason: LeaveReason::Banned.into(), 41 | }), 42 | None, 43 | EventContext::empty(), 44 | ); 45 | 46 | svc.dispatch_guild_leave(guild_id, user_to_ban).await?; 47 | 48 | Ok((BanUserResponse {}).into_response()) 49 | } 50 | -------------------------------------------------------------------------------- /src/impls/chat/moderation/get_banned_users.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | svc.deps.auth(&request).await?; 8 | 9 | let GetBannedUsersRequest { guild_id } = request.into_message().await?; 10 | 11 | let prefix = make_guild_banned_mem_prefix(guild_id); 12 | let banned_users = 13 | svc.deps 14 | .chat_tree 15 | .scan_prefix(&prefix) 16 | .await 17 | .try_fold(Vec::new(), |mut all, res| { 18 | let (key, _) = res?; 19 | if key.len() == make_banned_member_key(0, 0).len() { 20 | all.push(deser_id(key.split_at(prefix.len()).1)); 21 | } 22 | ServerResult::Ok(all) 23 | })?; 24 | 25 | Ok((GetBannedUsersResponse { banned_users }).into_response()) 26 | } 27 | -------------------------------------------------------------------------------- /src/impls/chat/moderation/kick_user.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let KickUserRequest { 10 | guild_id, 11 | user_id: user_to_kick, 12 | } = request.into_message().await?; 13 | 14 | if user_id == user_to_kick { 15 | return Err(ServerError::CantKickOrBanYourself.into()); 16 | } 17 | 18 | let chat_tree = &svc.deps.chat_tree; 19 | 20 | chat_tree.check_guild_user(guild_id, user_id).await?; 21 | chat_tree.is_user_in_guild(guild_id, user_to_kick).await?; 22 | chat_tree 23 | .check_perms(guild_id, None, user_id, "user.manage.kick", false) 24 | .await?; 25 | 26 | chat_tree.kick_user_logic(guild_id, user_to_kick).await?; 27 | 28 | svc.send_event_through_chan( 29 | EventSub::Guild(guild_id), 30 | stream_event::Event::LeftMember(stream_event::MemberLeft { 31 | guild_id, 32 | member_id: user_to_kick, 33 | leave_reason: LeaveReason::Kicked.into(), 34 | }), 35 | None, 36 | EventContext::empty(), 37 | ); 38 | 39 | svc.dispatch_guild_leave(guild_id, user_to_kick).await?; 40 | 41 | Ok((KickUserResponse {}).into_response()) 42 | } 43 | -------------------------------------------------------------------------------- /src/impls/chat/moderation/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod ban_user; 4 | pub mod get_banned_users; 5 | pub mod kick_user; 6 | pub mod unban_user; 7 | -------------------------------------------------------------------------------- /src/impls/chat/moderation/unban_user.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let UnbanUserRequest { 10 | guild_id, 11 | user_id: user_to_unban, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree.check_guild_user(guild_id, user_id).await?; 17 | chat_tree 18 | .check_perms(guild_id, None, user_id, "user.manage.unban", false) 19 | .await?; 20 | 21 | chat_tree 22 | .remove(make_banned_member_key(guild_id, user_to_unban)) 23 | .await?; 24 | 25 | Ok((UnbanUserResponse {}).into_response()) 26 | } 27 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/add_guild_role.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | #[allow(unused_variables)] 8 | let user_id = svc.deps.auth(&request).await?; 9 | 10 | let AddGuildRoleRequest { 11 | guild_id, 12 | name, 13 | color, 14 | hoist, 15 | pingable, 16 | } = request.into_message().await?; 17 | 18 | let chat_tree = &svc.deps.chat_tree; 19 | 20 | chat_tree.check_guild_user(guild_id, user_id).await?; 21 | chat_tree 22 | .check_perms(guild_id, None, user_id, "roles.manage", false) 23 | .await?; 24 | 25 | let role = Role { 26 | name: name.clone(), 27 | color, 28 | hoist, 29 | pingable, 30 | }; 31 | let role_id = chat_tree.add_guild_role_logic(guild_id, None, role).await?; 32 | svc.send_event_through_chan( 33 | EventSub::Guild(guild_id), 34 | stream_event::Event::RoleCreated(stream_event::RoleCreated { 35 | guild_id, 36 | role_id, 37 | name, 38 | color, 39 | hoist, 40 | pingable, 41 | }), 42 | Some(PermCheck::new( 43 | guild_id, 44 | None, 45 | all_permissions::ROLES_GET, 46 | false, 47 | )), 48 | EventContext::empty(), 49 | ); 50 | 51 | Ok((AddGuildRoleResponse { role_id }).into_response()) 52 | } 53 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/delete_guild_role.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteGuildRoleRequest { guild_id, role_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | chat_tree 15 | .check_perms(guild_id, None, user_id, "roles.manage", false) 16 | .await?; 17 | 18 | chat_tree 19 | .chat_tree 20 | .remove(&make_guild_role_key(guild_id, role_id)) 21 | .await 22 | .map_err(ServerError::DbError)? 23 | .ok_or(ServerError::NoSuchRole { guild_id, role_id })?; 24 | 25 | svc.send_event_through_chan( 26 | EventSub::Guild(guild_id), 27 | stream_event::Event::RoleDeleted(stream_event::RoleDeleted { guild_id, role_id }), 28 | Some(PermCheck::new( 29 | guild_id, 30 | None, 31 | all_permissions::ROLES_GET, 32 | false, 33 | )), 34 | EventContext::empty(), 35 | ); 36 | 37 | Ok((DeleteGuildRoleResponse {}).into_response()) 38 | } 39 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/get_guild_roles.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetGuildRolesRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | chat_tree 15 | .check_perms(guild_id, None, user_id, "roles.get", false) 16 | .await?; 17 | 18 | let roles = chat_tree.get_guild_roles_logic(guild_id).await?; 19 | 20 | Ok((GetGuildRolesResponse { roles }).into_response()) 21 | } 22 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/get_permissions.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetPermissionsRequest { 10 | guild_id, 11 | channel_id, 12 | role_id, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree.check_guild_user(guild_id, user_id).await?; 18 | chat_tree 19 | .check_perms( 20 | guild_id, 21 | channel_id, 22 | user_id, 23 | "permissions.manage.get", 24 | false, 25 | ) 26 | .await?; 27 | 28 | let perms = chat_tree 29 | .get_permissions_logic(guild_id, channel_id, role_id) 30 | .await? 31 | .into_iter() 32 | .map(|(m, ok)| Permission { 33 | matches: m.into(), 34 | ok, 35 | }) 36 | .collect(); 37 | 38 | Ok((GetPermissionsResponse { perms }).into_response()) 39 | } 40 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/get_user_roles.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetUserRolesRequest { 10 | guild_id, 11 | user_id: user_to_fetch, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree.check_guild_user(guild_id, user_id).await?; 17 | chat_tree.is_user_in_guild(guild_id, user_to_fetch).await?; 18 | let fetch_user = (user_to_fetch == 0) 19 | .then(|| user_id) 20 | .unwrap_or(user_to_fetch); 21 | if fetch_user != user_id { 22 | chat_tree 23 | .check_perms(guild_id, None, user_id, "roles.user.get", false) 24 | .await?; 25 | } 26 | 27 | let roles = chat_tree.get_user_roles_logic(guild_id, fetch_user).await?; 28 | 29 | Ok((GetUserRolesResponse { roles }).into_response()) 30 | } 31 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/give_up_ownership.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GiveUpOwnershipRequest { guild_id } = request.into_message().await?; 10 | 11 | let chat_tree = &svc.deps.chat_tree; 12 | 13 | chat_tree.check_guild_user(guild_id, user_id).await?; 14 | 15 | chat_tree 16 | .check_perms(guild_id, None, user_id, "", true) 17 | .await?; 18 | 19 | let mut guild = chat_tree.get_guild_logic(guild_id).await?; 20 | if guild.owner_ids.len() > 1 { 21 | if let Some(pos) = guild.owner_ids.iter().position(|id| user_id.eq(id)) { 22 | guild.owner_ids.remove(pos); 23 | } 24 | } else { 25 | return Err(ServerError::MustNotBeLastOwner.into()); 26 | } 27 | 28 | Ok((GiveUpOwnershipResponse {}).into_response()) 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/grant_ownership.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GrantOwnershipRequest { 10 | new_owner_id, 11 | guild_id, 12 | } = request.into_message().await?; 13 | 14 | let chat_tree = &svc.deps.chat_tree; 15 | 16 | chat_tree.check_guild_user(guild_id, user_id).await?; 17 | chat_tree.is_user_in_guild(guild_id, new_owner_id).await?; 18 | 19 | chat_tree 20 | .check_perms(guild_id, None, user_id, "", true) 21 | .await?; 22 | 23 | let mut guild = chat_tree.get_guild_logic(guild_id).await?; 24 | guild.owner_ids.push(new_owner_id); 25 | chat_tree.put_guild_logic(guild_id, guild).await?; 26 | 27 | Ok((GrantOwnershipResponse {}).into_response()) 28 | } 29 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/manage_user_roles.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let ManageUserRolesRequest { 10 | guild_id, 11 | user_id: user_to_manage, 12 | give_role_ids, 13 | take_role_ids, 14 | } = request.into_message().await?; 15 | 16 | let chat_tree = &svc.deps.chat_tree; 17 | 18 | chat_tree.check_guild_user(guild_id, user_id).await?; 19 | chat_tree.is_user_in_guild(guild_id, user_to_manage).await?; 20 | chat_tree 21 | .check_perms(guild_id, None, user_id, "roles.user.manage", false) 22 | .await?; 23 | let user_to_manage = if user_to_manage != 0 { 24 | user_to_manage 25 | } else { 26 | user_id 27 | }; 28 | 29 | let new_role_ids = chat_tree 30 | .manage_user_roles_logic(guild_id, user_to_manage, give_role_ids, take_role_ids) 31 | .await?; 32 | 33 | svc.send_event_through_chan( 34 | EventSub::Guild(guild_id), 35 | stream_event::Event::UserRolesUpdated(stream_event::UserRolesUpdated { 36 | guild_id, 37 | user_id: user_to_manage, 38 | new_role_ids, 39 | }), 40 | Some(PermCheck::new(guild_id, None, "roles.user.get", false)), 41 | EventContext::empty(), 42 | ); 43 | 44 | Ok((ManageUserRolesResponse {}).into_response()) 45 | } 46 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/mod.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub mod add_guild_role; 4 | pub mod delete_guild_role; 5 | pub mod get_guild_roles; 6 | pub mod get_permissions; 7 | pub mod get_user_roles; 8 | pub mod give_up_ownership; 9 | pub mod grant_ownership; 10 | pub mod manage_user_roles; 11 | pub mod modify_guild_role; 12 | pub mod move_role; 13 | pub mod query_has_permission; 14 | pub mod set_permissions; 15 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/modify_guild_role.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let ModifyGuildRoleRequest { 10 | guild_id, 11 | role_id, 12 | new_name, 13 | new_color, 14 | new_hoist, 15 | new_pingable, 16 | } = request.into_message().await?; 17 | 18 | let chat_tree = &svc.deps.chat_tree; 19 | 20 | chat_tree.check_guild_user(guild_id, user_id).await?; 21 | chat_tree 22 | .check_perms(guild_id, None, user_id, "roles.manage", false) 23 | .await?; 24 | 25 | let key = make_guild_role_key(guild_id, role_id); 26 | let mut role = if let Some(raw) = chat_tree.get(key).await? { 27 | db::deser_role(raw) 28 | } else { 29 | return Err(ServerError::NoSuchRole { guild_id, role_id }.into()); 30 | }; 31 | 32 | if let Some(new_name) = new_name.clone() { 33 | role.name = new_name; 34 | } 35 | if let Some(new_color) = new_color { 36 | role.color = new_color; 37 | } 38 | if let Some(new_hoist) = new_hoist { 39 | role.hoist = new_hoist; 40 | } 41 | if let Some(new_pingable) = new_pingable { 42 | role.pingable = new_pingable; 43 | } 44 | 45 | let ser_role = rkyv_ser(&role); 46 | chat_tree.insert(key, ser_role).await?; 47 | 48 | svc.send_event_through_chan( 49 | EventSub::Guild(guild_id), 50 | stream_event::Event::RoleUpdated(stream_event::RoleUpdated { 51 | guild_id, 52 | role_id, 53 | new_name, 54 | new_color, 55 | new_hoist, 56 | new_pingable, 57 | }), 58 | Some(PermCheck::new( 59 | guild_id, 60 | None, 61 | all_permissions::ROLES_GET, 62 | false, 63 | )), 64 | EventContext::empty(), 65 | ); 66 | 67 | Ok((ModifyGuildRoleResponse {}).into_response()) 68 | } 69 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/move_role.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let MoveRoleRequest { 10 | guild_id, 11 | role_id, 12 | new_position, 13 | } = request.into_message().await?; 14 | 15 | let chat_tree = &svc.deps.chat_tree; 16 | 17 | chat_tree.check_guild_user(guild_id, user_id).await?; 18 | chat_tree 19 | .check_perms(guild_id, None, user_id, "roles.manage", false) 20 | .await?; 21 | chat_tree.does_role_exist(guild_id, role_id).await?; 22 | 23 | if let Some(pos) = new_position { 24 | chat_tree 25 | .move_role_logic(guild_id, role_id, Some(pos.clone())) 26 | .await?; 27 | svc.send_event_through_chan( 28 | EventSub::Guild(guild_id), 29 | stream_event::Event::RoleMoved(stream_event::RoleMoved { 30 | guild_id, 31 | role_id, 32 | new_position: Some(pos), 33 | }), 34 | Some(PermCheck::new( 35 | guild_id, 36 | None, 37 | all_permissions::ROLES_GET, 38 | false, 39 | )), 40 | EventContext::empty(), 41 | ); 42 | } 43 | 44 | Ok((MoveRoleResponse {}).into_response()) 45 | } 46 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/query_has_permission.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | let request = request.into_message().await?; 9 | 10 | svc.deps 11 | .chat_tree 12 | .query_has_permission_request(user_id, request) 13 | .await 14 | .map(IntoResponse::into_response) 15 | } 16 | -------------------------------------------------------------------------------- /src/impls/chat/permissions/set_permissions.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ChatServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let SetPermissionsRequest { 10 | guild_id, 11 | channel_id, 12 | role_id, 13 | perms_to_give, 14 | } = request.into_message().await?; 15 | 16 | let chat_tree = &svc.deps.chat_tree; 17 | 18 | chat_tree.check_guild_user(guild_id, user_id).await?; 19 | chat_tree 20 | .check_perms( 21 | guild_id, 22 | channel_id, 23 | user_id, 24 | "permissions.manage.set", 25 | false, 26 | ) 27 | .await?; 28 | 29 | // TODO: fix 30 | if !perms_to_give.is_empty() { 31 | chat_tree 32 | .set_permissions_logic(guild_id, channel_id, role_id, perms_to_give.clone()) 33 | .await?; 34 | let members = chat_tree.get_guild_members_logic(guild_id).await?.members; 35 | let guild_owners = chat_tree.get_guild_owners(guild_id).await?; 36 | let mut for_users = Vec::with_capacity(members.len()); 37 | for user_id in members.iter() { 38 | if !guild_owners.contains(user_id) { 39 | let maybe_user = chat_tree 40 | .get_user_roles_logic(guild_id, *user_id) 41 | .await? 42 | .contains(&role_id) 43 | .then(|| *user_id); 44 | if let Some(user_id) = maybe_user { 45 | for_users.push(user_id); 46 | } 47 | } 48 | } 49 | for perm in &perms_to_give { 50 | svc.send_event_through_chan( 51 | EventSub::Guild(guild_id), 52 | stream_event::Event::PermissionUpdated(stream_event::PermissionUpdated { 53 | guild_id, 54 | channel_id, 55 | query: perm.matches.clone(), 56 | ok: perm.ok, 57 | }), 58 | None, 59 | EventContext::new(for_users.clone()), 60 | ); 61 | } 62 | svc.send_event_through_chan( 63 | EventSub::Guild(guild_id), 64 | stream_event::Event::RolePermsUpdated(stream_event::RolePermissionsUpdated { 65 | guild_id, 66 | channel_id, 67 | role_id, 68 | new_perms: perms_to_give, 69 | }), 70 | Some(PermCheck::new( 71 | guild_id, 72 | None, 73 | all_permissions::ROLES_MANAGE, 74 | false, 75 | )), 76 | EventContext::empty(), 77 | ); 78 | Ok((SetPermissionsResponse {}).into_response()) 79 | } else { 80 | Err(ServerError::NoPermissionsSpecified.into()) 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/impls/chat/stream_events.rs: -------------------------------------------------------------------------------- 1 | use tracing::Instrument; 2 | 3 | use super::*; 4 | 5 | pub async fn handler( 6 | svc: &ChatServer, 7 | request: Request<()>, 8 | socket: Socket, 9 | ) -> Result<(), HrpcServerError> { 10 | let user_id = svc.deps.auth(&request).await?; 11 | 12 | let fut = async move { 13 | tracing::debug!("stream events validated"); 14 | 15 | let mut cancel_recv = svc.deps.chat_event_canceller.subscribe(); 16 | 17 | tracing::debug!("creating stream events processor"); 18 | let mut send_task = svc.spawn_event_stream_processor(user_id, socket); 19 | 20 | loop { 21 | tokio::select! { 22 | Ok(cancelled_user_id) = cancel_recv.recv() => { 23 | if cancelled_user_id == user_id { 24 | return Err(("scherzo.stream-cancelled", "stream events cancelled manually").into()); 25 | } 26 | } 27 | res = &mut send_task => { 28 | match res { 29 | Err(err) => return Err(format!("stream events send loop task panicked: {}, aborting", err).into()), 30 | Ok(_) => break, 31 | } 32 | } 33 | else => tokio::task::yield_now().await, 34 | } 35 | } 36 | 37 | tracing::debug!("stream events ended"); 38 | 39 | Ok(()) 40 | }; 41 | 42 | fut.instrument(tracing::debug_span!("stream_events", user_id = %user_id)) 43 | .await 44 | } 45 | -------------------------------------------------------------------------------- /src/impls/chat/trigger_action.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &ChatServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/emote/add_emote_to_pack.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let AddEmoteToPackRequest { pack_id, emote } = request.into_message().await?; 10 | 11 | if let Some(emote) = emote { 12 | svc.deps 13 | .emote_tree 14 | .check_if_emote_pack_owner(pack_id, user_id) 15 | .await?; 16 | 17 | let emote_key = make_emote_pack_emote_key(pack_id, &emote.name); 18 | let data = rkyv_ser(&emote); 19 | 20 | svc.deps.emote_tree.insert(emote_key, data).await?; 21 | 22 | let equipped_users = svc 23 | .deps 24 | .emote_tree 25 | .calculate_users_pack_equipped(pack_id) 26 | .await?; 27 | svc.send_event_through_chan( 28 | EventSub::Homeserver, 29 | stream_event::Event::EmotePackEmotesUpdated(EmotePackEmotesUpdated { 30 | pack_id, 31 | added_emotes: vec![emote], 32 | deleted_emotes: Vec::new(), 33 | }), 34 | None, 35 | EventContext::new(equipped_users), 36 | ); 37 | } 38 | 39 | Ok((AddEmoteToPackResponse {}).into_response()) 40 | } 41 | -------------------------------------------------------------------------------- /src/impls/emote/create_emote_pack.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let CreateEmotePackRequest { pack_name } = request.into_message().await?; 10 | 11 | let pack_id = gen_rand_u64(); 12 | let key = make_emote_pack_key(pack_id); 13 | 14 | let emote_pack = EmotePack { 15 | pack_id, 16 | pack_name, 17 | pack_owner: user_id, 18 | }; 19 | let data = rkyv_ser(&emote_pack); 20 | 21 | svc.deps.emote_tree.insert(key, data).await?; 22 | 23 | svc.deps 24 | .emote_tree 25 | .equip_emote_pack_logic(user_id, pack_id) 26 | .await?; 27 | svc.send_event_through_chan( 28 | EventSub::Homeserver, 29 | stream_event::Event::EmotePackAdded(EmotePackAdded { 30 | pack: Some(emote_pack), 31 | }), 32 | None, 33 | EventContext::new(vec![user_id]), 34 | ); 35 | 36 | Ok((CreateEmotePackResponse { pack_id }).into_response()) 37 | } 38 | -------------------------------------------------------------------------------- /src/impls/emote/delete_emote_from_pack.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteEmoteFromPackRequest { pack_id, name } = request.into_message().await?; 10 | 11 | svc.deps 12 | .emote_tree 13 | .check_if_emote_pack_owner(pack_id, user_id) 14 | .await?; 15 | 16 | let key = make_emote_pack_emote_key(pack_id, &name); 17 | 18 | svc.deps.emote_tree.remove(key).await?; 19 | 20 | let equipped_users = svc 21 | .deps 22 | .emote_tree 23 | .calculate_users_pack_equipped(pack_id) 24 | .await?; 25 | svc.send_event_through_chan( 26 | EventSub::Homeserver, 27 | stream_event::Event::EmotePackEmotesUpdated(EmotePackEmotesUpdated { 28 | pack_id, 29 | added_emotes: Vec::new(), 30 | deleted_emotes: vec![name], 31 | }), 32 | None, 33 | EventContext::new(equipped_users), 34 | ); 35 | 36 | Ok((DeleteEmoteFromPackResponse {}).into_response()) 37 | } 38 | -------------------------------------------------------------------------------- /src/impls/emote/delete_emote_pack.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DeleteEmotePackRequest { pack_id } = request.into_message().await?; 10 | 11 | svc.deps 12 | .emote_tree 13 | .check_if_emote_pack_owner(pack_id, user_id) 14 | .await?; 15 | 16 | let key = make_emote_pack_key(pack_id); 17 | 18 | let mut batch = Batch::default(); 19 | batch.remove(key); 20 | for res in svc.deps.emote_tree.scan_prefix(&key).await { 21 | let (key, _) = res?; 22 | batch.remove(key); 23 | } 24 | svc.deps 25 | .emote_tree 26 | .inner 27 | .apply_batch(batch) 28 | .await 29 | .map_err(ServerError::DbError)?; 30 | 31 | svc.deps 32 | .emote_tree 33 | .dequip_emote_pack_logic(user_id, pack_id) 34 | .await?; 35 | 36 | let equipped_users = svc 37 | .deps 38 | .emote_tree 39 | .calculate_users_pack_equipped(pack_id) 40 | .await?; 41 | svc.send_event_through_chan( 42 | EventSub::Homeserver, 43 | stream_event::Event::EmotePackDeleted(EmotePackDeleted { pack_id }), 44 | None, 45 | EventContext::new(equipped_users), 46 | ); 47 | 48 | Ok((DeleteEmotePackResponse {}).into_response()) 49 | } 50 | -------------------------------------------------------------------------------- /src/impls/emote/dequip_emote_pack.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let DequipEmotePackRequest { pack_id } = request.into_message().await?; 10 | 11 | svc.deps 12 | .emote_tree 13 | .dequip_emote_pack_logic(user_id, pack_id) 14 | .await?; 15 | 16 | svc.send_event_through_chan( 17 | EventSub::Homeserver, 18 | stream_event::Event::EmotePackDeleted(EmotePackDeleted { pack_id }), 19 | None, 20 | EventContext::new(vec![user_id]), 21 | ); 22 | 23 | Ok((DequipEmotePackResponse {}).into_response()) 24 | } 25 | -------------------------------------------------------------------------------- /src/impls/emote/equip_emote_pack.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let EquipEmotePackRequest { pack_id } = request.into_message().await?; 10 | 11 | let key = make_emote_pack_key(pack_id); 12 | if let Some(data) = svc.deps.emote_tree.get(key).await? { 13 | let pack = db::deser_emote_pack(data); 14 | svc.deps 15 | .emote_tree 16 | .equip_emote_pack_logic(user_id, pack_id) 17 | .await?; 18 | svc.send_event_through_chan( 19 | EventSub::Homeserver, 20 | stream_event::Event::EmotePackAdded(EmotePackAdded { pack: Some(pack) }), 21 | None, 22 | EventContext::new(vec![user_id]), 23 | ); 24 | } else { 25 | return Err(ServerError::EmotePackNotFound.into()); 26 | } 27 | 28 | Ok((EquipEmotePackResponse {}).into_response()) 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/emote/get_emote_pack_emotes.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | svc.deps.auth(&request).await?; 8 | 9 | let GetEmotePackEmotesRequest { pack_id } = request.into_message().await?; 10 | 11 | let pack_key = make_emote_pack_key(pack_id); 12 | 13 | if svc.deps.emote_tree.get(pack_key).await?.is_none() { 14 | return Err(ServerError::EmotePackNotFound.into()); 15 | } 16 | 17 | let emotes = svc 18 | .deps 19 | .emote_tree 20 | .inner 21 | .scan_prefix(&pack_key) 22 | .await 23 | .try_fold(Vec::new(), |mut all, res| { 24 | let (key, value) = res.map_err(ServerError::from)?; 25 | if key.len() > pack_key.len() { 26 | all.push(db::deser_emote(value)); 27 | } 28 | ServerResult::Ok(all) 29 | })?; 30 | 31 | Ok((GetEmotePackEmotesResponse { emotes }).into_response()) 32 | } 33 | -------------------------------------------------------------------------------- /src/impls/emote/get_emote_packs.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &EmoteServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let prefix = make_equipped_emote_prefix(user_id); 10 | let equipped_packs = svc 11 | .deps 12 | .emote_tree 13 | .inner 14 | .scan_prefix(&prefix) 15 | .await 16 | .try_fold(Vec::new(), |mut all, res| { 17 | let (key, _) = res.map_err(ServerError::from)?; 18 | if key.len() == make_equipped_emote_key(user_id, 0).len() { 19 | // Safety: since it will always be 8 bytes left afterwards 20 | let pack_id = deser_id(key.split_at(prefix.len()).1); 21 | all.push(pack_id); 22 | } 23 | ServerResult::Ok(all) 24 | })?; 25 | 26 | let packs = svc 27 | .deps 28 | .emote_tree 29 | .inner 30 | .scan_prefix(EMOTEPACK_PREFIX) 31 | .await 32 | .try_fold(Vec::new(), |mut all, res| { 33 | let (key, val) = res.map_err(ServerError::from)?; 34 | if key.len() == make_emote_pack_key(0).len() { 35 | // Safety: since it will always be 8 bytes left afterwards 36 | let pack_id = deser_id(key.split_at(EMOTEPACK_PREFIX.len()).1); 37 | if equipped_packs.contains(&pack_id) { 38 | all.push(db::deser_emote_pack(val)); 39 | } 40 | } 41 | ServerResult::Ok(all) 42 | })?; 43 | 44 | Ok((GetEmotePacksResponse { packs }).into_response()) 45 | } 46 | -------------------------------------------------------------------------------- /src/impls/emote/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::api::{ 2 | chat::Event, 3 | emote::{emote_service_server::EmoteService, *}, 4 | }; 5 | 6 | use super::{ 7 | chat::{EventBroadcast, EventContext, EventSub, PermCheck}, 8 | gen_rand_u64, 9 | prelude::*, 10 | }; 11 | 12 | use db::{ 13 | emote::*, 14 | profile::{make_user_profile_key, USER_PREFIX}, 15 | }; 16 | 17 | pub mod add_emote_to_pack; 18 | pub mod create_emote_pack; 19 | pub mod delete_emote_from_pack; 20 | pub mod delete_emote_pack; 21 | pub mod dequip_emote_pack; 22 | pub mod equip_emote_pack; 23 | pub mod get_emote_pack_emotes; 24 | pub mod get_emote_packs; 25 | 26 | #[derive(Clone)] 27 | pub struct EmoteServer { 28 | disable_ratelimits: bool, 29 | deps: Arc, 30 | } 31 | 32 | impl EmoteServer { 33 | pub fn new(deps: Arc) -> Self { 34 | Self { 35 | disable_ratelimits: deps.config.policy.ratelimit.disable, 36 | deps, 37 | } 38 | } 39 | 40 | pub fn batch(mut self) -> Self { 41 | self.disable_ratelimits = true; 42 | self 43 | } 44 | 45 | #[inline(always)] 46 | fn send_event_through_chan( 47 | &self, 48 | sub: EventSub, 49 | event: stream_event::Event, 50 | perm_check: Option>, 51 | context: EventContext, 52 | ) { 53 | let broadcast = EventBroadcast::new(sub, Event::Emote(event), perm_check, context); 54 | 55 | drop(self.deps.chat_event_sender.send(Arc::new(broadcast))); 56 | } 57 | } 58 | 59 | impl EmoteService for EmoteServer { 60 | impl_unary_handlers! { 61 | #[rate(7, 5)] 62 | delete_emote_from_pack, DeleteEmoteFromPackRequest, DeleteEmoteFromPackResponse; 63 | #[rate(7, 5)] 64 | add_emote_to_pack, AddEmoteToPackRequest, AddEmoteToPackResponse; 65 | #[rate(3, 5)] 66 | delete_emote_pack, DeleteEmotePackRequest, DeleteEmotePackResponse; 67 | #[rate(3, 5)] 68 | create_emote_pack, CreateEmotePackRequest, CreateEmotePackResponse; 69 | #[rate(7, 4)] 70 | get_emote_pack_emotes, GetEmotePackEmotesRequest, GetEmotePackEmotesResponse; 71 | #[rate(5, 5)] 72 | get_emote_packs, GetEmotePacksRequest, GetEmotePacksResponse; 73 | #[rate(5, 5)] 74 | equip_emote_pack, EquipEmotePackRequest, EquipEmotePackResponse; 75 | #[rate(5, 5)] 76 | dequip_emote_pack, DequipEmotePackRequest, DequipEmotePackResponse; 77 | } 78 | } 79 | 80 | #[derive(Clone)] 81 | pub struct EmoteTree { 82 | pub inner: Tree, 83 | } 84 | 85 | impl EmoteTree { 86 | impl_db_methods!(inner); 87 | 88 | pub async fn new(db: &Db) -> DbResult { 89 | let inner = db.open_tree(b"emote").await?; 90 | Ok(Self { inner }) 91 | } 92 | 93 | pub async fn check_if_emote_pack_owner( 94 | &self, 95 | pack_id: u64, 96 | user_id: u64, 97 | ) -> ServerResult { 98 | let key = make_emote_pack_key(pack_id); 99 | 100 | let pack = if let Some(data) = self.get(key).await? { 101 | let pack = db::deser_emote_pack(data); 102 | 103 | if pack.pack_owner != user_id { 104 | return Err(ServerError::NotEmotePackOwner.into()); 105 | } 106 | 107 | pack 108 | } else { 109 | return Err(ServerError::EmotePackNotFound.into()); 110 | }; 111 | 112 | Ok(pack) 113 | } 114 | 115 | pub async fn dequip_emote_pack_logic(&self, user_id: u64, pack_id: u64) -> ServerResult<()> { 116 | let key = make_equipped_emote_key(user_id, pack_id); 117 | self.remove(key).await?; 118 | Ok(()) 119 | } 120 | 121 | pub async fn equip_emote_pack_logic(&self, user_id: u64, pack_id: u64) -> ServerResult<()> { 122 | let key = make_equipped_emote_key(user_id, pack_id); 123 | self.insert(key, &[]).await?; 124 | Ok(()) 125 | } 126 | 127 | pub async fn calculate_users_pack_equipped(&self, pack_id: u64) -> ServerResult> { 128 | let mut result = Vec::new(); 129 | for user_id in 130 | self.inner 131 | .scan_prefix(USER_PREFIX) 132 | .await 133 | .try_fold(Vec::new(), |mut all, res| { 134 | let (key, _) = res.map_err(ServerError::from)?; 135 | if key.len() == make_user_profile_key(0).len() { 136 | all.push(deser_id(key.split_at(USER_PREFIX.len()).1)); 137 | } 138 | ServerResult::Ok(all) 139 | })? 140 | { 141 | let prefix = make_equipped_emote_prefix(user_id); 142 | let mut has = false; 143 | for res in self.inner.scan_prefix(&prefix).await { 144 | let (key, _) = res.map_err(ServerError::from)?; 145 | if key.len() == make_equipped_emote_key(user_id, 0).len() { 146 | let id = deser_id(key.split_at(prefix.len()).1); 147 | if id == pack_id { 148 | has = true; 149 | break; 150 | } 151 | } 152 | } 153 | if has { 154 | result.push(user_id); 155 | } 156 | } 157 | Ok(result) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/impls/mediaproxy/can_instant_view.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &MediaproxyServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | svc.deps.auth(&request).await?; 8 | 9 | let CanInstantViewRequest { url } = request.into_message().await?; 10 | 11 | if let Some(val) = get_from_cache(&url) { 12 | return Ok((CanInstantViewResponse { 13 | can_instant_view: matches!(val.value, Metadata::Site(_)), 14 | }) 15 | .into_response()); 16 | } 17 | 18 | let response = svc 19 | .http 20 | .get(url) 21 | .send() 22 | .await 23 | .map_err(ServerError::FailedToFetchLink)? 24 | .error_for_status() 25 | .map_err(ServerError::FailedToFetchLink)?; 26 | 27 | let ok = get_mimetype(response.headers()).eq("text/html"); 28 | 29 | Ok((CanInstantViewResponse { 30 | can_instant_view: ok, 31 | }) 32 | .into_response()) 33 | } 34 | -------------------------------------------------------------------------------- /src/impls/mediaproxy/fetch_link_metadata.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &MediaproxyServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | svc.deps.auth(&request).await?; 8 | 9 | let FetchLinkMetadataRequest { url } = request.into_message().await?; 10 | 11 | let data = svc.fetch_metadata(url).await?.into(); 12 | 13 | Ok((FetchLinkMetadataResponse { data: Some(data) }).into_response()) 14 | } 15 | -------------------------------------------------------------------------------- /src/impls/mediaproxy/instant_view.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &MediaproxyServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | svc.deps.auth(&request).await?; 8 | 9 | let InstantViewRequest { url } = request.into_message().await?; 10 | 11 | let data = svc.fetch_metadata(url).await?; 12 | 13 | let msg = if let Metadata::Site(html) = data { 14 | let metadata = site_metadata_from_html(&html); 15 | 16 | InstantViewResponse { 17 | content: html.text_content.clone(), 18 | is_valid: true, 19 | metadata: Some(metadata), 20 | } 21 | } else { 22 | InstantViewResponse { 23 | is_valid: false, 24 | ..Default::default() 25 | } 26 | }; 27 | 28 | Ok(msg.into_response()) 29 | } 30 | -------------------------------------------------------------------------------- /src/impls/profile/get_app_data.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ProfileServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let GetAppDataRequest { app_id } = request.into_message().await?; 10 | let app_data = svc 11 | .deps 12 | .profile_tree 13 | .get(make_user_metadata_key(user_id, &app_id)) 14 | .await? 15 | .unwrap_or_default() 16 | .into(); 17 | 18 | Ok((GetAppDataResponse { app_data }).into_response()) 19 | } 20 | -------------------------------------------------------------------------------- /src/impls/profile/get_profile.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ProfileServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | svc.deps.auth(&request).await?; 8 | 9 | let GetProfileRequest { user_id } = request.into_message().await?; 10 | 11 | svc.deps 12 | .profile_tree 13 | .get_profile_logic(user_id) 14 | .await 15 | .map(|p| GetProfileResponse { profile: Some(p) }) 16 | .map(IntoResponse::into_response) 17 | .map_err(Into::into) 18 | } 19 | -------------------------------------------------------------------------------- /src/impls/profile/mod.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | chat::{EventBroadcast, EventContext, EventSub, PermCheck}, 3 | prelude::*, 4 | }; 5 | 6 | use crate::api::{ 7 | chat::Event, 8 | profile::{profile_service_server::ProfileService, *}, 9 | }; 10 | use db::profile::*; 11 | 12 | pub mod get_app_data; 13 | pub mod get_profile; 14 | pub mod set_app_data; 15 | pub mod update_profile; 16 | 17 | #[derive(Clone)] 18 | pub struct ProfileServer { 19 | disable_ratelimits: bool, 20 | deps: Arc, 21 | } 22 | 23 | impl ProfileServer { 24 | pub fn new(deps: Arc) -> Self { 25 | Self { 26 | disable_ratelimits: deps.config.policy.ratelimit.disable, 27 | deps, 28 | } 29 | } 30 | 31 | pub fn batch(mut self) -> Self { 32 | self.disable_ratelimits = true; 33 | self 34 | } 35 | 36 | #[inline(always)] 37 | fn send_event_through_chan( 38 | &self, 39 | sub: EventSub, 40 | event: stream_event::Event, 41 | perm_check: Option>, 42 | context: EventContext, 43 | ) { 44 | let broadcast = EventBroadcast::new(sub, Event::Profile(event), perm_check, context); 45 | 46 | drop(self.deps.chat_event_sender.send(Arc::new(broadcast))); 47 | } 48 | } 49 | 50 | impl ProfileService for ProfileServer { 51 | impl_unary_handlers! { 52 | #[rate(8, 5)] 53 | get_profile, GetProfileRequest, GetProfileResponse; 54 | #[rate(4, 2)] 55 | get_app_data, GetAppDataRequest, GetAppDataResponse; 56 | #[rate(2, 5)] 57 | set_app_data, SetAppDataRequest, SetAppDataResponse; 58 | #[rate(4, 5)] 59 | update_profile, UpdateProfileRequest, UpdateProfileResponse; 60 | } 61 | } 62 | 63 | #[derive(Clone)] 64 | pub struct ProfileTree { 65 | pub inner: Tree, 66 | } 67 | 68 | impl ProfileTree { 69 | impl_db_methods!(inner); 70 | 71 | pub async fn new(db: &Db) -> DbResult { 72 | let inner = db.open_tree(b"profile").await?; 73 | Ok(Self { inner }) 74 | } 75 | 76 | pub async fn update_profile_logic( 77 | &self, 78 | user_id: u64, 79 | new_user_name: Option, 80 | new_user_avatar: Option, 81 | new_user_status: Option, 82 | new_is_bot: Option, 83 | ) -> ServerResult<()> { 84 | let key = make_user_profile_key(user_id); 85 | 86 | let mut profile = self 87 | .get(key) 88 | .await? 89 | .map_or_else(Profile::default, db::deser_profile); 90 | 91 | if let Some(new_username) = new_user_name { 92 | profile.user_name = new_username; 93 | } 94 | if let Some(new_avatar) = new_user_avatar { 95 | if new_avatar.is_empty() { 96 | profile.user_avatar = None; 97 | } else { 98 | profile.user_avatar = Some(new_avatar); 99 | } 100 | } 101 | if let Some(new_status) = new_user_status { 102 | profile.user_status = new_status; 103 | } 104 | if let Some(new_is_bot) = new_is_bot { 105 | profile.is_bot = new_is_bot; 106 | } 107 | 108 | let buf = rkyv_ser(&profile); 109 | self.insert(key, buf).await?; 110 | 111 | Ok(()) 112 | } 113 | 114 | pub async fn get_profile_logic(&self, user_id: u64) -> ServerResult { 115 | let key = make_user_profile_key(user_id); 116 | 117 | let profile = if let Some(profile_raw) = self.get(key).await? { 118 | db::deser_profile(profile_raw) 119 | } else { 120 | return Err(ServerError::NoSuchUser(user_id).into()); 121 | }; 122 | 123 | Ok(profile) 124 | } 125 | 126 | pub async fn does_username_exist(&self, username: &str) -> ServerResult { 127 | for res in self.scan_prefix(USER_PREFIX).await { 128 | let (_, value) = res?; 129 | let profile = db::rkyv_arch::(&value); 130 | if profile.user_name == username { 131 | return Ok(true); 132 | } 133 | } 134 | Ok(false) 135 | } 136 | 137 | pub async fn does_user_exist(&self, user_id: u64) -> ServerResult<()> { 138 | self.contains_key(&make_user_profile_key(user_id)) 139 | .await? 140 | .then(|| Ok(())) 141 | .unwrap_or_else(|| Err(ServerError::NoSuchUser(user_id).into())) 142 | } 143 | 144 | /// Converts a local user ID to the corresponding foreign user ID and the host 145 | pub async fn local_to_foreign_id(&self, local_id: u64) -> ServerResult> { 146 | let key = make_local_to_foreign_user_key(local_id); 147 | 148 | Ok(self.get(key).await?.map(|raw| { 149 | let (raw_id, raw_host) = raw.split_at(size_of::()); 150 | // Safety: safe since we split at u64 boundary. 151 | let foreign_id = deser_id(raw_id); 152 | // Safety: all stored hosts are valid UTF-8 153 | let host = (unsafe { std::str::from_utf8_unchecked(raw_host) }).into(); 154 | (foreign_id, host) 155 | })) 156 | } 157 | 158 | /// Convert a foreign user ID to a local user ID 159 | pub async fn foreign_to_local_id( 160 | &self, 161 | foreign_id: u64, 162 | host: &str, 163 | ) -> ServerResult> { 164 | let key = make_foreign_to_local_user_key(foreign_id, host); 165 | 166 | // Safety: we store u64's only for these keys 167 | Ok(self.get(key).await?.map(deser_id)) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/impls/profile/set_app_data.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ProfileServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let user_id = svc.deps.auth(&request).await?; 8 | 9 | let SetAppDataRequest { app_id, app_data } = request.into_message().await?; 10 | svc.deps 11 | .profile_tree 12 | .insert(make_user_metadata_key(user_id, &app_id), app_data) 13 | .await?; 14 | 15 | Ok((SetAppDataResponse {}).into_response()) 16 | } 17 | -------------------------------------------------------------------------------- /src/impls/profile/update_profile.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &ProfileServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | #[allow(unused_variables)] 8 | let user_id = svc.deps.auth(&request).await?; 9 | 10 | let UpdateProfileRequest { 11 | new_user_name, 12 | new_user_avatar, 13 | new_user_status, 14 | new_is_bot, 15 | } = request.into_message().await?; 16 | 17 | if let Some(username) = new_user_name.as_deref() { 18 | if svc.deps.profile_tree.does_username_exist(username).await? { 19 | bail!(ServerError::UserAlreadyExists); 20 | } 21 | } 22 | 23 | svc.deps 24 | .profile_tree 25 | .update_profile_logic( 26 | user_id, 27 | new_user_name.clone(), 28 | new_user_avatar.clone(), 29 | new_user_status, 30 | new_is_bot, 31 | ) 32 | .await?; 33 | 34 | svc.send_event_through_chan( 35 | EventSub::Homeserver, 36 | stream_event::Event::ProfileUpdated(ProfileUpdated { 37 | user_id, 38 | new_username: new_user_name, 39 | new_avatar: new_user_avatar, 40 | new_status: new_user_status, 41 | new_is_bot, 42 | new_account_kind: None, 43 | }), 44 | None, 45 | EventContext::new( 46 | svc.deps 47 | .chat_tree 48 | .calculate_users_seeing_user(user_id) 49 | .await?, 50 | ), 51 | ); 52 | 53 | Ok((UpdateProfileResponse {}).into_response()) 54 | } 55 | -------------------------------------------------------------------------------- /src/impls/rest/about.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use crate::{rest_error_response, SCHERZO_VERSION}; 4 | 5 | use super::*; 6 | use crate::api::rest::About; 7 | use hrpc::{ 8 | common::future::{ready, Ready}, 9 | server::transport::http::HttpResponse, 10 | }; 11 | use tower::Service; 12 | 13 | pub fn handler(deps: Arc) -> RateLimit { 14 | let client_ip_header_name = deps.config.policy.ratelimit.client_ip_header_name.clone(); 15 | let allowed_ips = deps.config.policy.ratelimit.allowed_ips.clone(); 16 | RateLimit::new( 17 | AboutService { deps }, 18 | 3, 19 | Duration::from_secs(5), 20 | client_ip_header_name, 21 | allowed_ips, 22 | ) 23 | } 24 | 25 | pub struct AboutService { 26 | deps: Arc, 27 | } 28 | 29 | impl Service for AboutService { 30 | type Response = HttpResponse; 31 | 32 | type Error = Infallible; 33 | 34 | type Future = Ready>; 35 | 36 | fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll> { 37 | Ok(()).into() 38 | } 39 | 40 | fn call(&mut self, req: HttpRequest) -> Self::Future { 41 | if req.method() != Method::GET { 42 | return ready(Ok(rest_error_response( 43 | "method must be GET".to_string(), 44 | StatusCode::METHOD_NOT_ALLOWED, 45 | ))); 46 | } 47 | 48 | let json = serde_json::to_vec(&About { 49 | server_name: "Scherzo".to_string(), 50 | version: SCHERZO_VERSION.to_string(), 51 | about_server: self.deps.config.server_description.clone(), 52 | message_of_the_day: self.deps.runtime_config.lock().motd.clone(), 53 | }) 54 | .unwrap(); 55 | 56 | ready(Ok(http::Response::builder() 57 | .status(StatusCode::OK) 58 | .header( 59 | http::header::CONTENT_TYPE, 60 | HeaderValue::from_static("text/json"), 61 | ) 62 | .body(box_body(Body::from(json))) 63 | .unwrap())) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/impls/rest/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{http, utils::http_ratelimit::RateLimit}; 2 | 3 | use self::{about::AboutService, download::DownloadService, upload::UploadService}; 4 | 5 | use super::{gen_rand_inline_str, get_content_length, prelude::*}; 6 | 7 | use std::{ 8 | borrow::Cow, 9 | cmp, 10 | convert::Infallible, 11 | fs::Metadata, 12 | future::Future, 13 | path::{Path, PathBuf}, 14 | pin::Pin, 15 | str::FromStr, 16 | task::{Context, Poll}, 17 | time::Duration, 18 | }; 19 | 20 | use crate::api::{ 21 | exports::{ 22 | hrpc::{ 23 | exports::futures_util::{ 24 | future::{self, BoxFuture, Either}, 25 | ready, stream, FutureExt, Stream, StreamExt, 26 | }, 27 | server::transport::http::{box_body, HttpRequest, HttpResponse}, 28 | }, 29 | prost::bytes::{Bytes, BytesMut}, 30 | }, 31 | rest::{extract_file_info_from_download_response, FileId}, 32 | }; 33 | use hrpc::common::future::Ready; 34 | use http::{header, HeaderValue, Method, StatusCode, Uri}; 35 | use hyper::Body; 36 | use pin_project::pin_project; 37 | use tokio::{ 38 | fs::File, 39 | io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt, BufReader, BufWriter}, 40 | }; 41 | use tokio_util::io::poll_read_buf; 42 | use tower::{Layer, Service}; 43 | use tracing::info; 44 | 45 | pub mod about; 46 | pub mod download; 47 | pub mod upload; 48 | 49 | const SEPERATOR: u8 = b'\n'; 50 | 51 | type Out = Result; 52 | 53 | #[derive(Clone)] 54 | pub struct RestServiceLayer { 55 | deps: Arc, 56 | } 57 | 58 | impl RestServiceLayer { 59 | pub fn new(deps: Arc) -> Self { 60 | Self { deps } 61 | } 62 | } 63 | 64 | impl Layer for RestServiceLayer 65 | where 66 | S: tower::Service + Send + 'static, 67 | { 68 | type Service = RestService; 69 | 70 | fn layer(&self, inner: S) -> Self::Service { 71 | RestService { 72 | download: download::handler(self.deps.clone()), 73 | upload: upload::handler(self.deps.clone()), 74 | about: about::handler(self.deps.clone()), 75 | inner, 76 | } 77 | } 78 | } 79 | 80 | pub struct RestService { 81 | download: RateLimit, 82 | upload: RateLimit, 83 | about: RateLimit, 84 | inner: S, 85 | } 86 | 87 | impl Service for RestService 88 | where 89 | S: tower::Service + Send + 'static, 90 | { 91 | type Response = HttpResponse; 92 | 93 | type Error = Infallible; 94 | 95 | type Future = RestFuture; 96 | 97 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 98 | let pending = Service::poll_ready(&mut self.inner, cx).is_pending() 99 | | Service::poll_ready(&mut self.about, cx).is_pending() 100 | | Service::poll_ready(&mut self.download, cx).is_pending() 101 | | Service::poll_ready(&mut self.upload, cx).is_pending(); 102 | 103 | pending 104 | .then(|| Poll::Pending) 105 | .unwrap_or(Poll::Ready(Ok(()))) 106 | } 107 | 108 | fn call(&mut self, req: HttpRequest) -> Self::Future { 109 | let path = req.uri().path(); 110 | 111 | if path.starts_with("/_harmony/media/download/") { 112 | RestFuture::Other(Service::call(&mut self.download, req)) 113 | } else { 114 | match path { 115 | "/_harmony/media/upload" => RestFuture::Other(Service::call(&mut self.upload, req)), 116 | "/_harmony/about" => RestFuture::About(Service::call(&mut self.about, req)), 117 | _ => RestFuture::Inner(Service::call(&mut self.inner, req)), 118 | } 119 | } 120 | } 121 | } 122 | 123 | #[pin_project(project = EnumProj)] 124 | pub enum RestFuture { 125 | Inner(#[pin] Fut), 126 | About(crate::utils::http_ratelimit::RateLimitFuture>), 127 | Other(crate::utils::http_ratelimit::RateLimitFuture>), 128 | } 129 | 130 | impl Future for RestFuture 131 | where 132 | Fut: Future, 133 | { 134 | type Output = Out; 135 | 136 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 137 | let this = self.project(); 138 | 139 | match this { 140 | EnumProj::About(fut) => fut.poll_unpin(cx), 141 | EnumProj::Other(fut) => fut.poll_unpin(cx), 142 | EnumProj::Inner(fut) => Future::poll(fut, cx), 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/impls/rest/upload.rs: -------------------------------------------------------------------------------- 1 | use std::convert::Infallible; 2 | 3 | use hrpc::{exports::futures_util::future::BoxFuture, server::transport::http::HttpResponse}; 4 | use tower::Service; 5 | 6 | use crate::{impls::auth::get_token_from_header_map, rest_error_response}; 7 | 8 | use super::*; 9 | 10 | pub fn handler(deps: Arc) -> RateLimit { 11 | let client_ip_header_name = deps.config.policy.ratelimit.client_ip_header_name.clone(); 12 | let allowed_ips = deps.config.policy.ratelimit.allowed_ips.clone(); 13 | RateLimit::new( 14 | UploadService { deps }, 15 | 3, 16 | Duration::from_secs(5), 17 | client_ip_header_name, 18 | allowed_ips, 19 | ) 20 | } 21 | 22 | pub struct UploadService { 23 | deps: Arc, 24 | } 25 | 26 | impl Service for UploadService { 27 | type Response = HttpResponse; 28 | 29 | type Error = Infallible; 30 | 31 | type Future = BoxFuture<'static, Result>; 32 | 33 | fn poll_ready(&mut self, _: &mut std::task::Context<'_>) -> Poll> { 34 | Ok(()).into() 35 | } 36 | 37 | fn call(&mut self, request: HttpRequest) -> Self::Future { 38 | let deps = self.deps.clone(); 39 | 40 | let fut = async move { 41 | if let Err(err) = deps 42 | .auth_with(get_token_from_header_map(request.headers())) 43 | .await 44 | { 45 | return Ok(err.into_rest_http_response()); 46 | } 47 | let boundary_res = request 48 | .headers() 49 | .get(&header::CONTENT_TYPE) 50 | .and_then(|h| h.to_str().ok()) 51 | .and_then(|v| multer::parse_boundary(v).ok()); 52 | let boundary = match boundary_res { 53 | Some(b) => b, 54 | None => { 55 | return Ok(rest_error_response( 56 | "content_type header not found or was invalid".to_string(), 57 | StatusCode::BAD_REQUEST, 58 | )) 59 | } 60 | }; 61 | let mut multipart = multer::Multipart::with_constraints( 62 | request.into_body(), 63 | boundary, 64 | multer::Constraints::new() 65 | .allowed_fields(vec!["file"]) 66 | .size_limit( 67 | multer::SizeLimit::new() 68 | .whole_stream(deps.config.media.max_upload_length * 1024 * 1024), 69 | ), 70 | ); 71 | 72 | match multipart.next_field().await { 73 | Ok(maybe_field) => match maybe_field { 74 | Some(field) => { 75 | let id = 76 | match write_file(deps.config.media.media_root.as_path(), field, None) 77 | .await 78 | { 79 | Ok(id) => id, 80 | Err(err) => return Ok(err.into_rest_http_response()), 81 | }; 82 | 83 | Ok(http::Response::builder() 84 | .status(StatusCode::OK) 85 | .body(box_body(Body::from( 86 | format!(r#"{{ "id": "{}" }}"#, id).into_bytes(), 87 | ))) 88 | .unwrap()) 89 | } 90 | None => Ok(ServerError::MissingFiles.into_rest_http_response()), 91 | }, 92 | Err(err) => Ok(ServerError::from(err).into_rest_http_response()), 93 | } 94 | }; 95 | 96 | Box::pin(fut) 97 | } 98 | } 99 | 100 | pub async fn write_file( 101 | media_root: &Path, 102 | mut part: multer::Field<'static>, 103 | write_to: Option, 104 | ) -> Result { 105 | let id = gen_rand_inline_str(); 106 | let path = write_to.unwrap_or_else(|| media_root.join(id.as_str())); 107 | if path.exists() { 108 | return Ok(id); 109 | } 110 | let first_chunk = part.chunk().await?.ok_or(ServerError::MissingFiles)?; 111 | 112 | let file = tokio::fs::OpenOptions::default() 113 | .append(true) 114 | .create(true) 115 | .open(path) 116 | .await?; 117 | let mut buf_writer = BufWriter::new(file); 118 | 119 | // [tag:ascii_filename_upload] 120 | let name = part.file_name().unwrap_or("unknown"); 121 | // [tag:ascii_mimetype_upload] 122 | let content_type = part 123 | .content_type() 124 | .map(|m| m.essence_str()) 125 | .or_else(|| infer::get(&first_chunk).map(|t| t.mime_type())) 126 | .unwrap_or("application/octet-stream"); 127 | 128 | // Write prefix 129 | buf_writer.write_all(name.as_bytes()).await?; 130 | buf_writer.write_all(&[SEPERATOR]).await?; 131 | buf_writer.write_all(content_type.as_bytes()).await?; 132 | buf_writer.write_all(&[SEPERATOR]).await?; 133 | 134 | // Write our first chunk 135 | buf_writer.write_all(&first_chunk).await?; 136 | 137 | // flush before starting to write other chunks 138 | buf_writer.flush().await?; 139 | 140 | while let Some(chunk) = part.chunk().await? { 141 | buf_writer.write_all(&chunk).await?; 142 | } 143 | 144 | // flush everything else 145 | buf_writer.flush().await?; 146 | 147 | Ok(id) 148 | } 149 | -------------------------------------------------------------------------------- /src/impls/sync/notify_new_id.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | _svc: &SyncServer, 5 | _request: Request, 6 | ) -> ServerResult> { 7 | Err(ServerError::NotImplemented.into()) 8 | } 9 | -------------------------------------------------------------------------------- /src/impls/sync/pull.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &SyncServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let host = svc.auth(&request).await?; 8 | let queue = svc.get_event_queue(&host).await?; 9 | Ok(queue.into_response()) 10 | } 11 | -------------------------------------------------------------------------------- /src/impls/sync/push.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | pub async fn handler( 4 | svc: &SyncServer, 5 | request: Request, 6 | ) -> ServerResult> { 7 | let host = svc.auth(&request).await?; 8 | let key = make_host_key(&host); 9 | if !svc 10 | .deps 11 | .sync_tree 12 | .contains_key(&key) 13 | .await 14 | .map_err(ServerError::DbError)? 15 | { 16 | svc.deps 17 | .sync_tree 18 | .insert(&key, &[]) 19 | .await 20 | .map_err(ServerError::DbError)?; 21 | } 22 | if let Some(event) = request.into_message().await?.event { 23 | svc.push_logic(&host, event).await?; 24 | } 25 | Ok((PushResponse {}).into_response()) 26 | } 27 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use crate::api::{ 4 | auth::{auth_service_client::AuthServiceClient, KeyRequest}, 5 | harmonytypes::Token, 6 | }; 7 | use ahash::RandomState; 8 | use dashmap::{mapref::one::RefMut, DashMap}; 9 | use ed25519_compact::{KeyPair, PublicKey, Seed}; 10 | use hrpc::{client::transport::http::Hyper, encode::encode_protobuf_message}; 11 | use hyper::Uri; 12 | use prost::Message; 13 | use smol_str::SmolStr; 14 | 15 | use crate::ServerError; 16 | 17 | pub const KEY_TAG: &str = "ED25519 PUBLIC KEY"; 18 | 19 | pub fn verify_token(token: &Token, pubkey: &PublicKey) -> Result<(), ServerError> { 20 | let Token { sig, data } = token; 21 | 22 | let sig = ed25519_compact::Signature::from_slice(sig.as_slice()) 23 | .map_err(|_| ServerError::InvalidTokenSignature)?; 24 | 25 | pubkey 26 | .verify(data, &sig) 27 | .map_err(|_| ServerError::CouldntVerifyTokenData) 28 | } 29 | 30 | #[derive(Debug)] 31 | pub struct Manager { 32 | keys: DashMap, 33 | clients: DashMap, RandomState>, 34 | federation_key: PathBuf, 35 | } 36 | 37 | impl Manager { 38 | pub fn new(federation_key: PathBuf) -> Self { 39 | Self { 40 | federation_key, 41 | keys: DashMap::default(), 42 | clients: DashMap::default(), 43 | } 44 | } 45 | 46 | pub async fn generate_token(&self, data: impl Message) -> Result { 47 | let buf = encode_protobuf_message(&data); 48 | let data = buf.to_vec(); 49 | 50 | let key = self.get_own_key().await?; 51 | let sig = key 52 | .sk 53 | .sign(&data, Some(ed25519_compact::Noise::generate())) 54 | .to_vec(); 55 | 56 | Ok(Token { sig, data }) 57 | } 58 | 59 | pub fn invalidate_key(&self, host: &str) { 60 | self.keys.remove(host); 61 | } 62 | 63 | pub async fn get_own_key(&self) -> Result { 64 | match tokio::fs::read(&self.federation_key).await { 65 | Ok(key) => { 66 | ed25519_compact::KeyPair::from_slice(&key).map_err(|_| ServerError::CantGetKey) 67 | } 68 | Err(err) => { 69 | if err.kind() == std::io::ErrorKind::NotFound { 70 | let new_key = ed25519_compact::KeyPair::from_seed(Seed::generate()); 71 | tokio::fs::write(&self.federation_key, new_key.as_ref()) 72 | .await 73 | .map(|_| new_key) 74 | .map_err(|_| ServerError::CantGetKey) 75 | } else { 76 | Err(ServerError::CantGetKey) 77 | } 78 | } 79 | } 80 | } 81 | 82 | pub async fn get_key(&self, host: SmolStr) -> Result { 83 | let key = if let Some(key) = self.keys.get(&host) { 84 | *key 85 | } else { 86 | let key = self 87 | .get_client(host.clone()) 88 | .value_mut() 89 | .key(KeyRequest {}) 90 | .await 91 | .map_err(|_| ServerError::CantGetHostKey(host.clone()))? 92 | .into_message() 93 | .await? 94 | .key; 95 | let key = ed25519_compact::PublicKey::from_slice(key.as_slice()) 96 | .map_err(|_| ServerError::CantGetHostKey(host.clone()))?; 97 | self.keys.insert(host, key); 98 | key 99 | }; 100 | 101 | Ok(key) 102 | } 103 | 104 | fn get_client( 105 | &self, 106 | host: SmolStr, 107 | ) -> RefMut<'_, SmolStr, AuthServiceClient, RandomState> { 108 | self.clients.entry(host.clone()).or_insert_with(|| { 109 | let host_url: Uri = host.parse().unwrap(); 110 | 111 | AuthServiceClient::new_transport(Hyper::new(host_url).unwrap()) 112 | }) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature( 2 | once_cell, 3 | let_else, 4 | const_intrinsic_copy, 5 | const_ptr_offset, 6 | const_mut_refs, 7 | type_alias_impl_trait 8 | )] 9 | #![allow(clippy::unit_arg, clippy::blocks_in_if_conditions)] 10 | 11 | use hrpc::exports::http; 12 | use parking_lot::Mutex; 13 | use triomphe::Arc; 14 | 15 | pub mod config; 16 | pub mod db; 17 | pub mod error; 18 | pub mod impls; 19 | pub mod key; 20 | pub mod utils; 21 | 22 | pub use self::error::{rest_error_response, ServerError}; 23 | 24 | pub const SCHERZO_VERSION: &str = git_version::git_version!( 25 | prefix = "git:", 26 | cargo_prefix = "cargo:", 27 | fallback = "unknown" 28 | ); 29 | 30 | pub type ServerResult = Result; 31 | 32 | pub type SharedConfig = Arc>; 33 | #[derive(Default)] 34 | pub struct SharedConfigData { 35 | pub motd: String, 36 | } 37 | 38 | pub use harmony_rust_sdk::api; 39 | -------------------------------------------------------------------------------- /src/utils/either.rs: -------------------------------------------------------------------------------- 1 | use hrpc::exports::futures_util::ready; 2 | use pin_project::pin_project; 3 | use std::{ 4 | future::Future, 5 | pin::Pin, 6 | task::{Context, Poll}, 7 | }; 8 | use tower::{layer::util::Identity, Layer, Service}; 9 | 10 | /// Combine two different service types into a single type. 11 | /// 12 | /// Both services must be of the same request, response, and error types. 13 | /// [`Either`] is useful for handling conditional branching in service middleware 14 | /// to different inner service types. 15 | #[pin_project(project = EitherProj)] 16 | #[derive(Clone, Debug)] 17 | pub enum Either { 18 | /// One type of backing [`Service`]. 19 | A(#[pin] A), 20 | /// The other type of backing [`Service`]. 21 | B(#[pin] B), 22 | } 23 | 24 | impl Service for Either 25 | where 26 | A: Service, 27 | B: Service, 28 | { 29 | type Response = A::Response; 30 | type Error = A::Error; 31 | type Future = Either; 32 | 33 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 34 | use self::Either::*; 35 | 36 | match self { 37 | A(service) => Poll::Ready(Ok(ready!(service.poll_ready(cx))?)), 38 | B(service) => Poll::Ready(Ok(ready!(service.poll_ready(cx))?)), 39 | } 40 | } 41 | 42 | fn call(&mut self, request: Request) -> Self::Future { 43 | use self::Either::*; 44 | 45 | match self { 46 | A(service) => A(service.call(request)), 47 | B(service) => B(service.call(request)), 48 | } 49 | } 50 | } 51 | 52 | impl Future for Either 53 | where 54 | A: Future>, 55 | B: Future>, 56 | { 57 | type Output = Result; 58 | 59 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 60 | match self.project() { 61 | EitherProj::A(fut) => Poll::Ready(Ok(ready!(fut.poll(cx)).map_err(Into::into)?)), 62 | EitherProj::B(fut) => Poll::Ready(Ok(ready!(fut.poll(cx)).map_err(Into::into)?)), 63 | } 64 | } 65 | } 66 | 67 | impl Layer for Either 68 | where 69 | A: Layer, 70 | B: Layer, 71 | { 72 | type Service = Either; 73 | 74 | fn layer(&self, inner: S) -> Self::Service { 75 | match self { 76 | Either::A(layer) => Either::A(layer.layer(inner)), 77 | Either::B(layer) => Either::B(layer.layer(inner)), 78 | } 79 | } 80 | } 81 | 82 | pub fn option_layer(layer: Option) -> Either { 83 | if let Some(layer) = layer { 84 | Either::A(layer) 85 | } else { 86 | Either::B(Identity::new()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/utils/evec.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Deref, DerefMut}; 2 | 3 | use rkyv::AlignedVec; 4 | #[cfg(feature = "sled")] 5 | use sled::IVec; 6 | 7 | #[derive(Debug, Clone)] 8 | pub enum EVec { 9 | #[cfg(feature = "sled")] 10 | Inline(IVec), 11 | Owned(AlignedVec), 12 | } 13 | 14 | impl Default for EVec { 15 | fn default() -> Self { 16 | EVec::Owned(AlignedVec::new()) 17 | } 18 | } 19 | 20 | impl From for AlignedVec { 21 | fn from(evec: EVec) -> Self { 22 | match evec { 23 | #[cfg(feature = "sled")] 24 | EVec::Inline(inline) => { 25 | let mut vec = AlignedVec::with_capacity(inline.len()); 26 | vec.extend_from_slice(inline.as_ref()); 27 | vec 28 | } 29 | EVec::Owned(owned) => owned, 30 | } 31 | } 32 | } 33 | 34 | impl From for Vec { 35 | fn from(evec: EVec) -> Self { 36 | match evec { 37 | #[cfg(feature = "sled")] 38 | EVec::Inline(inline) => inline.to_vec(), 39 | EVec::Owned(owned) => owned.into_vec(), 40 | } 41 | } 42 | } 43 | 44 | impl From for EVec { 45 | fn from(avec: AlignedVec) -> Self { 46 | EVec::Owned(avec) 47 | } 48 | } 49 | 50 | #[cfg(feature = "sled")] 51 | impl From for EVec { 52 | fn from(ivec: IVec) -> Self { 53 | let vec: AlignedVec = EVec::Inline(ivec).into(); 54 | EVec::Owned(vec) 55 | } 56 | } 57 | 58 | #[cfg(feature = "sled")] 59 | impl From for IVec { 60 | fn from(evec: EVec) -> Self { 61 | match evec { 62 | EVec::Inline(ivec) => ivec, 63 | EVec::Owned(vec) => vec.into_vec().into(), 64 | } 65 | } 66 | } 67 | 68 | impl From> for EVec { 69 | fn from(vec: Vec) -> Self { 70 | EVec::Owned({ 71 | let mut avec = AlignedVec::with_capacity(vec.len()); 72 | avec.extend_from_slice(&vec); 73 | avec 74 | }) 75 | } 76 | } 77 | 78 | impl From<&[u8]> for EVec { 79 | fn from(v: &[u8]) -> Self { 80 | EVec::Owned({ 81 | let mut vec = AlignedVec::with_capacity(v.len()); 82 | vec.extend_from_slice(v); 83 | vec 84 | }) 85 | } 86 | } 87 | 88 | impl From<[u8; N]> for EVec { 89 | fn from(arr: [u8; N]) -> Self { 90 | EVec::Owned({ 91 | let mut vec = AlignedVec::with_capacity(arr.len()); 92 | vec.extend_from_slice(&arr); 93 | vec 94 | }) 95 | } 96 | } 97 | 98 | impl AsRef<[u8]> for EVec { 99 | fn as_ref(&self) -> &[u8] { 100 | match self { 101 | #[cfg(feature = "sled")] 102 | EVec::Inline(inline) => inline.as_ref(), 103 | EVec::Owned(owned) => owned.as_slice(), 104 | } 105 | } 106 | } 107 | 108 | impl AsMut<[u8]> for EVec { 109 | fn as_mut(&mut self) -> &mut [u8] { 110 | match self { 111 | #[cfg(feature = "sled")] 112 | EVec::Inline(inline) => inline.as_mut(), 113 | EVec::Owned(owned) => owned.as_mut_slice(), 114 | } 115 | } 116 | } 117 | 118 | impl std::borrow::Borrow<[u8]> for EVec { 119 | fn borrow(&self) -> &[u8] { 120 | self.as_ref() 121 | } 122 | } 123 | 124 | impl std::borrow::Borrow<[u8]> for &EVec { 125 | fn borrow(&self) -> &[u8] { 126 | self.as_ref() 127 | } 128 | } 129 | 130 | impl Deref for EVec { 131 | type Target = [u8]; 132 | 133 | fn deref(&self) -> &Self::Target { 134 | self.as_ref() 135 | } 136 | } 137 | 138 | impl DerefMut for EVec { 139 | fn deref_mut(&mut self) -> &mut Self::Target { 140 | self.as_mut() 141 | } 142 | } 143 | 144 | impl TryFrom for [u8; N] { 145 | type Error = EVec; 146 | 147 | fn try_from(v: EVec) -> Result<[u8; N], Self::Error> { 148 | match v { 149 | #[cfg(feature = "sled")] 150 | EVec::Inline(ivec) => ivec.as_ref().try_into().map_err(|_| EVec::Inline(ivec)), 151 | EVec::Owned(vec) => vec.as_ref().try_into().map_err(|_| EVec::Owned(vec)), 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod either; 2 | pub mod evec; 3 | pub mod http_ratelimit; 4 | pub mod ratelimit; 5 | pub mod test; 6 | 7 | use hrpc::exports::{bytes::Bytes, http}; 8 | use hyper::HeaderMap; 9 | use rand::Rng; 10 | pub use ratelimit::rate_limit; 11 | 12 | use smol_str::SmolStr; 13 | 14 | pub fn get_time_millisecs() -> u64 { 15 | std::time::UNIX_EPOCH 16 | .elapsed() 17 | .expect("time is before unix epoch") 18 | .as_millis() as u64 19 | } 20 | 21 | pub fn get_time_secs() -> u64 { 22 | std::time::UNIX_EPOCH 23 | .elapsed() 24 | .expect("time is before unix epoch") 25 | .as_secs() 26 | } 27 | 28 | pub fn gen_rand_inline_str() -> SmolStr { 29 | // Safety: arrays generated by gen_rand_arr are alphanumeric, so they are valid ASCII chars as well as UTF-8 chars [ref:alphanumeric_array_gen] 30 | let arr = gen_rand_arr::<_, 22>(&mut rand::thread_rng()); 31 | let str = unsafe { std::str::from_utf8_unchecked(&arr) }; 32 | // Safety: generated array is exactly 22 u8s long 33 | SmolStr::new_inline(str) 34 | } 35 | 36 | #[allow(dead_code)] 37 | pub fn gen_rand_str() -> SmolStr { 38 | let arr = gen_rand_arr::<_, LEN>(&mut rand::thread_rng()); 39 | // Safety: arrays generated by gen_rand_arr are alphanumeric, so they are valid ASCII chars as well as UTF-8 chars [ref:alphanumeric_array_gen] 40 | let str = unsafe { std::str::from_utf8_unchecked(&arr) }; 41 | SmolStr::new(str) 42 | } 43 | 44 | pub fn gen_rand_arr(rng: &mut RNG) -> [u8; LEN] { 45 | let mut res = [0_u8; LEN]; 46 | 47 | let random = rng 48 | .sample_iter(rand::distributions::Alphanumeric) // [tag:alphanumeric_array_gen] 49 | .take(LEN); 50 | 51 | random 52 | .zip(res.iter_mut()) 53 | .for_each(|(new_ch, ch)| *ch = new_ch); 54 | 55 | res 56 | } 57 | 58 | pub fn gen_rand_u64() -> u64 { 59 | rand::thread_rng().gen_range(1..u64::MAX) 60 | } 61 | 62 | pub fn get_mimetype(headers: &HeaderMap) -> &str { 63 | headers 64 | .get(&http::header::CONTENT_TYPE) 65 | .and_then(|val| val.to_str().ok()) 66 | .and_then(|s| s.split(';').next()) 67 | .unwrap_or("application/octet-stream") 68 | } 69 | 70 | pub fn get_content_length(headers: &HeaderMap) -> http::HeaderValue { 71 | headers 72 | .get(&http::header::CONTENT_LENGTH) 73 | .cloned() 74 | .unwrap_or_else(|| unsafe { 75 | http::HeaderValue::from_maybe_shared_unchecked(Bytes::from_static(b"0")) 76 | }) 77 | } 78 | -------------------------------------------------------------------------------- /src/utils/ratelimit.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::HashSet, 3 | net::{IpAddr, SocketAddr}, 4 | str::FromStr, 5 | time::Duration, 6 | }; 7 | 8 | use hrpc::{request::BoxRequest, server::layer::ratelimit::RateLimitLayer}; 9 | 10 | pub type ExtractKey = impl Fn(&mut BoxRequest) -> Option + Clone; 11 | pub type CheckKey = impl Fn(&IpAddr) -> bool + Clone; 12 | 13 | pub fn rate_limit( 14 | num: u64, 15 | per: Duration, 16 | check_header_for_ip: Option, 17 | allowed_ips: Option>, 18 | ) -> RateLimitLayer { 19 | let allowed_ips = allowed_ips.map(|ips| { 20 | ips.into_iter() 21 | .flat_map(|s| IpAddr::from_str(&s)) 22 | .collect::>() 23 | }); 24 | 25 | RateLimitLayer::new(num, per).set_key_fns( 26 | move |req| { 27 | check_header_for_ip 28 | .as_deref() 29 | .and_then(|header_name| get_ip_addr_from_header(req, header_name)) 30 | .or_else(|| get_ip_addr(req)) 31 | }, 32 | move |ip| allowed_ips.as_ref().map_or(false, |ips| ips.contains(ip)), 33 | ) 34 | } 35 | 36 | fn get_ip_addr(req: &BoxRequest) -> Option { 37 | req.extensions().get::().map(|addr| addr.ip()) 38 | } 39 | 40 | fn get_ip_addr_from_header(req: &BoxRequest, check_header_for_ip: &str) -> Option { 41 | req.header_map() 42 | .and_then(|headers| headers.get(check_header_for_ip)) 43 | .and_then(|val| val.to_str().ok()) 44 | .and_then(|ips| ips.split(',').map(str::trim).next()) 45 | .and_then(|ip_raw| IpAddr::from_str(ip_raw).ok()) 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/test.rs: -------------------------------------------------------------------------------- 1 | use crate::api::{ 2 | auth::auth_service_client::AuthServiceClient, chat::chat_service_client::ChatServiceClient, 3 | emote::emote_service_client::EmoteServiceClient, 4 | mediaproxy::media_proxy_service_client::MediaProxyServiceClient, 5 | profile::profile_service_client::ProfileServiceClient, 6 | }; 7 | use hrpc::client::transport::mock::Mock as MockClient; 8 | 9 | pub struct Clients { 10 | pub chat: ChatServiceClient, 11 | pub auth: AuthServiceClient, 12 | pub mediaproxy: MediaProxyServiceClient, 13 | pub profile: ProfileServiceClient, 14 | pub emote: EmoteServiceClient, 15 | } 16 | 17 | pub fn create_mock( 18 | deps: triomphe::Arc, 19 | fed: tokio::sync::mpsc::UnboundedReceiver, 20 | ) -> Clients { 21 | use hrpc::{ 22 | common::transport::mock::new_mock_channels, 23 | server::transport::{mock::Mock as MockServer, Transport}, 24 | }; 25 | 26 | let (tx, rx) = new_mock_channels(); 27 | 28 | let transport = MockServer::new(rx); 29 | let server = crate::impls::setup_server(deps, fed, tracing::Level::DEBUG); 30 | let fut = transport.serve(server.0); 31 | 32 | tokio::spawn(fut); 33 | 34 | let transport = MockClient::new(tx); 35 | 36 | let chat = ChatServiceClient::new_transport(transport.clone()); 37 | let auth = AuthServiceClient::new_transport(transport.clone()); 38 | let mediaproxy = MediaProxyServiceClient::new_transport(transport.clone()); 39 | let profile = ProfileServiceClient::new_transport(transport.clone()); 40 | let emote = EmoteServiceClient::new_transport(transport); 41 | 42 | Clients { 43 | chat, 44 | auth, 45 | emote, 46 | mediaproxy, 47 | profile, 48 | } 49 | } 50 | 51 | macro_rules! unit_test { 52 | ($name:ident, $clients:ident, $body:expr) => { 53 | #[cfg(test)] 54 | #[tokio::test] 55 | async fn $name() { 56 | let mut config = $crate::config::Config::default(); 57 | config.policy.ratelimit.disable = true; 58 | let db = $crate::db::open_temp(); 59 | let (deps, fed) = $crate::impls::Dependencies::new(&db, config) 60 | .await 61 | .expect("failed to create dependencies"); 62 | 63 | let mut $clients = $crate::utils::test::create_mock(deps, fed); 64 | 65 | { 66 | $body 67 | } 68 | } 69 | }; 70 | } 71 | 72 | pub(crate) use unit_test; 73 | --------------------------------------------------------------------------------