Compare commits

...

617 commits

Author SHA1 Message Date
Olivia Lee
c4abca1eb5
Bump ruma to c4f467781a7ef330dc0b7eb5d0d0cad77ebc3337 (refactor Capabilities)
<https://github.com/ruma/ruma/pull/2086>
2025-08-30 18:38:08 +02:00
Olivia Lee
9532c853bd
Bump ruma to daa3c8b014a601fa277e8653dea86e8897b17552 (optional UiaaInfo::params)
<https://github.com/ruma/ruma/pull/2050>

We could just set None now, but the ruma changelog states that

> Servers are encouraged to keep sending it for compatibility with
> clients that required it.
2025-08-30 18:38:08 +02:00
Olivia Lee
8c9800735b
Bump ruma to 2ea8b833e3a80c1d650964a1f3e83ee569cf5c0b (RoomVersion -> RoomVersionRules)
<https://github.com/ruma/ruma/pull/2045>

Co-authored-by: Jonas Platte <jplatte+git@posteo.de>
2025-08-30 18:37:54 +02:00
Olivia Lee
c4c294d64c
Bump ruma to 3924283c5e94d2020f0991ce35c27667f8c61f0a (add RoomVersion arg to auth_types_for_event) 2025-08-30 13:57:03 +02:00
Kierre
b5bc53bb2d
Add configuration option to allow invalid TLS certificates 2025-08-22 02:03:01 -04:00
Olivia Lee
d8ec961589 Bump ruma to e8b0876dda083433a7f9181d47d0aff5a5e05497 (auth check return type change) 2025-08-10 14:57:28 -07:00
Olivia Lee
644639ad10 Refactor soft fail check 2025-08-10 14:57:28 -07:00
Olivia Lee
aade2e17c2 Refactor redaction permission checks 2025-08-10 14:57:28 -07:00
Charles Hall
61d2ff7183 care less about room versions prior to 3 2025-08-10 14:53:46 -07:00
Olivia Lee
15ab91c012
Bump ruma to d84760a925d8e5830d668b031fec53ddf9101814 (drop compat-signature-id) 2025-08-09 02:28:20 +02:00
Olivia Lee
98dee6ad49
Bump ruma to 649d683f3f5b8f4f7eb1e728443e4baf25cfebca (stateres refactor #1)
The only change to the internal interface for this one is removing the
current_third_party_invite argument from state_res::auth_check. Ruma now
fetches the event using the fetch_state closure. This is convenient for
us, because we previously didn't bother to implement it.
2025-08-09 02:28:02 +02:00
Jonas Platte
d283da51c6
Upgrade rust-rocksdb 2025-08-09 01:18:51 +02:00
Olivia Lee
b94cc429b7
Bump ruma to 1387667de806c37a6d7f72125117009bd618e32a
The significant change is 26edd40a704040e7104161da81c9bae91b7ddcaa,
which removes the global compat feature, so that each compat feature
must now be enabled individually. We're using the slightly later
1387667d because it has a bugfix that ruma needs to compile.

There are a few ruma compat features that were not previously part of
the global compat feature:

 - compat-arbitrary-length-ids
 - compat-upload-signature
 - compat-encrypted-stickers

I have not enabled these here, to avoid a behavior change.
2025-08-09 00:31:35 +02:00
Jonas Platte
ab5f76f49f
Put ruma dependency in its own TOML table 2025-08-09 00:13:27 +02:00
Olivia Lee
6bf289a714
Remove unused parameter
… of filter_keys_server_map and filter_keys_single_server.
2025-08-09 00:13:17 +02:00
Jonas Platte
d87848b9a6
Remove support for MSC3575 (sliding sync)
Co-authored-by: Olivia Lee <olivia@computer.surgery>
2025-08-08 22:24:56 +02:00
Olivia Lee
98222b38c2 bump cargo dependencies
There are two new default features:

'system-proxy' for reqwest, which we are not enabling because it only
does anything on windows and mac, which we don't support.

'prefer-post-quantum' for rustls, which we are not enabling because it
only does anything for aws-lc-rs, which we don't use.
2025-08-06 12:27:45 -07:00
Olivia Lee
2648991092 fix lints revealed by tracing change
<https://github.com/tokio-rs/tracing/pull/3108> changes #[instrument] so
that several lints that were previously hidden on instrumented functions
are now visible.
2025-08-06 12:27:45 -07:00
Charles Hall
13203d3b45 update rust to 1.88.0 2025-08-06 12:27:45 -07:00
Charles Hall
595f35b673 fix lints for upcoming 1.88.0 toolchain 2025-08-06 12:27:45 -07:00
Olivia Lee
fa6fb37a2c bump rust version to 1.85.0
base64ct 1.8 (a transitive dependency of argon2) requires >=1.85.0.
2025-08-06 12:27:45 -07:00
Olivia Lee
fb4d168921 fix new lints from clippy 0.1.85 2025-08-06 12:27:45 -07:00
Olivia Lee
e65815c82e remove unused lifetime parameter from utils::calculate_hash
This doesn't trigger a warning before rustc 1.58.0, for whatever reason.
2025-08-06 12:27:45 -07:00
Olivia Lee
d2ee80db94 update flake.lock
• Updated input 'attic':
    'github:zhaofengli/attic/ff8a897d1f4408ebbf4d45fa9049c06b3e1e3f4e' (2025-02-02)
  → 'github:zhaofengli/attic/24fad0622fc9404c69e83bab7738359c5be4988e' (2025-07-11)
• Updated input 'attic/crane':
    'github:ipetkov/crane/4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4' (2024-08-06)
  → 'github:ipetkov/crane/aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd' (2025-07-03)
• Removed input 'attic/crane/nixpkgs'
• Updated input 'attic/flake-compat':
    'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33' (2023-10-04)
  → 'github:edolstra/flake-compat/9100a0f413b0c601e0533d1d94ffd501ce2e7885' (2025-05-12)
• Updated input 'attic/flake-parts':
    'github:hercules-ci/flake-parts/8471fe90ad337a8074e957b69ca4d0089218391d' (2024-08-01)
  → 'github:hercules-ci/flake-parts/77826244401ea9de6e3bac47c2db46005e1f30b5' (2025-07-01)
• Updated input 'attic/nix-github-actions':
    'github:nix-community/nix-github-actions/e04df33f62cdcf93d73e9a04142464753a16db67' (2024-10-24)
  → 'github:nix-community/nix-github-actions/f4158fa080ef4503c8f4c820967d946c2af31ec9' (2025-01-21)
• Updated input 'attic/nixpkgs':
    'github:NixOS/nixpkgs/159be5db480d1df880a0135ca0bfed84c2f88353' (2024-09-11)
  → 'github:NixOS/nixpkgs/9b008d60392981ad674e04016d25619281550a9d' (2025-07-08)
• Updated input 'attic/nixpkgs-stable':
    'github:NixOS/nixpkgs/797f7dc49e0bc7fab4b57c021cdf68f595e47841' (2024-08-22)
  → 'github:NixOS/nixpkgs/29e290002bfff26af1db6f64d070698019460302' (2025-07-05)
• Updated input 'crane':
    'github:ipetkov/crane/70947c1908108c0c551ddfd73d4f750ff2ea67cd' (2025-03-19)
  → 'github:ipetkov/crane/544d09fecc8c2338542c57f3f742f1a0c8c71e13' (2025-07-19)
• Updated input 'fenix':
    'github:nix-community/fenix/7d9ba794daf5e8cc7ee728859bc688d8e26d5f06' (2025-03-20)
  → 'github:nix-community/fenix/62105e0745d7450976b26dbd1497b8cbe15eb9ff' (2025-07-20)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/15d87419f1a123d8f888d608129c3ce3ff8f13d4' (2025-03-18)
  → 'github:rust-lang/rust-analyzer/ed193af36937d2fd4bb14a815ec589875c5c7304' (2025-07-19)
• Updated input 'flake-compat':
    'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec' (2024-12-04)
  → 'github:edolstra/flake-compat/9100a0f413b0c601e0533d1d94ffd501ce2e7885' (2025-05-12)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/698214a32beb4f4c8e3942372c694f40848b360d' (2025-03-25)
  → 'github:NixOS/nixpkgs/c87b95e25065c028d31a94f06a62927d18763fdf' (2025-07-19)
2025-08-06 12:27:45 -07:00
Charles Hall
a3a2485edb improve link accessibility
The previous text causes lints to fire in new versions of markdownlint.
2025-08-06 12:27:45 -07:00
Charles Hall
e31f4454bd assume nix is available 2025-08-03 10:59:28 -07:00
Charles Hall
5e9c1f9ee3 combine conditions in before_script 2025-08-03 10:59:28 -07:00
Charles Hall
38202813ff remove nix-direnv
It doesn't actually do anything unless more configuration is added, but
it's not really necessary for this use case so removing it is easier.
2025-08-03 10:59:28 -07:00
Charles Hall
547881fb4a show path upload count 2025-08-03 10:59:28 -07:00
Charles Hall
132528b545 improve variable names' clarity 2025-08-03 10:59:28 -07:00
Charles Hall
47c210d5de reorder attic token check 2025-08-03 10:59:05 -07:00
Charles Hall
7fc39b1845 simplify attic call
The comment isn't really needed since the reasoning applies to the
other instances of readarray and here-strings too, and the IFS bit is
unnecessary.
2025-08-03 10:57:39 -07:00
Charles Hall
85b9080c77 change working directory
Did you know installables are parsed with regex? This mitigates how
horrifying that is.
2025-08-02 20:49:19 -07:00
Charles Hall
be3822d585 drop eval
Shellcheck says so.
2025-08-02 20:49:19 -07:00
Charles Hall
cf4f2fe51b use more readarray
The first part replaces a loop which is probably a performance
improvement, and the second part addresses a shellcheck complaint about
not declaring and assigning separately.
2025-08-02 20:49:19 -07:00
Charles Hall
27667911ed upgrade docker images to lix 2.93.3 2025-08-02 20:49:13 -07:00
Jonas Platte
c713e9f21f
Remove no-op #[async_trait] attribute 2025-07-31 20:44:54 +02:00
Jonas Platte
a3da77ce2c
Add a couple extra Sync bounds
Not necessary right now, but required for axum 0.8.
2025-07-31 20:44:54 +02:00
Charles Hall
40da74f28c Revert "do default-feature unification in nix"
This reverts commit 8f24ac1f27.

This is no longer necessary since the set of all features is equal to
the set of default features.
2025-07-24 15:09:12 -07:00
Charles Hall
b5294f9aa0 remove all-features devshell
It's no longer necessary since it's equivalent to the default-features
one now. The `DIRENV_DEVSHELL` thing is left in place however in case
that becomes useful again in the future.
2025-07-24 15:00:02 -07:00
Charles Hall
e6dbc293f1 remove jemalloc support
It's no longer being developed and it is very very annoying to maintain
support for.
2025-07-21 21:48:57 -07:00
Olivia Lee
55a01e7113
don't strip unknown keys when copying redacts property to/from content
Servers are required to preserve unknown properties in event content,
since they may be added by a future version of the spec. Round-tripping
through RoomRedactionEventContent results in dropping all unknown
properties.
2025-07-20 18:07:30 -07:00
Olivia Lee
9a142c7557
refactor Pdu::copy_redacts to use early returns 2025-07-20 12:46:06 -07:00
Olivia Lee
88ad596e8d
add type-safe accessors to account_data service 2025-06-02 11:07:21 -07:00
Olivia Lee
b82458a460
use event content in account_data service api instead of full events
This eliminates the possibility of passing an event that has a
mismatching type, reducing the space of possible invalid states.
2025-06-02 11:07:21 -07:00
Olivia Lee
66210bc32d
separate account_data service methods for room vs global events
Previously we were mashing everything together as RoomAccountDataEvent,
even the global events. This technically worked, because of the hidden
custom fields on the ruma event types, but it's confusing and easy to
mess up. Separate methods with appropriate types are preferable.
2025-06-02 11:07:21 -07:00
Olivia Lee
6897f0ba34
clarify behavior in service::account_data::changes_since docs 2025-06-02 11:07:21 -07:00
Olivia Lee
fe14300d91
reintroduce account_data::Service struct
In preparation for adding some additional methods at the service level.

Dropping the tracing spans for the data methods, because two duplicate
spans here seems kinda excessive.
2025-06-02 11:07:21 -07:00
LordMZTE
868bb44adf
support listening on Unix sockets 2025-06-01 22:16:48 +02:00
Olivia Lee
188eac5cfd include ban reasons when banning a user that already had a member event
The case where the user never had a member event in the room is already
handled correctly.
2025-06-01 09:38:27 +00:00
Lambda
c965c9747b Send User-Agent header in outbound requests 2025-05-29 19:53:02 +00:00
Lambda
d425ba72f8
Update ruma to 0.12.2 2025-05-04 14:03:46 -07:00
avdb13
48ecf50973 update documentation of service::users::Data::iter 2025-04-20 03:47:07 +00:00
Charles Hall
d1370f9834
refactor fetch_unknown_prev_events
Early returns (or continues, in this case) good.
2025-04-16 19:16:57 -07:00
Olivia Lee
6cb7896e17
use trust-dns for all DNS queries
Previously we were only using trust-dns for resolving SRV records in
server discovery, and then for resolving the hostname from the SRV
record target if one exists. With the previous behavior, admins need to
ensure that both their system resolver and trust-dns are working
correctly in order for outgoing traffic to work reliably. This can be
confusing to debug, because it's not obvious to the admin if or when
each resolver are being used. Now, everything goes through trust-dns and
outgoing federation DNS should fail/succeed more predictably.

I also expect some performance improvement from having an in-process DNS
cache, but haven't taken measurements yet.
2025-04-14 15:56:33 -07:00
Olivia Lee
e249aed1cb
rename Resolver -> FederationResolver and document 2025-04-14 15:50:04 -07:00
Olivia Lee
e98dd5b9a3
put global trust-dns resolver in an Arc
This allows us to reference it in reqwest clients configuration.
2025-04-14 15:50:04 -07:00
Olivia Lee
33f3592612
fix starting the server when db/media dirs do not exist yet
This used to be supported, as we explicitly call std::fs::create_dir_all
when initializing these, but it was broken in
b01b70fc20, which attempts to canonicalize
the paths to check for overlap before creating them.
2025-04-14 15:28:31 -07:00
Olivia Lee
abb1b5681e
add partial_canonicalize helper function
This is useful for checking for potential overlap between paths that
have not been fully created yet.
2025-04-14 15:28:31 -07:00
Olivia Lee
c03103a142
document that conduwuit migration is unlikely to work
<https://conduwuit.puppyirl.gay/#can-i-migrate-or-switch-from-conduit>
says that they broke db compatibility explicitly. I'm not sure when this
happened and which versions are compatible. Until we evaluate that we
should warn people not to try.
2025-04-11 13:10:23 -07:00
Olivia Lee
1529d60a6a
set default database backend in nixos module to rocksdb
The media backend already has a default value.
2025-04-06 20:06:40 -07:00
Charles Hall
1cd12460d7
make systemd patient for startup
RocksDB compaction can take quite a while if it doesn't happen
regularly, which it doesn't, currently.
2025-04-06 15:46:27 -07:00
Lambda
6bcc4e310e
Immediately trigger EDU sending after client read receipt
Previously, read receipts would only be forwarded via federation
incidentally when some PDU was later sent to the destination server.
Trigger a send without any event to collect EDUs and get read receipts
out directly.
2025-03-30 16:48:06 -07:00
Lambda
81c5f09919
service/sending: tiny refactors, NFC 2025-03-30 16:48:06 -07:00
Lambda
d9c7fbbd82
services/sending: avoid unnecessary clone 2025-03-30 16:48:06 -07:00
Lambda
ed12b0bf67
services/sending: guard against federation requests to ourselves
These would always fail and get stuck/retried forever.
2025-03-30 16:48:02 -07:00
Charles Hall
bb80f1cf2e
update rust deps that don't require code changes
I also had to pin ruma because otherwise cargo complained that features
were removed from ruma so it couldn't resolve the dependency by itself.
2025-03-27 16:31:45 -07:00
Charles Hall
a901d62575
skip checks for deps-only derivation
This is probably a crane bug that it's doing this in the first place.
2025-03-27 16:31:41 -07:00
Charles Hall
8b7cbb5f25
update rocksdb
This is done separately since it requires more involved changes than
just cargo's files.
2025-03-27 16:30:52 -07:00
Charles Hall
8da6c5d4b3
bump lockfile format version
This was actually introduced in version 1.83 of the rust toolchain.
2025-03-27 16:30:48 -07:00
K900
7ee117d36a fix: update nix inputs, fix build with latest crane
Fixes references to rustc leaking into the binary.

Flake lock file updates:

• Updated input 'attic':
    'github:zhaofengli/attic/47752427561f1c34debb16728a210d378f0ece36' (2024-11-10)
  → 'github:zhaofengli/attic/ff8a897d1f4408ebbf4d45fa9049c06b3e1e3f4e' (2025-02-02)
• Updated input 'crane':
    'github:ipetkov/crane/ef80ead953c1b28316cc3f8613904edc2eb90c28' (2024-11-08)
  → 'github:ipetkov/crane/70947c1908108c0c551ddfd73d4f750ff2ea67cd' (2025-03-19)
• Updated input 'fenix':
    'github:nix-community/fenix/e10ba121773f754a30d31b6163919a3e404a434f' (2024-11-16)
  → 'github:nix-community/fenix/7d9ba794daf5e8cc7ee728859bc688d8e26d5f06' (2025-03-20)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/1b90e979aeee8d1db7fe14603a00834052505497' (2024-11-15)
  → 'github:rust-lang/rust-analyzer/15d87419f1a123d8f888d608129c3ce3ff8f13d4' (2025-03-18)
• Updated input 'flake-compat':
    'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33' (2023-10-04)
  → 'github:edolstra/flake-compat/ff81ac966bb2cae68946d5ed5fc4994f96d0ffec' (2024-12-04)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5e4fbfb6b3de1aa2872b76d49fafc942626e2add' (2024-11-15)
  → 'github:NixOS/nixpkgs/698214a32beb4f4c8e3942372c694f40848b360d' (2025-03-25)
2025-03-27 22:37:39 +03:00
Charles Hall
d6475eee6d
remove redundant condition 2025-03-21 16:48:56 -07:00
Charles Hall
799594cd1f
fix typo and update changelog
The MR number for the original MR was missing the leading `1`.
2025-03-21 16:28:11 -07:00
Charles Hall
218d75d573
update nixos module
Can't believe I forgot to do this...
2025-03-21 16:25:42 -07:00
Charles Hall
a04951541a
don't check canonicalized paths while sandboxed
Because the configured paths won't exist in the sandbox, so
canonicalization would fail.
2025-03-21 16:25:42 -07:00
Charles Hall
50583bc93e
reject overlapping non-canonical paths too
For example, if the database path is `/foo` and the media path is
`/foo/bar`, but `/foo/bar` is a symlink or hardlink to `/baz`, the
previous check would pass. The whole point of this check is to ensure
that the database and media data can't step on each other, so this check
is needed to deny that kind of situation as well.

It would probably be good to add a test for this behavior.
2025-03-21 16:25:31 -07:00
Charles Hall
b01b70fc20
reject overlapping media and database paths 2025-03-20 11:33:59 -07:00
Charles Hall
81a449d1d2
make database path a pathbuf
I'm guessing it wasn't like this already because of one of the admin
commands I deleted.
2025-03-20 11:33:59 -07:00
Charles Hall
5a5608e088
separate media and database paths
The primary motivation for this change is to support databases that
don't take a path, e.g. out of process databases.

This configuration structure leaves the door open for other media
storage mechanisms in the future, such as S3.

It's also structured to avoid `#[serde(flatten)]` so that we can use
`#[serde(deny_unknown_fields)]`.
2025-03-20 11:33:59 -07:00
Charles Hall
ae920fdbe8
make option name less redundant 2025-03-20 10:40:20 -07:00
Charles Hall
0a6d2b2731
make a media section in the config file 2025-03-20 10:40:20 -07:00
Charles Hall
f6b0a10e6e
implement _file in terms of _folder
what the heck lol
2025-03-20 10:40:16 -07:00
K900
fd2a0ac2ab nixos: allow Unix sockets
Because OF COURSE.
2025-03-18 18:18:09 +03:00
K900
ed67ae6418 nixos: fix systemd service type 2025-03-18 13:13:59 +00:00
Charles Hall
adb174d985
don't use file type flake inputs
Because they aren't supported by flake-compat and can't be without IFD.
2025-03-02 11:12:38 -08:00
Charles Hall
114e7ba577
allow supplying custom version extra info in nix 2025-03-02 10:57:13 -08:00
Charles Hall
e11e27bc0c
don't try to set version extra outside flakes
The two attributes being accessed here don't exist when calling though
flake-compat, so we need to handle that case by making the environment
variable unset if neither attribute exists.
2025-03-02 10:54:56 -08:00
Olivia Lee
57c79b1999
Fix native compilation on aarch64
Turns out fenix.packages.aarch64-linux.targets.x86_64-*.minimal.rustc
is an x86_64 compiler. The only component that you actually need to pull
from 'targets' for cross compilation is rust-std.

Because x86_64-unknown-linux-gnu is first in the target list, we were
getting x86 rustc and cargo binaries, making it impossible to compile
grapevine on an aarch64 host.

We were also missing aarch64-unknown-linux-gnu in the targets list,
which is used by the default package on a aarch64 host.
2025-03-02 01:20:51 -08:00
Lambda
65ec500d75 Enable useful new clippy lints
Picked from this filtered list:
https://rust-lang.github.io/rust-clippy/master/index.html?versions=gte%3A82%2Clte%3A84&levels=allow&groups=cargo%2Ccomplexity%2Ccorrectness%2Cnursery%2Cperf%2Crestriction%2Cstyle%2Csuspicious
2025-02-18 17:06:08 +00:00
Lambda
175a62007d Update MSRV to 1.84.0
And appease clippy (`__CARGO_FIX_YOLO=1 cargo clippy --fix` plus some
manual type shuffling).
2025-02-18 17:06:08 +00:00
Lambda
5616510727 Use UInt instead of u32 for max request size
Sometimes you just really want to upload a full disk image as media.
2025-02-18 16:48:47 +00:00
Lambda
99924e5779 Add admin commands to get and reset tracing filters 2025-02-17 17:41:14 +00:00
Lambda
5eab758bd2 Make tracing filter reload handles mutable
Makes the following diff nicer.
2025-02-17 17:40:55 +00:00
Olivia Lee
4cc390345a
don't markdownlint ignored files
I like to put a bunch of untracked stuff in a /scratch directory for
each project, and then puth /scratch in my global gitignore. There are
usually some markdown notes files in here that I don't care about style
for. The previous markdownlint invokation didn't respect the global
gitignore, making local 'engage' runs kinda useless due to false
positives from the scratch dir.
2025-01-23 13:04:59 -08:00
Olivia Lee
472f51c350
allow adding canonical aliases from remote servers
Like is mentioned in the comment, this isn't explicitly required by the
spec, but it's reasonable and what synapse does.
2025-01-23 09:20:10 -08:00
Olivia Lee
29d8fbaefa
only validate canonical aliases that are new
Previously we required every alias in a canonical alias event sent by a
client to be valid, and would only validate local aliases. This
prevented clients from adding/removing canonical aliases if there were
existing remote or invalid aliases.
2025-01-23 09:20:10 -08:00
Olivia Lee
50c1e77cd6
factor canonical alias event validation into a helper function
Not all the semantics from the spec quote in the doc comment are
implemented yet.
2025-01-23 09:20:10 -08:00
Olivia Lee
051221c0ab
validate schema of new canonical alias events sent by clients
Previously, we would only attempt to validate the aliases in the event
content if we were able to parse the event, and would silently allow it
otherwise.
2025-01-23 09:20:10 -08:00
Olivia Lee
c748c7c188
return M_BAD_ALIAS when trying to set non-existent canonical aliases
This is the error code named in the spec.
2025-01-20 16:59:01 -08:00
Charles Hall
8537c0e8ac
rearrange introduction
I think it's most important for people to read the "Chat with us", "Can
I use it?" and "Expectations management" sections, though I'm not sure
what the best ordering is. This is probably fine.
2024-12-13 17:00:37 -08:00
Charles Hall
5c4d7bbe37
rewrite "Can I use it?" section 2024-12-13 17:00:10 -08:00
Charles Hall
1808ad66f4
document supported targets
This is also a good spot to link to the pre-built binaries. And since I
did that, I can also remove the bit about not publishing binary builds
from the introduction section.
2024-12-13 16:59:13 -08:00
Charles Hall
28a4483c6e
move conduit migration to installing section 2024-12-13 16:59:13 -08:00
Charles Hall
f831dfd1f1
add "Installing" chapter to the book 2024-12-13 16:59:13 -08:00
Olivia Lee
491518d2f3
add changelog entry for M_NOT_YET_UPLOADED when media is missing in fs
The first half of this change happened in !55, but we never added a
changelog entry until now.
2024-12-13 01:21:14 -08:00
Olivia Lee
795ce42518
return M_NOT_YET_UPLOADED when backing files are missing for a thumbnail
This was done for fetching original media files in
c70cfd3d25, but the change for thumbnails
was missed.
2024-12-13 01:21:14 -08:00
Olivia Lee
f216112455
don't treat media file open errors other than NotFound as missing media
For example, we want to return M_UNKNOWN and propagate the error if
somebody set up their database directory permissions wrong.
2024-12-13 01:21:05 -08:00
Olivia Lee
67f0689d73
move media file read/write logic to a helper function 2024-12-13 01:18:46 -08:00
Charles Hall
1e050c8983
expose static binaries in gitlab artifacts again 2024-12-11 14:37:43 -08:00
Charles Hall
94c1a39437
add a style guide 2024-12-11 14:01:38 -08:00
Charles Hall
42adad330e
move security info to its own page
This makes it easier to find.

Also sort the maintainers list while I'm here.
2024-12-11 13:26:16 -08:00
Charles Hall
3a10e23d94
move matrix link to introduction page 2024-12-11 13:25:12 -08:00
Charles Hall
cf067dbeb1
move changelog section above contributing 2024-12-11 13:25:07 -08:00
Olivia Lee
ba72616672
do not backoff remote device key queries when a request fails due to backoff
The previous logic would increment the backoff counter both when a
request actually fails and when we do not make a request because the
server was already in backoff. This lead to a positive feedback loop
where every request made while a server is in backoff increases the
backoff delay, making it impossible to recover from backoff unless the
entire backoff delay elapses with zero requests.
2024-12-11 13:02:11 -08:00
Olivia Lee
4ee8312068
reset device key query backoff after a successful request
Failing to reset the backoff state resulted in a monotonically
increasing backoff delay. If a remote server was temporarily
unavailable, we would have a persistently increased rate of key query
failures until the backoff state was reset by a server restart. If
enough key queries were attempted while the remote was unavailable, it
can accumulate an arbitrarily long backoff delay and effectively block
all future key queries to this server.
2024-12-11 13:02:10 -08:00
Olivia Lee
79cedccdb6
factor remote device key request logic into helper functions
This is pure code-motion, with no behavior changes. The new structure
will make it easier to fix the backoff behavior, and makes the code
somewhat less of a nightmare to follow.
2024-12-11 13:02:10 -08:00
Charles Hall
9e3738d330
run some jobs on the final commit only
These are too expensive to be worth running against all commits.
2024-12-11 11:39:20 -08:00
Charles Hall
f4dfb496e1
skip jobs that don't make artifacts on main branch
This is okay because we only allow fast-forward merges, changes always
go through MRs, and MRs are subject to, e.g., the code quality checks.
This change is desirable because it should save some time and energy.
2024-12-11 11:39:20 -08:00
Charles Hall
976aef690f
cache and skip commits that have passed already
Commits marked as passed can be re-run by changing their hash or
clearing the cache.
2024-12-11 11:39:20 -08:00
Charles Hall
2265b6615e
run ci for each commit
GitLab doesn't seem to have built-in support for this because of course
it doesn't.

To do this, we move the job scripts to a different file to make it
possible to share code between job scripts.
2024-12-11 11:39:20 -08:00
Charles Hall
a5eba45472
set owner write bit in pages job
This makes deleting the copied files easier.
2024-12-11 11:39:20 -08:00
Charles Hall
540cc89c83
move ci job scipts into an actual script
This will make it possible/easier to:

* share code between jobs
* run jobs locally
2024-12-11 11:39:20 -08:00
Charles Hall
d4ffa78979
make it possible to direnv exec . at top level
This way we don't have to be so careful about where we call `direnv
exec .` and can safely assume e.g. in scripts that we're always in the
direnv environment.
2024-12-11 11:39:20 -08:00
Charles Hall
a520c4e032
fix sorting in shell.nix 2024-12-11 11:39:20 -08:00
Charles Hall
554751917a
fix some minor documentation typos
* Capitalize Conduit and Grapevine
* Replace a "Not" with "Note"
* Change a "Conduit 0.7.0" to "Conduit <0.8.0"
* Expand "db" to "database" and "database schema" as appropriate
2024-12-11 10:08:55 -08:00
Lambda
daceadb310 sync/v3: factor out into separate functions
This is both easier to read and produces much better tracing spans.
2024-12-11 17:38:49 +00:00
Lambda
8a7f87e9b4 sync/v3: move readonly data to context struct
This makes it a lot easier to factor out parts of the big
sync_events_route().
2024-12-11 17:38:49 +00:00
Lambda
55a04f77c6 sync/v3: record relevant span fields 2024-12-11 17:38:49 +00:00
Lambda
79783ebe56 sync: split into separate files 2024-12-11 17:38:49 +00:00
Olivia Lee
c7011fb70d
added "can I use it" section to the docs
This was based on the similar section in the hedgedoc. I dropped the bit
about grapevine users getting banned from the conduwuit rooms cause we
haven't heard about that happening for a while and the original case was
pretty unclear.
2024-12-09 21:03:57 -08:00
Olivia Lee
eda34adef2
add conduit migration section to docs
Most of the database compatibility info came from Charles' notes
in #17.
2024-12-09 21:03:57 -08:00
Olivia Lee
54c25ceb3c
update docs link in the readme 2024-12-06 11:37:17 -08:00
Olivia Lee
26e986a89b
update olivia's gitlab and matrix usernames 2024-12-06 11:37:11 -08:00
Olivia Lee
a5fe9129f1
update media deletion entry in changelog 2024-12-01 16:18:05 -08:00
Olivia Lee
46e8a63489
allow deleting dangling thumbnails
Previously attempting to delete an MXC that is only associated with
dangling thumbnails would fail, because it assumes that every thumbnail
must have a corresponding original in the db, and errors out if it can't
find the original. This is incorrect because we create dangling
thumbnails when requesting a remote thumbnail over federation when we
don't have the original file.
2024-12-01 16:18:05 -08:00
Olivia Lee
916088a22f
include mxcs from dangling thumbnails in service::media::iter_all
When requesting remote thumbnails over federation, we can end up with a
thumbnail in the media db without an associated original file. Because
of this, skipping thumbnails is insufficient to get a list of all MXCs.
2024-12-01 16:18:04 -08:00
Olivia Lee
230192be1b
skip thumbnails with only one nonzero dimension in all_file_metadata
Previously we were only skipping thumbnails that had both dimensions
nonzero. I think the assumption was that only the dimensions allowed by
services::media::thumbnail_properties would be used. This is not the
case because we have used arbitrary thumbnail dimensions when requesting
remote thumbnails.
2024-12-01 16:18:04 -08:00
Olivia Lee
14b44064b3
propagate corrupted media key errors
Now that we are able to distinguish between corrupted media keys and
missing files, it makes more sense to propagate the corrupted keys up to
the caller.
2024-12-01 16:18:04 -08:00
Olivia Lee
f0f81db99b
return Option from media::data::search_file_metadata
This is useful to easily distinguish missing files from corrupted keys.
All existing usage sites have been modified so there is no behavior
change in this commit.
2024-12-01 16:18:04 -08:00
Charles Hall
861016ce0f
inline state accessor service code from data trait
These were all calling into services() and not actually directly
accessing the database at all anyway.
2024-12-01 09:13:50 -08:00
Charles Hall
ec01a84efb
add custom release profile with debuginfo enabled
This is primarily useful for profiling. Also change the nix package to
decide whether to strip based on whether the "release" profile is used.
2024-12-01 08:47:09 -08:00
Charles Hall
f0e41ade59
set rules for gitlab ci jobs
This should give us access to more CI variables. This also replaces our
usage of `only`, which is apparently deprecated.
2024-11-29 15:36:40 -08:00
Charles Hall
2f8e0e3e52
update rust dependencies
Ruma dropped a couple dependencies and includes a stateres performance
improvement. May as well pull in everything else (except OTel) while
we're at it.
2024-11-29 10:39:53 -08:00
Olivia Lee
8fcec6396e
keep going when one deletion fails in delete-remote-media
We *should* ensure that media deletion is always successful, but when a
bug causes a single object to fail deletion it's better to try to delete
the remaining objects than to give up entirely.
2024-11-28 18:32:05 -08:00
Olivia Lee
11b5055647
log details of failed media deletion 2024-11-28 18:32:05 -08:00
Olivia Lee
bdb623cb07
add comment explaining that failed media keys are logged 2024-11-28 16:46:54 -08:00
Olivia Lee
ed789e6536
show count of corrupted media keys in delete-remote-media command 2024-11-28 15:55:20 -08:00
Olivia Lee
79bc2525e3
add unit tests for media key parsing
We've had at least a couple media key parsing bugs. Let's get a *little*
more confidence that this code works :)
2024-11-27 22:24:54 -08:00
Olivia Lee
103a4fb56b
handle media keys where thumbnail size contains 0xFF
Our current code should never write these, because we have an allowlist
of thumbnail sizes. None of the allowed sizes contain a 0xFF byte. We
have observed keys with a 0xFF in the thumbnail size a couple times on
real servers, and believe an early version of conduit wrote these before
the allowlist was added.

These keys were originally handled correctly, and were broken by
e2cba15ed2. Before that commit, we were
parsing media keys backwards, and never tried to read the thumbnail size
or mxc url.
2024-11-27 22:15:07 -08:00
Charles Hall
2bf1975e75
add flake output for the website root 2024-11-22 11:21:39 -08:00
Charles Hall
af15f0c596
update rust dependencies
Except OTel stuff, as usual.

The main point is to bump `ruma-state-res` for a state resolution fix.
2024-11-18 11:07:23 -08:00
Charles Hall
7fc8f224eb
update flake.lock
Flake lock file updates:

• Updated input 'attic':
    'github:zhaofengli/attic/48c8b395bfbc6b76c7eae74df6c74351255a095c' (2024-10-30)
  → 'github:zhaofengli/attic/47752427561f1c34debb16728a210d378f0ece36' (2024-11-10)
• Updated input 'crane':
    'github:ipetkov/crane/498d9f122c413ee1154e8131ace5a35a80d8fa76' (2024-10-27)
  → 'github:ipetkov/crane/ef80ead953c1b28316cc3f8613904edc2eb90c28' (2024-11-08)
• Updated input 'fenix':
    'github:nix-community/fenix/87b4d20f896c99018dde4702a9c6157b516f2a76' (2024-11-01)
  → 'github:nix-community/fenix/e10ba121773f754a30d31b6163919a3e404a434f' (2024-11-16)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/0ba893e1a00d92557ac91efb771d72eee36ca687' (2024-10-31)
  → 'github:rust-lang/rust-analyzer/1b90e979aeee8d1db7fe14603a00834052505497' (2024-11-15)
• Updated input 'flake-utils':
    'github:numtide/flake-utils/c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a' (2024-09-17)
  → 'github:numtide/flake-utils/11707dc2f618dd54ca8739b309ec4fc024de578b' (2024-11-13)
• Updated input 'nix-filter':
    'github:numtide/nix-filter/776e68c1d014c3adde193a18db9d738458cd2ba4' (2024-10-29)
  → 'github:numtide/nix-filter/f7653272fd234696ae94229839a99b73c9ab7de0' (2024-11-13)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/807e9154dcb16384b1b765ebe9cd2bba2ac287fd' (2024-10-29)
  → 'github:NixOS/nixpkgs/5e4fbfb6b3de1aa2872b76d49fafc942626e2add' (2024-11-15)
2024-11-18 11:07:19 -08:00
Olivia Lee
887e59cf03
update name and email in mailmap 2024-11-16 21:15:16 -08:00
Charles Hall
93c714a199
update changelog 2024-11-12 00:36:36 -08:00
Charles Hall
51b30d9ba3
largely stop using RoomCreateEventContent
This became a problem because  #foundation-office:matrix.org has a
malformed create event with its `predecessor` set to a string instead of
a map.

The solution to this is, unfortunately, to do more shotgun parsing to
extract only the desired fields rather than trying to parse the entire
content every time. To prevent this kind of problem from happening
again, `RoomCreateEventContent` must only be used for creating new PDUs,
existing PDUs must be shotgun-parsed.
2024-11-08 20:36:46 -08:00
Charles Hall
9d0cf428a5
stop passing the entire create event around
This gets rid of 3 instances of re-parsing the room version.

There's one place where we need the event ID of the room create event to
verify federation responses, so now we just look up the event ID at that
point instead.
2024-11-08 20:36:07 -08:00
Charles Hall
a4e1522875
generalize get_room_version
There are other fields of `m.room.create` events that are useful to
individually extract without caring about the values of other fields.
2024-11-08 18:38:16 -08:00
Charles Hall
c9c30fba30
upgrade to latest ruma 2024-11-08 17:26:44 -08:00
Charles Hall
2dbb101140
upgrade to latest rust-rocksdb
We gotta overrideAttrs to set the src and version to a newer version
than nixpkgs has now.
2024-11-08 17:26:38 -08:00
Charles Hall
d565b22da9
run cargo upgrade && cargo update
There are a few available incompatible updates not covered here:

* RocksDB
* Ruma
* OTel stuff
2024-11-07 11:36:13 -08:00
Charles Hall
e74c8687f5
drop dependency on once-cell 2024-11-07 11:36:13 -08:00
Charles Hall
a550d8db1f
assert that rocksdb versions match during eval 2024-11-07 11:36:13 -08:00
Charles Hall
9f4a1578aa
move rocksdb to its own file 2024-11-07 11:36:13 -08:00
Charles Hall
2e6bf86a42
move rust-jemalloc-sys to its own file 2024-11-07 11:36:13 -08:00
Charles Hall
f07c8c2b6f
update flake.lock
And some supporting changes:

* crane: It removed its dependency on nixpkgs and made overrideToolchain
  take a function for splicing reasons, but we're doing splicing
  ourselves so we can just ignore the function argument. These changes
  are in `flake.nix`.
* [NixOS/nixpkgs#347228][0]: linkerFor* were removed because the linker
  no longer needs to be different in some edge cases. Based on the
  diff of the PR that introduced this change, ccFor* are the proper
  replacements. These changes are in `cross-compilation-env.nix` in the
  compiler-and-linker-choosing section.
* [NixOS/nixpkgs#350299][1]: buildPlatform isn't at the top level
  anymore, we have to go through stdenv now. These changes are in
  `nix/shell.nix`.
* rocksdb: nixpkgs has 9.6.1 now so we need to upgrade our rust
  library to use the matching version. These changes are in
  `Cargo.toml`, `Cargo.lock`, `nix/pkgs/default/default.nix`, and
  `cross-compilation-env.nix` in the linker flags section.

[0]: https://github.com/NixOS/nixpkgs/pull/347228
[1]: https://github.com/NixOS/nixpkgs/pull/350299

Flake lock file updates:

• Updated input 'attic':
    'github:zhaofengli/attic/4dbdbee45728d8ce5788db6461aaaa89d98081f0' (2024-03-29)
  → 'github:zhaofengli/attic/48c8b395bfbc6b76c7eae74df6c74351255a095c' (2024-10-30)
• Updated input 'attic/crane':
    'github:ipetkov/crane/7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb' (2023-12-18)
  → 'github:ipetkov/crane/4c6c77920b8d44cd6660c1621dea6b3fc4b4c4f4' (2024-08-06)
• Updated input 'attic/flake-compat':
    'github:edolstra/flake-compat/35bb57c0c8d8b62bbfd284272c928ceb64ddbde9' (2023-01-17)
  → 'github:edolstra/flake-compat/0f9255e01c2351cc7d116c072cb317785dd33b33' (2023-10-04)
• Added input 'attic/flake-parts':
    'github:hercules-ci/flake-parts/8471fe90ad337a8074e957b69ca4d0089218391d' (2024-08-01)
• Added input 'attic/flake-parts/nixpkgs-lib':
    follows 'attic/nixpkgs'
• Removed input 'attic/flake-utils'
• Updated input 'attic/nixpkgs':
    'github:NixOS/nixpkgs/07262b18b97000d16a4bdb003418bd2fb067a932' (2024-03-25)
  → 'github:NixOS/nixpkgs/159be5db480d1df880a0135ca0bfed84c2f88353' (2024-09-11)
• Updated input 'attic/nixpkgs-stable':
    'github:NixOS/nixpkgs/44733514b72e732bd49f5511bd0203dea9b9a434' (2024-03-26)
  → 'github:NixOS/nixpkgs/797f7dc49e0bc7fab4b57c021cdf68f595e47841' (2024-08-22)
• Added input 'attic/nix-github-actions':
    'github:nix-community/nix-github-actions/e04df33f62cdcf93d73e9a04142464753a16db67' (2024-10-24)
• Added input 'attic/nix-github-actions/nixpkgs':
    follows 'attic/nixpkgs'
• Updated input 'crane':
    'github:ipetkov/crane/109987da061a1bf452f435f1653c47511587d919' (2024-05-24)
  → 'github:ipetkov/crane/498d9f122c413ee1154e8131ace5a35a80d8fa76' (2024-10-27)
• Removed input 'crane/nixpkgs'
• Updated input 'fenix':
    'github:nix-community/fenix/b6fc5035b28e36a98370d0eac44f4ef3fd323df6' (2024-05-22)
  → 'github:nix-community/fenix/87b4d20f896c99018dde4702a9c6157b516f2a76' (2024-11-01)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/21ec8f523812b88418b2bfc64240c62b3dd967bd' (2024-05-19)
  → 'github:rust-lang/rust-analyzer/0ba893e1a00d92557ac91efb771d72eee36ca687' (2024-10-31)
• Updated input 'flake-utils':
    'github:numtide/flake-utils/b1d9ab70662946ef0850d488da1c9019f3a9752a' (2024-03-11)
  → 'github:numtide/flake-utils/c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a' (2024-09-17)
• Updated input 'nix-filter':
    'github:numtide/nix-filter/3342559a24e85fc164b295c3444e8a139924675b' (2024-03-11)
  → 'github:numtide/nix-filter/776e68c1d014c3adde193a18db9d738458cd2ba4' (2024-10-29)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/5710852ba686cc1fd0d3b8e22b3117d43ba374c2' (2024-05-21)
  → 'github:NixOS/nixpkgs/807e9154dcb16384b1b765ebe9cd2bba2ac287fd' (2024-10-29)
2024-11-07 11:35:48 -08:00
Charles Hall
d42a5ec1f0
avoid overhead when cache sizes are zero
Don't even try taking locks, inserting or removing anything, etc.
2024-11-01 14:25:52 -07:00
Charles Hall
1148c6004f
make all caches individually configurable
Also:

* Removes the `cache_capacity_modifier` option
* Renames the `pdu_cache_capacity` option to `cache.pdu`
2024-11-01 14:25:52 -07:00
Charles Hall
9fab7538a0
scale roomid_spacechunk_cache by modifier
Not scaling this was probably unintentional.
2024-11-01 14:25:52 -07:00
Charles Hall
4083451a10
rename Services constructor 2024-11-01 14:25:52 -07:00
Charles Hall
fefc84e8c7
rename constructor for globals service 2024-11-01 14:25:52 -07:00
Charles Hall
55b605f046
rename constructor for sending service 2024-11-01 14:25:52 -07:00
Charles Hall
4faa8ea37c
rename constructor for admin service 2024-11-01 14:25:52 -07:00
Charles Hall
e06d126d4e
add constructor for users service 2024-11-01 14:25:52 -07:00
Charles Hall
f771d319b2
add constructor for spaces service
Also adds a public function to invalidate the cache rather than exposing
the entire cache publicly.
2024-11-01 14:25:52 -07:00
Charles Hall
f702b6cccd
add constructor for state compressor service 2024-11-01 14:25:52 -07:00
Charles Hall
c6e2f8372c
add constructor for state accessor service 2024-11-01 14:25:52 -07:00
Charles Hall
a083ff9200
add constructor for lazy-loading service 2024-11-01 14:25:52 -07:00
Charles Hall
3b28d0cfda
add constructor for typing service 2024-11-01 14:25:52 -07:00
Charles Hall
b18df8de70
rename appservice service constructor 2024-11-01 14:25:52 -07:00
Charles Hall
5be1e20eb4
call maximize_fd_limit at top of main
This way we don't shoot ourselves in the foot by forgetting to do it for
other subcommands (e.g. that manipulate the database) in the future.
2024-11-01 13:16:36 -07:00
Andreas Fuchs
9529d43a21 ChangeLog entry for check-config subcmd 2024-11-01 12:16:49 -04:00
Andreas Fuchs
dcf64f03fb Validate generated config file in the nixos module
This uses the usual pkgs.runCommand pattern to ensure that no
non-parseable config files can make it to the command line.
2024-11-01 12:10:01 -04:00
Andreas Fuchs
a02c551a5e Disallow any unknown fields in configuration files
This will break backwards compatibility of configurations, but
ensures that a previously-configured setting won't get dropped
arbitrarily. Pretty much worth it, I think.
2024-11-01 12:09:58 -04:00
Andreas Fuchs
26ba489aa3 Add a "check-config" command to validate config files & tests for it 2024-11-01 12:08:17 -04:00
Lambda
70ee206031 Extract source address for requests 2024-10-25 20:48:38 +00:00
Lambda
3247c64cd8 Add support for HAProxy proxy protocol for listeners 2024-10-25 20:48:38 +00:00
Lambda
99f3e2aecd Refactor server listener spawning 2024-10-25 20:47:04 +00:00
Charles Hall
86481fd651
make reload_handles optional for creating Services
This will be useful for instantiating services in CLI subcommands, which
have different requirements around observeability.
2024-10-25 11:27:11 -07:00
Charles Hall
b03c2a15b3
add observability infrastructure for cli subcmds 2024-10-25 11:27:11 -07:00
K900
b93c39ee93 fix: use non-alias name in nixos module 2024-10-24 14:46:44 +00:00
Charles Hall
ce7efc1eff
move lasttimelinecount_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
107f4521e0
move appservice_in_room_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
9d62865b28
move our_real_users_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
d3b62e598d
move shortstatekey_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
190b788683
move statekeyshort_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
2b2b4169df
move eventidshort_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
095ee483ac
move auth_chain_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
47502d1f36
move shorteventid_cache to service 2024-10-20 13:29:33 -07:00
Charles Hall
7563360bee
move pdu_cache to service 2024-10-20 13:29:32 -07:00
Charles Hall
fb534d8140
move userdevicesessionid_uiaarequest to service 2024-10-20 13:29:32 -07:00
Charles Hall
a1fe0f3fff
add service type for short
This will be necessary in the near future.
2024-10-20 13:29:32 -07:00
Charles Hall
e0cf163486
delete useless admin commands
To clear caches, restart the server. We may want to consider adding the
cache sizes and database memory usage as metrics in the future.
2024-10-20 13:29:28 -07:00
Lambda
6a44d0af2b Fix u8_slice_to_hex()
For bytes <0x10, this would omit the leading zero.
2024-10-20 18:50:05 +00:00
Andreas Fuchs
76a633cb66
Log failed remote resident server join requests 2024-10-13 19:26:35 -07:00
Andreas Fuchs
e001356653
Return local join error if all remote joins fail
If all join requests to resident servers fail or if the joining server
is the only resident server (i.e. the room is local-only), we would
previously send a 500 error, even if the more correct response would be
M_UNAUTHORIZED (e.g. if the user tries to join an invite-only room).

To fix this, we now return the error generated by attempting the join
locally, which correctly informs the client about why their request
failed.
2024-10-13 19:10:58 -07:00
Charles Hall
5a490a4397
fix mod/use order
Yes, it does actually bother me, thanks for asking.
2024-10-03 15:28:24 -07:00
mikoto
287f6b9163
refactor calculate_invite_state
That was terribly named and terribly implemented.

Co-authored-by: Charles Hall <charles@computer.surgery>
2024-10-03 10:52:07 -07:00
Lambda
e14b7f28f2
Implement federation self-test 2024-09-27 10:51:32 -07:00
Lambda
6022d56094
Use enums for options to send_request(), add allow_loopback 2024-09-27 10:48:12 -07:00
Lambda
94d523ebcb
Reload TLS config on SIGHUP 2024-09-27 09:51:17 -07:00
Lambda
39880cc6ac
Abstract over sd_notify 2024-09-27 09:50:51 -07:00
Charles Hall
6ab87f97dd
include traceresponse header if possible
This can help with debugging.
2024-09-26 19:01:15 -07:00
Benjamin Lee
9add9a1e96
fix room version comparisons
Fixes a set of bugs introduced by 00b77144c1,
where we replaced explicit `RoomVersionId` matches with `version < V11`
comparisons. The `Ord` impl on `RoomVersionId` does not work like that,
and is in fact a lexicographic string comparison[1]. The most visible
effect of these bugs is that incoming redaction events would sometimes
be ignored.

Instead of reverting to the explicit matches, which were quite verbose,
I implemented a `RoomVersion` struct that has flags for each property
that we care about. This is similar to the approach used by ruma[2] and
synapse[3].

[1]: 7cfa3be0c6/crates/ruma-common/src/identifiers/room_version_id.rs (L136)
[2]: 7cfa3be0c6/crates/ruma-state-res/src/room_version.rs
[3]: c856ae4724/synapse/api/room_versions.py
2024-09-26 13:01:25 -07:00
Charles Hall
ad37eae869
use OnceLock instead of RwLock for SERVICES
It actually has the semantics we need. Until we get rid of SERVICES.
2024-09-25 10:43:05 -07:00
Charles Hall
032e1ca3c6
hide global services jank in service module
Mainly to make it easier to initialize the SERVICES global correctly in
more than one place.

Also this stuff really shouldn't live at the crate root anyway.
2024-09-25 10:43:05 -07:00
Charles Hall
1fd20cdeba
factor server_name change check into a reusable fn 2024-09-25 10:43:05 -07:00
Charles Hall
c2c6083277
make load_or_create *only* load_or_create
Extracted the other logic to its current singular callsite for now.

The load_or_create function finally does nothing other than load or
create the database (and do some related checks, which is fine). This
paves the way for more/better database surgery tooling.
2024-09-25 10:39:46 -07:00
Charles Hall
e9caf228b3
move config check into config load function 2024-09-25 10:39:46 -07:00
Charles Hall
75ef57e0ce
remove config check
* Database load function is the wrong place for this
* There's no good lower bound to check for this
* Surely people setting this to a small value would realize what they're
  in for
2024-09-25 10:39:42 -07:00
Benjamin Lee
279c6472c5
split some logic out of KeyValueDatabase::load_or_create
This method did _a lot_ of things at the same time. In order to use
`KeyValueDatabase` for the migrate-db command, we need to be able to
open a db without attempting to apply all the migrations and without
spawning a bunch of unrelated background tasks.

The state after this refactor is still not great, but it's enough to do
a migration tool.
2024-09-24 20:57:57 -07:00
Benjamin Lee
059dfe54e3
factor out helper function for db migrations 2024-09-24 16:02:04 -07:00
Benjamin Lee
e2318cad8a
fix serving tls by setting rustls default crypto provider
The rustls version bump in c24f79b79b
introduced a panic when serving listeners with 'tls = true':

> thread 'main' panicked at /nix/store/eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee-vendor-cargo-deps/c19b7c6f923b580ac259164a89f2577984ad5ab09ee9d583b888f934adbbe8d0/rustls-0.23.13/src/crypto/mod.rs:265:14:
> no process-level CryptoProvider available -- call CryptoProvider::install_default() before this point

This commit fixes this by setting the default provider to ring. I chose
ring (the old rustls default) over aws-lc-rs (the new default) for a few
reasons:

 - Judging by github issues, aws-lc-rs seems to have a lot of build problems.
   We don't need more of that.
 - The "motivation" section in the aws-lc-rs docs only talks about FIPS,
   which we do not care about.
 - My past experience with things that start with "aws-" has been very
   negative.
2024-09-23 23:39:23 -07:00
Lambda
084d862e51
Allow configuring served components per listener 2024-09-23 16:43:52 -07:00
Lambda
d62d0e2f0e
Split routes into components 2024-09-23 16:43:52 -07:00
Charles Hall
b0d1cc1b63
bump otel to v0.24.0
Someone contributed opentelemetry-prometheus support for v0.24 and this
version also doesn't put stupid requirements on the tokio version. This
version of the OTel ecosystem also fixes an apparent bug with some hacks
I plan on doing in the future...
2024-09-23 14:22:55 -07:00
Charles Hall
c24f79b79b
update rust deps except rocksdb and otel clownery
* OTel v0.25.0 requires downgrading Tokio to 1.38 [0]
* They have a fix for this but aren't cutting a release just for release
  schedule reasons [1]
* Prometheus support (at least for server-pull) was dropped at OTel
  v0.23 and isn't planned to be picked up again until OTel v1 [2]
* No real reasoning was provided for this decision AFAICT [3] [4]
* So many compiler errors
* Unhelpful changelogs

The last two points are what made me give up on trying to upgrade to
OTel v0.24 too.

RocksDB isn't updated because we'd need to update our nixpkgs too but
that causes other problems, such as an upstream bug in liburing when
building for musl.

[0]: https://github.com/open-telemetry/opentelemetry-rust/issues/2094
[1]: https://github.com/open-telemetry/opentelemetry-rust/issues/2094#issuecomment-2346834030
[2]: https://docs.rs/opentelemetry-prometheus/0.17.0/opentelemetry_prometheus/index.html
[3]: https://github.com/open-telemetry/opentelemetry-rust/pull/1792
[4]: https://github.com/open-telemetry/opentelemetry-rust/pull/1792#issuecomment-2121514344
2024-09-23 14:22:55 -07:00
Benjamin Lee
c1bf4a8ee3
changelog entry for CLI compatibility break 2024-09-21 14:11:40 -07:00
Benjamin Lee
5315bac0c5
split out separate error type for serve command 2024-09-21 14:11:40 -07:00
Benjamin Lee
86515d53cc
move 'serve' command logic into a submodule of 'cli'
The changes to 'main.rs' and 'cli/serve.rs' in this commit are almost
pure code-motion.
2024-09-21 14:11:39 -07:00
Benjamin Lee
be87774a3b
set up structure for multiple cli commands
The previous cli is now behind the 'serve' subcommand.
2024-09-21 14:11:26 -07:00
Charles Hall
1ee3bbb316
oops, i dropped my fork
The maintainers had a discussion internally and decided it's unlikely
that we'll have the capacity to try to do a rewrite, which was the
original reason for the suffix's presence. So, now can get rid of it.
2024-09-20 16:52:05 -07:00
Charles Hall
d388994657
rewrite media key parser
Fixes a regression in e2cba15ed2 where the
Content-Type and Content-Disposition parts are extracted in the wrong
order.

Fixes a long-standing issue in b6d721374f
where the Content-Type part was allowed to be completely missing rather
than present and 0 bytes long.

Improves the error messages for various parsing failures to be unique
and more obvious.
2024-09-19 15:27:10 -07:00
Charles Hall
88b009a8d4
update changelog 2024-09-19 15:23:59 -07:00
Charles Hall
b34d78a030
skip over broken keys instead of aborting
Errors will show up in the logs in this case with detailed information
about what broke.

In the future we should add some kind of database integrity check
functionality and also functionality to repair/delete broken data, but
for now this at least makes it work 99.99% of the time.
2024-09-19 15:23:59 -07:00
Charles Hall
cb3e0c620a
improve media key decoding logs
On my HS I observed 5 instances of keys with the following format:

* MXC bytes.
* A 0xFF byte.
* 4 bytes where the width and height are supposed to be, which are
  supposed to be 8 bytes in length.
* 3 consecutive 0xFF bytes. This means that the `content-type` and
  `content-disposition` sections both parse as the empty string, and
  there's an extra separator at the end too.
* Extra bytes, all of which were `image/png`.

The 4 bytes where the width and height are supposed to be were one of:

* 003ED000
* 003EE000
* 003EF001

Which seems to have some kind of pattern to it...

After much digging, we have absolutely no idea what could've caused
this. Cursed.
2024-09-19 15:23:20 -07:00
Charles Hall
d848e787d3
ignore files that were probably never created
File data is inserted into the database before being created on disk,
which means that it's possible for data to exist in the database that
doesn't exist on disk. In this case, the media deletion functions should
simply ignore this error.
2024-09-19 12:29:51 -07:00
Lambda
ca6bc74074 Fix X-Matrix signature validation for incoming requests
For HTTP/1 requests, an inbound Request's URI contains only the path and
query parameters, since there's no way to synthesize the authority part.
This is exactly what we need for the X-Matrix "uri" field.

HTTP/2 requests however can contain the :authority pseudo-header, which
is used to populate the Request's URI. Using a URL that includes an
authority breaks the signature check.

Largely inspired by conduit MR !631
(https://gitlab.com/famedly/conduit/-/merge_requests/631).

Co-authored-by: strawberry <strawberry@puppygock.gay>
2024-09-19 16:25:23 +00:00
Lambda
0d6a7eb968 Disable unauthenticated media access 2024-09-18 20:33:28 +00:00
Charles Hall
b9ee898920
require client base_url, rename from authority
The previous code used `server_name` as a fallback but in reality there
is no real relationship between `server_name` and the location clients
are supposed to make requests to.

Additionally, the `insecure` option is gone, because we now allow users
to control the entire URL, so they're free to choose the scheme.
2024-09-18 13:03:49 -07:00
Benjamin Lee
48850605b0
changelog entry for media deletion admin commands 2024-09-17 19:31:54 -07:00
Benjamin Lee
ba7b224c38
add dry-run mode to delete-remote-media-files admin command 2024-09-17 19:31:54 -07:00
Benjamin Lee
9d14c5d461
add admin command to delete all remote media files 2024-09-17 19:31:51 -07:00
Benjamin Lee
d7087c66bb
add admin command to delete individual media files 2024-09-17 19:13:54 -07:00
Benjamin Lee
7672cc8473
use OwnedMxcUri in media service
Not using `MxcData` because it borrows it's fields, and so we wouldn't
be able to return an owned `MxcData` from functions that read the db.
2024-09-15 00:32:17 -07:00
Benjamin Lee
e2cba15ed2
factor out helper for parsing media keys
Leaving this private in `database::key_value::media` because the way
the metadata is encoded in media keys is a mess. I want to fix that in
the future, and want to limit the number of things that rely on it for
now.
2024-09-15 00:32:17 -07:00
Lambda
3bb4a25c1d Include old verify keys in _matrix/key/v2/server response 2024-09-13 17:02:30 +00:00
Lambda
296824fef4 Always use local keypair instead of trying to find our own keys in cache 2024-09-13 17:02:30 +00:00
Lambda
458a7458dc Support specifying old_verify_keys in config 2024-09-13 17:02:30 +00:00
Lambda
5691cf0868 Better debugging for signing key fetching 2024-09-13 13:31:04 +00:00
Charles Hall
9e6a5e6604
update changelog 2024-09-08 14:08:32 -07:00
Charles Hall
449c27642c
hide sliding sync behind explicit option
We want to make sure users know this sliding sync impl is pretty buggy
before they attempt to use it.
2024-09-08 14:08:32 -07:00
Charles Hall
806cc0cb28
serve well-known client and server config
This way users can have a simpler time configuring this stuff and we can
worry about the spec compliance parts and specifying the same thing over
and over parts.
2024-09-08 13:35:38 -07:00
Lambda
3a55684623
Update MSRV to 1.81.0
Plus a "__CARGO_FIX_YOLO=1 cargo clippy --fix"
2024-09-08 09:54:08 -07:00
Lambda
74589043f7
Fix weird type gymnastics 2024-09-08 09:54:04 -07:00
Lambda
5c4062742f
Log curl command line for all requests at trace 2024-09-08 09:10:40 -07:00
Lambda
b0f33207fe Add wrapper types for short IDs 2024-09-06 17:20:14 +00:00
Lambda
f1642c92d1 Take iterator in calculate_hash()
Avoids unnecessary allocations.
2024-09-06 17:20:14 +00:00
Lambda
cce83beedb Properly type stored EDUs 2024-09-06 17:20:14 +00:00
Lambda
26322d5a95 Add PduId wrapper struct
Death to Vec<u8>
2024-09-06 17:20:14 +00:00
Lambda
341f4213d0 Use self instead of going through services() 2024-09-05 19:42:37 +00:00
Lambda
f52cf53931 Remove obsolete clippy #[allow]s 2024-09-05 19:42:37 +00:00
Lambda
22ce624a81 event_handler: remove AsyncRecursiveType alias, simplify signatures 2024-09-05 19:42:37 +00:00
Lambda
542e097cdf server_server: use non-deprecated create_join_event types
Instead of working with v1 types and converting them to v2 as required,
do it the other way around. Allows us to remove a very broad
#[allow(deprecated)].
2024-09-05 19:42:37 +00:00
Benjamin Lee
be14f5bddc
fetch signing keys on join even when no cached keys for origin
Silly mistake
2024-09-01 12:22:10 -07:00
Lambda
b4fecbc517 client/media: work around matrix-media-repo bug
It rejects Authenticated Media requests if X-Matrix values aren't
unnecessarily quoted:
https://github.com/t2bot/matrix-media-repo/issues/609
2024-09-01 09:42:45 +00:00
Lambda
3fe0110649 media: convert allow_remote to enum 2024-09-01 09:42:45 +00:00
Lambda
a4b7df1b3a media: use FileMeta instead of tuples 2024-09-01 09:42:45 +00:00
Lambda
1ccb1e572b media: add MediaFileKey wrapper
One more win in the fight against the Vec<u8>s
2024-09-01 09:42:45 +00:00
Lambda
84850a163d Factor content out of FileMeta
That's not what *meta*data means
2024-09-01 09:42:45 +00:00
Lambda
a06c8db996 Fetch and thumbnail original media if fetching thumbnail fails
E.g. because the remote server's thumbnail endpoint is broken in
hilarious ways: https://github.com/element-hq/synapse/issues/17518
2024-09-01 09:42:45 +00:00
Lambda
82aacdc153 Update changelog for Authenticated Media 2024-09-01 09:42:45 +00:00
Lambda
d3b6718812 server_server: implement authenticated media endpoints 2024-09-01 09:42:45 +00:00
Lambda
79053ad052 client_server: use and provide authenticated media API 2024-09-01 09:42:45 +00:00
Lambda
7f6ab63752 client_server: factor out width/height conversion 2024-09-01 09:42:45 +00:00
Lambda
edfaa83405 server_server: make outbound requests using spec v1.11
This is required for authenticated media requests, which are otherwise
performed using the unstable endpoint.
2024-09-01 09:42:45 +00:00
Lambda
94204415ee Add MxcData helper 2024-09-01 09:42:45 +00:00
Lambda
64b3c357dd media: put old API behind legacy_media import 2024-09-01 09:42:45 +00:00
Lambda
5a5bea3217 cargo update ruma httparse
This adds authenticated media APIs.
2024-09-01 09:42:45 +00:00
Stephen D
556f2157a2 add expired keys fix to changelog 2024-08-31 23:25:54 -03:00
Stephen D
926091223d fetch keys that are expired 2024-08-31 23:25:44 -03:00
Stephen D
06fa49ac27 Update changelog to reflect key retrieval logic changes 2024-08-31 23:03:00 -03:00
Stephen D
1b13d7f7ab Fix bug when retrieving keys for an event.
It's possible for a server to have multiple associated public keys. This can happen when a Matrix server is set up on a particular domain, its key is lost, and the server continues running on the domain. Now there will be two keys associated to the domain. The old logic wouldn't fetch the new key if we already had the old key cached. The new logic will fetch any keys we don't have that we need, rather than just fetching one key per server.
2024-08-31 23:02:53 -03:00
Stephen D
2db3b18ce8 Clean up existing code in get_server_keys_from_cache. It's a little DRYer now 2024-08-31 22:25:43 -03:00
Lambda
006ea0eb94 Fix appservice users getting invited over federation
Invites are magic sauce, we need to manually send it off to the
appservice if it's for an appservice user.
2024-08-31 10:18:29 +00:00
Benjamin Lee
4ad50e2708
only log banned users if they are local
This was the intent of f03b6cde29, but it
was missing the check that the user is actually local, and so was
logging *all* banned users.
2024-08-30 23:08:52 -07:00
avdb13
f03b6cde29 feat: report local users getting banned 2024-08-26 02:17:01 +02:00
Lambda
14afa1357e tracing: allow configuring service name
This is essential when consuming tracing data from multiple servers.
2024-08-24 19:31:39 +00:00
Charles Hall
c355e2ad39
ensure we don't use aliases again accidentally 2024-08-19 10:36:32 -07:00
K900
6685f8cd14
remove uses of aliases 2024-08-17 12:31:48 -07:00
Benjamin Lee
3b99032456
add changelog entry for non-IFD nix build support 2024-08-13 12:12:57 -07:00
Benjamin Lee
e9c0b3ef76
disable IFD in CI
We want to know if future changes break builds without IFD.
2024-08-13 12:12:57 -07:00
Benjamin Lee
992e6b945a
remove usage of IFD in nix packages
Lix builders have IFD disabled, and the changes needed to support this
are minimal.

We do need to do slightly more manual work to keep rust-toolchain.toml
in sync with flake.nix, but it's not significant.
2024-08-13 12:12:57 -07:00
avdb13
b45c0afe37 update changelog 2024-08-12 06:18:28 +02:00
avdb13
00b77144c1 chore: deprecate support for unstable room versions 2024-08-12 06:18:28 +02:00
Benjamin Lee
91739899e7
changelog entry for remote device key query logging 2024-08-11 16:39:08 -07:00
Benjamin Lee
141c60e4a3
log more detailed backoff state for remote device key requests 2024-08-11 16:39:08 -07:00
Benjamin Lee
1551833501
log failed remote device key requests 2024-08-11 16:39:08 -07:00
Benjamin Lee
827f670c54
add changelog entries for the account data validation fixes 2024-08-11 15:53:25 -07:00
Benjamin Lee
a32dc1a3ee
tolerate invalid m.direct events when upgrading rooms
Previous behavior was causing us to error out of the entire
state_cache::update_membership function when it saw an invalid m.direct,
making it impossible for affected users to join upgraded rooms. This bug
is especially bad if an affected user attempts to upgrade a room,
because we will fail to create their join event in the new room, leaving
both rooms permanently bricked.

The new behavior should never prevent users from joining a room based on
the contents of their account data, and should migrate the `m.direct`
event in a reasonable way even if it is partially corrupted by the
element bug.

This also fixes a bug where the previous implementation will
unintentionally remove any keys that aren't part of the expected
m.direct schema. I don't know of any cases where this came up in
practice.

Fixes: #46
2024-08-11 15:53:25 -07:00
Benjamin Lee
18360cd3f9
refactor error handling in copy_upgraded_account_data_tag
The new error handling logic is semantically different from the old, but
I don't know of any cases you could hit in practice where it would
matter.
2024-08-11 15:53:24 -07:00
Benjamin Lee
51dd75c3dc
factor account data upgrade logic into functions
The implementation inside each function is unchanged from the original.
I intend to clean up and fix bugs in later commits, so the diff will be
more clear.
2024-08-11 15:53:24 -07:00
Lambda
dd24a44112 Use TokenSet for roomid_mutex_federation 2024-07-22 18:07:03 +00:00
Lambda
4893c54f4f Use TokenSet for roomid_mutex_insert 2024-07-22 18:07:03 +00:00
Lambda
34ccb2cd06 Use TokenSet for roomid_mutex_state 2024-07-22 18:07:03 +00:00
Lambda
07b5233980 Use OnDemandHashMap for servername_ratelimiter
This way, semaphores are actually cleaned up eventually.
2024-07-22 18:07:03 +00:00
Lambda
2c19abc535 Add OnDemandHashMap and TokenSet 2024-07-22 18:07:00 +00:00
Lambda
aea6019c0a upgrade_room_route: fix state lock
This was using the lock for the replacement room to send events to the
original room, which may or may not cause problems.
2024-07-22 18:06:11 +00:00
Lambda
ef1b6fe111 upgrade_room_route: give more descriptive names to locks
This makes the next commit, which is an actual fix, much easier to read.
2024-07-22 18:06:11 +00:00
tranquillity-codes
c70cfd3d25
Return 504 when a file is missing to be by-spec
The spec defines that the media endpoints should return 504 when a file
is not-yet-uploaded, which has been interpreted to include when a file
was deleted. Modifies the /media/v3/download/ and /media/r0/thumbnail
endpoints.
2024-07-21 20:42:07 -07:00
Charles Hall
bf799c1fa1
update changelog 2024-07-16 11:16:08 -07:00
Charles Hall
96e6ac9563
fix service/rooms/spaces events 2024-07-16 11:16:08 -07:00
Charles Hall
b4d81bb067
fix service/rooms/event_handler events 2024-07-16 11:16:08 -07:00
Charles Hall
4b5d127368
fix service/rooms/state_cache events 2024-07-16 11:16:08 -07:00
Charles Hall
592e42ba7d
fix service/rooms/timeline events 2024-07-16 11:16:08 -07:00
Charles Hall
ee95b6be3c
fix api/client_server/sync events
Some duplicated-ish code from src/database/key_value/rooms/timeline.rs
about handling errors from `pdus_since`/`pdus_until`, it seems like the
error message was actually directly copy-pasted from there because it
referred to the wrong function lol
2024-07-16 11:16:08 -07:00
Charles Hall
dacb2ae7dd
fix api/client_server/context events 2024-07-16 11:12:08 -07:00
Charles Hall
4b9c6a754c
fix api/client_server/room events 2024-07-16 11:12:08 -07:00
Charles Hall
196c923a5a
fix api/client_server/account events 2024-07-16 11:12:08 -07:00
Charles Hall
d2fc2fad1f
fix service/rooms/state events 2024-07-16 11:12:08 -07:00
Charles Hall
1283dc6477
fix service/rooms/state_accessor events 2024-07-16 11:12:08 -07:00
Charles Hall
e7087444db
fix service/sending events 2024-07-16 11:12:08 -07:00
Charles Hall
60b89aba78
fix api/client_server/session events
And also add a new event for logging out to match the one for logging in
because why not.
2024-07-16 11:12:08 -07:00
Charles Hall
0aef00c58b
fix api/client_server/keys events 2024-07-16 11:12:08 -07:00
Charles Hall
42e397203a
fix service/pdu events 2024-07-16 11:12:08 -07:00
Charles Hall
7392880bf8
fix utils/error events 2024-07-16 11:12:08 -07:00
Charles Hall
5a376ceb0a
fix database/key_value/rooms/timeline events 2024-07-16 11:12:08 -07:00
Charles Hall
52decf0cea
fix service/pusher events 2024-07-16 11:12:07 -07:00
Charles Hall
9d8e1a1490
fix api/server_server events 2024-07-16 11:12:07 -07:00
Charles Hall
db666fe903
fix api/client_server/directory events 2024-07-16 11:12:07 -07:00
Charles Hall
b6cba0c4ae
extract closure into a function
This was mostly written by using rust-analyzer's "extract to function"
and "extract to variable" functionality.
2024-07-16 11:12:07 -07:00
Charles Hall
a7cdbab44a
fix database events 2024-07-16 11:12:07 -07:00
Charles Hall
e49fe04f10
fix api/appservice_server events 2024-07-16 11:12:07 -07:00
Charles Hall
162d01f615
fix service/uiaa events 2024-07-16 11:12:07 -07:00
Charles Hall
37beaea48b
fix main events 2024-07-16 11:12:07 -07:00
Charles Hall
f5e10f5a8f
fix api/ruma_wrapper/axum events 2024-07-16 11:12:07 -07:00
Charles Hall
ca4f780c93
fix api/client_server/membership events 2024-07-16 11:12:07 -07:00
Lambda
b05c91b13e
Update changelog for config organization changes
Charles wrote item 12 in the "Added" section.

Co-authored-by: Charles Hall <charles@computer.surgery>
2024-06-27 16:18:21 -07:00
Lambda
f89e1c7dfc
Allow tracing filters to be changed at runtime
ReloadHandle is taken from conduwuit commit
8a5599adf9eafe9111f3d1597f8fb333b8b76849, authored by Benjamin.

Co-authored-by: Benjamin Lee <benjamin@computer.surgery>
2024-06-27 16:04:23 -07:00
Lambda
f576aff7eb
Don't accidentally ignore observability errors 2024-06-27 16:04:23 -07:00
Lambda
5a6e4fac73
Move federation config to separate config section
This renames:

allow_federation -> federation.enable
trusted_servers -> federation.trusted_servers
max_fetch_prev_events -> federation.max_fetch_prev_events
max_concurrent_requests -> federation.max_concurrent_requests
2024-06-27 16:04:23 -07:00
Lambda
e0e7d8fd91
Make observability more configurable
The following are now configurable:

- the OpenTelemetry endpoint,
- the tracing-flame filename, and
- whether the log output should include timestamps (useful to disable if
  it goes straight into journald).
2024-06-27 16:04:23 -07:00
Lambda
df571818f1
Make tracing filters configurable per backend 2024-06-27 16:04:23 -07:00
Lambda
98d49554ce
Move observability config to separate config section
This renames:

allow_prometheus -> observability.metrics.enable
allow_jaeger -> observability.traces.enable
tracing_flame -> observability.flame.enable
log -> observability.logs.filter
log_colors -> observability.logs.colors
log_format -> observability.logs.format

New config values in these sections will follow.
2024-06-27 16:04:23 -07:00
Lambda
8a30817930
config: convert database backend to enum
This reports a nice error when the config is loaded, rather than later
when the database is initialized.
2024-06-27 16:04:23 -07:00
Lambda
d26b87a2f2
Move database config to separate section
This renames:

database_backend -> database.backend
database_path -> database.path
db_cache_capacity_mb -> database.cache_capacity_mb
rocksdb_max_open_files -> database.rocksdb_max_open_files

Charles updated the NixOS module.

Co-authored-by: Charles Hall <charles@computer.surgery>
2024-06-27 16:03:43 -07:00
Lambda
79d5d306cc Move TURN config to separate config section
This renames:

turn_username -> turn.username
turn_password -> turn.password
turn_uris -> turn.uris
turn_secret -> turn.secret
turn_ttl -> turn.ttl
2024-06-27 18:16:37 +00:00
Lambda
e911518aac config: make db_cache_capacity_mb field unconditional
This is also used by sqlite, causing builds with `--no-default-features
--features sqlite` to fail.
2024-06-27 18:16:37 +00:00
Lambda
b171f7c123 config: fix order of items 2024-06-27 18:16:37 +00:00
Benjamin Lee
9c44aa877c
changelog entry for auth chain room id mismatch logging 2024-06-26 09:11:14 -07:00
Benjamin Lee
3ca6d7776b
better log message for auth chain room id mismatch
The previous "Evil event in db" message does not indicate what's going
on, and does not help identify *which* events are causing the problem.
2024-06-26 09:10:58 -07:00
Lambda
b11cbb6991 Instrument rocksdb functions at TRACE level
This allows e.g. aggregate time statistics if you really care about it
by adding grapevine::database::abstraction::rocksdb=trace to the tracing
filter.
2024-06-26 06:11:59 +00:00
Charles Hall
6fb9abed62
update changelog 2024-06-24 12:43:28 -07:00
Charles Hall
027ff907db
fix changelog indentation
Whoops.
2024-06-24 12:43:28 -07:00
Charles Hall
cb036593ea
refactor send_request in api/server_server
Seriously, what is going on with the control flow in this codebase?
2024-06-24 12:43:28 -07:00
Charles Hall
e13db834ed
refactor handle_response in service/sending
Early returns good.
2024-06-24 12:43:28 -07:00
Charles Hall
f2e5b281c9
include method in otel.name for incoming requests
Also change the name of the field from path to endpoint since it's not
the exact request path, and swap the order of method and endpoint.
2024-06-24 12:43:28 -07:00
Charles Hall
1b51e0beec
increase the default log level
The vast majority of spans are at the info level, so increasing the log
level to info will greatly increase the amount of (useful!) inforamtion
included in the logs.

However, `ruma_state_res` generates a substantial amount of logs, so
that one gets to stay fixed at `warn` for now.
2024-06-24 12:43:28 -07:00
Charles Hall
32e6b3b039
don't log twice, make event structured 2024-06-24 12:43:28 -07:00
Charles Hall
e83a30af4b
reduce duplicate events
I hate `log_error`. A better way to do this would be to not reuse the
same error type literally everywhere, so you could distinguish, in
`crate::service::sending::Service::handle_response`, whether to emit an
event based on which function created the error. Fixing that is a lot
more work, though.
2024-06-24 12:40:59 -07:00
Charles Hall
82cc605b5f
reflow macro 2024-06-24 12:40:59 -07:00
Charles Hall
573fac553c
promote request error event from info to warn
And also make it structured.
2024-06-24 12:40:59 -07:00
Charles Hall
230172718f
demote event from info to debug
And also make it structured instead of stringified.
2024-06-24 12:40:59 -07:00
Charles Hall
601c2ed3e5
clean up shutdown events
The shutdown function is called in exactly one place, and that place
has a better log message, so we'll just delete the extra one.
2024-06-24 12:40:59 -07:00
Charles Hall
12b0fb7f91
don't write KBs of html to the logs
Handing this to tracing as a String makes it automatically escape
newlines and such.
2024-06-24 12:40:57 -07:00
Charles Hall
6aca128547
generalize documentation, make the name shorter
It's more useful than just *debug* logs, but yes, should not be relied
upon for logic reasons.
2024-06-24 12:38:34 -07:00
Charles Hall
08cd8f19e3
add and backfill changelog 2024-06-17 16:39:22 -07:00
Charles Hall
d07394a840
add a page about contributing in general
I think it'd also be good to have a document explaining how to set up
and work with the development environment for contributing code, but I
think that should probably live on a separate, dedicated page.
2024-06-17 16:39:22 -07:00
Charles Hall
9517cdc483
add a code of conduct 2024-06-17 16:39:22 -07:00
Charles Hall
cfd3ca0d33
fill in the introduction section 2024-06-17 16:39:22 -07:00
Charles Hall
9a5e552ca0
set up mdbook
This adds some new tools in CI for lint the book and also sets up
automated deployment to GitLab Pages. Also adds a readme that suggests
reading the book, since that's where all the information will be.
2024-06-16 20:23:18 -07:00
Charles Hall
e318dfcb3d
drop oci image outputs
We don't support this anyway. OCI images will be needed for testing with
Complement, but there are a bunch of other special requirements that has
so these oci image outputs won't be very useful for that anyway.
2024-06-14 14:04:46 -07:00
Benjamin Lee
0b5a07d192
add alternate !admin prefix for admin commands
This is supported on conduwuit, and I liked it because it's faster for
me to type than tab-completing the bot user, and because it makes it a
little easier to copy-paste admin commands.
2024-06-14 11:32:52 -07:00
Benjamin Lee
a909e2079b
config options for log format and color usage
We want to be able to disable colors for complement logs (since they
are likely to be opened in a text editor). There's no pressing need for
alternative log formats, but I'm interested in whether the 'pretty'
format will be easier for debugging.

I chose to add 'log_*' options rather than making a separate 'log'
section for now. There's been some discussion about trying to separate
the tracing/logging stuff into more structured sections, but that can
happen later.
2024-06-13 21:21:14 -07:00
Benjamin Lee
4f041f9153
specify listener in error messages and logs
The "listening for incoming traffic on ..." log line is new, but
something I've wanted even when we only supported one listener.

I considered getting rid of `clippy::too_many_lines` by factoring out
the construction of `app` to a separate function, but found that
specifying it's type (or relevant traits) got quite hairy.
2024-06-13 15:49:21 -07:00
Benjamin Lee
f7d7952f9b
allow listening on multiple ports in config
This is a config compatibility break.

The ability to listen on multiple ports, including both TLS and non-TLS,
is necessary for running complement against grapevine.
2024-06-13 13:33:50 -07:00
Benjamin Lee
b7ad00ef6e
distinct error types for running server
This is in preparation for the config change to allow specifying
multiple listeners, which will add several other possible error
conditions.
2024-06-13 13:30:29 -07:00
Charles Hall
76b060aa84
only process admin commands if bot is in room
This isn't exploitable in any way, it just meant that the bot could
receive and run commands from users who were already in the admin room
despite the bot not being in the room, which also means the bot would be
unable to send response messages.

Now, the bot will simply ignore admin room messages if the bot isn't in
the admin room.
2024-06-12 20:48:06 -07:00
Charles Hall
b0d85bb575
use admin_bot_user_id more
Also change some terminology to be less weird.
2024-06-12 20:42:13 -07:00
Charles Hall
d4b5f62bfe
simplify is_admin 2024-06-12 20:42:13 -07:00
Charles Hall
70fa17dde0
enable clap's wrap_help feature
This makes the output easier to read.
2024-06-12 19:57:00 -07:00
Charles Hall
85e77832e9
follow xdg base dirs spec by default 2024-06-12 19:57:00 -07:00
Charles Hall
c46eaed0e0
parse configured EnvFilter once
This allows the error handling to be done upfront instead of for each
use. In particular, the `toml` error now points to the span of text in
the config file where the misconfigured EnvFilter value is. This is much
better than the previous error that did not indicate what was actually
causing it to happen.
2024-06-12 19:56:59 -07:00
Charles Hall
6b819d6f2d
move config loading to config module
This separates concerns a bit more. We will probably want to extend the
logic for config loading in the future, and that stuff should all live
in the relevant place. This change points us in the right direction.
2024-06-12 19:56:59 -07:00
Charles Hall
2b0bc140cf
drop figment
Just deserialize directly via the `toml` crate.
2024-06-12 19:56:59 -07:00
Charles Hall
003c0a4928
drop nested config
This functionality was never actually used AFAICT, as no way to provide
alternate profiles was ever provided.

This changes the configuration format to remove the `[global]` section,
everything that was previously under that namespace is now at the top
level.
2024-06-12 19:56:59 -07:00
Charles Hall
44088852cf
remove show-config admin room command
Just `cat` the config file. Also this code would be very annoying to
maintain. Getting rid of this also revealed that another config option
is specific to RocksDB, so `cfg`s for that have been added.
2024-06-12 19:56:59 -07:00
Charles Hall
a6087e97e1
remove config deprecation functionality
This is a hard fork, we don't need to inherit this cruft. Really, I
should've noticed and removed this closer to the beginning of our
history.
2024-06-12 19:56:59 -07:00
Charles Hall
9a92a8047e
drop support for environment variables
The configuration file is now the canonical way to, well, configure.
This change is desirable because it gives us much more flexibility with
how configuration is structured. Environment variables are insufficient
because, for example, they're a flat namespace and have no built-in way
to represent lists.
2024-06-12 19:56:59 -07:00
Charles Hall
9b115b4110
require config path via cli, remove env var 2024-06-12 19:56:59 -07:00
Charles Hall
3650fde0ac
rename src/{clap -> args}.rs 2024-06-12 19:56:59 -07:00
Charles Hall
b34e001997
remove unused Clone derives 2024-06-12 19:56:59 -07:00
Charles Hall
0643a3f081
remove pointless pub(crate) and use self::
And also fix the ordering of things in state_compressor.
2024-06-12 19:56:56 -07:00
Charles Hall
c7e03a06f7
refuse admin room alias changes unless admin bot
I.e. don't allow the `#admins:example.com` alias to be set or unset by
any user other than `@grapevine:example.com`.
2024-06-12 18:36:55 -07:00
Charles Hall
273ab33809
reintroduce rooms::alias::Serivce struct
We're going to need it.
2024-06-12 16:41:52 -07:00
Charles Hall
339a869872
create admin bot room alias id once and reuse it 2024-06-12 16:30:08 -07:00
Charles Hall
95a24c761d
create admin bot user id once and reuse it
This way we don't need to remember to do the conditional everywhere.
2024-06-12 16:23:22 -07:00
Matthias Ahouansou
9087da91db
fix(keys): only use keys valid at the time of PDU or transaction, and actually refresh keys
Previously, we only fetched keys once, only requesting them again if we have any missing, allowing for ancient keys to be used to sign PDUs and transactions
Now we refresh keys that either have or are about to expire, preventing attacks that make use of leaked private keys of a homeserver
We also ensure that when validating PDUs or transactions, that they are valid at the origin_server_ts or time of us receiving the transaction respectfully
As to not break event authorization for old rooms, we need to keep old keys around
We move verify_keys which we no longer see in direct requests to the origin to old_verify_keys
We keep old_verify_keys indefinitely as mentioned above, as to not break event authorization (at least until a future MSC addresses this)

Original patch by Matthias. Benjamin just rebased it onto grapevine and
fixed clippy/rustc warnings.

Co-authored-by: Benjamin Lee <benjamin@computer.surgery>
2024-06-12 11:10:50 -07:00
Matthias Ahouansou
da99b0706e
fix(edus): ensure sender server is the same as the user in the content
Original patch by Matthias. Benjamin modified the logic to include
logging when an event was rejected, for consistency with the existing
check on device key updates.

Co-authored-by: Benjamin Lee <benjamin@computer.surgery>
2024-06-12 10:36:41 -07:00
Benjamin Lee
83cdc9c708
drop redacted events from search results 2024-06-12 10:32:36 -07:00
Matthias Ahouansou
f74043df9a
fix: de-index pdus when redacted 2024-06-12 10:27:39 -07:00
Benjamin Lee
cc5a9d3440
factor search tokenization out into a function
This ensures that the tokenization algorithm will remain in sync between
querying, indexing, and deindexing. The existing code had slightly
different behavior for querying, because it did not discard words with
>50 bytes. This was inconsequential, because >50 byte tokens are never
present in the index.
2024-06-12 10:26:14 -07:00
Charles Hall
0c2094a56f
record FoundIn with metrics instead of traces
This is much more efficient in terms of network use and data storage,
and also easier to visualize.
2024-06-06 20:56:36 -07:00
Charles Hall
9364d44ce2
use strum instead 2024-06-05 17:41:39 -07:00
Charles Hall
792300d220
derive Copy on FoundIn 2024-06-05 17:41:39 -07:00
Charles Hall
784b86a1f6
make stringified names mirror variant names 2024-06-05 17:41:39 -07:00
Charles Hall
38382128ac
rename FoundIn::{value -> as_str} 2024-06-05 17:41:39 -07:00
Charles Hall
22dd7f1a54
move FoundIn to observability.rs 2024-06-05 17:41:36 -07:00
Charles Hall
0bd2c43dab
don't strip binary for dev builds 2024-06-05 16:42:40 -07:00
Charles Hall
972c25beca
remove unnecessary whitespace 2024-06-05 16:42:16 -07:00
Charles Hall
71f3d84115
rename password-related utils functions 2024-06-04 19:35:25 -07:00
Charles Hall
aa4cd8b1e1
switch to RustCrypto's argon2 crate 2024-06-04 19:35:21 -07:00
Lambda
60d32ddf48
Fix tracing in fetch_signing_keys() 2024-06-04 13:32:32 -07:00
Lambda
7dbae9ac2b
Fix tracing in send_request() 2024-06-04 13:32:32 -07:00
Lambda
c6f75a1b93
Add tracing span for Ar::from_request()
The fact that this is called for every request is somewhat obscured, it
should be obvious in tracing at least.
2024-06-04 13:32:32 -07:00
Lambda
dd6cda4519
Useful span names for incoming HTTP requests
Usually it's not possible to make span names dynamic, but the magic
otel.name attribute changes the span name in the OpenTelemetry tracing
backend.
2024-06-04 13:32:32 -07:00
Lambda
0b9e43cbdd
Record authentication info in incoming requests 2024-06-04 13:32:32 -07:00
Lambda
f7a0e3012b
server_server: log ignored signing key updates 2024-06-04 13:32:32 -07:00
Lambda
88bb2ea600
Remove redundant span attributes
There's no need to record attributes that are already present in all
callers.
2024-06-04 13:32:31 -07:00
Lambda
f35cbfd89e
More tracing spans 2024-06-04 13:32:31 -07:00
Lambda
2e34241465
tracing: add span for globals.watch()
This spawns a ton of futures which are otherwise all flat in the parent.
2024-06-04 13:32:31 -07:00
Lambda
3365608101
tracing: record HTTP request method 2024-06-04 13:32:31 -07:00
Lambda
148df18989
Stop debug-logging every incoming request 2024-06-04 13:32:31 -07:00
Benjamin Lee
3551a6ef7a
fix dropped events in search
The previous code would drop some events entirely if any events between
`skip` and `skip + limit` were not visible to the user. This would cause
the set of events skipped by the `skip(skip)` method to extend past
`skip` in the raw result set, because `skip(skip)` was being called
*after* filtering out invisible events.

This bug will become much more severe with a full filtering
implementation, because it will be more likely for events to be filtered
out. Currently, it is only possible to trigger with rooms that have
history visibility set to "invited" or "joined".
2024-06-04 01:12:53 -07:00
Benjamin Lee
0cdf03288a
fix missing next_batch for search
The previous code would fail to return next_batch if any of the events
in the window were not visible to the user. It would also return an
unnecessary next_batch when no more results are available if the total
number of results is exactly `skip + limit`.

This bug will become much more severe with a full filtering
implementation, because we will be more likely to trigger it by
filtering out events in a search call. Currently, it is only possible to
trigger with rooms that have history visibility set to "invited" or
"joined".
2024-06-04 01:12:53 -07:00
Charles Hall
c1ec1111ae
improve route-not-found tracing event 2024-06-03 18:18:44 -07:00
Charles Hall
04ecf4972e
record histogram of http requests 2024-06-03 18:18:44 -07:00
Charles Hall
a0b92c82e8
set up opentelemetry for metrics
Also adds an `allow_prometheus` option (disabled by default) to expose
a `/metrics` endpoint that returns Prometheus data.
2024-06-03 18:18:44 -07:00
Charles Hall
94fda7c875
improve usage of Resource
* Extract into reusable function (we'll need this later)
* Merge the values we want to override over the defaults, instead of
  dropping the defaults completely
* Don't unnecessarily allocate (the `vec![]` usage is gone)
2024-06-03 18:18:44 -07:00
Benjamin Lee
c64a474954
workaround to fix search in element
We inherited a similar workaround from conduit, but removed it in
71c48f66c4. At the time, it was not clear
that this had broken search.

Fixes: !26
2024-06-03 18:02:41 -07:00
Lambda
5c39c7c5ff Use destination field in X-Matrix Authorization header
Both validating and sending it is a MUST since Matrix v1.3.
2024-06-03 20:50:49 +00:00
Lambda
62dd097f49 Use Ruma XMatrix type instead of rolling our own
Both the hand-rolled parser and serialization were wrong in countless
ways. The current Ruma parser is much better, and the Ruma serialization
will be fixed by https://github.com/ruma/ruma/pull/1830.
2024-06-03 20:50:49 +00:00
Charles Hall
aec314ce85
get tracing-opentelemetry from crates.io
They made a release with the changes we needed; we can stop pulling it
from git now.
2024-06-02 17:27:54 -07:00
Lambda
bf1d54defc axum: factor out non-generic parts of request conversion
This saves ~10% in binary size!
2024-05-31 10:42:47 +00:00
Benjamin Lee
624ff57414
test all-features in CI
This should catch if jemalloc linking is broken, which wouldn't be
caught by the `lints::cargo-clippy/all` task.
2024-05-30 21:19:09 -07:00
Benjamin Lee
3a71b8e5d1
optionally use nom to build devshell dependencies
This is nice when messing around with the nix configuration, since
you'll often end up building rocksdb *many* times.

We shouldn't need to do anything to get the logs in CI because we're
calling `nix-build-and-cache ci` at the beginning
2024-05-30 21:19:09 -07:00
Benjamin Lee
823515b475
load .env file before initializing flake
This allows specifying DIRENV_DEVSHELL in .env
2024-05-30 21:19:09 -07:00
Benjamin Lee
467417c32a
use all-features devshell for clippy/all in CI
Without this, we're building a static rocksdb inside the rust-rocksdb
build script every time. As far as I know this doesn't change clippy's
behavior, but it does take a *long* time.
2024-05-30 21:19:08 -07:00
Benjamin Lee
fa48c48d52
add all-features devshell
Because the nix rocksdb build depends on the jemalloc feature, you need
to use a different devshell when passing --all-features to cargo than
the default.
2024-05-30 21:19:08 -07:00
Benjamin Lee
8f24ac1f27
do default-feature unification in nix
Some of the features affect nix dependencies, so we need to have a
full feature list available when constructing the nix derivation. This
incidentally fixes the bug where we weren't enabling jemalloc on rocksdb
in CI/devshells, because jemalloc is now a default feature. It does not
fix the more general class of that issue, where CI is performing an
`--all-features` build in a nix devshell built for default-features.

I am now passing `--no-default-features` to cargo, and having it use our
unified feature list rather than duplicating the unification inside cargo.
2024-05-30 21:19:08 -07:00
Benjamin Lee
ee43c2ff4c
only link to one jemalloc build
Without setting JEMALLOC_OVERRIDE, we end up linking to two different
jemalloc builds. Once dynamically, as a transitive dependency through
rocksdb, and a second time to the static jemalloc that tikv-jemalloc-sys
builds.

This fixes dynamically-linked jemalloc builds, for the reasons described
in <https://github.com/girlbossceo/conduwuit/pull/400#issue-2316700200>.
2024-05-30 21:19:08 -07:00
Lambda
b6fc9b0feb
service/media: add some tracing 2024-05-30 15:40:28 -07:00
Lambda
0a92c72566
Put thumbnail creation inside spawn_blocking()
This can take milliseconds or even several seconds for huge inputs,
while the rule of thumb is <100us between await points.
2024-05-30 15:40:28 -07:00
Lambda
c973485c73
service/media: refactor to reduce indentation 2024-05-30 15:40:24 -07:00
Benjamin Lee
ec1b086a35
very minor cleanup in the sync endpoint
I meant to do this in 146465693e, but
looks like I forgot.
2024-05-30 10:19:24 -07:00
Benjamin Lee
72962c6402
enable allow_federation config by default
We inherited the disabled-by-default setting from conduit. Conduwuit
change it to enabled-by-default in [1]. This can make things confusing
for users migrating from conduwuit to grapevine, especially since we
currently do not log a warning when federation is disabled.

[1]: 24605e151d
2024-05-30 09:30:13 -07:00
Lambda
12e7f525aa Clean up and deduplicate parse_servercurrentevent() 2024-05-29 20:42:59 +00:00
Lambda
25353da8b8 sending.rs: record handler errors using Display 2024-05-29 20:42:46 +00:00
Lambda
fb52ded222 sending.rs: try to preserve requester spans
Add a `follows_from` relationship to the dispatched worker's span
pointing to the caller of `send_*()`, if known.
2024-05-29 20:42:46 +00:00
Lambda
18992b4d1d sending.rs: rename OutgoingKind to Destination
That's what it is. It describes the destination of the event.
2024-05-29 20:42:45 +00:00
Lambda
9071e11e06 sending.rs: move handler functions out of service
These don't take `self`, no reason for them to be associated functions.
2024-05-29 20:42:14 +00:00
Lambda
13f79dfee1 sending.rs: factor out event handlers into separate functions
This allows for much nicer control flow, since they don't need to return
Result<OutgoingKind, (OutgoingKind, Error)>.
2024-05-29 20:42:14 +00:00
Lambda
5caafdec06 sending.rs: formatting 2024-05-29 20:42:14 +00:00
Lambda
aa69b7d15c sending.rs: rename handle_futures->handle_response
The future is already done when this is called.
2024-05-29 20:42:14 +00:00
Lambda
9961f1465f sending.rs: add RequestData to replace big tuple 2024-05-29 20:42:14 +00:00
Lambda
e294543ddb sending.rs: add RequestKey
Much easier to reason about than with a bunch of Vec<u8> everywhere.
2024-05-29 20:42:13 +00:00
Charles Hall
507de063f5
don't hardcode EnvFilter for tracing-flame 2024-05-27 10:06:26 -07:00
Lambda
7a154f7416
observability: allow combining multiple tracing layers
Previously, jaeger, tracing_flame and stderr output were mutually
exclusive. There's no good reason for this.
2024-05-27 10:03:44 -07:00
Lambda
263edcc8a1
observability: don't drop tracing_flame drop guard until exit 2024-05-27 10:02:42 -07:00
Charles Hall
c17ab5328d
factor observability things into its own module
And also create a new error for the observability initialization.
2024-05-27 10:01:00 -07:00
Charles Hall
a275543494
enable axum's tracing feature
This pretty much only makes it log a single error event, but it's one
that's useful to know about.
2024-05-26 19:47:00 -07:00
Charles Hall
8f0fdfb2f2
upgrade all cargo dependencies
Unfortunately we need to pull tracing-opentelemetry from git because
there hasn't been a release including the dependency bump on the other
opentelemetry crates.
2024-05-26 19:47:00 -07:00
Charles Hall
d5da913c79
pin nixos/nix to 2.18.2
Not sure if Lix has Docker images we can use yet, but we can at least do
this for now.
2024-05-26 19:47:00 -07:00
Charles Hall
13de9ecd41
enable unnecessary_to_owned lint
*facepalm*
2024-05-26 19:47:00 -07:00
Charles Hall
7e7911abcf
enable thread_local_initializer_can_be_made_const
There's barely enough room in the commit title for this lint's name...
2024-05-26 19:47:00 -07:00
Charles Hall
3daf2229d6
enable option_as_ref_cloned lint 2024-05-26 19:47:00 -07:00
Charles Hall
eaeb7620d9
enable multiple_bound_locations lint 2024-05-26 19:47:00 -07:00
Charles Hall
92d9f81a78
enable mixed_attributes_style lint 2024-05-26 19:47:00 -07:00
Charles Hall
41a5e6fb50
enable manual_is_variant_and lint 2024-05-26 19:47:00 -07:00
Charles Hall
5048af3a8f
enable doc_markdown lint 2024-05-26 19:47:00 -07:00
Charles Hall
c9859a9b2d
enable assigning_clones lint 2024-05-26 19:47:00 -07:00
Charles Hall
793d809ac6
enable unused_qualifications lint 2024-05-26 19:47:00 -07:00
Charles Hall
d7e945f4c5
enable dead_code lint 2024-05-26 19:46:48 -07:00
Charles Hall
518d0c9cf3
update the rust toolchain, ignore new lints
I will fix them in follow-up commits. Also apparently rustc doesn't warn
you when you pass it lints that don't exist (yet) so that's annoying.
2024-05-24 20:09:41 -07:00
Charles Hall
82c37f2983
remove stale reference
This should've been done in d5a9c6ac32,
my bad.
2024-05-24 20:09:41 -07:00
Charles Hall
b5321d81c0
update flake.lock
Nixpkgs and `rust-rocksdb` line up so we can drop our RocksDB input for
the time being.

Flake lock file updates:

• Updated input 'attic':
    'github:zhaofengli/attic/6eabc3f02fae3683bffab483e614bebfcd476b21' (2024-02-14)
  → 'github:zhaofengli/attic/4dbdbee45728d8ce5788db6461aaaa89d98081f0' (2024-03-29)
• Updated input 'attic/nixpkgs':
    'github:NixOS/nixpkgs/aa9d4729cbc99dabacb50e3994dcefb3ea0f7447' (2023-12-14)
  → 'github:NixOS/nixpkgs/07262b18b97000d16a4bdb003418bd2fb067a932' (2024-03-25)
• Updated input 'attic/nixpkgs-stable':
    'github:NixOS/nixpkgs/1e2e384c5b7c50dbf8e9c441a9e58d85f408b01f' (2023-12-17)
  → 'github:NixOS/nixpkgs/44733514b72e732bd49f5511bd0203dea9b9a434' (2024-03-26)
• Updated input 'crane':
    'github:ipetkov/crane/55f4939ac59ff8f89c6a4029730a2d49ea09105f' (2024-04-21)
  → 'github:ipetkov/crane/109987da061a1bf452f435f1653c47511587d919' (2024-05-24)
• Updated input 'fenix':
    'github:nix-community/fenix/c8943ea9e98d41325ff57d4ec14736d330b321b2' (2024-03-05)
  → 'github:nix-community/fenix/b6fc5035b28e36a98370d0eac44f4ef3fd323df6' (2024-05-22)
• Updated input 'fenix/rust-analyzer-src':
    'github:rust-lang/rust-analyzer/9f14343f9ee24f53f17492c5f9b653427e2ad15e' (2024-03-04)
  → 'github:rust-lang/rust-analyzer/21ec8f523812b88418b2bfc64240c62b3dd967bd' (2024-05-19)
• Updated input 'flake-utils':
    'github:numtide/flake-utils/d465f4819400de7c8d874d50b982301f28a84605' (2024-02-28)
  → 'github:numtide/flake-utils/b1d9ab70662946ef0850d488da1c9019f3a9752a' (2024-03-11)
• Updated input 'nix-filter':
    'github:numtide/nix-filter/3449dc925982ad46246cfc36469baf66e1b64f17' (2024-01-15)
  → 'github:numtide/nix-filter/3342559a24e85fc164b295c3444e8a139924675b' (2024-03-11)
• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/b8697e57f10292a6165a20f03d2f42920dfaf973' (2024-03-03)
  → 'github:NixOS/nixpkgs/5710852ba686cc1fd0d3b8e22b3117d43ba374c2' (2024-05-21)
• Removed input 'rocksdb'
2024-05-24 20:08:54 -07:00
Lambda
67cb6f817d Instrument caches 2024-05-22 20:10:42 +00:00
Lambda
62bff27d50 Remove unused cache 2024-05-22 20:08:43 +00:00
Charles Hall
6e2eec012f
special case userroomid_highlightcount
This fixes the panic on startup with a fresh database.
2024-05-21 17:56:56 -07:00
Charles Hall
b3709f262e
panic when a column family would be created twice
`create_cf` already fails when the column family already exists, but
this gives us a much better error message; namely, it tells us what
column family name is at fault.
2024-05-21 17:48:33 -07:00
Charles Hall
53fbd3fc41
store old_cfs as a HashSet
This list can't have duplicates.
2024-05-21 17:25:23 -07:00
Charles Hall
a60a9551e1
Revert "Merge branch 'check-if-membership-is-case-endpoints' into 'next'"
This reverts commit 7ace9b0dff, reversing
changes made to 624654a88b.
2024-05-21 16:34:26 -07:00
Charles Hall
49660b9e39
return a concrete error type from try_main
Also adds a utility for formatting an error message in addition to all
of its sources, i.e. errors that came before it that led to the current
error.

This helps us to provide better error messages to users by including
more information: both our own error message and all of the underlying
error messages, if any, instead of only one or the other.
2024-05-20 14:51:30 -07:00
Charles Hall
4407e15d78
reword error message about GRAPEVINE_CONFIG
The new message more accurately reflects the purpose and behaviors of
the environment variable.
2024-05-20 14:47:38 -07:00
Charles Hall
23f99015df
return ExitCode instead of using panic or exit
`panic!()` (and things that invoke it, such as `expect` and `unwrap`)
produces terrible looking error messages and `std::process::exit()`
doesn't run destructors. Instead, we'll make a `try_main` that can
return a `Result` with a structured error type, but for now I'm going to
be lazy and just use `Box<dyn Error>`. Then, `main` will call it and
return the appropriate `ExitCode` value based on `try_main`'s `Result`.
This gives us the opportunity to produce good error messages and doesn't
violently terminate the program.
2024-05-20 14:47:34 -07:00
Lambda
edfccea30a Convert giant tuple in state_compressor to struct 2024-05-20 21:27:57 +00:00
Benjamin Lee
8d09a7e490 don't return extra member count or e2ee device updates from sync
Previously, we were returning redundant member count updates or encrypted
device updates from the /sync endpoint in some cases. The extra member
count updates are spec-compliant, but unnecessary, while the extra
encrypted device updates violate the spec.

The refactor necessary to fix this bug is also necessary to support
filtering on state events in sync.

Details:

Joined room incremental sync needs to examine state events for four
purposes:

 1. determining whether we need to return an update to room member counts
 2. determining the set of left/joined devices for encrypted rooms
    (returned in `device_lists`)
 3. returning state events to the client (in `rooms.joined.*.state`)
 4. tracking which member events we have sent to the client, so they can
    be omitted on future requests when lazy-loading is enabled.

The state events that we need to examine for the first two cases is member
events in the delta between `since` and the end of `timeline`. For the
second two cases, we need the delta between `since` and the start of
`timeline`, plus contextual member events for any senders that occur in
`timeline`. The second list is subject to filtering, while the first is
not.

Before this change, we were using the same set of state events that we are
returning to the client (cases 3/4) to do the analysis for cases 1/2.
In a compliant implementation, this would result in us missing some
relevant member events in 1/2 in addition to seeing redundant member
events. In current grapevine this is not the case because the set of
events that we return to the client is always a superset of the set that
is needed for cases 1/2. This is because we don't support filtering, and
we have an existing bug[1] where we are returning the delta between
`since` and the end of `timeline` rather than the start.

[1]: https://gitlab.computer.surgery/matrix/grapevine-fork/-/issues/5

Fixing this is necessary to implement filtering because otherwise
we would start missing some member events for member count or encrypted
device updates if the relevant member events are rejected by the filter.
This would be much worse than our current behavior.
2024-05-20 21:13:13 +00:00
Lambda
5172f66c1a More useful tracing spans 2024-05-20 16:49:45 +00:00
Lambda
c0ce2ebbf8 Add utility function for truncating strings in logging 2024-05-20 16:49:45 +00:00
Lambda
9dbc7d92e2 utils: add helper for adding unbounded slices to tracing spans 2024-05-20 16:49:42 +00:00
Lambda
5a178ba393 Add comment to task spawner middleware 2024-05-20 16:49:40 +00:00
Lambda
ac42e0bfff Fix spans in tokio::spawn-ed tasks
tokio::spawn is a span boundary, the spawned future has no parent span.

For short futures, we simply inherit the current span with
`.in_current_span()`.

For long running futures containing a sleeping infinite loop, we don't
actually want a span on the entire task or even the entire loop body,
both would result in very long spans. Instead, we put the outermost span
(created using #[tracing::instrument] or .instrument()) around the
actual work happening after the sleep, which results in a new root span
being created after every sleep.
2024-05-20 16:49:40 +00:00
Lambda
5e9e5b76bc service/sending: factor out closures into methods 2024-05-20 16:49:40 +00:00
Lambda
092315e2cd remove unnecessary async and select!{} 2024-05-20 16:49:20 +00:00
Charles Hall
a60501189d
prevent xss via user-uploaded media
Previously, `Content-Disposition` was always set to `inline`, even for
HTML, which means that XSS could be easily acheived by uploading
malicious HTML and getting someone to click on the Matrix HTTP API link
for that piece of media. Now, we have an allowlist of safe values for
`Content-Type` that use `inline` while everything else defaults to
`attachment`, including HTML and SVG, which prevents XSS.

We also set the `Content-Security-Policy` header because why not.

A `set_header_or_panic` function is introduced to do what it says in
case Ruma begins providing better or worse values for the relevant
headers in the future. The safest way to handle such a case is simply
to panic.
2024-05-19 21:05:02 -07:00
Charles Hall
6024f866e3
fix mod/use order 2024-05-19 19:04:23 -07:00
Charles Hall
f8961d5578
rename Ruma to Ar
This follows the pattern of the previous commit.
2024-05-19 19:04:20 -07:00
Charles Hall
7ea98dac72
rename RumaResponse to Ra
It's very commonly used so having a short name is worthwhile, I think.
2024-05-19 19:03:45 -07:00
Charles Hall
230ebd3884
don't automatically wrap in RumaResponse
This allows us to use the `ruma_route` convenience function even when we
need to add our own hacks into the responses, thus making us less
reliant on Ruma.
2024-05-18 18:31:36 -07:00
Charles Hall
87ac0e2a38
don't log that federation is disabled
This mostly just spams the logs with useless information when doing
cursed local testing.
2024-05-16 22:33:37 -07:00
Charles Hall
93a2bf9c93
change FedDest::{into -> to}_*
They don't need to take ownership of `self`.
2024-05-16 22:15:06 -07:00
Charles Hall
e7b8f78867
update rocksdb
Flake lock file updates:

• Updated input 'rocksdb':
    'github:facebook/rocksdb/bcf88d48ce8aa8b536aee4dd305533b3b83cf435' (2024-04-16)
  → 'github:facebook/rocksdb/6f7cabeac80a3a6150be2c8a8369fcecb107bf43' (2024-04-22)
2024-05-16 21:50:31 -07:00
Benjamin Lee
146465693e
remove sync response cache
This cache can serve invalid responses, and has an extremely low hit
rate.

It serves invalid responses because because it's only keyed off
the `since` parameter, but many of the other request parameters also
affect the response or it's side effects. This will become worse once we
implement filtering, because there will be a wider space of parameters
with different responses. This problem is fixable, but not worth it
because of the low hit rate.

The low hit rate is because normal clients will always issue the next
sync request with `since` set to the `prev_batch` value of the previous
response. The only time we expect to see multiple requests with the same
`since` is when the response is empty, but we don't cache empty
responses.

This was confirmed experimentally by logging cache hits and misses over
15 minutes with a wide variety of clients. This test was run on
matrix.computer.surgery, which has only a few active users, but a
large volume of sync traffic from many rooms. Over the test period, we
had 3 hits and 5309 misses. All hits occurred in the first minute, so I
suspect that they had something to do with client recovery from an
offline state. The clients that were connected during the test are:

 - element web
 - schildichat web
 - iamb
 - gomuks
 - nheko
 - fractal
 - fluffychat web
 - fluffychat android
 - cinny web
 - element android
 - element X android

Fixes: #2
2024-05-16 21:33:06 -07:00
Charles Hall
5cb2551422
enable error_on_line_overflow and fix errors
These required some manual intervention.
2024-05-16 19:11:40 -07:00
Charles Hall
0afc1d2f50
change rustfmt configuration
This change is fully automated, except the `rustfmt.toml` changes and
a few clippy directives to allow specific functions with too many lines
because they are longer now.
2024-05-16 19:11:40 -07:00
Charles Hall
40d6ce230d
reformat report formatting
I manually expanded the HTML into a more readable format, the rest was
rustfmt's doing. It's beyond me why/how someone would willing write a
pile of HTML like that...
2024-05-16 19:10:52 -07:00
Charles Hall
6bc17b268c
factor select bodies into closures
I would've preferred to factor these out into their own functions, but
unfortunately the inner type of the `FuturesUnordered` is unnameable.
`Box` or TAIT would help, but the former has a performance cost and the
latter doesn't exist on stable yet.
2024-05-16 19:09:57 -07:00
Charles Hall
ac53948450
use more, qualify less
Doing this will allow `rustfmt` to collapse lines more efficiently.
Specifically, a lot of these lines fail to wrap to 80 columns without
these changes.
2024-05-16 19:09:10 -07:00
Charles Hall
dc096d7ad7
abbreviate api categories
This reduces the amount of characters on each line.
2024-05-16 16:55:33 -07:00
Charles Hall
592dee368d
replace comment on services with a doc comment
The previous comment is pretty self-evident and also misunderstands
async closures.
2024-05-16 16:55:06 -07:00
Charles Hall
94978b95a6
fix references in doc comments
At least, this is all I've found so far.
2024-05-16 16:43:24 -07:00
Charles Hall
04184c6137
use gender-neutral pronouns 2024-05-16 16:17:40 -07:00
Charles Hall
05be778fbb
stop putting comments in the middle of call chains
`rustfmt` doesn't handle this very well.
2024-05-16 16:17:40 -07:00
Charles Hall
1911ad34d9
stop putting comments and code on the same line 2024-05-16 15:22:35 -07:00
Charles Hall
0915aba44c
remove commented-out code 2024-05-16 15:20:56 -07:00
Charles Hall
d3b7eecaed
swap commented code for prose about "ownership"
This more clearly communicates the purpose of the comments, and thus
also the order of fields in the struct.
2024-05-16 15:20:55 -07:00
Charles Hall
f8420883a1
expand abbreviation for clarity
This looked like a typo before.
2024-05-16 15:20:26 -07:00
Charles Hall
de662a88f5
clarify the meaning of this comment 2024-05-16 01:08:48 -07:00
Charles Hall
bac8b34faf
clarify comments about lazy loading 2024-05-16 01:08:48 -07:00
Charles Hall
3efe3fb337
remove comments about filtering buggy items 2024-05-16 01:08:48 -07:00
Charles Hall
40f9aa6f60
remove stale comments
Should've been removed in 98f1480e2b.
2024-05-15 23:45:54 -07:00
Charles Hall
034169bb8a
remove obvious comments 2024-05-15 16:43:52 -07:00
Charles Hall
f9f066417b
remove pointless else branch 2024-05-15 16:43:52 -07:00
Charles Hall
46731d1f85
how did clippy not catch this 2024-05-15 15:54:12 -07:00
Lambda
4aab4c9b2e
service/appservice: fix weird indirection through global services 2024-05-14 20:21:51 -07:00
Lambda
ad7a5ea777
Remove useless wrapper Services 2024-05-14 20:21:51 -07:00
Lambda
87a1012ab5
Replace Box::into_pin(Box::new( with Box::pin( 2024-05-14 20:21:51 -07:00
Charles Hall
509b70bd82
use the version function for s2s API too
This way the extra version information can appear there as well. Mainly
useful for showing off :P
2024-05-14 20:21:51 -07:00
Charles Hall
a25f2ec950
add conduit compat mode
This makes it possible to deploy Grapevine while using a database
originally created by Conduit, including leaving the admin bot user's
localpart the same as before.
2024-05-14 20:21:51 -07:00
Charles Hall
33e7a46b53
add a nixos module 2024-05-14 20:21:51 -07:00
Charles Hall
17eb354590
prevent bindgen and dependents from building twice
See also <https://crane.dev/faq/rebuilds-bindgen.html>.
2024-05-14 20:21:51 -07:00
Charles Hall
21a4b9e5a1
only set GRAPEVINE_VERSION_EXTRA for final build
This prevents us from needing to recompile the dependencies when that
environment variable changes, which generally changes on every commit,
which is far more frequently than the dependencies are actually changed.
2024-05-14 20:21:51 -07:00
Charles Hall
bbb1a6fea4
make it easy to configure cargo profiles from nix
This way you can easily build in debug mode with Nix.
2024-05-14 20:21:51 -07:00
Charles Hall
51f9650ca7
make it easy to configure cargo features from nix
Users of the nix package can now just use `.override` to choose what
features they want.

This also makes RocksDB automatically use jemalloc when Grapevine is
configured to use jemalloc.
2024-05-14 20:21:51 -07:00
Charles Hall
09751227ea
get rocksdb via flake inputs
Flake lock file updates:

• Added input 'rocksdb':
    'github:facebook/rocksdb/bcf88d48ce8aa8b536aee4dd305533b3b83cf435' (2024-04-16)
2024-05-14 20:21:51 -07:00
Charles Hall
0f8d1a5ed7
factor out nix code into new files via makeScope
This makes the Nix code a lot easier to reason about.
2024-05-14 20:21:51 -07:00
Charles Hall
8d8d4425f3
always go through inputs
This way we don't have to modify the destructuring of `outputs`'
argument when adding or removing inputs.
2024-05-14 20:08:37 -07:00
Charles Hall
238bc85b1d
sort flake inputs 2024-05-14 20:08:37 -07:00
Charles Hall
17cb7732bd
flatten flake inputs 2024-05-14 20:08:37 -07:00
Charles Hall
81d85153e9
specify ref for all flake inputs 2024-05-14 20:08:37 -07:00
Charles Hall
b7df5a7da6
allow loading env vars from .env if it exists
This is primarily useful for replicating the environment from CI so that
the `nix-build-and-cache` script is easier to invoke.
2024-05-14 20:08:37 -07:00
Charles Hall
ca03722072
build and cache all packages and CI dependencies
This fixes the problem where some artifacts were not being cached when
they should have been. The secret sauce is the  `nix-store` command.

Also stops emitting artifacts to GitLab. Automatic build scheduling via
Nix is too convenient. Maybe I'll figure out a way to do both later on.

Also pins the remaining unpinned dependencies, namely direnv and
nix-direnv.
2024-05-14 20:08:37 -07:00
Charles Hall
ce5ce60dd9
use nix-output-monitor if available 2024-05-14 20:08:37 -07:00
Charles Hall
f205280520
use the intended default port by default
Why was it not like this before? All the documentation and examples used
this as the default port.
2024-05-14 20:08:37 -07:00
Charles Hall
e3c57fa83d
enable most pedantic lints as a group
I'm turning off the documentation related ones because they generate
way too many warnings, this kind of thing will need to be improved over
a longer timespan.
2024-05-14 20:01:25 -07:00
Charles Hall
a569bf8d99
enable zero_sized_map_values lint 2024-05-14 20:01:25 -07:00
Charles Hall
44b15dcb08
enable wildcard_imports lint 2024-05-14 20:01:25 -07:00
Charles Hall
3978b9c580
enable used_underscore_binding lint 2024-05-14 20:01:25 -07:00
Charles Hall
e3672eb4e0
enable unused_self lint
Functions using `services()` are allowed to pointlessly take `self`
because the existence of `services()` is a crime and the solution is
making the types store references to their dependencies and then going
through `self`, so just allowing the lint saves us from modifying some
code only to switch it back later. Much later. Getting rid of
`services()` will probably be an ordeal.
2024-05-14 20:01:25 -07:00
Charles Hall
f855bd09d1
enable unused_async lint
This also caused a "new" `unnecessary_wraps` lint to fire too, so that
got fixed too.
2024-05-14 20:01:25 -07:00
Charles Hall
4419e855ae
enable unreadable_literal lint 2024-05-14 20:01:25 -07:00
Charles Hall
a4ff32667e
enable unnested_or_patterns lint 2024-05-14 20:01:25 -07:00
Charles Hall
4e80dc028e
enable unnecessary_wraps lint 2024-05-14 20:01:25 -07:00
Charles Hall
86218f4771
enable uninlined_format_args lint 2024-05-14 20:01:24 -07:00
Charles Hall
baab928281
enable too_many_lines lint
And just disable it everywhere it fires, I know.
2024-05-14 20:01:24 -07:00
Charles Hall
8ef278d358
enable struct_excessive_bools lint
And ignore it in the one place it fires, I know.
2024-05-14 20:01:24 -07:00
Charles Hall
75358340bb
enable single_match_else lint
Also collapses some if statements in the federation name resolution code
2024-05-14 20:01:24 -07:00
Charles Hall
a62fa7b6ee
enable similar_names lint 2024-05-14 20:01:24 -07:00
Charles Hall
db4951c5fd
enable semicolon_if_nothing_returned lint 2024-05-14 20:01:24 -07:00
Charles Hall
96e1877639
enable redundant_closure_for_method_calls lint 2024-05-14 20:01:24 -07:00
Charles Hall
a636405bed
enable needless_pass_by_value lint 2024-05-14 20:01:24 -07:00
Charles Hall
c4a9bca16f
enable match_wildcard_for_single_variants lint 2024-05-14 20:01:24 -07:00
Charles Hall
2b8b5ccb1a
enable match_same_arms lint 2024-05-14 20:01:24 -07:00
Charles Hall
4e6c8451ca
enable match_bool lint 2024-05-14 20:01:24 -07:00
Charles Hall
224ba65d06
enable map_unwrap_or lint 2024-05-14 20:01:24 -07:00
Charles Hall
645d88177a
enable manual_string_new lint 2024-05-14 20:01:24 -07:00
Charles Hall
9606f59141
enable manual_let_else lint 2024-05-14 20:01:23 -07:00
Charles Hall
c51e87ec9a
enable items_after_statements lint 2024-05-14 19:59:43 -07:00
Charles Hall
5c9967f89c
enable inconsistent_struct_constructor lint 2024-05-14 19:59:43 -07:00
Charles Hall
ebae8ceeb0
enable implicit_clone lint 2024-05-14 19:59:43 -07:00
Charles Hall
b0f65913f3
enable ignored_unit_patterns lint 2024-05-14 19:59:43 -07:00
Charles Hall
623824dc0c
enable if_not_else lint 2024-05-14 19:59:40 -07:00
Charles Hall
a32b7c1ac1
enable flat_map_option lint 2024-05-14 16:41:04 -07:00
Charles Hall
703e08947f
enable explicit_into_iter_loop lint 2024-05-14 16:38:21 -07:00
Charles Hall
cb2cd6e253
enable enum_glob_use lint 2024-05-14 16:36:45 -07:00
Charles Hall
da440934bd
enable doc_markdown lint 2024-05-14 16:34:10 -07:00
Charles Hall
0f2cf26a36
enable default_trait_access lint 2024-05-14 16:08:26 -07:00
Charles Hall
da842c6499
enable unneeded_field_pattern lint 2024-05-12 19:14:59 -07:00
Charles Hall
2b6a933538
enable undocumented_unsafe_blocks lint
There was only one unsafe block (thankfully) but it also had no docs.
I did some reading and found out this in fact safe, but only for cursed
reasons, and documented them. Also, the name of the type was misleading,
as the entire point is the aliasing, and `Box` is already non-aliasing.
2024-05-12 19:10:31 -07:00
Charles Hall
844b32f097
enable string_slice lint
Also swaps out vendored HTML-escaping code for a dependency that I
imagine has decent testing considering all of its reverse depedencies.
2024-05-12 19:08:12 -07:00
Charles Hall
9abe4799db
enable string_add lint 2024-05-12 19:01:29 -07:00
Charles Hall
cc5977b4e4
enable same_name_method lint 2024-05-12 18:51:48 -07:00
Charles Hall
d144db8688
enable ref_patterns lint 2024-05-12 18:51:27 -07:00
Charles Hall
6bdaeab1af
enable multiple_inherent_impl lint 2024-05-12 18:51:27 -07:00
Charles Hall
4a7b9c16cc
enable missing_assert_message lint 2024-05-12 18:51:27 -07:00
Charles Hall
052f3088e9
enable let_underscore_must_use lint 2024-05-12 18:51:26 -07:00
Charles Hall
2ded335adb
enable impl_trait_in_params lint
See also <https://github.com/rust-lang/rust-clippy/issues/12792>.
2024-05-12 18:51:26 -07:00
Charles Hall
52c2893073
enable if_then_some_else_none lint 2024-05-12 18:51:26 -07:00
Charles Hall
bd6ea4e358
enable format_push_string lint 2024-05-12 18:51:26 -07:00
Charles Hall
5fd156a6bb
enable error_impl_error lint
Except the 1 violation would have to be renamed in, like, every single
file in this project. So we're just enabling it so that we don't make
the same mistake in the future.
2024-05-12 18:51:26 -07:00
Charles Hall
a127253daa
enable empty_struct_with_brackets lint 2024-05-12 18:51:26 -07:00
Charles Hall
885fc8428c
enable deref_by_slicing lint 2024-05-12 18:51:26 -07:00
Charles Hall
71c48f66c4
enable as_conversions lint
There were some very, uh, creative (and inconsistent) ways to convert
between numeric types in here...
2024-05-12 18:51:26 -07:00
Charles Hall
a78bf8f50b
enable unused_lifetimes lint 2024-05-12 18:51:26 -07:00
Charles Hall
5d5e28770b
enable single_use_lifetimes lint 2024-05-12 18:51:26 -07:00
Charles Hall
bc2f7c6826
enable enum_variant_names lint 2024-05-12 18:51:26 -07:00
Charles Hall
2ff08c9fc4
enable dead_code lint
And delete all the dead code. And add some cfgs for feature-specific
items.
2024-05-12 18:51:26 -07:00
Charles Hall
d748544f0e
enable unreachable_pub lint
This causes some other lints to start firing too (which is good), but
I'm going to fix them in follow-up commits to keep things organized.
2024-05-12 18:51:26 -07:00
Charles Hall
a626e7b0f0
enable mod_module_files lint 2024-05-12 18:51:26 -07:00
Charles Hall
242d9e84cc
enable more lints that aren't already violated
These are good lints to have and we want them enabled, and luckily no
existing code needs to be modified to keep the linters happy for these.
2024-05-12 18:51:12 -07:00
Charles Hall
806fabc4c7
run clippy for no, default, and all features
This should be good enough for now.
2024-04-30 21:54:56 -07:00
Charles Hall
d21221d54e
remove backend_* features that do nothing 2024-04-30 21:54:56 -07:00
Charles Hall
c0972c2b57
use dep: syntax in cargo features 2024-04-30 21:54:56 -07:00
Charles Hall
daf7b1e678
sort keys in Cargo.toml 2024-04-30 21:54:56 -07:00
Charles Hall
c27fc4e177
convert license to markdown 2024-04-30 21:54:56 -07:00
Charles Hall
f437a9ab88
add gitignore 2024-04-30 21:54:56 -07:00
Charles Hall
360e020b64
rename conduit to grapevine 2024-04-30 21:54:56 -07:00
Charles Hall
5619d7e318
remove program name from config option
This is technically a breaking change but this is really silly, I can't
not get rid of this.
2024-04-30 21:54:56 -07:00
Charles Hall
d41f0fbf72
remove documentation, metadata, etc
This drastically reduces the maintenance burden.

You may notice the `CODE_OF_CONDUCT.md` is deleted here. This is because
I don't feel like updating the relevant parts and I don't think this
will ever actually have a community build around it. If that changes for
some reason, I'm not opposed to adding a code of conduct again.
2024-04-30 21:54:55 -07:00
Charles Hall
c496878afa
remove unused dependencies 2024-04-30 21:54:55 -07:00
Charles Hall
c097e79e52
remove src/lib.rs 2024-04-30 21:54:55 -07:00
Charles Hall
0f7b6d482c
remove conduit_bin feature 2024-04-30 21:54:55 -07:00
Charles Hall
f27941d510
remove half-baked presence implementation
But I'm leaving behind the database state for now in case we want it
back later, so we won't need to do a migration or whatever.
2024-04-30 21:54:55 -07:00
Charles Hall
c765a1634d
remove unused database backends 2024-04-30 21:54:39 -07:00
Charles Hall
aa51acf152
remove dead code 2024-04-30 00:08:26 -07:00
Charles Hall
c9945f6bba
remove welcome message 2024-04-30 00:08:26 -07:00
Charles Hall
cddf6991f2
remove optional automatic display name emoji 2024-04-30 00:08:07 -07:00
Charles Hall
17a0b34309
remove version checker
This reverts commit b8c164dc60.
2024-04-29 22:18:28 -07:00
311 changed files with 28130 additions and 23725 deletions

2
.cargo/config.toml Normal file
View file

@ -0,0 +1,2 @@
[env]
RUMA_UNSTABLE_EXHAUSTIVE_TYPES = "1"

View file

@ -1,28 +0,0 @@
# Local build and dev artifacts
target
tests
# Docker files
Dockerfile*
docker-compose*
# IDE files
.vscode
.idea
*.iml
# Git folder
.git
.gitea
.gitlab
.github
# Dot files
.env
.gitignore
# Toml files
rustfmt.toml
# Documentation
#*.md

View file

@ -1,15 +0,0 @@
# EditorConfig is awesome: https://EditorConfig.org
root = true
[*]
charset = utf-8
end_of_line = lf
tab_width = 4
indent_size = 4
indent_style = space
insert_final_newline = true
max_line_length = 120
[*.nix]
indent_size = 2

14
.envrc
View file

@ -1,5 +1,17 @@
#!/usr/bin/env bash
use flake
dotenv_if_exists
system="$(nix eval --impure --raw --expr 'builtins.currentSystem')"
devshell="${DIRENV_DEVSHELL:-default}"
if command -v nom &> /dev/null && [ -t 0 ]; then
# if nom is available, build the devshell dependencies with it to get nicer
# progress monitoring. Don't do this when stdout is piped, because it shows
# up weird in engage.
nom build ".#devShells.$system.$devshell"
fi
use flake ".#$devshell"
PATH_add bin

View file

@ -1 +0,0 @@
- [ ] I agree to release my code and all other changes of this PR under the Apache-2.0 license

View file

@ -1,11 +0,0 @@
---
name: "Issue with / Feature Request for Conduit"
about: "Please file issues on GitLab: https://gitlab.com/famedly/conduit/-/issues/new"
title: "CLOSE ME"
---
**⚠️ Conduit development does not happen on GitHub. Issues opened here will not be addressed**
Please open issues on GitLab: https://gitlab.com/famedly/conduit/-/issues/new

83
.gitignore vendored
View file

@ -1,76 +1,19 @@
# CMake
cmake-build-*/
# Local environment overrides
/.env
# IntelliJ
.idea/
out/
*.iml
modules.xml
*.ipr
# mpeltonen/sbt-idea plugin
.idea_modules/
# Linux backup files
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# Rust
/target/
### vscode ###
.vscode/*
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows shortcuts
*.lnk
# Conduit
conduit.toml
conduit.db
# Etc.
**/*.rs.bk
cached_target
# Nix artifacts
/result*
# Cargo artifacts
target
# Direnv cache
/.direnv
# Gitlab CI cache
/.gitlab-ci.d
# Nix artifacts
result*
# mdbook output
public/
# GitLab CI artifacts
/.gitlab-ci.d
/grapevine-static-aarch64-unknown-linux-musl
/grapevine-static-x86_64-unknown-linux-musl
# mdbook artifacts
/public

View file

@ -1,184 +1,82 @@
stages:
- ci
- artifacts
- publish
- deploy
variables:
# Makes some things print in color
TERM: ansi
# Avoid duplicate pipelines
# See: https://docs.gitlab.com/ee/ci/yaml/workflow.html#switch-between-branch-pipelines-and-merge-request-pipelines
workflow:
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
when: never
- if: $CI
before_script:
# Enable nix-command and flakes
- if command -v nix > /dev/null; then echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf; fi
- |
# Enable nix-command and flakes
echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
# Add our own binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix.computer.surgery/conduit" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = conduit:ZGAf6P6LhNvnoJJ3Me3PRg7tlLSrPxcQ2RiE5LIppjo=" >> /etc/nix/nix.conf; fi
# Disable IFD, to ensure we are able to build without it
echo "allow-import-from-derivation = false" >> /etc/nix/nix.conf
# Add alternate binary cache
- if command -v nix > /dev/null && [ -n "$ATTIC_ENDPOINT" ]; then echo "extra-substituters = $ATTIC_ENDPOINT" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null && [ -n "$ATTIC_PUBLIC_KEY" ]; then echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf; fi
# Add crane binary cache
echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf
echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf
# Add crane binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://crane.cachix.org" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = crane.cachix.org-1:8Scfpmn9w+hGdXH/Q9tTLiYAE/2dnJYRJP7kl80GuRk=" >> /etc/nix/nix.conf; fi
# Add nix-community binary cache
echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf
echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf
# Add nix-community binary cache
- if command -v nix > /dev/null; then echo "extra-substituters = https://nix-community.cachix.org" >> /etc/nix/nix.conf; fi
- if command -v nix > /dev/null; then echo "extra-trusted-public-keys = nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs=" >> /etc/nix/nix.conf; fi
# Add our own binary cache
if [ -n "$ATTIC_ENDPOINT" ] && [ -n "$ATTIC_CACHE" ] && [ -n "$ATTIC_PUBLIC_KEY" ]; then
echo "extra-substituters = $ATTIC_ENDPOINT/$ATTIC_CACHE" >> /etc/nix/nix.conf
echo "extra-trusted-public-keys = $ATTIC_PUBLIC_KEY" >> /etc/nix/nix.conf
fi
# Install direnv and nix-direnv
- if command -v nix > /dev/null; then nix-env -iA nixpkgs.direnv nixpkgs.nix-direnv; fi
# Install direnv
nix profile install --impure --inputs-from . nixpkgs#direnv
# Allow .envrc
- if command -v nix > /dev/null; then direnv allow; fi
# Allow .envrc
direnv allow
# Set CARGO_HOME to a cacheable path
- export CARGO_HOME="$(git rev-parse --show-toplevel)/.gitlab-ci.d/cargo"
# Set CARGO_HOME to a cacheable path
export CARGO_HOME="$(git rev-parse --show-toplevel)/.gitlab-ci.d/cargo"
cache-ci-deps:
stage: ci
image: git.lix.systems/lix-project/lix:2.93.3
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script: direnv exec . job cache-ci-deps
ci:
stage: ci
image: nixos/nix:2.20.4
image: git.lix.systems/lix-project/lix:2.93.3
rules:
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
script:
# Cache the inputs required for the devShell
- ./bin/nix-build-and-cache .#devShells.x86_64-linux.default.inputDerivation
- direnv exec . engage
- direnv exec . job ci
cache:
key: nix
paths:
- target
- .gitlab-ci.d
rules:
# CI on upstream runners (only available for maintainers)
- if: $CI_PIPELINE_SOURCE == "merge_request_event" && $IS_UPSTREAM_CI == "true"
# Manual CI on unprotected branches that are not MRs
- if: $CI_PIPELINE_SOURCE != "merge_request_event" && $CI_COMMIT_REF_PROTECTED == "false"
when: manual
# Manual CI on forks
- if: $IS_UPSTREAM_CI != "true"
when: manual
- if: $CI
interruptible: true
artifacts:
stage: artifacts
image: nixos/nix:2.20.4
image: git.lix.systems/lix-project/lix:2.93.3
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
script:
- ./bin/nix-build-and-cache .#static-x86_64-unknown-linux-musl
- cp result/bin/conduit x86_64-unknown-linux-musl
- mkdir -p target/release
- cp result/bin/conduit target/release
- direnv exec . cargo deb --no-build
- mv target/debian/*.deb x86_64-unknown-linux-musl.deb
# Since the OCI image package is based on the binary package, this has the
# fun side effect of uploading the normal binary too. Conduit users who are
# deploying with Nix can leverage this fact by adding our binary cache to
# their systems.
#
# Note that although we have an `oci-image-x86_64-unknown-linux-musl`
# output, we don't build it because it would be largely redundant to this
# one since it's all containerized anyway.
- ./bin/nix-build-and-cache .#oci-image
- cp result oci-image-amd64.tar.gz
- ./bin/nix-build-and-cache .#static-aarch64-unknown-linux-musl
- cp result/bin/conduit aarch64-unknown-linux-musl
- ./bin/nix-build-and-cache .#oci-image-aarch64-unknown-linux-musl
- cp result oci-image-arm64v8.tar.gz
- ./bin/nix-build-and-cache .#book
# We can't just copy the symlink, we need to dereference it https://gitlab.com/gitlab-org/gitlab/-/issues/19746
- cp -r --dereference result public
- direnv exec . job artifacts
artifacts:
paths:
- x86_64-unknown-linux-musl
- aarch64-unknown-linux-musl
- x86_64-unknown-linux-musl.deb
- oci-image-amd64.tar.gz
- oci-image-arm64v8.tar.gz
- public
rules:
# CI required for all MRs
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
# Optional CI on forks
- if: $IS_UPSTREAM_CI != "true"
when: manual
allow_failure: true
- if: $CI
interruptible: true
.push-oci-image:
stage: publish
image: docker:25.0.0
services:
- docker:25.0.0-dind
variables:
IMAGE_SUFFIX_AMD64: amd64
IMAGE_SUFFIX_ARM64V8: arm64v8
script:
- docker load -i oci-image-amd64.tar.gz
- IMAGE_ID_AMD64=$(docker images -q conduit:next)
- docker load -i oci-image-arm64v8.tar.gz
- IMAGE_ID_ARM64V8=$(docker images -q conduit:next)
# Tag and push the architecture specific images
- docker tag $IMAGE_ID_AMD64 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
- docker tag $IMAGE_ID_ARM64V8 $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
- docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64
- docker push $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
# Tag the multi-arch image
- docker manifest create $IMAGE_NAME:$CI_COMMIT_SHA --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
- docker manifest push $IMAGE_NAME:$CI_COMMIT_SHA
# Tag and push the git ref
- docker manifest create $IMAGE_NAME:$CI_COMMIT_REF_NAME --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
- docker manifest push $IMAGE_NAME:$CI_COMMIT_REF_NAME
# Tag git tags as 'latest'
- |
if [[ -n "$CI_COMMIT_TAG" ]]; then
docker manifest create $IMAGE_NAME:latest --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_AMD64 --amend $IMAGE_NAME:$CI_COMMIT_SHA-$IMAGE_SUFFIX_ARM64V8
docker manifest push $IMAGE_NAME:latest
fi
dependencies:
- artifacts
only:
- next
- master
- tags
oci-image:push-gitlab:
extends: .push-oci-image
variables:
IMAGE_NAME: $CI_REGISTRY_IMAGE/matrix-conduit
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
oci-image:push-dockerhub:
extends: .push-oci-image
variables:
IMAGE_NAME: matrixconduit/matrix-conduit
before_script:
- docker login -u $DOCKER_HUB_USER -p $DOCKER_HUB_PASSWORD
- grapevine-static-aarch64-unknown-linux-musl
- grapevine-static-x86_64-unknown-linux-musl
pages:
stage: publish
dependencies:
- artifacts
only:
- next
stage: deploy
image: git.lix.systems/lix-project/lix:2.93.3
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- "true"
- direnv exec . job pages
artifacts:
paths:
- public

View file

@ -1,5 +0,0 @@
# Nix things
.envrc @CobaltCause
flake.lock @CobaltCause
flake.nix @CobaltCause
nix/ @CobaltCause

View file

@ -1,19 +0,0 @@
<!--
If you're requesting a new feature, that isn't part of this project yet,
then please consider filling out a "Feature Request" instead!
If you need a hand setting up your conduit server, feel free to ask for help in the
Conduit Matrix Chat: https://matrix.to/#/#conduit:fachschaften.org.
-->
### Description
<!-- What did you do and what happened? Why is that bad? -->
### System Configuration
<!-- Other data that might help us debug this issue, like os, conduit version, database backend -->
Conduit Version:
Database backend (default is sqlite): sqlite
/label ~conduit

View file

@ -1,17 +0,0 @@
<!--
If you want to report a bug or an error,
then please consider filling out a "Bug Report" instead!
-->
### Is your feature request related to a problem? Please describe.
<!-- Eg. I'm always frustrated when [...] -->
### Describe the solution you'd like
/label ~conduit

View file

@ -1,8 +0,0 @@
<!-- Please describe your changes here -->
-----------------------------------------------------------------------------
- [ ] I ran `cargo fmt` and `cargo test`
- [ ] I agree to release my code and all other changes of this MR under the Apache-2.0 license

View file

@ -1,3 +0,0 @@
# Docs: Map markdown to html files
- source: /docs/(.+)\.md/
public: '\1.html'

View file

@ -1,37 +0,0 @@
#!/bin/sh
set -eux
# --------------------------------------------------------------------- #
# #
# Configures docker buildx to use a remote server for arm building. #
# Expects $SSH_PRIVATE_KEY to be a valid ssh ed25519 private key with #
# access to the server $ARM_SERVER_USER@$ARM_SERVER_IP #
# #
# This is expected to only be used in the official CI/CD pipeline! #
# #
# Requirements: openssh-client, docker buildx #
# Inspired by: https://depot.dev/blog/building-arm-containers #
# #
# --------------------------------------------------------------------- #
cat "$BUILD_SERVER_SSH_PRIVATE_KEY" | ssh-add -
# Test server connections:
ssh "$ARM_SERVER_USER@$ARM_SERVER_IP" "uname -a"
ssh "$AMD_SERVER_USER@$AMD_SERVER_IP" "uname -a"
# Connect remote arm64 server for all arm builds:
docker buildx create \
--name "multi" \
--driver "docker-container" \
--platform "linux/arm64,linux/arm/v7" \
"ssh://$ARM_SERVER_USER@$ARM_SERVER_IP"
# Connect remote amd64 server for adm64 builds:
docker buildx create --append \
--name "multi" \
--driver "docker-container" \
--platform "linux/amd64" \
"ssh://$AMD_SERVER_USER@$AMD_SERVER_IP"
docker buildx use multi

1
.lycheeignore Symbolic link
View file

@ -0,0 +1 @@
.gitignore

1
.mailmap Normal file
View file

@ -0,0 +1 @@
Olivia Lee <olivia@computer.surgery> <benjamin@computer.surgery>

1
.markdownlintignore Symbolic link
View file

@ -0,0 +1 @@
.gitignore

View file

@ -1,11 +0,0 @@
{
"recommendations": [
"rust-lang.rust-analyzer",
"bungcip.better-toml",
"ms-azuretools.vscode-docker",
"eamodio.gitlens",
"serayuzgur.crates",
"vadimcn.vscode-lldb",
"timonwong.shellcheck"
]
}

35
.vscode/launch.json vendored
View file

@ -1,35 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "lldb",
"request": "launch",
"name": "Debug conduit",
"sourceLanguages": ["rust"],
"cargo": {
"args": [
"build",
"--bin=conduit",
"--package=conduit"
],
"filter": {
"name": "conduit",
"kind": "bin"
}
},
"args": [],
"env": {
"RUST_BACKTRACE": "1",
"CONDUIT_CONFIG": "",
"CONDUIT_SERVER_NAME": "localhost",
"CONDUIT_DATABASE_PATH": "/tmp",
"CONDUIT_ADDRESS": "0.0.0.0",
"CONDUIT_PORT": "6167"
},
"cwd": "${workspaceFolder}"
}
]
}

View file

@ -1,134 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of
any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address,
without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement over email at
coc@koesters.xyz or over Matrix at @timo:conduit.rs.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

2995
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,188 +1,194 @@
# Keep alphabetically sorted
[workspace.lints.rust]
elided_lifetimes_in_paths = "warn"
explicit_outlives_requirements = "warn"
macro_use_extern_crate = "warn"
missing_abi = "warn"
noop_method_call = "warn"
single_use_lifetimes = "warn"
unreachable_pub = "warn"
unsafe_op_in_unsafe_fn = "warn"
unused_extern_crates = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
unused_qualifications = "warn"
# Keep alphabetically sorted
[workspace.lints.clippy]
cloned_instead_of_copied = "warn"
# Groups. Keep alphabetically sorted
pedantic = { level = "warn", priority = -1 }
# Lints. Keep alphabetically sorted
as_conversions = "warn"
assertions_on_result_states = "warn"
dbg_macro = "warn"
default_union_representation = "warn"
deref_by_slicing = "warn"
empty_drop = "warn"
empty_structs_with_brackets = "warn"
error_impl_error = "warn"
filetype_is_file = "warn"
float_cmp_const = "warn"
format_push_string = "warn"
get_unwrap = "warn"
if_then_some_else_none = "warn"
impl_trait_in_params = "warn"
let_underscore_must_use = "warn"
lossy_float_literal = "warn"
mem_forget = "warn"
missing_assert_message = "warn"
mod_module_files = "warn"
multiple_inherent_impl = "warn"
mutex_atomic = "warn"
negative_feature_names = "warn"
non_zero_suggestions = "warn"
pub_without_shorthand = "warn"
rc_buffer = "warn"
rc_mutex = "warn"
redundant_feature_names = "warn"
redundant_type_annotations = "warn"
ref_patterns = "warn"
rest_pat_in_fully_bound_structs = "warn"
same_name_method = "warn"
semicolon_inside_block = "warn"
str_to_string = "warn"
string_add = "warn"
string_lit_chars_any = "warn"
string_slice = "warn"
string_to_string = "warn"
suspicious_xor_used_as_pow = "warn"
tests_outside_test_module = "warn"
try_err = "warn"
undocumented_unsafe_blocks = "warn"
unnecessary_safety_comment = "warn"
unnecessary_safety_doc = "warn"
unnecessary_self_imports = "warn"
unneeded_field_pattern = "warn"
unseparated_literal_suffix = "warn"
unused_result_ok = "warn"
verbose_file_reads = "warn"
wildcard_dependencies = "warn"
# TODO: Remove these:
missing_errors_doc = "allow"
missing_panics_doc = "allow"
[package]
name = "conduit"
name = "grapevine"
description = "A Matrix homeserver written in Rust"
license = "Apache-2.0"
authors = ["timokoesters <timo@koesters.xyz>"]
homepage = "https://conduit.rs"
repository = "https://gitlab.com/famedly/conduit"
readme = "README.md"
version = "0.7.0"
version = "0.1.0"
edition = "2021"
# See also `rust-toolchain.toml`
rust-version = "1.75.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
rust-version = "1.85.0"
[lints]
workspace = true
# Keep sorted
[dependencies]
# Web framework
axum = { version = "0.6.18", default-features = false, features = ["form", "headers", "http1", "http2", "json", "matched-path"], optional = true }
axum-server = { version = "0.5.1", features = ["tls-rustls"] }
tower = { version = "0.4.13", features = ["util"] }
tower-http = { version = "0.4.1", features = ["add-extension", "cors", "sensitive-headers", "trace", "util"] }
# Used for matrix spec type definitions and helpers
#ruma = { version = "0.4.0", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-pre-spec", "unstable-exhaustive-types"] }
ruma = { git = "https://github.com/ruma/ruma", rev = "5495b85aa311c2805302edb0a7de40399e22b397", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
#ruma = { git = "https://github.com/timokoesters/ruma", rev = "4ec9c69bb7e09391add2382b3ebac97b6e8f4c64", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
#ruma = { path = "../ruma/crates/ruma", features = ["compat", "rand", "appservice-api-c", "client-api", "federation-api", "push-gateway-api-c", "state-res", "unstable-msc2448", "unstable-msc3575", "unstable-exhaustive-types", "ring-compat", "unstable-unspecified" ] }
# Async runtime and utilities
tokio = { version = "1.28.1", features = ["fs", "macros", "signal", "sync"] }
# Used for storing data permanently
#sled = { version = "0.34.7", features = ["compression", "no_metrics"], optional = true }
#sled = { git = "https://github.com/spacejam/sled.git", rev = "e4640e0773595229f398438886f19bca6f7326a2", features = ["compression"] }
persy = { version = "1.4.4", optional = true, features = ["background_ops"] }
# Used for the http request / response body type for Ruma endpoints used with reqwest
bytes = "1.4.0"
http = "0.2.9"
# Used to find data directory for default db path
directories = "4.0.1"
# Used for ruma wrapper
serde_json = { version = "1.0.96", features = ["raw_value"] }
# Used for appservice registration files
serde_yaml = "0.9.21"
# Used for pdu definition
serde = { version = "1.0.163", features = ["rc"] }
# Used for secure identifiers
rand = "0.8.5"
# Used to hash passwords
rust-argon2 = "1.0.0"
# Used to send requests
hyper = "0.14.26"
reqwest = { version = "0.11.18", default-features = false, features = ["rustls-tls-native-roots", "socks"] }
# Used for conduit::Error type
thiserror = "1.0.40"
# Used to generate thumbnails for images
image = { version = "0.24.6", default-features = false, features = ["jpeg", "png", "gif"] }
# Used to encode server public key
base64 = "0.21.2"
# Used when hashing the state
ring = "0.17.7"
# Used when querying the SRV record of other servers
trust-dns-resolver = "0.22.0"
# Used to find matching events for appservices
regex = "1.8.1"
# jwt jsonwebtokens
jsonwebtoken = "9.2.0"
# Performance measurements
tracing = { version = "0.1.37", features = [] }
tracing-subscriber = { version = "0.3.17", features = ["env-filter"] }
tracing-flame = "0.2.0"
opentelemetry = { version = "0.18.0", features = ["rt-tokio"] }
opentelemetry-jaeger = { version = "0.17.0", features = ["rt-tokio"] }
tracing-opentelemetry = "0.18.0"
lru-cache = "0.1.2"
rusqlite = { version = "0.29.0", optional = true, features = ["bundled"] }
parking_lot = { version = "0.12.1", optional = true }
# crossbeam = { version = "0.8.2", optional = true }
num_cpus = "1.15.0"
threadpool = "1.8.1"
# heed = { git = "https://github.com/timokoesters/heed.git", rev = "f6f825da7fb2c758867e05ad973ef800a6fe1d5d", optional = true }
# Used for ruma wrapper
serde_html_form = "0.2.0"
thread_local = "1.1.7"
# used for TURN server authentication
argon2 = "0.5.3"
async-trait = "0.1.88"
axum = { version = "0.7.9", default-features = false, features = ["form", "http1", "http2", "json", "matched-path", "tokio", "tracing"] }
axum-extra = { version = "0.9.6", features = ["typed-header"] }
axum-server = { git = "https://gitlab.computer.surgery/matrix/thirdparty/axum-server.git", rev = "v0.7.2+grapevine-1", version = "0.7.2", features = ["tls-rustls-no-provider"] }
base64 = "0.22.1"
bytes = "1.10.1"
clap = { version = "4.5.42", default-features = false, features = ["std", "derive", "help", "usage", "error-context", "string", "wrap_help"] }
futures-util = { version = "0.3.31", default-features = false }
hmac = "0.12.1"
html-escape = "0.2.13"
http = "1.3.1"
http-body-util = "0.1.3"
hyper = "1.6.0"
image = { version = "0.25.6", default-features = false, features = ["jpeg", "png", "gif"] }
jsonwebtoken = "9.3.1"
lru-cache = "0.1.2"
num_cpus = "1.17.0"
opentelemetry = "0.24.0"
opentelemetry-jaeger-propagator = "0.3.0"
opentelemetry-otlp = "0.17.0"
opentelemetry-prometheus = "0.17.0"
opentelemetry_sdk = { version = "0.24.1", features = ["rt-tokio"] }
parking_lot = { version = "0.12.4", optional = true }
phf = { version = "0.11.3", features = ["macros"] }
pin-project-lite = "0.2.16"
prometheus = "0.13.4"
proxy-header = { version = "0.1.2", features = ["tokio"] }
rand = "0.8.5"
regex = "1.11.1"
reqwest = { version = "0.12.22", default-features = false, features = ["http2", "rustls-tls-native-roots", "socks"] }
ring = "0.17.14"
rocksdb = { package = "rust-rocksdb", version = "0.42.1", features = ["lz4", "multi-threaded-cf", "zstd"], optional = true }
rusqlite = { version = "0.34.0", optional = true, features = ["bundled"] }
rustls = { version = "0.23.31", default-features = false, features = ["ring", "log", "logging", "std", "tls12"] }
sd-notify = { version = "0.4.5", optional = true }
serde = { version = "1.0.219", features = ["rc"] }
serde_html_form = "0.2.7"
serde_json = { version = "1.0.141", features = ["raw_value"] }
serde_yaml = "0.9.34"
sha-1 = "0.10.1"
# used for conduit's CLI and admin room command parsing
clap = { version = "4.3.0", default-features = false, features = ["std", "derive", "help", "usage", "error-context", "string"] }
futures-util = { version = "0.3.28", default-features = false }
# Used for reading the configuration from conduit.toml & environment variables
figment = { version = "0.10.8", features = ["env", "toml"] }
strum = { version = "0.27.2", features = ["derive"] }
thiserror = "2.0.12"
thread_local = "1.1.9"
tokio = { version = "1.47.0", features = ["fs", "macros", "signal", "sync"] }
toml = "0.8.23"
tower = { version = "0.5.2", features = ["util"] }
tower-http = { version = "0.6.6", features = ["add-extension", "cors", "sensitive-headers", "trace", "util"] }
tracing = { version = "0.1.41", features = [] }
tracing-flame = "0.2.0"
tracing-opentelemetry = "0.25.0"
tracing-subscriber = { version = "0.3.19", features = ["env-filter", "json"] }
trust-dns-resolver = "0.23.2"
xdg = "2.5.2"
tikv-jemalloc-ctl = { version = "0.5.0", features = ["use_std"], optional = true }
tikv-jemallocator = { version = "0.5.0", features = ["unprefixed_malloc_on_supported_platforms"], optional = true }
lazy_static = "1.4.0"
async-trait = "0.1.68"
sd-notify = { version = "0.4.1", optional = true }
[dependencies.rocksdb]
package = "rust-rocksdb"
version = "0.24.0"
optional = true
[dependencies.ruma]
git = "https://github.com/ruma/ruma.git"
rev = "c4f467781a7ef330dc0b7eb5d0d0cad77ebc3337"
features = [
"multi-threaded-cf",
"zstd",
"lz4",
"compat-server-signing-key-version",
"compat-empty-string-null",
"compat-null",
"compat-optional",
"compat-unset-avatar",
"compat-get-3pids",
"compat-tag-info",
"compat-optional-txn-pdus",
"rand",
"appservice-api-c",
"client-api",
"federation-api",
"push-gateway-api-c",
"state-res",
"unstable-msc2448",
"ring-compat",
]
[target.'cfg(unix)'.dependencies]
nix = { version = "0.28", features = ["resource"] }
nix = { version = "0.29", features = ["resource", "time"] }
[dev-dependencies]
assert_cmd = "2.0.17"
insta = { version = "1.43.1", features = ["filters", "json", "redactions"] }
predicates = "3.1.3"
tempfile = "3.20.0"
[profile.dev.package.insta]
opt-level = 3
[profile.dev.package.similar]
opt-level = 3
[features]
default = ["conduit_bin", "backend_sqlite", "backend_rocksdb", "systemd"]
#backend_sled = ["sled"]
backend_persy = ["persy", "parking_lot"]
backend_sqlite = ["sqlite"]
#backend_heed = ["heed", "crossbeam"]
backend_rocksdb = ["rocksdb"]
jemalloc = ["tikv-jemalloc-ctl", "tikv-jemallocator"]
sqlite = ["rusqlite", "parking_lot", "tokio/signal"]
conduit_bin = ["axum"]
systemd = ["sd-notify"]
default = ["rocksdb", "sqlite", "systemd"]
[[bin]]
name = "conduit"
path = "src/main.rs"
required-features = ["conduit_bin"]
# Keep sorted
rocksdb = ["dep:rocksdb"]
sqlite = ["dep:rusqlite", "dep:parking_lot", "tokio/signal"]
systemd = ["dep:sd-notify"]
[lib]
name = "conduit"
path = "src/lib.rs"
[package.metadata.deb]
name = "matrix-conduit"
maintainer = "Paul van Tilburg <paul@luon.net>"
copyright = "2020, Timo Kösters <timo@koesters.xyz>"
license-file = ["LICENSE", "3"]
depends = "$auto, ca-certificates"
extended-description = """\
A fast Matrix homeserver that is optimized for smaller, personal servers, \
instead of a server that has high scalability."""
section = "net"
priority = "optional"
assets = [
["debian/README.md", "usr/share/doc/matrix-conduit/README.Debian", "644"],
["README.md", "usr/share/doc/matrix-conduit/", "644"],
["target/release/conduit", "usr/sbin/matrix-conduit", "755"],
]
conf-files = [
"/etc/matrix-conduit/conduit.toml"
]
maintainer-scripts = "debian/"
systemd-units = { unit-name = "matrix-conduit" }
[profile.dev]
lto = 'off'
incremental = true
[profile.release]
lto = 'thin'
incremental = true
codegen-units=32
# If you want to make flamegraphs, enable debug info:
# debug = true
# For releases also try to max optimizations for dependencies:
[profile.release.build-override]
opt-level = 3
[profile.release.package."*"]
opt-level = 3
[profile.release-debug]
inherits = "release"
debug = true

176
LICENSE
View file

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

172
LICENSE.md Normal file
View file

@ -0,0 +1,172 @@
# Apache License
Version 2.0, January 2004, <http://www.apache.org/licenses/>
## TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
### 1. Definitions
* **"License"** shall mean the terms and conditions for use, reproduction, and
distribution as defined by Sections 1 through 9 of this document.
* **"Licensor"** shall mean the copyright owner or entity authorized by the
copyright owner that is granting the License.
* **"Legal Entity"** shall mean the union of the acting entity and all other
entities that control, are controlled by, or are under common control with
that entity. For the purposes of this definition, "control" means (i) the
power, direct or indirect, to cause the direction or management of such
entity, whether by contract or otherwise, or (ii) ownership of fifty percent
(50%) or more of the outstanding shares, or (iii) beneficial ownership of such
entity.
* **"You"** (or **"Your"**) shall mean an individual or Legal Entity exercising
permissions granted by this License.
* **"Source"** form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.
* **"Object"** form shall mean any form resulting from mechanical transformation
or translation of a Source form, including but not limited to compiled object
code, generated documentation, and conversions to other media types.
* **"Work"** shall mean the work of authorship, whether in Source or Object
form, made available under the License, as indicated by a copyright notice
that is included in or attached to the work (an example is provided in the
Appendix below).
* **"Derivative Works"** shall mean any work, whether in Source or Object form,
that is based on (or derived from) the Work and for which the editorial
revisions, annotations, elaborations, or other modifications represent, as
a whole, an original work of authorship. For the purposes of this License,
Derivative Works shall not include works that remain separable from, or merely
link (or bind by name) to the interfaces of, the Work and Derivative Works
thereof.
* **"Contribution"** shall mean any work of authorship, including the original
version of the Work and any modifications or additions to that Work or
Derivative Works thereof, that is intentionally submitted to Licensor for
inclusion in the Work by the copyright owner or by an individual or Legal
Entity authorized to submit on behalf of the copyright owner. For the purposes
of this definition, "submitted" means any form of electronic, verbal, or
written communication sent to the Licensor or its representatives, including
but not limited to communication on electronic mailing lists, source code
control systems, and issue tracking systems that are managed by, or on behalf
of, the Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise designated
in writing by the copyright owner as "Not a Contribution."
* **"Contributor"** shall mean Licensor and any individual or Legal Entity on
behalf of whom a Contribution has been received by Licensor and subsequently
incorporated within the Work.
### 2. Grant of Copyright License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the Work and such
Derivative Works in Source or Object form.
### 3. Grant of Patent License
Subject to the terms and conditions of this License, each Contributor hereby
grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
irrevocable (except as stated in this section) patent license to make, have
made, use, offer to sell, sell, import, and otherwise transfer the Work, where
such license applies only to those patent claims licensable by such Contributor
that are necessarily infringed by their Contribution(s) alone or by combination
of their Contribution(s) with the Work to which such Contribution(s) was
submitted. If You institute patent litigation against any entity (including
a cross-claim or counterclaim in a lawsuit) alleging that the Work or
a Contribution incorporated within the Work constitutes direct or contributory
patent infringement, then any patent licenses granted to You under this License
for that Work shall terminate as of the date such litigation is filed.
### 4. Redistribution
You may reproduce and distribute copies of the Work or Derivative Works thereof
in any medium, with or without modifications, and in Source or Object form,
provided that You meet the following conditions:
1. You must give any other recipients of the Work or Derivative Works a copy of
this License; and
2. You must cause any modified files to carry prominent notices stating that You
changed the files; and
3. You must retain, in the Source form of any Derivative Works that You
distribute, all copyright, patent, trademark, and attribution notices from
the Source form of the Work, excluding those notices that do not pertain to
any part of the Derivative Works; and
4. If the Work includes a "NOTICE" text file as part of its distribution, then
any Derivative Works that You distribute must include a readable copy of the
attribution notices contained within such NOTICE file, excluding those
notices that do not pertain to any part of the Derivative Works, in at least
one of the following places: within a NOTICE text file distributed as part of
the Derivative Works; within the Source form or documentation, if provided
along with the Derivative Works; or, within a display generated by the
Derivative Works, if and wherever such third-party notices normally appear.
The contents of the NOTICE file are for informational purposes only and do
not modify the License. You may add Your own attribution notices within
Derivative Works that You distribute, alongside or as an addendum to the
NOTICE text from the Work, provided that such additional attribution notices
cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide
additional or different license terms and conditions for use, reproduction, or
distribution of Your modifications, or for any such Derivative Works as a whole,
provided Your use, reproduction, and distribution of the Work otherwise complies
with the conditions stated in this License.
### 5. Submission of Contributions
Unless You explicitly state otherwise, any Contribution intentionally submitted
for inclusion in the Work by You to the Licensor shall be under the terms and
conditions of this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify the terms of
any separate license agreement you may have executed with Licensor regarding
such Contributions.
### 6. Trademarks
This License does not grant permission to use the trade names, trademarks,
service marks, or product names of the Licensor, except as required for
reasonable and customary use in describing the origin of the Work and
reproducing the content of the NOTICE file.
### 7. Disclaimer of Warranty
Unless required by applicable law or agreed to in writing, Licensor provides the
Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
including, without limitation, any warranties or conditions of TITLE,
NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
solely responsible for determining the appropriateness of using or
redistributing the Work and assume any risks associated with Your exercise of
permissions under this License.
### 8. Limitation of Liability
In no event and under no legal theory, whether in tort (including negligence),
contract, or otherwise, unless required by applicable law (such as deliberate
and grossly negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special, incidental,
or consequential damages of any character arising as a result of this License or
out of the use or inability to use the Work (including but not limited to
damages for loss of goodwill, work stoppage, computer failure or malfunction, or
any and all other commercial damages or losses), even if such Contributor has
been advised of the possibility of such damages.
### 9. Accepting Warranty or Additional Liability
While redistributing the Work or Derivative Works thereof, You may choose to
offer, and charge a fee for, acceptance of support, warranty, indemnity, or
other liability obligations and/or rights consistent with this License. However,
in accepting such obligations, You may act only on Your own behalf and on Your
sole responsibility, not on behalf of any other Contributor, and only if You
agree to indemnify, defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason of your
accepting any such warranty or additional liability.

View file

@ -1,77 +1,9 @@
# Conduit
# Grapevine
<!-- ANCHOR: catchphrase -->
### A Matrix homeserver written in Rust
<!-- ANCHOR_END: catchphrase -->
A Matrix homeserver.
Please visit the [Conduit documentation](https://famedly.gitlab.io/conduit) for more information.
Alternatively you can open [docs/introduction.md](docs/introduction.md) in this repository.
## Read the book
<!-- ANCHOR: body -->
#### What is Matrix?
[Click here to read the latest version.][0]
[Matrix](https://matrix.org) is an open network for secure and decentralized
communication. Users from every Matrix homeserver can chat with users from all
other Matrix servers. You can even use bridges (also called Matrix appservices)
to communicate with users outside of Matrix, like a community on Discord.
#### What is the goal?
An efficient Matrix homeserver that's easy to set up and just works. You can install
it on a mini-computer like the Raspberry Pi to host Matrix for your family,
friends or company.
#### Can I try it out?
Yes! You can test our Conduit instance by opening a client that supports registration tokens such as [Element web](https://app.element.io/), [Nheko](https://matrix.org/ecosystem/clients/nheko/) or [SchildiChat web](https://app.schildi.chat/) and registering on the `conduit.rs` homeserver. The registration token is "for_testing_only". Don't share personal information. Once you have registered, you can use any other [Matrix client](https://matrix.org/ecosystem/clients) to login.
Server hosting for conduit.rs is donated by the Matrix.org Foundation.
#### What is the current status?
Conduit is Beta, meaning you can join and participate in most
Matrix rooms, but not all features are supported and you might run into bugs
from time to time.
There are still a few important features missing:
- E2EE emoji comparison over federation (E2EE chat works)
- Outgoing read receipts, typing, presence over federation (incoming works)
<!-- ANCHOR_END: body -->
<!-- ANCHOR: footer -->
#### How can I contribute?
1. Look for an issue you would like to work on and make sure no one else is currently working on it.
2. Tell us that you are working on the issue (comment on the issue or chat in
[#conduit:fachschaften.org](https://matrix.to/#/#conduit:fachschaften.org)). If it is more complicated, please explain your approach and ask questions.
3. Fork the repo, create a new branch and push commits.
4. Submit a MR
#### Contact
If you have any questions, feel free to
- Ask in `#conduit:fachschaften.org` on Matrix
- Write an E-Mail to `conduit@koesters.xyz`
- Send an direct message to `@timokoesters:fachschaften.org` on Matrix
- [Open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new)
#### Thanks to
Thanks to FUTO, Famedly, Prototype Fund (DLR and German BMBF) and all individuals for financially supporting this project.
Thanks to the contributors to Conduit and all libraries we use, for example:
- Ruma: A clean library for the Matrix Spec in Rust
- axum: A modular web framework
#### Donate
- Liberapay: <https://liberapay.com/timokoesters/>
- Bitcoin: `bc1qnnykf986tw49ur7wx9rpw2tevpsztvar5x8w4n`
#### Logo
- Lightning Bolt Logo: <https://github.com/mozilla/fxemoji/blob/gh-pages/svgs/nature/u26A1-bolt.svg>
- Logo License: <https://github.com/mozilla/fxemoji/blob/gh-pages/LICENSE.md>
<!-- ANCHOR_END: footer -->
[0]: https://grapevine.computer.surgery/

View file

@ -1,37 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# Path to Complement's source code
COMPLEMENT_SRC="$1"
# A `.jsonl` file to write test logs to
LOG_FILE="$2"
# A `.jsonl` file to write test results to
RESULTS_FILE="$3"
OCI_IMAGE="complement-conduit:dev"
env \
-C "$(git rev-parse --show-toplevel)" \
docker build \
--tag "$OCI_IMAGE" \
--file complement/Dockerfile \
.
# It's okay (likely, even) that `go test` exits nonzero
set +o pipefail
env \
-C "$COMPLEMENT_SRC" \
COMPLEMENT_BASE_IMAGE="$OCI_IMAGE" \
go test -json ./tests | tee "$LOG_FILE"
set -o pipefail
# Post-process the results into an easy-to-compare format
cat "$LOG_FILE" | jq -c '
select(
(.Action == "pass" or .Action == "fail" or .Action == "skip")
and .Test != null
) | {Action: .Action, Test: .Test}
' | sort > "$RESULTS_FILE"

131
bin/job Executable file
View file

@ -0,0 +1,131 @@
#!/usr/bin/env bash
job_artifacts() (
set -euo pipefail
nix-build-and-cache packages
# Subsequent `nix build` calls should be fast because the above line ensures
# the packages have been built already.
packages=(
static-aarch64-unknown-linux-musl
static-x86_64-unknown-linux-musl
)
for x in "${packages[@]}"; do
nix build ".#$x"
cp result/bin/grapevine grapevine-"$x"
chmod u+w grapevine-"$x"
done
)
job_ci() (
set -euo pipefail
nix-build-and-cache ci
direnv exec . engage
)
job_pages() (
set -euo pipefail
nix build .#website-root
cp --recursive --dereference result public
chmod u+w -R public
)
job_cache-ci-deps() (
set -euo pipefail
nix-build-and-cache ci
)
bail() (
set -euo pipefail
git show --shortstat
echo
echo "Failure caused by the above commit"
exit 1
)
mark_commit_passed() (
set -euo pipefail
mkdir -p ".gitlab-ci.d/passed/$1"
touch ".gitlab-ci.d/passed/$1/$(git rev-parse HEAD)"
)
commit_passed() (
set -euo pipefail
[[ -f ".gitlab-ci.d/passed/$1/$(git rev-parse HEAD)" ]]
)
contains() (
set -euo pipefail
local -n xs=$1
for x in "${xs[@]}"; do
if [[ "$x" == "$2" ]]; then
return 0
else
return 1
fi
done
)
# Jobs that should only run on the latest commit rather than since the branch's
# base.
last_commit_only=(
artifacts
)
run() (
set -euo pipefail
if [[ -z "${1+x}" ]]; then
echo "You must supply a job to run. Available jobs:"
declare -F | rg \
--only-matching \
--color never \
--replace '* $1' \
'^declare -f job_(.*)$'
exit 1
fi
job="$1"
cd "$(git rev-parse --show-toplevel)"
if \
[[ -z "${CI_MERGE_REQUEST_DIFF_BASE_SHA+x}" ]] \
|| contains last_commit_only "$job"
then
echo "Running against latest commit only..."
job_"$job" || bail
else
echo "Running against all commits since this branch's base..."
readarray -t commits < \
<(git rev-list --reverse "$CI_MERGE_REQUEST_DIFF_BASE_SHA..HEAD")
for commit in "${commits[@]}"; do
git checkout "$commit"
if commit_passed "$job"; then
echo "Skipping commit because it already passed: $commit"
continue
fi
job_"$job" || bail
mark_commit_passed "$job"
done
fi
)
run "$@"

View file

@ -2,25 +2,69 @@
set -euo pipefail
# The first argument must be the desired installable
INSTALLABLE="$1"
# Build and cache the specified arguments
just() {
if command -v nom &> /dev/null; then
nom build "$@"
else
nix build "$@"
fi
# Build the installable and forward any other arguments too
nix build "$@"
# Find all output paths of the installables and their build dependencies
readarray -t derivations < <(nix path-info --derivation "$@")
readarray -t upload_paths < <(
xargs \
nix-store --query --requisites --include-outputs \
<<< "${derivations[*]}"
)
if [ ! -z ${ATTIC_TOKEN+x} ]; then
nix run --inputs-from . attic -- \
login \
conduit \
"${ATTIC_ENDPOINT:-https://nix.computer.surgery/conduit}" \
"$ATTIC_TOKEN"
echo "Found ${#upload_paths[@]} paths to upload"
# Push the target installable and its build dependencies
nix run --inputs-from . attic -- \
push \
conduit \
"$(nix path-info "$INSTALLABLE" --derivation)" \
"$(nix path-info "$INSTALLABLE")"
else
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
fi
if [ -z ${ATTIC_TOKEN+x} ]; then
echo "\$ATTIC_TOKEN is unset, skipping uploading to the binary cache"
return
fi
nix run --inputs-from . attic#default -- \
login "$ATTIC_SERVER" "$ATTIC_ENDPOINT" "$ATTIC_TOKEN"
# Upload them to Attic. It seems to insist on newlines to separate the
# paths.
(
IFS=$'\n'
nix run --inputs-from . attic#default -- \
push --stdin --no-closure "$ATTIC_SERVER:$ATTIC_CACHE" \
<<< "${upload_paths[*]}"
)
}
# Build and cache things needed for CI
ci() {
installables=(
--inputs-from .
# Keep sorted
".#devShells.x86_64-linux.default"
attic#default
nixpkgs#direnv
nixpkgs#jq
)
just "${installables[@]}"
}
# Build and cache all the package outputs
packages() {
readarray -t installables < <(
nix flake show --json 2> /dev/null |
nix run --inputs-from . nixpkgs#jq -- \
-r \
'.packages."x86_64-linux" | keys | map(".#" + .) | .[]'
)
just "${installables[@]}"
}
pushd "$(git rev-parse --show-toplevel)" > /dev/null
"$@"
popd > /dev/null

View file

@ -1,18 +1,12 @@
[book]
title = "Conduit"
description = "Conduit is a simple, fast and reliable chat server for the Matrix protocol"
title = "Grapevine"
language = "en"
multilingual = false
src = "docs"
src = "book"
[build]
build-dir = "public"
create-missing = true
build-dir = "target/book"
[output.html]
git-repository-url = "https://gitlab.com/famedly/conduit"
edit-url-template = "https://gitlab.com/famedly/conduit/-/edit/next/{path}"
git-repository-icon = "fa-git-square"
[output.html.search]
limit-results = 15
git-repository-url = "https://gitlab.computer.surgery/matrix/grapevine"

12
book/SUMMARY.md Normal file
View file

@ -0,0 +1,12 @@
# Summary
* [Introduction](./introduction.md)
* [Code of conduct](./code-of-conduct.md)
* [Changelog](./changelog.md)
* [Installing](./installing.md)
* [Supported targets](./installing/supported-targets.md)
* [Migrating to/from Conduit](./installing/migrating-conduit.md)
* [Migrating to/from Conduwuit](./installing/migrating-conduwuit.md)
* [Contributing](./contributing.md)
* [Coordinated vulnerability disclosure](./contributing/security.md)
* [Style guide](./contributing/style-guide.md)

340
book/changelog.md Normal file
View file

@ -0,0 +1,340 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog][keep-a-changelog], and this project
adheres to [Semantic Versioning][semver].
[keep-a-changelog]: https://keepachangelog.com/en/1.0.0/
[semver]: https://semver.org/spec/v2.0.0.html
<!--
Changelog sections must appear in the following order if they appear for a
particular version so that attention can be drawn to the important parts:
1. Security
2. Removed
3. Deprecated
4. Changed
5. Fixed
6. Added
Entries within each section should be sorted by merge order. If multiple changes
result in a single entry, choose the merge order of the first or last change.
-->
## Unreleased
<!-- TODO: Change "will be" to "is" on release -->
This will be the first release of Grapevine since it was forked from Conduit
0.7.0.
### Security
1. Prevent XSS via user-uploaded media.
([!8](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/8))
2. Switch from incorrect, hand-rolled `X-Matrix` `Authorization` parser to the
much better implementation provided by Ruma.
([!31](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/31))
* This is not practically exploitable to our knowledge, but this change does
reduce risk.
3. Switch to a more trustworthy password hashing library.
([!29](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/29))
* This is not practically exploitable to our knowledge, but this change does
reduce risk.
4. Don't return redacted events from the search endpoint.
([!41 (f74043d)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/41/diffs?commit_id=f74043df9aa59b406b5086c2e9fa2791a31aa41b),
[!41 (83cdc9c)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/41/diffs?commit_id=83cdc9c708cd7b50fe1ab40ea6a68dcf252c190b))
5. Prevent impersonation in EDUs.
([!41 (da99b07)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/41/diffs?commit_id=da99b0706e683a2d347768efe5b50676abdf7b44))
* `m.signing_key_update` was not affected by this bug.
6. Verify PDUs and transactions against the temporally-correct signing keys.
([!41 (9087da9)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/41/diffs?commit_id=9087da91db8585f34d026a48ba8fdf64865ba14d))
7. Only allow the admin bot to change the room ID that the admin room alias
points to.
([!42](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/42))
### Removed
1. Remove update checker.
([17a0b34](https://gitlab.computer.surgery/matrix/grapevine/-/commit/17a0b3430934fbb8370066ee9dc3506102c5b3f6))
2. Remove optional automatic display name emoji for newly registered users.
([cddf699](https://gitlab.computer.surgery/matrix/grapevine/-/commit/cddf6991f280008b5af5acfab6a9719bb0cfb7f1))
3. Remove admin room welcome message on first startup.
([c9945f6](https://gitlab.computer.surgery/matrix/grapevine/-/commit/c9945f6bbac6e22af6cf955cfa99826d4b04fe8c))
4. Remove incomplete presence implementation.
([f27941d](https://gitlab.computer.surgery/matrix/grapevine/-/commit/f27941d5108acda250921c6a58499a46568fd030))
5. Remove Debian packaging.
([d41f0fb](https://gitlab.computer.surgery/matrix/grapevine/-/commit/d41f0fbf72dae6562358173f425d23bb0e174ca2))
6. Remove Docker packaging.
([!48](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/48))
7. **BREAKING:** Remove unstable room versions.
([!59](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/59))
8. Remove `memory-usage`, `clear-database-caches`, and `clear-service-caches`
admin commands.
([!123](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/123))
* The `memory-usage` command wasn't particularly useful since it can't
actually give you an accurate value in bytes and isn't supported on all
database backends.
* The latter two commands had poor UX and didn't have any noticable effect on
memory consumption.
9. **BREAKING:** Remove the `global.conduit_cache_capacity_modifier` and
`global.pdu_cache_capacity` configuration options.
([!124](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/124))
* Instead, it is now possible to configure each cache capacity individually.
10. Remove jemalloc support.
([!93](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/193))
11. Removed support for MSC3575 (sliding sync), which has been closed.
([!198](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/198))
### Changed
1. **BREAKING:** Rename Conduit to Grapevine.
([360e020](https://gitlab.computer.surgery/matrix/grapevine/-/commit/360e020b644bd012ed438708b661a25fbd124f68))
* The `CONDUIT_VERSION_EXTRA` build-time environment variable has been
renamed to `GRAPEVINE_VERSION_EXTRA`. This change only affects distribution
packagers or non-Nix users who are building from source. If you fall into
one of those categories *and* were explicitly setting this environment
variable, make sure to change its name before building Grapevine.
2. **BREAKING:** Change the default port from 8000 to 6167.
([f205280](https://gitlab.computer.surgery/matrix/grapevine/-/commit/f2052805201f0685d850592b1c96f4861c58fb22))
* If you relied on the default port being 8000, either update your other
configuration to use the new port, or explicitly configure Grapevine's port
to 8000.
3. Improve tracing spans and events.
([!11 (a275db3)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/11/diffs?commit_id=a275db3847b8d5aaa0c651a686c19cfbf9fdb8b5)
(merged as [5172f66](https://gitlab.computer.surgery/matrix/grapevine/-/commit/5172f66c1a90e0e97b67be2897ae59fbc00208a4)),
[!11 (a275db3)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/11/diffs?commit_id=a275db3847b8d5aaa0c651a686c19cfbf9fdb8b5)
(merged as [5172f66](https://gitlab.computer.surgery/matrix/grapevine/-/commit/5172f66c1a90e0e97b67be2897ae59fbc00208a4)),
[!11 (f556fce)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/11/diffs?commit_id=f556fce73eb7beec2ed7b1781df0acdf47920d9c)
(merged as [ac42e0b](https://gitlab.computer.surgery/matrix/grapevine/-/commit/ac42e0bfff6af8677636a3dc1a56701a3255071d)),
[!18](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/18),
[!26](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/26),
[!50](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/50),
[!52](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/52),
[!54](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/54),
[!56](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/56),
[!69](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/69),
[!102](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/102),
[!127](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/127),
[!141](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/141))
4. Stop returning unnecessary member counts from `/_matrix/client/{r0,v3}/sync`.
([!12](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/12))
5. **BREAKING:** Allow federation by default.
([!24](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/24))
* If you relied on federation being disabled by default, make sure to
explicitly disable it before upgrading.
6. **BREAKING:** Remove the `[global]` section from the configuration file.
([!38](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/38))
* Details on how to migrate can be found in the merge request's description.
7. **BREAKING:** Allow specifying multiple transport listeners in the
configuration file.
([!39](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/39))
* Details on how to migrate can be found in the merge request's description.
8. Increase default log level so that span information is included.
([!50](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/50))
9. **BREAKING:** Reorganize config into sections.
([!49](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/49))
* Details on how to migrate can be found in the merge request's description.
10. Try to generate thumbnails for remote media ourselves if the federation
thumbnail request fails.
([!58](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/58))
11. **BREAKING:** Disable unauthenticated access to media by default. Use
`media.allow_unauthenticated_access` to configure this behavior.
([!103](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/103),
[!140](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/140))
12. **BREAKING:** Split CLI into multiple subcommands. The CLI invocation to run
the server is now behind the `serve` command, so `grapevine --config ...`
becomes `grapevine serve --config ...`.
([!108](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/108))
13. **BREAKING:** The path to media files is now specified separately from the
database path.
([!140](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/140),
[!170](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/170))
14. Use trust-dns for all DNS queries, instead of only for SRV records and SRV
record targets in server discovery.
([!156](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/156))
### Fixed
1. Fix questionable numeric conversions.
([71c48f6](https://gitlab.computer.surgery/matrix/grapevine/-/commit/71c48f66c4922813c2dc30b7b875200e06ce4b75))
2. Stop sending no-longer-valid cached responses from the
`/_matrix/client/{r0,v3}/sync` endpoints.
([!7](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/7))
3. Stop returning extra E2EE device updates from `/_matrix/client/{r0,v3}/sync`
as that violates the specification.
([!12](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/12))
4. Make certain membership state transitions work correctly again.
([!16](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/16))
* For example, it was previously impossible to unban users from rooms.
5. Ensure that `tracing-flame` flushes all its data before the process exits.
([!20 (263edcc)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/20/diffs?commit_id=263edcc8a127ad2a541a3bb6ad35a8a459ea5616))
6. Reduce the likelihood of locking up the async runtime.
([!19](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/19))
7. Fix search results not including subsequent pages in certain situations.
([!35 (0cdf032)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/35/diffs?commit_id=0cdf03288ab8fa363c313bd929c8b5183d14ab77))
8. Fix search results missing events in subsequent pages in certain situations.
([!35 (3551a6e)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/35/diffs?commit_id=3551a6ef7a29219b9b30f50a7e8c92b92debcdcf))
9. Only process admin commands if the admin bot is in the admin room.
([!43](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/43))
10. Fix bug where invalid account data from a client could prevent a user from
joining any upgraded rooms and brick rooms that affected users attempted to
upgrade.
([!53](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/53))
11. Fix bug where unexpected keys were deleted from `m.direct` account data
events when joining an upgraded room.
([!53](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/53))
12. Fixed appservice users not receiving federated invites if the local server
isn't already resident in the room
([!80](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/80))
13. Fix bug where, if a server has multiple public keys, only one would be fetched.
([!78](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/78))
14. Fix bug where expired keys may not be re-fetched in some scenarios.
([!78](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/78))
15. Fix bug where signing keys would not be fetched when joining a room if we
hadn't previously seen any signing keys from that server.
([!87](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/87))
16. Fixed bug
([#48](https://gitlab.computer.surgery/matrix/grapevine/-/issues/48))
that caused us to attempt to fetch our own signing keys from ourselves over
federation, and fail ("Won't send federation request to ourselves").
([!96](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/96))
17. Fixed incoming HTTP/2 requests failing federation signature check.
([!104](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/104))
18. Return 403 instead of 500 when joins to a local-only room are denied.
Consequently fixes Heisenbridge being unable to join puppeted users to its
rooms ([#85](https://gitlab.computer.surgery/matrix/grapevine/-/issues/85)).
([!127](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/127))
19. Fix handling of v11 rooms with `m.room.create` event content that passes
the authorization rules but doesn't match other parts of the spec.
([!139](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/139))
20. Fix tiebreaking comparisons between events during state resolution. This
will reduce the rate at which servers disagree about the state of rooms.
([!141](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/141))
21. Fix bug where the backoff state for remote device key queries was not reset
after a successful request, causing an increasing rate of key query failures
over time until a server restart.
([!149](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/149))
22. Fix bug where remote key queries that were skipped because the target server
was in backoff would increment the backoff delay further, leading to a
positive feedback loop.
([!149](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/149))
23. Return 504 M_NOT_YET_UPLOADED instead of 500 M_UNKNOWN when a media file is
present in the database but the contents are missing in the filesystem.
Removing media from the filesystem was the only way to delete media before
[!99](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/99),
so this situation is common.
([!55](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/55))
([!153](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/153))
24. Return 400 M_BAD_ALIAS from
[PUT /_matrix/client/v3/rooms/{roomId}/state/{eventType}/{stateKey}](https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidstateeventtypestatekey)
instead of 400 M_FORBIDDEN when trying to set a canonical alias that does
not exist.
([!158](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/158))
25. Validate schema of new `m.room.canonical_alias` event sent by clients,
rather than silently allowing any contents if the event can't be parsed.
([!158](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/158))
26. Only validate canonical aliases that are new, rather than rather than
revalidating every alias. This makes it possible to add/remove aliases when
some of the existing aliases cannot be validated.
([!158](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/158))
27. Fix read receipts not being sent over federation (or only arbitrarily late)
([!162](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/162))
28. Fix bug where ban reasons would be ignored when the banned user already had
a member event in the room.
([!185](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/185))
29. Stop stripping unknown properties from redaction events before sending them
to clients.
([!191](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/191))
### Added
1. Add various conveniences for users of the Nix package.
([51f9650](https://gitlab.computer.surgery/matrix/grapevine/-/commit/51f9650ca7bc9378690d331192c85fea3c151b58),
[bbb1a6f](https://gitlab.computer.surgery/matrix/grapevine/-/commit/bbb1a6fea45b16e8d4f94c1afbf7fa22c9281f37))
2. Add a NixOS module.
([33e7a46](https://gitlab.computer.surgery/matrix/grapevine/-/commit/33e7a46b5385ea9035c9d13c6775d63e5626a4c7))
3. Add a Conduit compat mode.
([a25f2ec](https://gitlab.computer.surgery/matrix/grapevine/-/commit/a25f2ec95045c5620c98eead88197a0bf13e6bb3))
* **BREAKING:** If you're migrating from Conduit, this option must be enabled
or else your homeserver will refuse to start.
4. Include `GRAPEVINE_VERSION_EXTRA` information in the
`/_matrix/federation/v1/version` endpoint.
([509b70b](https://gitlab.computer.surgery/matrix/grapevine/-/commit/509b70bd827fec23b88e223b57e0df3b42cede34))
5. Allow multiple tracing subscribers to be active at once.
([!20 (7a154f74)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/20/diffs?commit_id=7a154f74166c1309ca5752149e02bbe44cd91431))
6. Allow configuring the filter for `tracing-flame`.
([!20 (507de06)](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/20/diffs?commit_id=507de063f53f52e0cf8e2c1a67215a5ad87bb35a))
7. Collect HTTP response time metrics via OpenTelemetry and optionally expose
them as Prometheus metrics. This functionality is disabled by default.
([!22](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/22))
8. Collect metrics for lookup results (e.g. cache hits/misses).
([!15](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/15),
[!36](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/36))
9. Add configuration options for controlling the log format and colors.
([!46](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/46))
10. Recognize the `!admin` prefix to invoke admin commands.
([!45](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/45))
11. Add the `tracing-filter` admin command to view and change log/metrics/flame
filters dynamically at runtime.
([!49](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/49),
[!164](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/164))
12. Add more configuration options.
([!49](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/49))
* `observability.traces.filter`: The `tracing` filter to use for
OpenTelemetry traces.
* `observability.traces.endpoint`: Where OpenTelemetry should send traces.
* `observability.flame.filter`: The `tracing` filter for `tracing-flame`.
* `observability.flame.filename`: Where `tracing-flame` will write its
output.
* `observability.logs.timestamp`: Whether timestamps should be included in
the logs.
13. Support building nix packages without IFD
([!73](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/73))
14. Report local users getting banned in the server logs and admin room.
([!65](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/65),
[!84](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/84))
15. Added support for Authenticated Media ([MSC3916](https://github.com/matrix-org/matrix-spec-proposals/pull/3916)).
([!58](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/58),
[!111](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/111))
16. **BREAKING:** Added support for configuring and serving
`/.well-known/matrix/...` data.
([!90](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/90),
[!94](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/94))
* The `server_discovery.client.base_url` option is now required.
17. Added support for configuring old verify/signing keys in config (`federation.old_verify_keys`)
([!96](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/96))
18. Added admin commands to delete media
([!99](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/99),
[!102](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/102),
[!148](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/148))
19. Allow configuring the served API components per listener.
([!109](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/109))
20. Include the [`traceresponse` header](https://w3c.github.io/trace-context/#traceresponse-header)
if OpenTelemetry Tracing is in use.
([!112](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/112))
21. Sending SIGHUP to the grapevine process now reloads TLS certificates from
disk.
([!97](https://gitlab.computer.surgery/matrix/grapevine-fork/-/merge_requests/97))
22. Added a federation self-test, perfomed automatically on startup.
([!106](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/106))
23. Added support for HAProxy [proxy protocol](http://www.haproxy.org/download/3.0/doc/proxy-protocol.txt)
listeners.
([!97](https://gitlab.computer.surgery/matrix/grapevine-fork/-/merge_requests/97))
24. Add a `check-config` CLI subcommand to check whether the configuration file
is valid.
([!121](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/121))
25. Add configuration options to tune the value of each cache individually.
([!124](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/124))
26. Allow adding canonical aliases from remote servers.
([!158](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/158))
27. Grapevine now sends a User-Agent header on outbound requests
([!189](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/189))
28. Added the ability to listen on Unix sockets
([!187](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/187))
29. Added the ability to allow invalid TLS certificates
([!203](https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/203))

12
book/code-of-conduct.md Normal file
View file

@ -0,0 +1,12 @@
# Code of conduct
We follow the [Rust Code of Conduct][rust-coc] with some extra points:
* In the absence of evidence to suggest otherwise, assume good faith when
engaging with others
* Moderation actions may be taken for behavior observed outside of
project-specific spaces
* We have limited patience, so violations may skip the warning and directly
result in a ban
[rust-coc]: https://www.rust-lang.org/policies/code-of-conduct

15
book/contributing.md Normal file
View file

@ -0,0 +1,15 @@
# Contributing
Instructions for getting GitLab access can be found on the [sign-in][sign-in]
page.
GitLab access is primarily useful if you'd like to open issues, engage in
discussions on issues or merge requests, or submit your own merge requests.
Note that if the sign-up process is too much trouble and you'd just
like to report an issue, feel free to report it in the Matrix room at
[#grapevine:computer.surgery][room]; someone with GitLab access can open an
issue on your behalf.
[room]: https://matrix.to/#/#grapevine:computer.surgery
[sign-in]: https://gitlab.computer.surgery/users/sign_in

View file

@ -0,0 +1,15 @@
# Coordinated vulnerability disclosure
If you find a security vulnerability in Grapevine, please privately report it to
the Grapevine maintainers in one of the following ways:
* Open a GitLab issue that's marked as confidential
* Create a private, invite-only, E2EE Matrix room and invite the following
users:
* `@charles:computer.surgery`
* `@olivia:computer.surgery`
* `@xiretza:xiretza.xyz`
If the maintainers determine that the vulnerability is shared with Conduit or
other forks, we'll work with their teams to ensure that all affected projects
can release a fix at the same time.

View file

@ -0,0 +1,148 @@
# Style guide
It is recommended that contributors follow this guide to minimize nitpicking on
their merge requests.
However, this guide is not set in stone. It is open to changes as new patterns
emerge, requirements change, compelling arguments are made, and so on. The goal
is to document the existing style so it can be applied consistently, not to
ensure the style never changes.
## Merge requests
When updating a merge request branch, use `git rebase`; do not create merge
commits to merge other branches into a merge request branch.
**Why?** This keeps the history simpler, and lacking merge commits makes it
easier to revert any individual commit.
## Commit messages
[Here's a good article][git-commit] on how to write good commit messages in
general.
Specifically for this project:
* Capitalizing the first letter is not required.
* It is recommended to avoid "conventional commits", as they take away from the
very limited subject line length, and will not be processed in an automated
fashion anyway.
**Why?** The linked article explains why this is good practice.
[git-commit]: https://cbea.ms/git-commit/
## Structuring commits
Try to structure each commit so that it falls into one of the following
categories:
* Refactoring, with no behavior change.
* Changing behavior, with no refactoring.
* Removing behavior, with no refactoring.
* Adding behavior, with no refactoring.
* Rewriting something completely. It is rare that these kinds of commits are
warranted.
If you find yourself wanting to use the word "and" in the commit's subject line,
it should probably be broken into multiple commits.
During code review, it's common to get feedback requesting changes to your
commits. To apply this feedback, do not make and push a new commit containing
the requested change. Instead, include the requested change in the commit of
yours that gave rise to the suggestion. If you are unfamiliar with rewriting
history in git, [this website][git-rebase] is a great tutorial.
**Why?** Small, targeted, and well-explained commits make it easier for
reviewers to verify that a change has its intended effect. Or, for someone
running `git bisect` to find a more granular answer to why their test began
failing.
[git-rebase]: https://git-rebase.io/
## `mod`/`use` order
`mod` and `use` statements should appear in the following order, separated by
a blank line:
1. `use` statements referring to `std`, `alloc`, or `core`, if any.
2. `use` statements referring to other crates, if any.
3. `use` statements referring to `super` or `crate`, if any.
4. Macro definitions that need to be accessible from child modules, if any.
5. `mod` statements, if any.
6. `use` statements referring to modules declared by the above `mod` statements,
if any.
`rust-analyzer` and `rustfmt` automate most of this except points 4 and 5.
**Why?** Consistency is good.
## Testing
When writing tests, be sure to keep the contents of [this article][cargo-test]
in mind. Especially, keeping Cargo unit tests in a dedicated tests file
(mentioned towards the end of the article).
**Why?** The linked article explains why this is good practice.
[cargo-test]: https://matklad.github.io/2021/02/27/delete-cargo-integration-tests.html
## Tracing
`tracing` events should:
1. Start with a capital letter (when applicable).
`tracing` events should not:
1. End with punctuation.
2. Interpolate values into the event's message.
* Instead, add those values as structured fields.
**Why?** Consistency is good. Also, interpolating values into the event message
essentially defeats the point of structured logging.
### Examples
#### 1
```rust,ignore
// This does not conform because it does not start with a capital letter.
info!("started pentametric fan");
// Do this instead:
info!("Started pentametric fan");
```
#### 2
```rust,ignore
// This does not conform because it ends with punctuation.
info!("Started pentametric fan.");
// Do this instead:
info!("Started pentametric fan");
```
#### 3
```rust,ignore
// This does not conform because it interpolates values into the event's
// message.
warn!("Noticed {} discombobulated waneshafts", count);
// Do this instead:
warn!(count, "Noticed discombobulated waneshafts");
```
## Services
Services are abstraction units that live inside the `src/service` directory.
Calling service constructors must not cause side effects, with a few exceptions:
* Database reads.
* Local filesystem reads.
**Why?** This restriction makes it possible to implement subcommands that run
"offline" that reuse service code.

7
book/installing.md Normal file
View file

@ -0,0 +1,7 @@
# Installing
This chapter will explain how to start running a Grapevine instance for the
first time.
**Note:** Pre-built binaries can be found in the [**Supported
targets**](./installing/supported-targets.md) subchapter.

View file

@ -0,0 +1,118 @@
# Migrating to/from Conduit
Before migrating a Conduit instance to Grapevine, make sure to read through
all of the breaking changes listed in [the changelog](../changelog.md).
In order to migrate an existing Conduit instance to/from Grapevine, the
Grapevine config must include `conduit_compat = true`. This parameter cannot
currently be modified after creating the database for the first time, so make
sure to set it when creating a fresh Grapevine instance that you may want to
migrate to a different implementation in the future.
## Config
Grapevine includes several breaking changes to the config schema. We don't
currently have docs on how to migrate an existing config. All breaking config
changes are mentioned in [the changelog](../changelog.md), so the best current
option is to read through those. Feel free to ask for config migration help in
[#grapevine:computer.surgery][room] if something is unclear.
We plan to add [a config migration tool][config-migration-issue] to support
automatically migrating existing configs to the new schema.
[room]: https://matrix.to/#/#grapevine:computer.surgery
[config-migration-issue]: https://gitlab.computer.surgery/matrix/grapevine/-/issues/38
## Filesystem
Grapevine requires database data and media data to live in **separate**,
**non-nested** directories, which are configurable. Here is a typical example,
starting with the filesystem structure:
```text
/var/lib/grapevine
+ database/
| + database-file-1
| + ...
| + database-file-n
+ media/
+ media-file-1
+ ...
+ media-file-n
```
And here is the matching configuration:
```toml
[database]
path = "/var/lib/grapevine/database"
[media.backend]
type = "filesystem"
path = "/var/lib/grapevine/media"
```
On the other hand, Conduit's filesystem layout looks like this:
```text
/var/lib/conduit
+ media/
| + media-file-1
| + ...
| + media-file-n
+ database-file-1
+ ...
+ database-file-n
```
Which **nests** the media directory inside the database directory. Grapevine
will reject this layout, so the filesystem layout must be changed before
starting Grapevine. It is important to migrate the filesystem layout before
starting Grapevine, because otherwise it will create a fresh database instead of
using the existing one.
## Database
Grapevine is currently compatible with the Conduit 0.7.0 database format. It is
still possible to migrate to or from some newer Conduit versions, but it may
require manual intervention or break some functionality.
We plan to add [a migration tool][db-compatibility-mr] to support cleanly
migrating to or from Conduit versions we are not internally compatible with.
| Is migrating from | to | workable? |
|-|-|-|
| Conduit <=0.8.0 | Grapevine | Yes |
| Conduit 0.9.0 | Grapevine | [Yes, with caveats](#conduit-090-to-grapevine) |
| Grapevine | Conduit 0.7.0 | Yes |
| Grapevine | Conduit 0.8.0 | [Yes, with caveats](#grapevine-to-conduit-080-or-090) |
| Grapevine | Conduit 0.9.0 | [Yes, with caveats](#grapevine-to-conduit-080-or-090) |
[db-compatibility-mr]: https://gitlab.computer.surgery/matrix/grapevine/-/merge_requests/85
### Conduit 0.9.0 to Grapevine
Conduit 0.9.0 includes [a database migration][conduit-db-16-migration] that
modifies data that Grapevine doesn't read. Grapevine does not currently
recognize the new database schema version, and will fail to start against
a Conduit 0.9.0 database. Grapevine can start and run without issues if the
version recorded in the databse is rolled back from 16 to 13. It is possible to
do this by editing the database manually, or by modifying Grapevine to change
the version. [This patch][conduit-db-16-patch] is an example of the latter
approach.
[conduit-db-16-migration]: https://gitlab.com/famedly/conduit/-/blob/f8d7ef04e664580e882bac852877b68e7bd3ab1e/src/database/mod.rs#L945
[conduit-db-16-patch]: https://gitlab.computer.surgery/matrix/grapevine/-/commit/fdaa30f0d670c6f04f4e6be5d193f9146d179d95
### Grapevine to Conduit 0.8.0 or 0.9.0
Conduit 0.8.0 added [a new database table][alias_userid-commit] to track which
users created each room alias. Grapevine does not write to this table, so it is
not possible to delete aliases created in Grapevine through the normal
client-server API after migrating to Conduit 0.8.0. It is possible to delete
aliases with the `remove-alias` admin command. Note that this issue also applies
to migrations from Conduit <0.8.0 to Conduit 0.8.0.
There are no additional known issues when migrating to Conduit 0.9.0.
[alias_userid-commit]: https://gitlab.com/famedly/conduit/-/commit/144d548ef739324ca97db12e8cada60ca3e43e09

View file

@ -0,0 +1,6 @@
# Migrating to/from Conduwuit
Current Conduwuit is explicitly incompatible with the Conduit/Grapevine
database format. Some older versions have been migrated to Grapevine
successfully, but we haven't evaluated which versions it is safe to migrate
from yet. Try this at your own risk, and *definitely* take a backup first.

View file

@ -0,0 +1,37 @@
# Supported targets
<!-- Keep this table's rows sorted -->
<!-- markdownlint-disable-next-line MD013 MD033 -->
| Architecture | Vendor | OS | libc | Linkage | Tier | Availability[^1]<sup>, </sup>[^2] |
|-|-|-|-|-|-|-|
| aarch64 | unknown | linux | musl | static | 2 | Nix, [Download](https://gitlab.computer.surgery/api/v4/projects/matrix%2Fgrapevine/jobs/artifacts/main/raw/grapevine-static-aarch64-unknown-linux-musl?job=artifacts) |
| x86_64 | unknown | linux | glibc | dynamic | 1 | Nix |
| x86_64 | unknown | linux | musl | static | 2 | Nix, [Download](https://gitlab.computer.surgery/api/v4/projects/matrix%2Fgrapevine/jobs/artifacts/main/raw/grapevine-static-x86_64-unknown-linux-musl?job=artifacts) |
[^1]: All download links refer to the latest build of the `main` branch.
[^2]: All targets can theoretically also be built from source without Nix.
However, this may require spending several hours debugging build systems.
## Target tiers
The "Tier" column for each target indicates the level of support that target
has. Below is an explanation of what each tier means.
### Tier 1
Tier 1 targets are guaranteed to:
* Build
* Pass the test suite
### Tier 2
Tier 2 targets are guaranteed to:
* Build
## Unsupported targets
Targets that don't appear in the table at the top of this page are unsupported.
At any given time, such targets may or may not build, and may or may not pass
the test suite.

110
book/introduction.md Normal file
View file

@ -0,0 +1,110 @@
# Introduction
Grapevine is a [Matrix][matrix] homeserver that was originally forked from
[Conduit 0.7.0][conduit].
[matrix]: https://matrix.org/
[conduit]: https://gitlab.com/famedly/conduit/-/tree/v0.7.0?ref_type=tags
## Chat with us
Currently, the Matrix room at [#grapevine:computer.surgery][room] serves
multiple purposes:
* General discussion about the project, such as answering questions about it
* Reporting issues with Grapevine, if getting GitLab access is too much trouble
for you
* Providing support to users running Grapevine
* Discussing the development of Grapevine
If you'd like to engage in or observe any of those things, please join!
[room]: https://matrix.to/#/#grapevine:computer.surgery
## Can I use it?
Theoretically yes, but it's not ready for general use yet, because:
* [There aren't any releases][releases-issue].
* [There isn't very much user-facing documentation][docs-issue].
* There have been several additions and breaking changes to the configuration
file format that haven't been documented in detail. This means you'll need to
read the source code to figure out what all the options are and what they do.
If these issues don't scare you away, go for it! (And if you use NixOS, [here's
an example][nixos-example].)
[docs-issue]: https://gitlab.computer.surgery/matrix/grapevine/-/issues/21
[releases-issue]: https://gitlab.computer.surgery/matrix/grapevine/-/issues/18
[nixos-example]: https://gitlab.computer.surgery/charles/servy-fleet/-/blob/main/config/grapevine/default.nix
## Expectations management
This project is run and maintained entirely by volunteers who are doing their
best. Additionally, due to our goals, the development of new features may be
slower than alternatives. We find this to be an acceptable tradeoff considering
the importance of the reliability of a project like this.
## Goals
Our goal is to provide a robust and reliable Matrix homeserver implementation.
In order to accomplish this, we aim to do the following:
* Optimize for maintainability
* Implement automated testing to ensure correctness
* Improve instrumentation to provide real-world data to aid decision-making
## Non-goals
We also have some things we specifically want to avoid as we feel they inhibit
our ability to accomplish our goals:
* macOS or Windows support
* These operating systems are very uncommon in the hobbyist server space, and
we feel our effort is better spent elsewhere.
* Docker support
* Docker tends to generate a high volume of support requests that are solely
due to Docker itself or how users are using Docker. In attempt to mitigate
this, we will not provide first-party Docker images. Instead, we'd recommend
avoiding Docker and either using our pre-built statically-linked binaries
or building from source. However, if your deployment mechanism *requires*
Docker, it should be straightforward to build your own Docker image.
* Configuration via environment variables
* Environment variables restrict the options for structuring configuration and
support for them would increase the maintenance burden. If your deployment
mechanism requires this, consider using an external tool like
[`envsubst`][envsubst].
* Configuration compatibility with Conduit
* To provide a secure and ergonomic configuration experience, breaking changes
are required. However, [we do intend to provide a migration tool to ease
migration][migration-tool].
* Perfect database compatibility with Conduit
* [This issue tracks the database compatibility status][db-compat]. In the
long run, it's inevitable that changes will be made to Conduit that we won't
want to pull in, or that we need to make changes that Conduit won't want to
pull in.
[envsubst]: https://github.com/a8m/envsubst
[migration-tool]: https://gitlab.computer.surgery/matrix/grapevine/-/issues/38
[db-compat]: https://gitlab.computer.surgery/matrix/grapevine/-/issues/17
## Project management
The project's current maintainers[^1] are:
| Matrix username | GitLab username |
|-|-|
| `@charles:computer.surgery` | `charles` |
| `@olivia:computer.surgery` | `olivia` |
| `@xiretza:xiretza.xyz` | `Lambda` |
We would like to expand this list in the future as social trust is built and
technical competence is demonstrated by other contributors.
We require at least 1 approving code review from a maintainer[^2] before changes
can be merged. This number may increase in the future as the list of maintainers
grows.
[^1]: A "maintainer" is someone who has the ability to close issues opened by
someone else and merge changes.
[^2]: A maintainer approving their own change doesn't count.

1
book/migration.md Normal file
View file

@ -0,0 +1 @@
# Migration to/from Conduit

4
clippy.toml Normal file
View file

@ -0,0 +1,4 @@
doc-valid-idents = [
"SemVer",
"..",
]

View file

@ -1,45 +0,0 @@
FROM rust:1.75.0
WORKDIR /workdir
RUN apt-get update && apt-get install -y --no-install-recommends \
libclang-dev
COPY Cargo.toml Cargo.toml
COPY Cargo.lock Cargo.lock
COPY src src
RUN cargo build --release \
&& mv target/release/conduit conduit \
&& rm -rf target
# Install caddy
RUN apt-get update \
&& apt-get install -y \
debian-keyring \
debian-archive-keyring \
apt-transport-https \
curl \
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/gpg.key' \
| gpg --dearmor -o /usr/share/keyrings/caddy-testing-archive-keyring.gpg \
&& curl -1sLf 'https://dl.cloudsmith.io/public/caddy/testing/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-testing.list \
&& apt-get update \
&& apt-get install -y caddy
COPY conduit-example.toml conduit.toml
COPY complement/caddy.json caddy.json
ENV SERVER_NAME=localhost
ENV CONDUIT_CONFIG=/workdir/conduit.toml
RUN sed -i "s/port = 6167/port = 8008/g" conduit.toml
RUN echo "log = \"warn,_=off,sled=off\"" >> conduit.toml
RUN sed -i "s/address = \"127.0.0.1\"/address = \"0.0.0.0\"/g" conduit.toml
EXPOSE 8008 8448
CMD uname -a && \
sed -i "s/#server_name = \"your.server.name\"/server_name = \"${SERVER_NAME}\"/g" conduit.toml && \
sed -i "s/your.server.name/${SERVER_NAME}/g" caddy.json && \
caddy start --config caddy.json > /dev/null && \
/workdir/conduit

View file

@ -1,11 +0,0 @@
# Complement
## What's that?
Have a look at [its repository](https://github.com/matrix-org/complement).
## How do I use it with Conduit?
The script at [`../bin/complement`](../bin/complement) has automation for this.
It takes a few command line arguments, you can read the script to find out what
those are.

View file

@ -1,72 +0,0 @@
{
"logging": {
"logs": {
"default": {
"level": "WARN"
}
}
},
"apps": {
"http": {
"https_port": 8448,
"servers": {
"srv0": {
"listen": [":8448"],
"routes": [{
"match": [{
"host": ["your.server.name"]
}],
"handle": [{
"handler": "subroute",
"routes": [{
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{
"dial": "127.0.0.1:8008"
}]
}]
}]
}],
"terminal": true
}],
"tls_connection_policies": [{
"match": {
"sni": ["your.server.name"]
}
}]
}
}
},
"pki": {
"certificate_authorities": {
"local": {
"name": "Complement CA",
"root": {
"certificate": "/complement/ca/ca.crt",
"private_key": "/complement/ca/ca.key"
},
"intermediate": {
"certificate": "/complement/ca/ca.crt",
"private_key": "/complement/ca/ca.key"
}
}
}
},
"tls": {
"automation": {
"policies": [{
"subjects": ["your.server.name"],
"issuers": [{
"module": "internal"
}],
"on_demand": true
}, {
"issuers": [{
"module": "internal",
"ca": "local"
}]
}]
}
}
}
}

View file

@ -1,67 +0,0 @@
# =============================================================================
# This is the official example config for Conduit.
# If you use it for your server, you will need to adjust it to your own needs.
# At the very least, change the server_name field!
# =============================================================================
[global]
# The server_name is the pretty name of this server. It is used as a suffix for user
# and room ids. Examples: matrix.org, conduit.rs
# The Conduit server needs all /_matrix/ requests to be reachable at
# https://your.server.name/ on port 443 (client-server) and 8448 (federation).
# If that's not possible for you, you can create /.well-known files to redirect
# requests. See
# https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client
# and
# https://matrix.org/docs/spec/server_server/r0.1.4#get-well-known-matrix-server
# for more information
# YOU NEED TO EDIT THIS
#server_name = "your.server.name"
# This is the only directory where Conduit will save its data
database_path = "/var/lib/matrix-conduit/"
database_backend = "rocksdb"
# The port Conduit will be running on. You need to set up a reverse proxy in
# your web server (e.g. apache or nginx), so all requests to /_matrix on port
# 443 and 8448 will be forwarded to the Conduit instance running on this port
# Docker users: Don't change this, you'll need to map an external port to this.
port = 6167
# Max size for uploads
max_request_size = 20_000_000 # in bytes
# Enables registration. If set to false, no users can register on this server.
allow_registration = true
# A static registration token that new users will have to provide when creating
# an account. YOU NEED TO EDIT THIS.
# - Insert a password that users will have to enter on registration
# - Start the line with '#' to remove the condition
registration_token = ""
allow_federation = true
allow_check_for_updates = true
# Enable the display name lightning bolt on registration.
enable_lightning_bolt = true
# Servers listed here will be used to gather public keys of other servers.
# Generally, copying this exactly should be enough. (Currently, Conduit doesn't
# support batched key requests, so this list should only contain Synapse
# servers.)
trusted_servers = ["matrix.org"]
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
# Controls the log verbosity. See also [here][0].
#
# [0]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
#log = "..."
address = "127.0.0.1" # This makes sure Conduit can only be reached using the reverse proxy
#address = "0.0.0.0" # If Conduit is running in a container, make sure the reverse proxy (ie. Traefik) can reach it.

37
debian/README.md vendored
View file

@ -1,37 +0,0 @@
Conduit for Debian
==================
Installation
------------
Information about downloading, building and deploying the Debian package, see
the "Installing Conduit" section in the Deploying docs.
All following sections until "Setting up the Reverse Proxy" be ignored because
this is handled automatically by the packaging.
Configuration
-------------
When installed, Debconf generates the configuration of the homeserver
(host)name, the address and port it listens on. This configuration ends up in
`/etc/matrix-conduit/conduit.toml`.
You can tweak more detailed settings by uncommenting and setting the variables
in `/etc/matrix-conduit/conduit.toml`. This involves settings such as the maximum
file size for download/upload, enabling federation, etc.
Running
-------
The package uses the `matrix-conduit.service` systemd unit file to start and
stop Conduit. It loads the configuration file mentioned above to set up the
environment before running the server.
This package assumes by default that Conduit will be placed behind a reverse
proxy such as Apache or nginx. This default deployment entails just listening
on `127.0.0.1` and the free port `6167` and is reachable via a client using the URL
<http://localhost:6167>.
At a later stage this packaging may support also setting up TLS and running
stand-alone. In this case, however, you need to set up some certificates and
renewal, for it to work properly.

17
debian/config vendored
View file

@ -1,17 +0,0 @@
#!/bin/sh
set -e
# Source debconf library.
. /usr/share/debconf/confmodule
# Ask for the Matrix homeserver name, address and port.
db_input high matrix-conduit/hostname || true
db_go
db_input low matrix-conduit/address || true
db_go
db_input medium matrix-conduit/port || true
db_go
exit 0

View file

@ -1,47 +0,0 @@
[Unit]
Description=Conduit Matrix homeserver
After=network.target
[Service]
DynamicUser=yes
User=_matrix-conduit
Group=_matrix-conduit
Type=simple
AmbientCapabilities=
CapabilityBoundingSet=
LockPersonality=yes
MemoryDenyWriteExecute=yes
NoNewPrivileges=yes
ProtectClock=yes
ProtectControlGroups=yes
ProtectHome=yes
ProtectHostname=yes
ProtectKernelLogs=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectSystem=strict
PrivateDevices=yes
PrivateMounts=yes
PrivateTmp=yes
PrivateUsers=yes
RemoveIPC=yes
RestrictAddressFamilies=AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
StateDirectory=matrix-conduit
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
ExecStart=/usr/sbin/matrix-conduit
Restart=on-failure
RestartSec=10
StartLimitInterval=1m
StartLimitBurst=5
[Install]
WantedBy=multi-user.target

104
debian/postinst vendored
View file

@ -1,104 +0,0 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
CONDUIT_CONFIG_FILE="${CONDUIT_CONFIG_PATH}/conduit.toml"
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit/
case "$1" in
configure)
# Create the `_matrix-conduit` user if it does not exist yet.
if ! getent passwd _matrix-conduit > /dev/null ; then
echo 'Adding system user for the Conduit Matrix homeserver' 1>&2
adduser --system --group --quiet \
--home "$CONDUIT_DATABASE_PATH" \
--disabled-login \
--force-badname \
_matrix-conduit
fi
# Create the database path if it does not exist yet and fix up ownership
# and permissions.
mkdir -p "$CONDUIT_DATABASE_PATH"
chown _matrix-conduit "$CONDUIT_DATABASE_PATH"
chmod 700 "$CONDUIT_DATABASE_PATH"
if [ ! -e "$CONDUIT_CONFIG_FILE" ]; then
# Write the debconf values in the config.
db_get matrix-conduit/hostname
CONDUIT_SERVER_NAME="$RET"
db_get matrix-conduit/address
CONDUIT_ADDRESS="$RET"
db_get matrix-conduit/port
CONDUIT_PORT="$RET"
mkdir -p "$CONDUIT_CONFIG_PATH"
cat > "$CONDUIT_CONFIG_FILE" << EOF
[global]
# The server_name is the pretty name of this server. It is used as a suffix for
# user and room ids. Examples: matrix.org, conduit.rs
# The Conduit server needs all /_matrix/ requests to be reachable at
# https://your.server.name/ on port 443 (client-server) and 8448 (federation).
# If that's not possible for you, you can create /.well-known files to redirect
# requests. See
# https://matrix.org/docs/spec/client_server/latest#get-well-known-matrix-client
# and
# https://matrix.org/docs/spec/server_server/r0.1.4#get-well-known-matrix-server
# for more information
server_name = "${CONDUIT_SERVER_NAME}"
# This is the only directory where Conduit will save its data.
database_path = "${CONDUIT_DATABASE_PATH}"
database_backend = "rocksdb"
# The address Conduit will be listening on.
# By default the server listens on address 0.0.0.0. Change this to 127.0.0.1 to
# only listen on the localhost when using a reverse proxy.
address = "${CONDUIT_ADDRESS}"
# The port Conduit will be running on. You need to set up a reverse proxy in
# your web server (e.g. apache or nginx), so all requests to /_matrix on port
# 443 and 8448 will be forwarded to the Conduit instance running on this port
# Docker users: Don't change this, you'll need to map an external port to this.
port = ${CONDUIT_PORT}
# Max size for uploads
max_request_size = 20_000_000 # in bytes
# Enables registration. If set to false, no users can register on this server.
allow_registration = true
# A static registration token that new users will have to provide when creating
# an account.
# - Insert a password that users will have to enter on registration
# - Start the line with '#' to remove the condition
#registration_token = ""
allow_federation = true
allow_check_for_updates = true
# Enable the display name lightning bolt on registration.
enable_lightning_bolt = true
# Servers listed here will be used to gather public keys of other servers.
# Generally, copying this exactly should be enough. (Currently, Conduit doesn't
# support batched key requests, so this list should only contain Synapse
# servers.)
trusted_servers = ["matrix.org"]
#max_concurrent_requests = 100 # How many requests Conduit sends to other servers at the same time
# Controls the log verbosity. See also [here][0].
#
# [0]: https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html#directives
#log = "..."
EOF
fi
;;
esac
#DEBHELPER#

27
debian/postrm vendored
View file

@ -1,27 +0,0 @@
#!/bin/sh
set -e
. /usr/share/debconf/confmodule
CONDUIT_CONFIG_PATH=/etc/matrix-conduit
CONDUIT_DATABASE_PATH=/var/lib/matrix-conduit
case $1 in
purge)
# Remove debconf changes from the db
db_purge
# Per https://www.debian.org/doc/debian-policy/ch-files.html#behavior
# "configuration files must be preserved when the package is removed, and
# only deleted when the package is purged."
if [ -d "$CONDUIT_CONFIG_PATH" ]; then
rm -r "$CONDUIT_CONFIG_PATH"
fi
if [ -d "$CONDUIT_DATABASE_PATH" ]; then
rm -r "$CONDUIT_DATABASE_PATH"
fi
;;
esac
#DEBHELPER#

21
debian/templates vendored
View file

@ -1,21 +0,0 @@
Template: matrix-conduit/hostname
Type: string
Default: localhost
Description: The server (host)name of the Matrix homeserver
This is the hostname the homeserver will be reachable at via a client.
.
If set to "localhost", you can connect with a client locally and clients
from other hosts and also other homeservers will not be able to reach you!
Template: matrix-conduit/address
Type: string
Default: 127.0.0.1
Description: The listen address of the Matrix homeserver
This is the address the homeserver will listen on. Leave it set to 127.0.0.1
when using a reverse proxy.
Template: matrix-conduit/port
Type: string
Default: 6167
Description: The port of the Matrix homeserver
This port is most often just accessed by a reverse proxy.

View file

@ -1,84 +0,0 @@
# syntax=docker/dockerfile:1
# ---------------------------------------------------------------------------------------------------------
# This Dockerfile is intended to be built as part of Conduit's CI pipeline.
# It does not build Conduit in Docker, but just copies the matching build artifact from the build jobs.
#
# It is mostly based on the normal Conduit Dockerfile, but adjusted in a few places to maximise caching.
# Credit's for the original Dockerfile: Weasy666.
# ---------------------------------------------------------------------------------------------------------
FROM docker.io/alpine:3.16.0@sha256:4ff3ca91275773af45cb4b0834e12b7eb47d1c18f770a0b151381cd227f4c253 AS runner
# Standard port on which Conduit launches.
# You still need to map the port when using the docker command or docker-compose.
EXPOSE 6167
# Users are expected to mount a volume to this directory:
ARG DEFAULT_DB_PATH=/var/lib/matrix-conduit
ENV CONDUIT_PORT=6167 \
CONDUIT_ADDRESS="0.0.0.0" \
CONDUIT_DATABASE_PATH=${DEFAULT_DB_PATH} \
CONDUIT_CONFIG=''
# └─> Set no config file to do all configuration with env vars
# Conduit needs:
# ca-certificates: for https
# iproute2: for `ss` for the healthcheck script
RUN apk add --no-cache \
ca-certificates \
iproute2
ARG CREATED
ARG VERSION
ARG GIT_REF
# Labels according to https://github.com/opencontainers/image-spec/blob/master/annotations.md
# including a custom label specifying the build command
LABEL org.opencontainers.image.created=${CREATED} \
org.opencontainers.image.authors="Conduit Contributors" \
org.opencontainers.image.title="Conduit" \
org.opencontainers.image.version=${VERSION} \
org.opencontainers.image.vendor="Conduit Contributors" \
org.opencontainers.image.description="A Matrix homeserver written in Rust" \
org.opencontainers.image.url="https://conduit.rs/" \
org.opencontainers.image.revision=${GIT_REF} \
org.opencontainers.image.source="https://gitlab.com/famedly/conduit.git" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.documentation="https://gitlab.com/famedly/conduit" \
org.opencontainers.image.ref.name=""
# Test if Conduit is still alive, uses the same endpoint as Element
COPY ./docker/healthcheck.sh /srv/conduit/healthcheck.sh
HEALTHCHECK --start-period=5s --interval=5s CMD ./healthcheck.sh
# Improve security: Don't run stuff as root, that does not need to run as root:
# Most distros also use 1000:1000 for the first real user, so this should resolve volume mounting problems.
ARG USER_ID=1000
ARG GROUP_ID=1000
RUN set -x ; \
deluser --remove-home www-data ; \
addgroup -S -g ${GROUP_ID} conduit 2>/dev/null ; \
adduser -S -u ${USER_ID} -D -H -h /srv/conduit -G conduit -g conduit conduit 2>/dev/null ; \
addgroup conduit conduit 2>/dev/null && exit 0 ; exit 1
# Change ownership of Conduit files to conduit user and group
RUN chown -cR conduit:conduit /srv/conduit && \
chmod +x /srv/conduit/healthcheck.sh && \
mkdir -p ${DEFAULT_DB_PATH} && \
chown -cR conduit:conduit ${DEFAULT_DB_PATH}
# Change user to conduit
USER conduit
# Set container home directory
WORKDIR /srv/conduit
# Run Conduit and print backtraces on panics
ENV RUST_BACKTRACE=1
ENTRYPOINT [ "/srv/conduit/conduit" ]
# Depending on the target platform (e.g. "linux/arm/v7", "linux/arm64/v8", or "linux/amd64")
# copy the matching binary into this docker image
ARG TARGETPLATFORM
COPY --chown=conduit:conduit ./$TARGETPLATFORM /srv/conduit/conduit

View file

@ -1,19 +0,0 @@
#!/bin/sh
# If the config file does not contain a default port and the CONDUIT_PORT env is not set, create
# try to get port from process list
if [ -z "${CONDUIT_PORT}" ]; then
CONDUIT_PORT=$(ss -tlpn | grep conduit | grep -m1 -o ':[0-9]*' | grep -m1 -o '[0-9]*')
fi
# If CONDUIT_ADDRESS is not set try to get the address from the process list
if [ -z "${CONDUIT_ADDRESS}" ]; then
CONDUIT_ADDRESS=$(ss -tlpn | awk -F ' +|:' '/conduit/ { print $4 }')
fi
# The actual health check.
# We try to first get a response on HTTP and when that fails on HTTPS and when that fails, we exit with code 1.
# TODO: Change this to a single wget call. Do we have a config value that we can check for that?
wget --no-verbose --tries=1 --spider "http://${CONDUIT_ADDRESS}:${CONDUIT_PORT}/_matrix/client/versions" || \
wget --no-verbose --tries=1 --spider "https://${CONDUIT_ADDRESS}:${CONDUIT_PORT}/_matrix/client/versions" || \
exit 1

View file

@ -1,12 +0,0 @@
# Summary
- [Introduction](introduction.md)
- [Configuration](configuration.md)
- [Deploying](deploying.md)
- [Generic](deploying/generic.md)
- [Debian](deploying/debian.md)
- [Docker](deploying/docker.md)
- [NixOS](deploying/nixos.md)
- [TURN](turn.md)
- [Appservices](appservices.md)

View file

@ -1,61 +0,0 @@
# Setting up Appservices
## Getting help
If you run into any problems while setting up an Appservice, write an email to `timo@koesters.xyz`, ask us in [#conduit:fachschaften.org](https://matrix.to/#/#conduit:fachschaften.org) or [open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new).
## Set up the appservice - general instructions
Follow whatever instructions are given by the appservice. This usually includes
downloading, changing its config (setting domain, homeserver url, port etc.)
and later starting it.
At some point the appservice guide should ask you to add a registration yaml
file to the homeserver. In Synapse you would do this by adding the path to the
homeserver.yaml, but in Conduit you can do this from within Matrix:
First, go into the #admins room of your homeserver. The first person that
registered on the homeserver automatically joins it. Then send a message into
the room like this:
@conduit:your.server.name: register-appservice
```
paste
the
contents
of
the
yaml
registration
here
```
You can confirm it worked by sending a message like this:
`@conduit:your.server.name: list-appservices`
The @conduit bot should answer with `Appservices (1): your-bridge`
Then you are done. Conduit will send messages to the appservices and the
appservice can send requests to the homeserver. You don't need to restart
Conduit, but if it doesn't work, restarting while the appservice is running
could help.
## Appservice-specific instructions
### Remove an appservice
To remove an appservice go to your admin room and execute
`@conduit:your.server.name: unregister-appservice <name>`
where `<name>` one of the output of `list-appservices`.
### Tested appservices
These appservices have been tested and work with Conduit without any extra steps:
- [matrix-appservice-discord](https://github.com/Half-Shot/matrix-appservice-discord)
- [mautrix-hangouts](https://github.com/mautrix/hangouts/)
- [mautrix-telegram](https://github.com/mautrix/telegram/)
- [mautrix-signal](https://github.com/mautrix/signal/) from version `0.2.2` forward.
- [heisenbridge](https://github.com/hifi/heisenbridge/)

View file

@ -1,110 +0,0 @@
# Configuration
**Conduit** is configured using a TOML file. The configuration file is loaded from the path specified by the `CONDUIT_CONFIG` environment variable.
> **Note:** The configuration file is required to run Conduit. If the `CONDUIT_CONFIG` environment variable is not set, Conduit will exit with an error.
> **Note:** If you update the configuration file, you must restart Conduit for the changes to take effect
Conduit's configuration file is divided into the following sections:
- [Global](#global)
- [TLS](#tls)
- [Proxy](#proxy)
## Global
The `global` section contains the following fields:
> **Note:** The `*` symbol indicates that the field is required, and the values in **parentheses** are the possible values
| Field | Type | Description | Default |
| --- | --- | --- | --- |
| `address` | `string` | The address to bind to | `"127.0.0.1"` |
| `port` | `integer` | The port to bind to | `8000` |
| `tls` | `table` | See the [TLS configuration](#tls) | N/A |
| `server_name`_*_ | `string` | The server name | N/A |
| `database_backend`_*_ | `string` | The database backend to use (`"rocksdb"` *recommended*, `"sqlite"`) | N/A |
| `database_path`_*_ | `string` | The path to the database file/dir | N/A |
| `db_cache_capacity_mb` | `float` | The cache capacity, in MB | `300.0` |
| `enable_lightning_bolt` | `boolean` | Add `⚡️` emoji to end of user's display name | `true` |
| `allow_check_for_updates` | `boolean` | Allow Conduit to check for updates | `true` |
| `conduit_cache_capacity_modifier` | `float` | The value to multiply the default cache capacity by | `1.0` |
| `rocksdb_max_open_files` | `integer` | The maximum number of open files | `1000` |
| `pdu_cache_capacity` | `integer` | The maximum number of Persisted Data Units (PDUs) to cache | `150000` |
| `cleanup_second_interval` | `integer` | How often conduit should clean up the database, in seconds | `60` |
| `max_request_size` | `integer` | The maximum request size, in bytes | `20971520` (20 MiB) |
| `max_concurrent_requests` | `integer` | The maximum number of concurrent requests | `100` |
| `max_fetch_prev_events` | `integer` | The maximum number of previous events to fetch per request if conduit notices events are missing | `100` |
| `allow_registration` | `boolean` | Opens your homeserver to public registration | `false` |
| `registration_token` | `string` | The token users need to have when registering to your homeserver | N/A |
| `allow_encryption` | `boolean` | Allow users to enable encryption in their rooms | `true` |
| `allow_federation` | `boolean` | Allow federation with other servers | `true` |
| `allow_room_creation` | `boolean` | Allow users to create rooms | `true` |
| `allow_unstable_room_versions` | `boolean` | Allow users to create and join rooms with unstable versions | `true` |
| `default_room_version` | `string` | The default room version (`"6"`-`"10"`)| `"10"` |
| `allow_jaeger` | `boolean` | Allow Jaeger tracing | `false` |
| `tracing_flame` | `boolean` | Enable flame tracing | `false` |
| `proxy` | `table` | See the [Proxy configuration](#proxy) | N/A |
| `jwt_secret` | `string` | The secret used in the JWT to enable JWT login without it a 400 error will be returned | N/A |
| `trusted_servers` | `array` | The list of trusted servers to gather public keys of offline servers | `["matrix.org"]` |
| `log` | `string` | The log verbosity to use | `"warn"` |
| `turn_username` | `string` | The TURN username | `""` |
| `turn_password` | `string` | The TURN password | `""` |
| `turn_uris` | `array` | The TURN URIs | `[]` |
| `turn_secret` | `string` | The TURN secret | `""` |
| `turn_ttl` | `integer` | The TURN TTL in seconds | `86400` |
| `emergency_password` | `string` | Set a password to login as the `conduit` user in case of emergency | N/A |
### TLS
The `tls` table contains the following fields:
- `certs`: The path to the public PEM certificate
- `key`: The path to the PEM private key
#### Example
```toml
[global.tls]
certs = "/path/to/cert.pem"
key = "/path/to/key.pem"
```
### Proxy
You can choose what requests conduit should proxy (if any). The `proxy` table contains the following fields
#### Global
The global option will proxy all outgoing requests. The `global` table contains the following fields:
- `url`: The URL of the proxy server
##### Example
```toml
[global.proxy.global]
url = "https://example.com"
```
#### By domain
An array of tables that contain the following fields:
- `url`: The URL of the proxy server
- `include`: Domains that should be proxied (assumed to be `["*"]` if unset)
- `exclude`: Domains that should not be proxied (takes precedent over `include`)
Both `include` and `exclude` allow for glob pattern matching.
##### Example
In this example, all requests to domains ending in `.onion` and `matrix.secretly-an-onion-domain.xyz`
will be proxied via `socks://localhost:9050`, except for domains ending in `.myspecial.onion`. You can add as many `by_domain` tables as you need.
```toml
[[global.proxy.by_domain]]
url = "socks5://localhost:9050"
include = ["*.onion", "matrix.secretly-an-onion-domain.xyz"]
exclude = ["*.clearnet.onion"]
```
### Example
> **Note:** The following example is a minimal configuration file. You should replace the values with your own.
```toml
[global]
{{#include ../conduit-example.toml:22:}}
```

View file

@ -1,3 +0,0 @@
# Deploying
This chapter describes various ways to deploy Conduit.

View file

@ -1 +0,0 @@
{{#include ../../debian/README.md}}

View file

@ -1,69 +0,0 @@
# Conduit - Behind Traefik Reverse Proxy
version: '3'
services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
### then you are ready to go.
image: matrixconduit/matrix-conduit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d
# build:
# context: .
# args:
# CREATED: '2021-03-16T08:18:27Z'
# VERSION: '0.1.0'
# LOCAL: 'false'
# GIT_REF: origin/master
restart: unless-stopped
volumes:
- db:/var/lib/matrix-conduit/
networks:
- proxy
environment:
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit/
CONDUIT_DATABASE_BACKEND: rocksdb
CONDUIT_PORT: 6167
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUIT_ALLOW_REGISTRATION: 'true'
#CONDUIT_REGISTRATION_TOKEN: '' # require password for registration
CONDUIT_ALLOW_FEDERATION: 'true'
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
CONDUIT_ADDRESS: 0.0.0.0
CONDUIT_CONFIG: '' # Ignore this
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
# and in the docker-compose override file.
well-known:
image: nginx:latest
restart: unless-stopped
volumes:
- ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf # the config to serve the .well-known/matrix files
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
# restart: unless-stopped
# volumes:
# - ./element_config.json:/app/config.json
# networks:
# - proxy
# depends_on:
# - homeserver
volumes:
db:
networks:
# This is the network Traefik listens to, if your network has a different
# name, don't forget to change it here and in the docker-compose.override.yml
proxy:
external: true

View file

@ -1,45 +0,0 @@
# Conduit - Traefik Reverse Proxy Labels
version: '3'
services:
homeserver:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
- "traefik.http.routers.to-conduit.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Conduit is hosted
- "traefik.http.routers.to-conduit.tls=true"
- "traefik.http.routers.to-conduit.tls.certresolver=letsencrypt"
- "traefik.http.routers.to-conduit.middlewares=cors-headers@docker"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS"
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
# and in the docker-compose file.
well-known:
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.to-matrix-wellknown.rule=Host(`<SUBDOMAIN>.<DOMAIN>`) && PathPrefix(`/.well-known/matrix`)"
- "traefik.http.routers.to-matrix-wellknown.tls=true"
- "traefik.http.routers.to-matrix-wellknown.tls.certresolver=letsencrypt"
- "traefik.http.routers.to-matrix-wellknown.middlewares=cors-headers@docker"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowOriginList=*"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowHeaders=Origin, X-Requested-With, Content-Type, Accept, Authorization"
- "traefik.http.middlewares.cors-headers.headers.accessControlAllowMethods=GET, POST, PUT, DELETE, OPTIONS"
### Uncomment this if you uncommented Element-Web App in the docker-compose.yml
# element-web:
# labels:
# - "traefik.enable=true"
# - "traefik.docker.network=proxy" # Change this to the name of your Traefik docker proxy network
# - "traefik.http.routers.to-element-web.rule=Host(`<SUBDOMAIN>.<DOMAIN>`)" # Change to the address on which Element-Web is hosted
# - "traefik.http.routers.to-element-web.tls=true"
# - "traefik.http.routers.to-element-web.tls.certresolver=letsencrypt"

View file

@ -1,96 +0,0 @@
# Conduit - Behind Traefik Reverse Proxy
version: '3'
services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use the Docker Hub image,
### then you are ready to go.
image: matrixconduit/matrix-conduit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d
# build:
# context: .
# args:
# CREATED: '2021-03-16T08:18:27Z'
# VERSION: '0.1.0'
# LOCAL: 'false'
# GIT_REF: origin/master
restart: unless-stopped
volumes:
- db:/srv/conduit/.local/share/conduit
### Uncomment if you want to use conduit.toml to configure Conduit
### Note: Set env vars will override conduit.toml values
# - ./conduit.toml:/srv/conduit/conduit.toml
networks:
- proxy
environment:
CONDUIT_SERVER_NAME: localhost:6167 # replace with your own name
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
CONDUIT_ALLOW_REGISTRATION : 'true'
### Uncomment and change values as desired
# CONDUIT_ADDRESS: 0.0.0.0
# CONDUIT_PORT: 6167
# CONDUIT_REGISTRATION_TOKEN: '' # require password for registration
# CONDUIT_CONFIG: '/srv/conduit/conduit.toml' # if you want to configure purely by env vars, set this to an empty string ''
# Available levels are: error, warn, info, debug, trace - more info at: https://docs.rs/env_logger/*/env_logger/#enabling-logging
# CONDUIT_ALLOW_ENCRYPTION: 'true'
# CONDUIT_ALLOW_FEDERATION: 'true'
# CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
# CONDUIT_DATABASE_PATH: /srv/conduit/.local/share/conduit
# CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
# We need some way to server the client and server .well-known json. The simplest way is to use a nginx container
# to serve those two as static files. If you want to use a different way, delete or comment the below service, here
# and in the docker-compose override file.
well-known:
image: nginx:latest
restart: unless-stopped
volumes:
- ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf # the config to serve the .well-known/matrix files
- ./nginx/www:/var/www/ # location of the client and server .well-known-files
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
# restart: unless-stopped
# volumes:
# - ./element_config.json:/app/config.json
# networks:
# - proxy
# depends_on:
# - homeserver
traefik:
image: "traefik:latest"
container_name: "traefik"
restart: "unless-stopped"
ports:
- "80:80"
- "443:443"
volumes:
- "/var/run/docker.sock:/var/run/docker.sock"
# - "./traefik_config:/etc/traefik"
- "acme:/etc/traefik/acme"
labels:
- "traefik.enable=true"
# middleware redirect
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
# global redirect to https
- "traefik.http.routers.redirs.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.redirs.entrypoints=http"
- "traefik.http.routers.redirs.middlewares=redirect-to-https"
networks:
- proxy
volumes:
db:
acme:
networks:
proxy:

View file

@ -1,53 +0,0 @@
# Conduit
version: '3'
services:
homeserver:
### If you already built the Conduit image with 'docker build' or want to use a registry image,
### then you are ready to go.
image: matrixconduit/matrix-conduit:latest
### If you want to build a fresh image from the sources, then comment the image line and uncomment the
### build lines. If you want meaningful labels in your built Conduit image, you should run docker-compose like this:
### CREATED=$(date -u +'%Y-%m-%dT%H:%M:%SZ') VERSION=$(grep -m1 -o '[0-9].[0-9].[0-9]' Cargo.toml) docker-compose up -d
# build:
# context: .
# args:
# CREATED: '2021-03-16T08:18:27Z'
# VERSION: '0.1.0'
# LOCAL: 'false'
# GIT_REF: origin/master
restart: unless-stopped
ports:
- 8448:6167
volumes:
- db:/var/lib/matrix-conduit/
environment:
CONDUIT_SERVER_NAME: your.server.name # EDIT THIS
CONDUIT_DATABASE_PATH: /var/lib/matrix-conduit/
CONDUIT_DATABASE_BACKEND: rocksdb
CONDUIT_PORT: 6167
CONDUIT_MAX_REQUEST_SIZE: 20_000_000 # in bytes, ~20 MB
CONDUIT_ALLOW_REGISTRATION: 'true'
CONDUIT_ALLOW_FEDERATION: 'true'
CONDUIT_ALLOW_CHECK_FOR_UPDATES: 'true'
CONDUIT_TRUSTED_SERVERS: '["matrix.org"]'
#CONDUIT_MAX_CONCURRENT_REQUESTS: 100
CONDUIT_ADDRESS: 0.0.0.0
CONDUIT_CONFIG: '' # Ignore this
#
### Uncomment if you want to use your own Element-Web App.
### Note: You need to provide a config.json for Element and you also need a second
### Domain or Subdomain for the communication between Element and Conduit
### Config-Docs: https://github.com/vector-im/element-web/blob/develop/docs/config.md
# element-web:
# image: vectorim/element-web:latest
# restart: unless-stopped
# ports:
# - 8009:80
# volumes:
# - ./element_config.json:/app/config.json
# depends_on:
# - homeserver
volumes:
db:

View file

@ -1,216 +0,0 @@
# Conduit for Docker
> **Note:** To run and use Conduit you should probably use it with a Domain or Subdomain behind a reverse proxy (like Nginx, Traefik, Apache, ...) with a Lets Encrypt certificate.
## Docker
To run Conduit with Docker you can either build the image yourself or pull it from a registry.
### Use a registry
OCI images for Conduit are available in the registries listed below. We recommend using the image tagged as `latest` from GitLab's own registry.
| Registry | Image | Size | Notes |
| --------------- | --------------------------------------------------------------- | ----------------------------- | ---------------------- |
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:latest][gl] | ![Image Size][shield-latest] | Stable image. |
| Docker Hub | [docker.io/matrixconduit/matrix-conduit:latest][dh] | ![Image Size][shield-latest] | Stable image. |
| GitLab Registry | [registry.gitlab.com/famedly/conduit/matrix-conduit:next][gl] | ![Image Size][shield-next] | Development version. |
| Docker Hub | [docker.io/matrixconduit/matrix-conduit:next][dh] | ![Image Size][shield-next] | Development version. |
[dh]: https://hub.docker.com/r/matrixconduit/matrix-conduit
[gl]: https://gitlab.com/famedly/conduit/container_registry/2497937
[shield-latest]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/latest
[shield-next]: https://img.shields.io/docker/image-size/matrixconduit/matrix-conduit/next
Use
```bash
docker image pull <link>
```
to pull it to your machine.
### Build using a dockerfile
The Dockerfile provided by Conduit has two stages, each of which creates an image.
1. **Builder:** Builds the binary from local context or by cloning a git revision from the official repository.
2. **Runner:** Copies the built binary from **Builder** and sets up the runtime environment, like creating a volume to persist the database and applying the correct permissions.
To build the image you can use the following command
```bash
docker build --tag matrixconduit/matrix-conduit:latest .
```
which also will tag the resulting image as `matrixconduit/matrix-conduit:latest`.
### Run
When you have the image you can simply run it with
```bash
docker run -d -p 8448:6167 \
-v db:/var/lib/matrix-conduit/ \
-e CONDUIT_SERVER_NAME="your.server.name" \
-e CONDUIT_DATABASE_BACKEND="rocksdb" \
-e CONDUIT_ALLOW_REGISTRATION=true \
-e CONDUIT_ALLOW_FEDERATION=true \
-e CONDUIT_MAX_REQUEST_SIZE="20_000_000" \
-e CONDUIT_TRUSTED_SERVERS="[\"matrix.org\"]" \
-e CONDUIT_MAX_CONCURRENT_REQUESTS="100" \
--name conduit <link>
```
or you can use [docker-compose](#docker-compose).
The `-d` flag lets the container run in detached mode. You now need to supply a `conduit.toml` config file, an example can be found [here](../configuration.md).
You can pass in different env vars to change config values on the fly. You can even configure Conduit completely by using env vars, but for that you need
to pass `-e CONDUIT_CONFIG=""` into your container. For an overview of possible values, please take a look at the `docker-compose.yml` file.
If you just want to test Conduit for a short time, you can use the `--rm` flag, which will clean up everything related to your container after you stop it.
### Docker-compose
If the `docker run` command is not for you or your setup, you can also use one of the provided `docker-compose` files.
Depending on your proxy setup, you can use one of the following files;
- If you already have a `traefik` instance set up, use [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml)
- If you don't have a `traefik` instance set up (or any other reverse proxy), use [`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)
- For any other reverse proxy, use [`docker-compose.yml`](docker-compose.yml)
When picking the traefik-related compose file, rename it so it matches `docker-compose.yml`, and
rename the override file to `docker-compose.override.yml`. Edit the latter with the values you want
for your server.
Additional info about deploying Conduit can be found [here](generic.md).
### Build
To build the Conduit image with docker-compose, you first need to open and modify the `docker-compose.yml` file. There you need to comment the `image:` option and uncomment the `build:` option. Then call docker-compose with:
```bash
docker-compose up
```
This will also start the container right afterwards, so if want it to run in detached mode, you also should use the `-d` flag.
### Run
If you already have built the image or want to use one from the registries, you can just start the container and everything else in the compose file in detached mode with:
```bash
docker-compose up -d
```
> **Note:** Don't forget to modify and adjust the compose file to your needs.
### Use Traefik as Proxy
As a container user, you probably know about Traefik. It is a easy to use reverse proxy for making
containerized app and services available through the web. With the two provided files,
[`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and
[`docker-compose.override.yml`](docker-compose.override.yml), it is equally easy to deploy
and use Conduit, with a little caveat. If you already took a look at the files, then you should have
seen the `well-known` service, and that is the little caveat. Traefik is simply a proxy and
loadbalancer and is not able to serve any kind of content, but for Conduit to federate, we need to
either expose ports `443` and `8448` or serve two endpoints `.well-known/matrix/client` and
`.well-known/matrix/server`.
With the service `well-known` we use a single `nginx` container that will serve those two files.
So...step by step:
1. Copy [`docker-compose.for-traefik.yml`](docker-compose.for-traefik.yml) (or
[`docker-compose.with-traefik.yml`](docker-compose.with-traefik.yml)) and [`docker-compose.override.yml`](docker-compose.override.yml) from the repository and remove `.for-traefik` (or `.with-traefik`) from the filename.
2. Open both files and modify/adjust them to your needs. Meaning, change the `CONDUIT_SERVER_NAME` and the volume host mappings according to your needs.
3. Create the `conduit.toml` config file, an example can be found [here](../configuration.md), or set `CONDUIT_CONFIG=""` and configure Conduit per env vars.
4. Uncomment the `element-web` service if you want to host your own Element Web Client and create a `element_config.json`.
5. Create the files needed by the `well-known` service.
- `./nginx/matrix.conf` (relative to the compose file, you can change this, but then also need to change the volume mapping)
```nginx
server {
server_name <SUBDOMAIN>.<DOMAIN>;
listen 80 default_server;
location /.well-known/matrix/server {
return 200 '{"m.server": "<SUBDOMAIN>.<DOMAIN>:443"}';
types { } default_type "application/json; charset=utf-8";
}
location /.well-known/matrix/client {
return 200 '{"m.homeserver": {"base_url": "https://<SUBDOMAIN>.<DOMAIN>"}}';
types { } default_type "application/json; charset=utf-8";
add_header "Access-Control-Allow-Origin" *;
}
location / {
return 404;
}
}
```
6. Run `docker-compose up -d`
7. Connect to your homeserver with your preferred client and create a user. You should do this immediately after starting Conduit, because the first created user is the admin.
## Voice communication
In order to make or receive calls, a TURN server is required. Conduit suggests using [Coturn](https://github.com/coturn/coturn) for this purpose, which is also available as a Docker image. Before proceeding with the software installation, it is essential to have the necessary configurations in place.
### Configuration
Create a configuration file called `coturn.conf` containing:
```conf
use-auth-secret
static-auth-secret=<a secret key>
realm=<your server domain>
```
A common way to generate a suitable alphanumeric secret key is by using `pwgen -s 64 1`.
These same values need to be set in conduit. You can either modify conduit.toml to include these lines:
```
turn_uris = ["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]
turn_secret = "<secret key from coturn configuration>"
```
or append the following to the docker environment variables dependig on which configuration method you used earlier:
```yml
CONDUIT_TURN_URIS: '["turn:<your server domain>?transport=udp", "turn:<your server domain>?transport=tcp"]'
CONDUIT_TURN_SECRET: "<secret key from coturn configuration>"
```
Restart Conduit to apply these changes.
### Run
Run the [Coturn](https://hub.docker.com/r/coturn/coturn) image using
```bash
docker run -d --network=host -v $(pwd)/coturn.conf:/etc/coturn/turnserver.conf coturn/coturn
```
or docker-compose. For the latter, paste the following section into a file called `docker-compose.yml`
and run `docker-compose up -d` in the same directory.
```yml
version: 3
services:
turn:
container_name: coturn-server
image: docker.io/coturn/coturn
restart: unless-stopped
network_mode: "host"
volumes:
- ./coturn.conf:/etc/coturn/turnserver.conf
```
To understand why the host networking mode is used and explore alternative configuration options, please visit the following link: https://github.com/coturn/coturn/blob/master/docker/coturn/README.md.
For security recommendations see Synapse's [Coturn documentation](https://github.com/matrix-org/synapse/blob/develop/docs/setup/turn/coturn.md#configuration).

View file

@ -1,292 +0,0 @@
# Generic deployment documentation
> ## Getting help
>
> If you run into any problems while setting up Conduit, write an email to `conduit@koesters.xyz`, ask us
> in `#conduit:fachschaften.org` or [open an issue on GitLab](https://gitlab.com/famedly/conduit/-/issues/new).
## Installing Conduit
Although you might be able to compile Conduit for Windows, we do recommend running it on a Linux server. We therefore
only offer Linux binaries.
You may simply download the binary that fits your machine. Run `uname -m` to see what you need. Now copy the appropriate url:
**Stable versions:**
| CPU Architecture | Download stable version |
| ------------------------------------------- | --------------------------------------------------------------- |
| x84_64 / amd64 (Most servers and computers) | [Binary][x84_64-glibc-master] / [.deb][x84_64-glibc-master-deb] |
| armv7 (e.g. Raspberry Pi by default) | [Binary][armv7-glibc-master] / [.deb][armv7-glibc-master-deb] |
| armv8 / aarch64 | [Binary][armv8-glibc-master] / [.deb][armv8-glibc-master-deb] |
These builds were created on and linked against the glibc version shipped with Debian bullseye.
If you use a system with an older glibc version (e.g. RHEL8), you might need to compile Conduit yourself.
[x84_64-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit?job=docker:master
[armv7-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit?job=docker:master
[armv8-glibc-master]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit?job=docker:master
[x84_64-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_amd64/conduit.deb?job=docker:master
[armv7-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm_v7/conduit.deb?job=docker:master
[armv8-glibc-master-deb]: https://gitlab.com/famedly/conduit/-/jobs/artifacts/master/raw/build-output/linux_arm64/conduit.deb?job=docker:master
**Latest versions:**
| Target | Type | Download |
|-|-|-|
| `x86_64-unknown-linux-musl` | Statically linked Debian package | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/x86_64-unknown-linux-musl.deb?job=artifacts) |
| `x86_64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/x86_64-unknown-linux-musl?job=artifacts) |
| `aarch64-unknown-linux-musl` | Statically linked binary | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/aarch64-unknown-linux-musl?job=artifacts) |
| `x86_64-unknown-linux-gnu` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-amd64.tar.gz?job=artifacts) |
| `aarch64-unknown-linux-musl` | OCI image | [link](https://gitlab.com/api/v4/projects/famedly%2Fconduit/jobs/artifacts/next/raw/oci-image-arm64v8.tar.gz?job=artifacts) |
```bash
$ sudo wget -O /usr/local/bin/matrix-conduit <url>
$ sudo chmod +x /usr/local/bin/matrix-conduit
```
Alternatively, you may compile the binary yourself. First, install any dependencies:
```bash
# Debian
$ sudo apt install libclang-dev build-essential
# RHEL
$ sudo dnf install clang
```
Then, `cd` into the source tree of conduit-next and run:
```bash
$ cargo build --release
```
## Adding a Conduit user
While Conduit can run as any user it is usually better to use dedicated users for different services. This also allows
you to make sure that the file permissions are correctly set up.
In Debian or RHEL, you can use this command to create a Conduit user:
```bash
sudo adduser --system conduit --group --disabled-login --no-create-home
```
## Forwarding ports in the firewall or the router
Conduit uses the ports 443 and 8448 both of which need to be open in the firewall.
If Conduit runs behind a router or in a container and has a different public IP address than the host system these public ports need to be forwarded directly or indirectly to the port mentioned in the config.
## Optional: Avoid port 8448
If Conduit runs behind Cloudflare reverse proxy, which doesn't support port 8448 on free plans, [delegation](https://matrix-org.github.io/synapse/latest/delegate.html) can be set up to have federation traffic routed to port 443:
```apache
# .well-known delegation on Apache
<Files "/.well-known/matrix/server">
ErrorDocument 200 '{"m.server": "your.server.name:443"}'
Header always set Content-Type application/json
Header always set Access-Control-Allow-Origin *
</Files>
```
[SRV DNS record](https://spec.matrix.org/latest/server-server-api/#resolving-server-names) delegation is also [possible](https://www.cloudflare.com/en-gb/learning/dns/dns-records/dns-srv-record/).
## Setting up a systemd service
Now we'll set up a systemd service for Conduit, so it's easy to start/stop Conduit and set it to autostart when your
server reboots. Simply paste the default systemd service you can find below into
`/etc/systemd/system/conduit.service`.
```systemd
[Unit]
Description=Conduit Matrix Server
After=network.target
[Service]
Environment="CONDUIT_CONFIG=/etc/matrix-conduit/conduit.toml"
User=conduit
Group=conduit
Restart=always
ExecStart=/usr/local/bin/matrix-conduit
[Install]
WantedBy=multi-user.target
```
Finally, run
```bash
$ sudo systemctl daemon-reload
```
## Creating the Conduit configuration file
Now we need to create the Conduit's config file in
`/etc/matrix-conduit/conduit.toml`. Paste in the contents of
[`conduit-example.toml`](../configuration.md) **and take a moment to read it.
You need to change at least the server name.**
You can also choose to use a different database backend, but right now only `rocksdb` and `sqlite` are recommended.
## Setting the correct file permissions
As we are using a Conduit specific user we need to allow it to read the config. To do that you can run this command on
Debian or RHEL:
```bash
sudo chown -R root:root /etc/matrix-conduit
sudo chmod 755 /etc/matrix-conduit
```
If you use the default database path you also need to run this:
```bash
sudo mkdir -p /var/lib/matrix-conduit/
sudo chown -R conduit:conduit /var/lib/matrix-conduit/
sudo chmod 700 /var/lib/matrix-conduit/
```
## Setting up the Reverse Proxy
This depends on whether you use Apache, Caddy, Nginx or another web server.
### Apache
Create `/etc/apache2/sites-enabled/050-conduit.conf` and copy-and-paste this:
```apache
# Requires mod_proxy and mod_proxy_http
#
# On Apache instance compiled from source,
# paste into httpd-ssl.conf or httpd.conf
Listen 8448
<VirtualHost *:443 *:8448>
ServerName your.server.name # EDIT THIS
AllowEncodedSlashes NoDecode
ProxyPass /_matrix/ http://127.0.0.1:6167/_matrix/ timeout=300 nocanon
ProxyPassReverse /_matrix/ http://127.0.0.1:6167/_matrix/
</VirtualHost>
```
**You need to make some edits again.** When you are done, run
```bash
# Debian
$ sudo systemctl reload apache2
# Installed from source
$ sudo apachectl -k graceful
```
### Caddy
Create `/etc/caddy/conf.d/conduit_caddyfile` and enter this (substitute for your server name).
```caddy
your.server.name, your.server.name:8448 {
reverse_proxy /_matrix/* 127.0.0.1:6167
}
```
That's it! Just start or enable the service and you're set.
```bash
$ sudo systemctl enable caddy
```
### Nginx
If you use Nginx and not Apache, add the following server section inside the http section of `/etc/nginx/nginx.conf`
```nginx
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
listen 8448 ssl http2;
listen [::]:8448 ssl http2;
server_name your.server.name; # EDIT THIS
merge_slashes off;
# Nginx defaults to only allow 1MB uploads
# Increase this to allow posting large files such as videos
client_max_body_size 20M;
location /_matrix/ {
proxy_pass http://127.0.0.1:6167;
proxy_set_header Host $http_host;
proxy_buffering off;
proxy_read_timeout 5m;
}
ssl_certificate /etc/letsencrypt/live/your.server.name/fullchain.pem; # EDIT THIS
ssl_certificate_key /etc/letsencrypt/live/your.server.name/privkey.pem; # EDIT THIS
ssl_trusted_certificate /etc/letsencrypt/live/your.server.name/chain.pem; # EDIT THIS
include /etc/letsencrypt/options-ssl-nginx.conf;
}
```
**You need to make some edits again.** When you are done, run
```bash
$ sudo systemctl reload nginx
```
## SSL Certificate
If you chose Caddy as your web proxy SSL certificates are handled automatically and you can skip this step.
The easiest way to get an SSL certificate, if you don't have one already, is to [install](https://certbot.eff.org/instructions) `certbot` and run this:
```bash
# To use ECC for the private key,
# paste into /etc/letsencrypt/cli.ini:
# key-type = ecdsa
# elliptic-curve = secp384r1
$ sudo certbot -d your.server.name
```
[Automated renewal](https://eff-certbot.readthedocs.io/en/stable/using.html#automated-renewals) is usually preconfigured.
If using Cloudflare, configure instead the edge and origin certificates in dashboard. In case youre already running a website on the same Apache server, you can just copy-and-paste the SSL configuration from your main virtual host on port 443 into the above-mentioned vhost.
## You're done!
Now you can start Conduit with:
```bash
$ sudo systemctl start conduit
```
Set it to start automatically when your system boots with:
```bash
$ sudo systemctl enable conduit
```
## How do I know it works?
You can open [a Matrix client](https://matrix.org/ecosystem/clients), enter your homeserver and try to register. If you are using a registration token, use [Element web](https://app.element.io/), [Nheko](https://matrix.org/ecosystem/clients/nheko/) or [SchildiChat web](https://app.schildi.chat/), as they support this feature.
You can also use these commands as a quick health check.
```bash
$ curl https://your.server.name/_matrix/client/versions
# If using port 8448
$ curl https://your.server.name:8448/_matrix/client/versions
```
- To check if your server can talk with other homeservers, you can use the [Matrix Federation Tester](https://federationtester.matrix.org/).
If you can register but cannot join federated rooms check your config again and also check if the port 8448 is open and forwarded correctly.
# What's next?
## Audio/Video calls
For Audio/Video call functionality see the [TURN Guide](../turn.md).
## Appservices
If you want to set up an appservice, take a look at the [Appservice Guide](../appservices.md).

View file

@ -1,18 +0,0 @@
# Conduit for NixOS
Conduit can be acquired by Nix from various places:
* The `flake.nix` at the root of the repo
* The `default.nix` at the root of the repo
* From Nixpkgs
The `flake.nix` and `default.nix` do not (currently) provide a NixOS module, so
(for now) [`services.matrix-conduit`][module] from Nixpkgs should be used to
configure Conduit.
If you want to run the latest code, you should get Conduit from the `flake.nix`
or `default.nix` and set [`services.matrix-conduit.package`][package]
appropriately.
[module]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit
[package]: https://search.nixos.org/options?channel=unstable&query=services.matrix-conduit.package

View file

@ -1,13 +0,0 @@
# Conduit
{{#include ../README.md:catchphrase}}
{{#include ../README.md:body}}
#### How can I deploy my own?
- [Deployment options](deploying.md)
If you want to connect an Appservice to Conduit, take a look at the [appservices documentation](appservices.md).
{{#include ../README.md:footer}}

View file

@ -1,25 +0,0 @@
# Setting up TURN/STURN
## General instructions
* It is assumed you have a [Coturn server](https://github.com/coturn/coturn) up and running. See [Synapse reference implementation](https://github.com/matrix-org/synapse/blob/develop/docs/turn-howto.md).
## Edit/Add a few settings to your existing conduit.toml
```
# Refer to your Coturn settings.
# `your.turn.url` has to match the REALM setting of your Coturn as well as `transport`.
turn_uris = ["turn:your.turn.url?transport=udp", "turn:your.turn.url?transport=tcp"]
# static-auth-secret of your turnserver
turn_secret = "ADD SECRET HERE"
# If you have your TURN server configured to use a username and password
# you can provide these information too. In this case comment out `turn_secret above`!
#turn_username = ""
#turn_password = ""
```
## Apply settings
Restart Conduit.

View file

@ -35,6 +35,25 @@ name = "lychee"
group = "versions"
script = "lychee --version"
[[task]]
name = "markdownlint"
group = "versions"
script = "markdownlint --version"
[[task]]
name = "lychee"
group = "lints"
script = "lychee --offline ."
[[task]]
name = "markdownlint"
group = "lints"
# don't just use 'markdownlint .' because it will lint files that are ignored by
# git
script = """
git ls-files --cached --others --exclude-standard '*.md' | xargs markdownlint
"""
[[task]]
name = "cargo-fmt"
group = "lints"
@ -52,17 +71,25 @@ RUSTDOCFLAGS="-D warnings" cargo doc \
"""
[[task]]
name = "cargo-clippy"
name = "cargo-clippy/none"
group = "lints"
script = """
cargo clippy \
--workspace \
--all-targets \
--no-default-features \
--color=always \
-- \
-D warnings
"""
[[task]]
name = "cargo-clippy/default"
group = "lints"
script = "cargo clippy --workspace --all-targets --color=always -- -D warnings"
[[task]]
name = "lychee"
group = "lints"
script = "lychee --offline docs"
[[task]]
name = "cargo"
name = "cargo/default"
group = "tests"
script = """
cargo test \

171
flake.lock generated
View file

@ -4,16 +4,17 @@
"inputs": {
"crane": "crane",
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"flake-parts": "flake-parts",
"nix-github-actions": "nix-github-actions",
"nixpkgs": "nixpkgs",
"nixpkgs-stable": "nixpkgs-stable"
},
"locked": {
"lastModified": 1707922053,
"narHash": "sha256-wSZjK+rOXn+UQiP1NbdNn5/UW6UcBxjvlqr2wh++MbM=",
"lastModified": 1752217044,
"narHash": "sha256-5TomR72rn4q+5poQcN6EnanxeXKqJSqWVAoDAFN0lUc=",
"owner": "zhaofengli",
"repo": "attic",
"rev": "6eabc3f02fae3683bffab483e614bebfcd476b21",
"rev": "24fad0622fc9404c69e83bab7738359c5be4988e",
"type": "github"
},
"original": {
@ -24,18 +25,12 @@
}
},
"crane": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1702918879,
"narHash": "sha256-tWJqzajIvYcaRWxn+cLUB9L9Pv4dQ3Bfit/YjU5ze3g=",
"lastModified": 1751562746,
"narHash": "sha256-smpugNIkmDeicNz301Ll1bD7nFOty97T79m4GUMUczA=",
"owner": "ipetkov",
"repo": "crane",
"rev": "7195c00c272fdd92fc74e7d5a0a2844b9fadb2fb",
"rev": "aed2020fd3dc26e1e857d4107a5a67a33ab6c1fd",
"type": "github"
},
"original": {
@ -45,17 +40,12 @@
}
},
"crane_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1713721181,
"narHash": "sha256-Vz1KRVTzU3ClBfyhOj8gOehZk21q58T1YsXC30V23PU=",
"lastModified": 1752946753,
"narHash": "sha256-g5uP3jIj+STUcfTJDKYopxnSijs2agRg13H0SGL5iE4=",
"owner": "ipetkov",
"repo": "crane",
"rev": "55f4939ac59ff8f89c6a4029730a2d49ea09105f",
"rev": "544d09fecc8c2338542c57f3f742f1a0c8c71e13",
"type": "github"
},
"original": {
@ -73,15 +63,16 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1709619709,
"narHash": "sha256-l6EPVJfwfelWST7qWQeP6t/TDK3HHv5uUB1b2vw4mOQ=",
"lastModified": 1752993983,
"narHash": "sha256-3YKCySMNhFDdHbFiRS4QbEwk0U5l42NMD1scDtniESY=",
"owner": "nix-community",
"repo": "fenix",
"rev": "c8943ea9e98d41325ff57d4ec14736d330b321b2",
"rev": "62105e0745d7450976b26dbd1497b8cbe15eb9ff",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "main",
"repo": "fenix",
"type": "github"
}
@ -89,11 +80,11 @@
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
@ -105,74 +96,104 @@
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"lastModified": 1747046372,
"narHash": "sha256-CIVLLkVgvHYbgI2UpXvIIBJ12HWgX+fjA8Xf8PUmqCY=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885",
"type": "github"
},
"original": {
"owner": "edolstra",
"ref": "master",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1751413152,
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"locked": {
"lastModified": 1667395993,
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1709126324,
"narHash": "sha256-q6EQdSeUZOG26WelxqkmR7kArjgWCdw5sfJVHPH/7j8=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "d465f4819400de7c8d874d50b982301f28a84605",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"ref": "main",
"repo": "flake-utils",
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1705332318,
"narHash": "sha256-kcw1yFeJe9N4PjQji9ZeX47jg0p9A0DuU4djKvg1a7I=",
"lastModified": 1731533336,
"narHash": "sha256-oRam5PS1vcrr5UPgALW0eo1m/5/pls27Z/pabHNy2Ms=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "3449dc925982ad46246cfc36469baf66e1b64f17",
"rev": "f7653272fd234696ae94229839a99b73c9ab7de0",
"type": "github"
},
"original": {
"owner": "numtide",
"ref": "main",
"repo": "nix-filter",
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"attic",
"nixpkgs"
]
},
"locked": {
"lastModified": 1737420293,
"narHash": "sha256-F1G5ifvqTpJq7fdkT34e/Jy9VCyzd5XfJ9TO8fHhJWE=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "f4158fa080ef4503c8f4c820967d946c2af31ec9",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1702539185,
"narHash": "sha256-KnIRG5NMdLIpEkZTnN5zovNYc0hhXjAgv6pfd5Z4c7U=",
"lastModified": 1751949589,
"narHash": "sha256-mgFxAPLWw0Kq+C8P3dRrZrOYEQXOtKuYVlo9xvPntt8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "aa9d4729cbc99dabacb50e3994dcefb3ea0f7447",
"rev": "9b008d60392981ad674e04016d25619281550a9d",
"type": "github"
},
"original": {
@ -184,27 +205,27 @@
},
"nixpkgs-stable": {
"locked": {
"lastModified": 1702780907,
"narHash": "sha256-blbrBBXjjZt6OKTcYX1jpe9SRof2P9ZYWPzq22tzXAA=",
"lastModified": 1751741127,
"narHash": "sha256-t75Shs76NgxjZSgvvZZ9qOmz5zuBE8buUaYD28BMTxg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1e2e384c5b7c50dbf8e9c441a9e58d85f408b01f",
"rev": "29e290002bfff26af1db6f64d070698019460302",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-23.11",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1709479366,
"narHash": "sha256-n6F0n8UV6lnTZbYPl1A9q1BS0p4hduAv1mGAP17CVd0=",
"lastModified": 1752950548,
"narHash": "sha256-NS6BLD0lxOrnCiEOcvQCDVPXafX1/ek1dfJHX1nUIzc=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b8697e57f10292a6165a20f03d2f42920dfaf973",
"rev": "c87b95e25065c028d31a94f06a62927d18763fdf",
"type": "github"
},
"original": {
@ -214,25 +235,43 @@
"type": "github"
}
},
"rocksdb": {
"flake": false,
"locked": {
"lastModified": 1752084860,
"narHash": "sha256-mKh6zsmxsiUix4LX+npiytmKvLbo6WNA9y4Ns/EY+bE=",
"owner": "facebook",
"repo": "rocksdb",
"rev": "410c5623195ecbe4699b9b5a5f622c7325cec6fe",
"type": "github"
},
"original": {
"owner": "facebook",
"ref": "v10.4.2",
"repo": "rocksdb",
"type": "github"
}
},
"root": {
"inputs": {
"attic": "attic",
"crane": "crane_2",
"fenix": "fenix",
"flake-compat": "flake-compat_2",
"flake-utils": "flake-utils_2",
"flake-utils": "flake-utils",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_2"
"nixpkgs": "nixpkgs_2",
"rocksdb": "rocksdb"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1709571018,
"narHash": "sha256-ISFrxHxE0J5g7lDAscbK88hwaT5uewvWoma9TlFmRzM=",
"lastModified": 1752913824,
"narHash": "sha256-kRpDlijAr4p5VmcPSRw2mfhaBZ4cE3EDWzqLDIbASgA=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "9f14343f9ee24f53f17492c5f9b653427e2ad15e",
"rev": "ed193af36937d2fd4bb14a815ec589875c5c7304",
"type": "github"
},
"original": {

406
flake.nix
View file

@ -1,312 +1,130 @@
{
# Keep sorted
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
nix-filter.url = "github:numtide/nix-filter";
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
crane = {
url = "github:ipetkov/crane?ref=master";
inputs.nixpkgs.follows = "nixpkgs";
};
attic.url = "github:zhaofengli/attic?ref=main";
crane.url = "github:ipetkov/crane?ref=master";
fenix = { url = "github:nix-community/fenix?ref=main"; inputs.nixpkgs.follows = "nixpkgs"; };
flake-compat = { url = "github:edolstra/flake-compat?ref=master"; flake = false; };
flake-utils.url = "github:numtide/flake-utils?ref=main";
nix-filter.url = "github:numtide/nix-filter?ref=main";
nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
rocksdb = { url = "github:facebook/rocksdb?ref=v10.4.2"; flake = false; };
};
outputs =
{ self
, nixpkgs
, flake-utils
, nix-filter
, fenix
, crane
, ...
}: flake-utils.lib.eachDefaultSystem (system:
outputs = inputs:
let
pkgsHost = nixpkgs.legacyPackages.${system};
# Nix-accessible `Cargo.toml`
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
# The Rust toolchain to use
toolchain = fenix.packages.${system}.fromToolchainFile {
file = ./rust-toolchain.toml;
# See also `rust-toolchain.toml`
sha256 = "sha256-SXRtAuO4IqNOQq+nLbrsDFbVk+3aVA8NNpSZsKlVH/8=";
rust-manifest = builtins.fetchurl {
# Keep version in sync with rust-toolchain.toml
url = "https://static.rust-lang.org/dist/channel-rust-1.88.0.toml";
sha256 = "sha256-Qxt8XAuaUR2OMdKbN4u8dBJOhSHxS+uS06Wl9+flVEk=";
};
builder = pkgs:
((crane.mkLib pkgs).overrideToolchain toolchain).buildPackage;
# Keep sorted
mkScope = pkgs: pkgs.lib.makeScope pkgs.newScope (self: {
craneLib =
(inputs.crane.mkLib pkgs).overrideToolchain (_: self.toolchain);
nativeBuildInputs = pkgs: [
# bindgen needs the build platform's libclang. Apparently due to
# "splicing weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't
# quite do the right thing here.
pkgs.pkgsBuildHost.rustPlatform.bindgenHook
];
default = self.callPackage ./nix/pkgs/default {};
rocksdb' = pkgs:
let
version = "9.1.0";
in
pkgs.rocksdb.overrideAttrs (old: {
inherit version;
src = pkgs.fetchFromGitHub {
owner = "facebook";
repo = "rocksdb";
rev = "v${version}";
hash = "sha256-vRPyrXkXVVhP56n5FVYef8zbIsnnanQSpElmQLZ7mh8=";
inherit inputs;
# Return a new scope with overrides applied to the 'default' package
overrideDefaultPackage = args: self.overrideScope (final: prev: {
default = prev.default.override args;
});
rocksdb = self.callPackage ./nix/pkgs/rocksdb {
inherit (pkgs) rocksdb;
};
});
env = pkgs: {
CONDUIT_VERSION_EXTRA = self.shortRev or self.dirtyShortRev;
ROCKSDB_INCLUDE_DIR = "${rocksdb' pkgs}/include";
ROCKSDB_LIB_DIR = "${rocksdb' pkgs}/lib";
}
// pkgs.lib.optionalAttrs pkgs.stdenv.hostPlatform.isStatic {
ROCKSDB_STATIC = "";
}
// {
CARGO_BUILD_RUSTFLAGS = let inherit (pkgs) lib stdenv; in
lib.concatStringsSep " " ([]
++ lib.optionals
# This disables PIE for static builds, which isn't great in terms
# of security. Unfortunately, my hand is forced because nixpkgs'
# `libstdc++.a` is built without `-fPIE`, which precludes us from
# leaving PIE enabled.
stdenv.hostPlatform.isStatic
["-C" "relocation-model=static"]
++ lib.optionals
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
["-l" "c"]
++ lib.optionals
# This check has to match the one [here][0]. We only need to set
# these flags when using a different linker. Don't ask me why,
# though, because I don't know. All I know is it breaks otherwise.
#
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L37-L40
(
# Nixpkgs doesn't check for x86_64 here but we do, because I
# observed a failure building statically for x86_64 without
# including it here. Linkers are weird.
(stdenv.hostPlatform.isAarch64 || stdenv.hostPlatform.isx86_64)
&& stdenv.hostPlatform.isStatic
&& !stdenv.isDarwin
&& !stdenv.cc.bintools.isLLVM
shell = self.callPackage ./nix/shell.nix {};
# The Rust toolchain to use
# Using fromManifestFile and parsing the toolchain file with importTOML
# instead of fromToolchainFile to avoid IFD
toolchain = let
toolchainFile = pkgs.lib.importTOML ./rust-toolchain.toml;
defaultProfileComponents = [
"rustc"
"cargo"
"rust-docs"
"rustfmt"
"clippy"
];
components = defaultProfileComponents ++
toolchainFile.toolchain.components;
targets = toolchainFile.toolchain.targets;
fenix = inputs.fenix.packages.${pkgs.stdenv.buildPlatform.system};
nativeToolchain = (fenix.fromManifestFile rust-manifest)
.withComponents components;
crossComponents = builtins.map
(target:
(fenix.targets.${target}.fromManifestFile rust-manifest)
.rust-std)
targets;
in
fenix.combine ([nativeToolchain] ++ crossComponents);
website-root = self.callPackage ./nix/pkgs/website-root {};
});
in
inputs.flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import inputs.nixpkgs {
inherit system;
# Some users find it useful to set this on their Nixpkgs instance and
# we want to support that use case, so we set it here too to help us
# test/ensure that this works.
config.allowAliases = false;
};
in
{
packages = {
default = (mkScope pkgs).default;
website-root = (mkScope pkgs).website-root;
}
//
builtins.listToAttrs
(builtins.concatLists
(builtins.map
(crossSystem:
let
binaryName = "static-${crossSystem}";
pkgsCrossStatic =
(import inputs.nixpkgs {
inherit system;
crossSystem = {
config = crossSystem;
};
# Some users find it useful to set this on their Nixpkgs
# instance and we want to support that use case, so we set
# it here too to help us test/ensure that this works.
config.allowAliases = false;
}).pkgsStatic;
in
[
# An output for a statically-linked binary
{
name = binaryName;
value = (mkScope pkgsCrossStatic).default;
}
]
)
[
"-l"
"stdc++"
"-L"
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
]
);
}
# What follows is stolen from [here][0]. Its purpose is to properly
# configure compilers and linkers for various stages of the build, and
# even covers the case of build scripts that need native code compiled and
# run on the build platform (I think).
#
# [0]: https://github.com/NixOS/nixpkgs/blob/5cdb38bb16c6d0a38779db14fcc766bc1b2394d6/pkgs/build-support/rust/lib/default.nix#L57-L80
// (
let
inherit (pkgs.rust.lib) envVars;
in
pkgs.lib.optionalAttrs
(pkgs.stdenv.targetPlatform.rust.rustcTarget
!= pkgs.stdenv.hostPlatform.rust.rustcTarget)
(
let
inherit (pkgs.stdenv.targetPlatform.rust) cargoEnvVarTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" =
envVars.linkerForTarget;
}
)
// (
let
inherit (pkgs.stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForHost;
CARGO_BUILD_TARGET = rustcTarget;
}
)
// (
let
inherit (pkgs.stdenv.buildPlatform.rust) cargoEnvVarTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.linkerForBuild;
HOST_CC = "${pkgs.pkgsBuildHost.stdenv.cc}/bin/cc";
HOST_CXX = "${pkgs.pkgsBuildHost.stdenv.cc}/bin/c++";
}
));
package = pkgs: builder pkgs {
src = nix-filter {
root = ./.;
include = [
"src"
"Cargo.toml"
"Cargo.lock"
];
};
# This is redundant with CI
doCheck = false;
env = env pkgs;
nativeBuildInputs = nativeBuildInputs pkgs;
meta.mainProgram = cargoToml.package.name;
};
mkOciImage = pkgs: package:
pkgs.dockerTools.buildImage {
name = package.pname;
tag = "next";
copyToRoot = [
pkgs.dockerTools.caCertificates
];
config = {
# Use the `tini` init system so that signals (e.g. ctrl+c/SIGINT)
# are handled as expected
Entrypoint = [
"${pkgs.lib.getExe' pkgs.tini "tini"}"
"--"
];
Cmd = [
"${pkgs.lib.getExe package}"
];
};
};
in
{
packages = {
default = package pkgsHost;
oci-image = mkOciImage pkgsHost self.packages.${system}.default;
book =
let
package = self.packages.${system}.default;
in
pkgsHost.stdenv.mkDerivation {
pname = "${package.pname}-book";
version = package.version;
src = nix-filter {
root = ./.;
include = [
"book.toml"
"conduit-example.toml"
"README.md"
"debian/README.md"
"docs"
];
};
nativeBuildInputs = (with pkgsHost; [
mdbook
]);
buildPhase = ''
mdbook build
mv public $out
'';
};
}
//
builtins.listToAttrs
(builtins.concatLists
(builtins.map
(crossSystem:
let
binaryName = "static-${crossSystem}";
pkgsCrossStatic =
(import nixpkgs {
inherit system;
crossSystem = {
config = crossSystem;
};
}).pkgsStatic;
in
[
# An output for a statically-linked binary
{
name = binaryName;
value = package pkgsCrossStatic;
}
# An output for an OCI image based on that binary
{
name = "oci-image-${crossSystem}";
value = mkOciImage
pkgsCrossStatic
self.packages.${system}.${binaryName};
}
"x86_64-unknown-linux-musl"
"aarch64-unknown-linux-musl"
]
)
[
"x86_64-unknown-linux-musl"
"aarch64-unknown-linux-musl"
]
)
);
);
devShells.default = pkgsHost.mkShell {
env = env pkgsHost // {
# Rust Analyzer needs to be able to find the path to default crate
# sources, and it can read this environment variable to do so. The
# `rust-src` component is required in order for this to work.
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
};
# Development tools
nativeBuildInputs = nativeBuildInputs pkgsHost ++ [
# Always use nightly rustfmt because most of its options are unstable
#
# This needs to come before `toolchain` in this list, otherwise
# `$PATH` will have stable rustfmt instead.
fenix.packages.${system}.latest.rustfmt
toolchain
] ++ (with pkgsHost; [
engage
# Needed for producing Debian packages
cargo-deb
# Needed for Complement
go
olm
# Needed for our script for Complement
jq
# Needed for finding broken markdown links
lychee
# Useful for editing the book locally
mdbook
]);
};
});
devShells.default = (mkScope pkgs).shell;
}
)
//
{
nixosModules.default = import ./nix/modules/default inputs;
};
}

View file

@ -0,0 +1,154 @@
inputs:
{ config
, lib
, pkgs
, ...
}:
let
inherit (lib) types;
cfg = config.services.grapevine;
configFile = format.generate "config.toml" cfg.settings;
validateConfig = file: pkgs.runCommand "grapevine-checked-config" {} ''
${lib.getExe cfg.package} check-config -sc ${lib.escapeShellArg file}
ln -s ${lib.escapeShellArg file} "$out"
'';
format = pkgs.formats.toml {};
in
{
options.services.grapevine = {
enable = lib.mkEnableOption "grapevine";
package = lib.mkPackageOption
inputs.self.packages.${pkgs.stdenv.hostPlatform.system}
"grapevine"
{
default = "default";
pkgsText = "inputs.grapevine.packages.\${pkgs.stdenv.hostPlatform.system}";
};
settings = lib.mkOption {
type = types.submodule {
freeformType = format.type;
options = {
conduit_compat = lib.mkOption {
type = types.bool;
description = ''
Whether to operate as a drop-in replacement for Conduit.
'';
default = false;
};
database = {
backend = lib.mkOption {
type = types.nonEmptyStr;
description = ''
The database backend to use.
'';
default = "rocksdb";
};
path = lib.mkOption {
type = types.nonEmptyStr;
readOnly = true;
description = ''
The path to store database files in.
Note that this is read-only because this module makes use of
systemd's `StateDirectory` option.
'';
default = if cfg.settings.conduit_compat
then "/var/lib/matrix-conduit/database"
else "/var/lib/grapevine/database";
};
};
media.backend = {
type = lib.mkOption {
type = types.nonEmptyStr;
readOnly = true;
description = ''
The media backend to use.
Note that this is read-only because `filesystem` is currently
the only valid option.
'';
default = "filesystem";
};
path = lib.mkOption {
type = types.nonEmptyStr;
readOnly = true;
description = ''
The path to store database files in.
Note that this is read-only because this module makes use of
systemd's `StateDirectory` option.
'';
default = if cfg.settings.conduit_compat
then "/var/lib/matrix-conduit/media"
else "/var/lib/grapevine/media";
};
};
listen = lib.mkOption {
type = types.listOf format.type;
description = ''
List of places to listen for incoming connections.
'';
default = [
{
type = "tcp";
address = "::1";
port = 6167;
}
];
};
};
};
default = {};
description = ''
The TOML configuration file is generated from this attribute set.
'';
};
};
config = lib.mkIf cfg.enable {
systemd.services.grapevine = {
description = "Grapevine (Matrix homeserver)";
wantedBy = [ "multi-user.target" ];
# Keep sorted
serviceConfig = {
DynamicUser = true;
ExecStart = "${lib.getExe cfg.package} serve --config ${validateConfig configFile}";
LockPersonality = true;
MemoryDenyWriteExecute = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
Restart = "on-failure";
RestartSec = 10;
RestrictAddressFamilies = [ "AF_INET" "AF_INET6" "AF_UNIX" ];
RestrictNamespaces = true;
RestrictRealtime = true;
StartLimitBurst = 5;
StateDirectory = if cfg.settings.conduit_compat
then "matrix-conduit"
else "grapevine";
StateDirectoryMode = "0700";
SystemCallArchitectures = "native";
SystemCallFilter = [ "@system-service" "~@privileged" ];
TimeoutStartSec = "infinity";
Type = "notify";
UMask = "077";
User = if cfg.settings.conduit_compat
then "conduit"
else "grapevine";
};
};
};
}

View file

@ -0,0 +1,95 @@
# Keep sorted
{ lib
, pkgsBuildHost
, rust
, snappy
, stdenv
}:
lib.optionalAttrs stdenv.hostPlatform.isStatic {
ROCKSDB_STATIC = "";
}
//
{
CARGO_BUILD_RUSTFLAGS =
lib.concatStringsSep
" "
([]
# This disables PIE for static builds, which isn't great in terms of
# security. Unfortunately, my hand is forced because nixpkgs'
# `libstdc++.a` is built without `-fPIE`, which precludes us from
# leaving PIE enabled.
++ lib.optionals
stdenv.hostPlatform.isStatic
[ "-C" "relocation-model=static" ]
# I'm not sure why any of this is necessary but it is so *shrug*
++ lib.optionals
(stdenv.buildPlatform.config != stdenv.hostPlatform.config)
[
"-l"
"c"
"-l"
"stdc++"
"-L"
"${stdenv.cc.cc.lib}/${stdenv.hostPlatform.config}/lib"
"-l"
"snappy"
"-L"
"${snappy}/lib"
]
);
}
# What follows is stolen from [here][0]. Its purpose is to properly configure
# compilers and linkers for various stages of the build, and even covers the
# case of build scripts that need native code compiled and run on the build
# platform (I think).
#
# [0]: https://github.com/NixOS/nixpkgs/blob/2768c7d042a37de65bb1b5b3268fc987e534c49d/pkgs/build-support/rust/lib/default.nix#L45-L68
//
(
let
inherit (rust.lib) envVars;
in
lib.optionalAttrs
(stdenv.targetPlatform.rust.rustcTarget
!= stdenv.hostPlatform.rust.rustcTarget)
(
let
inherit (stdenv.targetPlatform.rust) cargoEnvVarTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForTarget;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForTarget;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForTarget;
}
)
//
(
let
inherit (stdenv.hostPlatform.rust) cargoEnvVarTarget rustcTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForHost;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForHost;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForHost;
CARGO_BUILD_TARGET = rustcTarget;
}
)
//
(
let
inherit (stdenv.buildPlatform.rust) cargoEnvVarTarget;
in
{
"CC_${cargoEnvVarTarget}" = envVars.ccForBuild;
"CXX_${cargoEnvVarTarget}" = envVars.cxxForBuild;
"CARGO_TARGET_${cargoEnvVarTarget}_LINKER" = envVars.ccForBuild;
HOST_CC = "${pkgsBuildHost.stdenv.cc}/bin/cc";
HOST_CXX = "${pkgsBuildHost.stdenv.cc}/bin/c++";
}
)
)

View file

@ -0,0 +1,109 @@
# Dependencies (keep sorted)
{ craneLib
, inputs
, jq
, lib
, pkgsBuildHost
, rocksdb
, rust
, snappy
, stdenv
# Options (keep sorted)
, default-features ? true
, features ? []
, profile ? "release"
, version-extra ? inputs.self.shortRev
or inputs.self.dirtyShortRev
or null,
}:
let
cargoManifest = lib.importTOML "${inputs.self}/Cargo.toml";
buildDepsOnlyEnv =
{
NIX_OUTPATH_USED_AS_RANDOM_SEED = "randomseed";
CARGO_PROFILE = profile;
ROCKSDB_INCLUDE_DIR = "${rocksdb}/include";
ROCKSDB_LIB_DIR = "${rocksdb}/lib";
}
//
(import ./cross-compilation-env.nix {
# Keep sorted
inherit
lib
pkgsBuildHost
rust
snappy
stdenv;
});
buildPackageEnv =
(lib.optionalAttrs (version-extra != null) {
GRAPEVINE_VERSION_EXTRA = version-extra;
})
// buildDepsOnlyEnv;
commonAttrs = {
# Reading from cargoManifest directly instead of using
# createNameFromCargoToml to avoid IFD
pname = cargoManifest.package.name;
version = cargoManifest.package.version;
src = let filter = inputs.nix-filter.lib; in filter {
root = inputs.self;
# Keep sorted
include = [
".cargo/config.toml"
"Cargo.lock"
"Cargo.toml"
"src"
];
};
dontStrip = profile != "release";
nativeBuildInputs = [
# bindgen needs the build platform's libclang. Apparently due to "splicing
# weirdness", pkgs.rustPlatform.bindgenHook on its own doesn't quite do the
# right thing here.
pkgsBuildHost.rustPlatform.bindgenHook
# We don't actually depend on `jq`, but crane's `buildPackage` does, but
# its `buildDepsOnly` doesn't. This causes those two derivations to have
# differing values for `NIX_CFLAGS_COMPILE`, which contributes to spurious
# rebuilds of bindgen and its depedents.
jq
];
# Opt out of crane's automagic cross support
doIncludeCrossToolchainEnv = false;
# This is redundant with CI
doCheck = false;
};
in
craneLib.buildPackage (commonAttrs // {
cargoArtifacts = craneLib.buildDepsOnly (commonAttrs // {
env = buildDepsOnlyEnv;
});
cargoExtraArgs = "--locked "
+ lib.optionalString
(!default-features)
"--no-default-features "
+ lib.optionalString
(features != [])
"--features " + (builtins.concatStringsSep "," features);
env = buildPackageEnv;
passthru = {
env = buildPackageEnv;
};
meta.mainProgram = commonAttrs.pname;
})

View file

@ -0,0 +1,40 @@
# Dependencies (keep sorted)
{ inputs
, lib
, rocksdb
}:
let
rocksdb' = (rocksdb.overrideAttrs (old: {
src = inputs.rocksdb;
version = lib.removePrefix
"v"
(builtins.fromJSON
(builtins.readFile ../../../flake.lock)
).nodes.rocksdb.original.ref;
})).override {
enableLiburing = false;
};
cVersion = rocksdb'.version;
rustVersion = builtins.elemAt
(lib.splitString
"+"
(lib.findSingle
(x: x.name == "rust-librocksdb-sys")
(builtins.throw "Multiple rust-librocksdb-sys versions in Cargo.lock")
(builtins.throw "No rust-librocksdb-sys versions in Cargo.lock")
(builtins.fromTOML (builtins.readFile ../../../Cargo.lock)).package
).version
)
1;
in
if cVersion == rustVersion
then rocksdb'
else builtins.throw
(builtins.concatStringsSep " " [
"C version (${cVersion}) and Rust version (${rustVersion}) of RocksDB do"
"not match"
])

View file

@ -0,0 +1,28 @@
# Keep sorted
{ inputs
, lib
, mdbook
, stdenv
}:
stdenv.mkDerivation {
name = "website-root";
src = let filter = inputs.nix-filter.lib; in filter {
root = inputs.self;
# Keep sorted
include = [
"book"
"book.toml"
];
};
buildPhase = ''
${lib.getExe mdbook} build
'';
installPhase = ''
mv target/book $out
'';
}

55
nix/shell.nix Normal file
View file

@ -0,0 +1,55 @@
# Keep sorted
{ cargo-insta
, default
, engage
, findutils
, inputs
, jq
, lychee
, markdownlint-cli
, mdbook
, mkShell
, ripgrep
, stdenv
, toolchain
}:
mkShell {
env = default.env // {
# Rust Analyzer needs to be able to find the path to default crate
# sources, and it can read this environment variable to do so. The
# `rust-src` component is required in order for this to work.
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";
};
# Development tools
nativeBuildInputs = [
# Always use nightly rustfmt because most of its options are unstable
#
# This needs to come before `toolchain` in this list, otherwise
# `$PATH` will have stable rustfmt instead.
inputs.fenix.packages.${stdenv.buildPlatform.system}.latest.rustfmt
# Keep sorted
cargo-insta
engage
findutils
jq
lychee
markdownlint-cli
mdbook
ripgrep
toolchain
]
++
default.nativeBuildInputs
++
default.propagatedBuildInputs
++
default.buildInputs;
shellHook = ''
# Workaround for <https://github.com/NixOS/nix/issues/8355>
unset TMPDIR
'';
}

View file

@ -2,7 +2,6 @@
#
# Other files that need upkeep when this changes:
#
# * `.gitlab-ci.yml`
# * `Cargo.toml`
# * `flake.nix`
#
@ -10,7 +9,7 @@
# If you're having trouble making the relevant changes, bug a maintainer.
[toolchain]
channel = "1.75.0"
channel = "1.88.0"
components = [
# For rust-analyzer
"rust-src",
@ -18,5 +17,6 @@ components = [
targets = [
"x86_64-unknown-linux-gnu",
"x86_64-unknown-linux-musl",
"aarch64-unknown-linux-gnu",
"aarch64-unknown-linux-musl",
]

View file

@ -1,2 +1,18 @@
unstable_features = true
imports_granularity="Crate"
edition = "2021"
condense_wildcard_suffixes = true
error_on_line_overflow = true
format_code_in_doc_comments = true
format_macro_bodies = true
format_macro_matchers = true
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Upper"
imports_granularity = "Crate"
max_width = 80
newline_style = "Unix"
reorder_impl_items = true
use_field_init_shorthand = true
use_small_heuristics = "Off"
use_try_shorthand = true
wrap_comments = true

5
src/api.rs Normal file
View file

@ -0,0 +1,5 @@
pub(crate) mod appservice_server;
pub(crate) mod client_server;
pub(crate) mod ruma_wrapper;
pub(crate) mod server_server;
pub(crate) mod well_known;

View file

@ -1,27 +1,28 @@
use crate::{services, utils, Error, Result};
use std::{fmt::Debug, mem, time::Duration};
use bytes::BytesMut;
use ruma::api::{
appservice::Registration, IncomingResponse, MatrixVersion, OutgoingRequest, SendAccessToken,
appservice::Registration, IncomingResponse, MatrixVersion, OutgoingRequest,
SendAccessToken,
};
use std::{fmt::Debug, mem, time::Duration};
use tracing::warn;
use crate::{services, utils, Error, Result};
/// Sends a request to an appservice
///
/// Only returns None if there is no url specified in the appservice registration file
/// Only returns None if there is no url specified in the appservice
/// registration file
#[tracing::instrument(skip(request))]
pub(crate) async fn send_request<T: OutgoingRequest>(
pub(crate) async fn send_request<T>(
registration: Registration,
request: T,
) -> Result<Option<T::IncomingResponse>>
where
T: Debug,
T: OutgoingRequest + Debug,
{
let destination = match registration.url {
Some(url) => url,
None => {
return Ok(None);
}
let Some(destination) = registration.url else {
return Ok(None);
};
let hs_token = registration.hs_token.as_str();
@ -33,7 +34,7 @@ where
&[MatrixVersion::V1_0],
)
.unwrap()
.map(|body| body.freeze());
.map(BytesMut::freeze);
let mut parts = http_request.uri().clone().into_parts();
let old_path_and_query = parts.path_and_query.unwrap().as_str().to_owned();
@ -44,38 +45,36 @@ where
};
parts.path_and_query = Some(
(old_path_and_query + symbol + "access_token=" + hs_token)
format!("{old_path_and_query}{symbol}access_token={hs_token}")
.parse()
.unwrap(),
);
*http_request.uri_mut() = parts.try_into().expect("our manipulation is always valid");
*http_request.uri_mut() =
parts.try_into().expect("our manipulation is always valid");
let mut reqwest_request = reqwest::Request::try_from(http_request)?;
*reqwest_request.timeout_mut() = Some(Duration::from_secs(30));
let url = reqwest_request.url().clone();
let mut response = match services()
let mut response = services()
.globals
.default_client()
.execute(reqwest_request)
.await
{
Ok(r) => r,
Err(e) => {
.inspect_err(|error| {
warn!(
"Could not send request to appservice {:?} at {}: {}",
registration.id, destination, e
%error,
appservice = registration.id,
%destination,
"Could not send request to appservice",
);
return Err(e.into());
}
};
})?;
// reqwest::Response -> http::Response conversion
let status = response.status();
let mut http_response_builder = http::Response::builder()
.status(status)
.version(response.version());
let mut http_response_builder =
http::Response::builder().status(status).version(response.version());
mem::swap(
response.headers_mut(),
http_response_builder
@ -83,18 +82,22 @@ where
.expect("http::response::Builder is usable"),
);
let body = response.bytes().await.unwrap_or_else(|e| {
warn!("server error: {}", e);
// TODO: handle timeout
let body = response.bytes().await.unwrap_or_else(|error| {
warn!(%error, "Server error");
Vec::new().into()
}); // TODO: handle timeout
});
if status != 200 {
warn!(
"Appservice returned bad response {} {}\n{}\n{:?}",
destination,
status,
url,
utils::string_from_bytes(&body)
appservice = %destination,
%status,
%url,
body = %utils::dbg_truncate_str(
String::from_utf8_lossy(&body).as_ref(),
100,
),
"Appservice returned bad response",
);
}
@ -104,10 +107,12 @@ where
.expect("reqwest body is valid http body"),
);
response.map(Some).map_err(|_| {
response.map(Some).map_err(|error| {
warn!(
"Appservice returned invalid response bytes {}\n{}",
destination, url
%error,
appservice = %destination,
%url,
"Appservice returned invalid response bytes",
);
Error::BadServerResponse("Server returned bad response.")
})

72
src/api/client_server.rs Normal file
View file

@ -0,0 +1,72 @@
mod account;
mod alias;
mod backup;
mod capabilities;
mod config;
mod context;
mod device;
mod directory;
mod filter;
mod keys;
mod media;
mod membership;
mod message;
mod profile;
mod push;
mod read_marker;
mod redact;
mod relations;
mod report;
mod room;
mod search;
mod session;
mod space;
mod state;
mod sync;
mod tag;
mod thirdparty;
mod threads;
mod to_device;
mod typing;
mod unversioned;
mod user_directory;
mod voip;
pub(crate) use account::*;
pub(crate) use alias::*;
pub(crate) use backup::*;
pub(crate) use capabilities::*;
pub(crate) use config::*;
pub(crate) use context::*;
pub(crate) use device::*;
pub(crate) use directory::*;
pub(crate) use filter::*;
pub(crate) use keys::*;
pub(crate) use media::*;
pub(crate) use membership::*;
pub(crate) use message::*;
pub(crate) use profile::*;
pub(crate) use push::*;
pub(crate) use read_marker::*;
pub(crate) use redact::*;
pub(crate) use relations::*;
pub(crate) use report::*;
pub(crate) use room::*;
pub(crate) use search::*;
pub(crate) use session::*;
pub(crate) use space::*;
pub(crate) use state::*;
pub(crate) use sync::*;
pub(crate) use tag::*;
pub(crate) use thirdparty::*;
pub(crate) use threads::*;
pub(crate) use to_device::*;
pub(crate) use typing::*;
pub(crate) use unversioned::*;
pub(crate) use user_directory::*;
pub(crate) use voip::*;
pub(crate) const DEVICE_ID_LENGTH: usize = 10;
pub(crate) const TOKEN_LENGTH: usize = 32;
pub(crate) const SESSION_ID_LENGTH: usize = 32;
pub(crate) const AUTO_GEN_PASSWORD_LENGTH: usize = 15;

View file

@ -1,22 +1,25 @@
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{api::client_server, services, utils, Error, Result, Ruma};
use register::RegistrationKind;
use ruma::{
api::client::{
account::{
change_password, deactivate, get_3pids, get_username_availability,
register::{self, LoginType},
request_3pid_management_token_via_email, request_3pid_management_token_via_msisdn,
whoami, ThirdPartyIdRemovalStatus,
request_3pid_management_token_via_email,
request_3pid_management_token_via_msisdn, whoami,
ThirdPartyIdRemovalStatus,
},
error::ErrorKind,
uiaa::{AuthFlow, AuthType, UiaaInfo},
},
events::{room::message::RoomMessageEventContent, GlobalAccountDataEventType},
push, UserId,
events::room::message::RoomMessageEventContent,
push,
serde::Raw,
UserId,
};
use tracing::{info, warn};
use register::RegistrationKind;
use super::{DEVICE_ID_LENGTH, SESSION_ID_LENGTH, TOKEN_LENGTH};
use crate::{api::client_server, services, utils, Ar, Error, Ra, Result};
const RANDOM_USER_ID_LENGTH: usize = 10;
@ -29,10 +32,11 @@ const RANDOM_USER_ID_LENGTH: usize = 10;
/// - The server name of the user id matches this server
/// - No user or appservice on this server already claimed this username
///
/// Note: This will not reserve the username, so the username might become invalid when trying to register
pub async fn get_register_available_route(
body: Ruma<get_username_availability::v3::Request>,
) -> Result<get_username_availability::v3::Response> {
/// Note: This will not reserve the username, so the username might become
/// invalid when trying to register
pub(crate) async fn get_register_available_route(
body: Ar<get_username_availability::v3::Request>,
) -> Result<Ra<get_username_availability::v3::Response>> {
// Validate user id
let user_id = UserId::parse_with_server_name(
body.username.to_lowercase(),
@ -40,7 +44,8 @@ pub async fn get_register_available_route(
)
.ok()
.filter(|user_id| {
!user_id.is_historical() && user_id.server_name() == services().globals.server_name()
!user_id.is_historical()
&& user_id.server_name() == services().globals.server_name()
})
.ok_or(Error::BadRequest(
ErrorKind::InvalidUsername,
@ -58,26 +63,37 @@ pub async fn get_register_available_route(
// TODO add check for appservice namespaces
// If no if check is true we have an username that's available to be used.
Ok(get_username_availability::v3::Response { available: true })
Ok(Ra(get_username_availability::v3::Response {
available: true,
}))
}
/// # `POST /_matrix/client/r0/register`
///
/// Register an account on this homeserver.
///
/// You can use [`GET /_matrix/client/r0/register/available`](fn.get_register_available_route.html)
/// You can use [`GET
/// /_matrix/client/r0/register/available`](get_register_available_route)
/// to check if the user id is valid and available.
///
/// - Only works if registration is enabled
/// - If type is guest: ignores all parameters except initial_device_display_name
/// - If type is guest: ignores all parameters except
/// `initial_device_display_name`
/// - If sender is not appservice: Requires UIAA (but we only use a dummy stage)
/// - If type is not guest and no username is given: Always fails after UIAA check
/// - If type is not guest and no username is given: Always fails after UIAA
/// check
/// - Creates a new account and populates it with default account data
/// - If `inhibit_login` is false: Creates a device and returns device id and access_token
pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<register::v3::Response> {
if !services().globals.allow_registration() && body.appservice_info.is_none() {
/// - If `inhibit_login` is false: Creates a device and returns `device_id` and
/// `access_token`
#[allow(clippy::too_many_lines)]
pub(crate) async fn register_route(
body: Ar<register::v3::Request>,
) -> Result<Ra<register::v3::Response>> {
if !services().globals.allow_registration()
&& body.appservice_info.is_none()
{
return Err(Error::BadRequest(
ErrorKind::Forbidden,
ErrorKind::forbidden(),
"Registration has been disabled.",
));
}
@ -120,7 +136,7 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
};
if body.body.login_type == Some(LoginType::ApplicationService) {
if let Some(ref info) = body.appservice_info {
if let Some(info) = &body.appservice_info {
if !info.is_user_match(&user_id) {
return Err(Error::BadRequest(
ErrorKind::Exclusive,
@ -149,19 +165,20 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
stages: vec![AuthType::RegistrationToken],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
body.appservice_info.is_some()
} else {
// No registration token necessary, but clients must still go through the flow
// No registration token necessary, but clients must still go through
// the flow
uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Dummy],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
@ -171,26 +188,32 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
if !skip_auth {
if let Some(auth) = &body.auth {
let (worked, uiaainfo) = services().uiaa.try_auth(
&UserId::parse_with_server_name("", services().globals.server_name())
.expect("we know this is valid"),
&UserId::parse_with_server_name(
"",
services().globals.server_name(),
)
.expect("we know this is valid"),
"".into(),
auth,
&uiaainfo,
)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services().uiaa.create(
&UserId::parse_with_server_name("", services().globals.server_name())
.expect("we know this is valid"),
&UserId::parse_with_server_name(
"",
services().globals.server_name(),
)
.expect("we know this is valid"),
"".into(),
&uiaainfo,
&json,
)?;
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
@ -206,39 +229,28 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
services().users.create(&user_id, password)?;
// Default to pretty displayname
let mut displayname = user_id.localpart().to_owned();
let displayname = user_id.localpart().to_owned();
// If enabled append lightning bolt to display name (default true)
if services().globals.enable_lightning_bolt() {
displayname.push_str(" ⚡️");
}
services()
.users
.set_displayname(&user_id, Some(displayname.clone()))?;
services().users.set_displayname(&user_id, Some(displayname.clone()))?;
// Initial account data
services().account_data.update(
None,
services().account_data.update_global(
&user_id,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(ruma::events::push_rules::PushRulesEvent {
content: ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id),
},
&Raw::new(&ruma::events::push_rules::PushRulesEventContent {
global: push::Ruleset::server_default(&user_id),
})
.expect("to json always works"),
.expect("constructed event should be valid"),
)?;
// Inhibit login does not work for guests
if !is_guest && body.inhibit_login {
return Ok(register::v3::Response {
return Ok(Ra(register::v3::Response {
access_token: None,
user_id,
device_id: None,
refresh_token: None,
expires_in: None,
});
}));
}
// Generate new device id if the user didn't specify one
@ -260,42 +272,37 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
body.initial_device_display_name.clone(),
)?;
info!("New user {} registered on this server.", user_id);
info!(%user_id, "New user registered on this server");
if body.appservice_info.is_none() && !is_guest {
services()
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"New user {user_id} registered on this server."
)));
services().admin.send_message(RoomMessageEventContent::notice_plain(
format!("New user {user_id} registered on this server."),
));
}
// If this is the first real user, grant them admin privileges
// Note: the server user, @conduit:servername, is generated first
// Note: the server user, @grapevine:servername, is generated first
if !is_guest {
if let Some(admin_room) = services().admin.get_admin_room()? {
if services()
.rooms
.state_cache
.room_joined_count(&admin_room)?
if services().rooms.state_cache.room_joined_count(&admin_room)?
== Some(1)
{
services()
.admin
.make_user_admin(&user_id, displayname)
.await?;
services().admin.make_user_admin(&user_id, displayname).await?;
warn!("Granting {} admin privileges as the first user", user_id);
warn!(
%user_id,
"Granting admin privileges to the first user",
);
}
}
}
Ok(register::v3::Response {
Ok(Ra(register::v3::Response {
access_token: Some(token),
user_id,
device_id: Some(device_id),
refresh_token: None,
expires_in: None,
})
}))
}
/// # `POST /_matrix/client/r0/account/password`
@ -304,89 +311,91 @@ pub async fn register_route(body: Ruma<register::v3::Request>) -> Result<registe
///
/// - Requires UIAA to verify user password
/// - Changes the password of the sender user
/// - The password hash is calculated using argon2 with 32 character salt, the plain password is
/// not saved
/// - The password hash is calculated using argon2 with 32 character salt, the
/// plain password is not saved
///
/// If logout_devices is true it does the following for each device except the sender device:
/// If `logout_devices` is true it does the following for each device except the
/// sender device:
/// - Invalidates access token
/// - Deletes device metadata (device id, device display name, last seen ip, last seen ts)
/// - Deletes device metadata (device ID, device display name, last seen IP,
/// last seen timestamp)
/// - Forgets to-device events
/// - Triggers device list updates
pub async fn change_password_route(
body: Ruma<change_password::v3::Request>,
) -> Result<change_password::v3::Response> {
pub(crate) async fn change_password_route(
body: Ar<change_password::v3::Request>,
) -> Result<Ra<change_password::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) =
services()
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
let (worked, uiaainfo) = services().uiaa.try_auth(
sender_user,
sender_device,
auth,
&uiaainfo,
)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
services().uiaa.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(Box::new(uiaainfo)));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
services()
.users
.set_password(sender_user, Some(&body.new_password))?;
services().users.set_password(sender_user, Some(&body.new_password))?;
if body.logout_devices {
// Logout all devices except the current one
for id in services()
.users
.all_device_ids(sender_user)
.filter_map(|id| id.ok())
.filter_map(Result::ok)
.filter(|id| id != sender_device)
{
services().users.remove_device(sender_user, &id)?;
}
}
info!("User {} changed their password.", sender_user);
services()
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} changed their password."
)));
info!(user_id = %sender_user, "User changed their password");
services().admin.send_message(RoomMessageEventContent::notice_plain(
format!("User {sender_user} changed their password."),
));
Ok(change_password::v3::Response {})
Ok(Ra(change_password::v3::Response {}))
}
/// # `GET _matrix/client/r0/account/whoami`
///
/// Get user_id of the sender user.
/// Get `user_id` of the sender user.
///
/// Note: Also works for Application Services
pub async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoami::v3::Response> {
pub(crate) async fn whoami_route(
body: Ar<whoami::v3::Request>,
) -> Result<Ra<whoami::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device_id = body.sender_device.as_ref().cloned();
let device_id = body.sender_device.clone();
Ok(whoami::v3::Response {
Ok(Ra(whoami::v3::Response {
user_id: sender_user.clone(),
device_id,
is_guest: services().users.is_deactivated(sender_user)? && body.appservice_info.is_none(),
})
is_guest: services().users.is_deactivated(sender_user)?
&& body.appservice_info.is_none(),
}))
}
/// # `POST /_matrix/client/r0/account/deactivate`
@ -395,41 +404,43 @@ pub async fn whoami_route(body: Ruma<whoami::v3::Request>) -> Result<whoami::v3:
///
/// - Leaves all rooms and rejects all invitations
/// - Invalidates all access tokens
/// - Deletes all device metadata (device id, device display name, last seen ip, last seen ts)
/// - Deletes all device metadata (device id, device display name, last seen ip,
/// last seen ts)
/// - Forgets all to-device events
/// - Triggers device list updates
/// - Removes ability to log in again
pub async fn deactivate_route(
body: Ruma<deactivate::v3::Request>,
) -> Result<deactivate::v3::Response> {
pub(crate) async fn deactivate_route(
body: Ar<deactivate::v3::Request>,
) -> Result<Ra<deactivate::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
let mut uiaainfo = UiaaInfo {
flows: vec![AuthFlow {
stages: vec![AuthType::Password],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) =
services()
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
let (worked, uiaainfo) = services().uiaa.try_auth(
sender_user,
sender_device,
auth,
&uiaainfo,
)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
services().uiaa.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(Box::new(uiaainfo)));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
@ -440,16 +451,14 @@ pub async fn deactivate_route(
// Remove devices and mark account as deactivated
services().users.deactivate_account(sender_user)?;
info!("User {} deactivated their account.", sender_user);
services()
.admin
.send_message(RoomMessageEventContent::notice_plain(format!(
"User {sender_user} deactivated their account."
)));
info!(user_id = %sender_user, "User deactivated their account");
services().admin.send_message(RoomMessageEventContent::notice_plain(
format!("User {sender_user} deactivated their account."),
));
Ok(deactivate::v3::Response {
Ok(Ra(deactivate::v3::Response {
id_server_unbind_result: ThirdPartyIdRemovalStatus::NoSupport,
})
}))
}
/// # `GET _matrix/client/v3/account/3pid`
@ -457,22 +466,25 @@ pub async fn deactivate_route(
/// Get a list of third party identifiers associated with this account.
///
/// - Currently always returns empty list
pub async fn third_party_route(
body: Ruma<get_3pids::v3::Request>,
) -> Result<get_3pids::v3::Response> {
let _sender_user = body.sender_user.as_ref().expect("user is authenticated");
pub(crate) async fn third_party_route(
body: Ar<get_3pids::v3::Request>,
) -> Result<Ra<get_3pids::v3::Response>> {
let _sender_user =
body.sender_user.as_ref().expect("user is authenticated");
Ok(get_3pids::v3::Response::new(Vec::new()))
Ok(Ra(get_3pids::v3::Response::new(Vec::new())))
}
/// # `POST /_matrix/client/v3/account/3pid/email/requestToken`
///
/// "This API should be used to request validation tokens when adding an email address to an account"
/// "This API should be used to request validation tokens when adding an email
/// address to an account"
///
/// - 403 signals that The homeserver does not allow the third party identifier as a contact option.
pub async fn request_3pid_management_token_via_email_route(
_body: Ruma<request_3pid_management_token_via_email::v3::Request>,
) -> Result<request_3pid_management_token_via_email::v3::Response> {
/// - 403 signals that The homeserver does not allow the third party identifier
/// as a contact option.
pub(crate) async fn request_3pid_management_token_via_email_route(
_body: Ar<request_3pid_management_token_via_email::v3::Request>,
) -> Result<Ra<request_3pid_management_token_via_email::v3::Response>> {
Err(Error::BadRequest(
ErrorKind::ThreepidDenied,
"Third party identifier is not allowed",
@ -481,12 +493,14 @@ pub async fn request_3pid_management_token_via_email_route(
/// # `POST /_matrix/client/v3/account/3pid/msisdn/requestToken`
///
/// "This API should be used to request validation tokens when adding an phone number to an account"
/// "This API should be used to request validation tokens when adding an phone
/// number to an account"
///
/// - 403 signals that The homeserver does not allow the third party identifier as a contact option.
pub async fn request_3pid_management_token_via_msisdn_route(
_body: Ruma<request_3pid_management_token_via_msisdn::v3::Request>,
) -> Result<request_3pid_management_token_via_msisdn::v3::Response> {
/// - 403 signals that The homeserver does not allow the third party identifier
/// as a contact option.
pub(crate) async fn request_3pid_management_token_via_msisdn_route(
_body: Ar<request_3pid_management_token_via_msisdn::v3::Request>,
) -> Result<Ra<request_3pid_management_token_via_msisdn::v3::Response>> {
Err(Error::BadRequest(
ErrorKind::ThreepidDenied,
"Third party identifier is not allowed",

View file

@ -1,8 +1,7 @@
use crate::{services, Error, Result, Ruma};
use rand::seq::SliceRandom;
use ruma::{
api::{
appservice,
appservice::query::query_room_alias,
client::{
alias::{create_alias, delete_alias, get_alias},
error::ErrorKind,
@ -12,12 +11,17 @@ use ruma::{
OwnedRoomAliasId,
};
use crate::{services, Ar, Error, Ra, Result};
/// # `PUT /_matrix/client/r0/directory/room/{roomAlias}`
///
/// Creates a new room alias on this server.
pub async fn create_alias_route(
body: Ruma<create_alias::v3::Request>,
) -> Result<create_alias::v3::Response> {
pub(crate) async fn create_alias_route(
body: Ar<create_alias::v3::Request>,
) -> Result<Ra<create_alias::v3::Response>> {
let sender_user =
body.sender_user.as_deref().expect("user is authenticated");
if body.room_alias.server_name() != services().globals.server_name() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@ -25,39 +29,31 @@ pub async fn create_alias_route(
));
}
if let Some(ref info) = body.appservice_info {
if let Some(info) = &body.appservice_info {
if !info.aliases.is_match(body.room_alias.as_str()) {
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"Room alias is not in namespace.",
));
}
} else if services()
.appservice
.is_exclusive_alias(&body.room_alias)
.await
{
} else if services().appservice.is_exclusive_alias(&body.room_alias).await {
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"Room alias reserved by appservice.",
));
}
if services()
.rooms
.alias
.resolve_local_alias(&body.room_alias)?
.is_some()
{
if services().rooms.alias.resolve_local_alias(&body.room_alias)?.is_some() {
return Err(Error::Conflict("Alias already exists."));
}
services()
.rooms
.alias
.set_alias(&body.room_alias, &body.room_id)?;
services().rooms.alias.set_alias(
&body.room_alias,
&body.room_id,
sender_user,
)?;
Ok(create_alias::v3::Response::new())
Ok(Ra(create_alias::v3::Response::new()))
}
/// # `DELETE /_matrix/client/r0/directory/room/{roomAlias}`
@ -66,9 +62,12 @@ pub async fn create_alias_route(
///
/// - TODO: additional access control checks
/// - TODO: Update canonical alias event
pub async fn delete_alias_route(
body: Ruma<delete_alias::v3::Request>,
) -> Result<delete_alias::v3::Response> {
pub(crate) async fn delete_alias_route(
body: Ar<delete_alias::v3::Request>,
) -> Result<Ra<delete_alias::v3::Response>> {
let sender_user =
body.sender_user.as_deref().expect("user is authenticated");
if body.room_alias.server_name() != services().globals.server_name() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@ -76,29 +75,25 @@ pub async fn delete_alias_route(
));
}
if let Some(ref info) = body.appservice_info {
if let Some(info) = &body.appservice_info {
if !info.aliases.is_match(body.room_alias.as_str()) {
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"Room alias is not in namespace.",
));
}
} else if services()
.appservice
.is_exclusive_alias(&body.room_alias)
.await
{
} else if services().appservice.is_exclusive_alias(&body.room_alias).await {
return Err(Error::BadRequest(
ErrorKind::Exclusive,
"Room alias reserved by appservice.",
));
}
services().rooms.alias.remove_alias(&body.room_alias)?;
services().rooms.alias.remove_alias(&body.room_alias, sender_user)?;
// TODO: update alt_aliases?
Ok(delete_alias::v3::Response::new())
Ok(Ra(delete_alias::v3::Response::new()))
}
/// # `GET /_matrix/client/r0/directory/room/{roomAlias}`
@ -106,12 +101,14 @@ pub async fn delete_alias_route(
/// Resolve an alias locally or over federation.
///
/// - TODO: Suggest more servers to join via
pub async fn get_alias_route(
body: Ruma<get_alias::v3::Request>,
) -> Result<get_alias::v3::Response> {
get_alias_helper(body.body.room_alias).await
pub(crate) async fn get_alias_route(
body: Ar<get_alias::v3::Request>,
) -> Result<Ra<get_alias::v3::Response>> {
get_alias_helper(body.body.room_alias).await.map(Ra)
}
// Can't use `services().rooms.alias.resolve_alias` because we also need the set
// of servers from the remote get_room_information request.
pub(crate) async fn get_alias_helper(
room_alias: OwnedRoomAliasId,
) -> Result<get_alias::v3::Response> {
@ -121,7 +118,7 @@ pub(crate) async fn get_alias_helper(
.send_federation_request(
room_alias.server_name(),
federation::query::get_room_information::v1::Request {
room_alias: room_alias.to_owned(),
room_alias: room_alias.clone(),
},
)
.await?;
@ -143,7 +140,7 @@ pub(crate) async fn get_alias_helper(
.sending
.send_appservice_request(
appservice.registration.clone(),
appservice::query::query_room_alias::v1::Request {
query_room_alias::v1::Request {
room_alias: room_alias.clone(),
},
)
@ -157,23 +154,23 @@ pub(crate) async fn get_alias_helper(
.alias
.resolve_local_alias(&room_alias)?
.ok_or_else(|| {
Error::bad_config("Appservice lied to us. Room does not exist.")
Error::bad_config(
"Appservice lied to us. Room does not \
exist.",
)
})?,
);
break;
}
}
}
};
}
let room_id = match room_id {
Some(room_id) => room_id,
None => {
return Err(Error::BadRequest(
ErrorKind::NotFound,
"Room with alias not found.",
))
}
let Some(room_id) = room_id else {
return Err(Error::BadRequest(
ErrorKind::NotFound,
"Room with alias not found.",
));
};
Ok(get_alias::v3::Response::new(

View file

@ -1,49 +1,54 @@
use crate::{services, Error, Result, Ruma};
use ruma::api::client::{
backup::{
add_backup_keys, add_backup_keys_for_room, add_backup_keys_for_session,
create_backup_version, delete_backup_keys, delete_backup_keys_for_room,
delete_backup_keys_for_session, delete_backup_version, get_backup_info, get_backup_keys,
get_backup_keys_for_room, get_backup_keys_for_session, get_latest_backup_info,
update_backup_version,
delete_backup_keys_for_session, delete_backup_version, get_backup_info,
get_backup_keys, get_backup_keys_for_room, get_backup_keys_for_session,
get_latest_backup_info, update_backup_version,
},
error::ErrorKind,
};
use crate::{services, Ar, Error, Ra, Result};
/// # `POST /_matrix/client/r0/room_keys/version`
///
/// Creates a new backup.
pub async fn create_backup_version_route(
body: Ruma<create_backup_version::v3::Request>,
) -> Result<create_backup_version::v3::Response> {
pub(crate) async fn create_backup_version_route(
body: Ar<create_backup_version::v3::Request>,
) -> Result<Ra<create_backup_version::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let version = services()
.key_backups
.create_backup(sender_user, &body.algorithm)?;
let version =
services().key_backups.create_backup(sender_user, &body.algorithm)?;
Ok(create_backup_version::v3::Response { version })
Ok(Ra(create_backup_version::v3::Response {
version,
}))
}
/// # `PUT /_matrix/client/r0/room_keys/version/{version}`
///
/// Update information about an existing backup. Only `auth_data` can be modified.
pub async fn update_backup_version_route(
body: Ruma<update_backup_version::v3::Request>,
) -> Result<update_backup_version::v3::Response> {
/// Update information about an existing backup. Only `auth_data` can be
/// modified.
pub(crate) async fn update_backup_version_route(
body: Ar<update_backup_version::v3::Request>,
) -> Result<Ra<update_backup_version::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.key_backups
.update_backup(sender_user, &body.version, &body.algorithm)?;
services().key_backups.update_backup(
sender_user,
&body.version,
&body.algorithm,
)?;
Ok(update_backup_version::v3::Response {})
Ok(Ra(update_backup_version::v3::Response {}))
}
/// # `GET /_matrix/client/r0/room_keys/version`
///
/// Get information about the latest backup version.
pub async fn get_latest_backup_info_route(
body: Ruma<get_latest_backup_info::v3::Request>,
) -> Result<get_latest_backup_info::v3::Response> {
pub(crate) async fn get_latest_backup_info_route(
body: Ar<get_latest_backup_info::v3::Request>,
) -> Result<Ra<get_latest_backup_info::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let (version, algorithm) = services()
@ -54,20 +59,24 @@ pub async fn get_latest_backup_info_route(
"Key backup does not exist.",
))?;
Ok(get_latest_backup_info::v3::Response {
Ok(Ra(get_latest_backup_info::v3::Response {
algorithm,
count: (services().key_backups.count_keys(sender_user, &version)? as u32).into(),
count: services()
.key_backups
.count_keys(sender_user, &version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &version)?,
version,
})
}))
}
/// # `GET /_matrix/client/r0/room_keys/version`
///
/// Get information about an existing backup.
pub async fn get_backup_info_route(
body: Ruma<get_backup_info::v3::Request>,
) -> Result<get_backup_info::v3::Response> {
pub(crate) async fn get_backup_info_route(
body: Ar<get_backup_info::v3::Request>,
) -> Result<Ra<get_backup_info::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let algorithm = services()
.key_backups
@ -77,46 +86,45 @@ pub async fn get_backup_info_route(
"Key backup does not exist.",
))?;
Ok(get_backup_info::v3::Response {
Ok(Ra(get_backup_info::v3::Response {
algorithm,
count: (services()
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
version: body.version.to_owned(),
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
version: body.version.clone(),
}))
}
/// # `DELETE /_matrix/client/r0/room_keys/version/{version}`
///
/// Delete an existing key backup.
///
/// - Deletes both information about the backup, as well as all key data related to the backup
pub async fn delete_backup_version_route(
body: Ruma<delete_backup_version::v3::Request>,
) -> Result<delete_backup_version::v3::Response> {
/// - Deletes both information about the backup, as well as all key data related
/// to the backup
pub(crate) async fn delete_backup_version_route(
body: Ar<delete_backup_version::v3::Request>,
) -> Result<Ra<delete_backup_version::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.key_backups
.delete_backup(sender_user, &body.version)?;
services().key_backups.delete_backup(sender_user, &body.version)?;
Ok(delete_backup_version::v3::Response {})
Ok(Ra(delete_backup_version::v3::Response {}))
}
/// # `PUT /_matrix/client/r0/room_keys/keys`
///
/// Add the received backup keys to the database.
///
/// - Only manipulating the most recently created version of the backup is allowed
/// - Only manipulating the most recently created version of the backup is
/// allowed
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub async fn add_backup_keys_route(
body: Ruma<add_backup_keys::v3::Request>,
) -> Result<add_backup_keys::v3::Response> {
pub(crate) async fn add_backup_keys_route(
body: Ar<add_backup_keys::v3::Request>,
) -> Result<Ra<add_backup_keys::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
@ -127,7 +135,8 @@ pub async fn add_backup_keys_route(
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"You may only manipulate the most recently created version of the backup.",
"You may only manipulate the most recently created version of the \
backup.",
));
}
@ -139,31 +148,31 @@ pub async fn add_backup_keys_route(
room_id,
session_id,
key_data,
)?
)?;
}
}
Ok(add_backup_keys::v3::Response {
count: (services()
Ok(Ra(add_backup_keys::v3::Response {
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
}))
}
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Add the received backup keys to the database.
///
/// - Only manipulating the most recently created version of the backup is allowed
/// - Only manipulating the most recently created version of the backup is
/// allowed
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub async fn add_backup_keys_for_room_route(
body: Ruma<add_backup_keys_for_room::v3::Request>,
) -> Result<add_backup_keys_for_room::v3::Response> {
pub(crate) async fn add_backup_keys_for_room_route(
body: Ar<add_backup_keys_for_room::v3::Request>,
) -> Result<Ra<add_backup_keys_for_room::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
@ -174,7 +183,8 @@ pub async fn add_backup_keys_for_room_route(
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"You may only manipulate the most recently created version of the backup.",
"You may only manipulate the most recently created version of the \
backup.",
));
}
@ -185,30 +195,30 @@ pub async fn add_backup_keys_for_room_route(
&body.room_id,
session_id,
key_data,
)?
)?;
}
Ok(add_backup_keys_for_room::v3::Response {
count: (services()
Ok(Ra(add_backup_keys_for_room::v3::Response {
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
}))
}
/// # `PUT /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Add the received backup key to the database.
///
/// - Only manipulating the most recently created version of the backup is allowed
/// - Only manipulating the most recently created version of the backup is
/// allowed
/// - Adds the keys to the backup
/// - Returns the new number of keys in this backup and the etag
pub async fn add_backup_keys_for_session_route(
body: Ruma<add_backup_keys_for_session::v3::Request>,
) -> Result<add_backup_keys_for_session::v3::Response> {
pub(crate) async fn add_backup_keys_for_session_route(
body: Ar<add_backup_keys_for_session::v3::Request>,
) -> Result<Ra<add_backup_keys_for_session::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if Some(&body.version)
@ -219,7 +229,8 @@ pub async fn add_backup_keys_for_session_route(
{
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"You may only manipulate the most recently created version of the backup.",
"You may only manipulate the most recently created version of the \
backup.",
));
}
@ -231,116 +242,126 @@ pub async fn add_backup_keys_for_session_route(
&body.session_data,
)?;
Ok(add_backup_keys_for_session::v3::Response {
count: (services()
Ok(Ra(add_backup_keys_for_session::v3::Response {
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
}))
}
/// # `GET /_matrix/client/r0/room_keys/keys`
///
/// Retrieves all keys from the backup.
pub async fn get_backup_keys_route(
body: Ruma<get_backup_keys::v3::Request>,
) -> Result<get_backup_keys::v3::Response> {
pub(crate) async fn get_backup_keys_route(
body: Ar<get_backup_keys::v3::Request>,
) -> Result<Ra<get_backup_keys::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let rooms = services().key_backups.get_all(sender_user, &body.version)?;
Ok(get_backup_keys::v3::Response { rooms })
Ok(Ra(get_backup_keys::v3::Response {
rooms,
}))
}
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Retrieves all keys from the backup for a given room.
pub async fn get_backup_keys_for_room_route(
body: Ruma<get_backup_keys_for_room::v3::Request>,
) -> Result<get_backup_keys_for_room::v3::Response> {
pub(crate) async fn get_backup_keys_for_room_route(
body: Ar<get_backup_keys_for_room::v3::Request>,
) -> Result<Ra<get_backup_keys_for_room::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sessions = services()
.key_backups
.get_room(sender_user, &body.version, &body.room_id)?;
let sessions = services().key_backups.get_room(
sender_user,
&body.version,
&body.room_id,
)?;
Ok(get_backup_keys_for_room::v3::Response { sessions })
Ok(Ra(get_backup_keys_for_room::v3::Response {
sessions,
}))
}
/// # `GET /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Retrieves a key from the backup.
pub async fn get_backup_keys_for_session_route(
body: Ruma<get_backup_keys_for_session::v3::Request>,
) -> Result<get_backup_keys_for_session::v3::Response> {
pub(crate) async fn get_backup_keys_for_session_route(
body: Ar<get_backup_keys_for_session::v3::Request>,
) -> Result<Ra<get_backup_keys_for_session::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let key_data = services()
.key_backups
.get_session(sender_user, &body.version, &body.room_id, &body.session_id)?
.get_session(
sender_user,
&body.version,
&body.room_id,
&body.session_id,
)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"Backup key not found for this user's session.",
))?;
Ok(get_backup_keys_for_session::v3::Response { key_data })
Ok(Ra(get_backup_keys_for_session::v3::Response {
key_data,
}))
}
/// # `DELETE /_matrix/client/r0/room_keys/keys`
///
/// Delete the keys from the backup.
pub async fn delete_backup_keys_route(
body: Ruma<delete_backup_keys::v3::Request>,
) -> Result<delete_backup_keys::v3::Response> {
pub(crate) async fn delete_backup_keys_route(
body: Ar<delete_backup_keys::v3::Request>,
) -> Result<Ra<delete_backup_keys::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.key_backups
.delete_all_keys(sender_user, &body.version)?;
services().key_backups.delete_all_keys(sender_user, &body.version)?;
Ok(delete_backup_keys::v3::Response {
count: (services()
Ok(Ra(delete_backup_keys::v3::Response {
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
}))
}
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}`
///
/// Delete the keys from the backup for a given room.
pub async fn delete_backup_keys_for_room_route(
body: Ruma<delete_backup_keys_for_room::v3::Request>,
) -> Result<delete_backup_keys_for_room::v3::Response> {
pub(crate) async fn delete_backup_keys_for_room_route(
body: Ar<delete_backup_keys_for_room::v3::Request>,
) -> Result<Ra<delete_backup_keys_for_room::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.key_backups
.delete_room_keys(sender_user, &body.version, &body.room_id)?;
services().key_backups.delete_room_keys(
sender_user,
&body.version,
&body.room_id,
)?;
Ok(delete_backup_keys_for_room::v3::Response {
count: (services()
Ok(Ra(delete_backup_keys_for_room::v3::Response {
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
}))
}
/// # `DELETE /_matrix/client/r0/room_keys/keys/{roomId}/{sessionId}`
///
/// Delete a key from the backup.
pub async fn delete_backup_keys_for_session_route(
body: Ruma<delete_backup_keys_for_session::v3::Request>,
) -> Result<delete_backup_keys_for_session::v3::Response> {
pub(crate) async fn delete_backup_keys_for_session_route(
body: Ar<delete_backup_keys_for_session::v3::Request>,
) -> Result<Ra<delete_backup_keys_for_session::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services().key_backups.delete_room_key(
@ -350,13 +371,12 @@ pub async fn delete_backup_keys_for_session_route(
&body.session_id,
)?;
Ok(delete_backup_keys_for_session::v3::Response {
count: (services()
Ok(Ra(delete_backup_keys_for_session::v3::Response {
count: services()
.key_backups
.count_keys(sender_user, &body.version)? as u32)
.into(),
etag: services()
.key_backups
.get_etag(sender_user, &body.version)?,
})
.count_keys(sender_user, &body.version)?
.try_into()
.expect("count should fit in UInt"),
etag: services().key_backups.get_etag(sender_user, &body.version)?,
}))
}

View file

@ -1,19 +1,20 @@
use crate::{services, Result, Ruma};
use ruma::api::client::discovery::get_capabilities::{
self, Capabilities, RoomVersionStability, RoomVersionsCapability,
};
use std::collections::BTreeMap;
use ruma::api::client::discovery::get_capabilities::{
self,
v3::{Capabilities, RoomVersionStability, RoomVersionsCapability},
};
use crate::{services, Ar, Ra, Result};
/// # `GET /_matrix/client/r0/capabilities`
///
/// Get information on the supported feature set and other relevent capabilities of this server.
pub async fn get_capabilities_route(
_body: Ruma<get_capabilities::v3::Request>,
) -> Result<get_capabilities::v3::Response> {
/// Get information on the supported feature set and other relevent capabilities
/// of this server.
pub(crate) async fn get_capabilities_route(
_body: Ar<get_capabilities::v3::Request>,
) -> Result<Ra<get_capabilities::v3::Response>> {
let mut available = BTreeMap::new();
for room_version in &services().globals.unstable_room_versions {
available.insert(room_version.clone(), RoomVersionStability::Unstable);
}
for room_version in &services().globals.stable_room_versions {
available.insert(room_version.clone(), RoomVersionStability::Stable);
}
@ -24,5 +25,7 @@ pub async fn get_capabilities_route(
available,
};
Ok(get_capabilities::v3::Response { capabilities })
Ok(Ra(get_capabilities::v3::Response {
capabilities,
}))
}

View file

@ -1,116 +1,80 @@
use crate::{services, Error, Result, Ruma};
use ruma::{
api::client::{
config::{
get_global_account_data, get_room_account_data, set_global_account_data,
set_room_account_data,
},
error::ErrorKind,
use ruma::api::client::{
config::{
get_global_account_data, get_room_account_data,
set_global_account_data, set_room_account_data,
},
events::{AnyGlobalAccountDataEventContent, AnyRoomAccountDataEventContent},
serde::Raw,
error::ErrorKind,
};
use serde::Deserialize;
use serde_json::{json, value::RawValue as RawJsonValue};
use crate::{services, Ar, Error, Ra, Result};
/// # `PUT /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Sets some account data for the sender user.
pub async fn set_global_account_data_route(
body: Ruma<set_global_account_data::v3::Request>,
) -> Result<set_global_account_data::v3::Response> {
pub(crate) async fn set_global_account_data_route(
body: Ar<set_global_account_data::v3::Request>,
) -> Result<Ra<set_global_account_data::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data: serde_json::Value = serde_json::from_str(body.data.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string();
services().account_data.update(
None,
services().account_data.update_global_any(
sender_user,
event_type.clone().into(),
&json!({
"type": event_type,
"content": data,
}),
&body.event_type,
&body.data,
)?;
Ok(set_global_account_data::v3::Response {})
Ok(Ra(set_global_account_data::v3::Response {}))
}
/// # `PUT /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
///
/// Sets some room account data for the sender user.
pub async fn set_room_account_data_route(
body: Ruma<set_room_account_data::v3::Request>,
) -> Result<set_room_account_data::v3::Response> {
pub(crate) async fn set_room_account_data_route(
body: Ar<set_room_account_data::v3::Request>,
) -> Result<Ra<set_room_account_data::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let data: serde_json::Value = serde_json::from_str(body.data.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Data is invalid."))?;
let event_type = body.event_type.to_string();
services().account_data.update(
Some(&body.room_id),
services().account_data.update_room_any(
&body.room_id,
sender_user,
event_type.clone().into(),
&json!({
"type": event_type,
"content": data,
}),
&body.event_type,
&body.data,
)?;
Ok(set_room_account_data::v3::Response {})
Ok(Ra(set_room_account_data::v3::Response {}))
}
/// # `GET /_matrix/client/r0/user/{userId}/account_data/{type}`
///
/// Gets some account data for the sender user.
pub async fn get_global_account_data_route(
body: Ruma<get_global_account_data::v3::Request>,
) -> Result<get_global_account_data::v3::Response> {
pub(crate) async fn get_global_account_data_route(
body: Ar<get_global_account_data::v3::Request>,
) -> Result<Ra<get_global_account_data::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event: Box<RawJsonValue> = services()
let account_data = services()
.account_data
.get(None, sender_user, body.event_type.to_string().into())?
.get_global_any(sender_user, &body.event_type)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
let account_data = serde_json::from_str::<ExtractGlobalEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
Ok(get_global_account_data::v3::Response { account_data })
Ok(Ra(get_global_account_data::v3::Response {
account_data,
}))
}
/// # `GET /_matrix/client/r0/user/{userId}/rooms/{roomId}/account_data/{type}`
///
/// Gets some room account data for the sender user.
pub async fn get_room_account_data_route(
body: Ruma<get_room_account_data::v3::Request>,
) -> Result<get_room_account_data::v3::Response> {
pub(crate) async fn get_room_account_data_route(
body: Ar<get_room_account_data::v3::Request>,
) -> Result<Ra<get_room_account_data::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event: Box<RawJsonValue> = services()
let account_data = services()
.account_data
.get(Some(&body.room_id), sender_user, body.event_type.clone())?
.get_room_any(&body.room_id, sender_user, &body.event_type)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Data not found."))?;
let account_data = serde_json::from_str::<ExtractRoomEventContent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
Ok(get_room_account_data::v3::Response { account_data })
}
#[derive(Deserialize)]
struct ExtractRoomEventContent {
content: Raw<AnyRoomAccountDataEventContent>,
}
#[derive(Deserialize)]
struct ExtractGlobalEventContent {
content: Raw<AnyGlobalAccountDataEventContent>,
Ok(Ra(get_room_account_data::v3::Response {
account_data,
}))
}

View file

@ -1,60 +1,58 @@
use crate::{services, Error, Result, Ruma};
use ruma::{
api::client::{context::get_context, error::ErrorKind, filter::LazyLoadOptions},
events::StateEventType,
};
use std::collections::HashSet;
use ruma::{
api::client::{
context::get_context, error::ErrorKind, filter::LazyLoadOptions,
},
events::StateEventType,
uint,
};
use tracing::error;
use crate::{services, Ar, Error, Ra, Result};
/// # `GET /_matrix/client/r0/rooms/{roomId}/context`
///
/// Allows loading room history around an event.
///
/// - Only works if the user is joined (TODO: always allow, but only show events if the user was
/// joined, depending on history_visibility)
pub async fn get_context_route(
body: Ruma<get_context::v3::Request>,
) -> Result<get_context::v3::Response> {
/// - Only works if the user is joined (TODO: always allow, but only show events
/// if the user was joined, depending on `history_visibility`)
#[allow(clippy::too_many_lines)]
pub(crate) async fn get_context_route(
body: Ar<get_context::v3::Request>,
) -> Result<Ra<get_context::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
let (lazy_load_enabled, lazy_load_send_redundant) = match &body.filter.lazy_load_options {
LazyLoadOptions::Enabled {
include_redundant_members,
} => (true, *include_redundant_members),
_ => (false, false),
};
let (lazy_load_enabled, lazy_load_send_redundant) =
match &body.filter.lazy_load_options {
LazyLoadOptions::Enabled {
include_redundant_members,
} => (true, *include_redundant_members),
LazyLoadOptions::Disabled => (false, false),
};
let mut lazy_loaded = HashSet::new();
let base_token = services()
.rooms
.timeline
.get_pdu_count(&body.event_id)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"Base event id not found.",
))?;
let base_token =
services().rooms.timeline.get_pdu_count(&body.event_id)?.ok_or(
Error::BadRequest(ErrorKind::NotFound, "Base event id not found."),
)?;
let base_event =
services()
.rooms
.timeline
.get_pdu(&body.event_id)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"Base event not found.",
))?;
let base_event = services().rooms.timeline.get_pdu(&body.event_id)?.ok_or(
Error::BadRequest(ErrorKind::NotFound, "Base event not found."),
)?;
let room_id = base_event.room_id.clone();
if !services()
.rooms
.state_accessor
.user_can_see_event(sender_user, &room_id, &body.event_id)?
{
if !services().rooms.state_accessor.user_can_see_event(
sender_user,
&room_id,
&body.event_id,
)? {
return Err(Error::BadRequest(
ErrorKind::Forbidden,
ErrorKind::forbidden(),
"You don't have permission to view this event.",
));
}
@ -70,7 +68,8 @@ pub async fn get_context_route(
}
// Use limit with maximum 100
let limit = u64::from(body.limit).min(100) as usize;
let half_limit = usize::try_from(body.limit.min(uint!(100)) / uint!(2))
.expect("0-50 should fit in usize");
let base_event = base_event.to_room_event();
@ -78,8 +77,8 @@ pub async fn get_context_route(
.rooms
.timeline
.pdus_until(sender_user, &room_id, base_token)?
.take(limit / 2)
.filter_map(|r| r.ok()) // Remove buggy events
.take(half_limit)
.filter_map(Result::ok)
.filter(|(_, pdu)| {
services()
.rooms
@ -103,20 +102,17 @@ pub async fn get_context_route(
let start_token = events_before
.last()
.map(|(count, _)| count.stringify())
.unwrap_or_else(|| base_token.stringify());
.map_or_else(|| base_token.stringify(), |(count, _)| count.stringify());
let events_before: Vec<_> = events_before
.into_iter()
.map(|(_, pdu)| pdu.to_room_event())
.collect();
let events_before: Vec<_> =
events_before.into_iter().map(|(_, pdu)| pdu.to_room_event()).collect();
let events_after: Vec<_> = services()
.rooms
.timeline
.pdus_after(sender_user, &room_id, base_token)?
.take(limit / 2)
.filter_map(|r| r.ok()) // Remove buggy events
.take(half_limit)
.filter_map(Result::ok)
.filter(|(_, pdu)| {
services()
.rooms
@ -138,59 +134,46 @@ pub async fn get_context_route(
}
}
let shortstatehash = match services().rooms.state_accessor.pdu_shortstatehash(
events_after
.last()
.map_or(&*body.event_id, |(_, e)| &*e.event_id),
)? {
Some(s) => s,
None => services()
.rooms
.state
.get_room_shortstatehash(&room_id)?
.expect("All rooms have state"),
};
let shortstatehash =
match services().rooms.state_accessor.pdu_shortstatehash(
events_after.last().map_or(&*body.event_id, |(_, e)| &*e.event_id),
)? {
Some(s) => s,
None => services()
.rooms
.state
.get_room_shortstatehash(&room_id)?
.expect("All rooms have state"),
};
let state_ids = services()
.rooms
.state_accessor
.state_full_ids(shortstatehash)
.await?;
let state_ids =
services().rooms.state_accessor.state_full_ids(shortstatehash).await?;
let end_token = events_after
.last()
.map(|(count, _)| count.stringify())
.unwrap_or_else(|| base_token.stringify());
.map_or_else(|| base_token.stringify(), |(count, _)| count.stringify());
let events_after: Vec<_> = events_after
.into_iter()
.map(|(_, pdu)| pdu.to_room_event())
.collect();
let events_after: Vec<_> =
events_after.into_iter().map(|(_, pdu)| pdu.to_room_event()).collect();
let mut state = Vec::new();
for (shortstatekey, id) in state_ids {
let (event_type, state_key) = services()
.rooms
.short
.get_statekey_from_short(shortstatekey)?;
for (shortstatekey, event_id) in state_ids {
let (event_type, state_key) =
services().rooms.short.get_statekey_from_short(shortstatekey)?;
if event_type != StateEventType::RoomMember {
let pdu = match services().rooms.timeline.get_pdu(&id)? {
Some(pdu) => pdu,
None => {
error!("Pdu in state not found: {}", id);
continue;
}
let Some(pdu) = services().rooms.timeline.get_pdu(&event_id)?
else {
error!(%event_id, "Event in state not found");
continue;
};
state.push(pdu.to_state_event());
} else if !lazy_load_enabled || lazy_loaded.contains(&state_key) {
let pdu = match services().rooms.timeline.get_pdu(&id)? {
Some(pdu) => pdu,
None => {
error!("Pdu in state not found: {}", id);
continue;
}
let Some(pdu) = services().rooms.timeline.get_pdu(&event_id)?
else {
error!(%event_id, "Event in state not found");
continue;
};
state.push(pdu.to_state_event());
}
@ -205,5 +188,5 @@ pub async fn get_context_route(
state,
};
Ok(resp)
Ok(Ra(resp))
}

View file

@ -1,35 +1,40 @@
use crate::{services, utils, Error, Result, Ruma};
use ruma::api::client::{
device::{self, delete_device, delete_devices, get_device, get_devices, update_device},
device::{
self, delete_device, delete_devices, get_device, get_devices,
update_device,
},
error::ErrorKind,
uiaa::{AuthFlow, AuthType, UiaaInfo},
};
use super::SESSION_ID_LENGTH;
use crate::{services, utils, Ar, Error, Ra, Result};
/// # `GET /_matrix/client/r0/devices`
///
/// Get metadata on all devices of the sender user.
pub async fn get_devices_route(
body: Ruma<get_devices::v3::Request>,
) -> Result<get_devices::v3::Response> {
pub(crate) async fn get_devices_route(
body: Ar<get_devices::v3::Request>,
) -> Result<Ra<get_devices::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let devices: Vec<device::Device> = services()
.users
.all_devices_metadata(sender_user)
.filter_map(|r| r.ok()) // Filter out buggy devices
.filter_map(Result::ok)
.collect();
Ok(get_devices::v3::Response { devices })
Ok(Ra(get_devices::v3::Response {
devices,
}))
}
/// # `GET /_matrix/client/r0/devices/{deviceId}`
///
/// Get metadata on a single device of the sender user.
pub async fn get_device_route(
body: Ruma<get_device::v3::Request>,
) -> Result<get_device::v3::Response> {
pub(crate) async fn get_device_route(
body: Ar<get_device::v3::Request>,
) -> Result<Ra<get_device::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let device = services()
@ -37,15 +42,17 @@ pub async fn get_device_route(
.get_device_metadata(sender_user, &body.body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
Ok(get_device::v3::Response { device })
Ok(Ra(get_device::v3::Response {
device,
}))
}
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
///
/// Updates the metadata on a given device of the sender user.
pub async fn update_device_route(
body: Ruma<update_device::v3::Request>,
) -> Result<update_device::v3::Response> {
pub(crate) async fn update_device_route(
body: Ar<update_device::v3::Request>,
) -> Result<Ra<update_device::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut device = services()
@ -53,13 +60,15 @@ pub async fn update_device_route(
.get_device_metadata(sender_user, &body.device_id)?
.ok_or(Error::BadRequest(ErrorKind::NotFound, "Device not found."))?;
device.display_name = body.display_name.clone();
device.display_name.clone_from(&body.display_name);
services()
.users
.update_device_metadata(sender_user, &body.device_id, &device)?;
services().users.update_device_metadata(
sender_user,
&body.device_id,
&device,
)?;
Ok(update_device::v3::Response {})
Ok(Ra(update_device::v3::Response {}))
}
/// # `DELETE /_matrix/client/r0/devices/{deviceId}`
@ -68,14 +77,16 @@ pub async fn update_device_route(
///
/// - Requires UIAA to verify user password
/// - Invalidates access token
/// - Deletes device metadata (device id, device display name, last seen ip, last seen ts)
/// - Deletes device metadata (device id, device display name, last seen ip,
/// last seen ts)
/// - Forgets to-device events
/// - Triggers device list updates
pub async fn delete_device_route(
body: Ruma<delete_device::v3::Request>,
) -> Result<delete_device::v3::Response> {
pub(crate) async fn delete_device_route(
body: Ar<delete_device::v3::Request>,
) -> Result<Ra<delete_device::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
// UIAA
let mut uiaainfo = UiaaInfo {
@ -83,35 +94,33 @@ pub async fn delete_device_route(
stages: vec![AuthType::Password],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) =
services()
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
let (worked, uiaainfo) = services().uiaa.try_auth(
sender_user,
sender_device,
auth,
&uiaainfo,
)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
services().uiaa.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(Box::new(uiaainfo)));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
services()
.users
.remove_device(sender_user, &body.device_id)?;
services().users.remove_device(sender_user, &body.device_id)?;
Ok(delete_device::v3::Response {})
Ok(Ra(delete_device::v3::Response {}))
}
/// # `PUT /_matrix/client/r0/devices/{deviceId}`
@ -122,14 +131,16 @@ pub async fn delete_device_route(
///
/// For each device:
/// - Invalidates access token
/// - Deletes device metadata (device id, device display name, last seen ip, last seen ts)
/// - Deletes device metadata (device id, device display name, last seen ip,
/// last seen ts)
/// - Forgets to-device events
/// - Triggers device list updates
pub async fn delete_devices_route(
body: Ruma<delete_devices::v3::Request>,
) -> Result<delete_devices::v3::Response> {
pub(crate) async fn delete_devices_route(
body: Ar<delete_devices::v3::Request>,
) -> Result<Ra<delete_devices::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
// UIAA
let mut uiaainfo = UiaaInfo {
@ -137,33 +148,33 @@ pub async fn delete_devices_route(
stages: vec![AuthType::Password],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) =
services()
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
let (worked, uiaainfo) = services().uiaa.try_auth(
sender_user,
sender_device,
auth,
&uiaainfo,
)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
services().uiaa.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(Box::new(uiaainfo)));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
for device_id in &body.devices {
services().users.remove_device(sender_user, device_id)?
services().users.remove_device(sender_user, device_id)?;
}
Ok(delete_devices::v3::Response {})
Ok(Ra(delete_devices::v3::Response {}))
}

View file

@ -1,10 +1,9 @@
use crate::{services, Error, Result, Ruma};
use ruma::{
api::{
client::{
directory::{
get_public_rooms, get_public_rooms_filtered, get_room_visibility,
set_room_visibility,
get_public_rooms, get_public_rooms_filtered,
get_room_visibility, set_room_visibility,
},
error::ErrorKind,
room,
@ -16,26 +15,31 @@ use ruma::{
room::{
avatar::RoomAvatarEventContent,
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
history_visibility::{
HistoryVisibility, RoomHistoryVisibilityEventContent,
},
join_rules::{JoinRule, RoomJoinRulesEventContent},
topic::RoomTopicEventContent,
},
StateEventType,
},
ServerName, UInt,
uint, ServerName, UInt,
};
use tracing::{error, info, warn};
use crate::{
service::rooms::state::ExtractType, services, Ar, Error, Ra, Result,
};
/// # `POST /_matrix/client/r0/publicRooms`
///
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
pub async fn get_public_rooms_filtered_route(
body: Ruma<get_public_rooms_filtered::v3::Request>,
) -> Result<get_public_rooms_filtered::v3::Response> {
pub(crate) async fn get_public_rooms_filtered_route(
body: Ar<get_public_rooms_filtered::v3::Request>,
) -> Result<Ra<get_public_rooms_filtered::v3::Response>> {
get_public_rooms_filtered_helper(
body.server.as_deref(),
body.limit,
@ -44,6 +48,7 @@ pub async fn get_public_rooms_filtered_route(
&body.room_network,
)
.await
.map(Ra)
}
/// # `GET /_matrix/client/r0/publicRooms`
@ -51,9 +56,9 @@ pub async fn get_public_rooms_filtered_route(
/// Lists the public rooms on this server.
///
/// - Rooms are ordered by the number of joined members
pub async fn get_public_rooms_route(
body: Ruma<get_public_rooms::v3::Request>,
) -> Result<get_public_rooms::v3::Response> {
pub(crate) async fn get_public_rooms_route(
body: Ar<get_public_rooms::v3::Request>,
) -> Result<Ra<get_public_rooms::v3::Response>> {
let response = get_public_rooms_filtered_helper(
body.server.as_deref(),
body.limit,
@ -63,12 +68,12 @@ pub async fn get_public_rooms_route(
)
.await?;
Ok(get_public_rooms::v3::Response {
Ok(Ra(get_public_rooms::v3::Response {
chunk: response.chunk,
prev_batch: response.prev_batch,
next_batch: response.next_batch,
total_room_count_estimate: response.total_room_count_estimate,
})
}))
}
/// # `PUT /_matrix/client/r0/directory/list/room/{roomId}`
@ -76,9 +81,9 @@ pub async fn get_public_rooms_route(
/// Sets the visibility of a given room in the room directory.
///
/// - TODO: Access control checks
pub async fn set_room_visibility_route(
body: Ruma<set_room_visibility::v3::Request>,
) -> Result<set_room_visibility::v3::Response> {
pub(crate) async fn set_room_visibility_route(
body: Ar<set_room_visibility::v3::Request>,
) -> Result<Ra<set_room_visibility::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if !services().rooms.metadata.exists(&body.room_id)? {
@ -89,9 +94,15 @@ pub async fn set_room_visibility_route(
match &body.visibility {
room::Visibility::Public => {
services().rooms.directory.set_public(&body.room_id)?;
info!("{} made {} public", sender_user, body.room_id);
info!(
user_id = %sender_user,
room_id = %body.room_id,
"User made room public",
);
}
room::Visibility::Private => {
services().rooms.directory.set_not_public(&body.room_id)?;
}
room::Visibility::Private => services().rooms.directory.set_not_public(&body.room_id)?,
_ => {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
@ -100,29 +111,34 @@ pub async fn set_room_visibility_route(
}
}
Ok(set_room_visibility::v3::Response {})
Ok(Ra(set_room_visibility::v3::Response {}))
}
/// # `GET /_matrix/client/r0/directory/list/room/{roomId}`
///
/// Gets the visibility of a given room in the room directory.
pub async fn get_room_visibility_route(
body: Ruma<get_room_visibility::v3::Request>,
) -> Result<get_room_visibility::v3::Response> {
pub(crate) async fn get_room_visibility_route(
body: Ar<get_room_visibility::v3::Request>,
) -> Result<Ra<get_room_visibility::v3::Response>> {
if !services().rooms.metadata.exists(&body.room_id)? {
// Return 404 if the room doesn't exist
return Err(Error::BadRequest(ErrorKind::NotFound, "Room not found"));
}
Ok(get_room_visibility::v3::Response {
visibility: if services().rooms.directory.is_public_room(&body.room_id)? {
Ok(Ra(get_room_visibility::v3::Response {
visibility: if services()
.rooms
.directory
.is_public_room(&body.room_id)?
{
room::Visibility::Public
} else {
room::Visibility::Private
},
})
}))
}
#[allow(clippy::too_many_lines)]
pub(crate) async fn get_public_rooms_filtered_helper(
server: Option<&ServerName>,
limit: Option<UInt>,
@ -130,8 +146,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
filter: &Filter,
_network: &RoomNetwork,
) -> Result<get_public_rooms_filtered::v3::Response> {
if let Some(other_server) =
server.filter(|server| *server != services().globals.server_name().as_str())
if let Some(other_server) = server
.filter(|server| *server != services().globals.server_name().as_str())
{
let response = services()
.sending
@ -157,8 +173,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
});
}
let limit = limit.map_or(10, u64::from);
let mut num_since = 0_u64;
let limit = limit.unwrap_or(uint!(10));
let mut num_since = UInt::MIN;
if let Some(s) = &since {
let mut characters = s.chars();
@ -173,10 +189,9 @@ pub(crate) async fn get_public_rooms_filtered_helper(
}
};
num_since = characters
.collect::<String>()
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token."))?;
num_since = characters.collect::<String>().parse().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Invalid `since` token.")
})?;
if backwards {
num_since = num_since.saturating_sub(limit);
@ -187,129 +202,12 @@ pub(crate) async fn get_public_rooms_filtered_helper(
.rooms
.directory
.public_rooms()
.map(|room_id| {
let room_id = room_id?;
let chunk = PublicRoomsChunk {
canonical_alias: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCanonicalAlias, "")?
.map_or(Ok(None), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomCanonicalAliasEventContent| c.alias)
.map_err(|_| {
Error::bad_database("Invalid canonical alias event in database.")
})
})?,
name: services().rooms.state_accessor.get_name(&room_id)?,
num_joined_members: services()
.rooms
.state_cache
.room_joined_count(&room_id)?
.unwrap_or_else(|| {
warn!("Room {} has no member count", room_id);
0
})
.try_into()
.expect("user count should not be that big"),
topic: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomTopic, "")?
.map_or(Ok(None), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomTopicEventContent| Some(c.topic))
.map_err(|_| {
error!("Invalid room topic event in database for room {}", room_id);
Error::bad_database("Invalid room topic event in database.")
})
})?,
world_readable: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")?
.map_or(Ok(false), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomHistoryVisibilityEventContent| {
c.history_visibility == HistoryVisibility::WorldReadable
})
.map_err(|_| {
Error::bad_database(
"Invalid room history visibility event in database.",
)
})
})?,
guest_can_join: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomGuestAccess, "")?
.map_or(Ok(false), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomGuestAccessEventContent| {
c.guest_access == GuestAccess::CanJoin
})
.map_err(|_| {
Error::bad_database("Invalid room guest access event in database.")
})
})?,
avatar_url: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomAvatar, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomAvatarEventContent| c.url)
.map_err(|_| {
Error::bad_database("Invalid room avatar event in database.")
})
})
.transpose()?
// url is now an Option<String> so we must flatten
.flatten(),
join_rule: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomJoinRulesEventContent| match c.join_rule {
JoinRule::Public => Some(PublicRoomJoinRule::Public),
JoinRule::Knock => Some(PublicRoomJoinRule::Knock),
_ => None,
})
.map_err(|e| {
error!("Invalid room join rule event in database: {}", e);
Error::BadDatabase("Invalid room join rule event in database.")
})
})
.transpose()?
.flatten()
.ok_or_else(|| Error::bad_database("Missing room join rule event for room."))?,
room_type: services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCreate, "")?
.map(|s| {
serde_json::from_str::<RoomCreateEventContent>(s.content.get()).map_err(
|e| {
error!("Invalid room create event in database: {}", e);
Error::BadDatabase("Invalid room create event in database.")
},
)
})
.transpose()?
.and_then(|e| e.room_type),
room_id,
};
Ok(chunk)
})
.filter_map(|r: Result<_>| r.ok()) // Filter out buggy rooms
.filter_map(Result::ok)
.map(room_id_to_chunk)
.filter_map(Result::ok)
.filter(|chunk| {
if let Some(query) = filter
.generic_search_term
.as_ref()
.map(|q| q.to_lowercase())
if let Some(query) =
filter.generic_search_term.as_ref().map(|q| q.to_lowercase())
{
if let Some(name) = &chunk.name {
if name.as_str().to_lowercase().contains(&query) {
@ -324,7 +222,8 @@ pub(crate) async fn get_public_rooms_filtered_helper(
}
if let Some(canonical_alias) = &chunk.canonical_alias {
if canonical_alias.as_str().to_lowercase().contains(&query) {
if canonical_alias.as_str().to_lowercase().contains(&query)
{
return true;
}
}
@ -335,30 +234,31 @@ pub(crate) async fn get_public_rooms_filtered_helper(
true
}
})
// We need to collect all, so we can sort by member count
.collect();
all_rooms.sort_by(|l, r| r.num_joined_members.cmp(&l.num_joined_members));
let total_room_count_estimate = (all_rooms.len() as u32).into();
let total_room_count_estimate =
all_rooms.len().try_into().unwrap_or(UInt::MAX);
let chunk: Vec<_> = all_rooms
.into_iter()
.skip(num_since as usize)
.take(limit as usize)
.skip(num_since.try_into().expect("UInt should fit in usize"))
.take(limit.try_into().expect("UInt should fit in usize"))
.collect();
let prev_batch = if num_since == 0 {
let prev_batch = if num_since == uint!(0) {
None
} else {
Some(format!("p{num_since}"))
};
let next_batch = if chunk.len() < limit as usize {
None
} else {
Some(format!("n{}", num_since + limit))
};
let next_batch =
if chunk.len() < limit.try_into().expect("UInt should fit in usize") {
None
} else {
Some(format!("n{}", num_since + limit))
};
Ok(get_public_rooms_filtered::v3::Response {
chunk,
@ -367,3 +267,135 @@ pub(crate) async fn get_public_rooms_filtered_helper(
total_room_count_estimate: Some(total_room_count_estimate),
})
}
#[allow(clippy::too_many_lines)]
#[tracing::instrument]
fn room_id_to_chunk(room_id: ruma::OwnedRoomId) -> Result<PublicRoomsChunk> {
let canonical_alias = services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomCanonicalAlias, "")?
.map_or(Ok(None), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomCanonicalAliasEventContent| c.alias)
.map_err(|_| {
Error::bad_database(
"Invalid canonical alias event in database.",
)
})
})?;
let name = services().rooms.state_accessor.get_name(&room_id)?;
let num_joined_members = services()
.rooms
.state_cache
.room_joined_count(&room_id)?
.unwrap_or_else(|| {
warn!("Room has no member count");
0
})
.try_into()
.expect("user count should not be that big");
let topic = services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomTopic, "")?
.map_or(Ok(None), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomTopicEventContent| Some(c.topic))
.map_err(|_| {
error!("Invalid room topic event in database for room",);
Error::bad_database("Invalid room topic event in database.")
})
})?;
let world_readable = services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomHistoryVisibility, "")?
.map_or(Ok(false), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomHistoryVisibilityEventContent| {
c.history_visibility == HistoryVisibility::WorldReadable
})
.map_err(|_| {
Error::bad_database(
"Invalid room history visibility event in database.",
)
})
})?;
let guest_can_join = services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomGuestAccess, "")?
.map_or(Ok(false), |s| {
serde_json::from_str(s.content.get())
.map(|c: RoomGuestAccessEventContent| {
c.guest_access == GuestAccess::CanJoin
})
.map_err(|_| {
Error::bad_database(
"Invalid room guest access event in database.",
)
})
})?;
let avatar_url = services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomAvatar, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomAvatarEventContent| c.url)
.map_err(|_| {
Error::bad_database(
"Invalid room avatar event in database.",
)
})
})
.transpose()?
.flatten();
let join_rule = services()
.rooms
.state_accessor
.room_state_get(&room_id, &StateEventType::RoomJoinRules, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomJoinRulesEventContent| match c.join_rule {
JoinRule::Public => Some(PublicRoomJoinRule::Public),
JoinRule::Knock => Some(PublicRoomJoinRule::Knock),
_ => None,
})
.map_err(|error| {
error!(%error, "Invalid room join rule event in database");
Error::BadDatabase(
"Invalid room join rule event in database.",
)
})
})
.transpose()?
.flatten()
.ok_or_else(|| {
Error::bad_database("Missing room join rule event for room.")
})?;
let room_type =
services().rooms.state.get_create_content::<ExtractType>(&room_id)?;
Ok(PublicRoomsChunk {
canonical_alias,
name,
num_joined_members,
room_id,
topic,
world_readable,
guest_can_join,
avatar_url,
join_rule,
room_type,
})
}

View file

@ -1,34 +1,39 @@
use crate::{services, Error, Result, Ruma};
use ruma::api::client::{
error::ErrorKind,
filter::{create_filter, get_filter},
};
use crate::{services, Ar, Error, Ra, Result};
/// # `GET /_matrix/client/r0/user/{userId}/filter/{filterId}`
///
/// Loads a filter that was previously created.
///
/// - A user can only access their own filters
pub async fn get_filter_route(
body: Ruma<get_filter::v3::Request>,
) -> Result<get_filter::v3::Response> {
pub(crate) async fn get_filter_route(
body: Ar<get_filter::v3::Request>,
) -> Result<Ra<get_filter::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let filter = match services().users.get_filter(sender_user, &body.filter_id)? {
Some(filter) => filter,
None => return Err(Error::BadRequest(ErrorKind::NotFound, "Filter not found.")),
let Some(filter) =
services().users.get_filter(sender_user, &body.filter_id)?
else {
return Err(Error::BadRequest(
ErrorKind::NotFound,
"Filter not found.",
));
};
Ok(get_filter::v3::Response::new(filter))
Ok(Ra(get_filter::v3::Response::new(filter)))
}
/// # `PUT /_matrix/client/r0/user/{userId}/filter`
///
/// Creates a new filter to be used by other endpoints.
pub async fn create_filter_route(
body: Ruma<create_filter::v3::Request>,
) -> Result<create_filter::v3::Response> {
pub(crate) async fn create_filter_route(
body: Ar<create_filter::v3::Request>,
) -> Result<Ra<create_filter::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(create_filter::v3::Response::new(
Ok(Ra(create_filter::v3::Response::new(
services().users.create_filter(sender_user, &body.filter)?,
))
)))
}

View file

@ -1,44 +1,52 @@
use super::SESSION_ID_LENGTH;
use crate::{services, utils, Error, Result, Ruma};
use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet},
time::{Duration, Instant},
};
use futures_util::{stream::FuturesUnordered, StreamExt};
use ruma::{
api::{
client::{
error::ErrorKind,
keys::{
claim_keys, get_key_changes, get_keys, upload_keys, upload_signatures,
upload_signing_keys,
claim_keys, get_key_changes, get_keys, upload_keys,
upload_signatures, upload_signing_keys,
},
uiaa::{AuthFlow, AuthType, UiaaInfo},
},
federation,
},
serde::Raw,
DeviceKeyAlgorithm, OwnedDeviceId, OwnedUserId, UserId,
OneTimeKeyAlgorithm, OwnedDeviceId, OwnedServerName, OwnedUserId,
ServerName, UserId,
};
use serde_json::json;
use std::{
collections::{hash_map, BTreeMap, HashMap, HashSet},
time::{Duration, Instant},
};
use tracing::debug;
use super::SESSION_ID_LENGTH;
use crate::{services, utils, Ar, Error, Ra, Result};
/// # `POST /_matrix/client/r0/keys/upload`
///
/// Publish end-to-end encryption keys for the sender device.
///
/// - Adds one time keys
/// - If there are no device keys yet: Adds device keys (TODO: merge with existing keys?)
pub async fn upload_keys_route(
body: Ruma<upload_keys::v3::Request>,
) -> Result<upload_keys::v3::Response> {
/// - If there are no device keys yet: Adds device keys (TODO: merge with
/// existing keys?)
pub(crate) async fn upload_keys_route(
body: Ar<upload_keys::v3::Request>,
) -> Result<Ra<upload_keys::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
for (key_key, key_value) in &body.one_time_keys {
services()
.users
.add_one_time_key(sender_user, sender_device, key_key, key_value)?;
services().users.add_one_time_key(
sender_user,
sender_device,
key_key,
key_value,
)?;
}
if let Some(device_keys) = &body.device_keys {
@ -49,17 +57,19 @@ pub async fn upload_keys_route(
.get_device_keys(sender_user, sender_device)?
.is_none()
{
services()
.users
.add_device_keys(sender_user, sender_device, device_keys)?;
services().users.add_device_keys(
sender_user,
sender_device,
device_keys,
)?;
}
}
Ok(upload_keys::v3::Response {
Ok(Ra(upload_keys::v3::Response {
one_time_key_counts: services()
.users
.count_one_time_keys(sender_user, sender_device)?,
})
}))
}
/// # `POST /_matrix/client/r0/keys/query`
@ -68,25 +78,30 @@ pub async fn upload_keys_route(
///
/// - Always fetches users from other servers over federation
/// - Gets master keys, self-signing keys, user signing keys and device keys.
/// - The master and self-signing keys contain signatures that the user is allowed to see
pub async fn get_keys_route(body: Ruma<get_keys::v3::Request>) -> Result<get_keys::v3::Response> {
/// - The master and self-signing keys contain signatures that the user is
/// allowed to see
pub(crate) async fn get_keys_route(
body: Ar<get_keys::v3::Request>,
) -> Result<Ra<get_keys::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let response =
get_keys_helper(Some(sender_user), &body.device_keys, |u| u == sender_user).await?;
let response = get_keys_helper(Some(sender_user), &body.device_keys, |u| {
u == sender_user
})
.await?;
Ok(response)
Ok(Ra(response))
}
/// # `POST /_matrix/client/r0/keys/claim`
///
/// Claims one-time keys
pub async fn claim_keys_route(
body: Ruma<claim_keys::v3::Request>,
) -> Result<claim_keys::v3::Response> {
pub(crate) async fn claim_keys_route(
body: Ar<claim_keys::v3::Request>,
) -> Result<Ra<claim_keys::v3::Response>> {
let response = claim_keys_helper(&body.one_time_keys).await?;
Ok(response)
Ok(Ra(response))
}
/// # `POST /_matrix/client/r0/keys/device_signing/upload`
@ -94,11 +109,12 @@ pub async fn claim_keys_route(
/// Uploads end-to-end key information for the sender user.
///
/// - Requires UIAA to verify password
pub async fn upload_signing_keys_route(
body: Ruma<upload_signing_keys::v3::Request>,
) -> Result<upload_signing_keys::v3::Response> {
pub(crate) async fn upload_signing_keys_route(
body: Ar<upload_signing_keys::v3::Request>,
) -> Result<Ra<upload_signing_keys::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
// UIAA
let mut uiaainfo = UiaaInfo {
@ -106,26 +122,26 @@ pub async fn upload_signing_keys_route(
stages: vec![AuthType::Password],
}],
completed: Vec::new(),
params: Default::default(),
params: Some(Box::default()),
session: None,
auth_error: None,
};
if let Some(auth) = &body.auth {
let (worked, uiaainfo) =
services()
.uiaa
.try_auth(sender_user, sender_device, auth, &uiaainfo)?;
let (worked, uiaainfo) = services().uiaa.try_auth(
sender_user,
sender_device,
auth,
&uiaainfo,
)?;
if !worked {
return Err(Error::Uiaa(uiaainfo));
return Err(Error::Uiaa(Box::new(uiaainfo)));
}
// Success!
} else if let Some(json) = body.json_body {
uiaainfo.session = Some(utils::random_string(SESSION_ID_LENGTH));
services()
.uiaa
.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(uiaainfo));
services().uiaa.create(sender_user, sender_device, &uiaainfo, &json)?;
return Err(Error::Uiaa(Box::new(uiaainfo)));
} else {
return Err(Error::BadRequest(ErrorKind::NotJson, "Not json."));
}
@ -134,27 +150,29 @@ pub async fn upload_signing_keys_route(
services().users.add_cross_signing_keys(
sender_user,
master_key,
&body.self_signing_key,
&body.user_signing_key,
true, // notify so that other users see the new keys
body.self_signing_key.as_ref(),
body.user_signing_key.as_ref(),
// notify so that other users see the new keys
true,
)?;
}
Ok(upload_signing_keys::v3::Response {})
Ok(Ra(upload_signing_keys::v3::Response {}))
}
/// # `POST /_matrix/client/r0/keys/signatures/upload`
///
/// Uploads end-to-end key signatures from the sender user.
pub async fn upload_signatures_route(
body: Ruma<upload_signatures::v3::Request>,
) -> Result<upload_signatures::v3::Response> {
pub(crate) async fn upload_signatures_route(
body: Ar<upload_signatures::v3::Request>,
) -> Result<Ra<upload_signatures::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
for (user_id, keys) in &body.signed_keys {
for (key_id, key) in keys {
let key = serde_json::to_value(key)
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid key JSON"))?;
let key = serde_json::to_value(key).map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Invalid key JSON")
})?;
for signature in key
.get("signatures")
@ -173,7 +191,6 @@ pub async fn upload_signatures_route(
"Invalid signature.",
))?
.clone()
.into_iter()
{
// Signature validation?
let signature = (
@ -187,26 +204,31 @@ pub async fn upload_signatures_route(
))?
.to_owned(),
);
services()
.users
.sign_key(user_id, key_id, signature, sender_user)?;
services().users.sign_key(
user_id,
key_id,
signature,
sender_user,
)?;
}
}
}
Ok(upload_signatures::v3::Response {
failures: BTreeMap::new(), // TODO: integrate
})
Ok(Ra(upload_signatures::v3::Response {
// TODO: integrate
failures: BTreeMap::new(),
}))
}
/// # `POST /_matrix/client/r0/keys/changes`
///
/// Gets a list of users who have updated their device identity keys since the previous sync token.
/// Gets a list of users who have updated their device identity keys since the
/// previous sync token.
///
/// - TODO: left users
pub async fn get_key_changes_route(
body: Ruma<get_key_changes::v3::Request>,
) -> Result<get_key_changes::v3::Response> {
pub(crate) async fn get_key_changes_route(
body: Ar<get_key_changes::v3::Request>,
) -> Result<Ra<get_key_changes::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut device_list_updates = HashSet::new();
@ -216,23 +238,24 @@ pub async fn get_key_changes_route(
.users
.keys_changed(
sender_user.as_str(),
body.from
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`."))?,
Some(
body.to
.parse()
.map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`."))?,
),
body.from.parse().map_err(|_| {
Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid `from`.",
)
})?,
Some(body.to.parse().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")
})?),
)
.filter_map(|r| r.ok()),
.filter_map(Result::ok),
);
for room_id in services()
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(|r| r.ok())
.filter_map(Result::ok)
{
device_list_updates.extend(
services()
@ -240,21 +263,29 @@ pub async fn get_key_changes_route(
.keys_changed(
room_id.as_ref(),
body.from.parse().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Invalid `from`.")
Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid `from`.",
)
})?,
Some(body.to.parse().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Invalid `to`.")
Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid `to`.",
)
})?),
)
.filter_map(|r| r.ok()),
.filter_map(Result::ok),
);
}
Ok(get_key_changes::v3::Response {
Ok(Ra(get_key_changes::v3::Response {
changed: device_list_updates.into_iter().collect(),
left: Vec::new(), // TODO
})
// TODO
left: Vec::new(),
}))
}
#[allow(clippy::too_many_lines)]
pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
sender_user: Option<&UserId>,
device_keys_input: &BTreeMap<OwnedUserId, Vec<OwnedDeviceId>>,
@ -282,16 +313,24 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
let mut container = BTreeMap::new();
for device_id in services().users.all_device_ids(user_id) {
let device_id = device_id?;
if let Some(mut keys) = services().users.get_device_keys(user_id, &device_id)? {
if let Some(mut keys) =
services().users.get_device_keys(user_id, &device_id)?
{
let metadata = services()
.users
.get_device_metadata(user_id, &device_id)?
.ok_or_else(|| {
Error::bad_database("all_device_keys contained nonexistent device.")
Error::bad_database(
"all_device_keys contained nonexistent device.",
)
})?;
add_unsigned_device_display_name(&mut keys, metadata)
.map_err(|_| Error::bad_database("invalid device keys in database"))?;
.map_err(|_| {
Error::bad_database(
"invalid device keys in database",
)
})?;
container.insert(device_id, keys);
}
}
@ -299,7 +338,9 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
} else {
for device_id in device_ids {
let mut container = BTreeMap::new();
if let Some(mut keys) = services().users.get_device_keys(user_id, device_id)? {
if let Some(mut keys) =
services().users.get_device_keys(user_id, device_id)?
{
let metadata = services()
.users
.get_device_metadata(user_id, device_id)?
@ -309,29 +350,35 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
))?;
add_unsigned_device_display_name(&mut keys, metadata)
.map_err(|_| Error::bad_database("invalid device keys in database"))?;
.map_err(|_| {
Error::bad_database(
"invalid device keys in database",
)
})?;
container.insert(device_id.to_owned(), keys);
}
device_keys.insert(user_id.to_owned(), container);
}
}
if let Some(master_key) =
services()
.users
.get_master_key(sender_user, user_id, &allowed_signatures)?
{
if let Some(master_key) = services().users.get_master_key(
sender_user,
user_id,
&allowed_signatures,
)? {
master_keys.insert(user_id.to_owned(), master_key);
}
if let Some(self_signing_key) =
services()
.users
.get_self_signing_key(sender_user, user_id, &allowed_signatures)?
{
if let Some(self_signing_key) = services().users.get_self_signing_key(
sender_user,
user_id,
&allowed_signatures,
)? {
self_signing_keys.insert(user_id.to_owned(), self_signing_key);
}
if Some(user_id) == sender_user {
if let Some(user_signing_key) = services().users.get_user_signing_key(user_id)? {
if let Some(user_signing_key) =
services().users.get_user_signing_key(user_id)?
{
user_signing_keys.insert(user_id.to_owned(), user_signing_key);
}
}
@ -339,123 +386,167 @@ pub(crate) async fn get_keys_helper<F: Fn(&UserId) -> bool>(
let mut failures = BTreeMap::new();
let back_off = |id| async {
match services()
.globals
.bad_query_ratelimiter
.write()
.await
.entry(id)
{
hash_map::Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
}
hash_map::Entry::Occupied(mut e) => *e.get_mut() = (Instant::now(), e.get().1 + 1),
}
};
let mut futures: FuturesUnordered<_> = get_over_federation
.into_iter()
.map(|(server, vec)| async move {
if let Some((time, tries)) = services()
.globals
.bad_query_ratelimiter
.read()
.await
.get(server)
{
// Exponential backoff
let mut min_elapsed_duration = Duration::from_secs(30) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
if time.elapsed() < min_elapsed_duration {
debug!("Backing off query from {:?}", server);
return (
server,
Err(Error::BadServerResponse("bad query, still backing off")),
);
}
}
let mut device_keys_input_fed = BTreeMap::new();
for (user_id, keys) in vec {
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
}
(
server,
tokio::time::timeout(
Duration::from_secs(25),
services().sending.send_federation_request(
server,
federation::keys::get_keys::v1::Request {
device_keys: device_keys_input_fed,
},
),
)
.await
.map_err(|_e| Error::BadServerResponse("Query took too long")),
)
.map(|(server, keys)| async move {
(server, request_keys_from(server, keys).await)
})
.collect();
while let Some((server, response)) = futures.next().await {
match response {
Ok(Ok(response)) => {
for (user, masterkey) in response.master_keys {
let (master_key_id, mut master_key) =
services().users.parse_master_key(&user, &masterkey)?;
let Ok(response) = response else {
failures.insert(server.to_string(), json!({}));
continue;
};
if let Some(our_master_key) = services().users.get_key(
&master_key_id,
sender_user,
&user,
&allowed_signatures,
)? {
let (_, our_master_key) =
services().users.parse_master_key(&user, &our_master_key)?;
master_key.signatures.extend(our_master_key.signatures);
for (user, masterkey) in response.master_keys {
let (master_key_id, mut master_key) =
services().users.parse_master_key(&user, &masterkey)?;
if let Some(our_master_key) = services().users.get_key(
&master_key_id,
sender_user,
&user,
&allowed_signatures,
)? {
let (_, our_master_key) = services()
.users
.parse_master_key(&user, &our_master_key)?;
for (entity, v) in &*our_master_key.signatures {
for (key_identifier, value) in v {
master_key.signatures.insert_signature(
entity.clone(),
key_identifier.clone(),
value.clone(),
);
}
let json = serde_json::to_value(master_key).expect("to_value always works");
let raw = serde_json::from_value(json).expect("Raw::from_value always works");
services().users.add_cross_signing_keys(
&user, &raw, &None, &None,
false, // Dont notify. A notification would trigger another key request resulting in an endless loop
)?;
master_keys.insert(user, raw);
}
self_signing_keys.extend(response.self_signing_keys);
device_keys.extend(response.device_keys);
}
_ => {
back_off(server.to_owned()).await;
failures.insert(server.to_string(), json!({}));
}
let json = serde_json::to_value(master_key)
.expect("to_value always works");
let raw = serde_json::from_value(json)
.expect("Raw::from_value always works");
services().users.add_cross_signing_keys(
&user, &raw, None, None,
// Dont notify. A notification would trigger another key
// request resulting in an endless loop
false,
)?;
master_keys.insert(user, raw);
}
self_signing_keys.extend(response.self_signing_keys);
device_keys.extend(response.device_keys);
}
Ok(get_keys::v3::Response {
failures,
device_keys,
master_keys,
self_signing_keys,
user_signing_keys,
device_keys,
failures,
})
}
/// Returns `Err` if key requests to the server are being backed off due to
/// previous errors.
async fn check_key_requests_back_off(server: &ServerName) -> Result<()> {
if let Some((time, tries)) =
services().globals.bad_query_ratelimiter.read().await.get(server)
{
// Exponential backoff
let mut min_elapsed_duration =
Duration::from_secs(30) * (*tries) * (*tries);
if min_elapsed_duration > Duration::from_secs(60 * 60 * 24) {
min_elapsed_duration = Duration::from_secs(60 * 60 * 24);
}
if let Some(remaining) =
min_elapsed_duration.checked_sub(time.elapsed())
{
debug!(%server, %tries, ?remaining, "Backing off from server");
return Err(Error::BadServerResponse(
"bad query, still backing off",
));
}
}
Ok(())
}
/// Backs off future remote device key requests to a server after a failure.
async fn back_off_key_requests(server: OwnedServerName) {
match services().globals.bad_query_ratelimiter.write().await.entry(server) {
hash_map::Entry::Vacant(e) => {
e.insert((Instant::now(), 1));
}
hash_map::Entry::Occupied(mut e) => {
*e.get_mut() = (Instant::now(), e.get().1 + 1);
}
}
}
/// Stops backing off remote device key requests to a server after a success.
async fn reset_key_request_back_off(server: &ServerName) {
services().globals.bad_query_ratelimiter.write().await.remove(server);
}
/// Requests device keys from a remote server, unless the server is in backoff.
///
/// Updates backoff state depending on the result of the request.
async fn request_keys_from(
server: &ServerName,
keys: Vec<(&UserId, &Vec<OwnedDeviceId>)>,
) -> Result<federation::keys::get_keys::v1::Response> {
check_key_requests_back_off(server).await?;
let result = request_keys_from_inner(server, keys).await;
match &result {
Ok(_) => reset_key_request_back_off(server).await,
Err(error) => {
debug!(%server, %error, "remote device key query failed");
back_off_key_requests(server.to_owned()).await;
}
}
result
}
async fn request_keys_from_inner(
server: &ServerName,
keys: Vec<(&UserId, &Vec<OwnedDeviceId>)>,
) -> Result<federation::keys::get_keys::v1::Response> {
let mut device_keys_input_fed = BTreeMap::new();
for (user_id, keys) in keys {
device_keys_input_fed.insert(user_id.to_owned(), keys.clone());
}
// TODO: switch .and_then(|result| result) to .flatten() when stable
// <https://github.com/rust-lang/rust/issues/70142>
tokio::time::timeout(
Duration::from_secs(25),
services().sending.send_federation_request(
server,
federation::keys::get_keys::v1::Request {
device_keys: device_keys_input_fed,
},
),
)
.await
.map_err(|_e| Error::BadServerResponse("Query took too long"))
.and_then(|result| result)
}
fn add_unsigned_device_display_name(
keys: &mut Raw<ruma::encryption::DeviceKeys>,
metadata: ruma::api::client::device::Device,
) -> serde_json::Result<()> {
if let Some(display_name) = metadata.display_name {
let mut object = keys.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
let mut object = keys
.deserialize_as::<serde_json::Map<String, serde_json::Value>>()?;
let unsigned = object.entry("unsigned").or_insert_with(|| json!({}));
if let serde_json::Value::Object(unsigned_object) = unsigned {
unsigned_object.insert("device_display_name".to_owned(), display_name.into());
unsigned_object
.insert("device_display_name".to_owned(), display_name.into());
}
*keys = Raw::from_json(serde_json::value::to_raw_value(&object)?);
@ -465,7 +556,10 @@ fn add_unsigned_device_display_name(
}
pub(crate) async fn claim_keys_helper(
one_time_keys_input: &BTreeMap<OwnedUserId, BTreeMap<OwnedDeviceId, DeviceKeyAlgorithm>>,
one_time_keys_input: &BTreeMap<
OwnedUserId,
BTreeMap<OwnedDeviceId, OneTimeKeyAlgorithm>,
>,
) -> Result<claim_keys::v3::Response> {
let mut one_time_keys = BTreeMap::new();
@ -481,11 +575,11 @@ pub(crate) async fn claim_keys_helper(
let mut container = BTreeMap::new();
for (device_id, key_algorithm) in map {
if let Some(one_time_keys) =
services()
.users
.take_one_time_key(user_id, device_id, key_algorithm)?
{
if let Some(one_time_keys) = services().users.take_one_time_key(
user_id,
device_id,
key_algorithm,
)? {
let mut c = BTreeMap::new();
c.insert(one_time_keys.0, one_time_keys.1);
container.insert(device_id.clone(), c);

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,77 +1,82 @@
use crate::{
service::{pdu::PduBuilder, rooms::timeline::PduCount},
services, utils, Error, Result, Ruma,
};
use std::collections::{BTreeMap, HashSet};
use ruma::{
api::client::{
error::ErrorKind,
message::{get_message_events, send_message_event},
},
events::{StateEventType, TimelineEventType},
uint,
};
use std::{
collections::{BTreeMap, HashSet},
sync::Arc,
use crate::{
service::{pdu::PduBuilder, rooms::timeline::PduCount},
services, utils, Ar, Error, Ra, Result,
};
/// # `PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}`
///
/// Send a message event into the room.
///
/// - Is a NOOP if the txn id was already used before and returns the same event id again
/// - Is a NOOP if the txn id was already used before and returns the same event
/// id again
/// - The only requirement for the content is that it has to be valid json
/// - Tries to send the event into the room, auth rules will determine if it is allowed
pub async fn send_message_event_route(
body: Ruma<send_message_event::v3::Request>,
) -> Result<send_message_event::v3::Response> {
/// - Tries to send the event into the room, auth rules will determine if it is
/// allowed
pub(crate) async fn send_message_event_route(
body: Ar<send_message_event::v3::Request>,
) -> Result<Ra<send_message_event::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_deref();
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(body.room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let room_token = services()
.globals
.roomid_mutex_state
.lock_key(body.room_id.clone())
.await;
// Forbid m.room.encrypted if encryption is disabled
if TimelineEventType::RoomEncrypted == body.event_type.to_string().into()
&& !services().globals.allow_encryption()
{
return Err(Error::BadRequest(
ErrorKind::Forbidden,
ErrorKind::forbidden(),
"Encryption has been disabled",
));
}
// Check if this is a new transaction id
if let Some(response) =
services()
.transaction_ids
.existing_txnid(sender_user, sender_device, &body.txn_id)?
{
if let Some(response) = services().transaction_ids.existing_txnid(
sender_user,
sender_device,
&body.txn_id,
)? {
// The client might have sent a txnid of the /sendToDevice endpoint
// This txnid has no response associated with it
if response.is_empty() {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Tried to use txn id already used for an incompatible endpoint.",
"Tried to use txn id already used for an incompatible \
endpoint.",
));
}
let event_id = utils::string_from_bytes(&response)
.map_err(|_| Error::bad_database("Invalid txnid bytes in database."))?
.map_err(|_| {
Error::bad_database("Invalid txnid bytes in database.")
})?
.try_into()
.map_err(|_| Error::bad_database("Invalid event id in txnid data."))?;
return Ok(send_message_event::v3::Response { event_id });
.map_err(|_| {
Error::bad_database("Invalid event id in txnid data.")
})?;
return Ok(Ra(send_message_event::v3::Response {
event_id,
}));
}
let mut unsigned = BTreeMap::new();
unsigned.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
unsigned
.insert("transaction_id".to_owned(), body.txn_id.to_string().into());
let event_id = services()
.rooms
@ -80,14 +85,18 @@ pub async fn send_message_event_route(
PduBuilder {
event_type: body.event_type.to_string().into(),
content: serde_json::from_str(body.body.body.json().get())
.map_err(|_| Error::BadRequest(ErrorKind::BadJson, "Invalid JSON body."))?,
.map_err(|_| {
Error::BadRequest(
ErrorKind::BadJson,
"Invalid JSON body.",
)
})?,
unsigned: Some(unsigned),
state_key: None,
redacts: None,
},
sender_user,
&body.room_id,
&state_lock,
&room_token,
)
.await?;
@ -98,45 +107,51 @@ pub async fn send_message_event_route(
event_id.as_bytes(),
)?;
drop(state_lock);
drop(room_token);
Ok(send_message_event::v3::Response::new(
(*event_id).to_owned(),
))
Ok(Ra(send_message_event::v3::Response::new((*event_id).to_owned())))
}
/// # `GET /_matrix/client/r0/rooms/{roomId}/messages`
///
/// Allows paginating through room history.
///
/// - Only works if the user is joined (TODO: always allow, but only show events where the user was
/// joined, depending on history_visibility)
pub async fn get_message_events_route(
body: Ruma<get_message_events::v3::Request>,
) -> Result<get_message_events::v3::Response> {
/// - Only works if the user is joined (TODO: always allow, but only show events
/// where the user was joined, depending on `history_visibility`)
#[allow(clippy::too_many_lines)]
pub(crate) async fn get_message_events_route(
body: Ar<get_message_events::v3::Request>,
) -> Result<Ra<get_message_events::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let sender_device = body.sender_device.as_ref().expect("user is authenticated");
let sender_device =
body.sender_device.as_ref().expect("user is authenticated");
let from = match body.from.clone() {
Some(from) => PduCount::try_from_string(&from)?,
None => match body.dir {
ruma::api::Direction::Forward => PduCount::min(),
ruma::api::Direction::Backward => PduCount::max(),
ruma::api::Direction::Forward => PduCount::MIN,
ruma::api::Direction::Backward => PduCount::MAX,
},
};
let to = body
.to
.as_ref()
.and_then(|t| PduCount::try_from_string(t).ok());
let to = body.to.as_ref().and_then(|t| PduCount::try_from_string(t).ok());
services()
.rooms
.lazy_loading
.lazy_load_confirm_delivery(sender_user, sender_device, &body.room_id, from)
.lazy_load_confirm_delivery(
sender_user,
sender_device,
&body.room_id,
from,
)
.await?;
let limit = u64::from(body.limit).min(100) as usize;
let limit = body
.limit
.min(uint!(100))
.try_into()
.expect("0-100 should fit in usize");
let next_token;
@ -151,21 +166,27 @@ pub async fn get_message_events_route(
.timeline
.pdus_after(sender_user, &body.room_id, from)?
.take(limit)
.filter_map(|r| r.ok()) // Filter out buggy events
.filter_map(Result::ok)
.filter(|(_, pdu)| {
services()
.rooms
.state_accessor
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
.user_can_see_event(
sender_user,
&body.room_id,
&pdu.event_id,
)
.unwrap_or(false)
})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.take_while(|&(k, _)| Some(k) != to)
.collect();
for (_, event) in &events_after {
/* TODO: Remove this when these are resolved:
* https://github.com/vector-im/element-android/issues/3417
* https://github.com/vector-im/element-web/issues/21034
// * https://github.com/vector-im/element-android/issues/3417
// * https://github.com/vector-im/element-web/issues/21034
//
// TODO: When the above issues are resolved, uncomment this:
/*
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
@ -175,6 +196,7 @@ pub async fn get_message_events_route(
lazy_loaded.insert(event.sender.clone());
}
*/
// And delete this line:
lazy_loaded.insert(event.sender.clone());
}
@ -200,21 +222,27 @@ pub async fn get_message_events_route(
.timeline
.pdus_until(sender_user, &body.room_id, from)?
.take(limit)
.filter_map(|r| r.ok()) // Filter out buggy events
.filter_map(Result::ok)
.filter(|(_, pdu)| {
services()
.rooms
.state_accessor
.user_can_see_event(sender_user, &body.room_id, &pdu.event_id)
.user_can_see_event(
sender_user,
&body.room_id,
&pdu.event_id,
)
.unwrap_or(false)
})
.take_while(|&(k, _)| Some(k) != to) // Stop at `to`
.take_while(|&(k, _)| Some(k) != to)
.collect();
for (_, event) in &events_before {
/* TODO: Remove this when these are resolved:
* https://github.com/vector-im/element-android/issues/3417
* https://github.com/vector-im/element-web/issues/21034
// * https://github.com/vector-im/element-android/issues/3417
// * https://github.com/vector-im/element-web/issues/21034
//
// TODO: When the above issues are resolved, uncomment this:
/*
if !services().rooms.lazy_loading.lazy_load_was_sent_before(
sender_user,
sender_device,
@ -224,6 +252,7 @@ pub async fn get_message_events_route(
lazy_loaded.insert(event.sender.clone());
}
*/
// And delete this line:
lazy_loaded.insert(event.sender.clone());
}
@ -242,16 +271,21 @@ pub async fn get_message_events_route(
resp.state = Vec::new();
for ll_id in &lazy_loaded {
if let Some(member_event) = services().rooms.state_accessor.room_state_get(
&body.room_id,
&StateEventType::RoomMember,
ll_id.as_str(),
)? {
if let Some(member_event) =
services().rooms.state_accessor.room_state_get(
&body.room_id,
&StateEventType::RoomMember,
ll_id.as_str(),
)?
{
resp.state.push(member_event.to_state_event());
}
}
// TODO: enable again when we are sure clients can handle it
// * https://github.com/vector-im/element-android/issues/3417
// * https://github.com/vector-im/element-web/issues/21034
//
// TODO: When the above issues are resolved, uncomment this:
/*
if let Some(next_token) = next_token {
services().rooms.lazy_loading.lazy_load_mark_sent(
@ -264,5 +298,5 @@ pub async fn get_message_events_route(
}
*/
Ok(resp)
Ok(Ra(resp))
}

View file

@ -1,74 +0,0 @@
mod account;
mod alias;
mod backup;
mod capabilities;
mod config;
mod context;
mod device;
mod directory;
mod filter;
mod keys;
mod media;
mod membership;
mod message;
mod presence;
mod profile;
mod push;
mod read_marker;
mod redact;
mod relations;
mod report;
mod room;
mod search;
mod session;
mod space;
mod state;
mod sync;
mod tag;
mod thirdparty;
mod threads;
mod to_device;
mod typing;
mod unversioned;
mod user_directory;
mod voip;
pub use account::*;
pub use alias::*;
pub use backup::*;
pub use capabilities::*;
pub use config::*;
pub use context::*;
pub use device::*;
pub use directory::*;
pub use filter::*;
pub use keys::*;
pub use media::*;
pub use membership::*;
pub use message::*;
pub use presence::*;
pub use profile::*;
pub use push::*;
pub use read_marker::*;
pub use redact::*;
pub use relations::*;
pub use report::*;
pub use room::*;
pub use search::*;
pub use session::*;
pub use space::*;
pub use state::*;
pub use sync::*;
pub use tag::*;
pub use thirdparty::*;
pub use threads::*;
pub use to_device::*;
pub use typing::*;
pub use unversioned::*;
pub use user_directory::*;
pub use voip::*;
pub const DEVICE_ID_LENGTH: usize = 10;
pub const TOKEN_LENGTH: usize = 32;
pub const SESSION_ID_LENGTH: usize = 32;
pub const AUTO_GEN_PASSWORD_LENGTH: usize = 15;

View file

@ -1,90 +0,0 @@
use crate::{services, utils, Error, Result, Ruma};
use ruma::api::client::{
error::ErrorKind,
presence::{get_presence, set_presence},
};
use std::time::Duration;
/// # `PUT /_matrix/client/r0/presence/{userId}/status`
///
/// Sets the presence state of the sender user.
pub async fn set_presence_route(
body: Ruma<set_presence::v3::Request>,
) -> Result<set_presence::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
for room_id in services().rooms.state_cache.rooms_joined(sender_user) {
let room_id = room_id?;
services().rooms.edus.presence.update_presence(
sender_user,
&room_id,
ruma::events::presence::PresenceEvent {
content: ruma::events::presence::PresenceEventContent {
avatar_url: services().users.avatar_url(sender_user)?,
currently_active: None,
displayname: services().users.displayname(sender_user)?,
last_active_ago: Some(
utils::millis_since_unix_epoch()
.try_into()
.expect("time is valid"),
),
presence: body.presence.clone(),
status_msg: body.status_msg.clone(),
},
sender: sender_user.clone(),
},
)?;
}
Ok(set_presence::v3::Response {})
}
/// # `GET /_matrix/client/r0/presence/{userId}/status`
///
/// Gets the presence state of the given user.
///
/// - Only works if you share a room with the user
pub async fn get_presence_route(
body: Ruma<get_presence::v3::Request>,
) -> Result<get_presence::v3::Response> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let mut presence_event = None;
for room_id in services()
.rooms
.user
.get_shared_rooms(vec![sender_user.clone(), body.user_id.clone()])?
{
let room_id = room_id?;
if let Some(presence) = services()
.rooms
.edus
.presence
.get_last_presence_event(sender_user, &room_id)?
{
presence_event = Some(presence);
break;
}
}
if let Some(presence) = presence_event {
Ok(get_presence::v3::Response {
// TODO: Should ruma just use the presenceeventcontent type here?
status_msg: presence.content.status_msg,
currently_active: presence.content.currently_active,
last_active_ago: presence
.content
.last_active_ago
.map(|millis| Duration::from_millis(millis.into())),
presence: presence.content.presence,
})
} else {
Err(Error::BadRequest(
ErrorKind::NotFound,
"Presence state for this user was not found",
))
}
}

View file

@ -1,39 +1,41 @@
use crate::{service::pdu::PduBuilder, services, utils, Error, Result, Ruma};
use ruma::{
api::{
client::{
error::ErrorKind,
profile::{
get_avatar_url, get_display_name, get_profile, set_avatar_url, set_display_name,
get_avatar_url, get_display_name, get_profile, set_avatar_url,
set_display_name,
},
},
federation::{self, query::get_profile_information::v1::ProfileField},
},
events::{room::member::RoomMemberEventContent, StateEventType, TimelineEventType},
events::{
room::member::RoomMemberEventContent, StateEventType, TimelineEventType,
},
};
use serde_json::value::to_raw_value;
use std::sync::Arc;
use tracing::warn;
use crate::{service::pdu::PduBuilder, services, Ar, Error, Ra, Result};
/// # `PUT /_matrix/client/r0/profile/{userId}/displayname`
///
/// Updates the displayname.
///
/// - Also makes sure other users receive the update using presence EDUs
pub async fn set_displayname_route(
body: Ruma<set_display_name::v3::Request>,
) -> Result<set_display_name::v3::Response> {
pub(crate) async fn set_displayname_route(
body: Ar<set_display_name::v3::Request>,
) -> Result<Ra<set_display_name::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.users
.set_displayname(sender_user, body.displayname.clone())?;
services().users.set_displayname(sender_user, body.displayname.clone())?;
// Send a new membership event and presence update into all joined rooms
let all_rooms_joined: Vec<_> = services()
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(|r| r.ok())
.filter_map(Result::ok)
.map(|room_id| {
Ok::<_, Error>((
PduBuilder {
@ -52,14 +54,18 @@ pub async fn set_displayname_route(
)?
.ok_or_else(|| {
Error::bad_database(
"Tried to send displayname update for user not in the \
room.",
"Tried to send displayname update for \
user not in the room.",
)
})?
.content
.get(),
)
.map_err(|_| Error::bad_database("Database contains invalid PDU."))?
.map_err(|_| {
Error::bad_database(
"Database contains invalid PDU.",
)
})?
})
.expect("event is valid, we just created it"),
unsigned: None,
@ -69,50 +75,27 @@ pub async fn set_displayname_route(
room_id,
))
})
.filter_map(|r| r.ok())
.filter_map(Result::ok)
.collect();
for (pdu_builder, room_id) in all_rooms_joined {
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let _ = services()
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
let room_token = services()
.globals
.roomid_mutex_state
.lock_key(room_id.clone())
.await;
// Presence update
services().rooms.edus.presence.update_presence(
sender_user,
&room_id,
ruma::events::presence::PresenceEvent {
content: ruma::events::presence::PresenceEventContent {
avatar_url: services().users.avatar_url(sender_user)?,
currently_active: None,
displayname: services().users.displayname(sender_user)?,
last_active_ago: Some(
utils::millis_since_unix_epoch()
.try_into()
.expect("time is valid"),
),
presence: ruma::presence::PresenceState::Online,
status_msg: None,
},
sender: sender_user.clone(),
},
)?;
if let Err(error) = services()
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_token)
.await
{
warn!(%error, "failed to add PDU");
}
}
Ok(set_display_name::v3::Response {})
Ok(Ra(set_display_name::v3::Response {}))
}
/// # `GET /_matrix/client/r0/profile/{userId}/displayname`
@ -120,9 +103,9 @@ pub async fn set_displayname_route(
/// Returns the displayname of the user.
///
/// - If user is on another server: Fetches displayname over federation
pub async fn get_displayname_route(
body: Ruma<get_display_name::v3::Request>,
) -> Result<get_display_name::v3::Response> {
pub(crate) async fn get_displayname_route(
body: Ar<get_display_name::v3::Request>,
) -> Result<Ra<get_display_name::v3::Response>> {
if body.user_id.server_name() != services().globals.server_name() {
let response = services()
.sending
@ -135,40 +118,36 @@ pub async fn get_displayname_route(
)
.await?;
return Ok(get_display_name::v3::Response {
return Ok(Ra(get_display_name::v3::Response {
displayname: response.displayname,
});
}));
}
Ok(get_display_name::v3::Response {
Ok(Ra(get_display_name::v3::Response {
displayname: services().users.displayname(&body.user_id)?,
})
}))
}
/// # `PUT /_matrix/client/r0/profile/{userId}/avatar_url`
///
/// Updates the avatar_url and blurhash.
/// Updates the `avatar_url` and `blurhash`.
///
/// - Also makes sure other users receive the update using presence EDUs
pub async fn set_avatar_url_route(
body: Ruma<set_avatar_url::v3::Request>,
) -> Result<set_avatar_url::v3::Response> {
pub(crate) async fn set_avatar_url_route(
body: Ar<set_avatar_url::v3::Request>,
) -> Result<Ra<set_avatar_url::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.users
.set_avatar_url(sender_user, body.avatar_url.clone())?;
services().users.set_avatar_url(sender_user, body.avatar_url.clone())?;
services()
.users
.set_blurhash(sender_user, body.blurhash.clone())?;
services().users.set_blurhash(sender_user, body.blurhash.clone())?;
// Send a new membership event and presence update into all joined rooms
let all_joined_rooms: Vec<_> = services()
.rooms
.state_cache
.rooms_joined(sender_user)
.filter_map(|r| r.ok())
.filter_map(Result::ok)
.map(|room_id| {
Ok::<_, Error>((
PduBuilder {
@ -187,14 +166,18 @@ pub async fn set_avatar_url_route(
)?
.ok_or_else(|| {
Error::bad_database(
"Tried to send displayname update for user not in the \
room.",
"Tried to send displayname update for \
user not in the room.",
)
})?
.content
.get(),
)
.map_err(|_| Error::bad_database("Database contains invalid PDU."))?
.map_err(|_| {
Error::bad_database(
"Database contains invalid PDU.",
)
})?
})
.expect("event is valid, we just created it"),
unsigned: None,
@ -204,60 +187,38 @@ pub async fn set_avatar_url_route(
room_id,
))
})
.filter_map(|r| r.ok())
.filter_map(Result::ok)
.collect();
for (pdu_builder, room_id) in all_joined_rooms {
let mutex_state = Arc::clone(
services()
.globals
.roomid_mutex_state
.write()
.await
.entry(room_id.clone())
.or_default(),
);
let state_lock = mutex_state.lock().await;
let _ = services()
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_id, &state_lock)
let room_token = services()
.globals
.roomid_mutex_state
.lock_key(room_id.clone())
.await;
// Presence update
services().rooms.edus.presence.update_presence(
sender_user,
&room_id,
ruma::events::presence::PresenceEvent {
content: ruma::events::presence::PresenceEventContent {
avatar_url: services().users.avatar_url(sender_user)?,
currently_active: None,
displayname: services().users.displayname(sender_user)?,
last_active_ago: Some(
utils::millis_since_unix_epoch()
.try_into()
.expect("time is valid"),
),
presence: ruma::presence::PresenceState::Online,
status_msg: None,
},
sender: sender_user.clone(),
},
)?;
if let Err(error) = services()
.rooms
.timeline
.build_and_append_pdu(pdu_builder, sender_user, &room_token)
.await
{
warn!(%error, "failed to add PDU");
};
}
Ok(set_avatar_url::v3::Response {})
Ok(Ra(set_avatar_url::v3::Response {}))
}
/// # `GET /_matrix/client/r0/profile/{userId}/avatar_url`
///
/// Returns the avatar_url and blurhash of the user.
/// Returns the `avatar_url` and `blurhash` of the user.
///
/// - If user is on another server: Fetches avatar_url and blurhash over federation
pub async fn get_avatar_url_route(
body: Ruma<get_avatar_url::v3::Request>,
) -> Result<get_avatar_url::v3::Response> {
/// - If user is on another server: Fetches `avatar_url` and `blurhash` over
/// federation
pub(crate) async fn get_avatar_url_route(
body: Ar<get_avatar_url::v3::Request>,
) -> Result<Ra<get_avatar_url::v3::Response>> {
if body.user_id.server_name() != services().globals.server_name() {
let response = services()
.sending
@ -270,26 +231,26 @@ pub async fn get_avatar_url_route(
)
.await?;
return Ok(get_avatar_url::v3::Response {
return Ok(Ra(get_avatar_url::v3::Response {
avatar_url: response.avatar_url,
blurhash: response.blurhash,
});
}));
}
Ok(get_avatar_url::v3::Response {
Ok(Ra(get_avatar_url::v3::Response {
avatar_url: services().users.avatar_url(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id)?,
})
}))
}
/// # `GET /_matrix/client/r0/profile/{userId}`
///
/// Returns the displayname, avatar_url and blurhash of the user.
/// Returns the `displayname`, `avatar_url` and `blurhash` of the user.
///
/// - If user is on another server: Fetches profile over federation
pub async fn get_profile_route(
body: Ruma<get_profile::v3::Request>,
) -> Result<get_profile::v3::Response> {
pub(crate) async fn get_profile_route(
body: Ar<get_profile::v3::Request>,
) -> Result<Ra<get_profile::v3::Response>> {
if body.user_id.server_name() != services().globals.server_name() {
let response = services()
.sending
@ -302,11 +263,11 @@ pub async fn get_profile_route(
)
.await?;
return Ok(get_profile::v3::Response {
return Ok(Ra(get_profile::v3::Response {
displayname: response.displayname,
avatar_url: response.avatar_url,
blurhash: response.blurhash,
});
}));
}
if !services().users.exists(&body.user_id)? {
@ -317,9 +278,9 @@ pub async fn get_profile_route(
));
}
Ok(get_profile::v3::Response {
Ok(Ra(get_profile::v3::Response {
avatar_url: services().users.avatar_url(&body.user_id)?,
blurhash: services().users.blurhash(&body.user_id)?,
displayname: services().users.displayname(&body.user_id)?,
})
}))
}

View file

@ -1,69 +1,63 @@
use crate::{services, Error, Result, Ruma};
use ruma::{
api::client::{
error::ErrorKind,
push::{
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions, get_pushrule_enabled,
get_pushrules_all, set_pusher, set_pushrule, set_pushrule_actions,
set_pushrule_enabled, RuleScope,
delete_pushrule, get_pushers, get_pushrule, get_pushrule_actions,
get_pushrule_enabled, get_pushrules_all, set_pusher, set_pushrule,
set_pushrule_actions, set_pushrule_enabled,
},
},
events::{push_rules::PushRulesEvent, GlobalAccountDataEventType},
push::{InsertPushRuleError, RemovePushRuleError},
events::push_rules::PushRulesEventContent,
push::{AnyPushRuleRef, InsertPushRuleError, RemovePushRuleError},
serde::Raw,
};
use crate::{services, Ar, Error, Ra, Result};
/// # `GET /_matrix/client/r0/pushrules`
///
/// Retrieves the push rules event for this user.
pub async fn get_pushrules_all_route(
body: Ruma<get_pushrules_all::v3::Request>,
) -> Result<get_pushrules_all::v3::Response> {
pub(crate) async fn get_pushrules_all_route(
body: Ar<get_pushrules_all::v3::Request>,
) -> Result<Ra<get_pushrules_all::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
let account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
Ok(get_pushrules_all::v3::Response {
Ok(Ra(get_pushrules_all::v3::Response {
global: account_data.global,
})
}))
}
/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
///
/// Retrieves a single specified push rule for this user.
pub async fn get_pushrule_route(
body: Ruma<get_pushrule::v3::Request>,
) -> Result<get_pushrule::v3::Response> {
pub(crate) async fn get_pushrule_route(
body: Ar<get_pushrule::v3::Request>,
) -> Result<Ra<get_pushrule::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
let account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
let rule = account_data
.global
@ -71,47 +65,36 @@ pub async fn get_pushrule_route(
.map(Into::into);
if let Some(rule) = rule {
Ok(get_pushrule::v3::Response { rule })
Ok(Ra(get_pushrule::v3::Response {
rule,
}))
} else {
Err(Error::BadRequest(
ErrorKind::NotFound,
"Push rule not found.",
))
Err(Error::BadRequest(ErrorKind::NotFound, "Push rule not found."))
}
}
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
///
/// Creates a single specified push rule for this user.
pub async fn set_pushrule_route(
body: Ruma<set_pushrule::v3::Request>,
) -> Result<set_pushrule::v3::Response> {
pub(crate) async fn set_pushrule_route(
body: Ar<set_pushrule::v3::Request>,
) -> Result<Ra<set_pushrule::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
let body = body.body;
if body.scope != RuleScope::Global {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Scopes other than 'global' are not supported.",
));
}
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
let mut account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
if let Err(error) = account_data.content.global.insert(
if let Err(error) = account_data.global.insert(
body.rule.clone(),
body.after.as_deref(),
body.before.as_deref(),
@ -119,16 +102,20 @@ pub async fn set_pushrule_route(
let err = match error {
InsertPushRuleError::ServerDefaultRuleId => Error::BadRequest(
ErrorKind::InvalidParam,
"Rule IDs starting with a dot are reserved for server-default rules.",
"Rule IDs starting with a dot are reserved for server-default \
rules.",
),
InsertPushRuleError::InvalidRuleId => Error::BadRequest(
ErrorKind::InvalidParam,
"Rule ID containing invalid characters.",
),
InsertPushRuleError::RelativeToServerDefaultRule => Error::BadRequest(
ErrorKind::InvalidParam,
"Can't place a push rule relatively to a server-default rule.",
),
InsertPushRuleError::RelativeToServerDefaultRule => {
Error::BadRequest(
ErrorKind::InvalidParam,
"Can't place a push rule relatively to a server-default \
rule.",
)
}
InsertPushRuleError::UnknownRuleId => Error::BadRequest(
ErrorKind::NotFound,
"The before or after rule could not be found.",
@ -143,46 +130,34 @@ pub async fn set_pushrule_route(
return Err(err);
}
services().account_data.update(
None,
services().account_data.update_global(
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
&Raw::new(&account_data)
.expect("json event serialization should always succeed"),
)?;
Ok(set_pushrule::v3::Response {})
Ok(Ra(set_pushrule::v3::Response {}))
}
/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
///
/// Gets the actions of a single specified push rule for this user.
pub async fn get_pushrule_actions_route(
body: Ruma<get_pushrule_actions::v3::Request>,
) -> Result<get_pushrule_actions::v3::Response> {
pub(crate) async fn get_pushrule_actions_route(
body: Ar<get_pushrule_actions::v3::Request>,
) -> Result<Ra<get_pushrule_actions::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if body.scope != RuleScope::Global {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Scopes other than 'global' are not supported.",
));
}
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?
.content;
let account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
let global = account_data.global;
let actions = global
@ -193,41 +168,32 @@ pub async fn get_pushrule_actions_route(
"Push rule not found.",
))?;
Ok(get_pushrule_actions::v3::Response { actions })
Ok(Ra(get_pushrule_actions::v3::Response {
actions,
}))
}
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/actions`
///
/// Sets the actions of a single specified push rule for this user.
pub async fn set_pushrule_actions_route(
body: Ruma<set_pushrule_actions::v3::Request>,
) -> Result<set_pushrule_actions::v3::Response> {
pub(crate) async fn set_pushrule_actions_route(
body: Ar<set_pushrule_actions::v3::Request>,
) -> Result<Ra<set_pushrule_actions::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if body.scope != RuleScope::Global {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Scopes other than 'global' are not supported.",
));
}
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
let mut account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
if account_data
.content
.global
.set_actions(body.kind.clone(), &body.rule_id, body.actions.clone())
.is_err()
@ -238,90 +204,70 @@ pub async fn set_pushrule_actions_route(
));
}
services().account_data.update(
None,
services().account_data.update_global(
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
&Raw::new(&account_data)
.expect("json event serialization should always suceed"),
)?;
Ok(set_pushrule_actions::v3::Response {})
Ok(Ra(set_pushrule_actions::v3::Response {}))
}
/// # `GET /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled`
///
/// Gets the enabled status of a single specified push rule for this user.
pub async fn get_pushrule_enabled_route(
body: Ruma<get_pushrule_enabled::v3::Request>,
) -> Result<get_pushrule_enabled::v3::Response> {
pub(crate) async fn get_pushrule_enabled_route(
body: Ar<get_pushrule_enabled::v3::Request>,
) -> Result<Ra<get_pushrule_enabled::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if body.scope != RuleScope::Global {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Scopes other than 'global' are not supported.",
));
}
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
let account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
let global = account_data.content.global;
let global = account_data.global;
let enabled = global
.get(body.kind.clone(), &body.rule_id)
.map(|r| r.enabled())
.map(AnyPushRuleRef::enabled)
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"Push rule not found.",
))?;
Ok(get_pushrule_enabled::v3::Response { enabled })
Ok(Ra(get_pushrule_enabled::v3::Response {
enabled,
}))
}
/// # `PUT /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}/enabled`
///
/// Sets the enabled status of a single specified push rule for this user.
pub async fn set_pushrule_enabled_route(
body: Ruma<set_pushrule_enabled::v3::Request>,
) -> Result<set_pushrule_enabled::v3::Response> {
pub(crate) async fn set_pushrule_enabled_route(
body: Ar<set_pushrule_enabled::v3::Request>,
) -> Result<Ra<set_pushrule_enabled::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if body.scope != RuleScope::Global {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Scopes other than 'global' are not supported.",
));
}
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
let mut account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
if account_data
.content
.global
.set_enabled(body.kind.clone(), &body.rule_id, body.enabled)
.is_err()
@ -332,50 +278,37 @@ pub async fn set_pushrule_enabled_route(
));
}
services().account_data.update(
None,
services().account_data.update_global(
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
&Raw::new(&account_data)
.expect("json event serialization should always succeed"),
)?;
Ok(set_pushrule_enabled::v3::Response {})
Ok(Ra(set_pushrule_enabled::v3::Response {}))
}
/// # `DELETE /_matrix/client/r0/pushrules/{scope}/{kind}/{ruleId}`
///
/// Deletes a single specified push rule for this user.
pub async fn delete_pushrule_route(
body: Ruma<delete_pushrule::v3::Request>,
) -> Result<delete_pushrule::v3::Response> {
pub(crate) async fn delete_pushrule_route(
body: Ar<delete_pushrule::v3::Request>,
) -> Result<Ra<delete_pushrule::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if body.scope != RuleScope::Global {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Scopes other than 'global' are not supported.",
));
}
let event = services()
.account_data
.get(
None,
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
)?
.get_global::<PushRulesEventContent>(sender_user)?
.ok_or(Error::BadRequest(
ErrorKind::NotFound,
"PushRules event not found.",
))?;
let mut account_data = serde_json::from_str::<PushRulesEvent>(event.get())
.map_err(|_| Error::bad_database("Invalid account data event in db."))?;
let mut account_data = event.deserialize().map_err(|_| {
Error::bad_database("Invalid account data event in db.")
})?;
if let Err(error) = account_data
.content
.global
.remove(body.kind.clone(), &body.rule_id)
if let Err(error) =
account_data.global.remove(body.kind.clone(), &body.rule_id)
{
let err = match error {
RemovePushRuleError::ServerDefault => Error::BadRequest(
@ -391,27 +324,26 @@ pub async fn delete_pushrule_route(
return Err(err);
}
services().account_data.update(
None,
services().account_data.update_global(
sender_user,
GlobalAccountDataEventType::PushRules.to_string().into(),
&serde_json::to_value(account_data).expect("to json value always works"),
&Raw::new(&account_data)
.expect("json event serialization should always suceed"),
)?;
Ok(delete_pushrule::v3::Response {})
Ok(Ra(delete_pushrule::v3::Response {}))
}
/// # `GET /_matrix/client/r0/pushers`
///
/// Gets all currently active pushers for the sender user.
pub async fn get_pushers_route(
body: Ruma<get_pushers::v3::Request>,
) -> Result<get_pushers::v3::Response> {
pub(crate) async fn get_pushers_route(
body: Ar<get_pushers::v3::Request>,
) -> Result<Ra<get_pushers::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
Ok(get_pushers::v3::Response {
Ok(Ra(get_pushers::v3::Response {
pushers: services().pusher.get_pushers(sender_user)?,
})
}))
}
/// # `POST /_matrix/client/r0/pushers/set`
@ -419,14 +351,12 @@ pub async fn get_pushers_route(
/// Adds a pusher for the sender user.
///
/// - TODO: Handle `append`
pub async fn set_pushers_route(
body: Ruma<set_pusher::v3::Request>,
) -> Result<set_pusher::v3::Response> {
pub(crate) async fn set_pushers_route(
body: Ar<set_pusher::v3::Request>,
) -> Result<Ra<set_pusher::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
services()
.pusher
.set_pusher(sender_user, body.action.clone())?;
services().pusher.set_pusher(sender_user, body.action.clone())?;
Ok(set_pusher::v3::Response::default())
Ok(Ra(set_pusher::v3::Response::default()))
}

View file

@ -1,36 +1,40 @@
use crate::{service::rooms::timeline::PduCount, services, Error, Result, Ruma};
use std::collections::BTreeMap;
use ruma::{
api::client::{error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt},
events::{
receipt::{ReceiptThread, ReceiptType},
RoomAccountDataEventType,
api::client::{
error::ErrorKind, read_marker::set_read_marker, receipt::create_receipt,
},
events::receipt::{ReceiptThread, ReceiptType},
serde::Raw,
MilliSecondsSinceUnixEpoch,
};
use std::collections::BTreeMap;
use crate::{
service::rooms::timeline::PduCount, services, Ar, Error, Ra, Result,
};
/// # `POST /_matrix/client/r0/rooms/{roomId}/read_markers`
///
/// Sets different types of read markers.
///
/// - Updates fully-read account data event to `fully_read`
/// - If `read_receipt` is set: Update private marker and public read receipt EDU
pub async fn set_read_marker_route(
body: Ruma<set_read_marker::v3::Request>,
) -> Result<set_read_marker::v3::Response> {
/// - If `read_receipt` is set: Update private marker and public read receipt
/// EDU
pub(crate) async fn set_read_marker_route(
body: Ar<set_read_marker::v3::Request>,
) -> Result<Ra<set_read_marker::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if let Some(fully_read) = &body.fully_read {
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
content: ruma::events::fully_read::FullyReadEventContent {
let fully_read_event =
ruma::events::fully_read::FullyReadEventContent {
event_id: fully_read.clone(),
},
};
services().account_data.update(
Some(&body.room_id),
};
services().account_data.update_room(
&body.room_id,
sender_user,
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"),
&Raw::new(&fully_read_event)
.expect("json event serialization should always suceed"),
)?;
}
@ -42,14 +46,9 @@ pub async fn set_read_marker_route(
}
if let Some(event) = &body.private_read_receipt {
let count = services()
.rooms
.timeline
.get_pdu_count(event)?
.ok_or(Error::BadRequest(
ErrorKind::InvalidParam,
"Event does not exist.",
))?;
let count = services().rooms.timeline.get_pdu_count(event)?.ok_or(
Error::BadRequest(ErrorKind::InvalidParam, "Event does not exist."),
)?;
let count = match count {
PduCount::Backfilled(_) => {
return Err(Error::BadRequest(
@ -59,11 +58,11 @@ pub async fn set_read_marker_route(
}
PduCount::Normal(c) => c,
};
services()
.rooms
.edus
.read_receipt
.private_read_set(&body.room_id, sender_user, count)?;
services().rooms.edus.read_receipt.private_read_set(
&body.room_id,
sender_user,
count,
)?;
}
if let Some(event) = &body.read_receipt {
@ -86,26 +85,32 @@ pub async fn set_read_marker_route(
sender_user,
&body.room_id,
ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
content: ruma::events::receipt::ReceiptEventContent(
receipt_content,
),
room_id: body.room_id.clone(),
},
)?;
for server in services().rooms.state_cache.room_servers(&body.room_id) {
services().sending.trigger_edu_send(&server?)?;
}
}
Ok(set_read_marker::v3::Response {})
Ok(Ra(set_read_marker::v3::Response {}))
}
/// # `POST /_matrix/client/r0/rooms/{roomId}/receipt/{receiptType}/{eventId}`
///
/// Sets private read marker and public read receipt EDU.
pub async fn create_receipt_route(
body: Ruma<create_receipt::v3::Request>,
) -> Result<create_receipt::v3::Response> {
pub(crate) async fn create_receipt_route(
body: Ar<create_receipt::v3::Request>,
) -> Result<Ra<create_receipt::v3::Response>> {
let sender_user = body.sender_user.as_ref().expect("user is authenticated");
if matches!(
&body.receipt_type,
create_receipt::v3::ReceiptType::Read | create_receipt::v3::ReceiptType::ReadPrivate
create_receipt::v3::ReceiptType::Read
| create_receipt::v3::ReceiptType::ReadPrivate
) {
services()
.rooms
@ -115,16 +120,15 @@ pub async fn create_receipt_route(
match body.receipt_type {
create_receipt::v3::ReceiptType::FullyRead => {
let fully_read_event = ruma::events::fully_read::FullyReadEvent {
content: ruma::events::fully_read::FullyReadEventContent {
let fully_read_event =
ruma::events::fully_read::FullyReadEventContent {
event_id: body.event_id.clone(),
},
};
services().account_data.update(
Some(&body.room_id),
};
services().account_data.update_room(
&body.room_id,
sender_user,
RoomAccountDataEventType::FullyRead,
&serde_json::to_value(fully_read_event).expect("to json value always works"),
&Raw::new(&fully_read_event)
.expect("json event serialization should always succeed"),
)?;
}
create_receipt::v3::ReceiptType::Read => {
@ -140,16 +144,23 @@ pub async fn create_receipt_route(
receipts.insert(ReceiptType::Read, user_receipts);
let mut receipt_content = BTreeMap::new();
receipt_content.insert(body.event_id.to_owned(), receipts);
receipt_content.insert(body.event_id.clone(), receipts);
services().rooms.edus.read_receipt.readreceipt_update(
sender_user,
&body.room_id,
ruma::events::receipt::ReceiptEvent {
content: ruma::events::receipt::ReceiptEventContent(receipt_content),
content: ruma::events::receipt::ReceiptEventContent(
receipt_content,
),
room_id: body.room_id.clone(),
},
)?;
for server in
services().rooms.state_cache.room_servers(&body.room_id)
{
services().sending.trigger_edu_send(&server?)?;
}
}
create_receipt::v3::ReceiptType::ReadPrivate => {
let count = services()
@ -178,5 +189,5 @@ pub async fn create_receipt_route(
_ => return Err(Error::bad_database("Unsupported receipt type")),
}
Ok(create_receipt::v3::Response {})
Ok(Ra(create_receipt::v3::Response {}))
}

Some files were not shown because too many files have changed in this diff Show more