diff --git a/.env.example b/.env.example index 311a47a3..899ef754 100644 --- a/.env.example +++ b/.env.example @@ -2,14 +2,10 @@ THOTH_GRAPHQL_API=http://localhost:8000 # THOTH_EXPORT_API is used at compile time, must be a public facing URL THOTH_EXPORT_API=http://localhost:8181 -# Authentication cookie domain -THOTH_DOMAIN=localhost # Full postgres URL DATABASE_URL=postgres://thoth:thoth@localhost/thoth # Full redis URL REDIS_URL=redis://localhost:6379 -# Authentication cookie secret key -SECRET_KEY=an_up_to_255_bytes_random_key # Logging level RUST_LOG=info diff --git a/.gitignore b/.gitignore index c4534ef2..2ed3d5df 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env db/ target/ +machinekey/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a5cc3bf1..6f0759e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Changed + - [697](https://github.com/thoth-pub/thoth/pull/697) – Migrated GraphQL API authentication to OIDC via Zitadel. Internal JWT handling has been replaced with introspection of Zitadel-issued tokens. Authorisation is now based entirely on token claims, removing the need for the internal `account` and `publisher_account` tables. + - [697](https://github.com/thoth-pub/thoth/pull/697) – Replaced legacy password-based login in the Thoth APP with Zitadel OIDC authentication using PKCE. The app now redirects to Zitadel for login, exchanges authorisation codes for tokens, and introspects tokens client-side to determine roles and permissions. - [689](https://github.com/thoth-pub/thoth/issues/689) - Move `Work.fullTitle`, `Work.title` and `Work.subtitle` into a dedicated `Title` table, supporting multilingual and rich text fields - [689](https://github.com/thoth-pub/thoth/issues/689) - Move `Work.shortAbstract` and `Work.longAbstract` into a dedicated `Abstract` table with `abstractType`, supporting multilingual and rich text fields - [689](https://github.com/thoth-pub/thoth/issues/689) - Move `Contribution.biography` into a dedicated `Biography` table, supporting multilingual and rich text fields diff --git a/Cargo.lock b/Cargo.lock index 80d3aa23..1b8501e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,22 +73,6 @@ dependencies = [ "zstd", ] -[[package]] -name = "actix-identity" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b8ddc6f6a8b19c4016aaa13519968da9969bc3bc1c1c883cdb0f25dd6c8cf7" -dependencies = [ - "actix-service", - "actix-session", - "actix-utils", - "actix-web", - "derive_more 1.0.0", - "futures-core", - "serde", - "tracing", -] - [[package]] name = "actix-macros" version = "0.2.4" @@ -151,23 +135,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "actix-session" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe6976a74f34f1b6d07a6c05aadc0ed0359304a7781c367fa5b4029418db08f" -dependencies = [ - "actix-service", - "actix-utils", - "actix-web", - "anyhow", - "derive_more 1.0.0", - "rand 0.8.5", - "serde", - "serde_json", - "tracing", -] - [[package]] name = "actix-utils" version = "3.0.1" @@ -239,41 +206,6 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" -[[package]] -name = "aead" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "ahash" version = "0.8.12" @@ -407,6 +339,28 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "async-trait" version = "0.1.89" @@ -442,11 +396,70 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 1.0.2", + "tower 0.5.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper 1.0.2", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" -version = "0.20.0" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" @@ -454,6 +467,21 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + +[[package]] +name = "base64ct" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e050f626429857a27ddccb31e0aca21356bfa709c04041aefddac081a8f068a" + [[package]] name = "bitflags" version = "1.3.2" @@ -535,9 +563,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ "find-msvc-tools", "jobserver", @@ -574,16 +602,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "cipher" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clap" version = "4.5.53" @@ -653,6 +671,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + [[package]] name = "constant_time_eq" version = "0.1.5" @@ -674,14 +698,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ - "aes-gcm", - "base64 0.20.0", - "hkdf", - "hmac", "percent-encoding", - "rand 0.8.5", - "sha2", - "subtle", "time", "version_check", ] @@ -696,6 +713,16 @@ dependencies = [ "libc", ] +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -721,13 +748,24 @@ dependencies = [ ] [[package]] -name = "crypto-common" -version = "0.1.7" +name = "crypto-bigint" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", "typenum", ] @@ -776,14 +814,38 @@ dependencies = [ ] [[package]] -name = "ctr" -version = "0.9.2" +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "cipher", + "proc-macro2", + "quote", + "syn 2.0.111", ] +[[package]] +name = "custom_error" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8a51dd197fa6ba5b4dc98a990a43cc13693c23eb0089ebb0fcc1f04152bca6" + [[package]] name = "darling" version = "0.21.3" @@ -850,6 +912,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + [[package]] name = "deranged" version = "0.5.5" @@ -857,6 +930,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -870,34 +944,13 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl 1.0.0", -] - [[package]] name = "derive_more" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ - "derive_more-impl 2.1.0", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", - "unicode-xid", + "derive_more-impl", ] [[package]] @@ -1019,6 +1072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] @@ -1075,6 +1129,50 @@ dependencies = [ "dtoa", ] +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "ego-tree" version = "0.6.3" @@ -1087,6 +1185,27 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -1147,12 +1266,34 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + [[package]] name = "flate2" version = "1.1.5" @@ -1309,12 +1450,13 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.7" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -1351,16 +1493,6 @@ dependencies = [ "wasip2", ] -[[package]] -name = "ghash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" -dependencies = [ - "opaque-debug", - "polyval", -] - [[package]] name = "graphql-introspection-query" version = "0.2.0" @@ -1420,13 +1552,24 @@ dependencies = [ ] [[package]] -name = "h2" -version = "0.3.27" +name = "group" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ - "bytes", - "fnv", + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "h2" +version = "0.3.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0beca50380b1fc32983fc1cb4587bfa4bb9e78fc259aad4a0032d2080309222d" +dependencies = [ + "bytes", + "fnv", "futures-core", "futures-sink", "futures-util", @@ -1487,6 +1630,12 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hkdf" version = "0.12.4" @@ -1540,6 +1689,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", +] + [[package]] name = "http-body" version = "1.0.1" @@ -1559,7 +1719,7 @@ dependencies = [ "bytes", "futures-core", "http 1.4.0", - "http-body", + "http-body 1.0.1", "pin-project-lite", ] @@ -1575,6 +1735,30 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hyper" +version = "0.14.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.5.10", + "tokio", + "tower-service", + "tracing", + "want", +] + [[package]] name = "hyper" version = "1.8.1" @@ -1587,8 +1771,9 @@ dependencies = [ "futures-core", "h2 0.4.12", "http 1.4.0", - "http-body", + "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "pin-utils", @@ -1597,6 +1782,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.32", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.7" @@ -1604,12 +1803,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ "http 1.4.0", - "hyper", + "hyper 1.8.1", "hyper-util", - "rustls", + "rustls 0.23.35", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.4", + "tower-service", +] + +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper 1.8.1", + "hyper-util", + "pin-project-lite", + "tokio", "tower-service", ] @@ -1621,7 +1833,7 @@ checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", - "hyper", + "hyper 1.8.1", "hyper-util", "native-tls", "tokio", @@ -1641,14 +1853,14 @@ dependencies = [ "futures-core", "futures-util", "http 1.4.0", - "http-body", - "hyper", + "http-body 1.0.1", + "hyper 1.8.1", "ipnet", "libc", "percent-encoding", "pin-project-lite", "socket2 0.6.1", - "system-configuration", + "system-configuration 0.6.1", "tokio", "tower-service", "tracing", @@ -1727,9 +1939,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1741,9 +1953,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1801,6 +2013,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", + "serde", ] [[package]] @@ -1815,15 +2028,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "inout" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" -dependencies = [ - "generic-array", -] - [[package]] name = "instant" version = "0.1.13" @@ -1878,6 +2082,24 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" @@ -1987,6 +2209,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] [[package]] name = "libc" @@ -1994,6 +2219,12 @@ version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "linux-raw-sys" version = "0.11.0" @@ -2067,6 +2298,12 @@ dependencies = [ "tendril", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "memchr" version = "2.7.6" @@ -2132,6 +2369,12 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + [[package]] name = "native-tls" version = "0.2.14" @@ -2144,7 +2387,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2171,6 +2414,22 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.5", + "smallvec", + "zeroize", +] + [[package]] name = "num-conv" version = "0.1.0" @@ -2186,6 +2445,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -2193,6 +2463,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] @@ -2205,6 +2476,26 @@ dependencies = [ "libc", ] +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.16", + "http 0.2.12", + "rand 0.8.5", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror 1.0.69", + "url", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -2217,12 +2508,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "opaque-debug" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" - [[package]] name = "openapiv3-paper" version = "2.0.2" @@ -2234,6 +2519,38 @@ dependencies = [ "serde_json", ] +[[package]] +name = "openidconnect" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f47e80a9cfae4462dd29c41e987edd228971d6565553fbc14b8a11e666d91590" +dependencies = [ + "base64 0.13.1", + "chrono", + "dyn-clone", + "ed25519-dalek", + "hmac", + "http 0.2.12", + "itertools 0.10.5", + "log", + "oauth2", + "p256", + "p384", + "rand 0.8.5", + "rsa", + "serde", + "serde-value", + "serde_derive", + "serde_json", + "serde_path_to_error", + "serde_plain", + "serde_with", + "sha2", + "subtle", + "thiserror 1.0.69", + "url", +] + [[package]] name = "openssl" version = "0.10.75" @@ -2278,6 +2595,39 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "ordered-float" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paperclip" version = "0.9.5" @@ -2285,7 +2635,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa5b33308ca3f5902ccef8aa51f72dd71d6ee9f1c3cd04ac2e77eec33fe1da4f" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "once_cell", "openapiv3-paper", "paperclip-actix", @@ -2403,6 +2753,43 @@ dependencies = [ "windows-link", ] +[[package]] +name = "pbjson" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "pbjson-build" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" +dependencies = [ + "heck 0.5.0", + "itertools 0.13.0", + "prost", + "prost-types", +] + +[[package]] +name = "pbjson-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54e5e7bfb1652f95bc361d76f3c780d8e526b134b85417e774166ee941f0887" +dependencies = [ + "bytes", + "chrono", + "pbjson", + "pbjson-build", + "prost", + "prost-build", + "serde", +] + [[package]] name = "pem" version = "3.0.6" @@ -2413,12 +2800,31 @@ dependencies = [ "serde_core", ] +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.12.1", +] + [[package]] name = "phf" version = "0.10.1" @@ -2509,6 +2915,26 @@ dependencies = [ "siphasher 1.0.1", ] +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2522,23 +2948,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.32" +name = "pkcs1" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] [[package]] -name = "polyval" -version = "0.6.2" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", + "der", + "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -2595,6 +3030,25 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.111", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -2626,6 +3080,58 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck 0.5.0", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.111", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + [[package]] name = "pulldown-cmark" version = "0.13.0" @@ -2778,6 +3284,26 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "regex" version = "1.12.2" @@ -2815,9 +3341,50 @@ checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.27", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper-rustls 0.24.2", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + +[[package]] +name = "reqwest" +version = "0.12.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "3b4c14b2d9afca6a60277086b0cc6a6ae0b568f6f7916c943a8cdc79f8be240f" dependencies = [ "base64 0.22.1", "bytes", @@ -2825,10 +3392,10 @@ dependencies = [ "futures-core", "h2 0.4.12", "http 1.4.0", - "http-body", + "http-body 1.0.1", "http-body-util", - "hyper", - "hyper-rustls", + "hyper 1.8.1", + "hyper-rustls 0.27.7", "hyper-tls", "hyper-util", "js-sys", @@ -2841,10 +3408,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tokio-native-tls", - "tower", + "tower 0.5.2", "tower-http", "tower-service", "url", @@ -2862,7 +3429,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.4.0", - "reqwest", + "reqwest 0.12.26", "serde", "thiserror 1.0.69", "tower-service", @@ -2879,9 +3446,9 @@ dependencies = [ "futures", "getrandom 0.2.16", "http 1.4.0", - "hyper", + "hyper 1.8.1", "parking_lot 0.11.2", - "reqwest", + "reqwest 0.12.26", "reqwest-middleware", "retry-policies", "thiserror 1.0.69", @@ -2899,6 +3466,16 @@ dependencies = [ "rand 0.8.5", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -2922,6 +3499,26 @@ dependencies = [ "xmlparser", ] +[[package]] +name = "rsa" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -2944,19 +3541,63 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" dependencies = [ + "log", "once_cell", + "ring", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.103.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.5.1", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" version = "1.13.1" @@ -2966,6 +3607,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.103.8" @@ -2990,21 +3641,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] -name = "schannel" -version = "0.1.28" +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot 0.12.5", +] + +[[package]] +name = "schemars" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ - "windows-sys 0.61.2", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] -name = "scheduled-thread-pool" -version = "0.2.7" +name = "schemars" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +checksum = "9558e172d4e8533736ba97870c4b2cd63f84b382a3d6eb063da41b91cce17289" dependencies = [ - "parking_lot 0.12.5", + "dyn-clone", + "ref-cast", + "serde", + "serde_json", ] [[package]] @@ -3035,6 +3710,30 @@ dependencies = [ "tendril", ] +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + [[package]] name = "security-framework" version = "2.11.1" @@ -3042,7 +3741,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -3093,6 +3805,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-value" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c" +dependencies = [ + "ordered-float", + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3126,6 +3848,26 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_plain" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1fc6db65a611022b23a0dec6975d63fb80a302cb3388835ff02c097258d50" +dependencies = [ + "serde", +] + [[package]] name = "serde_spanned" version = "1.0.3" @@ -3147,6 +3889,37 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.12.1", + "schemars 0.9.0", + "schemars 1.1.0", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.111", +] + [[package]] name = "serde_yaml" version = "0.9.34+deprecated" @@ -3193,9 +3966,9 @@ dependencies = [ [[package]] name = "shell-words" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77" [[package]] name = "shlex" @@ -3212,11 +3985,21 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "simple_asn1" @@ -3285,6 +4068,22 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -3396,6 +4195,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.2" @@ -3416,6 +4221,17 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "system-configuration-sys 0.5.0", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -3423,8 +4239,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.10.0", - "core-foundation", - "system-configuration-sys", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", ] [[package]] @@ -3505,6 +4331,7 @@ dependencies = [ name = "thoth" version = "0.13.15" dependencies = [ + "base64 0.22.1", "clap", "dialoguer", "dotenv", @@ -3514,6 +4341,7 @@ dependencies = [ "thoth-errors", "thoth-export-server", "tokio", + "zitadel", ] [[package]] @@ -3544,6 +4372,7 @@ dependencies = [ "thoth-errors", "tokio", "uuid", + "zitadel", ] [[package]] @@ -3552,16 +4381,15 @@ version = "0.13.15" dependencies = [ "actix-cors", "actix-http", - "actix-identity", - "actix-session", "actix-web", + "base64 0.22.1", "env_logger", "futures-util", "log", "serde", - "serde_json", "thoth-api", "thoth-errors", + "zitadel", ] [[package]] @@ -3570,7 +4398,7 @@ version = "0.13.15" dependencies = [ "chrono", "graphql_client", - "reqwest", + "reqwest 0.12.26", "reqwest-middleware", "reqwest-retry", "serde", @@ -3593,11 +4421,12 @@ dependencies = [ "juniper", "marc", "phf 0.11.3", - "reqwest", + "reqwest 0.12.26", "reqwest-middleware", "serde", "serde_json", "thiserror 2.0.17", + "tonic", "uuid", "xml-rs", ] @@ -3708,13 +4537,34 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls", + "rustls 0.23.35", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +dependencies = [ + "futures-core", + "pin-project-lite", "tokio", ] @@ -3762,6 +4612,70 @@ dependencies = [ "winnow", ] +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.22.1", + "bytes", + "h2 0.4.12", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.8.1", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost", + "rustls-native-certs", + "rustls-pemfile 2.2.0", + "socket2 0.5.10", + "tokio", + "tokio-rustls 0.26.4", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-types" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0081d8ee0847d01271392a5aebe960a4600f5d4da6c67648a6382a0940f8b367" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower" version = "0.5.2" @@ -3771,7 +4685,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 1.0.2", "tokio", "tower-layer", "tower-service", @@ -3779,18 +4693,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags 2.10.0", "bytes", "futures-util", "http 1.4.0", - "http-body", + "http-body 1.0.1", "iri-string", "pin-project-lite", - "tower", + "tower 0.5.2", "tower-layer", "tower-service", ] @@ -3881,16 +4795,6 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" -[[package]] -name = "universal-hash" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "unsafe-libyaml" version = "0.2.11" @@ -4070,6 +4974,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "winapi" version = "0.3.9" @@ -4162,6 +5072,15 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -4198,6 +5117,21 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -4231,6 +5165,12 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4243,6 +5183,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4255,6 +5201,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4279,6 +5231,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4291,6 +5249,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4303,6 +5267,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4315,6 +5285,12 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4333,6 +5309,16 @@ version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "wit-bindgen" version = "0.46.0" @@ -4460,6 +5446,30 @@ dependencies = [ "syn 2.0.111", ] +[[package]] +name = "zitadel" +version = "5.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168b66027ca4fd1aa3c529f1359a59f94495db612b57223bf933b2900df4e052" +dependencies = [ + "actix-web", + "base64-compat", + "custom_error", + "jsonwebtoken", + "openidconnect", + "pbjson-types", + "prost", + "prost-types", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_urlencoded", + "time", + "tokio", + "tonic", + "tonic-types", +] + [[package]] name = "zstd" version = "0.13.3" diff --git a/Cargo.toml b/Cargo.toml index 4919b26d..ad60d07f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,8 +19,10 @@ thoth-api = { version = "=0.13.15", path = "thoth-api", features = ["backend"] } thoth-api-server = { version = "=0.13.15", path = "thoth-api-server" } thoth-errors = { version = "=0.13.15", path = "thoth-errors" } thoth-export-server = { version = "=0.13.15", path = "thoth-export-server" } +base64 = "0.22.1" clap = { version = "4.5.32", features = ["cargo", "env"] } dialoguer = { version = "0.11.0", features = ["password"] } dotenv = "0.15.0" lazy_static = "1.5.0" tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] } +zitadel = { version = "5.5.1", features = ["api", "interceptors"]} diff --git a/Makefile b/Makefile index c2aa7560..4ccf6d6d 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ .PHONY: \ help \ run-db \ + run-zitadel-db \ run-redis \ + run-zitadel \ run-graphql-api \ run-export-api \ build \ @@ -23,7 +25,9 @@ help: @echo "Available targets:" @echo " help Show this help" @echo " run-db Start PostgreSQL (docker)" + @echo " run-zitadel-db Start Zitadel PostgreSQL (docker)" @echo " run-redis Start Redis (docker)" + @echo " run-zitadel Start Zitadel (docker)" @echo " run-graphql-api Run GraphQL API (cargo)" @echo " run-export-api Run export API (cargo)" @echo " build Build the workspace" @@ -38,9 +42,15 @@ help: run-db: docker compose up db +run-zitadel-db: + docker compose up zitadel-db + run-redis: docker compose up redis +run-zitadel: + docker compose up zitadel + run-graphql-api: build RUST_BACKTRACE=1 cargo run init @@ -74,3 +84,4 @@ migration: mkdir -p $$dir; \ touch $$dir/up.sql; \ touch $$dir/down.sql; + diff --git a/docker-compose.yml b/docker-compose.yml index 99300d35..e2b86094 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,8 +9,29 @@ services: env_file: - .env + zitadel-db: + image: postgres:17 + container_name: "zitadel_db" + volumes: + - ./db/_zitadel:/var/lib/postgresql/data + environment: + POSTGRES_PASSWORD: postgres + redis: image: redis:alpine container_name: "thoth_redis" ports: - "6379:6379" + + zitadel: + image: ghcr.io/zitadel/zitadel:v3.2.2 + command: 'start-from-init --masterkey "${ZITADEL_MASTERKEY}" --tlsMode disabled' + container_name: "zitadel" + ports: + - "8282:8080" + env_file: + - .env + volumes: + - ./machinekey:/machinekey + depends_on: + - zitadel-db diff --git a/src/bin/arguments/mod.rs b/src/bin/arguments/mod.rs index 53d75d51..d363c496 100644 --- a/src/bin/arguments/mod.rs +++ b/src/bin/arguments/mod.rs @@ -42,37 +42,14 @@ pub fn port(default_value: &'static str, env_value: &'static str) -> Arg { .num_args(1) } -pub fn domain() -> Arg { - Arg::new("domain") - .short('d') - .long("domain") - .value_name("THOTH_DOMAIN") - .env("THOTH_DOMAIN") - .default_value("localhost") - .help("Authentication cookie domain") - .num_args(1) -} - pub fn key() -> Arg { Arg::new("key") .short('k') - .long("secret-key") - .value_name("SECRET") - .env("SECRET_KEY") - .help("Authentication cookie secret key") - .num_args(1) -} - -pub fn session() -> Arg { - Arg::new("duration") - .short('s') - .long("session-length") - .value_name("DURATION") - .env("SESSION_DURATION_SECONDS") - .default_value("3600") - .help("Authentication cookie session duration (seconds)") + .long("private-key") + .value_name("PRIVATE_KEY") + .env("PRIVATE_KEY") + .help("Thoth's GraphQL API zitadel private key (base64-encoded JSON key)") .num_args(1) - .value_parser(value_parser!(i64)) } pub fn gql_url() -> Arg { @@ -108,6 +85,27 @@ pub fn export_url() -> Arg { .num_args(1) } +pub fn zitadel_url() -> Arg { + Arg::new("zitadel-url") + .short('z') + .long("zitadel-url") + .value_name("ZITADEL_URL") + .env("ZITADEL_URL") + .default_value("http://localhost:8282") + .help("Zitadel's, public facing, root URL.") + .num_args(1) +} + +pub fn thoth_pat() -> Arg { + Arg::new("thoth-pat") + .short('P') + .long("thoth-pat") + .value_name("THOTH_PAT") + .env("THOTH_PAT") + .help("Thoth service account Personal Access Token (PAT)") + .num_args(1) +} + pub fn threads(env_value: &'static str) -> Arg { Arg::new("threads") .short('t') diff --git a/src/bin/commands/mod.rs b/src/bin/commands/mod.rs index f6a585c9..797914d6 100644 --- a/src/bin/commands/mod.rs +++ b/src/bin/commands/mod.rs @@ -3,18 +3,15 @@ use clap::Command; use lazy_static::lazy_static; use thoth::{ api::{ - db::{ - init_pool as init_pg_pool, revert_migrations as revert_db_migrations, - run_migrations as run_db_migrations, PgPool, - }, + db::{revert_migrations as revert_db_migrations, run_migrations as run_db_migrations}, redis::{init_pool as init_redis_pool, RedisPool}, }, errors::ThothResult, }; -pub(super) mod account; pub(super) mod cache; pub(super) mod start; +pub(super) mod zitadel; lazy_static! { pub(super) static ref INIT: Command = Command::new("init") @@ -25,9 +22,7 @@ lazy_static! { .arg(arguments::threads("GRAPHQL_API_THREADS")) .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) .arg(arguments::gql_url()) - .arg(arguments::domain()) - .arg(arguments::key()) - .arg(arguments::session()); + .arg(arguments::key()); } lazy_static! { @@ -37,11 +32,6 @@ lazy_static! { .arg(arguments::revert()); } -fn get_pg_pool(arguments: &clap::ArgMatches) -> PgPool { - let database_url = arguments.get_one::("db").unwrap(); - init_pg_pool(database_url) -} - fn get_redis_pool(arguments: &clap::ArgMatches) -> RedisPool { let redis_url = arguments.get_one::("redis").unwrap(); init_redis_pool(redis_url) diff --git a/src/bin/commands/start.rs b/src/bin/commands/start.rs index 0235581c..bfc7f471 100644 --- a/src/bin/commands/start.rs +++ b/src/bin/commands/start.rs @@ -17,9 +17,7 @@ lazy_static! { .arg(arguments::threads("GRAPHQL_API_THREADS")) .arg(arguments::keep_alive("GRAPHQL_API_KEEP_ALIVE")) .arg(arguments::gql_url()) - .arg(arguments::domain()) - .arg(arguments::key()) - .arg(arguments::session()), + .arg(arguments::key()), ) .subcommand( Command::new("export-api") @@ -41,9 +39,7 @@ pub fn graphql_api(arguments: &ArgMatches) -> ThothResult<()> { let threads = *arguments.get_one::("threads").unwrap(); let keep_alive = *arguments.get_one::("keep-alive").unwrap(); let url = arguments.get_one::("gql-url").unwrap().to_owned(); - let domain = arguments.get_one::("domain").unwrap().to_owned(); - let secret_str = arguments.get_one::("key").unwrap().to_owned(); - let session_duration = *arguments.get_one::("duration").unwrap(); + let private_key = arguments.get_one::("key").unwrap().to_owned(); api_server( database_url, host, @@ -51,9 +47,7 @@ pub fn graphql_api(arguments: &ArgMatches) -> ThothResult<()> { threads, keep_alive, url, - domain, - secret_str, - session_duration, + private_key, ) .map_err(|e| e.into()) } diff --git a/src/bin/commands/zitadel.rs b/src/bin/commands/zitadel.rs new file mode 100644 index 00000000..2859ea84 --- /dev/null +++ b/src/bin/commands/zitadel.rs @@ -0,0 +1,158 @@ +use crate::arguments; +use base64::{engine::general_purpose, Engine as _}; +use clap::{ArgMatches, Command}; +use lazy_static::lazy_static; +use thoth::errors::{ThothError, ThothResult}; +use zitadel::api::{ + clients::ClientBuilder, + zitadel::app::v1::{ + ApiAuthMethodType, OidcAppType, OidcAuthMethodType, OidcGrantType, OidcResponseType, + OidcTokenType, OidcVersion, + }, + zitadel::authn::v1::KeyType, + zitadel::management::v1::{ + AddApiAppRequest, AddAppKeyRequest, AddOidcAppRequest, AddProjectRequest, + AddProjectRoleRequest, AddUserGrantRequest, + }, + zitadel::project::v1::PrivateLabelingSetting, + zitadel::user::v2::{ListUsersRequest, UserFieldName}, +}; + +lazy_static! { + pub(crate) static ref COMMAND: Command = Command::new("zitadel") + .about("Manage Zitadel workflows") + .arg(arguments::zitadel_url()) + .arg(arguments::thoth_pat()) + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand(Command::new("setup").about("Intial setup of OIDC APPs in zitadel")); +} + +pub fn setup(arguments: &ArgMatches) -> ThothResult<()> { + let zitadel_url = arguments.get_one::("zitadel-url").unwrap(); + let pat = arguments.get_one::("thoth-pat").unwrap(); + let runtime = tokio::runtime::Builder::new_multi_thread() + .worker_threads(1) + .enable_all() + .build()?; + + runtime.block_on(async { + let mut management_client = ClientBuilder::new(zitadel_url) + .with_access_token(pat) + .build_management_client() + .await?; + let mut user_client = ClientBuilder::new(zitadel_url) + .with_access_token(pat) + .build_user_client() + .await?; + + // Create Zitadel project + let project_name = "Thoth"; + let project = management_client + .add_project(AddProjectRequest { + name: project_name.to_string(), + project_role_assertion: false, + project_role_check: false, + has_project_check: false, + private_labeling_setting: PrivateLabelingSetting::EnforceProjectResourceOwnerPolicy + as i32, + }) + .await? + .into_inner(); + println!("\n✅ Created Zitadel project: {}", project_name); + + // Create project user roles + let roles = [ + ("SUPERUSER", "Superuser", "Superusers"), + ("PUBLISHER_ADMIN", "Publisher Admin", "Publisher admins"), + ("PUBLISHER_USER", "Publisher User", "Publisher users"), + ]; + for (role_key, display_name, group) in roles { + management_client + .add_project_role(AddProjectRoleRequest { + project_id: project.id.clone(), + role_key: role_key.to_string(), + display_name: display_name.to_string(), + group: group.to_string(), + }) + .await?; + println!("\n✅ Added project role: {}", role_key); + } + + // Assign SUPERUSER role to default accounts + let users = user_client + .list_users(ListUsersRequest { + query: None, + sorting_column: UserFieldName::CreationDate as i32, + queries: vec![], + }) + .await? + .into_inner() + .result; + for user in users { + management_client + .add_user_grant(AddUserGrantRequest { + user_id: user.user_id.clone(), + project_id: project.id.clone(), + project_grant_id: "".to_string(), + role_keys: vec!["SUPERUSER".to_string()], + }) + .await?; + println!("\n✅ Granted SUPERUSER role to user: {}", user.username); + } + + // Create Zitadel APPs for GraphQL API and APP + let graphql_api_name = "Thoth GraphQL API"; + let graphql_api = management_client + .add_api_app(AddApiAppRequest { + project_id: project.id.clone(), + name: graphql_api_name.to_string(), + auth_method_type: ApiAuthMethodType::PrivateKeyJwt as i32, + }) + .await? + .into_inner(); + println!("\n✅ Created API app: {}", graphql_api_name); + + let graphql_api_key = management_client + .add_app_key(AddAppKeyRequest { + project_id: project.id.clone(), + app_id: graphql_api.app_id, + r#type: KeyType::Json as i32, + expiration_date: None, + }) + .await? + .into_inner(); + let encoded_key = general_purpose::STANDARD.encode(&graphql_api_key.key_details); + println!("\n✅ {} application key generated.", graphql_api_name); + println!("👉 Please copy the following and add it to the `.env` file as `PRIVATE_KEY`:\n"); + println!("PRIVATE_KEY={}\n", encoded_key); + + let app_name = "Thoth APP"; + management_client + .add_oidc_app(AddOidcAppRequest { + project_id: project.id.clone(), + name: app_name.to_string(), + redirect_uris: vec!["http://localhost:8080/callback".to_string()], + response_types: vec![OidcResponseType::Code as i32], + grant_types: vec![OidcGrantType::AuthorizationCode as i32], + app_type: OidcAppType::UserAgent as i32, + auth_method_type: OidcAuthMethodType::None as i32, // PKCE + post_logout_redirect_uris: vec!["http://localhost:8080/logout".to_string()], + version: OidcVersion::OidcVersion10 as i32, + dev_mode: true, + access_token_type: OidcTokenType::Bearer as i32, + access_token_role_assertion: false, + id_token_role_assertion: false, + id_token_userinfo_assertion: false, + clock_skew: None, + additional_origins: vec!["http://localhost:8080".to_string()], + skip_native_app_success_page: false, + back_channel_logout_uri: "".to_string(), + login_version: None, + }) + .await?; + println!("\n✅ Created OIDC app: {}", app_name); + + Ok::<(), ThothError>(()) + }) +} diff --git a/src/bin/thoth.rs b/src/bin/thoth.rs index 2d263c37..6ee60f80 100644 --- a/src/bin/thoth.rs +++ b/src/bin/thoth.rs @@ -11,8 +11,8 @@ lazy_static::lazy_static! { .subcommand(commands::MIGRATE.clone()) .subcommand(commands::start::COMMAND.clone()) .subcommand(commands::INIT.clone()) - .subcommand(commands::account::COMMAND.clone()) - .subcommand(commands::cache::COMMAND.clone()); + .subcommand(commands::cache::COMMAND.clone()) + .subcommand(commands::zitadel::COMMAND.clone()); } fn main() -> thoth::errors::ThothResult<()> { @@ -30,16 +30,14 @@ fn main() -> thoth::errors::ThothResult<()> { commands::run_migrations(arguments)?; commands::start::graphql_api(arguments) } - Some(("account", arguments)) => match arguments.subcommand() { - Some(("register", _)) => commands::account::register(arguments), - Some(("publishers", _)) => commands::account::publishers(arguments), - Some(("password", _)) => commands::account::password(arguments), - _ => unreachable!(), - }, Some(("cache", arguments)) => match arguments.subcommand() { Some(("delete", _)) => commands::cache::delete(arguments), _ => unreachable!(), }, + Some(("zitadel", arguments)) => match arguments.subcommand() { + Some(("setup", _)) => commands::zitadel::setup(arguments), + _ => unreachable!(), + }, _ => unreachable!(), } } diff --git a/thoth-api-server/Cargo.toml b/thoth-api-server/Cargo.toml index 833837b6..11430e9e 100644 --- a/thoth-api-server/Cargo.toml +++ b/thoth-api-server/Cargo.toml @@ -14,10 +14,9 @@ thoth-errors = { version = "=0.13.15", path = "../thoth-errors" } actix-web = "4.10" actix-cors = "0.7.1" actix-http = "3.10.0" -actix-identity = "0.8.0" -actix-session = { version = "0.10.1", features = ["cookie-session"] } +base64 = "0.22.1" env_logger = "0.11.7" futures-util = "0.3.31" log = "0.4.26" serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +zitadel = { version = "5.5.1", features = ["actix"]} diff --git a/thoth-api-server/src/lib.rs b/thoth-api-server/src/lib.rs index 318faaf0..08bd8b5a 100644 --- a/thoth-api-server/src/lib.rs +++ b/thoth-api-server/src/lib.rs @@ -4,28 +4,27 @@ mod logger; use std::{io, sync::Arc, time::Duration}; use actix_cors::Cors; -use actix_identity::{Identity, IdentityMiddleware}; -use actix_session::{config::PersistentSession, storage::CookieSessionStore, SessionMiddleware}; use actix_web::{ - cookie::{time::Duration as CookieDuration, Key}, - error, get, + get, http::header, middleware::Compress, post, web::{Data, Json}, - App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer, Result, + App, Error, HttpResponse, HttpServer, Result, }; +use base64::{engine::general_purpose, Engine as _}; use serde::Serialize; use thoth_api::{ - account::model::{AccountDetails, DecodedToken, LoginCredentials}, - account::service::{get_account, get_account_details, login}, db::{init_pool, PgPool}, graphql::{ model::{create_schema, Context, Schema}, GraphQLRequest, }, }; -use thoth_errors::ThothError; +use zitadel::{ + actix::introspection::{IntrospectedUser, IntrospectionConfigBuilder}, + credentials::Application, +}; use crate::graphiql::graphiql_source; use crate::logger::{BodyLogger, Logger}; @@ -91,10 +90,10 @@ async fn graphql_schema(st: Data>) -> HttpResponse { async fn graphql( st: Data>, pool: Data, - token: DecodedToken, + user: Option, data: Json, ) -> Result { - let ctx = Context::new(pool.into_inner(), token); + let ctx = Context::new(pool.into_inner(), user); let result = data.execute(&st, &ctx).await; match result.is_ok() { true => Ok(HttpResponse::Ok().json(result)), @@ -102,86 +101,6 @@ async fn graphql( } } -#[post("/account/login")] -async fn login_credentials( - request: HttpRequest, - payload: Json, - pool: Data, -) -> Result { - let r = payload.into_inner(); - - login(&r.email, &r.password, &pool) - .and_then(|account| { - account.issue_token(&pool)?; - let details = get_account_details(&account.email, &pool).unwrap(); - let user_string = serde_json::to_string(&details) - .map_err(|_| ThothError::InternalError("Serder error".into()))?; - Identity::login(&request.extensions(), user_string) - .map_err(|_| ThothError::InternalError("Failed to store session cookie".into()))?; - Ok(HttpResponse::Ok().json(details)) - }) - .map_err(error::ErrorUnauthorized) -} - -#[post("/account/token/renew")] -async fn login_session( - request: HttpRequest, - token: DecodedToken, - identity: Option, - pool: Data, -) -> Result { - let email = match identity { - Some(session) => { - let id = session.id().map_err(|_| ThothError::Unauthorised)?; - let details: AccountDetails = - serde_json::from_str(&id).map_err(|_| ThothError::Unauthorised)?; - details.email - } - None => { - token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - let t = token.jwt.unwrap(); - t.sub - } - }; - - get_account(&email, &pool) - .and_then(|account| { - account.issue_token(&pool)?; - let details = get_account_details(&account.email, &pool).unwrap(); - let user_string = serde_json::to_string(&details) - .map_err(|_| ThothError::InternalError("Serder error".into()))?; - Identity::login(&request.extensions(), user_string) - .map_err(|_| ThothError::InternalError("Failed to store session cookie".into()))?; - Ok(HttpResponse::Ok().json(details)) - }) - .map_err(error::ErrorUnauthorized) -} - -#[get("/account")] -async fn account_details( - token: DecodedToken, - identity: Option, - pool: Data, -) -> Result { - let email = match identity { - Some(session) => { - let id = session.id().map_err(|_| ThothError::Unauthorised)?; - let details: AccountDetails = - serde_json::from_str(&id).map_err(|_| ThothError::Unauthorised)?; - details.email - } - None => { - token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - let t = token.jwt.unwrap(); - t.sub - } - }; - - get_account_details(&email, &pool) - .map(|account_details| HttpResponse::Ok().json(account_details)) - .map_err(error::ErrorUnauthorized) -} - #[allow(clippy::too_many_arguments)] #[actix_web::main] pub async fn start_server( @@ -191,33 +110,26 @@ pub async fn start_server( threads: usize, keep_alive: u64, public_url: String, - domain: String, - secret_str: String, - session_duration: i64, + private_key: String, ) -> io::Result<()> { env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); + let decoded_private_key = general_purpose::STANDARD + .decode(&private_key) + .expect("Failed to base64-decode private key"); + let decoded_str = + std::str::from_utf8(&decoded_private_key).expect("Decoded key is not valid UTF-8"); + let auth = IntrospectionConfigBuilder::new("http://localhost:8282") + .with_jwt_profile(Application::load_from_json(decoded_str).unwrap()) + .build() + .await + .unwrap(); + HttpServer::new(move || { App::new() .wrap(Compress::default()) .wrap(Logger::default()) .wrap(BodyLogger) - .wrap(IdentityMiddleware::default()) - .wrap( - SessionMiddleware::builder( - CookieSessionStore::default(), - Key::from(secret_str.as_bytes()), - ) - .cookie_name("auth".to_string()) - .cookie_path("/".to_string()) - .cookie_domain(Some(domain.clone())) - .cookie_secure(domain.clone().ne("localhost")) // Authentication requires https unless running on localhost - .session_lifecycle( - PersistentSession::default() - .session_ttl(CookieDuration::seconds(session_duration)), - ) - .build(), - ) .wrap( Cors::default() .allowed_methods(vec!["GET", "POST", "OPTIONS"]) @@ -226,6 +138,7 @@ pub async fn start_server( .allowed_header(header::CONTENT_TYPE) .supports_credentials(), ) + .app_data(auth.clone()) .app_data(Data::new(ApiConfig::new(public_url.clone()))) .app_data(Data::new(init_pool(&database_url))) .app_data(Data::new(Arc::new(create_schema()))) @@ -233,9 +146,6 @@ pub async fn start_server( .service(graphql_index) .service(graphql) .service(graphiql_interface) - .service(login_credentials) - .service(login_session) - .service(account_details) .service(graphql_schema) }) .workers(threads) diff --git a/thoth-api/Cargo.toml b/thoth-api/Cargo.toml index 637a0357..e403f6f6 100644 --- a/thoth-api/Cargo.toml +++ b/thoth-api/Cargo.toml @@ -21,7 +21,8 @@ backend = [ "jsonwebtoken", "deadpool-redis", "rand", - "argon2rs" + "argon2rs", + "zitadel" ] [dependencies] @@ -48,6 +49,7 @@ serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" strum = { version = "0.27.1", features = ["derive"] } uuid = { version = "1.16.0", features = ["serde", "v4"] } +zitadel = { version = "5.5.1", features = ["actix"], optional = true} [dev-dependencies] tokio = { version = "1.44", features = ["macros"] } diff --git a/thoth-api/migrations/20260107_v1.0.0/down.sql b/thoth-api/migrations/20260107_v1.0.0/down.sql new file mode 100644 index 00000000..a94277fe --- /dev/null +++ b/thoth-api/migrations/20260107_v1.0.0/down.sql @@ -0,0 +1,98 @@ +-- Recreate the `account` table +CREATE TABLE account ( + account_id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + name TEXT NOT NULL CHECK (octet_length(name) >= 1), + surname TEXT NOT NULL CHECK (octet_length(surname) >= 1), + email TEXT NOT NULL CHECK (octet_length(email) >= 1), + hash BYTEA NOT NULL, + salt TEXT NOT NULL CHECK (octet_length(salt) >= 1), + is_superuser BOOLEAN NOT NULL DEFAULT False, + is_bot BOOLEAN NOT NULL DEFAULT False, + is_active BOOLEAN NOT NULL DEFAULT True, + token TEXT NULL CHECK (OCTET_LENGTH(token) >= 1), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP +); +SELECT diesel_manage_updated_at('account'); + +-- case-insensitive UNIQ index on email +CREATE UNIQUE INDEX email_uniq_idx ON account(lower(email)); + +-- Recreate the `publisher_account` table +CREATE TABLE publisher_account ( + account_id UUID NOT NULL REFERENCES account(account_id) ON DELETE CASCADE, + publisher_id UUID NOT NULL REFERENCES publisher(publisher_id) ON DELETE CASCADE, + is_admin BOOLEAN NOT NULL DEFAULT False, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (account_id, publisher_id) +); +SELECT diesel_manage_updated_at('publisher_account'); + +-- Rename column user_id → account_id and change type to UUID +ALTER TABLE abstract_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE affiliation_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE biography_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE contact_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE contribution_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE contributor_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE funding_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE imprint_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE institution_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE issue_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE language_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE location_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE price_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE publication_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE publisher_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE reference_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE series_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE subject_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE title_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE work_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; +ALTER TABLE work_relation_history ALTER COLUMN user_id TYPE UUID USING user_id::uuid; + +ALTER TABLE abstract_history RENAME COLUMN user_id TO account_id; +ALTER TABLE affiliation_history RENAME COLUMN user_id TO account_id; +ALTER TABLE biography_history RENAME COLUMN user_id TO account_id; +ALTER TABLE contact_history RENAME COLUMN user_id TO account_id; +ALTER TABLE contribution_history RENAME COLUMN user_id TO account_id; +ALTER TABLE contributor_history RENAME COLUMN user_id TO account_id; +ALTER TABLE funding_history RENAME COLUMN user_id TO account_id; +ALTER TABLE imprint_history RENAME COLUMN user_id TO account_id; +ALTER TABLE institution_history RENAME COLUMN user_id TO account_id; +ALTER TABLE issue_history RENAME COLUMN user_id TO account_id; +ALTER TABLE language_history RENAME COLUMN user_id TO account_id; +ALTER TABLE location_history RENAME COLUMN user_id TO account_id; +ALTER TABLE price_history RENAME COLUMN user_id TO account_id; +ALTER TABLE publication_history RENAME COLUMN user_id TO account_id; +ALTER TABLE publisher_history RENAME COLUMN user_id TO account_id; +ALTER TABLE reference_history RENAME COLUMN user_id TO account_id; +ALTER TABLE series_history RENAME COLUMN user_id TO account_id; +ALTER TABLE subject_history RENAME COLUMN user_id TO account_id; +ALTER TABLE title_history RENAME COLUMN user_id TO account_id; +ALTER TABLE work_history RENAME COLUMN user_id TO account_id; +ALTER TABLE work_relation_history RENAME COLUMN user_id TO account_id; + +-- Restore foreign key constraints +ALTER TABLE abstract_history ADD CONSTRAINT abstract_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE affiliation_history ADD CONSTRAINT affiliation_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE biography_history ADD CONSTRAINT biography_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE contact_history ADD CONSTRAINT contact_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE contribution_history ADD CONSTRAINT contribution_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE contributor_history ADD CONSTRAINT contributor_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE funding_history ADD CONSTRAINT funding_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE imprint_history ADD CONSTRAINT imprint_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE institution_history ADD CONSTRAINT institution_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE issue_history ADD CONSTRAINT issue_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE language_history ADD CONSTRAINT language_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE location_history ADD CONSTRAINT location_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE price_history ADD CONSTRAINT price_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE publication_history ADD CONSTRAINT publication_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE publisher_history ADD CONSTRAINT publisher_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE reference_history ADD CONSTRAINT reference_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE series_history ADD CONSTRAINT series_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE subject_history ADD CONSTRAINT subject_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE title_history ADD CONSTRAINT title_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE work_history ADD CONSTRAINT work_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); +ALTER TABLE work_relation_history ADD CONSTRAINT work_relation_history_account_id_fkey FOREIGN KEY (account_id) REFERENCES account(account_id); \ No newline at end of file diff --git a/thoth-api/migrations/20260107_v1.0.0/up.sql b/thoth-api/migrations/20260107_v1.0.0/up.sql new file mode 100644 index 00000000..156921b8 --- /dev/null +++ b/thoth-api/migrations/20260107_v1.0.0/up.sql @@ -0,0 +1,72 @@ +-- Drop foreign key constraints +ALTER TABLE abstract_history DROP CONSTRAINT IF EXISTS abstract_history_account_id_fkey; +ALTER TABLE affiliation_history DROP CONSTRAINT IF EXISTS affiliation_history_account_id_fkey; +ALTER TABLE biography_history DROP CONSTRAINT IF EXISTS biography_history_account_id_fkey; +ALTER TABLE contact_history DROP CONSTRAINT IF EXISTS contact_history_account_id_fkey; +ALTER TABLE contribution_history DROP CONSTRAINT IF EXISTS contribution_history_account_id_fkey; +ALTER TABLE contributor_history DROP CONSTRAINT IF EXISTS contributor_history_account_id_fkey; +ALTER TABLE funding_history DROP CONSTRAINT IF EXISTS funding_history_account_id_fkey; +ALTER TABLE imprint_history DROP CONSTRAINT IF EXISTS imprint_history_account_id_fkey; +ALTER TABLE institution_history DROP CONSTRAINT IF EXISTS institution_history_account_id_fkey; +ALTER TABLE institution_history DROP CONSTRAINT IF EXISTS funder_history_account_id_fkey; -- historical +ALTER TABLE issue_history DROP CONSTRAINT IF EXISTS issue_history_account_id_fkey; +ALTER TABLE language_history DROP CONSTRAINT IF EXISTS language_history_account_id_fkey; +ALTER TABLE location_history DROP CONSTRAINT IF EXISTS location_history_account_id_fkey; +ALTER TABLE price_history DROP CONSTRAINT IF EXISTS price_history_account_id_fkey; +ALTER TABLE publication_history DROP CONSTRAINT IF EXISTS publication_history_account_id_fkey; +ALTER TABLE publisher_history DROP CONSTRAINT IF EXISTS publisher_history_account_id_fkey; +ALTER TABLE reference_history DROP CONSTRAINT IF EXISTS reference_history_account_id_fkey; +ALTER TABLE series_history DROP CONSTRAINT IF EXISTS series_history_account_id_fkey; +ALTER TABLE subject_history DROP CONSTRAINT IF EXISTS subject_history_account_id_fkey; +ALTER TABLE title_history DROP CONSTRAINT IF EXISTS title_history_account_id_fkey; +ALTER TABLE work_history DROP CONSTRAINT IF EXISTS work_history_account_id_fkey; +ALTER TABLE work_relation_history DROP CONSTRAINT IF EXISTS work_relation_history_account_id_fkey; + +-- Rename column account_id to user_id and change type to TEXT +ALTER TABLE abstract_history RENAME COLUMN account_id TO user_id; +ALTER TABLE affiliation_history RENAME COLUMN account_id TO user_id; +ALTER TABLE biography_history RENAME COLUMN account_id TO user_id; +ALTER TABLE contact_history RENAME COLUMN account_id TO user_id; +ALTER TABLE contribution_history RENAME COLUMN account_id TO user_id; +ALTER TABLE contributor_history RENAME COLUMN account_id TO user_id; +ALTER TABLE funding_history RENAME COLUMN account_id TO user_id; +ALTER TABLE imprint_history RENAME COLUMN account_id TO user_id; +ALTER TABLE institution_history RENAME COLUMN account_id TO user_id; +ALTER TABLE issue_history RENAME COLUMN account_id TO user_id; +ALTER TABLE language_history RENAME COLUMN account_id TO user_id; +ALTER TABLE location_history RENAME COLUMN account_id TO user_id; +ALTER TABLE price_history RENAME COLUMN account_id TO user_id; +ALTER TABLE publication_history RENAME COLUMN account_id TO user_id; +ALTER TABLE publisher_history RENAME COLUMN account_id TO user_id; +ALTER TABLE reference_history RENAME COLUMN account_id TO user_id; +ALTER TABLE series_history RENAME COLUMN account_id TO user_id; +ALTER TABLE subject_history RENAME COLUMN account_id TO user_id; +ALTER TABLE title_history RENAME COLUMN account_id TO user_id; +ALTER TABLE work_history RENAME COLUMN account_id TO user_id; +ALTER TABLE work_relation_history RENAME COLUMN account_id TO user_id; + +ALTER TABLE abstract_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE affiliation_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE biography_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE contact_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE contribution_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE contributor_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE funding_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE imprint_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE institution_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE issue_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE language_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE location_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE price_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE publication_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE publisher_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE reference_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE series_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE subject_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE title_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE work_history ALTER COLUMN user_id TYPE TEXT; +ALTER TABLE work_relation_history ALTER COLUMN user_id TYPE TEXT; + +-- Drop the obsolete tables +DROP TABLE IF EXISTS publisher_account; +DROP TABLE IF EXISTS account; \ No newline at end of file diff --git a/thoth-api/src/account/handler.rs b/thoth-api/src/account/handler.rs deleted file mode 100644 index dfa36608..00000000 --- a/thoth-api/src/account/handler.rs +++ /dev/null @@ -1,216 +0,0 @@ -use diesel::prelude::*; -use dotenv::dotenv; -use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation}; -use regex::Regex; -use std::env; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; -use uuid::Uuid; - -use crate::account::{ - model::{ - Account, AccountAccess, AccountData, DecodedToken, LinkedPublisher, NewAccount, - NewPassword, NewPublisherAccount, PublisherAccount, Token, - }, - service::get_account, - util::{make_hash, make_salt}, -}; -use crate::db::PgPool; -use thoth_errors::{ThothError, ThothResult}; - -impl Account { - pub fn get_permissions(&self, pool: &PgPool) -> ThothResult> { - let publisher_accounts = self.get_publisher_accounts(pool)?; - let permissions: Vec = - publisher_accounts.into_iter().map(|p| p.into()).collect(); - Ok(permissions) - } - - pub fn get_publisher_accounts(&self, pool: &PgPool) -> ThothResult> { - use crate::schema::publisher_account::dsl::*; - let mut conn = pool.get()?; - - let publisher_accounts = publisher_account - .filter(account_id.eq(self.account_id)) - .load::(&mut conn) - .expect("Error loading publisher accounts"); - Ok(publisher_accounts) - } - - pub fn add_publisher_account( - &self, - pool: &PgPool, - linked_publisher: LinkedPublisher, - ) -> ThothResult { - use crate::schema::publisher_account::dsl::*; - let mut conn = pool.get()?; - let new_publisher_account = NewPublisherAccount { - account_id: self.account_id, - publisher_id: linked_publisher.publisher_id, - is_admin: linked_publisher.is_admin, - }; - diesel::insert_into(publisher_account) - .values(&new_publisher_account) - .get_result::(&mut conn) - .map_err(Into::into) - } - - pub fn get_account_access(&self, linked_publishers: Vec) -> AccountAccess { - AccountAccess { - is_superuser: self.is_superuser, - is_bot: self.is_bot, - linked_publishers, - } - } - - pub fn issue_token(&self, pool: &PgPool) -> ThothResult { - const DEFAULT_TOKEN_VALIDITY: i64 = 24 * 60 * 60; - let mut connection = pool.get()?; - dotenv().ok(); - let linked_publishers: Vec = - self.get_permissions(pool).unwrap_or_default(); - let namespace = self.get_account_access(linked_publishers); - let secret_str = env::var("SECRET_KEY").expect("SECRET_KEY must be set"); - let secret: &[u8] = secret_str.as_bytes(); - let now = SystemTime::now() - .duration_since(UNIX_EPOCH) - .map_err(|_| ThothError::InternalError("Unable to set token iat".into()))?; - let claim = Token { - sub: self.email.clone(), - exp: now.as_secs() as i64 + DEFAULT_TOKEN_VALIDITY, - iat: now.as_secs() as i64, - jti: Uuid::new_v4().to_string(), - namespace, - }; - let token = encode( - &Header::default(), - &claim, - &EncodingKey::from_secret(secret), - ) - .map_err(|_| ThothError::InternalError("Unable to create token".into())); - - use crate::schema::account::dsl; - let updated_account = diesel::update(dsl::account.find(self.account_id)) - .set(dsl::token.eq(token?)) - .get_result::(&mut connection) - .expect("Unable to set token"); - Ok(updated_account.token.unwrap()) - } -} - -impl From for NewAccount { - fn from(account_data: AccountData) -> Self { - let AccountData { - name, - surname, - email, - password, - is_superuser, - is_bot, - .. - } = account_data; - - let salt = make_salt(); - let hash = make_hash(&password, &salt).to_vec(); - Self { - name, - surname, - email, - hash, - salt, - is_superuser, - is_bot, - } - } -} - -impl From for LinkedPublisher { - fn from(publisher_account: PublisherAccount) -> Self { - let PublisherAccount { - publisher_id, - is_admin, - .. - } = publisher_account; - Self { - publisher_id, - is_admin, - } - } -} - -impl Token { - pub fn verify(token: &str) -> ThothResult { - dotenv().ok(); - let secret_str = env::var("SECRET_KEY").expect("SECRET_KEY must be set"); - let secret: &[u8] = secret_str.as_bytes(); - - let data = decode::( - token, - &DecodingKey::from_secret(secret), - &Validation::default(), - ) - .map_err(|_| ThothError::InvalidToken)?; - Ok(data.claims) - } - - pub fn account_id(&self, pool: &PgPool) -> Uuid { - get_account(&self.sub, pool).unwrap().account_id - } -} - -lazy_static::lazy_static! { - static ref BEARER_REGEXP : Regex = Regex::new(r"^Bearer\s(.*)$").expect("Bearer regexp failed!"); -} - -impl actix_web::FromRequest for DecodedToken { - type Error = actix_web::Error; - type Future = futures::future::Ready>; - - fn from_request(req: &actix_web::HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future { - let token = req - .headers() - .get(actix_web::http::header::AUTHORIZATION) - .and_then(|v| v.to_str().ok()) - .and_then(|authorization| { - BEARER_REGEXP - .captures(authorization) - .and_then(|captures| captures.get(1)) - }) - .map(|v| v.as_str()); - - futures::future::ready(Ok(match token { - None => DecodedToken { jwt: None }, - Some(token) => match Token::verify(token) { - Ok(decoded) => DecodedToken { jwt: Some(decoded) }, - Err(_) => DecodedToken { jwt: None }, - }, - })) - } -} - -impl NewPassword { - pub fn new(email: String, password: String) -> Self { - let salt = make_salt(); - let hash = make_hash(&password, &salt).to_vec(); - Self { email, hash, salt } - } -} - -impl PublisherAccount { - pub fn delete(&self, pool: &PgPool) -> ThothResult<()> { - use crate::schema::publisher_account::dsl::*; - - pool.get()?.transaction(|connection| { - diesel::delete( - publisher_account.filter( - account_id - .eq(self.account_id) - .and(publisher_id.eq(self.publisher_id)), - ), - ) - .execute(connection) - .map(|_| ()) - .map_err(Into::into) - }) - } -} diff --git a/thoth-api/src/account/mod.rs b/thoth-api/src/account/mod.rs index 225e37d9..65880be0 100644 --- a/thoth-api/src/account/mod.rs +++ b/thoth-api/src/account/mod.rs @@ -1,7 +1 @@ -#[cfg(feature = "backend")] -pub mod handler; pub mod model; -#[cfg(feature = "backend")] -pub mod service; -#[cfg(feature = "backend")] -pub mod util; diff --git a/thoth-api/src/account/model.rs b/thoth-api/src/account/model.rs index 773c54cc..b3a2be5b 100644 --- a/thoth-api/src/account/model.rs +++ b/thoth-api/src/account/model.rs @@ -2,10 +2,6 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::model::Timestamp; -#[cfg(feature = "backend")] -use crate::schema::account; -#[cfg(feature = "backend")] -use crate::schema::publisher_account; use thoth_errors::ThothError; use thoth_errors::ThothResult; @@ -26,18 +22,6 @@ pub struct Account { pub token: Option, } -#[cfg_attr(feature = "backend", derive(Insertable))] -#[cfg_attr(feature = "backend", diesel(table_name = account))] -pub struct NewAccount { - pub name: String, - pub surname: String, - pub email: String, - pub hash: Vec, - pub salt: String, - pub is_superuser: bool, - pub is_bot: bool, -} - #[derive(Debug)] pub struct AccountData { pub name: String, @@ -48,23 +32,6 @@ pub struct AccountData { pub is_bot: bool, } -#[cfg_attr(feature = "backend", derive(Queryable))] -pub struct PublisherAccount { - pub account_id: Uuid, - pub publisher_id: Uuid, - pub is_admin: bool, - pub created_at: Timestamp, - pub updated_at: Timestamp, -} - -#[cfg_attr(feature = "backend", derive(Insertable))] -#[cfg_attr(feature = "backend", diesel(table_name = publisher_account))] -pub struct NewPublisherAccount { - pub account_id: Uuid, - pub publisher_id: Uuid, - pub is_admin: bool, -} - #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AccountAccess { @@ -114,13 +81,6 @@ pub struct LoginCredentials { pub password: String, } -#[cfg_attr(feature = "backend", derive(AsChangeset), diesel(table_name = account))] -pub struct NewPassword { - pub email: String, - pub hash: Vec, - pub salt: String, -} - impl DecodedToken { pub fn get_user_permissions(&self) -> AccountAccess { if let Some(jwt) = &self.jwt { diff --git a/thoth-api/src/account/service.rs b/thoth-api/src/account/service.rs deleted file mode 100644 index 45943391..00000000 --- a/thoth-api/src/account/service.rs +++ /dev/null @@ -1,126 +0,0 @@ -use diesel::prelude::*; - -use crate::account::{ - model::{Account, AccountData, AccountDetails, LinkedPublisher, NewAccount, NewPassword}, - util::verify, -}; -use crate::db::PgPool; -use crate::model::publisher::Publisher; -use thoth_errors::{ThothError, ThothResult}; - -pub fn login(user_email: &str, user_password: &str, pool: &PgPool) -> ThothResult { - use crate::schema::account::dsl; - - let mut conn = pool.get()?; - let account = dsl::account - .filter(dsl::email.eq(user_email)) - .first::(&mut conn) - .map_err(|_| ThothError::Unauthorised)?; - - if verify(&account, user_password) { - Ok(account) - } else { - Err(ThothError::Unauthorised) - } -} - -pub fn get_account(email: &str, pool: &PgPool) -> ThothResult { - use crate::schema::account::dsl; - - let mut conn = pool.get()?; - let account = dsl::account - .filter(dsl::email.eq(email)) - .first::(&mut conn) - .map_err(|_| ThothError::Unauthorised)?; - Ok(account) -} - -pub fn get_account_details(email: &str, pool: &PgPool) -> ThothResult { - use crate::schema::account::dsl; - - let mut conn = pool.get()?; - let account = dsl::account - .filter(dsl::email.eq(email)) - .first::(&mut conn) - .map_err(|_| ThothError::Unauthorised)?; - let linked_publishers: Vec = account.get_permissions(pool).unwrap_or_default(); - let resource_access = account.get_account_access(linked_publishers); - let account_details = AccountDetails { - account_id: account.account_id, - name: account.name, - surname: account.surname, - email: account.email, - token: account.token, - created_at: account.created_at, - updated_at: account.updated_at, - resource_access, - }; - Ok(account_details) -} - -pub fn register( - pool: &PgPool, - name: String, - surname: String, - email: String, - password: String, - is_superuser: bool, - is_bot: bool, -) -> ThothResult { - use crate::schema::account::dsl; - - let mut connection = pool.get()?; - let account: NewAccount = AccountData { - name, - surname, - email, - password, - is_superuser, - is_bot, - } - .into(); - let created_account: Account = diesel::insert_into(dsl::account) - .values(&account) - .get_result::(&mut connection)?; - Ok(created_account) -} - -pub fn all_emails(pool: &PgPool) -> ThothResult> { - let mut connection = pool.get()?; - - use crate::schema::account::dsl; - let emails = dsl::account - .select((dsl::email, dsl::is_superuser, dsl::is_bot, dsl::is_active)) - .order(dsl::email.asc()) - .load::<(String, bool, bool, bool)>(&mut connection) - .map_err(|_| ThothError::InternalError("Unable to load records".into()))?; - Ok(emails) -} - -pub fn all_publishers(pool: &PgPool) -> ThothResult> { - let mut connection = pool.get()?; - - use crate::schema::publisher::dsl; - let publishers = dsl::publisher - .order(dsl::publisher_name.asc()) - .load::(&mut connection) - .map_err(|_| ThothError::InternalError("Unable to load records".into()))?; - Ok(publishers) -} - -pub fn update_password(email: &str, password: &str, pool: &PgPool) -> ThothResult { - let mut connection = pool.get()?; - - let new_password = NewPassword::new(email.to_string(), password.to_string()); - use crate::schema::account::dsl; - - let account_obj = dsl::account - .filter(dsl::email.eq(email)) - .first::(&mut connection) - .map_err(Into::::into)?; - - diesel::update(dsl::account.find(&account_obj.account_id)) - .set(&new_password) - .get_result(&mut connection) - .map_err(Into::into) -} diff --git a/thoth-api/src/account/util.rs b/thoth-api/src/account/util.rs deleted file mode 100644 index 79d86dca..00000000 --- a/thoth-api/src/account/util.rs +++ /dev/null @@ -1,30 +0,0 @@ -use argon2rs::argon2i_simple; - -use super::model::Account; - -pub fn make_salt() -> String { - use rand::Rng; - const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ\ - abcdefghijklmnopqrstuvwxyz\ - 0123456789)(*&^%$#@!~"; - const PASSWORD_LEN: usize = 128; - let mut rng = rand::rng(); - - let password: String = (0..PASSWORD_LEN) - .map(|_| { - let idx = rng.random_range(0..CHARSET.len()); - CHARSET[idx] as char - }) - .collect(); - password -} - -pub fn make_hash(password: &str, salt: &str) -> [u8; argon2rs::defaults::LENGTH] { - argon2i_simple(password, salt) -} - -pub fn verify(account: &Account, password: &str) -> bool { - let Account { hash, salt, .. } = account; - - make_hash(password, salt) == hash.as_ref() -} diff --git a/thoth-api/src/graphql/model.rs b/thoth-api/src/graphql/model.rs index a86552ca..b25c1bfd 100644 --- a/thoth-api/src/graphql/model.rs +++ b/thoth-api/src/graphql/model.rs @@ -3,9 +3,9 @@ use std::sync::Arc; use chrono::naive::NaiveDate; use juniper::{EmptySubscription, FieldError, FieldResult, RootNode}; use uuid::Uuid; +use zitadel::actix::introspection::IntrospectedUser; use super::utils::{Direction, Expression, MAX_SHORT_ABSTRACT_CHAR_LIMIT}; -use crate::account::model::{AccountAccess, DecodedToken}; use crate::db::PgPool; use crate::model::{ affiliation::{Affiliation, AffiliationOrderBy, NewAffiliation, PatchAffiliation}, @@ -47,21 +47,92 @@ use thoth_errors::{ThothError, ThothResult}; impl juniper::Context for Context {} -#[derive(Clone)] pub struct Context { pub db: Arc, - pub account_access: AccountAccess, - pub token: DecodedToken, + pub user: Option, +} + +trait UserAccess { + fn is_superuser(&self) -> bool; + fn can_edit(&self, publisher_id: &Uuid) -> ThothResult<()>; +} + +impl UserAccess for IntrospectedUser { + fn is_superuser(&self) -> bool { + self.project_roles + .as_ref() + .is_some_and(|roles| roles.contains_key("SUPERUSER")) + } + + /// Determines whether the user has edit permissions for the given `publisher_id`. + /// + /// A user is authorized to edit a publisher if: + /// - They have the `SUPERUSER` role (see [`is_superuser`]) — or + /// - Their `metadata` includes a `publishers` key containing a + /// comma-separated list of UUIDs they are associated with. + /// + /// ### Expected Metadata Format + /// + /// ```json + /// { + /// "publishers": "85fd969a-a16c-480b-b641-cb9adf979c3b, 12345678-9abc-def0-1234-56789abcdef0" + /// } + /// ``` + /// + /// The value **must** be a single string of UUIDs, separated by commas, + /// with optional whitespace. + /// + /// If the `publishers` key is missing, or does not contain the provided `publisher_id`, + /// the user is considered unauthorised. + /// + /// # Errors + /// + /// Returns [`ThothError::Unauthorised`] if the user is not a superuser and + /// does not have access to the given publisher. + fn can_edit(&self, publisher_id: &Uuid) -> ThothResult<()> { + if self.is_superuser() { + return Ok(()); + } + + self.metadata + .as_ref() + .and_then(|meta| meta.get("publishers")) + .map(|val| val.as_str()) + .map(|raw| { + raw.split(',') + .map(str::trim) + .filter_map(|s| Uuid::parse_str(s).ok()) + .any(|id| id == *publisher_id) + }) + .filter(|&matches| matches) + .map(|_| ()) + .ok_or(ThothError::Unauthorised) + } } impl Context { - pub fn new(pool: Arc, token: DecodedToken) -> Self { - Self { - db: pool, - account_access: token.get_user_permissions(), - token, + pub fn new(pool: Arc, user: Option) -> Self { + Self { db: pool, user } + } + + fn require_authentication(&self) -> ThothResult<&IntrospectedUser> { + self.user.as_ref().ok_or(ThothError::Unauthorised) + } + + fn require_superuser(&self) -> ThothResult<&IntrospectedUser> { + let user = self.require_authentication()?; + if user.is_superuser() { + Ok(user) + } else { + Err(ThothError::Unauthorised) } } + + fn require_publisher(&self, publisher_id: &Uuid) -> ThothResult<&IntrospectedUser> { + let user = self.require_authentication()?; + user.can_edit(publisher_id)?; + Ok(user) + } } #[derive(juniper::GraphQLInputObject)] @@ -1815,13 +1886,11 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for work to be created")] data: NewWork, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_imprint_id(&context.db, data.imprint_id)?)?; - + context.require_publisher(&publisher_id_from_imprint_id( + &context.db, + &data.imprint_id, + )?)?; data.validate()?; - Work::create(&context.db, &data).map_err(Into::into) } @@ -1830,12 +1899,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for publisher to be created")] data: NewPublisher, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - // Only superusers can create new publishers - NewPublisher has no ID field - if !context.account_access.is_superuser { - return Err(ThothError::Unauthorised.into()); - } - + context.require_superuser()?; Publisher::create(&context.db, &data).map_err(Into::into) } @@ -1844,9 +1908,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for imprint to be created")] data: NewImprint, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context.account_access.can_edit(data.publisher_id)?; - + context.require_publisher(&data.publisher_id)?; Imprint::create(&context.db, &data).map_err(Into::into) } @@ -1855,7 +1917,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for contributor to be created")] data: NewContributor, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; Contributor::create(&context.db, &data).map_err(Into::into) } @@ -1864,11 +1926,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for contribution to be created")] data: NewContribution, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; Contribution::create(&context.db, &data).map_err(Into::into) } @@ -1877,13 +1935,8 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for publication to be created")] data: NewPublication, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; data.validate(&context.db)?; - Publication::create(&context.db, &data).map_err(Into::into) } @@ -1892,11 +1945,10 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for series to be created")] data: NewSeries, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_imprint_id(&context.db, data.imprint_id)?)?; - + context.require_publisher(&publisher_id_from_imprint_id( + &context.db, + &data.imprint_id, + )?)?; Series::create(&context.db, &data).map_err(Into::into) } @@ -1905,13 +1957,8 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for issue to be created")] data: NewIssue, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; data.imprints_match(&context.db)?; - Issue::create(&context.db, &data).map_err(Into::into) } @@ -1920,11 +1967,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for language to be created")] data: NewLanguage, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; Language::create(&context.db, &data).map_err(Into::into) } @@ -1936,10 +1979,7 @@ impl MutationRoot { >, #[graphql(description = "Values for title to be created")] data: NewTitle, ) -> FieldResult { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; let has_canonical_title = Work::from_id(&context.db, &data.work_id)? .title(context) @@ -1972,10 +2012,7 @@ impl MutationRoot { >, #[graphql(description = "Values for abstract to be created")] data: NewAbstract, ) -> FieldResult<Abstract> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; let has_canonical_abstract = Abstract::all( &context.db, @@ -2019,13 +2056,10 @@ impl MutationRoot { >, #[graphql(description = "Values for biography to be created")] data: NewBiography, ) -> FieldResult<Biography> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_contribution_id( - &context.db, - data.contribution_id, - )?)?; + context.require_publisher(&publisher_id_from_contribution_id( + &context.db, + &data.contribution_id, + )?)?; let has_canonical_biography = Biography::all( &context.db, @@ -2060,7 +2094,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for institution to be created")] data: NewInstitution, ) -> FieldResult<Institution> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; Institution::create(&context.db, &data).map_err(Into::into) } @@ -2069,11 +2103,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for funding to be created")] data: NewFunding, ) -> FieldResult<Funding> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; Funding::create(&context.db, &data).map_err(Into::into) } @@ -2082,18 +2112,14 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for location to be created")] data: NewLocation, ) -> FieldResult<Location> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_publisher(&publisher_id_from_publication_id( + &context.db, + &data.publication_id, + )?)?; // Only superusers can create new locations where Location Platform is Thoth - if !context.account_access.is_superuser && data.location_platform == LocationPlatform::Thoth - { + if !user.is_superuser() && data.location_platform == LocationPlatform::Thoth { return Err(ThothError::ThothLocationError.into()); } - context - .account_access - .can_edit(publisher_id_from_publication_id( - &context.db, - data.publication_id, - )?)?; if data.canonical { data.canonical_record_complete(&context.db)?; @@ -2109,13 +2135,10 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for price to be created")] data: NewPrice, ) -> FieldResult<Price> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_publication_id( - &context.db, - data.publication_id, - )?)?; + context.require_publisher(&publisher_id_from_publication_id( + &context.db, + &data.publication_id, + )?)?; if data.unit_price <= 0.0 { // Prices must be non-zero (and non-negative). @@ -2130,13 +2153,8 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for subject to be created")] data: NewSubject, ) -> FieldResult<Subject> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; check_subject(&data.subject_type, &data.subject_code)?; - Subject::create(&context.db, &data).map_err(Into::into) } @@ -2145,14 +2163,10 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for affiliation to be created")] data: NewAffiliation, ) -> FieldResult<Affiliation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_contribution_id( - &context.db, - data.contribution_id, - )?)?; - + context.require_publisher(&publisher_id_from_contribution_id( + &context.db, + &data.contribution_id, + )?)?; Affiliation::create(&context.db, &data).map_err(Into::into) } @@ -2161,16 +2175,15 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for work relation to be created")] data: NewWorkRelation, ) -> FieldResult<WorkRelation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; // Work relations may link works from different publishers. // User must have permissions for all relevant publishers. - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - data.relator_work_id, + &data.relator_work_id, )?)?; - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - data.related_work_id, + &data.related_work_id, )?)?; WorkRelation::create(&context.db, &data).map_err(Into::into) @@ -2181,11 +2194,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for reference to be created")] data: NewReference, ) -> FieldResult<Reference> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; - + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; Reference::create(&context.db, &data).map_err(Into::into) } @@ -2194,9 +2203,7 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values for contact to be created")] data: NewContact, ) -> FieldResult<Contact> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - context.account_access.can_edit(data.publisher_id)?; - + context.require_publisher(&data.publisher_id)?; Contact::create(&context.db, &data).map_err(Into::into) } @@ -2205,16 +2212,15 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing work")] data: PatchWork, ) -> FieldResult<Work> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let work = Work::from_id(&context.db, &data.work_id)?; - context - .account_access - .can_edit(work.publisher_id(&context.db)?)?; + let user = context.require_publisher(&work.publisher_id(&context.db)?)?; if data.imprint_id != work.imprint_id { - context - .account_access - .can_edit(publisher_id_from_imprint_id(&context.db, data.imprint_id)?)?; + context.require_publisher(&publisher_id_from_imprint_id( + &context.db, + &data.imprint_id, + )?)?; work.can_update_imprint(&context.db)?; } @@ -2224,18 +2230,12 @@ impl MutationRoot { data.validate()?; - if work.is_published() && !data.is_published() && !context.account_access.is_superuser { + if work.is_published() && !data.is_published() && !user.is_superuser() { return Err(ThothError::ThothSetWorkStatusError.into()); } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); // update the work and, if it succeeds, synchronise its children statuses and pub. date - match work.update(&context.db, &data, &account_id) { + match work.update(&context.db, &data, &user.user_id) { Ok(w) => { // update chapters if their pub. data, withdrawn_date or work_status doesn't match the parent's for child in work.children(&context.db)? { @@ -2247,7 +2247,7 @@ impl MutationRoot { data.publication_date = w.publication_date; data.withdrawn_date = w.withdrawn_date; data.work_status = w.work_status; - child.update(&context.db, &data, &account_id)?; + child.update(&context.db, &data, &user.user_id)?; } } Ok(w) @@ -2261,21 +2261,15 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing publisher")] data: PatchPublisher, ) -> FieldResult<Publisher> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let publisher = Publisher::from_id(&context.db, &data.publisher_id)?; - context.account_access.can_edit(publisher.publisher_id)?; + let user = context.require_publisher(&publisher.publisher_id)?; if data.publisher_id != publisher.publisher_id { - context.account_access.can_edit(data.publisher_id)?; + context.require_publisher(&data.publisher_id)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); publisher - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2284,21 +2278,15 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing imprint")] data: PatchImprint, ) -> FieldResult<Imprint> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let imprint = Imprint::from_id(&context.db, &data.imprint_id)?; - context.account_access.can_edit(imprint.publisher_id())?; + let user = context.require_publisher(&imprint.publisher_id())?; if data.publisher_id != imprint.publisher_id { - context.account_access.can_edit(data.publisher_id)?; + context.require_publisher(&data.publisher_id)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); imprint - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2307,15 +2295,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing contributor")] data: PatchContributor, ) -> FieldResult<Contributor> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); + let user = context.require_authentication()?; Contributor::from_id(&context.db, &data.contributor_id)? - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2325,25 +2307,15 @@ impl MutationRoot { #[graphql(description = "Values to apply to existing contribution")] data: PatchContribution, ) -> FieldResult<Contribution> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let contribution = Contribution::from_id(&context.db, &data.contribution_id)?; - context - .account_access - .can_edit(contribution.publisher_id(&context.db)?)?; + let user = context.require_publisher(&contribution.publisher_id(&context.db)?)?; if data.work_id != contribution.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); contribution - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2352,28 +2324,21 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing publication")] data: PatchPublication, ) -> FieldResult<Publication> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let publication = Publication::from_id(&context.db, &data.publication_id)?; - context - .account_access - .can_edit(publication.publisher_id(&context.db)?)?; + let user = context.require_publisher(&publisher_id_from_publication_id( + &context.db, + &data.publication_id, + )?)?; if data.work_id != publication.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } data.validate(&context.db)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); publication - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2382,25 +2347,18 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing series")] data: PatchSeries, ) -> FieldResult<Series> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let series = Series::from_id(&context.db, &data.series_id)?; - context - .account_access - .can_edit(series.publisher_id(&context.db)?)?; + let user = context.require_publisher(&series.publisher_id(&context.db)?)?; if data.imprint_id != series.imprint_id { - context - .account_access - .can_edit(publisher_id_from_imprint_id(&context.db, data.imprint_id)?)?; + context.require_publisher(&publisher_id_from_imprint_id( + &context.db, + &data.imprint_id, + )?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); series - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2409,27 +2367,17 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing issue")] data: PatchIssue, ) -> FieldResult<Issue> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let issue = Issue::from_id(&context.db, &data.issue_id)?; - context - .account_access - .can_edit(issue.publisher_id(&context.db)?)?; + let user = context.require_publisher(&issue.publisher_id(&context.db)?)?; data.imprints_match(&context.db)?; if data.work_id != issue.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); issue - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2438,26 +2386,16 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing language")] data: PatchLanguage, ) -> FieldResult<Language> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let language = Language::from_id(&context.db, &data.language_id)?; - context - .account_access - .can_edit(language.publisher_id(&context.db)?)?; + let user = context.require_publisher(&language.publisher_id(&context.db)?)?; if data.work_id != language.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); language - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2466,15 +2404,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing institution")] data: PatchInstitution, ) -> FieldResult<Institution> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); + let user = context.require_authentication()?; Institution::from_id(&context.db, &data.institution_id)? - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2483,26 +2415,16 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing funding")] data: PatchFunding, ) -> FieldResult<Funding> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let funding = Funding::from_id(&context.db, &data.funding_id)?; - context - .account_access - .can_edit(funding.publisher_id(&context.db)?)?; + let user = context.require_publisher(&funding.publisher_id(&context.db)?)?; if data.work_id != funding.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); funding - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2511,8 +2433,10 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing location")] data: PatchLocation, ) -> FieldResult<Location> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let current_location = Location::from_id(&context.db, &data.location_id)?; + let user = context.require_publisher(¤t_location.publisher_id(&context.db)?)?; + let has_canonical_thoth_location = Publication::from_id(&context.db, &data.publication_id)? .locations( context, @@ -2524,41 +2448,28 @@ impl MutationRoot { .first() .is_some_and(|location| location.canonical); // Only superusers can update the canonical location when a Thoth Location Platform canonical location already exists - if has_canonical_thoth_location && data.canonical && !context.account_access.is_superuser { + if has_canonical_thoth_location && data.canonical && !user.is_superuser() { return Err(ThothError::ThothUpdateCanonicalError.into()); } // Only superusers can edit locations where Location Platform is Thoth - if !context.account_access.is_superuser - && current_location.location_platform == LocationPlatform::Thoth - { + if !user.is_superuser() && current_location.location_platform == LocationPlatform::Thoth { return Err(ThothError::ThothLocationError.into()); } - context - .account_access - .can_edit(current_location.publisher_id(&context.db)?)?; if data.publication_id != current_location.publication_id { - context - .account_access - .can_edit(publisher_id_from_publication_id( - &context.db, - data.publication_id, - )?)?; + context.require_publisher(&publisher_id_from_publication_id( + &context.db, + &data.publication_id, + )?)?; } if data.canonical { data.canonical_record_complete(&context.db)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); current_location - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2567,19 +2478,15 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing price")] data: PatchPrice, ) -> FieldResult<Price> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let price = Price::from_id(&context.db, &data.price_id)?; - context - .account_access - .can_edit(price.publisher_id(&context.db)?)?; + let user = context.require_publisher(&price.publisher_id(&context.db)?)?; if data.publication_id != price.publication_id { - context - .account_access - .can_edit(publisher_id_from_publication_id( - &context.db, - data.publication_id, - )?)?; + context.require_publisher(&publisher_id_from_publication_id( + &context.db, + &data.publication_id, + )?)?; } if data.unit_price <= 0.0 { @@ -2587,14 +2494,8 @@ impl MutationRoot { return Err(ThothError::PriceZeroError.into()); } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); price - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2603,28 +2504,18 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing subject")] data: PatchSubject, ) -> FieldResult<Subject> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let subject = Subject::from_id(&context.db, &data.subject_id)?; - context - .account_access - .can_edit(subject.publisher_id(&context.db)?)?; + let user = context.require_publisher(&subject.publisher_id(&context.db)?)?; if data.work_id != subject.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } check_subject(&data.subject_type, &data.subject_code)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); subject - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2633,29 +2524,19 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing affiliation")] data: PatchAffiliation, ) -> FieldResult<Affiliation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let affiliation = Affiliation::from_id(&context.db, &data.affiliation_id)?; - context - .account_access - .can_edit(affiliation.publisher_id(&context.db)?)?; + let user = context.require_publisher(&affiliation.publisher_id(&context.db)?)?; if data.contribution_id != affiliation.contribution_id { - context - .account_access - .can_edit(publisher_id_from_contribution_id( - &context.db, - data.contribution_id, - )?)?; + context.require_publisher(&publisher_id_from_contribution_id( + &context.db, + &data.contribution_id, + )?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); affiliation - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2665,40 +2546,34 @@ impl MutationRoot { #[graphql(description = "Values to apply to existing work relation")] data: PatchWorkRelation, ) -> FieldResult<WorkRelation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let work_relation = WorkRelation::from_id(&context.db, &data.work_relation_id)?; // Work relations may link works from different publishers. // User must have permissions for all relevant publishers. - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - work_relation.relator_work_id, + &work_relation.relator_work_id, )?)?; - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - work_relation.related_work_id, + &work_relation.related_work_id, )?)?; if data.relator_work_id != work_relation.relator_work_id { - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - data.relator_work_id, + &data.relator_work_id, )?)?; } if data.related_work_id != work_relation.related_work_id { - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - data.related_work_id, + &data.related_work_id, )?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); work_relation - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2707,26 +2582,16 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing reference")] data: PatchReference, ) -> FieldResult<Reference> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let reference = Reference::from_id(&context.db, &data.reference_id)?; - context - .account_access - .can_edit(reference.publisher_id(&context.db)?)?; + let user = context.require_publisher(&reference.publisher_id(&context.db)?)?; if data.work_id != reference.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); reference - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2735,21 +2600,16 @@ impl MutationRoot { context: &Context, #[graphql(description = "Values to apply to existing contact")] data: PatchContact, ) -> FieldResult<Contact> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let contact = Contact::from_id(&context.db, &data.contact_id)?; - context.account_access.can_edit(contact.publisher_id())?; + let user = context.require_publisher(&contact.publisher_id)?; if data.publisher_id != contact.publisher_id { - context.account_access.can_edit(data.publisher_id)?; + context.require_publisher(&data.publisher_id)?; } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); + contact - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2761,16 +2621,12 @@ impl MutationRoot { >, #[graphql(description = "Values to apply to existing title")] data: PatchTitle, ) -> FieldResult<Title> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let title = Title::from_id(&context.db, &data.title_id)?; - context - .account_access - .can_edit(title.publisher_id(&context.db)?)?; + let user = context.require_publisher(&title.publisher_id(&context.db)?)?; if data.work_id != title.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } let mut data = data.clone(); @@ -2784,14 +2640,8 @@ impl MutationRoot { .transpose()?; data.full_title = convert_to_jats(data.full_title, markup, ConversionLimit::Title)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); title - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2803,16 +2653,12 @@ impl MutationRoot { >, #[graphql(description = "Values to apply to existing abstract")] data: PatchAbstract, ) -> FieldResult<Abstract> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let r#abstract = Abstract::from_id(&context.db, &data.abstract_id)?; - context - .account_access - .can_edit(r#abstract.publisher_id(&context.db)?)?; + let user = context.require_publisher(&r#abstract.publisher_id(&context.db)?)?; if data.work_id != r#abstract.work_id { - context - .account_access - .can_edit(publisher_id_from_work_id(&context.db, data.work_id)?)?; + context.require_publisher(&publisher_id_from_work_id(&context.db, &data.work_id)?)?; } let mut data = data.clone(); @@ -2825,14 +2671,8 @@ impl MutationRoot { return Err(ThothError::ShortAbstractLimitExceedError.into()); } - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); r#abstract - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2844,34 +2684,24 @@ impl MutationRoot { >, #[graphql(description = "Values to apply to existing biography")] data: PatchBiography, ) -> FieldResult<Biography> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let biography = Biography::from_id(&context.db, &data.biography_id)?; - context - .account_access - .can_edit(biography.publisher_id(&context.db)?)?; + let user = context.require_publisher(&biography.publisher_id(&context.db)?)?; // If contribution changes, ensure permission on the new work via contribution if data.contribution_id != biography.contribution_id { - context - .account_access - .can_edit(publisher_id_from_contribution_id( - &context.db, - data.contribution_id, - )?)?; + context.require_publisher(&publisher_id_from_contribution_id( + &context.db, + &data.contribution_id, + )?)?; } let mut data = data.clone(); let markup = markup_format.ok_or(ThothError::MissingMarkupFormat)?; data.content = convert_to_jats(data.content, markup, ConversionLimit::Biography)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); biography - .update(&context.db, &data, &account_id) + .update(&context.db, &data, &user.user_id) .map_err(Into::into) } @@ -2880,13 +2710,11 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of work to be deleted")] work_id: Uuid, ) -> FieldResult<Work> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let work = Work::from_id(&context.db, &work_id)?; - context - .account_access - .can_edit(work.publisher_id(&context.db)?)?; + let user = context.require_publisher(&work.publisher_id(&context.db)?)?; - if work.is_published() && !context.account_access.is_superuser { + if work.is_published() && !user.is_superuser() { return Err(ThothError::ThothDeleteWorkError.into()); } @@ -2898,9 +2726,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of publisher to be deleted")] publisher_id: Uuid, ) -> FieldResult<Publisher> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let publisher = Publisher::from_id(&context.db, &publisher_id)?; - context.account_access.can_edit(publisher_id)?; + context.require_publisher(&publisher_id)?; publisher.delete(&context.db).map_err(Into::into) } @@ -2910,9 +2738,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of imprint to be deleted")] imprint_id: Uuid, ) -> FieldResult<Imprint> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let imprint = Imprint::from_id(&context.db, &imprint_id)?; - context.account_access.can_edit(imprint.publisher_id())?; + context.require_publisher(&imprint.publisher_id())?; imprint.delete(&context.db).map_err(Into::into) } @@ -2922,10 +2750,10 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of contributor to be deleted")] contributor_id: Uuid, ) -> FieldResult<Contributor> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let contributor = Contributor::from_id(&context.db, &contributor_id)?; for linked_publisher_id in contributor.linked_publisher_ids(&context.db)? { - context.account_access.can_edit(linked_publisher_id)?; + context.require_publisher(&linked_publisher_id)?; } contributor.delete(&context.db).map_err(Into::into) @@ -2936,11 +2764,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of contribution to be deleted")] contribution_id: Uuid, ) -> FieldResult<Contribution> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let contribution = Contribution::from_id(&context.db, &contribution_id)?; - context - .account_access - .can_edit(contribution.publisher_id(&context.db)?)?; + context.require_publisher(&contribution.publisher_id(&context.db)?)?; contribution.delete(&context.db).map_err(Into::into) } @@ -2950,11 +2776,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of publication to be deleted")] publication_id: Uuid, ) -> FieldResult<Publication> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let publication = Publication::from_id(&context.db, &publication_id)?; - context - .account_access - .can_edit(publication.publisher_id(&context.db)?)?; + context.require_publisher(&publication.publisher_id(&context.db)?)?; publication.delete(&context.db).map_err(Into::into) } @@ -2964,11 +2788,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of series to be deleted")] series_id: Uuid, ) -> FieldResult<Series> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let series = Series::from_id(&context.db, &series_id)?; - context - .account_access - .can_edit(series.publisher_id(&context.db)?)?; + context.require_publisher(&series.publisher_id(&context.db)?)?; series.delete(&context.db).map_err(Into::into) } @@ -2978,11 +2800,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of issue to be deleted")] issue_id: Uuid, ) -> FieldResult<Issue> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let issue = Issue::from_id(&context.db, &issue_id)?; - context - .account_access - .can_edit(issue.publisher_id(&context.db)?)?; + context.require_publisher(&issue.publisher_id(&context.db)?)?; issue.delete(&context.db).map_err(Into::into) } @@ -2992,11 +2812,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of language to be deleted")] language_id: Uuid, ) -> FieldResult<Language> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let language = Language::from_id(&context.db, &language_id)?; - context - .account_access - .can_edit(language.publisher_id(&context.db)?)?; + context.require_publisher(&language.publisher_id(&context.db)?)?; language.delete(&context.db).map_err(Into::into) } @@ -3006,11 +2824,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of title to be deleted")] title_id: Uuid, ) -> FieldResult<Title> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let title = Title::from_id(&context.db, &title_id)?; - context - .account_access - .can_edit(title.publisher_id(&context.db)?)?; + context.require_publisher(&title.publisher_id(&context.db)?)?; title.delete(&context.db).map_err(Into::into) } @@ -3020,10 +2836,10 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of institution to be deleted")] institution_id: Uuid, ) -> FieldResult<Institution> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let institution = Institution::from_id(&context.db, &institution_id)?; for linked_publisher_id in institution.linked_publisher_ids(&context.db)? { - context.account_access.can_edit(linked_publisher_id)?; + context.require_publisher(&linked_publisher_id)?; } institution.delete(&context.db).map_err(Into::into) @@ -3034,11 +2850,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of funding to be deleted")] funding_id: Uuid, ) -> FieldResult<Funding> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let funding = Funding::from_id(&context.db, &funding_id)?; - context - .account_access - .can_edit(funding.publisher_id(&context.db)?)?; + context.require_publisher(&funding.publisher_id(&context.db)?)?; funding.delete(&context.db).map_err(Into::into) } @@ -3048,17 +2862,13 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of location to be deleted")] location_id: Uuid, ) -> FieldResult<Location> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let location = Location::from_id(&context.db, &location_id)?; // Only superusers can delete locations where Location Platform is Thoth - if !context.account_access.is_superuser - && location.location_platform == LocationPlatform::Thoth - { + if !user.is_superuser() && location.location_platform == LocationPlatform::Thoth { return Err(ThothError::ThothLocationError.into()); } - context - .account_access - .can_edit(location.publisher_id(&context.db)?)?; + context.require_publisher(&location.publisher_id(&context.db)?)?; location.delete(&context.db).map_err(Into::into) } @@ -3068,11 +2878,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of price to be deleted")] price_id: Uuid, ) -> FieldResult<Price> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let price = Price::from_id(&context.db, &price_id)?; - context - .account_access - .can_edit(price.publisher_id(&context.db)?)?; + context.require_publisher(&price.publisher_id(&context.db)?)?; price.delete(&context.db).map_err(Into::into) } @@ -3082,11 +2890,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of subject to be deleted")] subject_id: Uuid, ) -> FieldResult<Subject> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let subject = Subject::from_id(&context.db, &subject_id)?; - context - .account_access - .can_edit(subject.publisher_id(&context.db)?)?; + context.require_publisher(&subject.publisher_id(&context.db)?)?; subject.delete(&context.db).map_err(Into::into) } @@ -3096,11 +2902,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of affiliation to be deleted")] affiliation_id: Uuid, ) -> FieldResult<Affiliation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let affiliation = Affiliation::from_id(&context.db, &affiliation_id)?; - context - .account_access - .can_edit(affiliation.publisher_id(&context.db)?)?; + context.require_publisher(&affiliation.publisher_id(&context.db)?)?; affiliation.delete(&context.db).map_err(Into::into) } @@ -3110,17 +2914,17 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of work relation to be deleted")] work_relation_id: Uuid, ) -> FieldResult<WorkRelation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let work_relation = WorkRelation::from_id(&context.db, &work_relation_id)?; // Work relations may link works from different publishers. // User must have permissions for all relevant publishers. - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - work_relation.relator_work_id, + &work_relation.relator_work_id, )?)?; - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - work_relation.related_work_id, + &work_relation.related_work_id, )?)?; work_relation.delete(&context.db).map_err(Into::into) @@ -3131,11 +2935,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of reference to be deleted")] reference_id: Uuid, ) -> FieldResult<Reference> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let reference = Reference::from_id(&context.db, &reference_id)?; - context - .account_access - .can_edit(reference.publisher_id(&context.db)?)?; + context.require_publisher(&reference.publisher_id(&context.db)?)?; reference.delete(&context.db).map_err(Into::into) } @@ -3145,11 +2947,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of abstract to be deleted")] abstract_id: Uuid, ) -> FieldResult<Abstract> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let r#abstract = Abstract::from_id(&context.db, &abstract_id)?; - context - .account_access - .can_edit(r#abstract.publisher_id(&context.db)?)?; + context.require_publisher(&r#abstract.publisher_id(&context.db)?)?; r#abstract.delete(&context.db).map_err(Into::into) } @@ -3159,11 +2959,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of biography to be deleted")] biography_id: Uuid, ) -> FieldResult<Biography> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let biography = Biography::from_id(&context.db, &biography_id)?; - context - .account_access - .can_edit(biography.publisher_id(&context.db)?)?; + context.require_publisher(&biography.publisher_id(&context.db)?)?; biography.delete(&context.db).map_err(Into::into) } @@ -3177,7 +2975,7 @@ impl MutationRoot { )] new_ordinal: i32, ) -> FieldResult<Affiliation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let affiliation = Affiliation::from_id(&context.db, &affiliation_id)?; if new_ordinal == affiliation.affiliation_ordinal { @@ -3185,22 +2983,14 @@ impl MutationRoot { return Ok(affiliation); } - context - .account_access - .can_edit(affiliation.publisher_id(&context.db)?)?; + context.require_publisher(&affiliation.publisher_id(&context.db)?)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); affiliation .change_ordinal( &context.db, affiliation.affiliation_ordinal, new_ordinal, - &account_id, + &user.user_id, ) .map_err(Into::into) } @@ -3214,7 +3004,7 @@ impl MutationRoot { )] new_ordinal: i32, ) -> FieldResult<Contribution> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let contribution = Contribution::from_id(&context.db, &contribution_id)?; if new_ordinal == contribution.contribution_ordinal { @@ -3222,22 +3012,14 @@ impl MutationRoot { return Ok(contribution); } - context - .account_access - .can_edit(contribution.publisher_id(&context.db)?)?; + context.require_publisher(&contribution.publisher_id(&context.db)?)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); contribution .change_ordinal( &context.db, contribution.contribution_ordinal, new_ordinal, - &account_id, + &user.user_id, ) .map_err(Into::into) } @@ -3249,7 +3031,7 @@ impl MutationRoot { #[graphql(description = "Ordinal representing position to which issue should be moved")] new_ordinal: i32, ) -> FieldResult<Issue> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let issue = Issue::from_id(&context.db, &issue_id)?; if new_ordinal == issue.issue_ordinal { @@ -3257,18 +3039,10 @@ impl MutationRoot { return Ok(issue); } - context - .account_access - .can_edit(issue.publisher_id(&context.db)?)?; + context.require_publisher(&issue.publisher_id(&context.db)?)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); issue - .change_ordinal(&context.db, issue.issue_ordinal, new_ordinal, &account_id) + .change_ordinal(&context.db, issue.issue_ordinal, new_ordinal, &user.user_id) .map_err(Into::into) } @@ -3281,7 +3055,7 @@ impl MutationRoot { )] new_ordinal: i32, ) -> FieldResult<Reference> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let reference = Reference::from_id(&context.db, &reference_id)?; if new_ordinal == reference.reference_ordinal { @@ -3289,22 +3063,14 @@ impl MutationRoot { return Ok(reference); } - context - .account_access - .can_edit(reference.publisher_id(&context.db)?)?; + context.require_publisher(&reference.publisher_id(&context.db)?)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); reference .change_ordinal( &context.db, reference.reference_ordinal, new_ordinal, - &account_id, + &user.user_id, ) .map_err(Into::into) } @@ -3316,7 +3082,7 @@ impl MutationRoot { #[graphql(description = "Ordinal representing position to which subject should be moved")] new_ordinal: i32, ) -> FieldResult<Subject> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let subject = Subject::from_id(&context.db, &subject_id)?; if new_ordinal == subject.subject_ordinal { @@ -3324,22 +3090,14 @@ impl MutationRoot { return Ok(subject); } - context - .account_access - .can_edit(subject.publisher_id(&context.db)?)?; + context.require_publisher(&subject.publisher_id(&context.db)?)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); subject .change_ordinal( &context.db, subject.subject_ordinal, new_ordinal, - &account_id, + &user.user_id, ) .map_err(Into::into) } @@ -3353,7 +3111,7 @@ impl MutationRoot { )] new_ordinal: i32, ) -> FieldResult<WorkRelation> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + let user = context.require_authentication()?; let work_relation = WorkRelation::from_id(&context.db, &work_relation_id)?; if new_ordinal == work_relation.relation_ordinal { // No action required @@ -3362,27 +3120,21 @@ impl MutationRoot { // Work relations may link works from different publishers. // User must have permissions for all relevant publishers. - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - work_relation.relator_work_id, + &work_relation.relator_work_id, )?)?; - context.account_access.can_edit(publisher_id_from_work_id( + context.require_publisher(&publisher_id_from_work_id( &context.db, - work_relation.related_work_id, + &work_relation.related_work_id, )?)?; - let account_id = context - .token - .jwt - .as_ref() - .ok_or(ThothError::Unauthorised)? - .account_id(&context.db); work_relation .change_ordinal( &context.db, work_relation.relation_ordinal, new_ordinal, - &account_id, + &user.user_id, ) .map_err(Into::into) } @@ -3392,9 +3144,9 @@ impl MutationRoot { context: &Context, #[graphql(description = "Thoth ID of contact to be deleted")] contact_id: Uuid, ) -> FieldResult<Contact> { - context.token.jwt.as_ref().ok_or(ThothError::Unauthorised)?; + context.require_authentication()?; let contact = Contact::from_id(&context.db, &contact_id)?; - context.account_access.can_edit(contact.publisher_id())?; + context.require_publisher(&contact.publisher_id)?; contact.delete(&context.db).map_err(Into::into) } @@ -5619,18 +5371,18 @@ pub fn create_schema() -> Schema { Schema::new(QueryRoot {}, MutationRoot {}, EmptySubscription::new()) } -fn publisher_id_from_imprint_id(db: &PgPool, imprint_id: Uuid) -> ThothResult<Uuid> { - Ok(Imprint::from_id(db, &imprint_id)?.publisher_id) +fn publisher_id_from_imprint_id(db: &PgPool, imprint_id: &Uuid) -> ThothResult<Uuid> { + Ok(Imprint::from_id(db, imprint_id)?.publisher_id) } -fn publisher_id_from_work_id(db: &PgPool, work_id: Uuid) -> ThothResult<Uuid> { - Work::from_id(db, &work_id)?.publisher_id(db) +fn publisher_id_from_work_id(db: &PgPool, work_id: &Uuid) -> ThothResult<Uuid> { + Work::from_id(db, work_id)?.publisher_id(db) } -fn publisher_id_from_publication_id(db: &PgPool, publication_id: Uuid) -> ThothResult<Uuid> { - Publication::from_id(db, &publication_id)?.publisher_id(db) +fn publisher_id_from_publication_id(db: &PgPool, publication_id: &Uuid) -> ThothResult<Uuid> { + Publication::from_id(db, publication_id)?.publisher_id(db) } -fn publisher_id_from_contribution_id(db: &PgPool, contribution_id: Uuid) -> ThothResult<Uuid> { - Contribution::from_id(db, &contribution_id)?.publisher_id(db) +fn publisher_id_from_contribution_id(db: &PgPool, contribution_id: &Uuid) -> ThothResult<Uuid> { + Contribution::from_id(db, contribution_id)?.publisher_id(db) } diff --git a/thoth-api/src/model/abstract/crud.rs b/thoth-api/src/model/abstract/crud.rs index 6879a34e..f4522427 100644 --- a/thoth-api/src/model/abstract/crud.rs +++ b/thoth-api/src/model/abstract/crud.rs @@ -157,10 +157,10 @@ impl Crud for Abstract { impl HistoryEntry for Abstract { type NewHistoryEntity = NewAbstractHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { abstract_id: self.abstract_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } diff --git a/thoth-api/src/model/abstract/mod.rs b/thoth-api/src/model/abstract/mod.rs index 6336ec67..734ee4a3 100644 --- a/thoth-api/src/model/abstract/mod.rs +++ b/thoth-api/src/model/abstract/mod.rs @@ -123,7 +123,7 @@ pub struct PatchAbstract { )] pub struct NewAbstractHistory { pub abstract_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } @@ -131,7 +131,7 @@ pub struct NewAbstractHistory { pub struct AbstractHistory { pub abstract_history_id: Uuid, pub abstract_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: chrono::DateTime<chrono::Utc>, } diff --git a/thoth-api/src/model/affiliation/crud.rs b/thoth-api/src/model/affiliation/crud.rs index 0fdd282f..3472af48 100644 --- a/thoth-api/src/model/affiliation/crud.rs +++ b/thoth-api/src/model/affiliation/crud.rs @@ -126,10 +126,10 @@ impl Crud for Affiliation { impl HistoryEntry for Affiliation { type NewHistoryEntity = NewAffiliationHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { affiliation_id: self.affiliation_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -180,13 +180,13 @@ mod tests { #[test] fn test_new_affiliation_history_from_affiliation() { let affiliation: Affiliation = Default::default(); - let account_id: Uuid = Default::default(); - let new_affiliation_history = affiliation.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_affiliation_history = affiliation.new_history_entry(&user_id); assert_eq!( new_affiliation_history.affiliation_id, affiliation.affiliation_id ); - assert_eq!(new_affiliation_history.account_id, account_id); + assert_eq!(new_affiliation_history.user_id, user_id); assert_eq!( new_affiliation_history.data, serde_json::Value::String(serde_json::to_string(&affiliation).unwrap()) diff --git a/thoth-api/src/model/affiliation/mod.rs b/thoth-api/src/model/affiliation/mod.rs index ae82db75..63d03cf3 100644 --- a/thoth-api/src/model/affiliation/mod.rs +++ b/thoth-api/src/model/affiliation/mod.rs @@ -67,7 +67,7 @@ pub struct PatchAffiliation { pub struct AffiliationHistory { pub affiliation_history_id: Uuid, pub affiliation_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -79,7 +79,7 @@ pub struct AffiliationHistory { )] pub struct NewAffiliationHistory { pub affiliation_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/biography/crud.rs b/thoth-api/src/model/biography/crud.rs index 752a3ca7..945919bf 100644 --- a/thoth-api/src/model/biography/crud.rs +++ b/thoth-api/src/model/biography/crud.rs @@ -143,10 +143,10 @@ impl Crud for Biography { impl HistoryEntry for Biography { type NewHistoryEntity = NewBiographyHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { biography_id: self.biography_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } diff --git a/thoth-api/src/model/biography/mod.rs b/thoth-api/src/model/biography/mod.rs index 16fc9a24..8bf062db 100644 --- a/thoth-api/src/model/biography/mod.rs +++ b/thoth-api/src/model/biography/mod.rs @@ -87,7 +87,7 @@ pub struct PatchBiography { )] pub struct NewBiographyHistory { pub biography_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } @@ -95,7 +95,7 @@ pub struct NewBiographyHistory { pub struct BiographyHistory { pub biography_history_id: Uuid, pub biography_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: chrono::DateTime<chrono::Utc>, } diff --git a/thoth-api/src/model/contact/crud.rs b/thoth-api/src/model/contact/crud.rs index 44441b50..946022cb 100644 --- a/thoth-api/src/model/contact/crud.rs +++ b/thoth-api/src/model/contact/crud.rs @@ -122,10 +122,10 @@ impl Crud for Contact { impl HistoryEntry for Contact { type NewHistoryEntity = NewContactHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { contact_id: self.contact_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -150,10 +150,10 @@ mod tests { #[test] fn test_new_contact_history_from_contact() { let contact: Contact = Default::default(); - let account_id: Uuid = Default::default(); - let new_contact_history = contact.new_history_entry(&account_id); + let user_id = "12345"; + let new_contact_history = contact.new_history_entry(user_id); assert_eq!(new_contact_history.contact_id, contact.contact_id); - assert_eq!(new_contact_history.account_id, account_id); + assert_eq!(new_contact_history.user_id, user_id); assert_eq!( new_contact_history.data, serde_json::Value::String(serde_json::to_string(&contact).unwrap()) diff --git a/thoth-api/src/model/contact/mod.rs b/thoth-api/src/model/contact/mod.rs index 62e5c128..e4fcb5dd 100644 --- a/thoth-api/src/model/contact/mod.rs +++ b/thoth-api/src/model/contact/mod.rs @@ -87,7 +87,7 @@ pub struct PatchContact { pub struct ContactHistory { pub contact_history_id: Uuid, pub contact_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -99,7 +99,7 @@ pub struct ContactHistory { )] pub struct NewContactHistory { pub contact_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/contribution/crud.rs b/thoth-api/src/model/contribution/crud.rs index bf980957..b91ebd8d 100644 --- a/thoth-api/src/model/contribution/crud.rs +++ b/thoth-api/src/model/contribution/crud.rs @@ -155,10 +155,10 @@ impl Crud for Contribution { impl HistoryEntry for Contribution { type NewHistoryEntity = NewContributionHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { contribution_id: self.contribution_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -209,13 +209,13 @@ mod tests { #[test] fn test_new_contribution_history_from_contribution() { let contribution: Contribution = Default::default(); - let account_id: Uuid = Default::default(); - let new_contribution_history = contribution.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_contribution_history = contribution.new_history_entry(&user_id); assert_eq!( new_contribution_history.contribution_id, contribution.contribution_id ); - assert_eq!(new_contribution_history.account_id, account_id); + assert_eq!(new_contribution_history.user_id, user_id); assert_eq!( new_contribution_history.data, serde_json::Value::String(serde_json::to_string(&contribution).unwrap()) diff --git a/thoth-api/src/model/contribution/mod.rs b/thoth-api/src/model/contribution/mod.rs index 0b05a152..76612075 100644 --- a/thoth-api/src/model/contribution/mod.rs +++ b/thoth-api/src/model/contribution/mod.rs @@ -173,7 +173,7 @@ pub struct PatchContribution { pub struct ContributionHistory { pub contribution_history_id: Uuid, pub contribution_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -185,7 +185,7 @@ pub struct ContributionHistory { )] pub struct NewContributionHistory { pub contribution_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/contributor/crud.rs b/thoth-api/src/model/contributor/crud.rs index 27cd37ec..be055101 100644 --- a/thoth-api/src/model/contributor/crud.rs +++ b/thoth-api/src/model/contributor/crud.rs @@ -135,10 +135,10 @@ impl Crud for Contributor { impl HistoryEntry for Contributor { type NewHistoryEntity = NewContributorHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { contributor_id: self.contributor_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -187,13 +187,13 @@ mod tests { #[test] fn test_new_contributor_history_from_contributor() { let contributor: Contributor = Default::default(); - let account_id: Uuid = Default::default(); - let new_contributor_history = contributor.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_contributor_history = contributor.new_history_entry(&user_id); assert_eq!( new_contributor_history.contributor_id, contributor.contributor_id ); - assert_eq!(new_contributor_history.account_id, account_id); + assert_eq!(new_contributor_history.user_id, user_id); assert_eq!( new_contributor_history.data, serde_json::Value::String(serde_json::to_string(&contributor).unwrap()) diff --git a/thoth-api/src/model/contributor/mod.rs b/thoth-api/src/model/contributor/mod.rs index 67d97fd2..79702d67 100644 --- a/thoth-api/src/model/contributor/mod.rs +++ b/thoth-api/src/model/contributor/mod.rs @@ -82,7 +82,7 @@ pub struct PatchContributor { pub struct ContributorHistory { pub contributor_history_id: Uuid, pub contributor_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -94,7 +94,7 @@ pub struct ContributorHistory { )] pub struct NewContributorHistory { pub contributor_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/funding/crud.rs b/thoth-api/src/model/funding/crud.rs index 49e7be66..cb6c35fe 100644 --- a/thoth-api/src/model/funding/crud.rs +++ b/thoth-api/src/model/funding/crud.rs @@ -132,10 +132,10 @@ impl Crud for Funding { impl HistoryEntry for Funding { type NewHistoryEntity = NewFundingHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { funding_id: self.funding_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -160,10 +160,10 @@ mod tests { #[test] fn test_new_funding_history_from_funding() { let funding: Funding = Default::default(); - let account_id: Uuid = Default::default(); - let new_funding_history = funding.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_funding_history = funding.new_history_entry(&user_id); assert_eq!(new_funding_history.funding_id, funding.funding_id); - assert_eq!(new_funding_history.account_id, account_id); + assert_eq!(new_funding_history.user_id, user_id); assert_eq!( new_funding_history.data, serde_json::Value::String(serde_json::to_string(&funding).unwrap()) diff --git a/thoth-api/src/model/funding/mod.rs b/thoth-api/src/model/funding/mod.rs index 157f3f35..9d4f5c02 100644 --- a/thoth-api/src/model/funding/mod.rs +++ b/thoth-api/src/model/funding/mod.rs @@ -78,7 +78,7 @@ pub struct PatchFunding { pub struct FundingHistory { pub funding_history_id: Uuid, pub funding_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -90,7 +90,7 @@ pub struct FundingHistory { )] pub struct NewFundingHistory { pub funding_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/imprint/crud.rs b/thoth-api/src/model/imprint/crud.rs index 72715828..104de665 100644 --- a/thoth-api/src/model/imprint/crud.rs +++ b/thoth-api/src/model/imprint/crud.rs @@ -132,10 +132,10 @@ impl Crud for Imprint { impl HistoryEntry for Imprint { type NewHistoryEntity = NewImprintHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { imprint_id: self.imprint_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -160,10 +160,10 @@ mod tests { #[test] fn test_new_imprint_history_from_imprint() { let imprint: Imprint = Default::default(); - let account_id: Uuid = Default::default(); - let new_imprint_history = imprint.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_imprint_history = imprint.new_history_entry(&user_id); assert_eq!(new_imprint_history.imprint_id, imprint.imprint_id); - assert_eq!(new_imprint_history.account_id, account_id); + assert_eq!(new_imprint_history.user_id, user_id); assert_eq!( new_imprint_history.data, serde_json::Value::String(serde_json::to_string(&imprint).unwrap()) diff --git a/thoth-api/src/model/imprint/mod.rs b/thoth-api/src/model/imprint/mod.rs index 1f6c2df4..8009309c 100644 --- a/thoth-api/src/model/imprint/mod.rs +++ b/thoth-api/src/model/imprint/mod.rs @@ -77,7 +77,7 @@ pub struct PatchImprint { pub struct ImprintHistory { pub imprint_history_id: Uuid, pub imprint_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -89,7 +89,7 @@ pub struct ImprintHistory { )] pub struct NewImprintHistory { pub imprint_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/institution/crud.rs b/thoth-api/src/model/institution/crud.rs index 9cb32812..8284b442 100644 --- a/thoth-api/src/model/institution/crud.rs +++ b/thoth-api/src/model/institution/crud.rs @@ -131,10 +131,10 @@ impl Crud for Institution { impl HistoryEntry for Institution { type NewHistoryEntity = NewInstitutionHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { institution_id: self.institution_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -194,13 +194,13 @@ mod tests { #[test] fn test_new_institution_history_from_institution() { let institution: Institution = Default::default(); - let account_id: Uuid = Default::default(); - let new_institution_history = institution.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_institution_history = institution.new_history_entry(&user_id); assert_eq!( new_institution_history.institution_id, institution.institution_id ); - assert_eq!(new_institution_history.account_id, account_id); + assert_eq!(new_institution_history.user_id, user_id); assert_eq!( new_institution_history.data, serde_json::Value::String(serde_json::to_string(&institution).unwrap()) diff --git a/thoth-api/src/model/institution/mod.rs b/thoth-api/src/model/institution/mod.rs index ad47910a..535b5537 100644 --- a/thoth-api/src/model/institution/mod.rs +++ b/thoth-api/src/model/institution/mod.rs @@ -869,7 +869,7 @@ pub enum CountryCode { pub struct InstitutionHistory { pub institution_history_id: Uuid, pub institution_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -881,7 +881,7 @@ pub struct InstitutionHistory { )] pub struct NewInstitutionHistory { pub institution_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/issue/crud.rs b/thoth-api/src/model/issue/crud.rs index f4275e92..e7c13694 100644 --- a/thoth-api/src/model/issue/crud.rs +++ b/thoth-api/src/model/issue/crud.rs @@ -116,10 +116,10 @@ impl Crud for Issue { impl HistoryEntry for Issue { type NewHistoryEntity = NewIssueHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { issue_id: self.issue_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -200,10 +200,10 @@ mod tests { #[test] fn test_new_issue_history_from_issue() { let issue: Issue = Default::default(); - let account_id: Uuid = Default::default(); - let new_issue_history = issue.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_issue_history = issue.new_history_entry(&user_id); assert_eq!(new_issue_history.issue_id, issue.issue_id); - assert_eq!(new_issue_history.account_id, account_id); + assert_eq!(new_issue_history.user_id, user_id); assert_eq!( new_issue_history.data, serde_json::Value::String(serde_json::to_string(&issue).unwrap()) diff --git a/thoth-api/src/model/issue/mod.rs b/thoth-api/src/model/issue/mod.rs index ef83b42d..36fb29a0 100644 --- a/thoth-api/src/model/issue/mod.rs +++ b/thoth-api/src/model/issue/mod.rs @@ -62,7 +62,7 @@ pub struct PatchIssue { pub struct IssueHistory { pub issue_history_id: Uuid, pub issue_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -70,7 +70,7 @@ pub struct IssueHistory { #[cfg_attr(feature = "backend", derive(Insertable), diesel(table_name = issue_history))] pub struct NewIssueHistory { pub issue_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/language/crud.rs b/thoth-api/src/model/language/crud.rs index 73f92eb2..43c57c2d 100644 --- a/thoth-api/src/model/language/crud.rs +++ b/thoth-api/src/model/language/crud.rs @@ -132,10 +132,10 @@ impl Crud for Language { impl HistoryEntry for Language { type NewHistoryEntity = NewLanguageHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { language_id: self.language_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -160,10 +160,10 @@ mod tests { #[test] fn test_new_language_history_from_language() { let language: Language = Default::default(); - let account_id: Uuid = Default::default(); - let new_language_history = language.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_language_history = language.new_history_entry(&user_id); assert_eq!(new_language_history.language_id, language.language_id); - assert_eq!(new_language_history.account_id, account_id); + assert_eq!(new_language_history.user_id, user_id); assert_eq!( new_language_history.data, serde_json::Value::String(serde_json::to_string(&language).unwrap()) diff --git a/thoth-api/src/model/language/mod.rs b/thoth-api/src/model/language/mod.rs index f81259da..22b6630e 100644 --- a/thoth-api/src/model/language/mod.rs +++ b/thoth-api/src/model/language/mod.rs @@ -1151,7 +1151,7 @@ pub enum LanguageCode { pub struct LanguageHistory { pub language_history_id: Uuid, pub language_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -1163,7 +1163,7 @@ pub struct LanguageHistory { )] pub struct NewLanguageHistory { pub language_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/location/crud.rs b/thoth-api/src/model/location/crud.rs index 050dbf1d..e48949bc 100644 --- a/thoth-api/src/model/location/crud.rs +++ b/thoth-api/src/model/location/crud.rs @@ -150,7 +150,7 @@ impl Crud for Location { &self, db: &crate::db::PgPool, data: &PatchLocation, - account_id: &Uuid, + user_id: &str, ) -> ThothResult<Self> { let mut connection = db.get()?; connection @@ -179,7 +179,7 @@ impl Crud for Location { } }) .and_then(|location| { - self.new_history_entry(account_id) + self.new_history_entry(user_id) .insert(&mut connection) .map(|_| location) }) @@ -198,10 +198,10 @@ impl Crud for Location { impl HistoryEntry for Location { type NewHistoryEntity = NewLocationHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { location_id: self.location_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -315,10 +315,10 @@ mod tests { #[test] fn test_new_location_history_from_location() { let location: Location = Default::default(); - let account_id: Uuid = Default::default(); - let new_location_history = location.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_location_history = location.new_history_entry(&user_id); assert_eq!(new_location_history.location_id, location.location_id); - assert_eq!(new_location_history.account_id, account_id); + assert_eq!(new_location_history.user_id, user_id); assert_eq!( new_location_history.data, serde_json::Value::String(serde_json::to_string(&location).unwrap()) diff --git a/thoth-api/src/model/location/mod.rs b/thoth-api/src/model/location/mod.rs index c96a26f3..1f5cc0db 100644 --- a/thoth-api/src/model/location/mod.rs +++ b/thoth-api/src/model/location/mod.rs @@ -216,7 +216,7 @@ pub struct PatchLocation { pub struct LocationHistory { pub location_history_id: Uuid, pub location_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -228,7 +228,7 @@ pub struct LocationHistory { )] pub struct NewLocationHistory { pub location_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/mod.rs b/thoth-api/src/model/mod.rs index 7bd79f39..f15bc9b6 100644 --- a/thoth-api/src/model/mod.rs +++ b/thoth-api/src/model/mod.rs @@ -366,7 +366,7 @@ where &self, db: &crate::db::PgPool, data: &Self::PatchEntity, - account_id: &Uuid, + user_id: &str, ) -> ThothResult<Self>; /// Delete the record from the database and obtain the deleted instance @@ -385,7 +385,7 @@ where /// The structure used to create a new history entity, e.g. `NewImprintHistory` for `Imprint` type NewHistoryEntity; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity; + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity; } #[cfg(feature = "backend")] @@ -411,7 +411,7 @@ where db: &crate::db::PgPool, current_ordinal: i32, new_ordinal: i32, - account_id: &Uuid, + user_id: &str, ) -> ThothResult<Self>; fn get_other_objects( @@ -472,7 +472,7 @@ macro_rules! crud_methods { &self, db: &$crate::db::PgPool, data: &Self::PatchEntity, - account_id: &Uuid, + user_id: &str, ) -> ThothResult<Self> { use diesel::{Connection, QueryDsl, RunQueryDsl}; @@ -483,7 +483,7 @@ macro_rules! crud_methods { .get_result(connection) .map_err(Into::into) .and_then(|c| { - self.new_history_entry(&account_id) + self.new_history_entry(user_id) .insert(connection) .map(|_| c) }) @@ -606,7 +606,7 @@ macro_rules! db_change_ordinal { db: &$crate::db::PgPool, current_ordinal: i32, new_ordinal: i32, - account_id: &Uuid, + user_id: &str, ) -> ThothResult<Self> { let mut connection = db.get()?; // Execute all updates within the same transaction, @@ -648,7 +648,7 @@ macro_rules! db_change_ordinal { .and_then(|t| { // On success, create a new history table entry. // Only record the original update, not the automatic reorderings. - self.new_history_entry(account_id) + self.new_history_entry(user_id) .insert(connection) .map(|_| t) }) diff --git a/thoth-api/src/model/price/crud.rs b/thoth-api/src/model/price/crud.rs index b273ee6e..6e844460 100644 --- a/thoth-api/src/model/price/crud.rs +++ b/thoth-api/src/model/price/crud.rs @@ -122,10 +122,10 @@ impl Crud for Price { impl HistoryEntry for Price { type NewHistoryEntity = NewPriceHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { price_id: self.price_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -150,10 +150,10 @@ mod tests { #[test] fn test_new_price_history_from_price() { let price: Price = Default::default(); - let account_id: Uuid = Default::default(); - let new_price_history = price.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_price_history = price.new_history_entry(&user_id); assert_eq!(new_price_history.price_id, price.price_id); - assert_eq!(new_price_history.account_id, account_id); + assert_eq!(new_price_history.user_id, user_id); assert_eq!( new_price_history.data, serde_json::Value::String(serde_json::to_string(&price).unwrap()) diff --git a/thoth-api/src/model/price/mod.rs b/thoth-api/src/model/price/mod.rs index cccf672f..10bfe271 100644 --- a/thoth-api/src/model/price/mod.rs +++ b/thoth-api/src/model/price/mod.rs @@ -827,7 +827,7 @@ pub enum CurrencyCode { pub struct PriceHistory { pub price_history_id: Uuid, pub price_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -835,7 +835,7 @@ pub struct PriceHistory { #[cfg_attr(feature = "backend", derive(Insertable), diesel(table_name = price_history))] pub struct NewPriceHistory { pub price_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/publication/crud.rs b/thoth-api/src/model/publication/crud.rs index 520a8ecd..a246f7bb 100644 --- a/thoth-api/src/model/publication/crud.rs +++ b/thoth-api/src/model/publication/crud.rs @@ -187,10 +187,10 @@ impl Crud for Publication { impl HistoryEntry for Publication { type NewHistoryEntity = NewPublicationHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { publication_id: self.publication_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -215,13 +215,13 @@ mod tests { #[test] fn test_new_publication_history_from_publication() { let publication: Publication = Default::default(); - let account_id: Uuid = Default::default(); - let new_publication_history = publication.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_publication_history = publication.new_history_entry(&user_id); assert_eq!( new_publication_history.publication_id, publication.publication_id ); - assert_eq!(new_publication_history.account_id, account_id); + assert_eq!(new_publication_history.user_id, user_id); assert_eq!( new_publication_history.data, serde_json::Value::String(serde_json::to_string(&publication).unwrap()) diff --git a/thoth-api/src/model/publication/mod.rs b/thoth-api/src/model/publication/mod.rs index b7baa0ab..172c0c1e 100644 --- a/thoth-api/src/model/publication/mod.rs +++ b/thoth-api/src/model/publication/mod.rs @@ -323,7 +323,7 @@ pub struct PatchPublication { pub struct PublicationHistory { pub publication_history_id: Uuid, pub publication_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -335,7 +335,7 @@ pub struct PublicationHistory { )] pub struct NewPublicationHistory { pub publication_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/publisher/crud.rs b/thoth-api/src/model/publisher/crud.rs index 06ea747f..06cdb737 100644 --- a/thoth-api/src/model/publisher/crud.rs +++ b/thoth-api/src/model/publisher/crud.rs @@ -137,10 +137,10 @@ impl Crud for Publisher { impl HistoryEntry for Publisher { type NewHistoryEntity = NewPublisherHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { publisher_id: self.publisher_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -165,10 +165,10 @@ mod tests { #[test] fn test_new_publisher_history_from_publisher() { let publisher: Publisher = Default::default(); - let account_id: Uuid = Default::default(); - let new_publisher_history = publisher.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_publisher_history = publisher.new_history_entry(&user_id); assert_eq!(new_publisher_history.publisher_id, publisher.publisher_id); - assert_eq!(new_publisher_history.account_id, account_id); + assert_eq!(new_publisher_history.user_id, user_id); assert_eq!( new_publisher_history.data, serde_json::Value::String(serde_json::to_string(&publisher).unwrap()) diff --git a/thoth-api/src/model/publisher/mod.rs b/thoth-api/src/model/publisher/mod.rs index cd989c1f..5cedd8df 100644 --- a/thoth-api/src/model/publisher/mod.rs +++ b/thoth-api/src/model/publisher/mod.rs @@ -81,7 +81,7 @@ pub struct PatchPublisher { pub struct PublisherHistory { pub publisher_history_id: Uuid, pub publisher_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -93,7 +93,7 @@ pub struct PublisherHistory { )] pub struct NewPublisherHistory { pub publisher_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/reference/crud.rs b/thoth-api/src/model/reference/crud.rs index 073495f0..a09a2487 100644 --- a/thoth-api/src/model/reference/crud.rs +++ b/thoth-api/src/model/reference/crud.rs @@ -239,10 +239,10 @@ impl Crud for Reference { impl HistoryEntry for Reference { type NewHistoryEntity = NewReferenceHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { reference_id: self.reference_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -290,10 +290,10 @@ mod tests { #[test] fn test_new_publisher_history_from_publisher() { let reference: Reference = Default::default(); - let account_id: Uuid = Default::default(); - let new_reference_history = reference.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_reference_history = reference.new_history_entry(&user_id); assert_eq!(new_reference_history.reference_id, reference.reference_id); - assert_eq!(new_reference_history.account_id, account_id); + assert_eq!(new_reference_history.user_id, user_id); assert_eq!( new_reference_history.data, serde_json::Value::String(serde_json::to_string(&reference).unwrap()) diff --git a/thoth-api/src/model/reference/mod.rs b/thoth-api/src/model/reference/mod.rs index f1ba9cb6..09758381 100644 --- a/thoth-api/src/model/reference/mod.rs +++ b/thoth-api/src/model/reference/mod.rs @@ -143,7 +143,7 @@ pub struct PatchReference { pub struct ReferenceHistory { pub reference_history_id: Uuid, pub reference_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -155,7 +155,7 @@ pub struct ReferenceHistory { )] pub struct NewReferenceHistory { pub reference_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/series/crud.rs b/thoth-api/src/model/series/crud.rs index 6f6b32d8..7f5f5c6a 100644 --- a/thoth-api/src/model/series/crud.rs +++ b/thoth-api/src/model/series/crud.rs @@ -163,10 +163,10 @@ impl Crud for Series { impl HistoryEntry for Series { type NewHistoryEntity = NewSeriesHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { series_id: self.series_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -191,10 +191,10 @@ mod tests { #[test] fn test_new_series_history_from_series() { let series: Series = Default::default(); - let account_id: Uuid = Default::default(); - let new_series_history = series.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_series_history = series.new_history_entry(&user_id); assert_eq!(new_series_history.series_id, series.series_id); - assert_eq!(new_series_history.account_id, account_id); + assert_eq!(new_series_history.user_id, user_id); assert_eq!( new_series_history.data, serde_json::Value::String(serde_json::to_string(&series).unwrap()) diff --git a/thoth-api/src/model/series/mod.rs b/thoth-api/src/model/series/mod.rs index 7bd09797..7443d309 100644 --- a/thoth-api/src/model/series/mod.rs +++ b/thoth-api/src/model/series/mod.rs @@ -121,7 +121,7 @@ pub struct PatchSeries { pub struct SeriesHistory { pub series_history_id: Uuid, pub series_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -129,7 +129,7 @@ pub struct SeriesHistory { #[cfg_attr(feature = "backend", derive(Insertable), diesel(table_name = series_history))] pub struct NewSeriesHistory { pub series_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/subject/crud.rs b/thoth-api/src/model/subject/crud.rs index e20323e8..b4fa8cc7 100644 --- a/thoth-api/src/model/subject/crud.rs +++ b/thoth-api/src/model/subject/crud.rs @@ -135,10 +135,10 @@ impl Crud for Subject { impl HistoryEntry for Subject { type NewHistoryEntity = NewSubjectHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { subject_id: self.subject_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -187,10 +187,10 @@ mod tests { #[test] fn test_new_subject_history_from_subject() { let subject: Subject = Default::default(); - let account_id: Uuid = Default::default(); - let new_subject_history = subject.new_history_entry(&account_id); + let user_id = "1234567".to_string(); + let new_subject_history = subject.new_history_entry(&user_id); assert_eq!(new_subject_history.subject_id, subject.subject_id); - assert_eq!(new_subject_history.account_id, account_id); + assert_eq!(new_subject_history.user_id, user_id); assert_eq!( new_subject_history.data, serde_json::Value::String(serde_json::to_string(&subject).unwrap()) diff --git a/thoth-api/src/model/subject/mod.rs b/thoth-api/src/model/subject/mod.rs index 100f084d..930ccc11 100644 --- a/thoth-api/src/model/subject/mod.rs +++ b/thoth-api/src/model/subject/mod.rs @@ -104,7 +104,7 @@ pub struct PatchSubject { pub struct SubjectHistory { pub subject_history_id: Uuid, pub subject_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -116,7 +116,7 @@ pub struct SubjectHistory { )] pub struct NewSubjectHistory { pub subject_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/title/crud.rs b/thoth-api/src/model/title/crud.rs index 50ab57ba..001f6a01 100644 --- a/thoth-api/src/model/title/crud.rs +++ b/thoth-api/src/model/title/crud.rs @@ -155,10 +155,10 @@ impl Crud for Title { impl HistoryEntry for Title { type NewHistoryEntity = NewTitleHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { title_id: self.title_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } diff --git a/thoth-api/src/model/title/mod.rs b/thoth-api/src/model/title/mod.rs index 7b0d6eaa..647928a6 100644 --- a/thoth-api/src/model/title/mod.rs +++ b/thoth-api/src/model/title/mod.rs @@ -95,7 +95,7 @@ pub struct PatchTitle { )] pub struct NewTitleHistory { pub title_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } @@ -103,7 +103,7 @@ pub struct NewTitleHistory { pub struct TitleHistory { pub title_history_id: Uuid, pub title_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: chrono::DateTime<chrono::Utc>, } diff --git a/thoth-api/src/model/work/crud.rs b/thoth-api/src/model/work/crud.rs index d4c618ed..9a784344 100644 --- a/thoth-api/src/model/work/crud.rs +++ b/thoth-api/src/model/work/crud.rs @@ -458,10 +458,10 @@ impl Crud for Work { impl HistoryEntry for Work { type NewHistoryEntity = NewWorkHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { work_id: self.work_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -486,10 +486,10 @@ mod tests { #[test] fn test_new_work_history_from_work() { let work: Work = Default::default(); - let account_id: Uuid = Default::default(); - let new_work_history = work.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_work_history = work.new_history_entry(&user_id); assert_eq!(new_work_history.work_id, work.work_id); - assert_eq!(new_work_history.account_id, account_id); + assert_eq!(new_work_history.user_id, user_id); assert_eq!( new_work_history.data, serde_json::Value::String(serde_json::to_string(&work).unwrap()) diff --git a/thoth-api/src/model/work/mod.rs b/thoth-api/src/model/work/mod.rs index 9c450055..edfe0f57 100644 --- a/thoth-api/src/model/work/mod.rs +++ b/thoth-api/src/model/work/mod.rs @@ -295,7 +295,7 @@ pub struct PatchWork { pub struct WorkHistory { pub work_history_id: Uuid, pub work_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -303,7 +303,7 @@ pub struct WorkHistory { #[cfg_attr(feature = "backend", derive(Insertable), diesel(table_name = work_history))] pub struct NewWorkHistory { pub work_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/model/work_relation/crud.rs b/thoth-api/src/model/work_relation/crud.rs index b8d563a4..c64944dc 100644 --- a/thoth-api/src/model/work_relation/crud.rs +++ b/thoth-api/src/model/work_relation/crud.rs @@ -181,7 +181,7 @@ impl Crud for WorkRelation { &self, db: &crate::db::PgPool, data: &PatchWorkRelation, - account_id: &Uuid, + user_id: &str, ) -> ThothResult<Self> { // For each Relator - Relationship - Related record we update, we must also // update the corresponding Related - InverseRelationship - Relator record. @@ -207,7 +207,7 @@ impl Crud for WorkRelation { .and_then(|t| { // On success, create a new history table entry. // Only record the original update, not the automatic inverse update. - self.new_history_entry(account_id) + self.new_history_entry(user_id) .insert(connection) .map(|_| t) }) @@ -241,10 +241,10 @@ impl Crud for WorkRelation { impl HistoryEntry for WorkRelation { type NewHistoryEntity = NewWorkRelationHistory; - fn new_history_entry(&self, account_id: &Uuid) -> Self::NewHistoryEntity { + fn new_history_entry(&self, user_id: &str) -> Self::NewHistoryEntity { Self::NewHistoryEntity { work_relation_id: self.work_relation_id, - account_id: *account_id, + user_id: user_id.to_string(), data: serde_json::Value::String(serde_json::to_string(&self).unwrap()), } } @@ -323,13 +323,13 @@ mod tests { #[test] fn test_new_work_relation_history_from_work_relation() { let work_relation: WorkRelation = Default::default(); - let account_id: Uuid = Default::default(); - let new_work_relation_history = work_relation.new_history_entry(&account_id); + let user_id = "123456".to_string(); + let new_work_relation_history = work_relation.new_history_entry(&user_id); assert_eq!( new_work_relation_history.work_relation_id, work_relation.work_relation_id ); - assert_eq!(new_work_relation_history.account_id, account_id); + assert_eq!(new_work_relation_history.user_id, user_id); assert_eq!( new_work_relation_history.data, serde_json::Value::String(serde_json::to_string(&work_relation).unwrap()) diff --git a/thoth-api/src/model/work_relation/mod.rs b/thoth-api/src/model/work_relation/mod.rs index 9cb22a72..3d5fddc1 100644 --- a/thoth-api/src/model/work_relation/mod.rs +++ b/thoth-api/src/model/work_relation/mod.rs @@ -150,7 +150,7 @@ pub struct PatchWorkRelation { pub struct WorkRelationHistory { pub work_relation_history_id: Uuid, pub work_relation_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, pub timestamp: Timestamp, } @@ -162,7 +162,7 @@ pub struct WorkRelationHistory { )] pub struct NewWorkRelationHistory { pub work_relation_id: Uuid, - pub account_id: Uuid, + pub user_id: String, pub data: serde_json::Value, } diff --git a/thoth-api/src/schema.rs b/thoth-api/src/schema.rs index 71fc7e01..a7ebe4e3 100644 --- a/thoth-api/src/schema.rs +++ b/thoth-api/src/schema.rs @@ -72,25 +72,6 @@ pub mod sql_types { pub struct AccessibilityException; } -table! { - use diesel::sql_types::*; - - account (account_id) { - account_id -> Uuid, - name -> Text, - surname -> Text, - email -> Text, - hash -> Bytea, - salt -> Text, - is_superuser -> Bool, - is_bot -> Bool, - is_active -> Bool, - created_at -> Timestamptz, - updated_at -> Timestamptz, - token -> Nullable<Text>, - } -} - table! { use diesel::sql_types::*; use super::sql_types::{LocaleCode, MarkupFormat, AbstractType}; @@ -139,7 +120,7 @@ table! { affiliation_history (affiliation_history_id) { affiliation_history_id -> Uuid, affiliation_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -165,7 +146,7 @@ table! { contact_history (contact_history_id) { contact_history_id -> Uuid, contact_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -196,7 +177,7 @@ table! { contribution_history (contribution_history_id) { contribution_history_id -> Uuid, contribution_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -223,7 +204,7 @@ table! { contributor_history (contributor_history_id) { contributor_history_id -> Uuid, contributor_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -252,7 +233,7 @@ table! { funding_history (funding_history_id) { funding_history_id -> Uuid, funding_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -278,7 +259,7 @@ table! { imprint_history (imprint_history_id) { imprint_history_id -> Uuid, imprint_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -305,7 +286,7 @@ table! { institution_history (institution_history_id) { institution_history_id -> Uuid, institution_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -330,7 +311,7 @@ table! { issue_history (issue_history_id) { issue_history_id -> Uuid, issue_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -358,7 +339,7 @@ table! { language_history (language_history_id) { language_history_id -> Uuid, language_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -386,7 +367,7 @@ table! { location_history (location_history_id) { location_history_id -> Uuid, location_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -412,7 +393,7 @@ table! { price_history (price_history_id) { price_history_id -> Uuid, price_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -452,7 +433,7 @@ table! { publication_history (publication_history_id) { publication_history_id -> Uuid, publication_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -473,25 +454,13 @@ table! { } } -table! { - use diesel::sql_types::*; - - publisher_account (account_id, publisher_id) { - account_id -> Uuid, - publisher_id -> Uuid, - is_admin -> Bool, - created_at -> Timestamptz, - updated_at -> Timestamptz, - } -} - table! { use diesel::sql_types::*; publisher_history (publisher_history_id) { publisher_history_id -> Uuid, publisher_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -535,7 +504,7 @@ table! { reference_history (reference_history_id) { reference_history_id -> Uuid, reference_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -566,7 +535,7 @@ table! { series_history (series_history_id) { series_history_id -> Uuid, series_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -593,7 +562,7 @@ table! { subject_history (subject_history_id) { subject_history_id -> Uuid, subject_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -646,7 +615,7 @@ table! { work_history (work_history_id) { work_history_id -> Uuid, work_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -673,7 +642,7 @@ table! { work_relation_history (work_relation_history_id) { work_relation_history_id -> Uuid, work_relation_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -702,7 +671,7 @@ table! { title_history (title_history_id) { title_history_id -> Uuid, title_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -714,7 +683,7 @@ table! { abstract_history (abstract_history_id) { abstract_history_id -> Uuid, abstract_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } @@ -726,81 +695,57 @@ table! { biography_history (biography_history_id) { biography_history_id -> Uuid, biography_id -> Uuid, - account_id -> Uuid, + user_id -> Text, data -> Jsonb, timestamp -> Timestamptz, } } joinable!(abstract_history -> work_abstract (abstract_id)); -joinable!(abstract_history -> account (account_id)); joinable!(affiliation -> contribution (contribution_id)); joinable!(affiliation -> institution (institution_id)); -joinable!(affiliation_history -> account (account_id)); joinable!(affiliation_history -> affiliation (affiliation_id)); joinable!(biography_history -> biography (biography_id)); -joinable!(biography_history -> account (account_id)); joinable!(contact -> publisher (publisher_id)); -joinable!(contact_history -> account (account_id)); joinable!(contact_history -> contact (contact_id)); joinable!(contribution -> contributor (contributor_id)); joinable!(contribution -> work (work_id)); -joinable!(contribution_history -> account (account_id)); joinable!(contribution_history -> contribution (contribution_id)); -joinable!(contributor_history -> account (account_id)); joinable!(contributor_history -> contributor (contributor_id)); joinable!(funding -> institution (institution_id)); joinable!(funding -> work (work_id)); -joinable!(funding_history -> account (account_id)); joinable!(funding_history -> funding (funding_id)); joinable!(imprint -> publisher (publisher_id)); -joinable!(imprint_history -> account (account_id)); joinable!(imprint_history -> imprint (imprint_id)); -joinable!(institution_history -> account (account_id)); joinable!(institution_history -> institution (institution_id)); joinable!(issue -> series (series_id)); joinable!(issue -> work (work_id)); -joinable!(issue_history -> account (account_id)); joinable!(issue_history -> issue (issue_id)); joinable!(language -> work (work_id)); -joinable!(language_history -> account (account_id)); joinable!(language_history -> language (language_id)); joinable!(location -> publication (publication_id)); -joinable!(location_history -> account (account_id)); joinable!(location_history -> location (location_id)); joinable!(price -> publication (publication_id)); -joinable!(price_history -> account (account_id)); joinable!(price_history -> price (price_id)); joinable!(publication -> work (work_id)); -joinable!(publication_history -> account (account_id)); joinable!(publication_history -> publication (publication_id)); -joinable!(publisher_account -> account (account_id)); -joinable!(publisher_account -> publisher (publisher_id)); -joinable!(publisher_history -> account (account_id)); joinable!(publisher_history -> publisher (publisher_id)); joinable!(reference -> work (work_id)); -joinable!(reference_history -> account (account_id)); joinable!(reference_history -> reference (reference_id)); joinable!(series -> imprint (imprint_id)); -joinable!(series_history -> account (account_id)); joinable!(series_history -> series (series_id)); joinable!(subject -> work (work_id)); -joinable!(subject_history -> account (account_id)); joinable!(subject_history -> subject (subject_id)); joinable!(title_history -> work_title (title_id)); -joinable!(title_history -> account (account_id)); joinable!(work -> imprint (imprint_id)); joinable!(work_abstract -> work (work_id)); -joinable!(work_history -> account (account_id)); joinable!(work_history -> work (work_id)); joinable!(work_relation -> work (relator_work_id)); -joinable!(work_relation_history -> account (account_id)); joinable!(work_relation_history -> work_relation (work_relation_id)); joinable!(work_title -> work (work_id)); allow_tables_to_appear_in_same_query!( abstract_history, - account, affiliation, affiliation_history, biography, @@ -828,7 +773,6 @@ allow_tables_to_appear_in_same_query!( publication, publication_history, publisher, - publisher_account, publisher_history, reference, reference_history, diff --git a/thoth-app/src/models/work/create_work_mutation.rs b/thoth-app/src/models/work/create_work_mutation.rs new file mode 100644 index 00000000..cedc16da --- /dev/null +++ b/thoth-app/src/models/work/create_work_mutation.rs @@ -0,0 +1,117 @@ +use chrono::NaiveDate; +use serde::Deserialize; +use serde::Serialize; +use thoth_api::model::work::Work; +use thoth_api::model::work::WorkStatus; +use thoth_api::model::work::WorkType; +use thoth_api::model::Doi; +use uuid::Uuid; + +const CREATE_WORK_MUTATION: &str = " + mutation CreateWork( + $workType: WorkType!, + $workStatus: WorkStatus!, + ) { + createWork( + data: { + workType: $workType + workStatus: $workStatus + fullTitle: $fullTitle + title: $title + subtitle: $subtitle + reference: $reference + edition: $edition + imprintId: $imprintId + doi: $doi + publicationDate: $publicationDate + withdrawnDate: $withdrawnDate + place: $place + pageCount: $pageCount + pageBreakdown: $pageBreakdown + imageCount: $imageCount + tableCount: $tableCount + audioCount: $audioCount + videoCount: $videoCount + license: $license + copyrightHolder: $copyrightHolder + landingPage: $landingPage + lccn: $lccn + oclc: $oclc + shortAbstract: $shortAbstract + longAbstract: $longAbstract + generalNote: $generalNote + bibliographyNote: $bibliographyNote + toc: $toc + coverUrl: $coverUrl + coverCaption: $coverCaption + firstPage: $firstPage + lastPage: $lastPage + pageInterval: $pageInterval + }){ + workId + workType + workStatus + fullTitle + title + imprintId + createdAt + updatedAt + updatedAtWithRelations + } + } +"; + +graphql_query_builder! { + CreateWorkRequest, + CreateWorkRequestBody, + Variables, + CREATE_WORK_MUTATION, + CreateWorkResponseBody, + CreateWorkResponseData, + PushCreateWork, + PushActionCreateWork +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct Variables { + pub work_type: WorkType, + pub work_status: WorkStatus, + pub full_title: String, + pub title: String, + pub subtitle: Option<String>, + pub reference: Option<String>, + pub edition: Option<i32>, + pub doi: Option<Doi>, + pub publication_date: Option<NaiveDate>, + pub withdrawn_date: Option<NaiveDate>, + pub place: Option<String>, + pub page_count: Option<i32>, + pub page_breakdown: Option<String>, + pub image_count: Option<i32>, + pub table_count: Option<i32>, + pub audio_count: Option<i32>, + pub video_count: Option<i32>, + pub license: Option<String>, + pub copyright_holder: Option<String>, + pub landing_page: Option<String>, + pub lccn: Option<String>, + pub oclc: Option<String>, + pub short_abstract: Option<String>, + pub long_abstract: Option<String>, + pub general_note: Option<String>, + pub bibliography_note: Option<String>, + pub toc: Option<String>, + pub cover_url: Option<String>, + pub cover_caption: Option<String>, + pub imprint_id: Uuid, + pub first_page: Option<String>, + pub last_page: Option<String>, + pub page_interval: Option<String>, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct CreateWorkResponseData { + pub create_work: Option<Work>, +} diff --git a/thoth-errors/Cargo.toml b/thoth-errors/Cargo.toml index dd55eb90..5e0c4edc 100644 --- a/thoth-errors/Cargo.toml +++ b/thoth-errors/Cargo.toml @@ -23,5 +23,6 @@ reqwest-middleware = "0.4" serde = "1.0" serde_json = "1.0" thiserror = "2.0" +tonic = "0.12.1" uuid = { package = "uuid", version = "1.16.0", features = ["serde", "v4"] } xml-rs = "0.8.25" diff --git a/thoth-errors/src/lib.rs b/thoth-errors/src/lib.rs index 74222c45..ff993586 100644 --- a/thoth-errors/src/lib.rs +++ b/thoth-errors/src/lib.rs @@ -273,6 +273,18 @@ impl From<Box<dyn std::error::Error + Send + Sync>> for ThothError { } } +impl From<Box<dyn std::error::Error>> for ThothError { + fn from(e: Box<dyn std::error::Error>) -> Self { + ThothError::InternalError(e.to_string()) + } +} + +impl From<tonic::Status> for ThothError { + fn from(e: tonic::Status) -> Self { + ThothError::InternalError(e.to_string()) + } +} + impl From<serde_json::Error> for ThothError { fn from(e: serde_json::Error) -> Self { ThothError::InternalError(e.to_string())