[Fix] User deletion & migration
This commit is contained in:
parent
c447e6e863
commit
19d97e4555
14 changed files with 456 additions and 148 deletions
228
Cargo.lock
generated
228
Cargo.lock
generated
|
|
@ -56,7 +56,7 @@ dependencies = [
|
||||||
"nom",
|
"nom",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -117,9 +117,9 @@ checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-rs"
|
name = "aws-lc-rs"
|
||||||
version = "1.17.3"
|
version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1"
|
checksum = "ce2b2dcc879c3bae0d371e77c99f2238400ef24ec001394befa67b6e543add9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-sys",
|
"aws-lc-sys",
|
||||||
"untrusted 0.7.1",
|
"untrusted 0.7.1",
|
||||||
|
|
@ -128,9 +128,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aws-lc-sys"
|
name = "aws-lc-sys"
|
||||||
version = "0.43.0"
|
version = "0.44.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c"
|
checksum = "f09fae7be8bb3174e05c6afdb34199e6dc0c7c04ba9fa237b1967adfbde27483"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
"cmake",
|
"cmake",
|
||||||
|
|
@ -145,6 +145,12 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64ct"
|
name = "base64ct"
|
||||||
version = "1.8.3"
|
version = "1.8.3"
|
||||||
|
|
@ -217,9 +223,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.4.0"
|
version = "1.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9"
|
checksum = "9066c49992464636f92905fa096ec58baaa4d57ec19a5c096c68d3e25ef3d136"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"find-msvc-tools",
|
"find-msvc-tools",
|
||||||
"jobserver",
|
"jobserver",
|
||||||
|
|
@ -467,8 +473,23 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.2.17",
|
"cpufeatures 0.2.17",
|
||||||
"curve25519-dalek-derive",
|
"curve25519-dalek-derive",
|
||||||
"digest 0.10.7",
|
"fiat-crypto 0.2.9",
|
||||||
"fiat-crypto",
|
"rustc_version",
|
||||||
|
"subtle",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "curve25519-dalek"
|
||||||
|
version = "5.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures 0.3.0",
|
||||||
|
"curve25519-dalek-derive",
|
||||||
|
"digest 0.11.3",
|
||||||
|
"fiat-crypto 0.3.0",
|
||||||
"rustc_version",
|
"rustc_version",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
|
@ -501,9 +522,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "data-encoding"
|
name = "data-encoding"
|
||||||
version = "2.11.0"
|
version = "2.11.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "deflate64"
|
name = "deflate64"
|
||||||
|
|
@ -518,7 +539,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid 0.9.6",
|
"const-oid 0.9.6",
|
||||||
"pem-rfc7468",
|
"pem-rfc7468 0.7.0",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -529,6 +550,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
|
checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"const-oid 0.10.2",
|
"const-oid 0.10.2",
|
||||||
|
"pem-rfc7468 1.0.0",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -608,24 +630,25 @@ checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519"
|
name = "ed25519"
|
||||||
version = "2.2.3"
|
version = "3.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53"
|
checksum = "29fcf32e6c73d1079f83ab4d782de2d81620346a5f38c6237a86a22f8368980a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pkcs8 0.10.2",
|
"pkcs8 0.11.0",
|
||||||
"signature 2.2.0",
|
"signature 3.0.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ed25519-dalek"
|
name = "ed25519-dalek"
|
||||||
version = "2.2.0"
|
version = "3.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9"
|
checksum = "6ebaa1a2bf1290ab3bfe5a7b771d050ebffab2711c19a81691c683a5144a25de"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek 5.0.0",
|
||||||
"ed25519",
|
"ed25519",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2 0.10.9",
|
"sha2 0.11.0",
|
||||||
|
"signature 3.0.0",
|
||||||
"subtle",
|
"subtle",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
@ -710,10 +733,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "find-msvc-tools"
|
name = "fiat-crypto"
|
||||||
version = "0.1.9"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
|
checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
|
|
@ -1077,9 +1106,9 @@ checksum = "1a9fcbcc408c5526c3ab80d534e5c86e7967c1fb7aa0a8c76abd1edc27deb877"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.4.2"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425"
|
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
|
@ -1122,9 +1151,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hybrid-array"
|
name = "hybrid-array"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c"
|
checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ctutils",
|
"ctutils",
|
||||||
"typenum",
|
"typenum",
|
||||||
|
|
@ -1173,7 +1202,7 @@ version = "0.1.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
@ -1325,9 +1354,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.12.0"
|
version = "2.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
|
checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
|
|
@ -1347,7 +1376,7 @@ dependencies = [
|
||||||
"jni-sys",
|
"jni-sys",
|
||||||
"log",
|
"log",
|
||||||
"simd_cesu8",
|
"simd_cesu8",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
@ -1416,9 +1445,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "keccak"
|
name = "keccak"
|
||||||
version = "0.2.0"
|
version = "0.2.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa"
|
checksum = "ffd9697dc4a9a62e2da93389f34400b77a28f0287711263cabb203b3ccb9c0e4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures 0.3.0",
|
"cpufeatures 0.3.0",
|
||||||
|
|
@ -1453,9 +1482,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libredox"
|
name = "libredox"
|
||||||
version = "0.1.18"
|
version = "0.1.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652"
|
checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"libc",
|
"libc",
|
||||||
|
|
@ -1614,7 +1643,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp"
|
name = "mtp"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-client",
|
"mtp-client",
|
||||||
"mtp-codec",
|
"mtp-codec",
|
||||||
|
|
@ -1630,7 +1659,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-client"
|
name = "mtp-client"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-codec",
|
"mtp-codec",
|
||||||
"mtp-common",
|
"mtp-common",
|
||||||
|
|
@ -1643,9 +1672,9 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-codec"
|
name = "mtp-codec"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.23.1",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"mtp-common",
|
"mtp-common",
|
||||||
"mtp-crypto",
|
"mtp-crypto",
|
||||||
|
|
@ -1656,20 +1685,20 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-common"
|
name = "mtp-common"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quinn",
|
"quinn",
|
||||||
"rustls",
|
"rustls",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"wtransport",
|
"wtransport",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-crypto"
|
name = "mtp-crypto"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.23.1",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"ed25519-dalek",
|
"ed25519-dalek",
|
||||||
"getrandom 0.4.3",
|
"getrandom 0.4.3",
|
||||||
|
|
@ -1681,7 +1710,7 @@ dependencies = [
|
||||||
"rustls",
|
"rustls",
|
||||||
"serde",
|
"serde",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
"thiserror 1.0.69",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
@ -1689,24 +1718,24 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-files"
|
name = "mtp-files"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-crypto",
|
"mtp-crypto",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
"thiserror 1.0.69",
|
"thiserror",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-host"
|
name = "mtp-host"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"mtp-codec",
|
"mtp-codec",
|
||||||
"mtp-common",
|
"mtp-common",
|
||||||
"mtp-crypto",
|
"mtp-crypto",
|
||||||
"mtp-transport",
|
"mtp-transport",
|
||||||
"rand 0.8.7",
|
"rand 0.10.2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"wtransport",
|
"wtransport",
|
||||||
|
|
@ -1715,7 +1744,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-transport"
|
name = "mtp-transport"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"mtp-codec",
|
"mtp-codec",
|
||||||
|
|
@ -1733,7 +1762,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-type-map"
|
name = "mtp-type-map"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
|
|
@ -1742,7 +1771,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mtp-webserver"
|
name = "mtp-webserver"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "git+https://git.methanium.net/methanium/mtp#a692bed326dbfc8eac1a05825f4a287cbab6fd3e"
|
source = "git+https://git.methanium.net/methanium/mtp#b067614a684eb1856bc5db7b3fd82148c036ce6b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -1761,7 +1790,7 @@ dependencies = [
|
||||||
"quinn",
|
"quinn",
|
||||||
"rand 0.10.2",
|
"rand 0.10.2",
|
||||||
"rustls",
|
"rustls",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
|
@ -1859,7 +1888,7 @@ name = "omega"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"dotenv",
|
"dotenv",
|
||||||
|
|
@ -1874,7 +1903,7 @@ dependencies = [
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"strum",
|
"strum",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"uuid",
|
"uuid",
|
||||||
"zip",
|
"zip",
|
||||||
|
|
@ -1943,7 +1972,7 @@ version = "3.0.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1956,6 +1985,15 @@ dependencies = [
|
||||||
"base64ct",
|
"base64ct",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem-rfc7468"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9"
|
||||||
|
dependencies = [
|
||||||
|
"base64ct",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.2"
|
version = "2.3.2"
|
||||||
|
|
@ -2082,7 +2120,7 @@ dependencies = [
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"rustls",
|
"rustls",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
|
|
@ -2107,7 +2145,7 @@ dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"rustls-platform-verifier",
|
"rustls-platform-verifier",
|
||||||
"slab",
|
"slab",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tinyvec",
|
"tinyvec",
|
||||||
"tracing",
|
"tracing",
|
||||||
"web-time",
|
"web-time",
|
||||||
|
|
@ -2237,7 +2275,7 @@ version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"encoding_rs",
|
"encoding_rs",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
|
@ -2329,9 +2367,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls"
|
name = "rustls"
|
||||||
version = "0.23.42"
|
version = "0.23.43"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138"
|
checksum = "0283386ce02abc0151e1761d08802dfe86c173b0b494af5cbc086574e453da06"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aws-lc-rs",
|
"aws-lc-rs",
|
||||||
"log",
|
"log",
|
||||||
|
|
@ -2598,7 +2636,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
|
checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"digest 0.11.3",
|
"digest 0.11.3",
|
||||||
"keccak 0.2.0",
|
"keccak 0.2.1",
|
||||||
"sponge-cursor",
|
"sponge-cursor",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2745,7 +2783,7 @@ version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"crc",
|
"crc",
|
||||||
"crossbeam-queue",
|
"crossbeam-queue",
|
||||||
|
|
@ -2766,7 +2804,7 @@ dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"sha2 0.10.9",
|
"sha2 0.10.9",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -2818,7 +2856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
|
@ -2848,7 +2886,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
@ -2860,7 +2898,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atoi",
|
"atoi",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"crc",
|
"crc",
|
||||||
|
|
@ -2885,7 +2923,7 @@ dependencies = [
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"stringprep",
|
"stringprep",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"whoami",
|
"whoami",
|
||||||
]
|
]
|
||||||
|
|
@ -2909,7 +2947,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sqlx-core",
|
"sqlx-core",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
@ -3018,33 +3056,13 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
|
|
||||||
dependencies = [
|
|
||||||
"thiserror-impl 1.0.69",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.19"
|
version = "2.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
|
checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl 2.0.19",
|
"thiserror-impl",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thiserror-impl"
|
|
||||||
version = "1.0.69"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn 2.0.119",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3060,9 +3078,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.54"
|
version = "0.3.55"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244"
|
checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|
@ -3133,13 +3151,13 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-macros"
|
name = "tokio-macros"
|
||||||
version = "2.7.1"
|
version = "2.7.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba"
|
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.119",
|
"syn 3.0.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3515,7 +3533,7 @@ version = "0.1.11"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.48.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -3728,7 +3746,7 @@ dependencies = [
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"sha2 0.11.0",
|
"sha2 0.11.0",
|
||||||
"socket2",
|
"socket2",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
|
@ -3745,7 +3763,7 @@ checksum = "d5867c629e4252f7439d82315923daaf27f4fa442410d51b78ab93ef4c432a11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"httlib-huffman",
|
"httlib-huffman",
|
||||||
"octets",
|
"octets",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3755,7 +3773,7 @@ version = "2.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"curve25519-dalek",
|
"curve25519-dalek 4.1.3",
|
||||||
"rand_core 0.6.4",
|
"rand_core 0.6.4",
|
||||||
"serde",
|
"serde",
|
||||||
"zeroize",
|
"zeroize",
|
||||||
|
|
@ -3776,7 +3794,7 @@ dependencies = [
|
||||||
"oid-registry",
|
"oid-registry",
|
||||||
"ring",
|
"ring",
|
||||||
"rusticata-macros",
|
"rusticata-macros",
|
||||||
"thiserror 2.0.19",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -3815,18 +3833,18 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy"
|
name = "zerocopy"
|
||||||
version = "0.8.55"
|
version = "0.8.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
|
checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zerocopy-derive",
|
"zerocopy-derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zerocopy-derive"
|
name = "zerocopy-derive"
|
||||||
version = "0.8.55"
|
version = "0.8.56"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
|
checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -3936,9 +3954,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zlib-rs"
|
name = "zlib-rs"
|
||||||
version = "0.6.6"
|
version = "0.6.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b142a20ec14a91d5bc708c1dc21b080c550113d8aa77afa29635673a65dd02c5"
|
checksum = "34b31d188d9d685a4f9c7b46d6e36631b07058d2cfe190267adce54dc230bf12"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zmij"
|
name = "zmij"
|
||||||
|
|
|
||||||
11
migrations/005_nullable_user_iota.sql
Normal file
11
migrations/005_nullable_user_iota.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
ALTER TABLE users
|
||||||
|
DROP FOREIGN KEY fk_users_iota;
|
||||||
|
|
||||||
|
ALTER TABLE users
|
||||||
|
MODIFY iota_id BIGINT NULL;
|
||||||
|
|
||||||
|
ALTER TABLE users
|
||||||
|
ADD CONSTRAINT fk_users_iota
|
||||||
|
FOREIGN KEY (iota_id)
|
||||||
|
REFERENCES iotas (id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
8
migrations/006_pending_iota_user_erasure.sql
Normal file
8
migrations/006_pending_iota_user_erasure.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
CREATE TABLE pending_iota_user_erasure (
|
||||||
|
user_id BIGINT NOT NULL,
|
||||||
|
iota_id BIGINT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (user_id, iota_id),
|
||||||
|
CONSTRAINT fk_pending_iota_user_erasure_iota
|
||||||
|
FOREIGN KEY (iota_id) REFERENCES iotas(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
@ -28,7 +28,7 @@ pub struct UserResponse {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub user_id: i64,
|
pub user_id: i64,
|
||||||
pub iota_id: i64,
|
pub iota_id: Option<i64>,
|
||||||
pub sub_level: i32,
|
pub sub_level: i32,
|
||||||
pub sub_end: i64,
|
pub sub_end: i64,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
@ -47,7 +47,7 @@ pub struct UsernameResponse {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub public_key: String,
|
pub public_key: String,
|
||||||
pub user_id: i64,
|
pub user_id: i64,
|
||||||
pub iota_id: i64,
|
pub iota_id: Option<i64>,
|
||||||
pub sub_level: i32,
|
pub sub_level: i32,
|
||||||
pub sub_end: i64,
|
pub sub_end: i64,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ const USER_COLUMNS: &str = "SELECT id, iota_id, username, display, status, prese
|
||||||
#[derive(FromRow)]
|
#[derive(FromRow)]
|
||||||
struct UserRow {
|
struct UserRow {
|
||||||
id: i64,
|
id: i64,
|
||||||
iota_id: i64,
|
iota_id: Option<i64>,
|
||||||
username: Vec<u8>,
|
username: Vec<u8>,
|
||||||
display: Option<Vec<u8>>,
|
display: Option<Vec<u8>>,
|
||||||
status: Option<Vec<u8>>,
|
status: Option<Vec<u8>>,
|
||||||
|
|
@ -124,7 +124,7 @@ impl TryFrom<UserRow> for User {
|
||||||
|value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error)));
|
|value| String::from_utf8(value).map_err(|error| sqlx::Error::Decode(Box::new(error)));
|
||||||
Ok(User {
|
Ok(User {
|
||||||
id: row.id.into(),
|
id: row.id.into(),
|
||||||
iota_id: row.iota_id.into(),
|
iota_id: row.iota_id.map(IotaId::from),
|
||||||
username: decode(row.username)?,
|
username: decode(row.username)?,
|
||||||
display: row.display.map(decode).transpose()?,
|
display: row.display.map(decode).transpose()?,
|
||||||
status: row.status.map(decode).transpose()?,
|
status: row.status.map(decode).transpose()?,
|
||||||
|
|
@ -344,9 +344,9 @@ pub async fn change_presence_preference(id: UserId, value: String) -> Result<()>
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn change_iota_id(id: UserId, value: IotaId) -> Result<()> {
|
pub async fn change_iota_id(id: UserId, value: Option<IotaId>) -> Result<()> {
|
||||||
sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?")
|
sqlx::query("UPDATE users SET iota_id = ? WHERE id = ?")
|
||||||
.bind(value.0)
|
.bind(value.map(|id| id.0))
|
||||||
.bind(id.0)
|
.bind(id.0)
|
||||||
.execute(&pool().await?)
|
.execute(&pool().await?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
@ -368,6 +368,42 @@ pub async fn delete_user(id: UserId) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Delete the central identity while retaining a durable instruction for the
|
||||||
|
/// last hosting Iota. The pending row is intentionally independent of users:
|
||||||
|
/// it must outlive the account row.
|
||||||
|
pub async fn delete_user_with_pending_erasure(id: UserId) -> Result<Option<IotaId>> {
|
||||||
|
let mut tx = pool().await?.begin().await?;
|
||||||
|
let row = sqlx::query("SELECT iota_id FROM users WHERE id = ? FOR UPDATE")
|
||||||
|
.bind(id.0)
|
||||||
|
.fetch_optional(&mut *tx)
|
||||||
|
.await?
|
||||||
|
.ok_or(OmegaError::NotFound)?;
|
||||||
|
let iota_id: Option<i64> = row.get("iota_id");
|
||||||
|
if let Some(iota_id) = iota_id {
|
||||||
|
sqlx::query("INSERT IGNORE INTO pending_iota_user_erasure (user_id, iota_id) VALUES (?, ?)")
|
||||||
|
.bind(id.0)
|
||||||
|
.bind(iota_id)
|
||||||
|
.execute(&mut *tx)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
sqlx::query("DELETE FROM registration_leases WHERE user_id = ?").bind(id.0).execute(&mut *tx).await?;
|
||||||
|
sqlx::query("DELETE FROM users WHERE id = ?").bind(id.0).execute(&mut *tx).await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
Ok(iota_id.map(IotaId::from))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn pending_erasures_for_iota(iota_id: IotaId) -> Result<Vec<UserId>> {
|
||||||
|
let rows = sqlx::query("SELECT user_id FROM pending_iota_user_erasure WHERE iota_id = ?")
|
||||||
|
.bind(iota_id.0).fetch_all(&pool().await?).await?;
|
||||||
|
Ok(rows.into_iter().map(|row| UserId::from(row.get::<i64, _>("user_id"))).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn acknowledge_pending_erasure(user_id: UserId, iota_id: IotaId) -> Result<bool> {
|
||||||
|
let result = sqlx::query("DELETE FROM pending_iota_user_erasure WHERE user_id = ? AND iota_id = ?")
|
||||||
|
.bind(user_id.0).bind(iota_id.0).execute(&pool().await?).await?;
|
||||||
|
Ok(result.rows_affected() == 1)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> {
|
pub async fn change_keys(id: UserId, public_key: PublicKeyBundle) -> Result<()> {
|
||||||
sqlx::query("UPDATE users SET public_key = ? WHERE id = ?")
|
sqlx::query("UPDATE users SET public_key = ? WHERE id = ?")
|
||||||
.bind(public_key.as_bytes())
|
.bind(public_key.as_bytes())
|
||||||
|
|
@ -438,7 +474,7 @@ pub async fn register_complete_user(
|
||||||
.map_err(OmegaError::from)?
|
.map_err(OmegaError::from)?
|
||||||
{
|
{
|
||||||
Some(existing)
|
Some(existing)
|
||||||
if existing.iota_id == iota_id
|
if existing.iota_id == Some(iota_id)
|
||||||
&& existing.username == username
|
&& existing.username == username
|
||||||
&& existing.public_key.as_bytes() == public_key.as_bytes()
|
&& existing.public_key.as_bytes() == public_key.as_bytes()
|
||||||
&& existing.token == token =>
|
&& existing.token == token =>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ use mtp::crypto::PublicKeyBundle;
|
||||||
#[derive(Clone, Debug, serde::Serialize)]
|
#[derive(Clone, Debug, serde::Serialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
pub iota_id: IotaId,
|
pub iota_id: Option<IotaId>,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub display: Option<String>,
|
pub display: Option<String>,
|
||||||
pub status: Option<String>,
|
pub status: Option<String>,
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ fn user_response(user: crate::models::User) -> UserResponse {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
public_key: user.public_key.to_base64(),
|
public_key: user.public_key.to_base64(),
|
||||||
user_id: user.id.0,
|
user_id: user.id.0,
|
||||||
iota_id: user.iota_id.0,
|
iota_id: user.iota_id.map(|id| id.0),
|
||||||
sub_level: user.sub_level,
|
sub_level: user.sub_level,
|
||||||
sub_end: user.sub_end,
|
sub_end: user.sub_end,
|
||||||
display: user.display,
|
display: user.display,
|
||||||
|
|
@ -83,7 +83,15 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
||||||
omikron_id
|
omikron_id
|
||||||
} else {
|
} else {
|
||||||
let user = get_by_user_id(UserId::from(id)).await?;
|
let user = get_by_user_id(UserId::from(id)).await?;
|
||||||
get_iota_primary_omikron_connection(user.iota_id.0).ok_or(OmegaError::NotFound)?
|
match user.iota_id {
|
||||||
|
Some(iota_id) => get_iota_primary_omikron_connection(iota_id.0),
|
||||||
|
None => get_random_omikron()
|
||||||
|
.await
|
||||||
|
.map_err(|_| OmegaError::NotFound)?
|
||||||
|
.get_omikron_id()
|
||||||
|
.await,
|
||||||
|
}
|
||||||
|
.ok_or(OmegaError::NotFound)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Database rows describe registered Omikrons. The public discovery
|
// Database rows describe registered Omikrons. The public discovery
|
||||||
|
|
@ -150,7 +158,7 @@ async fn route(path_parts: &[&str]) -> Result<(StatusCode, String)> {
|
||||||
username: user.username,
|
username: user.username,
|
||||||
public_key: user.public_key.to_base64(),
|
public_key: user.public_key.to_base64(),
|
||||||
user_id: user.id.0,
|
user_id: user.id.0,
|
||||||
iota_id: user.iota_id.0,
|
iota_id: user.iota_id.map(|id| id.0),
|
||||||
sub_level: user.sub_level,
|
sub_level: user.sub_level,
|
||||||
sub_end: user.sub_end,
|
sub_end: user.sub_end,
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
29
src/state.rs
29
src/state.rs
|
|
@ -1,18 +1,47 @@
|
||||||
use crate::sql::user_online_tracker::PresenceTracker;
|
use crate::sql::user_online_tracker::PresenceTracker;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use dashmap::DashMap;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum AccountChallengeOperation { Attach, Delete }
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct AccountChallenge {
|
||||||
|
pub operation: AccountChallengeOperation,
|
||||||
|
pub user_id: i64,
|
||||||
|
pub requester_iota_id: i64,
|
||||||
|
pub nonce: u64,
|
||||||
|
pub created_at: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct OmegaState {
|
pub struct OmegaState {
|
||||||
pub presence: Arc<PresenceTracker>,
|
pub presence: Arc<PresenceTracker>,
|
||||||
|
challenges: DashMap<(AccountChallengeOperation, i64, i64), AccountChallenge>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OmegaState {
|
impl Default for OmegaState {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
presence: Arc::new(PresenceTracker::default()),
|
presence: Arc::new(PresenceTracker::default()),
|
||||||
|
challenges: DashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl OmegaState {
|
||||||
|
pub fn issue_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64) -> u64 {
|
||||||
|
let nonce = rand::random::<u64>();
|
||||||
|
self.challenges.insert((operation, user_id, requester_iota_id), AccountChallenge { operation, user_id, requester_iota_id, nonce, created_at: Instant::now() });
|
||||||
|
nonce
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn consume_challenge(&self, operation: AccountChallengeOperation, user_id: i64, requester_iota_id: i64, nonce: u64) -> bool {
|
||||||
|
self.challenges.remove(&(operation, user_id, requester_iota_id)).is_some_and(|(_, value)|
|
||||||
|
value.nonce == nonce && value.created_at.elapsed() <= Duration::from_secs(120))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OmegaState {
|
impl OmegaState {
|
||||||
pub fn new() -> Arc<Self> {
|
pub fn new() -> Arc<Self> {
|
||||||
Arc::new(Self::default())
|
Arc::new(Self::default())
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@ use super::super::omikron_connection::{OmikronConnection, OmikronResult};
|
||||||
use crate::{
|
use crate::{
|
||||||
db::{iota_repo, user_repo},
|
db::{iota_repo, user_repo},
|
||||||
models::{IotaId, UserId},
|
models::{IotaId, UserId},
|
||||||
|
state::AccountChallengeOperation,
|
||||||
};
|
};
|
||||||
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
use mtp::{codec::{CommunicationType, CommunicationValue, DataType, DataValue}, crypto::{verify_ed25519, verify_ml_dsa}};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
async fn delete(
|
async fn delete(
|
||||||
|
|
@ -23,12 +24,8 @@ pub async fn user(
|
||||||
connection: Arc<OmikronConnection>,
|
connection: Arc<OmikronConnection>,
|
||||||
value: CommunicationValue,
|
value: CommunicationValue,
|
||||||
) -> OmikronResult<()> {
|
) -> OmikronResult<()> {
|
||||||
delete(
|
let user_id = UserId::from(value.get_sender() as i64);
|
||||||
connection,
|
complete_delete(connection, value, user_id).await
|
||||||
value.clone(),
|
|
||||||
user_repo::delete_user(UserId::from(value.get_sender() as i64)),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
pub async fn iota(
|
pub async fn iota(
|
||||||
connection: Arc<OmikronConnection>,
|
connection: Arc<OmikronConnection>,
|
||||||
|
|
@ -41,3 +38,157 @@ pub async fn iota(
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn release_from_iota(
|
||||||
|
connection: Arc<OmikronConnection>,
|
||||||
|
value: CommunicationValue,
|
||||||
|
) -> OmikronResult<()> {
|
||||||
|
let Some(user_id) = value
|
||||||
|
.get_data(DataType::UserId)
|
||||||
|
.as_signed_number()
|
||||||
|
.and_then(|id| i64::try_from(id).ok())
|
||||||
|
.filter(|id| *id > 0)
|
||||||
|
else {
|
||||||
|
return connection
|
||||||
|
.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId)
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
let requester = IotaId::from(value.get_sender() as i64);
|
||||||
|
let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else {
|
||||||
|
return connection
|
||||||
|
.send_error_response(value.get_id(), CommunicationType::ErrorNotFound)
|
||||||
|
.await;
|
||||||
|
};
|
||||||
|
if user.iota_id != Some(requester) {
|
||||||
|
return connection
|
||||||
|
.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
let previous_iota = user.iota_id;
|
||||||
|
match user_repo::change_iota_id(user.id, None).await {
|
||||||
|
Ok(()) => {
|
||||||
|
if let Some(iota) = previous_iota { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; }
|
||||||
|
connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await
|
||||||
|
},
|
||||||
|
Err(error) => connection
|
||||||
|
.send(&CommunicationValue::new(CommunicationType::ErrorInternal)
|
||||||
|
.with_id(value.get_id())
|
||||||
|
.add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string())))
|
||||||
|
.await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lifecycle_payload(domain: &[u8], user_id: i64, iota_id: i64, nonce: u64) -> Vec<u8> {
|
||||||
|
let mut payload = Vec::with_capacity(domain.len() + 32);
|
||||||
|
payload.extend_from_slice(domain);
|
||||||
|
payload.extend_from_slice(&user_id.to_be_bytes());
|
||||||
|
payload.extend_from_slice(&iota_id.to_be_bytes());
|
||||||
|
payload.extend_from_slice(&nonce.to_be_bytes());
|
||||||
|
payload
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn attach_begin(connection: Arc<OmikronConnection>, value: CommunicationValue) -> OmikronResult<()> {
|
||||||
|
let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else {
|
||||||
|
return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await;
|
||||||
|
};
|
||||||
|
if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() {
|
||||||
|
return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await;
|
||||||
|
}
|
||||||
|
let requester = value.get_sender() as i64;
|
||||||
|
let nonce = connection.state().issue_challenge(AccountChallengeOperation::Attach, user_id, requester);
|
||||||
|
connection.send(&CommunicationValue::new(CommunicationType::AttachUserChallenge).with_id(value.get_id())
|
||||||
|
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
|
||||||
|
.add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn attach_complete(connection: Arc<OmikronConnection>, value: CommunicationValue) -> OmikronResult<()> {
|
||||||
|
let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; };
|
||||||
|
let requester = value.get_sender() as i64;
|
||||||
|
let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; };
|
||||||
|
let signature = value.get_data(DataType::Signature).as_bytes();
|
||||||
|
let pq_signature = value.get_data(DataType::PqSignature).as_bytes();
|
||||||
|
let (Some(signature), Some(pq_signature)) = (signature, pq_signature) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; };
|
||||||
|
if !connection.state().consume_challenge(AccountChallengeOperation::Attach, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }
|
||||||
|
let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; };
|
||||||
|
let payload = lifecycle_payload(b"tensamin:user-attach:v1\0", user_id, requester, nonce);
|
||||||
|
if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; }
|
||||||
|
let previous_iota = user.iota_id;
|
||||||
|
match user_repo::change_iota_id(user.id, Some(IotaId::from(requester))).await {
|
||||||
|
Ok(()) => {
|
||||||
|
if let Some(iota) = previous_iota.filter(|id| id.0 != requester) { crate::transport::omikron_manager::publish_iota_user_snapshot(iota.0).await; }
|
||||||
|
crate::transport::omikron_manager::publish_iota_user_snapshot(requester).await;
|
||||||
|
connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await
|
||||||
|
},
|
||||||
|
Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn complete_delete(connection: Arc<OmikronConnection>, value: CommunicationValue, user_id: UserId) -> OmikronResult<()> {
|
||||||
|
match user_repo::delete_user_with_pending_erasure(user_id).await {
|
||||||
|
Ok(iota_id) => {
|
||||||
|
let cleanup_pending = iota_id.is_some();
|
||||||
|
if let Some(iota_id) = iota_id {
|
||||||
|
crate::transport::omikron_manager::publish_iota_user_snapshot(iota_id.0).await;
|
||||||
|
crate::transport::omikron_manager::deliver_pending_erasures(iota_id.0).await;
|
||||||
|
}
|
||||||
|
connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())
|
||||||
|
.add_typed_default(DataType::CleanupPending, DataValue::Bool(cleanup_pending))).await
|
||||||
|
}
|
||||||
|
Err(crate::error::OmegaError::NotFound) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await,
|
||||||
|
Err(error) => connection.send(&CommunicationValue::new(CommunicationType::ErrorInternal).with_id(value.get_id()).add_typed_default(DataType::ErrorType, DataValue::Str(error.to_string()))).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_credential_begin(connection: Arc<OmikronConnection>, value: CommunicationValue) -> OmikronResult<()> {
|
||||||
|
let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else {
|
||||||
|
return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await;
|
||||||
|
};
|
||||||
|
if user_repo::get_by_user_id(UserId::from(user_id)).await.is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; }
|
||||||
|
let requester = value.get_sender() as i64;
|
||||||
|
let nonce = connection.state().issue_challenge(AccountChallengeOperation::Delete, user_id, requester);
|
||||||
|
connection.send(&CommunicationValue::new(CommunicationType::DeleteUserCredentialChallenge).with_id(value.get_id())
|
||||||
|
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.into()))
|
||||||
|
.add_typed_default(DataType::ServerNonce, DataValue::SignedNumber(nonce.into()))).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_credential_complete(connection: Arc<OmikronConnection>, value: CommunicationValue) -> OmikronResult<()> {
|
||||||
|
let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; };
|
||||||
|
let requester = value.get_sender() as i64;
|
||||||
|
let Some(nonce) = value.get_data(DataType::ServerNonce).as_signed_number().and_then(|v| u64::try_from(v).ok()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; };
|
||||||
|
let (Some(signature), Some(pq_signature)) = (value.get_data(DataType::Signature).as_bytes(), value.get_data(DataType::PqSignature).as_bytes()) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; };
|
||||||
|
if !connection.state().consume_challenge(AccountChallengeOperation::Delete, user_id, requester, nonce) { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidChallenge).await; }
|
||||||
|
let Ok(user) = user_repo::get_by_user_id(UserId::from(user_id)).await else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotFound).await; };
|
||||||
|
let payload = lifecycle_payload(b"tensamin:user-delete:v1\0", user_id, requester, nonce);
|
||||||
|
if verify_ed25519(&user.public_key.sig_cl_public_key, &payload, &signature).is_err() || verify_ml_dsa(&user.public_key.sig_pq_public_key, &payload, &pq_signature).is_err() { return connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await; }
|
||||||
|
complete_delete(connection, value, user.id).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn erase_hosted_user_data_ack(connection: Arc<OmikronConnection>, value: CommunicationValue) -> OmikronResult<()> {
|
||||||
|
let Some(user_id) = value.get_data(DataType::UserId).as_signed_number().and_then(|v| i64::try_from(v).ok()).filter(|v| *v > 0) else { return connection.send_error_response(value.get_id(), CommunicationType::ErrorInvalidUserId).await; };
|
||||||
|
let iota_id = IotaId::from(value.get_sender() as i64);
|
||||||
|
match user_repo::acknowledge_pending_erasure(UserId::from(user_id), iota_id).await {
|
||||||
|
Ok(true) => connection.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id())).await,
|
||||||
|
Ok(false) => connection.send_error_response(value.get_id(), CommunicationType::ErrorNotAuthenticated).await,
|
||||||
|
Err(_) => connection.send_error_response(value.get_id(), CommunicationType::ErrorInternal).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// New lifecycle operation names are intentionally fail-closed until their
|
||||||
|
/// proof and durable-erasure handlers are enabled. This explicit dispatch
|
||||||
|
/// prevents either a bare Iota request or the legacy DeleteUser path from
|
||||||
|
/// acquiring account-deletion authority during a staged rollout.
|
||||||
|
pub async fn lifecycle_unavailable(
|
||||||
|
connection: Arc<OmikronConnection>,
|
||||||
|
value: CommunicationValue,
|
||||||
|
) -> OmikronResult<()> {
|
||||||
|
connection
|
||||||
|
.send(
|
||||||
|
&CommunicationValue::new(CommunicationType::ErrorNotAuthenticated)
|
||||||
|
.with_id(value.get_id())
|
||||||
|
.add_typed_default(
|
||||||
|
DataType::ErrorType,
|
||||||
|
DataValue::Str("user lifecycle proof handler is not enabled".into()),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ fn states_for_users(state: &OmegaState, users: &[crate::models::User]) -> HashMa
|
||||||
user.id.0,
|
user.id.0,
|
||||||
state
|
state
|
||||||
.presence
|
.presence
|
||||||
.resolve_public_state(user.id.0, user.iota_id.0),
|
.resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default()),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
|
|
@ -69,7 +69,7 @@ fn changed_states(
|
||||||
.filter_map(|user| {
|
.filter_map(|user| {
|
||||||
let after = state
|
let after = state
|
||||||
.presence
|
.presence
|
||||||
.resolve_public_state(user.id.0, user.iota_id.0);
|
.resolve_public_state(user.id.0, user.iota_id.map(|id| id.0).unwrap_or_default());
|
||||||
(before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after))
|
(before.get(&user.id.0) != Some(&after)).then_some((user.id.0, after))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
@ -275,7 +275,7 @@ pub async fn user_connected(
|
||||||
Ok(preferences) => preferences,
|
Ok(preferences) => preferences,
|
||||||
Err(error) => return Err(error.into()),
|
Err(error) => return Err(error.into()),
|
||||||
};
|
};
|
||||||
if user.iota_id.0 != iota_id || !state.presence.has_iota_route(iota_id) {
|
if user.iota_id.map(|id| id.0) != Some(iota_id) || !state.presence.has_iota_route(iota_id) {
|
||||||
return connection
|
return connection
|
||||||
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
.send_error_response(value.get_id(), CommunicationType::ErrorNoIota)
|
||||||
.await;
|
.await;
|
||||||
|
|
@ -497,6 +497,7 @@ pub async fn iota_connected(
|
||||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()))
|
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()))
|
||||||
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
|
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
|
||||||
connection.clone().send(&response).await?;
|
connection.clone().send(&response).await?;
|
||||||
|
crate::transport::omikron_manager::deliver_pending_erasures(iota_id).await;
|
||||||
publish_changed_states(&state, &before, &users).await;
|
publish_changed_states(&state, &before, &users).await;
|
||||||
connection
|
connection
|
||||||
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
.send(&CommunicationValue::new(CommunicationType::Success).with_id(value.get_id()))
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,7 @@ pub async fn get(
|
||||||
};
|
};
|
||||||
let status = state
|
let status = state
|
||||||
.presence
|
.presence
|
||||||
.resolve_public_state(user_id, user.iota_id.0)
|
.resolve_public_state(user_id, user.iota_id.map(|id| id.0).unwrap_or_default())
|
||||||
.to_string();
|
.to_string();
|
||||||
let mut map = Vec::new();
|
let mut map = Vec::new();
|
||||||
if let Some(kind) = DataType::UserId.try_to_id(&tm) {
|
if let Some(kind) = DataType::UserId.try_to_id(&tm) {
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ pub async fn get_user(
|
||||||
.await;
|
.await;
|
||||||
};
|
};
|
||||||
let id = user.id.0;
|
let id = user.id.0;
|
||||||
let iota_id = user.iota_id.0;
|
let iota_id = user.iota_id.map(|id| id.0);
|
||||||
let username = user.username.clone();
|
let username = user.username.clone();
|
||||||
let display = user
|
let display = user
|
||||||
.display
|
.display
|
||||||
|
|
@ -57,7 +57,6 @@ pub async fn get_user(
|
||||||
DataValue::Str(user.public_key.to_base64()),
|
DataValue::Str(user.public_key.to_base64()),
|
||||||
)
|
)
|
||||||
.add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into()))
|
.add_typed_default(DataType::UserId, DataValue::SignedNumber(id.into()))
|
||||||
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()))
|
|
||||||
.add_typed_default(DataType::Display, DataValue::Str(display))
|
.add_typed_default(DataType::Display, DataValue::Str(display))
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::SubLevel,
|
DataType::SubLevel,
|
||||||
|
|
@ -92,7 +91,7 @@ pub async fn get_user(
|
||||||
}
|
}
|
||||||
state.presence.resolve_private_state(id)
|
state.presence.resolve_private_state(id)
|
||||||
} else {
|
} else {
|
||||||
state.presence.resolve_public_state(id, iota_id)
|
iota_id.map(|iota_id| state.presence.resolve_public_state(id, iota_id)).unwrap_or(crate::sql::connection_status::UserStatus::user_offline)
|
||||||
};
|
};
|
||||||
response = response
|
response = response
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
|
|
@ -101,8 +100,11 @@ pub async fn get_user(
|
||||||
)
|
)
|
||||||
.add_typed_default(
|
.add_typed_default(
|
||||||
DataType::OmikronConnections,
|
DataType::OmikronConnections,
|
||||||
connections(&connection, iota_id),
|
iota_id.map(|iota_id| connections(&connection, iota_id)).unwrap_or_else(|| DataValue::Array(Vec::new())),
|
||||||
);
|
);
|
||||||
|
if let Some(iota_id) = iota_id {
|
||||||
|
response = response.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
|
||||||
|
}
|
||||||
if let Some(route) = route {
|
if let Some(route) = route {
|
||||||
response = response.add_typed_default(
|
response = response.add_typed_default(
|
||||||
DataType::OmikronId,
|
DataType::OmikronId,
|
||||||
|
|
@ -123,26 +125,25 @@ pub async fn get_iota(
|
||||||
.map(|iota| (iota.id.0, iota.public_key, None, None))
|
.map(|iota| (iota.id.0, iota.public_key, None, None))
|
||||||
} else if let Some(id) = value.get_data(DataType::UserId).as_number() {
|
} else if let Some(id) = value.get_data(DataType::UserId).as_number() {
|
||||||
if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await {
|
if let Ok(user) = user_repo::get_by_user_id(UserId::from(id as i64)).await {
|
||||||
iota_repo::get_iota_by_id(user.iota_id)
|
match user.iota_id {
|
||||||
.await
|
Some(iota_id) => iota_repo::get_iota_by_id(iota_id)
|
||||||
.ok()
|
.await
|
||||||
.map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None))
|
.ok()
|
||||||
|
.map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), None)),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
} else if let Some(name) = value.get_data(DataType::Username).as_str() {
|
} else if let Some(name) = value.get_data(DataType::Username).as_str() {
|
||||||
if let Ok(user) = user_repo::get_by_username(name).await {
|
if let Ok(user) = user_repo::get_by_username(name).await {
|
||||||
iota_repo::get_iota_by_id(user.iota_id)
|
match user.iota_id {
|
||||||
.await
|
Some(iota_id) => iota_repo::get_iota_by_id(iota_id)
|
||||||
.ok()
|
.await
|
||||||
.map(|iota| {
|
.ok()
|
||||||
(
|
.map(|iota| (iota.id.0, iota.public_key, Some(user.id.0), Some(name.to_owned()))),
|
||||||
iota.id.0,
|
None => None,
|
||||||
iota.public_key,
|
}
|
||||||
Some(user.id.0),
|
|
||||||
Some(name.to_owned()),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
@ -269,7 +270,7 @@ pub async fn change_iota(
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
let result =
|
let result =
|
||||||
match user_repo::change_iota_id(user_id, IotaId::from(value.get_sender() as i64)).await {
|
match user_repo::change_iota_id(user_id, Some(IotaId::from(value.get_sender() as i64))).await {
|
||||||
Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await,
|
Ok(()) => user_repo::change_token(user_id, new_token.to_owned()).await,
|
||||||
Err(error) => Err(error),
|
Err(error) => Err(error),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,7 @@ impl OmikronConnection {
|
||||||
&self.peer_capabilities
|
&self.peer_capabilities
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn handle(self: Arc<Self>, receiver: &mut WebMtpReceiver) {
|
pub async fn handle(self: Arc<Self>, receiver: &mut WebMtpReceiver) {
|
||||||
log_in!(
|
log_in!(
|
||||||
self.id as i64,
|
self.id as i64,
|
||||||
|
|
@ -215,6 +216,24 @@ impl OmikronConnection {
|
||||||
Some(CommunicationType::DeleteUser) => {
|
Some(CommunicationType::DeleteUser) => {
|
||||||
crate::transport::handlers::account::user(self, value).await
|
crate::transport::handlers::account::user(self, value).await
|
||||||
}
|
}
|
||||||
|
Some(CommunicationType::AttachUserBegin) => {
|
||||||
|
crate::transport::handlers::account::attach_begin(self, value).await
|
||||||
|
}
|
||||||
|
Some(CommunicationType::AttachUserComplete) => {
|
||||||
|
crate::transport::handlers::account::attach_complete(self, value).await
|
||||||
|
}
|
||||||
|
Some(CommunicationType::DeleteUserCredentialBegin) => {
|
||||||
|
crate::transport::handlers::account::delete_credential_begin(self, value).await
|
||||||
|
}
|
||||||
|
Some(CommunicationType::DeleteUserCredentialComplete) => {
|
||||||
|
crate::transport::handlers::account::delete_credential_complete(self, value).await
|
||||||
|
}
|
||||||
|
Some(CommunicationType::EraseHostedUserDataAck) => {
|
||||||
|
crate::transport::handlers::account::erase_hosted_user_data_ack(self, value).await
|
||||||
|
}
|
||||||
|
Some(CommunicationType::ReleaseUserFromIota) => {
|
||||||
|
crate::transport::handlers::account::release_from_iota(self, value).await
|
||||||
|
}
|
||||||
Some(CommunicationType::DeleteIota) => {
|
Some(CommunicationType::DeleteIota) => {
|
||||||
crate::transport::handlers::account::iota(self, value).await
|
crate::transport::handlers::account::iota(self, value).await
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ use crate::state::OmegaState;
|
||||||
use crate::transport::connection::OmikronConnection;
|
use crate::transport::connection::OmikronConnection;
|
||||||
use crate::transport::omikron_connection::OmikronResult;
|
use crate::transport::omikron_connection::OmikronResult;
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use mtp::codec::CommunicationValue;
|
use mtp::codec::{CommunicationType, CommunicationValue, DataType, DataValue};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::prelude::IteratorRandom;
|
use rand::prelude::IteratorRandom;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
@ -65,7 +65,8 @@ pub async fn get_all_connections()
|
||||||
for user in users {
|
for user in users {
|
||||||
for route in state.presence.routes_for_user(user.id.0) {
|
for route in state.presence.routes_for_user(user.id.0) {
|
||||||
if let Some(iotas) = result.get_mut(&route.omikron_id) {
|
if let Some(iotas) = result.get_mut(&route.omikron_id) {
|
||||||
if let Some(users) = iotas.get_mut(&user.iota_id.0) {
|
if let Some(iota_id) = user.iota_id
|
||||||
|
&& let Some(users) = iotas.get_mut(&iota_id.0) {
|
||||||
users.push(user.id.0);
|
users.push(user.id.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -113,3 +114,28 @@ pub async fn send_to_user(user_id: i64, cv: &CommunicationValue) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Publish the authoritative membership list after an attach, migration, or
|
||||||
|
/// release. Omikron replaces its full local index from this snapshot.
|
||||||
|
pub async fn publish_iota_user_snapshot(iota_id: i64) {
|
||||||
|
let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { return; };
|
||||||
|
let Some(connection) = get_connected_omikron(omikron_id) else { return; };
|
||||||
|
let Ok(users) = user_repo::get_users_by_iota_id(crate::models::IotaId::from(iota_id)).await else { return; };
|
||||||
|
let user_ids = users.into_iter().map(|user| DataValue::SignedNumber(user.id.0.into())).collect();
|
||||||
|
let snapshot = CommunicationValue::new(CommunicationType::IotaUserData)
|
||||||
|
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()))
|
||||||
|
.add_typed_default(DataType::UserIds, DataValue::Array(user_ids));
|
||||||
|
let _ = connection.send(&snapshot).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn deliver_pending_erasures(iota_id: i64) {
|
||||||
|
let Ok(users) = user_repo::pending_erasures_for_iota(crate::models::IotaId::from(iota_id)).await else { return; };
|
||||||
|
let Some(omikron_id) = get_iota_primary_omikron_connection(iota_id) else { return; };
|
||||||
|
let Some(connection) = get_connected_omikron(omikron_id) else { return; };
|
||||||
|
for user_id in users {
|
||||||
|
let request = CommunicationValue::new(CommunicationType::EraseHostedUserData)
|
||||||
|
.add_typed_default(DataType::UserId, DataValue::SignedNumber(user_id.0.into()))
|
||||||
|
.add_typed_default(DataType::IotaId, DataValue::SignedNumber(iota_id.into()));
|
||||||
|
let _ = connection.clone().send(&request).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue