experimental access control

This commit is contained in:
tezlm 2023-07-28 22:50:04 -07:00
parent 6bf0d2cdd9
commit f06811c7d6
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
29 changed files with 513 additions and 249 deletions

254
Cargo.lock generated
View file

@ -61,9 +61,9 @@ dependencies = [
[[package]]
name = "allocator-api2"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56fc6cf8dc8c4158eed8649f9b8b0ea1518eb62b544fe9490d66fa0b349eafe9"
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
[[package]]
name = "android-tzdata"
@ -148,9 +148,9 @@ dependencies = [
[[package]]
name = "async-trait"
version = "0.1.68"
version = "0.1.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
dependencies = [
"proc-macro2",
"quote",
@ -417,9 +417,9 @@ dependencies = [
[[package]]
name = "clap"
version = "4.3.14"
version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98330784c494e49850cb23b8e2afcca13587d2500b2e3f1f78ae20248059c9be"
checksum = "5fd304a20bff958a57f04c4e96a2e7594cc4490a0e809cbd48bb6437edaa452d"
dependencies = [
"clap_builder",
"clap_derive",
@ -428,9 +428,9 @@ dependencies = [
[[package]]
name = "clap_builder"
version = "4.3.14"
version = "4.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e182eb5f2562a67dda37e2c57af64d720a9e010c5e860ed87c056586aeafa52e"
checksum = "01c6a3f08f1fe5662a35cfe393aec09c4df95f60ee93b7556505260f75eee9e1"
dependencies = [
"anstream",
"anstyle",
@ -536,9 +536,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "cpufeatures"
version = "0.2.8"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03e69e28e9f7f77debdedbaafa2866e1de9ba56df55a8bd7cfc724c25a09987c"
checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1"
dependencies = [
"libc",
]
@ -743,9 +743,9 @@ dependencies = [
[[package]]
name = "either"
version = "1.8.1"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
dependencies = [
"serde",
]
@ -801,9 +801,9 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]]
name = "exr"
version = "1.6.4"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279d3efcc55e19917fff7ab3ddd6c14afb6a90881a0078465196fe2f99d08c56"
checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18"
dependencies = [
"bit_field",
"flume",
@ -834,12 +834,9 @@ checksum = "25c7df09945d65ea8d70b3321547ed414bbc540aad5bac6883d021b970f35b04"
[[package]]
name = "fastrand"
version = "1.9.0"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764"
[[package]]
name = "fdeflate"
@ -909,7 +906,7 @@ version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eeb4ed9e12f43b7fa0baae3f9cdda28352770132ef2e09a23760c29cae8bd47"
dependencies = [
"rustix 0.38.3",
"rustix",
"windows-sys",
]
@ -1080,9 +1077,9 @@ checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e"
[[package]]
name = "h2"
version = "0.3.19"
version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782"
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049"
dependencies = [
"bytes",
"fnv",
@ -1099,11 +1096,10 @@ dependencies = [
[[package]]
name = "half"
version = "2.3.1"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc52e53916c08643f1b56ec082790d1e86a32e58dc5268f897f313fbae7b4872"
checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0"
dependencies = [
"cfg-if",
"crunchy",
]
@ -1177,18 +1173,9 @@ dependencies = [
[[package]]
name = "hermit-abi"
version = "0.2.6"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]]
name = "hex"
@ -1262,9 +1249,9 @@ dependencies = [
[[package]]
name = "http-range-header"
version = "0.3.0"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f"
[[package]]
name = "httparse"
@ -1286,9 +1273,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.26"
version = "0.14.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468"
dependencies = [
"bytes",
"futures-channel",
@ -1317,7 +1304,7 @@ dependencies = [
"futures-util",
"http",
"hyper",
"rustls 0.21.3",
"rustls 0.21.5",
"tokio",
"tokio-rustls 0.24.1",
]
@ -1427,22 +1414,11 @@ dependencies = [
"web-sys",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.1",
"libc",
"windows-sys",
]
[[package]]
name = "ipnet"
version = "2.7.2"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f"
checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6"
[[package]]
name = "is-terminal"
@ -1450,8 +1426,8 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi 0.3.1",
"rustix 0.38.3",
"hermit-abi",
"rustix",
"windows-sys",
]
@ -1466,9 +1442,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.6"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
[[package]]
name = "jobserver"
@ -1528,9 +1504,9 @@ checksum = "0c2cdeb66e45e9f36bfad5bbdb4d2384e70936afbee843c6f6543f0c551ebb25"
[[package]]
name = "libc"
version = "0.2.146"
version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "libsqlite3-sys"
@ -1549,12 +1525,6 @@ version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "linux-raw-sys"
version = "0.4.3"
@ -1855,20 +1825,20 @@ dependencies = [
[[package]]
name = "num-traits"
version = "0.2.15"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi 0.2.6",
"hermit-abi",
"libc",
]
@ -2038,9 +2008,9 @@ dependencies = [
[[package]]
name = "paste"
version = "1.0.12"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "pbkdf2"
@ -2113,18 +2083,18 @@ dependencies = [
[[package]]
name = "pin-project"
version = "1.1.0"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead"
checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.0"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c"
dependencies = [
"proc-macro2",
"quote",
@ -2133,9 +2103,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.2.9"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57"
[[package]]
name = "pin-utils"
@ -2176,9 +2146,9 @@ checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro2"
version = "1.0.63"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
@ -2194,9 +2164,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.28"
version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [
"proc-macro2",
]
@ -2331,8 +2301,8 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.3.2",
"regex-syntax 0.7.3",
"regex-automata 0.3.3",
"regex-syntax 0.7.4",
]
[[package]]
@ -2346,13 +2316,13 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.3.2"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83d3daa6976cffb758ec878f108ba0e062a45b2d6ca3a2cca965338855476caf"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.7.3",
"regex-syntax 0.7.4",
]
[[package]]
@ -2363,9 +2333,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.7.3"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ab07dc67230e4a4718e70fd5c20055a4334b121f1f9db8fe63ef39ce9b8c846"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]]
name = "reqwest"
@ -2392,7 +2362,7 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls 0.21.3",
"rustls 0.21.5",
"rustls-pemfile",
"serde",
"serde_json",
@ -2448,28 +2418,14 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.37.23"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
dependencies = [
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys 0.3.8",
"windows-sys",
]
[[package]]
name = "rustix"
version = "0.38.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac5ffa1efe7548069688cd7028f32591853cd7b5b756d41bcffd2353e4fc75b4"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5"
dependencies = [
"bitflags 2.3.3",
"errno",
"libc",
"linux-raw-sys 0.4.3",
"linux-raw-sys",
"windows-sys",
]
@ -2487,9 +2443,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.21.3"
version = "0.21.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b19faa85ecb5197342b54f987b142fb3e30d0c90da40f80ef4fa9a726e6676ed"
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36"
dependencies = [
"log",
"ring",
@ -2508,9 +2464,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
version = "0.101.1"
version = "0.101.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e"
checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59"
dependencies = [
"ring",
"untrusted",
@ -2518,15 +2474,15 @@ dependencies = [
[[package]]
name = "rustversion"
version = "1.0.12"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "ryu"
version = "1.0.13"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "ryu-js"
@ -2551,9 +2507,9 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
version = "1.1.0"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sct"
@ -2590,9 +2546,9 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.175"
version = "1.0.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d25439cd7397d044e2748a6fe2432b5e85db703d6d097bd014b3c0ad1ebff0b"
checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a"
dependencies = [
"serde_derive",
]
@ -2610,9 +2566,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.175"
version = "1.0.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b23f7ade6f110613c0d63858ddb8b94c1041f550eab58a16b371bdf2c9c80ab4"
checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3"
dependencies = [
"proc-macro2",
"quote",
@ -2621,9 +2577,9 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.103"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
dependencies = [
"itoa",
"ryu",
@ -2632,10 +2588,11 @@ dependencies = [
[[package]]
name = "serde_path_to_error"
version = "0.1.11"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7f05c1d5476066defcdfacce1f52fc3cae3af1d3089727100c02ae92e5abbe0"
checksum = "4beec8bce849d58d06238cb50db2e1c417cfeafa4c63f692b15c82b7c80f8335"
dependencies = [
"itoa",
"serde",
]
@ -2759,9 +2716,9 @@ checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
[[package]]
name = "simd-adler32"
version = "0.3.5"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "siphasher"
@ -2789,9 +2746,9 @@ dependencies = [
[[package]]
name = "smallvec"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
[[package]]
name = "socket2"
@ -2976,9 +2933,9 @@ dependencies = [
[[package]]
name = "stringprep"
version = "0.1.2"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
checksum = "db3737bde7edce97102e0e2b15365bf7a20bfdb5f60f4f9e8d7004258a51a8da"
dependencies = [
"unicode-bidi",
"unicode-normalization",
@ -3169,15 +3126,14 @@ dependencies = [
[[package]]
name = "tempfile"
version = "3.6.0"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998"
dependencies = [
"autocfg",
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix 0.37.23",
"rustix",
"windows-sys",
]
@ -3194,18 +3150,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.40"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.40"
version = "1.0.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [
"proc-macro2",
"quote",
@ -3332,7 +3288,7 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081"
dependencies = [
"rustls 0.21.3",
"rustls 0.21.5",
"tokio",
]
@ -3391,9 +3347,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.4.1"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8bd22a874a2d0b70452d5597b12c537331d49060824a95f49f108994f94aa4c"
checksum = "55ae70283aba8d2a8b411c695c437fe25b8b5e44e23e780662002fc72fb47a82"
dependencies = [
"bitflags 2.3.3",
"bytes",
@ -3545,9 +3501,9 @@ checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460"
[[package]]
name = "unicode-ident"
version = "1.0.9"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c"
[[package]]
name = "unicode-normalization"
@ -3623,9 +3579,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "uuid"
version = "1.4.0"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d023da39d1fde5a8a3fe1f3e01ca9632ada0a63e9797de55a879d6e2236277be"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
dependencies = [
"getrandom 0.2.10",
"serde",
@ -3813,9 +3769,9 @@ dependencies = [
[[package]]
name = "windows-targets"
version = "0.48.0"
version = "0.48.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",

View file

@ -185,8 +185,8 @@ impl fuser::Filesystem for Filesystem {
let parents: Vec<u64> = [dir_all_name, dir_all_ref]
.into_iter()
.chain(vivify_tags(DirKind::Default, dir_tag_ref).into_iter())
.chain(vivify_tags(DirKind::ShowNames, dir_tag_name).into_iter())
.chain(vivify_tags(DirKind::Default, dir_tag_ref))
.chain(vivify_tags(DirKind::ShowNames, dir_tag_name))
.collect();
inodes.create(GraphItem::Event(Box::new(event)), parents);
}
@ -199,8 +199,8 @@ impl fuser::Filesystem for Filesystem {
let parents: Vec<u64> = [dir_all_name, dir_all_ref]
.into_iter()
.chain(vivify_tags(DirKind::Default, dir_tag_ref).into_iter())
.chain(vivify_tags(DirKind::ShowNames, dir_tag_name).into_iter())
.chain(vivify_tags(DirKind::Default, dir_tag_ref))
.chain(vivify_tags(DirKind::ShowNames, dir_tag_name))
.collect();
let targets: Vec<&ItemRef> = event.relations.keys().collect();
@ -245,8 +245,8 @@ impl fuser::Filesystem for Filesystem {
let to_remove: Vec<u64> = []
.into_iter()
.chain(inodes.read_dir(dir_tag_ref).unwrap().into_iter())
.chain(inodes.read_dir(dir_tag_name).unwrap().into_iter())
.chain(inodes.read_dir(dir_tag_ref).unwrap())
.chain(inodes.read_dir(dir_tag_name).unwrap())
.filter_map(|(ino, entry)| match &entry.item {
GraphItem::Directory(dir) => dir.entries.is_empty().then_some(ino),
_ => None,

View file

@ -211,7 +211,7 @@ async fn main() -> Result<(), Error> {
} else {
let mut refs = Vec::new();
let mut chunks = Vec::new();
println!("hash file chunks");
println!("hash file chunks for {}", file.display());
for chunk in buffer.chunks(1024 * 1024) {
use sha2::Digest;
let hash = {
@ -304,7 +304,7 @@ async fn main() -> Result<(), Error> {
long,
snippets,
} => {
let search = client.search(&query).await?;
let search = client.search(&query, snippets).await?;
if long {
if snippets {
println!(
@ -602,5 +602,5 @@ async fn main() -> Result<(), Error> {
}
fn into_hashset<T: Eq + Hash>(vec: Vec<T>) -> HashSet<T> {
HashSet::from_iter(vec.into_iter())
HashSet::from_iter(vec)
}

View file

@ -232,19 +232,24 @@ impl Client {
Ok(())
}
pub async fn search(&self, query: &str) -> Result<SearchResult, Error> {
let url =
reqwest::Url::parse_with_params(&format!("{}search", self.base_url), [("q", query)])?;
let res: SearchResult = self
pub async fn search(&self, query: &str, snippets: bool) -> Result<SearchResult, Error> {
let url = reqwest::Url::parse_with_params(
&format!("{}search", self.base_url),
[
("q", query),
("sniplen", if snippets { "256" } else { "0" }),
],
)?;
let res = self
.http
.get(url)
.header("authorization", format!("Bearer {}", self.token))
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(res)
if !res.status().is_success() {
return Err(DynError(res.text().await?))?;
}
Ok(res.json().await?)
}
pub async fn check(&self, refs: &[ItemRef]) -> Result<HashSet<ItemRef>, Error> {

View file

@ -1,7 +1,11 @@
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use crate::{actor::ActorId, event::Event, item::ItemRef};
use crate::{
actor::ActorId,
event::{Event, RelInfo},
item::ItemRef,
};
type Role = String;
@ -22,14 +26,14 @@ pub struct Acl {
// from_event_type, rel_type, to_event_type
type Permission = (String, String, String);
type Permissions = HashSet<Permission>;
type Relations = HashMap<ItemRef, (Event, RelInfo)>;
impl Acl {
pub fn can_send(
&self,
user: &ActorId,
event: &Event,
targets: HashMap<ItemRef, &Event>,
) -> bool {
pub fn can_view(&self, user: &ActorId) -> bool {
self.users.contains_key(user) || self.admins.contains(user)
}
pub fn can_send(&self, user: &ActorId, event: &Event, relations: &Relations) -> bool {
if self.admins.contains(user) {
return true;
}
@ -52,7 +56,7 @@ impl Acl {
}
for (item_ref, rel_info) in &event.relations {
let Some(target) = targets.get(item_ref) else {
let Some((target, _)) = relations.get(item_ref) else {
continue;
};

View file

@ -1,3 +1,4 @@
use crate::acl::Acl;
use crate::actor::{ActorId, ActorSignature};
use crate::derived::Derived;
use crate::item::ItemRef;
@ -24,6 +25,8 @@ pub struct Event {
pub derived: Derived,
pub sender: ActorId,
pub signature: ActorSignature,
/// the timestamp at the event's creator (in miliseconds + utc)
/// also acts as a nonce
pub origin_ts: u64,
}
@ -64,7 +67,7 @@ pub enum EventContent {
#[serde(rename = "x.redact")]
Redact(RedactEvent),
#[serde(rename = "x.acl")]
Acl(AclEvent),
Acl(Acl),
#[serde(rename = "x.annotate")]
Annotate(AnnotateEvent),
#[serde(rename = "x.annotate.local")]
@ -109,12 +112,6 @@ pub struct FileEvent {
pub name: Option<String>,
}
// TODO: currently unused
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AclEvent {
pub admins: Vec<String>,
}
impl EventContent {
pub fn get_type(&self) -> &str {
match self {

View file

@ -27,7 +27,7 @@ reqwest = { version = "0.11.18", features = ["json", "rustls-tls"], default-feat
serde = { version = "1.0.164", features = ["derive"] }
serde-aux = "4.2.0"
serde_json = "1.0.97"
sha2 = "0.10.7"
sha2 = { version = "0.10.7", features = ["asm"] }
sqlx = { version = "0.6.3", features = ["sqlite", "runtime-tokio-rustls", "offline"] }
tantivy = "0.20.2"
thiserror = "1.0.40"

24
server/ideas.md Normal file
View file

@ -0,0 +1,24 @@
# plugins
```ts
type Plugin = IndexPlugin | BlobPlugin | DerivePlugin;
/// a plugin for indexing
interface IndexPlugin {
type: "index",
key: string,
}
///
interface BlobPlugin {
type: "blob",
key: string,
endpoint: string,
}
///
interface DerivePlugin {
type: "derive",
key: string,
}
```

View file

@ -1,7 +1,9 @@
-- will add more stuff over time
-- TODO: foreign key for user
CREATE TABLE sessions (
id TEXT PRIMARY KEY NOT NULL,
user TEXT NOT NULL,
level INT NOT NULL
);
-- TODO: proper session management
INSERT INTO sessions VALUES ('hunter2', 2);
INSERT INTO sessions VALUES ('hunter2', '%2zO9HaRwyyA2-AOGcLNKnoTbpnQ6q7gTv6ds_OR-djU', 2);

View file

@ -4,6 +4,7 @@ use std::collections::HashMap;
use std::io::Cursor;
use std::process::Stdio;
use tokio::process::Command;
use tracing::debug;
use ufh::derived::DeriveMedia;
#[derive(Debug, Serialize, Deserialize)]
@ -105,7 +106,7 @@ impl Ffprobe {
}
pub async fn thumbnail(&self, buffer: &[u8]) -> Option<Vec<u8>> {
println!("generate thumbnail");
debug!("generate thumbnail");
match self
.streams
.iter()
@ -124,6 +125,7 @@ impl Ffprobe {
}
pub async fn subtitles(&self, buffer: &[u8]) -> Option<String> {
debug!("extract subtitles");
let stream = self.streams.iter().find(|s| s.codec_type == "subtitle")?;
String::from_utf8(
ffmpeg::extract(

View file

@ -127,7 +127,7 @@ impl Deriver {
}
let text = a.into_iter().flatten().collect::<Vec<_>>().join("\n");
if text.trim().len() == 0 {
if text.trim().is_empty() {
None
} else {
Some(text)

View file

@ -21,6 +21,8 @@ pub enum Error {
#[error("{0}")]
Validation(&'static str),
#[error("{0}")]
BadRequest(String),
#[error("{0}")]
ItemRefParse(ufh::item::ItemRefParseError),
#[error("{0}")]
RecvError(tokio::sync::broadcast::error::RecvError),
@ -112,6 +114,7 @@ impl Error {
Error::ItemRefParse(_) => StatusCode::BAD_REQUEST,
Error::RecvError(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::Tantivy(_) => StatusCode::INTERNAL_SERVER_ERROR,
Error::BadRequest(_) => StatusCode::BAD_REQUEST,
Error::Http(status, _) => *status,
}
}

View file

@ -25,7 +25,7 @@ pub async fn get_relations(db: &Sqlite, event: &Event) -> Result<Relations, Erro
rel_events
.into_iter()
.map(|(item_ref, item)| {
let rel_info = event.relations.get(&item_ref).unwrap();
let rel_info = &event.relations[&item_ref];
match item {
DbItem::Event(event) => Ok((item_ref, (*event, rel_info.clone()))),
DbItem::Blob => Err(Error::Validation(
@ -156,7 +156,8 @@ pub async fn derive(
media: deriver.get_media().await,
tags: HashSet::new(),
};
Ok((derived, deriver.get_thumbnail().await))
let thumb = deriver.get_thumbnail().await;
Ok((derived, thumb))
}
pub async fn update_search_index(

View file

@ -1,5 +1,24 @@
#![feature(async_fn_in_trait)] // ahh yes, experimental features
// general purpose lints
#![warn(
clippy::get_unwrap,
clippy::unnested_or_patterns,
clippy::undocumented_unsafe_blocks,
illegal_floating_point_literal_pattern
)]
// "production quality" lints
// #![warn(
// clippy::dbg_macro,
// clippy::todo,
// clippy::unimplemented,
// clippy::use_debug,
// clippy::print_stdout,
// clippy::unwrap_in_result,
// clippy::unwrap_used,
// )]
use axum::extract::Json;
use axum::{routing, Router, Server};
use clap::Parser;
@ -21,6 +40,7 @@ mod error;
mod items;
mod middleware;
mod peer;
mod perms;
mod routes;
mod state;
@ -49,6 +69,8 @@ pub struct ServerState {
// note: sharing was previously to be able to let people anonymously view blobs, which isnt needed now
// note: do i use #alias:server.tld like matrix or something else?
// TODO (future): use generic database trait instead of `Sqlite`
// FIXME: return json for all errors (some endpoints return plain text currently)
#[tokio::main]

120
server/src/perms.rs Normal file
View file

@ -0,0 +1,120 @@
#![allow(unused)]
use std::collections::VecDeque;
use crate::{
items::events::get_relations,
state::db::{sqlite::Sqlite, Database},
Relations,
};
use ufh::{
acl::Acl,
actor::ActorId,
event::{Event, EventContent},
item::ItemRef,
};
// TODO: find out how to cache this
#[async_recursion::async_recursion]
pub async fn can_view_event(db: &Sqlite, event: &Event, user: &ActorId) -> bool {
let relations = get_relations(db, event).await.unwrap();
// event is visible if user has sent the event
if &event.sender == user {
return true;
}
// or if event relates to any visible event
for (event, _) in relations.values() {
if can_view_event(db, event, user).await {
return true;
}
}
// or if an acl allows a user to view an event
get_acl(db, &event.id)
.await
.is_some_and(|acl| acl.can_view(user))
}
// async fn is_event_visible_nonrecursive(db: &Sqlite, event: &Event, user: &ActorId) -> bool {
// let mut events = VecDeque::from([event.clone()]);
//
// while let Some(next) = events.pop_back() {
// // event is visible if user has sent the event
// if &event.sender == user {
// return true;
// }
//
// // or if an acl allows a user to view an event
// if get_acl(db, &event.id).await.is_some_and(|acl| acl.can_view(user)) {
// return true;
// }
//
// // or if event relates to any visible event
// let relations = get_relations(db, event).await.unwrap();
// events.extend(relations.into_values().map(|(event, _)| event));
// }
//
// false
// }
struct Context<'a> {
relations: &'a Relations,
event: &'a Event,
user: &'a ActorId,
}
pub async fn can_send_event(db: &Sqlite, event: &Event, user: &ActorId) -> bool {
let relations = get_relations(db, event).await.unwrap();
let ctx = Context {
relations: &relations,
event,
user,
};
for (rel, _) in relations.values() {
if !is_relation_valid(db, rel, &ctx).await {
return false;
}
}
true
}
async fn validate_relations(db: &Sqlite, event: &Event, ctx: &Context<'_>) -> bool {
let relations = get_relations(db, event).await.unwrap();
for (rel, _) in relations.values() {
if !is_relation_valid(db, rel, ctx).await {
return false;
}
}
true
}
#[async_recursion::async_recursion]
async fn is_relation_valid(db: &Sqlite, relation: &Event, ctx: &Context<'_>) -> bool {
// a relation is allowed if the user has sent that event
if &relation.sender == ctx.user {
return true;
}
if let Some(acl) = get_acl(db, &relation.id).await {
// or if an acl set on it allows it
acl.can_send(ctx.user, ctx.event, ctx.relations)
} else {
// or if all of it's relations are also valid
validate_relations(db, relation, ctx).await
}
}
async fn get_acl(db: &Sqlite, item_ref: &ItemRef) -> Option<Acl> {
let result = db
.query_relations(&[("acl", "acl")], &[item_ref.clone()])
.await
.ok()?;
let (_, event) = result.into_iter().last()?;
match event.content {
EventContent::Acl(acl) => Some(acl),
_ => None,
}
}

View file

@ -64,7 +64,7 @@ async fn search(
.map(|doc| {
let item = items
.remove(&doc.item_ref)
.expect("search results has nonexistent event");
.expect("search results has duplicate or nonexistent event");
match item {
DbItem::Blob => panic!("search result returned a blob"),
DbItem::Event(event) => SearchDoc {

View file

@ -50,7 +50,7 @@ pub async fn route(
}
};
debug!("content-disposition: {}", disposition.to_str().unwrap());
debug!("content-disposition: {:?}", disposition);
let mut headers = HeaderMap::new();
headers.insert(

View file

@ -1,3 +1,4 @@
use crate::perms::can_view_event;
use crate::state::db::Database;
use crate::ServerState;
use axum::extract::{Query, State};
@ -43,12 +44,13 @@ impl IntoResponse for QueryResult {
#[tracing::instrument(skip_all, fields(query))]
pub async fn route(
State(state): State<Arc<ServerState>>,
_auth: Authenticate<perms::ReadOnly>,
auth: Authenticate<perms::ReadOnly>,
Query(params): Query<QueryParams>,
) -> Result<QueryResult, Error> {
tracing::Span::current().record("query", &params.query);
debug!("enumerate");
let user = auth.user.unwrap();
let queries = state.queries.read().await;
let query = queries
.get(&params.query)
@ -63,6 +65,21 @@ pub async fn route(
.db
.query_events(&query, params.limit, params.after.map(|p| p.to_string()))
.await?;
use futures_util::stream::{self, StreamExt};
let result = crate::state::db::QueryResult {
events: stream::iter(result.events)
.filter_map(|event| async {
can_view_event(&state.db, &event, &user)
.await
.then_some(event)
})
.collect()
.await,
..result
};
let result = match (params.timeout, result.events.is_empty()) {
(Some(timeout), true) => {
debug!("no events, waiting for new ones...");
@ -73,9 +90,17 @@ pub async fn route(
break match recv.recv().await {
Err(err) => Err(err.into()),
Ok((event, relations, rowid)) => match query.matches(&event, &relations) {
mt @ MatchType::Event => Ok((mt, event, rowid)),
mt @ MatchType::Relation => Ok((mt, event, rowid)),
MatchType::None => continue,
mt @ MatchType::Event
if can_view_event(&state.db, &event, &user).await =>
{
Ok((mt, event, rowid))
}
mt @ MatchType::Relation
if can_view_event(&state.db, &event, &user).await =>
{
Ok((mt, event, rowid))
}
_ => continue,
},
};
}
@ -114,8 +139,13 @@ pub async fn route(
// relations query
let relations = if !query.relations.is_empty() && !result.events.is_empty() {
let relations: Vec<_> = query.relations.into_iter().collect();
let map = state.db.query_relations(&relations, &result.events).await?;
let relations: Vec<_> = query
.relations
.iter()
.map(|(rel_type, event_type)| (rel_type.as_str(), event_type.as_str()))
.collect();
let ids: Vec<_> = result.events.iter().map(|ev| ev.id.clone()).collect();
let map = state.db.query_relations(&relations, &ids).await?;
debug!("fetched {} relations", map.len());
Some(map)
} else {

View file

@ -1,3 +1,4 @@
use super::{Error, Response};
use crate::routes::util::get_blob;
use crate::state::db::{Database, Thumbnail};
use crate::ServerState;
@ -10,15 +11,15 @@ use std::fmt::Display;
use std::io::Cursor;
use std::sync::Arc;
use std::time::Duration;
use tracing::debug;
use ufh::item::ItemRef;
use super::{Error, Response};
pub async fn route(
State(state): State<Arc<ServerState>>,
Path(item_ref): Path<ItemRef>,
Query(params): Query<ThumbnailParams>,
) -> Response<(TypedHeader<ContentType>, TypedHeader<CacheControl>, Bytes)> {
debug!("get thumbnail");
let size = ThumbnailSize::closest_to(params.width, params.height);
let thumb = state.db.thumbnail_get(&item_ref, &size).await?;
let cache_control = TypedHeader(
@ -29,6 +30,7 @@ pub async fn route(
let content_type = TypedHeader(ContentType::png());
match thumb {
Thumbnail::Raw(bytes) => {
debug!("generate thumbnail");
let thumb = generate_thumb(&bytes, &size)?;
state.db.thumbnail_create(&item_ref, &size, &thumb).await?;
Ok((content_type, cache_control, thumb))
@ -41,6 +43,7 @@ pub async fn route(
.await?
.ok_or(Error::NotFound)?;
let blob = get_blob(&state.items, &event, params.via.as_deref()).await?;
debug!("generate thumbnail");
let thumb = generate_thumb(&blob, &size)?;
state.db.thumbnail_create(&item_ref, &size, &thumb).await?;
Ok((content_type, cache_control, thumb))

View file

@ -4,7 +4,10 @@ use axum::{
};
use bytes::Bytes;
use reqwest::StatusCode;
use ufh::event::{Event, EventContent};
use ufh::{
actor::ActorId,
event::{Event, EventContent},
};
use crate::{items::Items, ServerState};
use std::{marker::PhantomData, sync::Arc};
@ -85,6 +88,7 @@ pub mod perms {
#[derive(Debug)]
pub struct Authenticate<T: perms::AuthLevel> {
pub id: Option<String>,
pub user: Option<ActorId>,
pub level: u8,
_lvl: PhantomData<T>,
}
@ -127,6 +131,7 @@ impl<T: AuthLevel> Authenticate<T> {
Err(sqlx::Error::RowNotFound) if T::new().to_num() == 0 => {
return Ok(Authenticate {
id: None,
user: None,
level: 0,
_lvl: PhantomData,
});
@ -148,8 +153,14 @@ impl<T: AuthLevel> Authenticate<T> {
));
}
let user = query
.user
.parse()
.expect("user id should be validated on input");
Ok(Authenticate {
id: Some(query.id),
user: Some(user),
level: query.level as u8,
_lvl: PhantomData,
})

View file

@ -59,8 +59,8 @@ pub trait Database {
) -> Result<QueryResult, Self::Error>;
async fn query_relations(
&self,
relations: &[(String, String)],
for_events: &[Event],
relations: &[(&str, &str)],
for_events: &[ItemRef],
) -> Result<HashMap<ItemRef, Event>, Self::Error>;
// routes::things::create has a lot of file-specific things

View file

@ -46,28 +46,6 @@ impl Sqlite {
}
}
/*
extern "pseudocode" fn is_event_visible(event: &Event, user: &ActorId) -> bool {
if event.sender == user {
return true;
}
let acl = state.db.query(Query { refs: [event.id], rels: [("acl", "x.acl")] });
if let Some(acl) = acl {
return acl.users.contains(user) || acl.admins.contains(user);
}
let acls_that_affected_event_sending = state.db.lookup_acls(&event).await?;
for acl in acls_that_affected_event_sending {
if acl.users.contains(user) || acl.admins.contains(user) {
return true;
}
}
return false;
}
*/
// TODO: confirm that every function here is idempotent...
// TODO (future): ...and warn when doing the same thing multiple times (to spot optimizations)
impl Database for Sqlite {
@ -278,8 +256,8 @@ impl Database for Sqlite {
#[tracing::instrument(skip_all)]
async fn query_relations(
&self,
relations: &[(String, String)],
for_events: &[Event],
relations: &[(&str, &str)],
for_events: &[ItemRef],
) -> Result<HashMap<ItemRef, Event>, Self::Error> {
let mut builder = sqlx::QueryBuilder::new(
"
@ -296,9 +274,9 @@ impl Database for Sqlite {
});
builder.push(") AND events_to.ref IN (");
let mut sep = builder.separated(",");
for event in for_events {
let event_id_str = event.id.to_string();
sep.push_bind(event_id_str);
for item_ref in for_events {
let item_ref_str = item_ref.to_string();
sep.push_bind(item_ref_str);
}
builder.push(")");
debug!("generated sql: {}", builder.sql());
@ -338,9 +316,6 @@ impl Database for Sqlite {
}
builder.push(")");
let records = builder.build().fetch_all(&self.pool).await?;
if !partial && records.len() != item_refs.len() {
return Err(Error::NotFound);
}
let mut map = HashMap::with_capacity(records.len());
for record in records {
let item_ref_str = record.get("item_ref");
@ -359,6 +334,11 @@ impl Database for Sqlite {
};
map.insert(item_ref, item);
}
let has_all = HashSet::<_>::from_iter(item_refs) == HashSet::<_>::from_iter(map.keys());
if !partial && !has_all {
return Err(Error::NotFound);
}
Ok(map)
}

View file

@ -19,6 +19,11 @@ pub struct Tantivy {
writer: Arc<Mutex<IndexWriter>>,
}
// NOTE: should snippets be supported?
// this requires an extra copy of the text to be stored and retreived,
// making it much more taxing on storage and performance
// howver, they are a *very* nice search qol feature
impl Tantivy {
pub async fn open(path: impl AsRef<std::path::Path>) -> Result<Tantivy, Error> {
let _ = tokio::fs::create_dir(&path).await;
@ -47,9 +52,9 @@ impl Tantivy {
builder.add_text_field("name", TEXT | STORED);
builder.add_text_field("body", TEXT | STORED);
builder.add_text_field("type", STRING | STORED);
builder.add_text_field("tag", STRING | STORED);
builder.add_date_field("btime", STORED);
builder.add_date_field("mtime", STORED);
builder.add_text_field("tag", STRING);
builder.add_date_field("btime", INDEXED);
builder.add_date_field("mtime", INDEXED);
builder.build()
}
}
@ -77,14 +82,25 @@ impl Search for Tantivy {
}
doc.add_text(field("body"), document.text);
doc.add_text(field("type"), document.event.content.get_type());
// FIXME: look at the api to see how to do this more properly
doc.add_date(
field("mtime"),
DateTime::from_timestamp_millis(OffsetDateTime::now_utc().unix_timestamp()),
DateTime::from_timestamp_nanos(
OffsetDateTime::now_utc()
.unix_timestamp_nanos()
.try_into()
.expect("your timestamp is too big!"),
),
);
// FIXME: don't do `as i64`
doc.add_date(
field("btime"),
DateTime::from_timestamp_millis(document.event.origin_ts as i64),
DateTime::from_timestamp_millis(
document
.event
.origin_ts
.try_into()
.expect("your timestamp is too big!"),
),
);
let mut product = Vec::new();
@ -132,22 +148,27 @@ impl Search for Tantivy {
);
let parsed = parser
.parse_query(query)
.map_err(|_| Error::Validation("invalid query"))?;
.map_err(|err| Error::BadRequest(format!("invalid query: {}", err)))?;
let mut snippets = tantivy::SnippetGenerator::create(&searcher, &parsed, field("body"))?;
snippets.set_max_num_chars(sniplen);
let collector = TopDocs::with_limit(limit).and_offset(offset);
let mut docs = Vec::new();
for (score, addr) in searcher.search(&parsed, &collector)? {
let doc = searcher.doc(addr)?;
let snippet = snippets.snippet_from_doc(&doc);
let snippet = (!snippet.is_empty()).then(|| Snippet {
text: snippet.fragment().to_string(),
ranges: snippet
.highlighted()
.iter()
.map(|range| (range.start, range.end))
.collect(),
});
let snippet = if sniplen > 0 {
let snippet = snippets.snippet_from_doc(&doc);
let snippet = (!snippet.is_empty()).then(|| Snippet {
text: snippet.fragment().to_string(),
ranges: snippet
.highlighted()
.iter()
.map(|range| (range.start, range.end))
.collect(),
});
snippet
} else {
None
};
let item_ref: ItemRef = doc
.get_first(field("ref"))
.expect("document doesn't have an item ref!")
@ -161,6 +182,7 @@ impl Search for Tantivy {
item_ref,
});
}
debug!("got {} docs", docs.len());
Ok(docs)
}

66
spec/acl.rs Normal file
View file

@ -0,0 +1,66 @@
/// pseudocode for validing events
/// the 2 main functions `is_event_visible` and `is_event_sendable`
type Relations = HashMap<ItemRef, (Event, RelInfo)>;
extern "pseudocode" fn is_event_visible(event: &Event, user: &ActorId) -> bool {
let relations = get_relations(event);
// event is visible if user has sent the event
if event.sender == user {
return true;
}
// or if event relates to any visible event
if relations.values().any(|rel| is_event_visible(rel, user)) {
return true;
}
// or if an acl allows a user to view an event
get_acl(&event).is_some_and(|acl| acl.can_view(user))
}
struct Context {
relations: Relations,
event: Event,
user: ActorId,
}
extern "pseudocode" fn is_event_sendable(event: &Event, user: &ActorId) -> bool {
let relations = get_relations(event);
let ctx = Context { relations, event, user };
relations
.values()
.all(|rel| is_relation_valid(rel, &ctx))
}
fn validate_relations(event: &Event, ctx: &Context) -> bool {
let relations = get_relations(event);
relations
.values()
.all(|rel| is_relation_valid(rel, ctx))
}
fn is_relation_valid(relation: &Event, ctx: &Context) -> bool {
// a relation is allowed if the user has sent that event
if relation.sender == ctx.user {
return true;
}
if let Some(acl) = get_acl(&relation) {
// or if an acl set on it allows it
acl.can_send(ctx.user, ctx.event, ctx.relations)
} else {
// or if all of it's relations are also valid
validate_relations(relation, ctx)
}
}
fn get_relations(_event: &Event) -> Relations {
unimplemented!()
}
fn get_acl(_event: &Event) -> Option<Acl> {
unimplemented!()
}

View file

@ -266,3 +266,9 @@ ones so far:
- `session`: users can manage sessions
- `search`: has full text search
- `share`: deprecated
## searching
see <https://docs.rs/tantivy/latest/tantivy/query/struct.QueryParser.html>
deriving takes a lot of dependencies, which might not be desierable

View file

@ -13,7 +13,7 @@ flume = "0.10.14"
futures-util = "0.3.28"
serde = { version = "1.0.163", features = ["derive"] }
serde_json = "1.0.96"
sha2 = "0.10.6"
sha2 = { version = "0.10.6", features = ["asm"] }
sqlx = { version = "0.6.3", features = ["sqlite", "runtime-tokio-rustls", "offline"] }
thiserror = "1.0.40"
tokio = { version = "1.28.2", features = ["rt-multi-thread", "macros", "fs"] }

1
sweep.timestamp Normal file
View file

@ -0,0 +1 @@
{"secs_since_epoch":1690528577,"nanos_since_epoch":119672817}

View file

@ -6,7 +6,7 @@
export let style = "";
export let url = api.getBlobUrl(event.id);
</script>
<audio title="{event.content.name}" src={url} autoplay controls/>
<audio title="{event.content.name}" src={url} autoplay controls loop />
<style>
audio {
display: block;

View file

@ -36,10 +36,14 @@
<Audio {event} />
{:else if info.mime?.startsWith("video/")}
<Video {event} style="max-width: 80vw; max-height: 80vh" />
{:else}
{:else if info.mime?.startsWith("text/") || info.mime === "application/json"}
<div class="text">
<Text {event} />
</div>
{:else if info.mime === "application/pdf"}
<embed src={url} type={info.mime} />
{:else}
<div class="text">[/// cannot view event ///]</div>
{/if}
</div>
<!-- <a target="_blank" href={url} download={event.content.name}>Open in new tab</a> -->
@ -89,4 +93,9 @@
font-family: monospace;
padding: 8px;
}
embed {
height: 80vh;
width: 80vw;
}
</style>