Compare commits

..

10 Commits

Author SHA1 Message Date
Renovate Bot 1c6aca1bbc chore(deps): update rust crate termimad to v0.35.1 2026-07-13 13:47:38 +00:00
Koen Oostveen 446ae93ad7 fix(web): Change incorrect deeplink for deleting a device 2026-07-13 13:45:46 +00:00
Renovate Bot 9ca0774b2d chore(deps): lock file maintenance 2026-07-13 13:44:47 +00:00
Ginger ce494eb0c0 fix: Properly sync newly created rooms 2026-07-13 08:50:53 -04:00
Ginger 1f5e178c3f fix: Properly handle appservice device creation 2026-07-13 08:50:48 -04:00
Renovate Bot ddd050d402 chore(deps): update ghcr.io/renovatebot/renovate docker tag to v43.260.2 2026-07-13 05:03:24 +00:00
Ginger 6ad3e679bc chore: Release 2026-07-12 17:34:16 -04:00
Ginger 65812bb246 chore: Update changelog 2026-07-12 17:33:52 -04:00
theS1LV3R 9f3a7994c7 chore: Changelog 2026-07-12 20:22:19 +02:00
theS1LV3R baef3289fe fix(service/admin): Prevent console from being spawned when no TTY is available 2026-07-12 20:22:18 +02:00
33 changed files with 388 additions and 884 deletions
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
name: Renovate
runs-on: ubuntu-latest
container:
image: ghcr.io/renovatebot/renovate:43.252.1@sha256:121eb04ef758537019fb58f587aa53c99e5fda4e703993ce2ca01bd4b3926bd4
image: ghcr.io/renovatebot/renovate:43.260.2@sha256:60d830108d9ff6408d11a55d5f110ee0976bed78a6b82b08211e3ddb72f72952
options: --tmpfs /tmp:exec
steps:
- name: Checkout
+9
View File
@@ -1,3 +1,12 @@
# Continuwuity 26.6.2 (2026-07-12)
## Bugfixes
- Fixed the server returning 500 errors if `admin_console_automatic` is enabled and no TTY is available. Contributed by @s1lv3r. (#1975)
- Fixed `global.oauth.compatibility_mode` being required, despite being ignored, when the `[global.oauth.oidc]` config section is provided.
- Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue, use `!admin users reset-password --convert-to-local-account` to reactivate them.
# Continuwuity 26.6.1 (2026-07-12)
## Features
Generated
+137 -234
View File
@@ -259,9 +259,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.17.0"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
checksum = "4342d8937fc7e5dd9b1c60292261c0670c882a2cd1719cfc11b1af41731e32ad"
dependencies = [
"aws-lc-sys",
"zeroize",
@@ -269,14 +269,15 @@ dependencies = [
[[package]]
name = "aws-lc-sys"
version = "0.41.0"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
checksum = "6d9ceb1da931507a12f4fccea479dccd00da1943e1b4ae72d8e502d707361444"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
"pkg-config",
]
[[package]]
@@ -557,9 +558,9 @@ checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
name = "bytemuck"
version = "1.25.0"
version = "1.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424"
[[package]]
name = "byteorder"
@@ -575,9 +576,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.12.0"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593"
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]]
name = "bytesize"
@@ -641,9 +642,9 @@ dependencies = [
[[package]]
name = "cc"
version = "1.2.65"
version = "1.2.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96"
checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
dependencies = [
"find-msvc-tools",
"jobserver",
@@ -826,7 +827,7 @@ dependencies = [
[[package]]
name = "conduwuit"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"aws-lc-rs",
"clap",
@@ -864,7 +865,7 @@ dependencies = [
[[package]]
name = "conduwuit_admin"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"assign",
"clap",
@@ -890,7 +891,7 @@ dependencies = [
[[package]]
name = "conduwuit_api"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"assign",
"async-trait",
@@ -928,7 +929,7 @@ dependencies = [
[[package]]
name = "conduwuit_build_metadata"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"built",
"cargo_metadata",
@@ -936,7 +937,7 @@ dependencies = [
[[package]]
name = "conduwuit_core"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"argon2",
"arrayvec",
@@ -1004,7 +1005,7 @@ dependencies = [
[[package]]
name = "conduwuit_database"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"async-channel",
"conduwuit_core",
@@ -1025,7 +1026,7 @@ dependencies = [
[[package]]
name = "conduwuit_macros"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"cargo_toml",
"itertools 0.15.0",
@@ -1036,7 +1037,7 @@ dependencies = [
[[package]]
name = "conduwuit_router"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"assign",
"axum",
@@ -1074,7 +1075,7 @@ dependencies = [
[[package]]
name = "conduwuit_service"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"askama",
"assign",
@@ -1126,7 +1127,7 @@ dependencies = [
[[package]]
name = "conduwuit_web"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"askama",
"assign",
@@ -1361,18 +1362,18 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@@ -1380,27 +1381,27 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
version = "0.9.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17"
[[package]]
name = "crossterm"
@@ -1463,9 +1464,9 @@ dependencies = [
[[package]]
name = "ctor"
version = "1.0.7"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01334b89b69ff726750c5ce5073fc8bd860e99aa9a8fc5ca11b04730e3aee97a"
checksum = "fb22e947478ccf9dc44d8922042c677a63fbb88f2cb468521d1145816e5087cb"
dependencies = [
"link-section",
"linktime-proc-macro",
@@ -1513,38 +1514,14 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "817fa642fb0ee7fe42e95783e00e0969927b96091bdd4b9b1af082acd943913b"
[[package]]
name = "darling"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
dependencies = [
"darling_core 0.20.11",
"darling_macro 0.20.11",
]
[[package]]
name = "darling"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [
"darling_core 0.23.0",
"darling_macro 0.23.0",
]
[[package]]
name = "darling_core"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
"darling_core",
"darling_macro",
]
[[package]]
@@ -1560,24 +1537,13 @@ dependencies = [
"syn",
]
[[package]]
name = "darling_macro"
version = "0.20.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
dependencies = [
"darling_core 0.20.11",
"quote",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [
"darling_core 0.23.0",
"darling_core",
"quote",
"syn",
]
@@ -1835,7 +1801,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -2122,9 +2088,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
dependencies = [
"cfg-if",
"js-sys",
"libc",
"r-efi 6.0.0",
"rand_core 0.10.1",
"wasm-bindgen",
]
[[package]]
@@ -2290,7 +2258,7 @@ dependencies = [
"http",
"httpdate",
"mime",
"sha1 0.10.6",
"sha1 0.10.7",
]
[[package]]
@@ -2906,11 +2874,11 @@ dependencies = [
[[package]]
name = "jobserver"
version = "0.1.34"
version = "0.1.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3"
dependencies = [
"getrandom 0.3.4",
"getrandom 0.4.3",
"libc",
]
@@ -3060,9 +3028,9 @@ dependencies = [
[[package]]
name = "link-section"
version = "0.18.3"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24670b639492630905459a6c7d47f063d33c2d4fcd5362f6e5827c5613976c9f"
checksum = "e333fe507b738576d6da5bb3f1a7d7a1c80307ed9ef31624c057d844c19c93e9"
[[package]]
name = "linked-hash-map"
@@ -3199,19 +3167,20 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]]
name = "memchr"
version = "2.8.2"
version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "memory-serve"
version = "2.1.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81b5bbad2035f57b1e95f66da606832edd935b47d82312e38e1ccffbcfb8a427"
checksum = "18718e8f7eefb3194d346474ac95f50ada065ff043e0074e5b8e85b85148122f"
dependencies = [
"axum",
"brotli",
"flate2",
"fs-err",
"mime_guess",
"sha256",
"tracing",
@@ -3376,7 +3345,7 @@ version = "0.50.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -3395,9 +3364,9 @@ dependencies = [
[[package]]
name = "num-bigint"
version = "0.4.7"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c863e9ab5e7bf9c99ba75e1050f1e4d624ae87ed3532d6238ffbdc7b585dbbe6"
checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367"
dependencies = [
"num-integer",
"num-traits",
@@ -3414,7 +3383,7 @@ dependencies = [
"num-integer",
"num-iter",
"num-traits",
"rand 0.8.6",
"rand 0.8.7",
"smallvec",
"zeroize",
]
@@ -3445,11 +3414,10 @@ dependencies = [
[[package]]
name = "num-iter"
version = "0.1.45"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
@@ -3495,7 +3463,7 @@ dependencies = [
"chrono",
"getrandom 0.2.17",
"http",
"rand 0.8.6",
"rand 0.8.7",
"reqwest 0.12.28",
"serde",
"serde_json",
@@ -3700,7 +3668,7 @@ dependencies = [
"oauth2",
"p256",
"p384",
"rand 0.8.6",
"rand 0.8.7",
"rsa",
"serde",
"serde-value",
@@ -3791,7 +3759,7 @@ dependencies = [
"opentelemetry",
"percent-encoding",
"portable-atomic",
"rand 0.9.4",
"rand 0.9.5",
"thiserror 2.0.18",
"tokio",
"tokio-stream",
@@ -3968,7 +3936,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
dependencies = [
"phf_shared",
"rand 0.8.6",
"rand 0.8.7",
]
[[package]]
@@ -4112,22 +4080,22 @@ dependencies = [
]
[[package]]
name = "proc-macro-error-attr2"
version = "2.0.0"
name = "proc-macro-error-attr3"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
checksum = "34e4dd828515431dd6c4a030d26f7eaed7dd4778226e9d2bb968d65ca4ec3d4d"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "proc-macro-error2"
version = "2.0.1"
name = "proc-macro-error3"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
checksum = "5ee475e440453418ff1335189eddf7101ba502cd818ab7ae04209bc83aa925aa"
dependencies = [
"proc-macro-error-attr2",
"proc-macro-error-attr3",
"proc-macro2",
"quote",
"syn",
@@ -4207,9 +4175,9 @@ checksum = "007d8adb5ddab6f8e3f491ac63566a7d5002cc7ed73901f72057943fa71ae1ae"
[[package]]
name = "pxfm"
version = "0.1.29"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "quick-error"
@@ -4240,15 +4208,16 @@ dependencies = [
[[package]]
name = "quinn-proto"
version = "0.11.15"
version = "0.11.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fcb935c5bec503c2f0e306bdd3e58bb9029dcb14fa8d9ac76e3a5256ac0763e"
checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.3.4",
"getrandom 0.4.3",
"lru-slab",
"rand 0.9.4",
"rand 0.10.2",
"rand_pcg",
"ring",
"rustc-hash",
"rustls",
@@ -4262,16 +4231,16 @@ dependencies = [
[[package]]
name = "quinn-udp"
version = "0.5.14"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
"windows-sys 0.61.2",
]
[[package]]
@@ -4303,9 +4272,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
version = "0.8.6"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
dependencies = [
"libc",
"rand_chacha 0.3.1",
@@ -4314,9 +4283,9 @@ dependencies = [
[[package]]
name = "rand"
version = "0.9.4"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea"
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
dependencies = [
"rand_chacha 0.9.0",
"rand_core 0.9.5",
@@ -4377,6 +4346,15 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
name = "rand_pcg"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a"
dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "recaptcha-verify"
version = "0.2.0"
@@ -4419,9 +4397,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.12.4"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
dependencies = [
"aho-corasick",
"memchr",
@@ -4431,9 +4409,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.14"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
dependencies = [
"aho-corasick",
"memchr",
@@ -4799,7 +4777,7 @@ dependencies = [
[[package]]
name = "ruminuwuity"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"assign",
"ruma",
@@ -4837,9 +4815,9 @@ dependencies = [
[[package]]
name = "rustc-demangle"
version = "0.1.27"
version = "0.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d"
checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb"
[[package]]
name = "rustc-hash"
@@ -4866,7 +4844,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -4925,7 +4903,7 @@ dependencies = [
"security-framework",
"security-framework-sys",
"webpki-root-certs",
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -4948,9 +4926,9 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.22"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
[[package]]
name = "rustyline-async"
@@ -5136,7 +5114,7 @@ version = "0.48.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8c44ee52fa3d30581f951b57aec754ef9cd70186df5fb788367804360c48097"
dependencies = [
"rand 0.9.4",
"rand 0.9.5",
"sentry-types",
"serde",
"serde_json",
@@ -5209,7 +5187,7 @@ checksum = "1be93ef0435a5ab91f88178fb7681249437e875d004f996ecb35be94dc99a0e0"
dependencies = [
"debugid",
"hex",
"rand 0.9.4",
"rand 0.9.5",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -5389,7 +5367,7 @@ version = "3.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660"
dependencies = [
"darling 0.23.0",
"darling",
"proc-macro2",
"quote",
"syn",
@@ -5397,9 +5375,9 @@ dependencies = [
[[package]]
name = "sha1"
version = "0.10.6"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
dependencies = [
"cfg-if",
"cpufeatures 0.2.17",
@@ -5574,7 +5552,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51"
dependencies = [
"libc",
"windows-sys 0.61.2",
"windows-sys 0.60.2",
]
[[package]]
@@ -5722,9 +5700,9 @@ dependencies = [
[[package]]
name = "termimad"
version = "0.35.0"
version = "0.35.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "903906afabf58273a7a90b47e0e60701cbd01731ca72ba88508b38b5539647f4"
checksum = "d53d4b1294b87e81925b7ae7f8f4d000376e3a5b3349d978429665428a793fcb"
dependencies = [
"coolor",
"crokey",
@@ -5788,9 +5766,9 @@ dependencies = [
[[package]]
name = "thread_local"
version = "1.1.9"
version = "1.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070"
dependencies = [
"cfg-if",
]
@@ -5865,9 +5843,9 @@ dependencies = [
[[package]]
name = "tinyvec"
version = "1.11.0"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f"
dependencies = [
"tinyvec_macros",
]
@@ -6230,7 +6208,7 @@ dependencies = [
"futures",
"http",
"parking_lot",
"rand 0.9.4",
"rand 0.9.5",
"serde",
"serde_json",
"thiserror 2.0.18",
@@ -6444,9 +6422,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "uuid"
version = "1.23.4"
version = "1.23.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53"
checksum = "ea5fab0d6c3c01ae70085a09cb03d4c7a1d6314e2b3e075392783396d724ca0a"
dependencies = [
"getrandom 0.4.3",
"js-sys",
@@ -6472,13 +6450,12 @@ dependencies = [
[[package]]
name = "validator_derive"
version = "0.20.0"
version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca"
checksum = "240e4b81c20a1d6d50d1d7265c658dfbd204e8b9ac4d80f3c931f39462196335"
dependencies = [
"darling 0.20.11",
"once_cell",
"proc-macro-error2",
"darling",
"proc-macro-error3",
"proc-macro2",
"quote",
"syn",
@@ -6694,7 +6671,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys 0.61.2",
"windows-sys 0.52.0",
]
[[package]]
@@ -6779,16 +6756,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
"windows-targets",
]
[[package]]
@@ -6806,31 +6774,14 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm 0.52.6",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
@@ -6839,96 +6790,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "winnow"
version = "0.7.15"
@@ -6972,7 +6875,7 @@ dependencies = [
[[package]]
name = "xtask"
version = "26.6.1"
version = "26.6.2"
dependencies = [
"askama",
"cargo_metadata",
@@ -7012,18 +6915,18 @@ dependencies = [
[[package]]
name = "zerocopy"
version = "0.8.52"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.8.52"
version = "0.8.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930"
checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
dependencies = [
"proc-macro2",
"quote",
@@ -7092,9 +6995,9 @@ dependencies = [
[[package]]
name = "zmij"
version = "1.0.21"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
checksum = "bd2f034a4bebf216c9e4b7083603e024cf930873fd67830cfb083c9fa33129d9"
[[package]]
name = "zstd"
+1 -1
View File
@@ -12,7 +12,7 @@ license = "Apache-2.0"
# See also `rust-toolchain.toml`
readme = "README.md"
repository = "https://forgejo.ellis.link/continuwuation/continuwuity"
version = "26.6.1"
version = "26.6.2"
[workspace.metadata.crane]
name = "conduwuit"
-1
View File
@@ -1 +0,0 @@
Added support for the OAuth2 device authorization flow. Contributed by @ginger
+1
View File
@@ -0,0 +1 @@
Appservices are now properly able to create devices for E2EE.
-1
View File
@@ -1 +0,0 @@
Fixed `global.oauth.compatibility_mode` being required, despite being ignored, when the `[global.oauth.oidc]` config section is provided.
-1
View File
@@ -1 +0,0 @@
Fixed an issue with a migration that could cause user accounts imported from an identity provider to be marked as deactivated when the server started. If you have accounts affected by this issue, use `!admin users reset-password --convert-to-local-account` to reactivate them.
+1
View File
@@ -0,0 +1 @@
Fixed newly created rooms failing to sync properly in clients using legacy sync.
+1
View File
@@ -0,0 +1 @@
Fixed the deeplink redirect for deleting devices. Contributed by @koen
Generated
+21 -21
View File
@@ -3,11 +3,11 @@
"advisory-db": {
"flake": false,
"locked": {
"lastModified": 1781566179,
"narHash": "sha256-Tqv8I586fYzWpEW/Smq/JqESFa3DVVzVWsnAMtvhy/I=",
"lastModified": 1783840254,
"narHash": "sha256-XjyvZk0f3YiinVHmkGOotmLBAzvK+LwEJYj2QqJ5pn8=",
"owner": "rustsec",
"repo": "advisory-db",
"rev": "74e084413d979d52d2f93b1d93b1ab7b9ee648f5",
"rev": "6e3286f4efa8c142fb33e5ea4342c8db6693cf34",
"type": "github"
},
"original": {
@@ -18,11 +18,11 @@
},
"crane": {
"locked": {
"lastModified": 1780532242,
"narHash": "sha256-D+BsdpxmtUwtqGoY0IXPhHgTlmqgcZKCEo1oMyn7ep0=",
"lastModified": 1783203018,
"narHash": "sha256-G6R9IT/xwFuu+CYBWDUAok6AdC4ERC4ZfPPFtEpxnZE=",
"owner": "ipetkov",
"repo": "crane",
"rev": "59a82a1222dd3b2080b5cc52a1a2e8d5f1b77f37",
"rev": "80db5bdc391be8a1794f6d8a2d56e3a84ebcede2",
"type": "github"
},
"original": {
@@ -39,11 +39,11 @@
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1781527054,
"narHash": "sha256-1fX9ev2Fh5QoKQ41G9dYutjo5j/jywu6tZse5Eb1Ck4=",
"lastModified": 1783844668,
"narHash": "sha256-3MOpw4y3reoErRLvFBDz0t9aG6FWXUj7leKvUns5eQA=",
"owner": "nix-community",
"repo": "fenix",
"rev": "8c2e51dffefc040a21975da7abf6f252c8c9b783",
"rev": "4e49ef62eedaa5c149d62b63b3f53844e1cc45d7",
"type": "github"
},
"original": {
@@ -74,11 +74,11 @@
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"lastModified": 1782949081,
"narHash": "sha256-vp6Y/Grm98ESt6ceOkWiHWyZRDV3J1RID4w+6NWK9yA=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"rev": "17c9d6cdfc60c64f4ee8d306f9bc0b4ccb51481e",
"type": "github"
},
"original": {
@@ -89,11 +89,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1781074563,
"narHash": "sha256-md8WlXOlfnIeHeOScMTTHFyf2d6iaTwPl2apR5EQ3P4=",
"lastModified": 1783776592,
"narHash": "sha256-UgCQzxeWI75XM8G+hPrPh+MKzEPjG3SpAj7dtqSbksA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "9ae611a455b90cf061d8f332b977e387bda8e1ca",
"rev": "e7a3ca8092b61ff85b6a45bf863ea2b2d6a661b3",
"type": "github"
},
"original": {
@@ -105,11 +105,11 @@
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"lastModified": 1782614948,
"narHash": "sha256-ePjCwr1sNm9NYUqywL7QfK3JnlS015msC+eBu2zKlp8=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"rev": "db3f255737b94216eb71cce308e2912cf6bc2d7c",
"type": "github"
},
"original": {
@@ -132,11 +132,11 @@
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1781453968,
"narHash": "sha256-+V3nK4pCngbmgyVGXY6Kkrlevp4ocPkJJLf2aqwkDNA=",
"lastModified": 1783779020,
"narHash": "sha256-vpm418WZa9l1KUl6HRs+Ga+SIfE+D2/sV4olwTlilYc=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "cc272809a173c2c11d0e479d639c811c1eacf049",
"rev": "5be5e89cf0145d73a85c805726821b4adfc3af48",
"type": "github"
},
"original": {
+48 -48
View File
@@ -510,14 +510,14 @@
}
},
"node_modules/@shikijs/core": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.2.0.tgz",
"integrity": "sha512-Hc87Ab1Ld/vEbZRCbwx344I5v+4RU8CVToUTRkqXL1+TjbuOp9U5Xa0M23V4GEWHxVn+yO5otb+HkQVm3ptWQQ==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.3.1.tgz",
"integrity": "sha512-ANMDxuaPsNMdDC1m4vfvhlDmJweMwkE5XitTwrq2rWHx5jM+dlm4MmHt2PP6t0uejfR77SuhrhJ0zEijIF/uhA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/primitive": "4.2.0",
"@shikijs/types": "4.2.0",
"@shikijs/primitive": "4.3.1",
"@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4",
"hast-util-to-html": "^9.0.5"
@@ -527,13 +527,13 @@
}
},
"node_modules/@shikijs/engine-javascript": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.2.0.tgz",
"integrity": "sha512-fjETeq1k5ffyXqRgS6+3hpvqseLalp1kjNfRbXpUgWR8FpZ1CmQfiNHovc5lncYjt/Vg5JK/WJEmLahjwMa0og==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.3.1.tgz",
"integrity": "sha512-JBItcnPuYq7jVJdZo/vMj94r+szT7XEjHFX+mvFDGSEIbVAXAGyHAHzhbWzpGOwYidCZrErJLLgn2PVeiokHnQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.2.0",
"@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2",
"oniguruma-to-es": "^4.3.6"
},
@@ -542,13 +542,13 @@
}
},
"node_modules/@shikijs/engine-oniguruma": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.2.0.tgz",
"integrity": "sha512-hTorK1dffPkpbMUk6Z+828PgRo7d07HbnizoP0hNPFjhxMHctj0Px/qoHeGMYafc6ju+u9iMldN4JbVzNQM++g==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.3.1.tgz",
"integrity": "sha512-OXyNMzg0pews+msMj4cHeqT4xiYKKvbnn6VbdAXxfoFl3SSx4fJTc8FadECuc5/H9p3BzhNAoAUXKwAu9rWYhg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.2.0",
"@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2"
},
"engines": {
@@ -556,26 +556,26 @@
}
},
"node_modules/@shikijs/langs": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.2.0.tgz",
"integrity": "sha512-bwrVRlJ0wUhZxAbVdvBbv2TTC9yLsh4C/IO5Ofz0T8MQntgDvyVnkbjw9vi50r1kx7RCIJdnJnjZAwmAsXFLZQ==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.3.1.tgz",
"integrity": "sha512-m0l9nsDqgBHvbZbk7A0/kXz/impK3uB/c6rAn6Gpg/uPtdZRQ+alsN/17MU5thb68XTj/4DxkZAotrM0GGSpDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.2.0"
"@shikijs/types": "4.3.1"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/primitive": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.2.0.tgz",
"integrity": "sha512-NOq+DtUkVBJtZMVXL5A0vI0Xk8nvDYaXetFHSJFlOqjDZIVhIPRYFdGkSoElDqNuegikcc3A76SNUa8dTqtAYA==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.3.1.tgz",
"integrity": "sha512-CXQRQOYy1leqQ8ceTeJdmXv/bsUY++6QyLpXJ94LZAAYj5X2SKRdc5ipguv4NPyGVKItB2PPwUpRNe0Sjh5S1A==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.2.0",
"@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
},
@@ -584,16 +584,16 @@
}
},
"node_modules/@shikijs/rehype": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.2.0.tgz",
"integrity": "sha512-ST3EWye/dwF1gWskczJNBnwFtDzEQ9ceytXZtyc/GfwR5V0qJrkoSGZO55O3SAKDDsXkTDcsfwd9pVe7ROlAHg==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/rehype/-/rehype-4.3.1.tgz",
"integrity": "sha512-oshrlfUF3VPUJfnp5K1lLwsS/SRBKrIxONpdWebSKZXdBE3UsZnxgqpvRUA8UsofS7vmjFOCAHIT71ECbmOxTw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.2.0",
"@shikijs/types": "4.3.1",
"@types/hast": "^3.0.4",
"hast-util-to-string": "^3.0.1",
"shiki": "4.2.0",
"shiki": "4.3.1",
"unified": "^11.0.5",
"unist-util-visit": "^5.1.0"
},
@@ -602,22 +602,22 @@
}
},
"node_modules/@shikijs/themes": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.2.0.tgz",
"integrity": "sha512-RX8IHYeLv8Cu2W6ruc3RxUqWn0IYCqSrMBzi/uRGAmfyDNOnNO5BF/Px7o97n4XTpmFTo5GbRaazuOWj+2ak2w==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.3.1.tgz",
"integrity": "sha512-dgpoJ4WqNi2yTmizQHBJ5zcX6j2lE6icN/0yt4l1kkf16jrY/pwPLoTb1ETsWMz0OBLf9ZNvwmxft+cH+N9qSA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/types": "4.2.0"
"@shikijs/types": "4.3.1"
},
"engines": {
"node": ">=20"
}
},
"node_modules/@shikijs/types": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.2.0.tgz",
"integrity": "sha512-VT/MKtlpOhEPZloSH3Pb9WCZEBDoQVMa9jedp5UAwmJOar1DVc9DRODAxmYPW9M93IK4ryuqRejFfmlvlVDemw==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.3.1.tgz",
"integrity": "sha512-CHFxE0jztBIZRHH6gxXE7DXUCFXjReEGxZ/j0rfSLGKZuwp2xBYycEP14875DSa9KLL/6700oxIq6oO6ef9K2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -684,9 +684,9 @@
}
},
"node_modules/@types/hast": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
"integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==",
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.5.tgz",
"integrity": "sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1076,9 +1076,9 @@
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
"integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz",
"integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==",
"dev": true,
"license": "ISC"
},
@@ -3589,18 +3589,18 @@
"license": "MIT"
},
"node_modules/shiki": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.2.0.tgz",
"integrity": "sha512-hjNax6o/ylDy9lefQEaSDtzaT3iVNtZ3WmpQnbuQNoG4xvnSKf2kSKbihZVO4JRG1TTMejs7CmNRYlWgAL66pQ==",
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.3.1.tgz",
"integrity": "sha512-oR+qDVi2OjX1tmDpyv+3KviX01KzO6Af+0NNnKnsp9491UEGz2YpxTuJboS/6VhYpTdqzmuJBuiTlrAWWJAssw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@shikijs/core": "4.2.0",
"@shikijs/engine-javascript": "4.2.0",
"@shikijs/engine-oniguruma": "4.2.0",
"@shikijs/langs": "4.2.0",
"@shikijs/themes": "4.2.0",
"@shikijs/types": "4.2.0",
"@shikijs/core": "4.3.1",
"@shikijs/engine-javascript": "4.3.1",
"@shikijs/engine-oniguruma": "4.3.1",
"@shikijs/langs": "4.3.1",
"@shikijs/themes": "4.3.1",
"@shikijs/types": "4.3.1",
"@shikijs/vscode-textmate": "^10.0.2",
"@types/hast": "^3.0.4"
},
+1 -1
View File
@@ -26,7 +26,7 @@
};
use service::{mailer::messages, uiaa::UiaaInitiator, users::HashedPassword};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use super::DEVICE_ID_LENGTH;
use crate::{Ruma, router::ClientIdentity};
pub(crate) mod register;
+8 -6
View File
@@ -20,9 +20,12 @@
assign,
};
use serde_json::value::RawValue;
use service::{mailer::messages, users::HashedPassword};
use service::{
mailer::messages,
users::{DeviceToken, HashedPassword},
};
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use super::DEVICE_ID_LENGTH;
use crate::Ruma;
/// # `POST /_matrix/client/v3/register`
@@ -119,7 +122,7 @@ pub(crate) async fn register_route(
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate new token for the device
let new_token = utils::random_string(TOKEN_LENGTH);
let new_token = DeviceToken::new_random();
// Create device for this account
services
@@ -127,8 +130,7 @@ pub(crate) async fn register_route(
.create_device(
&user_id,
&device_id,
&new_token,
None,
Some(new_token.clone()),
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
@@ -142,7 +144,7 @@ pub(crate) async fn register_route(
debug_info!(%user_id, ?device, "New account created via legacy registration");
Ok(assign!(register::v3::Response::new(user_id), {
access_token: token,
access_token: token.map(DeviceToken::into_token),
device_id: device,
refresh_token: None,
expires_in: None,
+1 -8
View File
@@ -89,14 +89,7 @@ pub(crate) async fn update_device_route(
services
.users
.create_device(
sender_user,
&device_id,
&appservice.registration.as_token,
None,
None,
Some(client.to_string()),
)
.create_device(sender_user, &device_id, None, None, Some(client.to_string()))
.await?;
return Ok(update_device::v3::Response::new());
-13
View File
@@ -1,13 +0,0 @@
use axum::{Form, Json, extract::State, response::IntoResponse};
use http::StatusCode;
use service::oauth::grant::DeviceCodeRequest;
pub(crate) async fn device_authorization_route(
State(services): State<crate::State>,
Form(request): Form<DeviceCodeRequest>,
) -> impl IntoResponse {
match services.oauth.request_device_code(request).await {
| Ok(response) => Ok(Json(response)),
| Err(err) => Err((StatusCode::BAD_REQUEST, Json(err))),
}
}
-3
View File
@@ -10,7 +10,6 @@
use serde_json::json;
pub(crate) use server_metadata::*;
mod device;
mod register_client;
mod server_metadata;
mod token;
@@ -21,7 +20,6 @@
const CLIENT_REGISTER_PATH: &str = "client/register";
const TOKEN_REVOKE_PATH: &str = "client/revoke";
const TOKEN_PATH: &str = "grant/token";
const DEVICE_AUTHORIZATION_PATH: &str = "device";
const ACCOUNT_MANAGEMENT_PATH: &str = concat!(conduwuit_core::ROUTE_PREFIX, "/account/deeplink");
pub(crate) fn router(state: crate::State) -> Router<crate::State> {
@@ -55,5 +53,4 @@ fn oauth_router() -> Router<crate::State> {
.route(concat!("/", JWKS_URI_PATH), get(async || Json(json!({"keys": []}))))
.route(concat!("/", TOKEN_PATH), post(token::token_route))
.route(concat!("/", TOKEN_REVOKE_PATH), post(token::revoke_token_route))
.route(concat!("/", DEVICE_AUTHORIZATION_PATH), post(device::device_authorization_route))
}
+3 -4
View File
@@ -12,8 +12,8 @@
use crate::{
Ruma,
client::oauth::{
ACCOUNT_MANAGEMENT_PATH, AUTH_CODE_PATH, CLIENT_REGISTER_PATH, DEVICE_AUTHORIZATION_PATH,
JWKS_URI_PATH, TOKEN_PATH, TOKEN_REVOKE_PATH,
ACCOUNT_MANAGEMENT_PATH, AUTH_CODE_PATH, CLIENT_REGISTER_PATH, JWKS_URI_PATH, TOKEN_PATH,
TOKEN_REVOKE_PATH,
},
};
@@ -49,8 +49,7 @@ pub(crate) async fn authorization_server_metadata(services: &Services) -> Value
],
"authorization_endpoint": endpoint_base.join(AUTH_CODE_PATH).unwrap(),
"code_challenge_methods_supported": ["S256"],
"device_authorization_endpoint": endpoint_base.join(DEVICE_AUTHORIZATION_PATH).unwrap(),
"grant_types_supported": ["authorization_code", "refresh_token", "urn:ietf:params:oauth:grant-type:device_code"],
"grant_types_supported": ["authorization_code", "refresh_token"],
"issuer": services.config.get_client_domain(),
"jwks_uri": endpoint_base.join(JWKS_URI_PATH).unwrap(),
"prompt_values_supported": ["create"],
+8 -8
View File
@@ -29,8 +29,9 @@
},
assign,
};
use service::users::DeviceToken;
use super::{DEVICE_ID_LENGTH, TOKEN_LENGTH};
use super::DEVICE_ID_LENGTH;
use crate::Ruma;
/// # `GET /_matrix/client/v3/login`
@@ -196,8 +197,8 @@ pub(crate) async fn login_route(
.clone()
.unwrap_or_else(|| utils::random_string(DEVICE_ID_LENGTH).into());
// Generate a new token for the device (ensuring no collisions)
let token = services.users.generate_unique_token().await;
// Generate a new token for the device
let token = DeviceToken::new_random();
// Determine if device_id was provided and exists in the db for this user
let device_exists = if body.device_id.is_some() {
@@ -213,7 +214,7 @@ pub(crate) async fn login_route(
if device_exists {
services
.users
.set_token(&user_id, &device_id, &token, None)
.set_token(&user_id, &device_id, token.clone())
.await?;
} else {
services
@@ -221,8 +222,7 @@ pub(crate) async fn login_route(
.create_device(
&user_id,
&device_id,
&token,
None,
Some(token.clone()),
body.initial_device_display_name.clone(),
Some(client.to_string()),
)
@@ -241,7 +241,7 @@ pub(crate) async fn login_route(
info!("{user_id} logged in");
#[allow(deprecated)]
Ok(assign!(login::v3::Response::new(user_id, token, device_id), {
Ok(assign!(login::v3::Response::new(user_id, token.into_token(), device_id), {
well_known: client_discovery_info,
expires_in: None,
home_server: Some(services.config.server_name.clone()),
@@ -273,7 +273,7 @@ pub(crate) async fn login_token_route(
.authenticate_password(&body.auth, sender_user, body.identity.sender_device(), None)
.await?;
let login_token = utils::random_string(TOKEN_LENGTH);
let login_token = DeviceToken::new_random().into_token();
let expires_in = services.users.create_login_token(sender_user, &login_token);
Ok(get_login_token::v1::Response::new(
+10 -2
View File
@@ -1,4 +1,6 @@
use conduwuit::{Err, Result, debug, debug_info, error, info};
use std::io::IsTerminal;
use conduwuit::{Err, Result, debug, debug_info, error, info, warn};
use ruma::events::room::message::RoomMessageEventContent;
use tokio::time::{Duration, sleep};
@@ -7,10 +9,16 @@
pub(super) const SIGNAL: &str = "SIGUSR2";
impl super::Service {
/// Possibly spawn the terminal console at startup if configured.
/// Possibly spawn the terminal console at startup if configured and a TTY
/// is available.
pub(super) async fn console_auto_start(&self) {
#[cfg(feature = "console")]
if self.services.server.config.admin_console_automatic {
if !std::io::stdin().is_terminal() {
warn!("Console enabled without a tty available; Not enabling console");
return;
}
// Allow more of the startup sequence to execute before spawning
tokio::task::yield_now().await;
self.console.start().await;
-2
View File
@@ -132,8 +132,6 @@ pub enum ApplicationType {
#[serde(rename_all = "snake_case")]
pub enum GrantType {
AuthorizationCode,
#[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
DeviceCode,
RefreshToken,
}
+17 -61
View File
@@ -13,7 +13,6 @@
use url::Url;
use super::client_metadata::ResponseType;
use crate::oauth::client_metadata::GrantType;
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct AuthorizationCodeQuery {
@@ -30,33 +29,6 @@ pub struct AuthorizationCodeQuery {
pub prompt: Option<Prompt>,
}
#[derive(Deserialize, Serialize)]
pub struct AuthorizationCodeResponse {
pub state: String,
pub code: String,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DeviceCodeRequest {
pub client_id: String,
pub scope: RawScopes,
}
#[derive(Deserialize, Serialize)]
pub struct DeviceCodeResponse {
pub device_code: String,
pub user_code: String,
pub verification_uri: Url,
#[serde(skip_serializing_if = "Option::is_none")]
pub verification_uri_complete: Option<Url>,
pub expires_in: u64,
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct DeviceCodeVerifyQuery {
pub user_code: Option<String>,
}
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
@@ -154,29 +126,19 @@ pub struct OAuthError {
impl OAuthError {
#[must_use]
pub fn new(error: ErrorCode, error_description: String) -> Self {
pub const fn invalid_request(error_description: &'static str) -> Self {
Self {
error,
error_description: Cow::Owned(error_description),
}
}
#[must_use]
pub const fn new_static(error: ErrorCode, error_description: &'static str) -> Self {
Self {
error,
error: ErrorCode::InvalidRequest,
error_description: Cow::Borrowed(error_description),
}
}
#[must_use]
pub const fn invalid_request(error_description: &'static str) -> Self {
Self::new_static(ErrorCode::InvalidRequest, error_description)
}
#[must_use]
pub const fn invalid_grant(error_description: &'static str) -> Self {
Self::new_static(ErrorCode::InvalidGrant, error_description)
Self {
error: ErrorCode::InvalidGrant,
error_description: Cow::Borrowed(error_description),
}
}
}
@@ -196,41 +158,35 @@ pub enum ErrorCode {
InvalidScope,
InvalidGrant,
InvalidClientMetadata,
AuthorizationPending,
ExpiredToken,
}
#[derive(Deserialize)]
pub struct TokenRequest {
pub client_id: String,
#[serde(flatten)]
pub request: TokenRequestType,
#[derive(Serialize, Deserialize)]
pub struct AuthorizationCodeResponse {
pub state: String,
pub code: String,
}
#[derive(Deserialize)]
#[serde(tag = "grant_type", rename_all = "snake_case")]
pub enum TokenRequestType {
pub enum TokenRequest {
AuthorizationCode {
code: String,
redirect_uri: Url,
client_id: String,
code_verifier: String,
},
#[serde(rename = "urn:ietf:params:oauth:grant-type:device_code")]
DeviceCode {
device_code: String,
},
RefreshToken {
client_id: String,
refresh_token: String,
},
}
impl TokenRequestType {
impl TokenRequest {
#[must_use]
pub fn grant_type(&self) -> GrantType {
pub fn client_id(&self) -> &str {
match self {
| Self::AuthorizationCode { .. } => GrantType::AuthorizationCode,
| Self::DeviceCode { .. } => GrantType::DeviceCode,
| Self::RefreshToken { .. } => GrantType::RefreshToken,
| Self::AuthorizationCode { client_id, .. }
| Self::RefreshToken { client_id, .. } => client_id,
}
}
}
+37 -235
View File
@@ -12,22 +12,20 @@
use database::{Deserialized, Json, Map};
use itertools::Itertools;
use lru_cache::LruCache;
use rand::distr::{Distribution, slice::Choose};
use ruma::{DeviceId, OwnedDeviceId, OwnedUserId, UserId};
use serde::{Deserialize, Serialize};
use url::Url;
use crate::{
Dep, config,
Dep,
oauth::{
client_metadata::{ApplicationType, ClientMetadata, ResponseType},
grant::{
AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod,
DeviceCodeRequest, DeviceCodeResponse, ErrorCode, OAuthError, ResponseMode, Scope,
TokenRequest, TokenRequestType, TokenResponse, TokenType,
AuthorizationCodeQuery, AuthorizationCodeResponse, CodeChallengeMethod, ErrorCode,
OAuthError, ResponseMode, Scope, TokenRequest, TokenResponse, TokenType,
},
},
users,
users::{self, DeviceToken},
};
pub mod client_metadata;
@@ -37,8 +35,7 @@ pub struct Service {
services: Services,
db: Data,
tickets: Mutex<HashMap<String, HashMap<OAuthTicket, SystemTime>>>,
pending_auth_code_grants: tokio::sync::Mutex<LruCache<String, PendingAuthCodeGrant>>,
pending_device_code_grants: tokio::sync::Mutex<LruCache<String, PendingDeviceCodeGrant>>,
pending_code_grants: tokio::sync::Mutex<LruCache<String, PendingCodeGrant>>,
}
struct Data {
@@ -49,7 +46,6 @@ struct Data {
struct Services {
users: Dep<users::Service>,
config: Dep<config::Service>,
}
#[derive(Debug, Deserialize, Serialize)]
@@ -66,7 +62,7 @@ struct RefreshTokenInfo {
device_id: OwnedDeviceId,
}
struct PendingAuthCodeGrant {
struct PendingCodeGrant {
authorizing_user: OwnedUserId,
requested_scopes: BTreeSet<Scope>,
client_name: Option<String>,
@@ -76,8 +72,12 @@ struct PendingAuthCodeGrant {
requested_at: SystemTime,
}
impl PendingAuthCodeGrant {
impl PendingCodeGrant {
const MAX_AGE: Duration = Duration::from_mins(1);
const RANDOM_CODE_LENGTH: usize = 32;
#[must_use]
pub(crate) fn generate_code() -> String { utils::random_string(Self::RANDOM_CODE_LENGTH) }
#[must_use]
pub(crate) fn is_valid_for(&self, client_id: &str) -> bool {
@@ -90,43 +90,6 @@ pub(crate) fn is_valid_for(&self, client_id: &str) -> bool {
}
}
struct PendingDeviceCodeGrant {
state: DeviceCodeGrantState,
requested_scopes: BTreeSet<Scope>,
client_name: Option<String>,
client_id: String,
requested_at: SystemTime,
}
enum DeviceCodeGrantState {
Unverified {
user_code: String,
},
Verified {
authorizing_user: OwnedUserId,
},
}
impl PendingDeviceCodeGrant {
const MAX_AGE: Duration = Duration::from_mins(1);
#[must_use]
pub(crate) fn is_valid_for(&self, client_id: &str) -> bool {
let now = SystemTime::now();
self.client_id == client_id
&& now
.duration_since(self.requested_at)
.is_ok_and(|age| age < Self::MAX_AGE)
}
}
pub struct DeviceCodeGrantInfo {
pub device_code: String,
pub client_metadata: ClientMetadata,
pub requested_scopes: BTreeSet<Scope>,
}
/// A time-limited grant for a client to perform some sensitive action.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum OAuthTicket {
@@ -149,7 +112,6 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
Ok(Arc::new(Self {
services: Services {
users: args.depend::<users::Service>("users"),
config: args.depend::<config::Service>("config"),
},
db: Data {
clientid_clientmetadata: args.db["clientid_clientmetadata"].clone(),
@@ -157,11 +119,8 @@ fn build(args: crate::Args<'_>) -> Result<Arc<Self>> {
refreshtoken_refreshtokeninfo: args.db["refreshtoken_refreshtokeninfo"].clone(),
},
tickets: Mutex::default(),
pending_auth_code_grants: tokio::sync::Mutex::new(LruCache::new(
Self::MAX_PENDING_GRANTS,
)),
pending_device_code_grants: tokio::sync::Mutex::new(LruCache::new(
Self::MAX_PENDING_GRANTS,
pending_code_grants: tokio::sync::Mutex::new(LruCache::new(
Self::MAX_PENDING_CODE_GRANTS,
)),
}))
}
@@ -174,21 +133,11 @@ impl Service {
// Maximum number of pending code grants which will be held in memory at once,
// to prevent unbounded memory use if someone decides to repeatedly reload the
// grant page.
const MAX_PENDING_GRANTS: usize = 100;
const MAX_PENDING_CODE_GRANTS: usize = 100;
const RANDOM_TOKEN_LENGTH: usize = 32;
const USER_CODE_CHARACTERS: &[char] = &['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
const USER_CODE_LENGTH: usize = 6;
fn generate_token() -> String { utils::random_string(Self::RANDOM_TOKEN_LENGTH) }
fn generate_user_code() -> String {
Choose::new(Self::USER_CODE_CHARACTERS)
.unwrap()
.sample_iter(&mut rand::rng())
.take(Self::USER_CODE_LENGTH)
.collect()
}
pub async fn register_client(&self, metadata: &ClientMetadata) -> Result<String, OAuthError> {
metadata.validate().map_err(|error| OAuthError {
error: ErrorCode::InvalidClientMetadata,
@@ -282,14 +231,14 @@ pub async fn request_authorization_code(
| ResponseMode::Query => '?',
};
let code = Self::generate_token();
let code = PendingCodeGrant::generate_code();
info!(
client_id = &query.client_id,
client_name = &client_metadata.client_name,
?requested_scopes,
?authorizing_user,
"Issuing OAuth authorization code"
"Issuing oauth authorization code"
);
let redirect_uri = format!(
@@ -303,7 +252,7 @@ pub async fn request_authorization_code(
.unwrap(),
);
let pending_grant = PendingAuthCodeGrant {
let pending_grant = PendingCodeGrant {
authorizing_user,
requested_scopes,
client_name: client_metadata.client_name,
@@ -313,7 +262,7 @@ pub async fn request_authorization_code(
requested_at: SystemTime::now(),
};
self.pending_auth_code_grants
self.pending_code_grants
.lock()
.await
.insert(code, pending_grant);
@@ -321,129 +270,15 @@ pub async fn request_authorization_code(
Ok(redirect_uri)
}
pub async fn request_device_code(
&self,
query: DeviceCodeRequest,
) -> Result<DeviceCodeResponse, OAuthError> {
let Some(client_metadata) = self.get_client_metadata(&query.client_id).await else {
return Err(OAuthError::invalid_grant("Invalid client ID"));
};
let requested_scopes = query
.scope
.to_scopes()
.map_err(|err| OAuthError::new(ErrorCode::InvalidGrant, err))?;
let device_code = Self::generate_token();
let user_code = Self::generate_user_code();
let verification_uri = self
.services
.config
.get_client_domain()
.join(&format!("{}/oauth2/grant/device_code", conduwuit::ROUTE_PREFIX))
.unwrap();
let mut verification_uri_complete = verification_uri.clone();
verification_uri_complete
.query_pairs_mut()
.append_pair("user_code", &user_code);
info!(
client_id = &query.client_id,
client_name = &client_metadata.client_name,
?requested_scopes,
"Issuing OAuth device code"
);
let pending_grant = PendingDeviceCodeGrant {
state: DeviceCodeGrantState::Unverified { user_code: user_code.clone() },
requested_scopes,
client_name: client_metadata.client_name,
client_id: query.client_id,
requested_at: SystemTime::now(),
};
self.pending_device_code_grants
.lock()
.await
.insert(device_code.clone(), pending_grant);
Ok(DeviceCodeResponse {
device_code,
user_code,
verification_uri,
verification_uri_complete: Some(verification_uri_complete),
expires_in: PendingDeviceCodeGrant::MAX_AGE.as_secs(),
})
}
pub async fn grant_info_for_user_code(
&self,
supplied_user_code: &str,
) -> Option<DeviceCodeGrantInfo> {
let pending_grants = self.pending_device_code_grants.lock().await;
let (device_code, grant) = pending_grants
.iter()
.find(|(_, grant)| {
matches!(&grant.state, DeviceCodeGrantState::Unverified { user_code } if user_code == supplied_user_code)
})?;
let client_metadata = self
.get_client_metadata(&grant.client_id)
.await
.expect("client should exist");
Some(DeviceCodeGrantInfo {
device_code: device_code.clone(),
client_metadata,
requested_scopes: grant.requested_scopes.clone(),
})
}
pub async fn validate_device_code(
&self,
authorizing_user: OwnedUserId,
device_code: &str,
) -> Result<(), String> {
let mut pending_grants = self.pending_device_code_grants.lock().await;
let Some(pending_grant) = pending_grants.get_mut(device_code) else {
return Err("Invalid device code".to_owned());
};
match &mut pending_grant.state {
| state @ DeviceCodeGrantState::Unverified { .. } => {
*state = DeviceCodeGrantState::Verified { authorizing_user };
Ok(())
},
| DeviceCodeGrantState::Verified {
authorizing_user: previous_authorizing_user,
} =>
if *previous_authorizing_user == authorizing_user {
Ok(())
} else {
Err("Device code is already verified".to_owned())
},
}
}
pub async fn issue_token(&self, request: TokenRequest) -> Result<TokenResponse, OAuthError> {
let TokenRequest { client_id, request } = request;
let Some(client_metadata) = self.get_client_metadata(&client_id).await else {
return Err(OAuthError::invalid_request("Invalid client ID"));
};
if !client_metadata.grant_types.contains(&request.grant_type()) {
return Err(OAuthError::invalid_grant("Client cannot request this grant type"));
}
match request {
| TokenRequestType::AuthorizationCode { code, redirect_uri, code_verifier } => {
let mut pending_grants = self.pending_auth_code_grants.lock().await;
| TokenRequest::AuthorizationCode {
code,
redirect_uri,
client_id,
code_verifier,
} => {
let mut pending_grants = self.pending_code_grants.lock().await;
let Some(pending_grant) = pending_grants
.remove(&code)
@@ -470,39 +305,7 @@ pub async fn issue_token(&self, request: TokenRequest) -> Result<TokenResponse,
)
.await
},
| TokenRequestType::DeviceCode { device_code } => {
let mut pending_grants = self.pending_device_code_grants.lock().await;
let Some(pending_grant) = pending_grants
.remove(&device_code)
.filter(|grant| grant.is_valid_for(&client_id))
else {
return Err(OAuthError::new_static(
ErrorCode::ExpiredToken,
"Invalid device code",
));
};
match &pending_grant.state {
| DeviceCodeGrantState::Unverified { .. } => {
pending_grants.insert(device_code, pending_grant);
Err(OAuthError::new_static(
ErrorCode::AuthorizationPending,
"Authorization is pending",
))
},
| DeviceCodeGrantState::Verified { authorizing_user } =>
self.create_session(
authorizing_user.to_owned(),
pending_grant.requested_scopes,
pending_grant.client_name,
client_id,
)
.await,
}
},
| TokenRequestType::RefreshToken { refresh_token } =>
| TokenRequest::RefreshToken { client_id, refresh_token } =>
self.refresh_session(client_id, refresh_token).await,
}
}
@@ -540,7 +343,7 @@ async fn create_session(
client_name: Option<String>,
client_id: String,
) -> Result<TokenResponse, OAuthError> {
let access_token = Self::generate_token();
let access_token = DeviceToken::new_random().with_max_age(Self::ACCESS_TOKEN_MAX_AGE);
let refresh_token = Self::generate_token();
let device_id = requested_scopes
@@ -561,10 +364,11 @@ async fn create_session(
.await
.is_ok()
{
return Err(OAuthError::new_static(
ErrorCode::InvalidScope,
"A device with the supplied ID already exists for this user",
));
return Err(OAuthError {
error: ErrorCode::InvalidScope,
error_description: "A device with the supplied ID already exists for this user"
.into(),
});
}
self.services
@@ -572,8 +376,7 @@ async fn create_session(
.create_device(
&authorizing_user,
device_id,
&access_token,
Some(Self::ACCESS_TOKEN_MAX_AGE),
Some(access_token.clone()),
client_name,
None,
)
@@ -609,7 +412,7 @@ async fn create_session(
);
Ok(TokenResponse {
access_token,
access_token: access_token.into_token(),
token_type: TokenType::Bearer,
expires_in: Self::ACCESS_TOKEN_MAX_AGE.as_secs(),
scope: requested_scopes.iter().join(" "),
@@ -645,7 +448,7 @@ async fn refresh_session(
assert_eq!(&client_id, &session_info.client_id, "session info client id mismatch");
let new_access_token = Self::generate_token();
let new_access_token = DeviceToken::new_random().with_max_age(Self::ACCESS_TOKEN_MAX_AGE);
let new_refresh_token = Self::generate_token();
let scope = session_info.scopes.iter().join(" ");
session_info
@@ -657,8 +460,7 @@ async fn refresh_session(
.set_token(
&refresh_token_info.user_id,
&refresh_token_info.device_id,
&new_access_token,
Some(Self::ACCESS_TOKEN_MAX_AGE),
new_access_token.clone(),
)
.await
.expect("should be able to set token");
@@ -675,7 +477,7 @@ async fn refresh_session(
.raw_put(&new_refresh_token, Json(refresh_token_info));
Ok(TokenResponse {
access_token: new_access_token,
access_token: new_access_token.into_token(),
token_type: TokenType::Bearer,
expires_in: Self::ACCESS_TOKEN_MAX_AGE.as_secs(),
scope,
-1
View File
@@ -41,7 +41,6 @@ pub async fn set_dehydrated_device(&self, user_id: &UserId, request: Request) ->
self.create_device(
user_id,
&request.device_id,
"",
None,
request.initial_device_display_name.clone(),
None,
+38 -29
View File
@@ -11,20 +11,44 @@
use futures::{Stream, StreamExt};
use ruma::{
DeviceId, MilliSecondsSinceUnixEpoch, OwnedDeviceId, OwnedUserId, UserId,
api::client::device::Device, events::AnyToDeviceEvent, serde::Raw, uint,
api::client::device::Device, assign, events::AnyToDeviceEvent, serde::Raw, uint,
};
use serde_json::json;
use crate::users::increment;
#[must_use]
#[derive(Clone)]
pub struct DeviceToken {
token: String,
max_age: Option<Duration>,
}
impl DeviceToken {
const DEVICE_ID_LENGTH: usize = 10;
pub fn new(token: String) -> Self { Self { token, max_age: None } }
pub fn new_random() -> Self { Self::new(utils::random_string(Self::DEVICE_ID_LENGTH)) }
pub fn with_max_age(self, max_age: Duration) -> Self {
assign!(self, { max_age: Some(max_age) })
}
#[must_use]
pub fn into_token(self) -> String { self.token }
}
impl super::Service {
/// Adds a new device to a user.
///
/// If no `token` is provided, the device will not be able to be logged
/// into.
pub async fn create_device(
&self,
user_id: &UserId,
device_id: &DeviceId,
token: &str,
token_max_age: Option<Duration>,
token: Option<DeviceToken>,
initial_device_display_name: Option<String>,
client_ip: Option<String>,
) -> Result<()> {
@@ -38,8 +62,12 @@ pub async fn create_device(
increment(&self.db.userid_devicelistversion, user_id.as_bytes());
self.db.userdeviceid_metadata.put(key, Json(device));
self.set_token(user_id, device_id, token, token_max_age)
.await
if let Some(token) = token {
self.set_token(user_id, device_id, token).await?;
}
Ok(())
}
/// Removes a device from a user.
@@ -97,31 +125,12 @@ pub async fn get_token(&self, user_id: &UserId, device_id: &DeviceId) -> Result<
self.db.userdeviceid_token.qry(&key).await.deserialized()
}
/// Generate a unique access token that doesn't collide with existing tokens
pub async fn generate_unique_token(&self) -> String {
loop {
let token = utils::random_string(32);
// Check for collision with existing appservice and user tokens
let (appservice, usr) = tokio::join!(
self.services.appservice.find_from_token(&token),
self.db.token_userdeviceid.get(&token)
);
if appservice.is_ok() || usr.is_ok() {
continue;
}
return token;
}
}
/// Replaces the access token of one device.
pub async fn set_token(
&self,
user_id: &UserId,
device_id: &DeviceId,
token: &str,
token_max_age: Option<Duration>,
DeviceToken { token, max_age }: DeviceToken,
) -> Result<()> {
let key = (user_id, device_id);
if self.db.userdeviceid_metadata.qry(&key).await.is_err() {
@@ -136,7 +145,7 @@ pub async fn set_token(
if self
.services
.appservice
.find_from_token(token)
.find_from_token(&token)
.await
.is_ok()
{
@@ -153,10 +162,10 @@ pub async fn set_token(
}
// Assign token to user device combination
self.db.userdeviceid_token.put_raw(key, token);
self.db.token_userdeviceid.raw_put(token, key);
self.db.userdeviceid_token.put_raw(key, &token);
self.db.token_userdeviceid.raw_put(&token, key);
if let Some(max_age) = token_max_age {
if let Some(max_age) = max_age {
let expires = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time should not be before the epoch")
+1
View File
@@ -14,6 +14,7 @@
utils::{self},
};
use database::Map;
pub use device::DeviceToken;
pub use profile::ProfileFieldChange;
use ruma::{UserId, api::error::ErrorKind, encryption::CrossSigningKey, serde::Raw};
use serde::{Deserialize, Serialize};
+1 -1
View File
@@ -155,7 +155,7 @@ async fn get_account_deeplink(
));
};
format!("device/{device_id}/delete")
format!("device/{device_id}/remove")
},
| AccountManagementAction::DeviceView => {
let Some(device_id) = query.device_id else {
+3 -17
View File
@@ -65,17 +65,17 @@ pub(super) async fn for_local_user(services: &Services, user_id: &UserId) -> Sel
}
pub(super) fn for_device(
client_metadata: Option<&ClientMetadata>,
oauth_metadata: Option<&ClientMetadata>,
display_name: Option<&str>,
) -> Self {
let avatar_src = client_metadata
let avatar_src = oauth_metadata
.and_then(|metadata| metadata.logo_uri.as_ref())
.map(|uri| uri.as_str().to_owned());
let avatar_type = if let Some(avatar_src) = avatar_src {
AvatarType::Image(avatar_src)
} else if let Some(initial) = display_name.and_then(|name| name.chars().next()) {
if client_metadata.is_some() {
if oauth_metadata.is_some() {
AvatarType::Initial(initial)
} else {
AvatarType::Initial('❖')
@@ -86,20 +86,6 @@ pub(super) fn for_device(
Self { avatar_type }
}
pub(super) fn for_client(client_metadata: &ClientMetadata) -> Self {
let avatar_type = if let Some(logo) = &client_metadata.logo_uri {
AvatarType::Image(logo.to_string())
} else if let Some(name) = &client_metadata.client_name
&& let Some(char) = name.chars().next()
{
AvatarType::Initial(char)
} else {
AvatarType::Initial('?')
};
Self { avatar_type }
}
}
#[derive(Debug, Template)]
+36 -108
View File
@@ -4,12 +4,9 @@
response::Redirect,
routing::on,
};
use conduwuit_service::oauth::{
client_metadata::ClientMetadata,
grant::{AuthorizationCodeQuery, DeviceCodeVerifyQuery, Prompt},
};
use conduwuit_service::oauth::grant::{AuthorizationCodeQuery, Prompt};
use ruma::OwnedUserId;
use serde::Deserialize;
use url::Url;
use crate::{
ROUTE_PREFIX, WebError,
@@ -17,7 +14,7 @@
pages::{
GET_POST, Result, TemplateContext,
account::register::{RegisterQuery, RequestedRegistrationFlow},
components::{Avatar, ClientScopes, UserCard},
components::{Avatar, AvatarType, ClientScopes},
},
response,
session::{LoginIntent, LoginQuery, LoginTarget, User},
@@ -25,9 +22,7 @@
};
pub(crate) fn build() -> Router<crate::State> {
Router::new()
.route("/authorization_code", on(GET_POST, route_authorization_code))
.route("/device_code", on(GET_POST, route_device_code))
Router::new().route("/authorization_code", on(GET_POST, route_authorization_code))
}
template! {
@@ -35,9 +30,12 @@ struct Grant use "grant.html.j2" {
logout_query: String,
user_id: OwnedUserId,
user_avatar: Avatar,
client_metadata: ClientMetadata,
scopes: ClientScopes,
device_code: Option<String>
client_uri: Url,
client_name: String,
client_avatar: Avatar,
policy_uri: Option<Url>,
tos_uri: Option<Url>,
scopes: ClientScopes
}
}
@@ -104,6 +102,27 @@ async fn route_authorization_code(
let scopes = query.scope.to_scopes().map_err(WebError::BadRequest)?;
let client_name = if let Some(name) = &client.client_name {
name
} else {
"Unknown application"
}
.to_owned();
let client_avatar = {
let avatar_type = if let Some(logo) = &client.logo_uri {
AvatarType::Image(logo.to_string())
} else if let Some(name) = &client.client_name
&& let Some(char) = name.chars().next()
{
AvatarType::Initial(char)
} else {
AvatarType::Initial('?')
};
Avatar { avatar_type }
};
let user_avatar = Avatar::for_local_user(&services, &user_id).await;
response!(Grant::new(
@@ -116,102 +135,11 @@ async fn route_authorization_code(
.unwrap(),
user_id,
user_avatar,
client,
client.client_uri.clone(),
client_name,
client_avatar,
client.policy_uri.clone(),
client.tos_uri.clone(),
ClientScopes { scopes },
None,
))
}
#[derive(Deserialize)]
#[serde(tag = "stage", rename_all = "snake_case")]
enum DeviceCodeForm {
Lookup {
user_code: String,
},
Confirm {
device_code: String,
},
}
template! {
struct DeviceCodeGrant use "device_code_grant.html.j2" {
user_card: UserCard,
body: DeviceCodeGrantBody
}
}
#[derive(Debug)]
enum DeviceCodeGrantBody {
Lookup {
user_code_error: bool,
},
Success,
}
async fn route_device_code(
State(services): State<crate::State>,
Extension(context): Extension<TemplateContext>,
user: User<true>,
Expect(Query(query)): Expect<Query<DeviceCodeVerifyQuery>>,
PostForm(form): PostForm<DeviceCodeForm>,
) -> Result {
let user_id = if let Some(user) = user.into_session() {
user.user_id
} else {
let next = LoginTarget::DeviceCode(query.clone());
let uri = format!(
"{}/account/login?{}",
ROUTE_PREFIX,
serde_urlencoded::to_string(LoginQuery { next: Some(next), ..Default::default() })
.unwrap()
);
return response!(Redirect::to(&uri));
};
let user_card = UserCard::for_local_user(&services, user_id.clone()).await;
match (form, query.user_code.clone()) {
| (None, Some(user_code)) | (Some(DeviceCodeForm::Lookup { user_code }), _) => {
let Some(grant_info) = services.oauth.grant_info_for_user_code(&user_code).await
else {
return response!(DeviceCodeGrant::new(
context,
user_card,
DeviceCodeGrantBody::Lookup { user_code_error: true }
));
};
let user_avatar = Avatar::for_local_user(&services, &user_id).await;
response!(Grant::new(
context,
serde_urlencoded::to_string(LoginQuery {
next: Some(LoginTarget::DeviceCode(query)),
intent: Some(LoginIntent::SwitchAccounts),
..Default::default()
})
.unwrap(),
user_id,
user_avatar,
grant_info.client_metadata,
ClientScopes { scopes: grant_info.requested_scopes },
Some(grant_info.device_code),
))
},
| (Some(DeviceCodeForm::Confirm { device_code }), _) => {
services
.oauth
.validate_device_code(user_id, &device_code)
.await
.map_err(WebError::BadRequest)?;
response!(DeviceCodeGrant::new(context, user_card, DeviceCodeGrantBody::Success))
},
| (None, None) =>
response!(DeviceCodeGrant::new(context, user_card, DeviceCodeGrantBody::Lookup {
user_code_error: false
})),
}
}
-14
View File
@@ -20,17 +20,3 @@
font-style: italic;
text-align: center;
}
.user-code {
display: flex;
flex-direction: column;
align-items: center;
input {
text-align: center;
font-weight: bold;
font-size: 200%;
padding: 0.2em;
width: 6em !important;
}
}
@@ -1,50 +0,0 @@
{% extends "_layout.html.j2" %}
{%- block head -%}
<link rel="stylesheet" href="{{ crate::ROUTE_PREFIX }}/resources/grant.css">
{%- endblock -%}
{%- block title -%}
Authorize client
{%- endblock -%}
{%- block content -%}
<div class="panel narrow">
<h1>Authorize device</h1>
{{ user_card }}
{% match body %}
{% when DeviceCodeGrantBody::Lookup { user_code_error } %}
<p>
Enter the code displayed on your device, or in the app you're signing into.
</p>
<p>
<em class="negative">Never enter a code given to you by someone else.</em>
Your homeserver administrator will never ask you to enter a code on this page.
</p>
<form method="post">
<p>
<div class="user-code">
<label for="user_code">Validation code</label>
<input
type="text"
id="user_code"
name="user_code"
maxlength="6"
spellcheck="false"
pattern="\d{6}"
title="Enter the six-digit validation code"
>
</div>
{% if user_code_error %}
<small class="error">Invalid code</small>
{% endif %}
</p>
<input type="hidden" name="stage" value="lookup">
<button type="submit">Continue</button>
</form>
{% when DeviceCodeGrantBody::Success %}
<p>Success! 🎉 You can close this tab and return to your device.</p>
{% endmatch %}
</div>
{% endblock %}
+3 -8
View File
@@ -9,7 +9,6 @@ Authorize client
{%- endblock -%}
{%- block content -%}
{% let client_name = client_metadata.client_name.as_deref().unwrap_or("Unknown application") %}
<div class="panel narrow">
<h1>Authorize {{ client_name }}</h1>
<div class="avatars">
@@ -19,17 +18,17 @@ Authorize client
<div class="separator" aria-hidden="true">
</div>
{{ Avatar::for_client(client_metadata) }}
{{ client_avatar }}
</div>
<div class="identity">
Signed in as <code>{{ user_id }}</code>. <a href="{{ crate::ROUTE_PREFIX }}/account/logout?{{ logout_query }}">Switch accounts</a>
</div>
<p>
<b>{{ client_name }}</b> (<a href="{{ client_metadata.client_uri }}">{{ client_metadata.client_uri.domain().unwrap() }}</a>) would like
<b>{{ client_name }}</b> (<a href="{{ client_uri }}">{{ client_uri.domain().unwrap() }}</a>) would like
your permission to:
{{ scopes }}
</p>
{% match (&client_metadata.policy_uri, &client_metadata.tos_uri) %}
{% match (&policy_uri, &tos_uri) %}
{% when (Some(policy_uri), Some(tos_uri)) %}
<p>
{{ client_name }}'s <a href="{{ policy_uri }}">policies</a>
@@ -50,10 +49,6 @@ Authorize client
{% endmatch %}
<form method="post">
<input type="hidden" name="stage" value="confirm">
{% if let Some(device_code) = device_code %}
<input type="hidden" name="device_code" value="{{ device_code }}">
{% endif %}
<button type="submit">Continue</button>
</form>
</div>
+1 -5
View File
@@ -9,7 +9,7 @@
http::request::Parts,
response::{IntoResponse, Redirect, Response},
};
use conduwuit_service::oauth::grant::{AuthorizationCodeQuery, DeviceCodeVerifyQuery};
use conduwuit_service::oauth::grant::AuthorizationCodeQuery;
use ruma::{OwnedUserId, UserId};
use serde::{Deserialize, Serialize};
use tower_sessions::Session;
@@ -38,7 +38,6 @@ pub(crate) enum LoginTarget {
ChangeEmail,
CrossSigningReset,
Deactivate,
DeviceCode(DeviceCodeVerifyQuery),
DeviceInfo(DevicePath),
RemoveDevice(DevicePath),
}
@@ -60,9 +59,6 @@ pub(crate) fn target_path(&self) -> String {
| Self::ChangeEmail => "account/email/change/".into(),
| Self::CrossSigningReset => "account/cross_signing_reset".into(),
| Self::Deactivate => "account/deactivate".into(),
| Self::DeviceCode(code) =>
format!("oauth2/grant/device_code?{}", serde_urlencoded::to_string(code).unwrap())
.into(),
| Self::DeviceInfo(path) => format!("account/device/{}/", path.device).into(),
| Self::RemoveDevice(path) => format!("account/device/{}/remove", path.device).into(),
};