From 7d2e370060afc7cc8de1792628b426eb60f29f22 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 2 Apr 2023 12:08:36 -0400 Subject: [PATCH 01/39] restart trifid-api to do it better --- Cargo.lock | 1479 ++++++++-------- trifid-api/.cargo/config.toml | 2 - trifid-api/.env | 2 +- trifid-api/Cargo.lock | 1541 ----------------- trifid-api/Cargo.toml | 34 +- trifid-api/build.rs | 19 +- trifid-api/config.example.toml | 81 - trifid-api/config.toml | 9 - trifid-api/diesel.toml | 5 + .../20230204174853_create_users.sql | 29 - .../migrations/20230204185754_magic_links.sql | 21 - .../20230206012409_create_session_tokens.sql | 21 - .../20230206031048_create_auth_tokens.sql | 21 - .../20230207145453_create_totp_tokens.sql | 22 - .../migrations/20230224000741_create_orgs.sql | 24 - ...226020713_create_orgs_authorized_users.sql | 23 - .../20230228132411_add_cacheddata.sql | 20 - .../migrations/20230302220748_add_roles.sql | 22 - .../20230302220808_add_firewall_rules.sql | 27 - trifid-api/src/auth.rs | 144 -- trifid-api/src/config.rs | 78 +- trifid-api/src/crypto.rs | 43 - trifid-api/src/db.rs | 15 - trifid-api/src/format.rs | 98 -- trifid-api/src/kv.rs | 28 - trifid-api/src/main.rs | 220 +-- trifid-api/src/org.rs | 77 - trifid-api/src/role.rs | 98 -- trifid-api/src/routes/mod.rs | 82 - .../src/routes/v1/auth/check_session.rs | 47 - trifid-api/src/routes/v1/auth/magic_link.rs | 78 - trifid-api/src/routes/v1/auth/mod.rs | 20 - trifid-api/src/routes/v1/auth/totp.rs | 89 - .../src/routes/v1/auth/verify_magic_link.rs | 91 - trifid-api/src/routes/v1/ca.rs | 85 - trifid-api/src/routes/v1/mod.rs | 25 - trifid-api/src/routes/v1/organization.rs | 190 -- trifid-api/src/routes/v1/roles.rs | 95 - trifid-api/src/routes/v1/signup.rs | 89 - .../src/routes/v1/totp_authenticators.rs | 76 - trifid-api/src/routes/v1/user.rs | 47 - .../routes/v1/verify_totp_authenticator.rs | 75 - trifid-api/src/routes/v2/mod.rs | 17 - trifid-api/src/routes/v2/whoami.rs | 85 - trifid-api/src/tokens.rs | 109 -- trifid-api/src/util.rs | 31 - 46 files changed, 877 insertions(+), 4657 deletions(-) delete mode 100644 trifid-api/.cargo/config.toml delete mode 100644 trifid-api/Cargo.lock delete mode 100644 trifid-api/config.example.toml delete mode 100644 trifid-api/config.toml create mode 100644 trifid-api/diesel.toml delete mode 100644 trifid-api/migrations/20230204174853_create_users.sql delete mode 100644 trifid-api/migrations/20230204185754_magic_links.sql delete mode 100644 trifid-api/migrations/20230206012409_create_session_tokens.sql delete mode 100644 trifid-api/migrations/20230206031048_create_auth_tokens.sql delete mode 100644 trifid-api/migrations/20230207145453_create_totp_tokens.sql delete mode 100644 trifid-api/migrations/20230224000741_create_orgs.sql delete mode 100644 trifid-api/migrations/20230226020713_create_orgs_authorized_users.sql delete mode 100644 trifid-api/migrations/20230228132411_add_cacheddata.sql delete mode 100644 trifid-api/migrations/20230302220748_add_roles.sql delete mode 100644 trifid-api/migrations/20230302220808_add_firewall_rules.sql delete mode 100644 trifid-api/src/auth.rs delete mode 100644 trifid-api/src/crypto.rs delete mode 100644 trifid-api/src/db.rs delete mode 100644 trifid-api/src/format.rs delete mode 100644 trifid-api/src/kv.rs delete mode 100644 trifid-api/src/org.rs delete mode 100644 trifid-api/src/role.rs delete mode 100644 trifid-api/src/routes/mod.rs delete mode 100644 trifid-api/src/routes/v1/auth/check_session.rs delete mode 100644 trifid-api/src/routes/v1/auth/magic_link.rs delete mode 100644 trifid-api/src/routes/v1/auth/mod.rs delete mode 100644 trifid-api/src/routes/v1/auth/totp.rs delete mode 100644 trifid-api/src/routes/v1/auth/verify_magic_link.rs delete mode 100644 trifid-api/src/routes/v1/ca.rs delete mode 100644 trifid-api/src/routes/v1/mod.rs delete mode 100644 trifid-api/src/routes/v1/organization.rs delete mode 100644 trifid-api/src/routes/v1/roles.rs delete mode 100644 trifid-api/src/routes/v1/signup.rs delete mode 100644 trifid-api/src/routes/v1/totp_authenticators.rs delete mode 100644 trifid-api/src/routes/v1/user.rs delete mode 100644 trifid-api/src/routes/v1/verify_totp_authenticator.rs delete mode 100644 trifid-api/src/routes/v2/mod.rs delete mode 100644 trifid-api/src/routes/v2/whoami.rs delete mode 100644 trifid-api/src/tokens.rs delete mode 100644 trifid-api/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index a072344..d1063f4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,47 +2,199 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" + +[[package]] +name = "actix-codec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-sink", + "log", + "memchr", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "actix-http" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2079246596c18b4a33e274ae10c0e50613f4d32a4198e09c7b93771013fed74" +dependencies = [ + "actix-codec", + "actix-rt", + "actix-service", + "actix-utils", + "ahash 0.8.3", + "base64 0.21.0", + "bitflags", + "brotli", + "bytes", + "bytestring", + "derive_more", + "encoding_rs", + "flate2", + "futures-core", + "h2", + "http", + "httparse", + "httpdate", + "itoa", + "language-tags", + "local-channel", + "mime", + "percent-encoding", + "pin-project-lite", + "rand", + "sha1", + "smallvec", + "tokio", + "tokio-util", + "tracing", + "zstd", +] + +[[package]] +name = "actix-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" +dependencies = [ + "quote", + "syn 1.0.107", +] + +[[package]] +name = "actix-router" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" +dependencies = [ + "bytestring", + "http", + "regex", + "serde", + "tracing", +] + +[[package]] +name = "actix-rt" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15265b6b8e2347670eb363c47fc8c75208b4a4994b27192f345fcbe707804f3e" +dependencies = [ + "futures-core", + "tokio", +] + +[[package]] +name = "actix-server" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e8613a75dd50cc45f473cee3c34d59ed677c0f7b44480ce3b8247d7dc519327" +dependencies = [ + "actix-rt", + "actix-service", + "actix-utils", + "futures-core", + "futures-util", + "mio", + "num_cpus", + "socket2", + "tokio", + "tracing", +] + +[[package]] +name = "actix-service" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" +dependencies = [ + "futures-core", + "paste", + "pin-project-lite", +] + +[[package]] +name = "actix-utils" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" +dependencies = [ + "local-waker", + "pin-project-lite", +] + +[[package]] +name = "actix-web" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd3cb42f9566ab176e1ef0b8b3a896529062b4efc6be0123046095914c4c1c96" +dependencies = [ + "actix-codec", + "actix-http", + "actix-macros", + "actix-router", + "actix-rt", + "actix-server", + "actix-service", + "actix-utils", + "actix-web-codegen", + "ahash 0.7.6", + "bytes", + "bytestring", + "cfg-if", + "cookie", + "derive_more", + "encoding_rs", + "futures-core", + "futures-util", + "http", + "itoa", + "language-tags", + "log", + "mime", + "once_cell", + "pin-project-lite", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "socket2", + "time 0.3.17", + "url", +] + +[[package]] +name = "actix-web-codegen" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2262160a7ae29e3415554a3f1fc04c764b1540c116aa524683208078b7a75bc9" +dependencies = [ + "actix-router", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" -[[package]] -name = "aead" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - [[package]] name = "ahash" version = "0.7.6" @@ -54,6 +206,48 @@ dependencies = [ "version_check", ] +[[package]] +name = "ahash" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" +dependencies = [ + "cfg-if", + "getrandom 0.2.8", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + [[package]] name = "android_system_properties" version = "0.1.5" @@ -64,20 +258,27 @@ dependencies = [ ] [[package]] -name = "async-stream" -version = "0.3.3" +name = "arrayvec" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-stream" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" dependencies = [ "proc-macro2", "quote", @@ -104,15 +305,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] - [[package]] name = "atty" version = "0.2.14" @@ -131,10 +323,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "base32" -version = "0.4.0" +name = "bae" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" +checksum = "33b8de67cc41132507eeece2584804efcb15f85ba516e34c944b7667f480397a" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.107", +] [[package]] name = "base64" @@ -142,12 +341,6 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" -[[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - [[package]] name = "base64" version = "0.21.0" @@ -171,10 +364,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" [[package]] -name = "binascii" -version = "0.1.4" +name = "bigdecimal" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" +checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] [[package]] name = "bitflags" @@ -191,6 +389,72 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive", + "hashbrown", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.107", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + [[package]] name = "bumpalo" version = "3.12.0" @@ -198,10 +462,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] -name = "bytemuck" -version = "1.13.0" +name = "bytecheck" +version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" +checksum = "13fe11640a23eb24562225322cd3e452b93a3d4091d62fab69c70542fcd17d1f" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31225543cb46f81a7e224762764f4a6a0f097b1db0b175f69e8065efaa42de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] [[package]] name = "byteorder" @@ -215,11 +495,23 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "bytestring" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238e4886760d98c4f899360c834fa93e62cf7f721ac3c2da375cbdf4b8679aae" +dependencies = [ + "bytes", +] + [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -237,21 +529,12 @@ dependencies = [ "js-sys", "num-integer", "num-traits", + "serde", "time 0.1.45", "wasm-bindgen", "winapi", ] -[[package]] -name = "cipher" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" -dependencies = [ - "crypto-common", - "inout", -] - [[package]] name = "clap" version = "4.1.10" @@ -273,7 +556,7 @@ version = "4.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fddf67631444a3a3e3e5ac51c36a5e01335302de677bd78759eaa90ab1f46644" dependencies = [ - "heck", + "heck 0.4.1", "proc-macro-error", "proc-macro2", "quote", @@ -299,12 +582,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - [[package]] name = "colored" version = "2.0.0" @@ -323,10 +600,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" [[package]] -name = "constant_time_eq" -version = "0.2.4" +name = "convert_case" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" @@ -334,14 +611,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", - "sha2", - "subtle", "time 0.3.17", "version_check", ] @@ -371,21 +641,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crc" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" -dependencies = [ - "crc-catalog", -] - -[[package]] -name = "crc-catalog" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" - [[package]] name = "crc32fast" version = "1.3.2" @@ -407,9 +662,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -421,19 +676,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core 0.6.4", "typenum", ] -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - [[package]] name = "ctrlc" version = "3.2.5" @@ -528,35 +773,15 @@ dependencies = [ ] [[package]] -name = "devise" -version = "0.3.1" +name = "derive_more" +version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" -dependencies = [ - "bitflags", + "convert_case", "proc-macro2", - "proc-macro2-diagnostics", "quote", + "rustc_version", "syn 1.0.107", ] @@ -639,9 +864,9 @@ dependencies = [ [[package]] name = "dotenvy" -version = "0.15.6" +version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "ed25519" @@ -726,20 +951,6 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90" -[[package]] -name = "figment" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" -dependencies = [ - "atomic", - "pear", - "serde", - "toml 0.5.11", - "uncased", - "version_check", -] - [[package]] name = "filetime" version = "0.2.20" @@ -868,19 +1079,6 @@ dependencies = [ "slab", ] -[[package]] -name = "generator" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "generic-array" version = "0.14.6" @@ -913,22 +1111,6 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] -[[package]] -name = "ghash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - [[package]] name = "h2" version = "0.3.15" @@ -954,7 +1136,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" dependencies = [ - "ahash", + "ahash 0.7.6", ] [[package]] @@ -966,6 +1148,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "heck" version = "0.4.1" @@ -1128,20 +1319,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "image" -version = "0.24.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "num-rational", - "num-traits", - "png", -] - [[package]] name = "indexmap" version = "1.9.2" @@ -1150,22 +1327,6 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", - "serde", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", ] [[package]] @@ -1224,6 +1385,15 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.61" @@ -1233,6 +1403,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "language-tags" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" + [[package]] name = "lazy_static" version = "1.4.0" @@ -1266,6 +1442,24 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "local-channel" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" +dependencies = [ + "futures-core", + "futures-sink", + "futures-util", + "local-waker", +] + +[[package]] +name = "local-waker" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" + [[package]] name = "lock_api" version = "0.4.9" @@ -1285,30 +1479,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - [[package]] name = "md-5" version = "0.10.5" @@ -1357,26 +1527,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "multer" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - [[package]] name = "native-tls" version = "0.2.11" @@ -1418,13 +1568,14 @@ dependencies = [ ] [[package]] -name = "nu-ansi-term" -version = "0.46.0" +name = "num-bigint" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" dependencies = [ - "overload", - "winapi", + "autocfg", + "num-integer", + "num-traits", ] [[package]] @@ -1437,17 +1588,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.15" @@ -1482,12 +1622,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - [[package]] name = "openssl" version = "0.10.45" @@ -1550,10 +1684,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" [[package]] -name = "overload" -version = "0.1.1" +name = "ouroboros" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +checksum = "e1358bd1558bd2a083fed428ffeda486fbfb323e698cdda7794259d592ca72db" +dependencies = [ + "aliasable", + "ouroboros_macro", +] + +[[package]] +name = "ouroboros_macro" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7d21ccd03305a674437ee1248f3ab5d4b1db095cf1caf49f1713ddf61956b7" +dependencies = [ + "Inflector", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.107", +] [[package]] name = "packed_simd_2" @@ -1619,29 +1770,6 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" -[[package]] -name = "pear" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn 1.0.107", -] - [[package]] name = "pem" version = "1.1.1" @@ -1691,36 +1819,21 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" -[[package]] -name = "png" -version = "0.17.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" -dependencies = [ - "bitflags", - "crc32fast", - "flate2", - "miniz_oxide", -] - -[[package]] -name = "polyval" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml 0.5.11", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1755,24 +1868,25 @@ dependencies = [ ] [[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" +name = "ptr_meta" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", "syn 1.0.107", - "version_check", - "yansi", ] -[[package]] -name = "qrcodegen" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4339fc7a1021c9c1621d87f5e3505f2805c8c105420ba2f2a4df86814590c142" - [[package]] name = "quick-protobuf" version = "0.8.1" @@ -1850,41 +1964,14 @@ dependencies = [ "thiserror", ] -[[package]] -name = "ref-cast" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] - [[package]] name = "regex" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] @@ -1894,6 +1981,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "rend" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +dependencies = [ + "bytecheck", +] + [[package]] name = "reqwest" version = "0.11.16" @@ -1932,85 +2028,70 @@ dependencies = [ ] [[package]] -name = "rocket" -version = "0.5.0-rc.2" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ - "async-stream", - "async-trait", - "atomic", - "atty", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot 0.12.1", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time 0.3.17", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", ] [[package]] -name = "rocket_codegen" -version = "0.5.0-rc.2" +name = "rkyv" +version = "0.7.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" +checksum = "21499ed91807f07ae081880aabb2ccc0235e9d88011867d984525e9a4c3cfa3e" +dependencies = [ + "bytecheck", + "hashbrown", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1c672430eb41556291981f45ca900a0239ad007242d1cb4b4167af842db666" dependencies = [ - "devise", - "glob", - "indexmap", "proc-macro2", "quote", - "rocket_http", "syn 1.0.107", - "unicode-xid", ] [[package]] -name = "rocket_http" -version = "0.5.0-rc.2" +name = "rust_decimal" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" dependencies = [ - "cookie", - "either", - "futures", - "http", - "hyper", - "indexmap", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", + "arrayvec", + "borsh", + "bytecheck", + "byteorder", + "bytes", + "num-traits", + "rand", + "rkyv", "serde", - "smallvec", - "stable-pattern", - "state", - "time 0.3.17", - "tokio", - "uncased", + "serde_json", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", ] [[package]] @@ -2028,10 +2109,31 @@ dependencies = [ ] [[package]] -name = "rustversion" -version = "1.0.11" +name = "rustls" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.0", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "ryu" @@ -2048,12 +2150,6 @@ dependencies = [ "windows-sys 0.42.0", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.1.0" @@ -2066,6 +2162,129 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "sea-orm" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d875e2fcd965320e50066028ac0b4877ff07edbb734a6ddfeff48a87dbab38" +dependencies = [ + "async-stream", + "async-trait", + "bigdecimal", + "chrono", + "futures", + "log", + "ouroboros", + "rust_decimal", + "sea-orm-macros", + "sea-query", + "sea-query-binder", + "sea-strum", + "serde", + "serde_json", + "sqlx", + "thiserror", + "time 0.3.17", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sea-orm-macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9b593e9c0cdbb18cafd4da7b92e67a9c2d9892934f3a2d8bbac73d5ba4a98a1" +dependencies = [ + "bae", + "heck 0.3.3", + "proc-macro2", + "quote", + "syn 1.0.107", +] + +[[package]] +name = "sea-query" +version = "0.28.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fbe015dbdaa7d8829d71c1e14fb6289e928ac256b93dfda543c85cd89d6f03" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query-derive", + "serde_json", + "time 0.3.17", + "uuid", +] + +[[package]] +name = "sea-query-binder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03548c63aec07afd4fd190923e0160d2f2fc92def27470b54154cf232da6203b" +dependencies = [ + "bigdecimal", + "chrono", + "rust_decimal", + "sea-query", + "serde_json", + "sqlx", + "time 0.3.17", + "uuid", +] + +[[package]] +name = "sea-query-derive" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63f62030c60f3a691f5fe251713b4e220b306e50a71e1d6f9cce1f24bb781978" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 1.0.107", + "thiserror", +] + +[[package]] +name = "sea-strum" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391d06a6007842cfe79ac6f7f53911b76dfd69fc9a6769f1cf6569d12ce20e1b" +dependencies = [ + "sea-strum_macros", +] + +[[package]] +name = "sea-strum_macros" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b4397b825df6ccf1e98bcdabef3bbcfc47ff5853983467850eeab878384f21" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.107", +] + +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "security-framework" version = "2.8.2" @@ -2089,6 +2308,12 @@ dependencies = [ "libc", ] +[[package]] +name = "semver" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" + [[package]] name = "serde" version = "1.0.159" @@ -2185,15 +2410,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2209,6 +2425,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fe458c98333f9c8152221191a77e2a44e8325d0193484af2e9421a53019e57d" +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "simple_logger" version = "4.1.0" @@ -2249,9 +2471,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" @@ -2286,17 +2508,18 @@ dependencies = [ [[package]] name = "sqlx-core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbc16ddba161afc99e14d1713a453747a2b07fc097d2009f4c300ec99286105" +checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" dependencies = [ - "ahash", + "ahash 0.7.6", "atoi", "base64 0.13.1", + "bigdecimal", "bitflags", "byteorder", "bytes", - "crc", + "chrono", "crossbeam-queue", "dirs 4.0.0", "dotenvy", @@ -2316,10 +2539,14 @@ dependencies = [ "log", "md-5", "memchr", + "num-bigint", "once_cell", "paste", "percent-encoding", "rand", + "rust_decimal", + "rustls", + "rustls-pemfile", "serde", "serde_json", "sha1", @@ -2329,8 +2556,11 @@ dependencies = [ "sqlx-rt", "stringprep", "thiserror", + "time 0.3.17", "tokio-stream", "url", + "uuid", + "webpki-roots", "whoami", ] @@ -2342,11 +2572,11 @@ checksum = "b850fa514dc11f2ee85be9d055c512aa866746adfacd1cb42d867d68e6a5b0d9" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.4.1", "once_cell", "proc-macro2", "quote", - "sha2", + "serde_json", "sqlx-core", "sqlx-rt", "syn 1.0.107", @@ -2355,32 +2585,13 @@ dependencies = [ [[package]] name = "sqlx-rt" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24c5b2d25fa654cc5f841750b8e1cdedbe21189bf9a9382ee90bfa9dd3562396" +checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ - "native-tls", "once_cell", "tokio", - "tokio-native-tls", -] - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", + "tokio-rustls", ] [[package]] @@ -2480,7 +2691,7 @@ dependencies = [ [[package]] name = "tfclient" -version = "0.1.4" +version = "0.1.5" dependencies = [ "base64 0.21.0", "base64-serde", @@ -2527,15 +2738,6 @@ dependencies = [ "syn 1.0.107", ] -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - [[package]] name = "time" version = "0.1.45" @@ -2607,21 +2809,9 @@ dependencies = [ "pin-project-lite", "signal-hook-registry", "socket2", - "tokio-macros", "windows-sys 0.42.0", ] -[[package]] -name = "tokio-macros" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] - [[package]] name = "tokio-native-tls" version = "0.3.0" @@ -2633,10 +2823,21 @@ dependencies = [ ] [[package]] -name = "tokio-stream" -version = "0.1.11" +name = "tokio-rustls" +version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + +[[package]] +name = "tokio-stream" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", "pin-project-lite", @@ -2700,25 +2901,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "totp-rs" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdd21080b6cf581e0c8fe849626ad627b42af1a0f71ce980244f2d6b1a47836" -dependencies = [ - "base32", - "base64 0.20.0", - "constant_time_eq", - "hmac", - "image", - "qrcodegen", - "rand", - "sha1", - "sha2", - "url", - "urlencoding", -] - [[package]] name = "tower-service" version = "0.3.2" @@ -2732,6 +2914,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2755,62 +2938,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" dependencies = [ "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", ] [[package]] name = "trifid-api" version = "0.1.0" dependencies = [ - "aes-gcm", - "base64 0.21.0", - "chrono", - "dotenvy", + "actix-web", "hex", - "ipnet", "log", - "paste", + "once_cell", "rand", - "rocket", + "sea-orm", "serde", - "sha2", - "sqlx", - "tokio", + "simple_logger", "toml 0.7.3", - "totp-rs", - "trifid-pki", - "url", - "urlencoding", - "uuid", ] [[package]] @@ -2841,25 +2983,6 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" -[[package]] -name = "ubyte" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" -dependencies = [ - "serde", -] - -[[package]] -name = "uncased" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" -dependencies = [ - "serde", - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.10" @@ -2905,22 +3028,18 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" -[[package]] -name = "universal-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" -dependencies = [ - "crypto-common", - "subtle", -] - [[package]] name = "unsafe-libyaml" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad2024452afd3874bf539695e04af6732ba06517424dbf958fdb16a01f3bef6c" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.3.1" @@ -2930,43 +3049,17 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", - "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" - [[package]] name = "uuid" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ - "getrandom 0.2.8", - "rand", - "uuid-macro-internal", + "serde", ] -[[package]] -name = "uuid-macro-internal" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b300a878652a387d2a0de915bdae8f1a548f0c6d45e072fe2688794b656cc9" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.107", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - [[package]] name = "vcpkg" version = "0.2.15" @@ -3084,10 +3177,29 @@ dependencies = [ ] [[package]] -name = "whoami" -version = "1.3.0" +name = "webpki" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45dbc71f0cdca27dc261a9bd37ddec174e4a0af2b900b890f378460f745426e3" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87" +dependencies = [ + "webpki", +] + +[[package]] +name = "whoami" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" dependencies = [ "wasm-bindgen", "web-sys", @@ -3124,19 +3236,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" -dependencies = [ - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", -] - [[package]] name = "windows-sys" version = "0.42.0" @@ -3144,12 +3243,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", + "windows_x86_64_msvc", ] [[package]] @@ -3168,12 +3267,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", + "windows_x86_64_msvc", ] [[package]] @@ -3182,48 +3281,24 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" -[[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - [[package]] name = "windows_aarch64_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" -[[package]] -name = "windows_i686_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - [[package]] name = "windows_i686_gnu" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" -[[package]] -name = "windows_i686_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - [[package]] name = "windows_i686_msvc" version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" -[[package]] -name = "windows_x86_64_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - [[package]] name = "windows_x86_64_gnu" version = "0.42.1" @@ -3236,12 +3311,6 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" -[[package]] -name = "windows_x86_64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - [[package]] name = "windows_x86_64_msvc" version = "0.42.1" @@ -3287,12 +3356,6 @@ dependencies = [ "libc", ] -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" - [[package]] name = "zeroize" version = "1.5.7" @@ -3313,3 +3376,33 @@ dependencies = [ "syn 1.0.107", "synstructure", ] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "6.0.4+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7afb4b54b8910cf5447638cb54bf4e8a65cbedd783af98b98c62ffe91f185543" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.7+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94509c3ba2fe55294d752b79842c530ccfab760192521df74a081a78d2b3c7f5" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/trifid-api/.cargo/config.toml b/trifid-api/.cargo/config.toml deleted file mode 100644 index a7194eb..0000000 --- a/trifid-api/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[build] -rustc-wrapper = "sccache" \ No newline at end of file diff --git a/trifid-api/.env b/trifid-api/.env index 752d444..54dc561 100644 --- a/trifid-api/.env +++ b/trifid-api/.env @@ -1 +1 @@ -DATABASE_URL=postgres://postgres@localhost/trifidapi \ No newline at end of file +DATABASE_URL=postgres://postgres@localhost/hotel \ No newline at end of file diff --git a/trifid-api/Cargo.lock b/trifid-api/Cargo.lock deleted file mode 100644 index 54b1be2..0000000 --- a/trifid-api/Cargo.lock +++ /dev/null @@ -1,1541 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "aead" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" -dependencies = [ - "crypto-common", - "generic-array", -] - -[[package]] -name = "aes" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "aes-gcm" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" -dependencies = [ - "aead", - "aes", - "cipher", - "ctr", - "ghash", - "subtle", -] - -[[package]] -name = "async-stream" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" -dependencies = [ - "async-stream-impl", - "futures-core", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "atomic" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" -dependencies = [ - "autocfg", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi 0.1.19", - "libc", - "winapi", -] - -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "base64" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" - -[[package]] -name = "base64" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" - -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" -dependencies = [ - "generic-array", -] - -[[package]] -name = "bytes" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" - -[[package]] -name = "cc" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cipher" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" -dependencies = [ - "crypto-common", - "inout", -] - -[[package]] -name = "cookie" -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", - "sha2", - "subtle", - "time", - "version_check", -] - -[[package]] -name = "cpufeatures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "rand_core", - "typenum", -] - -[[package]] -name = "ctr" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" -dependencies = [ - "cipher", -] - -[[package]] -name = "devise" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" -dependencies = [ - "bitflags", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" -dependencies = [ - "block-buffer", - "crypto-common", - "subtle", -] - -[[package]] -name = "either" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" - -[[package]] -name = "encoding_rs" -version = "0.8.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "fastrand" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" -dependencies = [ - "instant", -] - -[[package]] -name = "figment" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" -dependencies = [ - "atomic", - "pear", - "serde", - "toml", - "uncased", - "version_check", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "futures" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" - -[[package]] -name = "futures-io" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" - -[[package]] -name = "futures-sink" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" - -[[package]] -name = "futures-task" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" - -[[package]] -name = "futures-util" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generator" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", -] - -[[package]] -name = "generic-array" -version = "0.14.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "ghash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" -dependencies = [ - "opaque-debug", - "polyval", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "h2" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hkdf" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" -dependencies = [ - "hmac", -] - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "http" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-body" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" -dependencies = [ - "bytes", - "http", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" - -[[package]] -name = "hyper" -version = "0.14.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - -[[package]] -name = "indexmap" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" -dependencies = [ - "autocfg", - "hashbrown", - "serde", -] - -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - -[[package]] -name = "inout" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" -dependencies = [ - "generic-array", -] - -[[package]] -name = "instant" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "itoa" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.139" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" - -[[package]] -name = "lock_api" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata", -] - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mio" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys", -] - -[[package]] -name = "multer" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http", - "httparse", - "log", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - -[[package]] -name = "nu-ansi-term" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" -dependencies = [ - "overload", - "winapi", -] - -[[package]] -name = "num_cpus" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" -dependencies = [ - "hermit-abi 0.2.6", - "libc", -] - -[[package]] -name = "once_cell" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" - -[[package]] -name = "opaque-debug" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" - -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - -[[package]] -name = "parking_lot" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys", -] - -[[package]] -name = "pear" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - -[[package]] -name = "percent-encoding" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" - -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "polyval" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" -dependencies = [ - "cfg-if", - "cpufeatures", - "opaque-debug", - "universal-hash", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proc-macro2-diagnostics" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", - "yansi", -] - -[[package]] -name = "quote" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "redox_syscall" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" -dependencies = [ - "bitflags", -] - -[[package]] -name = "ref-cast" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "regex" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.6.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] - -[[package]] -name = "rocket" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" -dependencies = [ - "async-stream", - "async-trait", - "atomic", - "atty", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2", - "quote", - "rocket_http", - "syn", - "unicode-xid", -] - -[[package]] -name = "rocket_http" -version = "0.5.0-rc.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" -dependencies = [ - "cookie", - "either", - "futures", - "http", - "hyper", - "indexmap", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "uncased", -] - -[[package]] -name = "rustversion" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" - -[[package]] -name = "ryu" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "serde" -version = "1.0.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sharded-slab" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" -dependencies = [ - "lazy_static", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" - -[[package]] -name = "socket2" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "spin" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" - -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - -[[package]] -name = "state" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" -dependencies = [ - "loom", -] - -[[package]] -name = "subtle" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" - -[[package]] -name = "syn" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", -] - -[[package]] -name = "thread_local" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" -dependencies = [ - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" -dependencies = [ - "itoa", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" -dependencies = [ - "time-core", -] - -[[package]] -name = "tokio" -version = "1.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" -dependencies = [ - "autocfg", - "bytes", - "libc", - "memchr", - "mio", - "num_cpus", - "pin-project-lite", - "signal-hook-registry", - "socket2", - "tokio-macros", - "windows-sys", -] - -[[package]] -name = "tokio-macros" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tokio-stream" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", - "tracing", -] - -[[package]] -name = "toml" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" -dependencies = [ - "serde", -] - -[[package]] -name = "tower-service" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" - -[[package]] -name = "tracing" -version = "0.1.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" -dependencies = [ - "cfg-if", - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" -dependencies = [ - "once_cell", - "valuable", -] - -[[package]] -name = "tracing-log" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" -dependencies = [ - "lazy_static", - "log", - "tracing-core", -] - -[[package]] -name = "tracing-subscriber" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" -dependencies = [ - "matchers", - "nu-ansi-term", - "once_cell", - "regex", - "sharded-slab", - "smallvec", - "thread_local", - "tracing", - "tracing-core", - "tracing-log", -] - -[[package]] -name = "trifid" -version = "0.1.0" -dependencies = [ - "base64 0.21.0", - "log", - "rocket", -] - -[[package]] -name = "try-lock" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" - -[[package]] -name = "typenum" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" - -[[package]] -name = "ubyte" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" -dependencies = [ - "serde", -] - -[[package]] -name = "uncased" -version = "0.9.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" -dependencies = [ - "serde", - "version_check", -] - -[[package]] -name = "unicode-ident" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" - -[[package]] -name = "unicode-xid" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" - -[[package]] -name = "universal-hash" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" -dependencies = [ - "crypto-common", - "subtle", -] - -[[package]] -name = "valuable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "want" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" -dependencies = [ - "log", - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" -dependencies = [ - "windows_aarch64_msvc 0.39.0", - "windows_i686_gnu 0.39.0", - "windows_i686_msvc 0.39.0", - "windows_x86_64_gnu 0.39.0", - "windows_x86_64_msvc 0.39.0", -] - -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.1", - "windows_i686_gnu 0.42.1", - "windows_i686_msvc 0.42.1", - "windows_x86_64_gnu 0.42.1", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.1", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" - -[[package]] -name = "windows_i686_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" - -[[package]] -name = "windows_i686_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" - -[[package]] -name = "windows_i686_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" - -[[package]] -name = "windows_i686_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.39.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" - -[[package]] -name = "yansi" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index d63dc22..b9cfbd1 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -6,23 +6,17 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rocket = { version = "0.5.0-rc.2", features = ["json"] } -base64 = "0.21.0" -log = "0.4.17" -sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] } -tokio = { version = "1", features = ["full"] } -toml = "0.7.1" -serde = "1.0.152" -dotenvy = "0.15.6" -paste = "1.0.11" -totp-rs = { version = "4.2.0", features = ["qr", "otpauth", "gen_secret"]} -uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"]} -url = { version = "2.3.1", features = ["serde"] } -urlencoding = "2.1.2" -chrono = "0.4.23" -aes-gcm = "0.10.1" -hex = "0.4.3" -rand = "0.8.5" -trifid-pki = { version = "0.1.3", path = "../trifid-pki" } -sha2 = "0.10.6" -ipnet = { version = "2.7.1", features = ["serde"] } +actix-web = "4" # Web framework + +serde = { version = "1", features = ["derive"] } # Serialization and deserialization + +once_cell = "1" # Config +toml = "0.7" # Config + +log = "0.4" # Logging +simple_logger = "4" # Logging + +sea-orm = { version = "^0", features = [ "sqlx-postgres", "runtime-actix-rustls", "macros" ]} # Database + +rand = "0.8" # Misc. +hex = "0.4" # Misc. \ No newline at end of file diff --git a/trifid-api/build.rs b/trifid-api/build.rs index d658ea5..3c284bb 100644 --- a/trifid-api/build.rs +++ b/trifid-api/build.rs @@ -1,20 +1,3 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . -// generated by `sqlx migrate build-script` fn main() { - // trigger recompilation when a new migration is added - println!("cargo:rerun-if-changed=migrations"); + println!("cargo:rerun-if-changed=migrations/"); } \ No newline at end of file diff --git a/trifid-api/config.example.toml b/trifid-api/config.example.toml deleted file mode 100644 index 921f189..0000000 --- a/trifid-api/config.example.toml +++ /dev/null @@ -1,81 +0,0 @@ -################################## -# trifid-api example config file # -################################## -# trifid-api, an open source reimplementation of the Defined Networking nebula management server. -# Copyright (C) 2023 c0repwn3r -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -# Please read this file in it's entirety to learn what options you do or don't need to change -# to get a functional trifid-api instance. - -# What port should the API server listen on? -# e.g. 8000 would mean the server is reachable at localhost:8000. -# You probably don't need to change this. -listen_port = 8000 - -# What is the postgres connection url to connect to the database? -# Example: postgres://username:password@database_host/database_name -# You absolutely need to change this. -db_url = "postgres://postgres@localhost/trifidapi" - -# What is the externally accessible URL of this instance? -# If you are running behind a reverse proxy, or a domain name, or similar, -# you need to set this to the URL that the web UI can make requests to. -# e.g. http://localhost:8000 -# Reminder: this ip needs to be internet-accessible. -# You absolutely need to change this. -base = "http://localhost:8000" - -# What is the externally accessible URL of the **web ui** for this instance? -# This URL will be used to generate magic links, and needs to be correct. -# You absolutely need to change this. -web_root = "http://localhost:5173" - -# How long should magic links be valid for (in seconds)? -# You probably don't need to change this, 86400 (24 hours) is a sane default. -magic_links_valid_for = 86400 - -# How long should session tokens be valid for (in seconds)? -# This controls how long a user can go without requesting a new "magic link" to re-log-in. -# This is a completley independent timer than `totp_verification_valid_for` - the auth token can (and often will) expire -# while the session token remains completley valid. -# You probably don't need to change this, 86400 (24 hours) is a sane default. -session_tokens_valid_for = 86400 - -# How long should 2FA authentication be valid for (in seconds)? -# This controls how long a user can remain logged in without having to re-do the 2FA authentication process. -# This is a completley independent timer than `session_tokens_valid_for` - the session token can expire while the 2FA token -# remains completley valid. -# You probably don't need to change this, 3600 (1 hour) is a sane default. -totp_verification_valid_for = 3600 - -# The per-instance data encryption key to protect sensitive data in the instance. -# YOU ABSOLUTELY NEED TO CHANGE THIS. If you don't change anything else in this file, this should be the one thing you change. - -# This should be a 32-byte hex value. Generate it with `openssl rand -hex 32`, or any other tool of your choice. -# If you get "InvalidLength" errors while trying to do anything involving organizations, that indicates that this -# value was improperly generated. -# -# ------- WARNING ------- -# Do not change this value in a production instance. It will make existing data inaccessible until changed back. -# ------- WARNING ------- -data_key = "edd600bcebea461381ea23791b6967c8667e12827ac8b94dc022f189a5dc59a2" - -# How long should CA certs be valid for before they need to be replaced (in seconds)? -# This controls the maximum amount of time a network on this instance can go -# without a rekey. -# You probably don't need to change, this, 31536000 (1 year) is a sane default. -# This value only affects new certs signed by this instance. -ca_certs_valid_for = 31536000 \ No newline at end of file diff --git a/trifid-api/config.toml b/trifid-api/config.toml deleted file mode 100644 index f206eff..0000000 --- a/trifid-api/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -listen_port = 8000 -db_url = "postgres://postgres@localhost/trifidapi" -base = "http://localhost:8000" -web_root = "http://localhost:5173" -magic_links_valid_for = 86400 -session_tokens_valid_for = 86400 -totp_verification_valid_for = 3600 -data_key = "edd600bcebea461381ea23791b6967c8667e12827ac8b94dc022f189a5dc59a2" -ca_certs_valid_for = 31536000 \ No newline at end of file diff --git a/trifid-api/diesel.toml b/trifid-api/diesel.toml new file mode 100644 index 0000000..4ae5e4b --- /dev/null +++ b/trifid-api/diesel.toml @@ -0,0 +1,5 @@ +[print_schema] +file = "src/schema.rs" + +[migrations_directory] +dir = "migrations" \ No newline at end of file diff --git a/trifid-api/migrations/20230204174853_create_users.sql b/trifid-api/migrations/20230204174853_create_users.sql deleted file mode 100644 index 53fce23..0000000 --- a/trifid-api/migrations/20230204174853_create_users.sql +++ /dev/null @@ -1,29 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE users ( - id SERIAL NOT NULL PRIMARY KEY, - email VARCHAR(320) NOT NULL UNIQUE, - created_on INTEGER NOT NULL, -- Unix (seconds) timestamp of user creation - - banned INTEGER NOT NULL, -- Is the user banned? 1=Yes 0=No - ban_reason VARCHAR(1024) NOT NULL, -- What is the reason for this user's ban? - - totp_secret VARCHAR(128) NOT NULL, - totp_verified INTEGER NOT NULL, - totp_otpurl VARCHAR(3000) NOT NULL -); -CREATE INDEX idx_users_email ON users(email); \ No newline at end of file diff --git a/trifid-api/migrations/20230204185754_magic_links.sql b/trifid-api/migrations/20230204185754_magic_links.sql deleted file mode 100644 index 889ddb2..0000000 --- a/trifid-api/migrations/20230204185754_magic_links.sql +++ /dev/null @@ -1,21 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE magic_links ( - id VARCHAR(39) NOT NULL PRIMARY KEY UNIQUE, - user_id SERIAL NOT NULL REFERENCES users(id), - expires_on INTEGER NOT NULL -- Unix (seconds) timestamp of when this link expires -); \ No newline at end of file diff --git a/trifid-api/migrations/20230206012409_create_session_tokens.sql b/trifid-api/migrations/20230206012409_create_session_tokens.sql deleted file mode 100644 index e7d0133..0000000 --- a/trifid-api/migrations/20230206012409_create_session_tokens.sql +++ /dev/null @@ -1,21 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE session_tokens ( - id VARCHAR(39) NOT NULL PRIMARY KEY, - user_id SERIAL NOT NULL REFERENCES users(id), - expires_on INTEGER NOT NULL -- Unix (seconds) timestamp of when this session expires -); \ No newline at end of file diff --git a/trifid-api/migrations/20230206031048_create_auth_tokens.sql b/trifid-api/migrations/20230206031048_create_auth_tokens.sql deleted file mode 100644 index 23c9b5e..0000000 --- a/trifid-api/migrations/20230206031048_create_auth_tokens.sql +++ /dev/null @@ -1,21 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE auth_tokens ( - id VARCHAR(39) NOT NULL PRIMARY KEY, - user_id SERIAL NOT NULL REFERENCES users(id), - session_token VARCHAR(39) NOT NULL REFERENCES session_tokens(id) -); \ No newline at end of file diff --git a/trifid-api/migrations/20230207145453_create_totp_tokens.sql b/trifid-api/migrations/20230207145453_create_totp_tokens.sql deleted file mode 100644 index 86b493c..0000000 --- a/trifid-api/migrations/20230207145453_create_totp_tokens.sql +++ /dev/null @@ -1,22 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE totp_create_tokens ( - id VARCHAR(41) NOT NULL PRIMARY KEY, - expires_on INTEGER NOT NULL, -- The unix (seconds) timestamp of when this TOTP create token expires - totp_otpurl VARCHAR(3000) NOT NULL, - totp_secret VARCHAR(128) NOT NULL -); \ No newline at end of file diff --git a/trifid-api/migrations/20230224000741_create_orgs.sql b/trifid-api/migrations/20230224000741_create_orgs.sql deleted file mode 100644 index 5a50f13..0000000 --- a/trifid-api/migrations/20230224000741_create_orgs.sql +++ /dev/null @@ -1,24 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE organizations ( - id SERIAL NOT NULL PRIMARY KEY, - owner SERIAL NOT NULL REFERENCES users(id), - ca_key VARCHAR(3072) NOT NULL, -- The hex-encoded ENCRYPTED (see below) concatenation of all CA keys on this org - ca_crt VARCHAR(3072) NOT NULL, -- The concatenation of all CA certificates on this org. This is passed directly to NebulaCAPool - iv VARCHAR(128) NOT NULL -- The 12-byte hex-encoded IV, used to encrypt ca_key with the instance AES key -); -CREATE INDEX idx_organizations_owner ON organizations(owner); \ No newline at end of file diff --git a/trifid-api/migrations/20230226020713_create_orgs_authorized_users.sql b/trifid-api/migrations/20230226020713_create_orgs_authorized_users.sql deleted file mode 100644 index 530b9e4..0000000 --- a/trifid-api/migrations/20230226020713_create_orgs_authorized_users.sql +++ /dev/null @@ -1,23 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE organization_authorized_users ( - id SERIAL NOT NULL PRIMARY KEY, - user_id SERIAL NOT NULL REFERENCES users(id), - org_id SERIAL NOT NULL REFERENCES organizations(id) -); -CREATE INDEX idx_organization_authorized_users_user ON organization_authorized_users(user_id); -CREATE INDEX idx_organization_authorized_users_org ON organization_authorized_users(org_id); \ No newline at end of file diff --git a/trifid-api/migrations/20230228132411_add_cacheddata.sql b/trifid-api/migrations/20230228132411_add_cacheddata.sql deleted file mode 100644 index 169105d..0000000 --- a/trifid-api/migrations/20230228132411_add_cacheddata.sql +++ /dev/null @@ -1,20 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE cacheddata ( - datakey VARCHAR(256) NOT NULL PRIMARY KEY, - datavalue VARCHAR(2048) NOT NULL -); \ No newline at end of file diff --git a/trifid-api/migrations/20230302220748_add_roles.sql b/trifid-api/migrations/20230302220748_add_roles.sql deleted file mode 100644 index 54c1b55..0000000 --- a/trifid-api/migrations/20230302220748_add_roles.sql +++ /dev/null @@ -1,22 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE roles ( - id SERIAL NOT NULL PRIMARY KEY, - org SERIAL NOT NULL REFERENCES organizations(id), - name VARCHAR(128) NOT NULL, - description VARCHAR(4096) NOT NULL -); \ No newline at end of file diff --git a/trifid-api/migrations/20230302220808_add_firewall_rules.sql b/trifid-api/migrations/20230302220808_add_firewall_rules.sql deleted file mode 100644 index 69961af..0000000 --- a/trifid-api/migrations/20230302220808_add_firewall_rules.sql +++ /dev/null @@ -1,27 +0,0 @@ --- trifid-api, an open source reimplementation of the Defined Networking nebula management server. --- Copyright (C) 2023 c0repwn3r --- --- This program is free software: you can redistribute it and/or modify --- it under the terms of the GNU General Public License as published by --- the Free Software Foundation, either version 3 of the License, or --- (at your option) any later version. --- --- This program is distributed in the hope that it will be useful, --- but WITHOUT ANY WARRANTY; without even the implied warranty of --- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --- GNU General Public License for more details. --- --- You should have received a copy of the GNU General Public License --- along with this program. If not, see . - -CREATE TABLE roles_firewall_rules ( - id SERIAL NOT NULL PRIMARY KEY, - role SERIAL NOT NULL REFERENCES roles(id), - protocol INTEGER NOT NULL, -- 0: any 1: tcp 2: udp 3: icmp - port_range_start INTEGER NOT NULL, -- min: 1 max: 65535. Ignored if protocol==3 - port_range_end INTEGER NOT NULL, -- min: 1 max: 65535, must be greater than or equal to port_range_start. Ignored if protocol==3 - allow_from INTEGER NOT NULL, -- Allow traffic goverened by above rules from who? - -- -1: anybody - -- (a role, anything else): only that role - description VARCHAR(4096) NOT NULL -); \ No newline at end of file diff --git a/trifid-api/src/auth.rs b/trifid-api/src/auth.rs deleted file mode 100644 index cf399af..0000000 --- a/trifid-api/src/auth.rs +++ /dev/null @@ -1,144 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::http::Status; -use rocket::{Request}; -use rocket::request::{FromRequest, Outcome}; -use crate::tokens::{validate_auth_token, validate_session_token}; - -pub struct PartialUserInfo { - pub user_id: i32, - pub created_at: i64, - pub email: String, - pub has_totp_auth: bool, - - pub session_id: String, - pub auth_id: Option -} - -#[derive(Debug)] -pub enum AuthenticationError { - MissingToken, - InvalidToken(usize), - DatabaseError, - RequiresTOTP -} - -#[rocket::async_trait] -impl<'r> FromRequest<'r> for PartialUserInfo { - type Error = AuthenticationError; - - async fn from_request(req: &'r Request<'_>) -> Outcome { - let headers = req.headers(); - - // make sure the bearer token exists - if let Some(authorization) = headers.get_one("Authorization") { - // parse bearer token - let components = authorization.split(' ').collect::>(); - - if components.len() != 2 && components.len() != 3 { - return Outcome::Failure((Status::Unauthorized, AuthenticationError::MissingToken)); - } - - if components[0] != "Bearer" { - return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(0))); - } - - if components.len() == 2 && !components[1].starts_with("st-") { - return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(1))); - } - - let st: String; - let user_id: i64; - let at: Option; - - match &components[1][..3] { - "st-" => { - // validate session token - st = components[1].to_string(); - match validate_session_token(st.clone(), req.rocket().state().unwrap()).await { - Ok(uid) => user_id = uid, - Err(_) => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(2))) - } - }, - _ => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(3))) - } - - if components.len() == 3 { - match &components[2][..3] { - "at-" => { - // validate auth token - at = Some(components[2].to_string()); - match validate_auth_token(at.clone().unwrap().clone(), st.clone(), req.rocket().state().unwrap()).await { - Ok(_) => (), - Err(_) => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(4))) - } - }, - _ => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(5))) - } - } else { - at = None; - } - - // this user is 100% valid and authenticated, fetch their info - - let user = match sqlx::query!("SELECT * FROM users WHERE id = $1", user_id.clone() as i32).fetch_one(req.rocket().state().unwrap()).await { - Ok(u) => u, - Err(_) => return Outcome::Failure((Status::InternalServerError, AuthenticationError::DatabaseError)) - }; - - Outcome::Success(PartialUserInfo { - user_id: user_id as i32, - created_at: user.created_on as i64, - email: user.email, - has_totp_auth: at.is_some(), - session_id: st, - auth_id: at, - }) - } else { - Outcome::Failure((Status::Unauthorized, AuthenticationError::MissingToken)) - } - } -} - -pub struct TOTPAuthenticatedUserInfo { - pub user_id: i32, - pub created_at: i64, - pub email: String, -} -#[rocket::async_trait] -impl<'r> FromRequest<'r> for TOTPAuthenticatedUserInfo { - type Error = AuthenticationError; - - async fn from_request(request: &'r Request<'_>) -> Outcome { - let userinfo = PartialUserInfo::from_request(request).await; - match userinfo { - Outcome::Failure(e) => Outcome::Failure(e), - Outcome::Forward(f) => Outcome::Forward(f), - Outcome::Success(s) => { - if s.has_totp_auth { - Outcome::Success(Self { - user_id: s.user_id, - created_at: s.created_at, - email: s.email, - }) - } else { - Outcome::Failure((Status::Unauthorized, AuthenticationError::RequiresTOTP)) - } - } - } - } -} \ No newline at end of file diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 954a4f7..6abdc18 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -1,31 +1,51 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . +use std::fs; +use log::error; +use once_cell::sync::Lazy; +use serde::{Serialize, Deserialize}; -use serde::Deserialize; -use url::Url; +pub static CONFIG: Lazy = Lazy::new(|| { + let config_str = match fs::read_to_string("/etc/trifid/config.toml") { + Ok(str) => str, + Err(e) => { + error!("Unable to read config file: {}", e); + std::process::exit(1); + } + }; -#[derive(Deserialize)] -pub struct TFConfig { - pub listen_port: u16, - pub db_url: String, - pub base: Url, - pub web_root: Url, - pub magic_links_valid_for: u64, - pub session_tokens_valid_for: u64, - pub totp_verification_valid_for: u64, - pub data_key: String, - pub ca_certs_valid_for: u64 -} \ No newline at end of file + match toml::from_str(&config_str) { + Ok(cfg) => cfg, + Err(e) => { + error!("Unable to parse config file: {}", e); + std::process::exit(1); + } + } +}); + +#[derive(Serialize, Debug, Deserialize)] +pub struct TrifidConfig { + pub database: TrifidConfigDatabase +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct TrifidConfigDatabase { + pub url: String, + #[serde(default = "max_connections_default")] + pub max_connections: u32, + #[serde(default = "min_connections_default")] + pub min_connections: u32, + #[serde(default = "time_defaults")] + pub connect_timeout: u64, + #[serde(default = "time_defaults")] + pub acquire_timeout: u64, + #[serde(default = "time_defaults")] + pub idle_timeout: u64, + #[serde(default = "time_defaults")] + pub max_lifetime: u64, + #[serde(default = "sqlx_logging_default")] + pub sqlx_logging: bool +} + +fn max_connections_default() -> u32 { 100 } +fn min_connections_default() -> u32 { 5 } +fn time_defaults() -> u64 { 8 } +fn sqlx_logging_default() -> bool { true } \ No newline at end of file diff --git a/trifid-api/src/crypto.rs b/trifid-api/src/crypto.rs deleted file mode 100644 index bb879de..0000000 --- a/trifid-api/src/crypto.rs +++ /dev/null @@ -1,43 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::error::Error; -use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; -use aes_gcm::aead::{Aead, Payload}; -use rand::Rng; -use rand::rngs::OsRng; -use crate::config::TFConfig; - -pub fn get_cipher_from_config(config: &TFConfig) -> Result> { - let key_slice = hex::decode(&config.data_key)?; - Ok(Aes256Gcm::new_from_slice(&key_slice)?) -} - -pub fn encrypt_with_nonce(plaintext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result, aes_gcm::Error> { - let nonce = Nonce::from_slice(&nonce); - let ciphertext = cipher.encrypt(nonce, plaintext)?; - Ok(ciphertext) -} - -pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result, aes_gcm::Error> { - let nonce = Nonce::from_slice(&nonce); - let plaintext = cipher.decrypt(nonce, Payload::from(ciphertext))?; - Ok(plaintext) -} - -pub fn generate_random_iv() -> [u8; 12] { - OsRng.gen() -} \ No newline at end of file diff --git a/trifid-api/src/db.rs b/trifid-api/src/db.rs deleted file mode 100644 index e15090a..0000000 --- a/trifid-api/src/db.rs +++ /dev/null @@ -1,15 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . \ No newline at end of file diff --git a/trifid-api/src/format.rs b/trifid-api/src/format.rs deleted file mode 100644 index d193d65..0000000 --- a/trifid-api/src/format.rs +++ /dev/null @@ -1,98 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::fmt::{Display, Formatter}; -use crate::format::PEMValidationError::{IncorrectSegmentLength, InvalidBase64Data, MissingStartSentinel}; -use crate::util::base64decode; - -pub const ED_PUBKEY_START_STR: &str = "-----BEGIN NEBULA ED25519 PUBLIC KEY-----"; -pub const ED_PUBKEY_END_STR: &str = "-----END NEBULA ED25519 PUBLIC KEY-----"; - -pub const DH_PUBKEY_START_STR: &str = "-----BEGIN NEBULA X25519 PUBLIC KEY-----"; -pub const DH_PUBKEY_END_STR: &str = "-----END NEBULA X25519 PUBLIC KEY-----"; - -pub enum PEMValidationError { - MissingStartSentinel, - InvalidBase64Data, - MissingEndSentinel, - IncorrectSegmentLength(usize, usize) -} -impl Display for PEMValidationError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::MissingEndSentinel => write!(f, "Missing ending sentinel"), - Self::MissingStartSentinel => write!(f, "Missing starting sentinel"), - Self::InvalidBase64Data => write!(f, "invalid base64 data"), - Self::IncorrectSegmentLength(expected, got) => write!(f, "incorrect number of segments, expected {} got {}", expected, got) - } - } -} - -pub fn validate_ed_pubkey_pem(pubkey: &str) -> Result<(), PEMValidationError> { - let segments = pubkey.split('\n'); - let segs = segments.collect::>(); - if segs.len() < 3 { - return Err(IncorrectSegmentLength(3, segs.len())) - } - if segs[0] != ED_PUBKEY_START_STR { - return Err(MissingStartSentinel) - } - if base64decode(segs[1]).is_err() { - return Err(InvalidBase64Data) - } - if segs[2] != ED_PUBKEY_END_STR { - return Err(MissingStartSentinel) - } - Ok(()) -} - -pub fn validate_dh_pubkey_pem(pubkey: &str) -> Result<(), PEMValidationError> { - let segments = pubkey.split('\n'); - let segs = segments.collect::>(); - if segs.len() < 3 { - return Err(IncorrectSegmentLength(3, segs.len())) - } - if segs[0] != DH_PUBKEY_START_STR { - return Err(MissingStartSentinel) - } - if base64decode(segs[1]).is_err() { - return Err(InvalidBase64Data) - } - if segs[2] != DH_PUBKEY_END_STR { - return Err(MissingStartSentinel) - } - Ok(()) -} - -pub fn validate_ed_pubkey_base64(pubkey: &str) -> Result<(), PEMValidationError> { - match base64decode(pubkey) { - Ok(k) => validate_ed_pubkey_pem(match std::str::from_utf8(k.as_ref()) { - Ok(k) => k, - Err(_) => return Err(InvalidBase64Data) - }), - Err(_) => Err(InvalidBase64Data) - } -} - -pub fn validate_dh_pubkey_base64(pubkey: &str) -> Result<(), PEMValidationError> { - match base64decode(pubkey) { - Ok(k) => validate_dh_pubkey_pem(match std::str::from_utf8(k.as_ref()) { - Ok(k) => k, - Err(_) => return Err(InvalidBase64Data) - }), - Err(_) => Err(InvalidBase64Data) - } -} \ No newline at end of file diff --git a/trifid-api/src/kv.rs b/trifid-api/src/kv.rs deleted file mode 100644 index 15ccb05..0000000 --- a/trifid-api/src/kv.rs +++ /dev/null @@ -1,28 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::error::Error; -use sqlx::PgPool; - -pub async fn kv_get<'a>(key: &'a str, db: &PgPool) -> Result, Box> { - let res = sqlx::query!("SELECT datavalue FROM cacheddata WHERE datakey = $1", key).fetch_optional(db).await?; - Ok(res.map(|i| i.datavalue)) -} - -pub async fn kv_set(key: &str, value: &str, db: &PgPool) -> Result<(), Box> { - sqlx::query!("INSERT INTO cacheddata (datakey, datavalue) VALUES ($2, $1) ON CONFLICT (datakey) DO UPDATE SET datavalue = $1", value, key).execute(db).await?; - Ok(()) -} \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 847c3b1..0ef408b 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -1,213 +1,35 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -extern crate core; - use std::error::Error; -use std::fs; -use std::path::Path; -use dotenvy::dotenv; -use log::{error, info}; -use rocket::{catchers, Request, Response, routes}; -use rocket::fairing::{Fairing, Info, Kind}; -use rocket::http::Header; -use sha2::Sha256; -use sqlx::migrate::Migrator; -use sqlx::postgres::PgPoolOptions; -use crate::config::TFConfig; -use crate::kv::{kv_get, kv_set}; -use sha2::Digest; +use std::time::Duration; +use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfig}}; +use log::{error, info, Level}; +use sea_orm::{ConnectOptions, Database, DatabaseConnection}; +use serde::{Serialize, Deserialize}; +use crate::config::CONFIG; + -pub mod format; -pub mod util; -pub mod db; pub mod config; -pub mod tokens; -pub mod routes; -pub mod auth; -pub mod crypto; -pub mod org; -pub mod kv; -pub mod role; -static MIGRATOR: Migrator = sqlx::migrate!(); - -pub struct CORS; - -#[rocket::async_trait] -impl Fairing for CORS { - fn info(&self) -> Info { - Info { - name: "Add CORS headers to responses", - kind: Kind::Response, - } - } - - async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) { - response.set_header(Header::new("Access-Control-Allow-Origin", "*")); - response.set_header(Header::new("Access-Control-Allow-Methods", "POST, GET, PATCH, OPTIONS")); - response.set_header(Header::new("Access-Control-Allow-Headers", "*")); - response.set_header(Header::new("Access-Control-Allow-Credentials", "true")); - } -} - -#[rocket::main] +#[actix_web::main] async fn main() -> Result<(), Box> { - let _ = rocket::build(); + simple_logger::init_with_level(Level::Debug).unwrap(); - info!("[tfapi] loading config"); + info!("Connecting to database at {}...", CONFIG.database.url); - let _ = dotenv(); + let mut opt = ConnectOptions::new(CONFIG.database.url.clone()); + opt.max_connections(CONFIG.database.max_connections) + .min_connections(CONFIG.database.min_connections) + .connect_timeout(Duration::from_secs(CONFIG.database.connect_timeout)) + .acquire_timeout(Duration::from_secs(CONFIG.database.acquire_timeout)) + .idle_timeout(Duration::from_secs(CONFIG.database.idle_timeout)) + .max_lifetime(Duration::from_secs(CONFIG.database.max_lifetime)) + .sqlx_logging(CONFIG.database.sqlx_logging) + .sqlx_logging_level(log::LevelFilter::Info); - if std::env::var("CONFIG_FILE").is_err() && !Path::new("config.toml").exists() { - error!("[tfapi] fatal: the environment variable CONFIG_FILE is not set"); - error!("[tfapi] help: try creating a .env file that sets it"); - error!("[tfapi] help: or, create a file config.toml with your config, as it is loaded automatically"); - std::process::exit(1); - } + let db = Database::connect(opt).await?; - let config_file = if Path::new("config.toml").exists() { - "config.toml".to_string() - } else { - std::env::var("CONFIG_FILE").unwrap() - }; - let config_data = match fs::read_to_string(&config_file) { - Ok(d) => d, - Err(e) => { - error!("[tfapi] fatal: unable to read config from {}", config_file); - error!("[tfapi] fatal: {}", e); - std::process::exit(1); - } - }; - let config: TFConfig = match toml::from_str(&config_data) { - Ok(c) => c, - Err(e) => { - error!("[tfapi] fatal: unable to parse config from {}", config_file); - error!("[tfapi] fatal: {}", e); - std::process::exit(1); - } - }; - info!("[tfapi] connecting to database pool"); - - let pool = match PgPoolOptions::new().max_connections(5).connect(&config.db_url).await { - Ok(p) => p, - Err(e) => { - error!("[tfapi] fatal: unable to connect to database pool"); - error!("[tfapi] fatal: {}", e); - std::process::exit(1); - } - }; - - info!("[tfapi] running database migrations"); - - MIGRATOR.run(&pool).await?; - - info!("[tfapi] verifying encryption key"); - - let kv_hash = kv_get("pmk_hash", &pool).await.expect("Unable to get pmk hash from kv store"); - - let mut hasher = Sha256::new(); - hasher.update(config.data_key.as_bytes()); - let config_hash = hex::encode(hasher.finalize()); - if let Some(k_hash) = kv_hash { - if config_hash != k_hash { - error!("[tfapi] fatal: instance master key does not match key used to encrypt data"); - error!("[tfapi] fatal: datastore was encrypted with keyid {k_hash}"); - error!("[tfapi] fatal: the key in your config has the keyid {config_hash}"); - error!("[tfapi] fatal: you probably changed data_key. please return it to it's original value"); - std::process::exit(1); - } else { - info!("[tfapi] data keyid is {config_hash}"); - } - } else { - info!("[tfapi] detected first run"); - info!("[tfapi] welcome to trifid!"); - info!("[tfapi] data keyid is {config_hash}"); - - if let Err(e) = kv_set("pmk_hash", config_hash.as_str(), &pool).await { - error!("[tfapi] fatal: unable to set pmk_hash in kv store"); - error!("[tfapi] fatal: the database returned the following error:"); - error!("[tfapi] fatal: {e}"); - std::process::exit(1); - } else { - info!("[tfapi] configured instance information in kv store"); - } - } - - info!("[tfapi] building rocket config"); - - let figment = rocket::Config::figment().merge(("port", config.listen_port)); - - let _ = rocket::custom(figment) - .mount("/", routes![ - crate::routes::v1::auth::magic_link::magiclink_request, - crate::routes::v1::auth::magic_link::options, - crate::routes::v1::signup::signup_request, - crate::routes::v1::signup::options, - crate::routes::v1::auth::verify_magic_link::verify_magic_link, - crate::routes::v1::auth::verify_magic_link::options, - crate::routes::v1::totp_authenticators::totp_authenticators_request, - crate::routes::v1::totp_authenticators::options, - crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request, - crate::routes::v1::verify_totp_authenticator::options, - crate::routes::v1::auth::totp::totp_request, - crate::routes::v1::auth::totp::options, - crate::routes::v1::auth::check_session::check_session, - crate::routes::v1::auth::check_session::check_session_auth, - crate::routes::v1::auth::check_session::options, - crate::routes::v1::auth::check_session::options_auth, - crate::routes::v2::whoami::whoami_request, - crate::routes::v2::whoami::options, - - crate::routes::v1::organization::options, - crate::routes::v1::organization::orgidoptions, - crate::routes::v1::organization::orginfo_req, - crate::routes::v1::organization::orglist_req, - crate::routes::v1::organization::create_org, - crate::routes::v1::user::get_user, - crate::routes::v1::user::options, - crate::routes::v1::organization::createorgoptions, - crate::routes::v1::ca::get_cas_for_org, - crate::routes::v1::ca::options, - crate::routes::v1::roles::get_roles, - crate::routes::v1::roles::options, - crate::routes::v1::roles::options_roleadd, - crate::routes::v1::roles::role_add - ]) - .register("/", catchers![ - crate::routes::handler_400, - crate::routes::handler_401, - crate::routes::handler_403, - crate::routes::handler_404, - crate::routes::handler_422, - - crate::routes::handler_500, - crate::routes::handler_501, - crate::routes::handler_502, - crate::routes::handler_503, - crate::routes::handler_504, - crate::routes::handler_505, - ]) - .attach(CORS) - .manage(pool) - .manage(config) - .launch().await?; Ok(()) -} \ No newline at end of file +} diff --git a/trifid-api/src/org.rs b/trifid-api/src/org.rs deleted file mode 100644 index 501b14a..0000000 --- a/trifid-api/src/org.rs +++ /dev/null @@ -1,77 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::error::Error; - -use rocket::form::validate::Contains; -use sqlx::PgPool; -use trifid_pki::ca::NebulaCAPool; - -pub async fn get_org_by_owner_id(user: i32, db: &PgPool) -> Result, Box> { - - Ok(sqlx::query!("SELECT id FROM organizations WHERE owner = $1", user).fetch_optional(db).await?.map(|r| r.id)) -} - -pub async fn get_orgs_by_assoc_id(user: i32, db: &PgPool) -> Result, Box> { - let res: Vec<_> = sqlx::query!("SELECT org_id FROM organization_authorized_users WHERE user_id = $1", user).fetch_all(db).await?; - - let mut ret = vec![]; - - for i in res { - ret.push(i.org_id); - } - - Ok(ret) -} - -pub async fn get_users_by_assoc_org(org: i32, db: &PgPool) -> Result, Box> { - let res: Vec<_> = sqlx::query!("SELECT user_id FROM organization_authorized_users WHERE org_id = $1", org).fetch_all(db).await?; - - let mut ret = vec![]; - - for i in res { - ret.push(i.user_id); - } - - Ok(ret) -} - -pub async fn get_associated_orgs(user: i32, db: &PgPool) -> Result, Box> { - let mut assoc_orgs = vec![]; - - if let Some(owned_org) = get_org_by_owner_id(user, db).await? { - assoc_orgs.push(owned_org); - } - - assoc_orgs.append(&mut get_orgs_by_assoc_id(user, db).await?); - - Ok(assoc_orgs) -} - -pub async fn user_has_org_assoc(user: i32, org: i32, db: &PgPool) -> Result> { - let associated_orgs = get_associated_orgs(user, db).await?; - - Ok(associated_orgs.contains(org)) -} - -pub async fn get_org_ca_pool(org: i32, db: &PgPool) -> Result> { - // get CAPool PEM from db - let pem = sqlx::query!("SELECT ca_crt FROM organizations WHERE id = $1", org).fetch_one(db).await?.ca_crt; - - let ca_pool = NebulaCAPool::new_from_pem(&hex::decode(pem)?)?; - - Ok(ca_pool) -} \ No newline at end of file diff --git a/trifid-api/src/role.rs b/trifid-api/src/role.rs deleted file mode 100644 index 5660964..0000000 --- a/trifid-api/src/role.rs +++ /dev/null @@ -1,98 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::error::Error; -use sqlx::PgPool; -use serde::{Serialize, Deserialize}; - -#[derive(Serialize, Deserialize)] -#[repr(i32)] -pub enum Protocol { - Any = 0, - TCP = 1, - UDP = 2, - ICMP = 3 -} - -#[derive(Serialize, Deserialize)] -pub enum AllowFrom { - Anyone, - SpecificRole(i32) -} - -#[derive(Serialize, Deserialize)] -pub struct FirewallRule { - pub id: i32, - pub protocol: Protocol, - pub port_start: u16, - pub port_end: u16, - pub allow_from: AllowFrom, - pub description: String -} - -#[derive(Serialize, Deserialize)] -pub struct Role { - pub id: i32, - pub org_id: i32, - pub name: String, - pub description: String, - pub firewall_rules: Vec -} - -pub async fn get_role(role_id: i32, db: &PgPool) -> Result, Box> { - let query_result: Option<_> = sqlx::query!("SELECT * FROM roles WHERE id = $1", role_id).fetch_optional(db).await?; - - if let Some(res) = query_result { - // get all firewall rules - let rules_res = sqlx::query!("SELECT * FROM roles_firewall_rules WHERE role = $1", Some(role_id)).fetch_all(db).await?; - - let mut rules = vec![]; - - for rule in rules_res { - rules.push(FirewallRule { - id: rule.id, - protocol: match rule.protocol { - 0 => Protocol::Any, - 1 => Protocol::TCP, - 2 => Protocol::UDP, - 3 => Protocol::ICMP, - _ => return Err(format!("invalid protocol on a firewall rule {}", rule.id).into()) - }, - port_start: rule.port_range_start as u16, - port_end: rule.port_range_end as u16, - allow_from: match rule.allow_from { - -1 => AllowFrom::Anyone, - _ => AllowFrom::SpecificRole(rule.allow_from) - }, - description: rule.description, - }) - } - - Ok(Some(Role { - id: role_id, - org_id: res.org, - name: res.name, - description: res.description, - firewall_rules: rules, - })) - } else { - Ok(None) - } -} - -pub async fn get_role_ids_for_ca(org_id: i32, db: &PgPool) -> Result, Box> { - Ok(sqlx::query!("SELECT id FROM roles WHERE org = $1", org_id).fetch_all(db).await?.iter().map(|r| r.id).collect()) -} \ No newline at end of file diff --git a/trifid-api/src/routes/mod.rs b/trifid-api/src/routes/mod.rs deleted file mode 100644 index 074561f..0000000 --- a/trifid-api/src/routes/mod.rs +++ /dev/null @@ -1,82 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pub mod v1; -pub mod v2; - -use rocket::catch; -use serde::{Serialize}; -use rocket::http::Status; - -pub const ERR_MSG_MALFORMED_REQUEST: &str = "unable to parse the request body - is it valid JSON, using correct types?"; -pub const ERR_MSG_MALFORMED_REQUEST_CODE: &str = "ERR_MALFORMED_REQUEST"; - -/* -TODO: - /v1/auth/magic-link [done] - /v1/auth/totp [done] - /v1/auth/verify-magic-link [done] - /v1/hosts/host-{id}/enrollment-code - /v1/hosts/host-{id}/enrollment-code-check - /v1/hosts/host-{id} - /v1/roles/role-{id} - /v1/feature-flags - /v1/hosts - /v1/networks - /v1/roles - /v1/signup [done] - /v1/totp-authenticators [done] - /v1/verify-totp-authenticator [done] - /v1/dnclient - /v2/enroll - /v2/whoami [in-progress] - */ - -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct APIError { - errors: Vec -} -#[derive(Serialize)] -#[serde(crate = "rocket::serde")] -pub struct APIErrorSingular { - code: String, - message: String -} - -macro_rules! error_handler { - ($code: expr, $err: expr, $msg: expr) => { - ::paste::paste! { - #[catch($code)] - pub fn []() -> (Status, String) { - (Status::from_code($code).unwrap(), format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", $err, $msg)) - } - } - }; -} -error_handler!(400, "ERR_MALFORMED_REQUEST", "unable to parse the request body, is it properly formatted?"); -error_handler!(401, "ERR_AUTHENTICATION_REQUIRED", "this endpoint requires authentication but it was not provided"); -error_handler!(403, "ERR_UNAUTHORIZED", "authorization was provided but it is expired or invalid"); -error_handler!(404, "ERR_NOT_FOUND", "resource not found"); -error_handler!(405, "ERR_METHOD_NOT_ALLOWED", "method not allowed for this endpoint"); -error_handler!(422, "ERR_MALFORMED_REQUEST", "unable to parse the request body, is it properly formatted?"); - -error_handler!(500, "ERR_QL_QUERY_FAILED", "graphql query timed out"); -error_handler!(501, "ERR_NOT_IMPLEMENTED", "query not supported by this version of graphql"); -error_handler!(502, "ERR_PROXY_ERR", "servers under load, please try again later"); -error_handler!(503, "ERR_SERVER_OVERLOADED", "servers under load, please try again later"); -error_handler!(504, "ERR_PROXY_TIMEOUT", "servers under load, please try again later"); -error_handler!(505, "ERR_CLIENT_UNSUPPORTED", "your version of dnclient is out of date, please update"); diff --git a/trifid-api/src/routes/v1/auth/check_session.rs b/trifid-api/src/routes/v1/auth/check_session.rs deleted file mode 100644 index 07b256e..0000000 --- a/trifid-api/src/routes/v1/auth/check_session.rs +++ /dev/null @@ -1,47 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::{post, options}; -use crate::auth::{PartialUserInfo, TOTPAuthenticatedUserInfo}; - -/* -These endpoints do not return any actual data, and are used purely to check auth tokens -by the client code. -Since PartialUserInfo implements FromRequest, and will error out the req even before -it gets to our handler if the auth is invalid, these reqs just have to have -it as a param. They therefore don't need to s - */ - -#[options("/v1/auth/check_session")] -pub async fn options() -> &'static str { - "" -} - -#[post("/v1/auth/check_session")] -pub async fn check_session(_user: PartialUserInfo) -> &'static str { - "ok" -} - -#[options("/v1/auth/check_auth")] -pub async fn options_auth() -> &'static str { - "" -} - - -#[post("/v1/auth/check_auth")] -pub async fn check_session_auth(_user: TOTPAuthenticatedUserInfo) -> &'static str { - "ok" -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs deleted file mode 100644 index 5706818..0000000 --- a/trifid-api/src/routes/v1/auth/magic_link.rs +++ /dev/null @@ -1,78 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::{post, State}; -use rocket::serde::json::Json; -use serde::{Serialize, Deserialize}; -use rocket::http::{ContentType, Status}; -use sqlx::PgPool; -use crate::config::TFConfig; -use crate::tokens::send_magic_link; -use rocket::options; - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct MagicLinkRequest { - pub email: String, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct MagicLinkResponseMetadata {} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct MagicLinkResponse { - pub data: Option, - pub metadata: MagicLinkResponseMetadata, -} - - -#[options("/v1/auth/magic-link")] -pub async fn options() -> &'static str { - "" -} - -#[post("/v1/auth/magic-link", data = "")] -pub async fn magiclink_request(req: Json, pool: &State, config: &State) -> Result<(ContentType, Json), (Status, String)> { - // figure out if the user already exists - let mut id = -1; - match sqlx::query!("SELECT id FROM users WHERE email = $1", req.email.clone()).fetch_optional(pool.inner()).await { - Ok(res) => if let Some(r) = res { id = r.id as i64 }, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e))) - } - } - - if id == -1 { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_UNAUTHORIZED", "authorization was provided but it is expired or invalid"))) - } - - // send magic link to email - match send_magic_link(id, req.email.clone(), pool.inner(), config.inner()).await { - Ok(_) => (), - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e))) - } - }; - - // this endpoint doesn't actually ever return an error? it will send you the magic link no matter what - // this appears to do the exact same thing as /v1/auth/magic-link, but it doesn't check if you have an account (magic-link does) - Ok((ContentType::JSON, Json(MagicLinkResponse { - data: None, - metadata: MagicLinkResponseMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs deleted file mode 100644 index f00abb5..0000000 --- a/trifid-api/src/routes/v1/auth/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pub mod verify_magic_link; -pub mod magic_link; -pub mod totp; -pub mod check_session; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs deleted file mode 100644 index dc841b6..0000000 --- a/trifid-api/src/routes/v1/auth/totp.rs +++ /dev/null @@ -1,89 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use crate::auth::PartialUserInfo; -use serde::{Serialize, Deserialize}; -use rocket::{post, State}; -use sqlx::PgPool; -use rocket::options; -use crate::tokens::{generate_auth_token, get_totpmachine, user_has_totp}; - -pub const TOTP_GENERIC_UNAUTHORIZED_ERROR: &str = "{\"errors\":[{\"code\":\"ERR_INVALID_TOTP_CODE\",\"message\":\"invalid TOTP code (maybe it expired?)\",\"path\":\"code\"}]}"; -pub const TOTP_NO_TOTP_ERROR: &str = "{\"errors\":[{\"code\":\"ERR_NO_TOTP\",\"message\":\"logged-in user does not have totp enabled\",\"path\":\"code\"}]}"; - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpRequest { - pub code: String -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpResponseData { - #[serde(rename = "authToken")] - auth_token: String -} -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpResponseMetadata { -} -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpResponse { - data: TotpResponseData, - metadata: TotpResponseMetadata -} - -#[options("/v1/auth/totp")] -pub async fn options() -> &'static str { - "" -} - - -#[post("/v1/auth/totp", data = "")] -pub async fn totp_request(req: Json, user: PartialUserInfo, db: &State) -> Result<(ContentType, Json), (Status, String)> { - if !match user_has_totp(user.user_id, db.inner()).await { - Ok(b) => b, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } { - return Err((Status::UnprocessableEntity, TOTP_NO_TOTP_ERROR.to_string())) - } - - if user.has_totp_auth { - return Err((Status::BadRequest, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_TOTP_ALREADY_AUTHED", "user already has valid totp authentication"))) - } - - let totpmachine = match get_totpmachine(user.user_id, db.inner()).await { - Ok(t) => t, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } - }; - - if !totpmachine.check_current(&req.0.code).unwrap_or(false) { - return Err((Status::Unauthorized, TOTP_GENERIC_UNAUTHORIZED_ERROR.to_string())) - } - - Ok((ContentType::JSON, Json(TotpResponse { - data: TotpResponseData { auth_token: match generate_auth_token(user.user_id as i64, user.session_id, db.inner()).await { - Ok(t) => t, - Err(e) => { return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) } - } }, - metadata: TotpResponseMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs deleted file mode 100644 index 79a4eb8..0000000 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ /dev/null @@ -1,91 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::time::{SystemTime, UNIX_EPOCH}; -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use serde::{Serialize, Deserialize}; -use rocket::{post, State}; -use sqlx::PgPool; -use crate::config::TFConfig; -use crate::tokens::generate_session_token; -use rocket::options; - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct VerifyMagicLinkRequest { - #[serde(rename = "magicLinkToken")] - pub magic_link_token: String, -} - - -#[derive(Serialize, Deserialize)] -pub struct VerifyMagicLinkResponseMetadata {} - -#[derive(Serialize, Deserialize)] -pub struct VerifyMagicLinkResponseData { - #[serde(rename = "sessionToken")] - pub session_token: String, -} - -#[derive(Serialize, Deserialize)] -pub struct VerifyMagicLinkResponse { - pub data: VerifyMagicLinkResponseData, - pub metadata: VerifyMagicLinkResponseMetadata, -} - -#[options("/v1/auth/verify-magic-link")] -pub async fn options() -> &'static str { - "" -} - - -#[post("/v1/auth/verify-magic-link", data = "")] -pub async fn verify_magic_link(req: Json, db: &State, config: &State) -> Result<(ContentType, Json), (Status, String)> { - // get the current time to check if the token is expired - let (user_id, expired_at) = match sqlx::query!("SELECT user_id, expires_on FROM magic_links WHERE id = $1", req.0.magic_link_token).fetch_one(db.inner()).await { - Ok(row) => (row.user_id, row.expires_on), - Err(e) => { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNAUTHORIZED", "this token is invalid", e))) - } - }; - let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32; - println!("expired on {}, currently {}", expired_at, current_time); - if expired_at < current_time { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_UNAUTHORIZED", "valid authorization was provided but it is expired"))) - } - - // generate session token - let token = match generate_session_token(user_id as i64, db.inner(), config.inner()).await { - Ok(t) => t, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } - }; - - // delete the token - match sqlx::query!("DELETE FROM magic_links WHERE id = $1", req.0.magic_link_token).execute(db.inner()).await { - Ok(_) => (), - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } - } - - Ok((ContentType::JSON, Json(VerifyMagicLinkResponse { - data: VerifyMagicLinkResponseData { session_token: token }, - metadata: VerifyMagicLinkResponseMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/ca.rs b/trifid-api/src/routes/v1/ca.rs deleted file mode 100644 index fe7b442..0000000 --- a/trifid-api/src/routes/v1/ca.rs +++ /dev/null @@ -1,85 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - - -use rocket::{options, get, State}; -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use sqlx::PgPool; - -use serde::{Serialize, Deserialize}; -use crate::auth::TOTPAuthenticatedUserInfo; -use crate::org::{get_associated_orgs, get_org_ca_pool}; - -#[options("/v1/org/<_id>/ca")] -pub fn options(_id: i32) -> &'static str { - "" -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct CaList { - pub trusted_cas: Vec, - pub blocklisted_certs: Vec -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct CA { - pub fingerprint: String, - pub cert: String -} - -#[get("/v1/org//ca")] -pub async fn get_cas_for_org(id: i32, user: TOTPAuthenticatedUserInfo, db: &State) -> Result<(ContentType, Json), (Status, String)> { - let associated_orgs = match get_associated_orgs(user.user_id, db.inner()).await { - Ok(r) => r, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DB_QUERY_FAILED", "an error occurred while running the database query", e))) - }; - - if !associated_orgs.contains(&id) { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_NOT_YOUR_ORG", "you are not authorized to view details of this org"))) - } - - let ca_pool = match get_org_ca_pool(id, db.inner()).await { - Ok(pool) => pool, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"ERR_QL_QUERY_FAILED\",\"message\":\"unable to load certificates from database - {}\"}}]}}", e))); - } - }; - - let mut trusted_cas = vec![]; - - for (fingerprint, cert) in ca_pool.cas { - trusted_cas.push(CA { - fingerprint, - cert: match cert.serialize_to_pem() { - Ok(pem) => match String::from_utf8(pem) { - Ok(str) => str, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"ERR_QL_QUERY_FAILED\",\"message\":\"unable to encode one of the serialized certificates - {}\"}}]}}", e))) - }, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"ERR_QL_QUERY_FAILED\",\"message\":\"unable to serialize one of the certificates - {}\"}}]}}", e))); - } - } - }) - } - - Ok((ContentType::JSON, Json(CaList { - trusted_cas, - blocklisted_certs: ca_pool.cert_blocklist, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs deleted file mode 100644 index 7c0037d..0000000 --- a/trifid-api/src/routes/v1/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pub mod auth; -pub mod signup; - -pub mod totp_authenticators; -pub mod verify_totp_authenticator; -pub mod organization; -pub mod user; -pub mod ca; -pub mod roles; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/organization.rs b/trifid-api/src/routes/v1/organization.rs deleted file mode 100644 index 2e2519b..0000000 --- a/trifid-api/src/routes/v1/organization.rs +++ /dev/null @@ -1,190 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::time::{Duration, SystemTime}; -use ipnet::Ipv4Net; -use rocket::{get, post, options, State, error}; -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use serde::{Serialize, Deserialize}; -use sqlx::PgPool; -use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private}; -use trifid_pki::ed25519_dalek::{SigningKey}; -use trifid_pki::rand_core::OsRng; -use crate::auth::TOTPAuthenticatedUserInfo; -use crate::config::TFConfig; -use crate::crypto::{encrypt_with_nonce, generate_random_iv, get_cipher_from_config}; -use crate::org::{get_associated_orgs, get_org_by_owner_id, get_users_by_assoc_org, user_has_org_assoc}; - -#[options("/v1/orgs")] -pub fn options() -> &'static str { - "" -} -#[options("/v1/org/<_id>")] -pub fn orgidoptions(_id: i32) -> &'static str { - "" -} -#[options("/v1/org")] -pub fn createorgoptions() -> &'static str {""} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct OrglistStruct { - pub org_ids: Vec -} - - -#[get("/v1/orgs")] -pub async fn orglist_req(user: TOTPAuthenticatedUserInfo, db: &State) -> Result<(ContentType, Json), (Status, String)> { - // this endpoint lists the associated organizations this user has access to - let associated_orgs = match get_associated_orgs(user.user_id, db.inner()).await { - Ok(r) => r, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DB_QUERY_FAILED", "an error occurred while running the database query", e))) - }; - - Ok((ContentType::JSON, Json(OrglistStruct { - org_ids: associated_orgs - }))) -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct OrginfoStruct { - pub org_id: i32, - pub owner_id: i32, - pub ca_crt: String, - pub authorized_users: Vec -} - -#[get("/v1/org/")] -pub async fn orginfo_req(orgid: i32, user: TOTPAuthenticatedUserInfo, db: &State) -> Result<(ContentType, Json), (Status, String)> { - if !user_has_org_assoc(user.user_id, orgid, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org"))); - } - - let org = sqlx::query!("SELECT id, owner, ca_crt FROM organizations WHERE id = $1", orgid).fetch_one(db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?; - let authorized_users = get_users_by_assoc_org(orgid, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?; - - Ok((ContentType::JSON, Json( - OrginfoStruct { - org_id: orgid, - owner_id: org.owner, - ca_crt: org.ca_crt, - authorized_users, - } - ))) -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct CreateCARequest { - pub ip_ranges: Vec, - pub subnet_ranges: Vec, - pub groups: Vec -} - -#[post("/v1/org", data = "")] -pub async fn create_org(req: Json, user: TOTPAuthenticatedUserInfo, db: &State, config: &State) -> Result<(ContentType, Json), (Status, String)> { - if get_org_by_owner_id(user.user_id, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?.is_some() { - return Err((Status::Conflict, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_USER_OWNS_ORG", "a user can only own one organization at a time"))) - } - - // Generate the CA keypair - let private_key = SigningKey::generate(&mut OsRng); - let public_key = private_key.verifying_key(); - - // Create the CA certificate - let mut ca_cert = NebulaCertificate { - details: NebulaCertificateDetails { - name: format!("{}'s Organization - Root Signing CA", user.email), - ips: req.ip_ranges.clone(), - subnets: req.subnet_ranges.clone(), - groups: req.groups.clone(), - not_before: SystemTime::now(), - not_after: SystemTime::now() + Duration::from_secs(config.ca_certs_valid_for), - public_key: public_key.to_bytes(), - is_ca: true, - issuer: "".to_string(), // This is a self-signed certificate! There is no issuer present - }, - signature: vec![], - }; - // Self-sign the CA certificate - match ca_cert.sign(&private_key) { - Ok(_) => (), - Err(e) => { - error!("[tfapi] security: certificate signature error: {}", e); - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_CERT_SIGN_ERROR", "there was an error generating the CA certificate, please try again later"))) - } - } - - // PEM-encode the CA key - let ca_key_pem = serialize_ed25519_private(&private_key.to_keypair_bytes()); - // PEM-encode the CA cert - let ca_cert_pem = match ca_cert.serialize_to_pem() { - Ok(pem) => pem, - Err(e) => { - error!("[tfapi] security: certificate encoding error: {}", e); - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_CERT_ENCODE_ERROR", "there was an error encoding the CA certificate, please try again later"))) - } - }; - - // generate an AES iv to use for key encryption - let iv = generate_random_iv(); - let iv_hex = hex::encode(iv); - - let owner_id = user.user_id; - let cipher = match get_cipher_from_config(config) { - Ok(c) => c, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_CRYPTOGRAPHY_CREATE_CIPHER", "Unable to build cipher construct, please try again later", e))); - } - }; - let ca_key = match encrypt_with_nonce(&ca_key_pem, iv, &cipher) { - Ok(key) => hex::encode(key), - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_CRYPTOGRAPHY_ENCRYPT_KEY", "Unable to build cipher construct, please try again later", e))); - } - }; - let ca_crt = hex::encode(ca_cert_pem); - - let result = sqlx::query!("INSERT INTO organizations (owner, ca_key, ca_crt, iv) VALUES ($1, $2, $3, $4) RETURNING id", owner_id, ca_key, ca_crt, iv_hex).fetch_one(db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?; - - // last step: create a default role to allow pings from all hosts - let role_id = match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, 'Default', 'Allow pings from other hosts. Default role for new hosts.') RETURNING id", result.id).fetch_one(db.inner()).await { - Ok(r) => r.id, - Err(e) => { - error!("[tfapi] dberror: {}", e); - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_INTERNAL_SERVER_ERROR", "Unable to create default role"))); - } - }; - - match sqlx::query!("INSERT INTO roles_firewall_rules (role, protocol, port_range_start, port_range_end, allow_from, description) VALUES ($1, 3, 1, 1, -1, 'Allow pings from anyone on the network')", role_id).execute(db.inner()).await { - Ok(_) => {}, - Err(e) => { - error!("[tfapi] dberror: {} inserting on roleid {}", e, role_id); - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_INTERNAL_SERVER_ERROR", "Unable to create default role firewall rules"))); - } - } - - Ok((ContentType::JSON, Json( - OrginfoStruct { - org_id: result.id, - owner_id, - ca_crt, - authorized_users: vec![owner_id], - } - ))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs deleted file mode 100644 index 538f65a..0000000 --- a/trifid-api/src/routes/v1/roles.rs +++ /dev/null @@ -1,95 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use log::error; -use rocket::{options, get, State, post}; -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; - -use serde::{Serialize, Deserialize}; -use sqlx::PgPool; -use crate::auth::TOTPAuthenticatedUserInfo; -use crate::org::user_has_org_assoc; -use crate::role::{get_role, get_role_ids_for_ca, Role}; - -#[options("/v1/org/<_org>/roles")] -pub fn options(_org: i32) -> &'static str { "" } -#[options("/v1/org/<_org>/role")] -pub fn options_roleadd(_org: i32) -> &'static str { "" } - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct RolesResponse { - pub data: Vec -} - -#[get("/v1/org//roles")] -pub async fn get_roles(org: i32, user: TOTPAuthenticatedUserInfo, db: &State) -> Result<(ContentType, Json), (Status, String)> { - if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org"))); - } - - let roles = match get_role_ids_for_ca(org, db.inner()).await { - Ok(r) => r, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e))) - } - }; - - let mut resp = RolesResponse { - data: vec![] - }; - - for role in roles { - let role_info = match get_role(role, db.inner()).await { - Ok(r) => r, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e))) - } - }; - if let Some(info) = role_info { - resp.data.push(info); - } else { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", "missing role as returned by server - possibly deleted inbetween?"))) - } - } - - Ok((ContentType::JSON, Json(resp))) -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct RoleaddReq { - pub name: String, - pub description: String -} - -#[post("/v1/org//role", data = "")] -pub async fn role_add(req: Json, org: i32, user: TOTPAuthenticatedUserInfo, db: &State) -> Result<(ContentType, String), (Status, String)> { - if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org"))); - } - - match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, $2, $3)", org, req.name, req.description).execute(db.inner()).await { - Ok(_) => (), - Err(e) => { - error!("[tfapi] dberror: {}", e); - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "database returned error while trying to create role", "unable to insert role"))) - } - } - - Ok((ContentType::JSON, "{}".to_string())) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs deleted file mode 100644 index be6a59f..0000000 --- a/trifid-api/src/routes/v1/signup.rs +++ /dev/null @@ -1,89 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::{post, State}; -use rocket::serde::json::Json; -use serde::{Serialize, Deserialize}; -use std::time::{SystemTime, UNIX_EPOCH}; -use rocket::http::{ContentType, Status}; -use sqlx::PgPool; -use crate::config::TFConfig; -use crate::tokens::send_magic_link; -use rocket::options; - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct SignupRequest { - pub email: String, -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct SignupResponseMetadata {} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct SignupResponse { - pub data: Option, - pub metadata: SignupResponseMetadata, -} -/* -created_on TIMESTAMP NOT NULL, - - banned INTEGER NOT NULL, - ban_reason VARCHAR(1024) NOT NULL - */ -#[options("/v1/signup")] -pub async fn options() -> &'static str { - "" -} - -#[post("/v1/signup", data = "")] -pub async fn signup_request(req: Json, pool: &State, config: &State) -> Result<(ContentType, Json), (Status, String)> { - // figure out if the user already exists - let mut id = -1; - match sqlx::query!("SELECT id FROM users WHERE email = $1", req.email.clone()).fetch_optional(pool.inner()).await { - Ok(res) => if let Some(r) = res { id = r.id as i64 }, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e))) - } - } - - if id == -1 { - let id_res = match sqlx::query!("INSERT INTO users (email, created_on, banned, ban_reason, totp_secret, totp_otpurl, totp_verified) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT DO NOTHING RETURNING id;", req.email, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64, 0, "", "", "", 0).fetch_one(pool.inner()).await { - Ok(row) => row.id, - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e))) - } - }; - id = id_res as i64; - } - - // send magic link to email - match send_magic_link(id, req.email.clone(), pool.inner(), config.inner()).await { - Ok(_) => (), - Err(e) => { - return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e))) - } - }; - - // this endpoint doesn't actually ever return an error? it will send you the magic link no matter what - // this appears to do the exact same thing as /v1/auth/magic-link, but it doesn't check if you have an account (magic-link does) - Ok((ContentType::JSON, Json(SignupResponse { - data: None, - metadata: SignupResponseMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs deleted file mode 100644 index 55c4c92..0000000 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ /dev/null @@ -1,76 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use rocket::{State, post}; -use sqlx::PgPool; -use serde::{Serialize, Deserialize}; -use crate::auth::PartialUserInfo; -use crate::config::TFConfig; -use crate::tokens::{create_totp_token, user_has_totp}; -use rocket::options; - -#[derive(Deserialize)] -pub struct TotpAuthenticatorsRequest {} -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpAuthenticatorsResponseMetadata {} -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpAuthenticatorsResponseData { - #[serde(rename = "totpToken")] - pub totp_token: String, - pub secret: String, - pub url: String, -} -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct TotpAuthenticatorsResponse { - pub data: TotpAuthenticatorsResponseData, - pub metadata: TotpAuthenticatorsResponseMetadata, -} - -#[options("/v1/totp-authenticators")] -pub async fn options() -> &'static str { - "" -} - - -#[post("/v1/totp-authenticators", data = "<_req>")] -pub async fn totp_authenticators_request(_req: Json, user: PartialUserInfo, db: &State, config: &State) -> Result<(ContentType, Json), (Status, String)> { - if match user_has_totp(user.user_id, db.inner()).await { - Ok(b) => b, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } { - return Err((Status::BadRequest, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_TOTP_ALREADY_EXISTS", "this user already has a totp authenticator on their account"))) - } - - // generate a totp token - let (totptoken, totpmachine) = match create_totp_token(user.email, db.inner(), config.inner()).await { - Ok(t) => t, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured issuing a totp token, try again later", e))) - }; - - Ok((ContentType::JSON, Json(TotpAuthenticatorsResponse { - data: TotpAuthenticatorsResponseData { - totp_token: totptoken, - secret: totpmachine.get_secret_base32(), - url: totpmachine.get_url(), - }, - metadata: TotpAuthenticatorsResponseMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/user.rs b/trifid-api/src/routes/v1/user.rs deleted file mode 100644 index 99fe24e..0000000 --- a/trifid-api/src/routes/v1/user.rs +++ /dev/null @@ -1,47 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use serde::{Serialize, Deserialize}; -use rocket::{get, options}; -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use rocket::State; -use sqlx::PgPool; - -#[options("/v1/user/<_id>")] -pub fn options(_id: i32) -> &'static str { - "" -} - -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct UserInfoResponse { - email: String -} - -#[get("/v1/user/")] -pub async fn get_user(id: i32, db: &State) -> Result<(ContentType, Json), (Status, String)> { - let user = match sqlx::query!("SELECT email FROM users WHERE id = $1", id.clone() as i32).fetch_one(db.inner()).await { - Ok(u) => u, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e))) - }; - - Ok((ContentType::JSON, Json( - UserInfoResponse { - email: user.email, - } - ))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/verify_totp_authenticator.rs b/trifid-api/src/routes/v1/verify_totp_authenticator.rs deleted file mode 100644 index 223cffd..0000000 --- a/trifid-api/src/routes/v1/verify_totp_authenticator.rs +++ /dev/null @@ -1,75 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use rocket::http::{ContentType, Status}; -use crate::auth::PartialUserInfo; -use rocket::post; -use rocket::serde::json::Json; -use rocket::State; -use serde::{Serialize, Deserialize}; -use sqlx::PgPool; -use crate::tokens::{generate_auth_token, use_totp_token, verify_totp_token}; -use rocket::options; - -#[derive(Serialize, Deserialize)] -pub struct VerifyTotpAuthenticatorRequest { - #[serde(rename = "totpToken")] - pub totp_token: String, - pub code: String, -} - -#[derive(Serialize, Deserialize)] -pub struct VerifyTotpAuthenticatorResponseMetadata {} - -#[derive(Serialize, Deserialize)] -pub struct VerifyTotpAuthenticatorResponseData { - #[serde(rename = "authToken")] - pub auth_token: String, -} - -#[derive(Serialize, Deserialize)] -pub struct VerifyTotpAuthenticatorResponse { - pub data: VerifyTotpAuthenticatorResponseData, - pub metadata: VerifyTotpAuthenticatorResponseMetadata, -} - -#[options("/v1/verify-totp-authenticator")] -pub async fn options() -> &'static str { - "" -} - - -#[post("/v1/verify-totp-authenticator", data = "")] -pub async fn verify_totp_authenticator_request(req: Json, db: &State, user: PartialUserInfo) -> Result<(ContentType, Json), (Status, String)> { - let totpmachine = match verify_totp_token(req.0.totp_token.clone(), user.email.clone(), db.inner()).await { - Ok(t) => t, - Err(e) => return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNAUTHORIZED", "this token is invalid", e))) - }; - if !totpmachine.check_current(&req.0.code).unwrap() { - return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\",\"path\":\"totpToken\"}}]}}", "ERR_INVALID_TOTP_CODE", "Invalid TOTP code"))) - } - match use_totp_token(req.0.totp_token, user.email, db.inner()).await { - Ok(_) => (), - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } - Ok((ContentType::JSON, Json(VerifyTotpAuthenticatorResponse { - data: VerifyTotpAuthenticatorResponseData { auth_token: match generate_auth_token(user.user_id as i64, user.session_id, db.inner()).await { - Ok(at) => at, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) - } }, - metadata: VerifyTotpAuthenticatorResponseMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/routes/v2/mod.rs b/trifid-api/src/routes/v2/mod.rs deleted file mode 100644 index c603733..0000000 --- a/trifid-api/src/routes/v2/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pub mod whoami; \ No newline at end of file diff --git a/trifid-api/src/routes/v2/whoami.rs b/trifid-api/src/routes/v2/whoami.rs deleted file mode 100644 index fcf87d7..0000000 --- a/trifid-api/src/routes/v2/whoami.rs +++ /dev/null @@ -1,85 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use chrono::{NaiveDateTime, Utc}; -use serde::{Serialize, Deserialize}; -use rocket::{options, get, State}; -use rocket::http::{ContentType, Status}; -use rocket::serde::json::Json; -use sqlx::PgPool; -use crate::auth::PartialUserInfo; -use crate::org::get_org_by_owner_id; -use crate::tokens::user_has_totp; - -#[derive(Serialize, Deserialize)] -pub struct WhoamiMetadata {} - -#[derive(Serialize, Deserialize)] -pub struct WhoamiActor { - pub id: String, - #[serde(rename = "organizationID")] - pub organization_id: String, - pub email: String, - #[serde(rename = "createdAt")] - pub created_at: String, - #[serde(rename = "hasTOTPAuthenticator")] - pub has_totpauthenticator: bool, -} - -#[derive(Serialize, Deserialize)] -pub struct WhoamiData { - #[serde(rename = "actorType")] - pub actor_type: String, - pub actor: WhoamiActor, -} - -#[derive(Serialize, Deserialize)] -pub struct WhoamiResponse { - pub data: WhoamiData, - pub metadata: WhoamiMetadata, -} - -#[options("/v2/whoami")] -pub fn options() -> &'static str { - "" -} - -#[get("/v2/whoami")] -pub async fn whoami_request(user: PartialUserInfo, db: &State) -> Result<(ContentType, Json), (Status, String)> { - let org = match get_org_by_owner_id(user.user_id, db.inner()).await { - Ok(b) => match b { - Some(r) => r.to_string(), - None => String::new() - }, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DBERROR", "an error occured trying to verify your user", e))) - }; - Ok((ContentType::JSON, Json(WhoamiResponse { - data: WhoamiData { - actor_type: "user".to_string(), - actor: WhoamiActor { - id: user.user_id.to_string(), - organization_id: org, - email: user.email, - created_at: NaiveDateTime::from_timestamp_opt(user.created_at, 0).unwrap().and_local_timezone(Utc).unwrap().to_rfc3339(), - has_totpauthenticator: match user_has_totp(user.user_id, db.inner()).await { - Ok(b) => b, - Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DBERROR", "an error occured trying to verify your user", e))) - }, - } - }, - metadata: WhoamiMetadata {}, - }))) -} \ No newline at end of file diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs deleted file mode 100644 index 8590cdf..0000000 --- a/trifid-api/src/tokens.rs +++ /dev/null @@ -1,109 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::error::Error; -use log::info; -use sqlx::PgPool; -use uuid::Uuid; -use crate::config::TFConfig; -use std::time::SystemTime; -use std::time::UNIX_EPOCH; -use totp_rs::{Secret, TOTP}; -use crate::util::{TOTP_ALGORITHM, TOTP_DIGITS, TOTP_ISSUER, TOTP_SKEW, TOTP_STEP}; - -// https://admin.defined.net/auth/magic-link?email=coredoescode%40gmail.com&token=ml-ckBsgw_5IdK5VYgseBYcoV_v_cQjtdq1re_RhDu_MKg -pub async fn send_magic_link(id: i64, email: String, db: &PgPool, config: &TFConfig) -> Result<(), Box> { - let otp = format!("ml-{}", Uuid::new_v4()); - let otp_url = config.web_root.join(&format!("/auth/magic-link?email={}&token={}", urlencoding::encode(&email.clone()), otp.clone())).unwrap(); - sqlx::query!("INSERT INTO magic_links (id, user_id, expires_on) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;", otp, id as i32, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32 + config.magic_links_valid_for as i32).execute(db).await?; - // TODO: send email - info!("sent magic link {} to {}, valid for {} seconds", otp_url, email.clone(), config.magic_links_valid_for); - Ok(()) -} - -pub async fn generate_session_token(user_id: i64, db: &PgPool, config: &TFConfig) -> Result> { - let token = format!("st-{}", Uuid::new_v4()); - sqlx::query!("INSERT INTO session_tokens (id, user_id, expires_on) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;", token, user_id as i32, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32 + config.session_tokens_valid_for as i32).execute(db).await?; - Ok(token) -} -pub async fn validate_session_token(token: String, db: &PgPool) -> Result> { - Ok(sqlx::query!("SELECT user_id FROM session_tokens WHERE id = $1 AND expires_on > $2", token, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32).fetch_one(db).await?.user_id as i64) -} - -pub async fn generate_auth_token(user_id: i64, session_id: String, db: &PgPool) -> Result> { - let token = format!("at-{}", Uuid::new_v4()); - sqlx::query!("INSERT INTO auth_tokens (id, session_token, user_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;", token, session_id, user_id as i32).execute(db).await?; - Ok(token) -} -pub async fn validate_auth_token(token: String, session_id: String, db: &PgPool) -> Result<(), Box> { - validate_session_token(session_id.clone(), db).await?; - sqlx::query!("SELECT * FROM auth_tokens WHERE id = $1 AND session_token = $2", token, session_id).fetch_one(db).await?; - Ok(()) -} - - -/* -CREATE TABLE totp_create_tokens ( - id VARCHAR(39) NOT NULL PRIMARY KEY, - expires_on INTEGER NOT NULL, - totp_otpurl VARCHAR(3000) NOT NULL, - totp_secret VARCHAR(128) NOT NULL -); - */ - -pub async fn create_totp_token(email: String, db: &PgPool, config: &TFConfig) -> Result<(String, TOTP), Box> { - // create the TOTP parameters - - let secret = Secret::generate_secret(); - let totpmachine = TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), email).unwrap(); - let otpurl = totpmachine.get_url(); - let otpsecret = totpmachine.get_secret_base32(); - - let otpid = format!("totp-{}", Uuid::new_v4()); - - sqlx::query!("INSERT INTO totp_create_tokens (id, expires_on, totp_otpurl, totp_secret) VALUES ($1, $2, $3, $4);", otpid.clone(), (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + config.totp_verification_valid_for) as i32, otpurl, otpsecret).execute(db).await?; - - Ok((otpid, totpmachine)) -} - -pub async fn verify_totp_token(otpid: String, email: String, db: &PgPool) -> Result> { - let totprow = sqlx::query!("SELECT * FROM totp_create_tokens WHERE id = $1 AND expires_on > $2", otpid, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32).fetch_one(db).await?; - let secret = Secret::Encoded(totprow.totp_secret); - let totpmachine = TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), email).unwrap(); - - if totpmachine.get_url() != totprow.totp_otpurl { - return Err("OTPURLs do not match (email does not match?)".into()) - } - - Ok(totpmachine) -} - -pub async fn use_totp_token(otpid: String, email: String, db: &PgPool) -> Result> { - let totpmachine = verify_totp_token(otpid.clone(), email.clone(), db).await?; - sqlx::query!("DELETE FROM totp_create_tokens WHERE id = $1", otpid).execute(db).await?; - sqlx::query!("UPDATE users SET totp_otpurl = $1, totp_secret = $2, totp_verified = 1 WHERE email = $3", totpmachine.get_url(), totpmachine.get_secret_base32(), email).execute(db).await?; - Ok(totpmachine) -} - -pub async fn get_totpmachine(user: i32, db: &PgPool) -> Result> { - let user = sqlx::query!("SELECT totp_secret, totp_otpurl, email FROM users WHERE id = $1", user).fetch_one(db).await?; - let secret = Secret::Encoded(user.totp_secret); - Ok(TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), user.email).unwrap()) -} - -pub async fn user_has_totp(user: i32, db: &PgPool) -> Result> { - Ok(sqlx::query!("SELECT totp_verified FROM users WHERE id = $1", user).fetch_one(db).await?.totp_verified == 1) -} \ No newline at end of file diff --git a/trifid-api/src/util.rs b/trifid-api/src/util.rs deleted file mode 100644 index ca7857e..0000000 --- a/trifid-api/src/util.rs +++ /dev/null @@ -1,31 +0,0 @@ -// trifid-api, an open source reimplementation of the Defined Networking nebula management server. -// Copyright (C) 2023 c0repwn3r -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use base64::Engine; -use totp_rs::Algorithm; - -pub const TOTP_ALGORITHM: Algorithm = Algorithm::SHA1; -pub const TOTP_DIGITS: usize = 6; -pub const TOTP_SKEW: u8 = 1; -pub const TOTP_STEP: u64 = 30; -pub const TOTP_ISSUER: &str = "trifidapi"; - -pub fn base64decode(val: &str) -> Result, base64::DecodeError> { - base64::engine::general_purpose::STANDARD.decode(val) -} -pub fn base64encode(val: Vec) -> String { - base64::engine::general_purpose::STANDARD.encode(val) -} \ No newline at end of file From fca4fb4cd5d0ba341fb928cb55394c36abed1deb Mon Sep 17 00:00:00 2001 From: core Date: Sun, 2 Apr 2023 13:06:16 -0400 Subject: [PATCH 02/39] redo trifid-api with actix+sea-orm --- Cargo.lock | 440 +++++++++++++++++- Cargo.toml | 2 + trifid-api/Cargo.toml | 2 + trifid-api/src/config.rs | 13 +- trifid-api/src/error.rs | 128 +++++ trifid-api/src/main.rs | 24 +- trifid-api/src/routes/mod.rs | 0 trifid-api/trifid_api_entities/Cargo.toml | 9 + trifid-api/trifid_api_entities/src/lib.rs | 5 + trifid-api/trifid_api_entities/src/prelude.rs | 3 + trifid-api/trifid_api_entities/src/user.rs | 17 + trifid-api/trifid_api_migration/Cargo.toml | 25 + trifid-api/trifid_api_migration/README.md | 41 ++ trifid-api/trifid_api_migration/src/lib.rs | 14 + .../m20230402_162601_create_table_users.rs | 33 ++ trifid-api/trifid_api_migration/src/main.rs | 6 + 16 files changed, 751 insertions(+), 11 deletions(-) create mode 100644 trifid-api/src/error.rs create mode 100644 trifid-api/src/routes/mod.rs create mode 100644 trifid-api/trifid_api_entities/Cargo.toml create mode 100644 trifid-api/trifid_api_entities/src/lib.rs create mode 100644 trifid-api/trifid_api_entities/src/prelude.rs create mode 100644 trifid-api/trifid_api_entities/src/user.rs create mode 100644 trifid-api/trifid_api_migration/Cargo.toml create mode 100644 trifid-api/trifid_api_migration/README.md create mode 100644 trifid-api/trifid_api_migration/src/lib.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs create mode 100644 trifid-api/trifid_api_migration/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index d1063f4..e04b255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -263,6 +263,113 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "async-attributes" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" +dependencies = [ + "quote", + "syn 1.0.107", +] + +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", + "tokio", +] + +[[package]] +name = "async-io" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" +dependencies = [ + "async-lock", + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-lite", + "log", + "parking", + "polling", + "rustix 0.37.3", + "slab", + "socket2", + "waker-fn", +] + +[[package]] +name = "async-lock" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-std" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62565bb4402e926b29953c785397c6dc0391b7b446e45008b0049eb43cec6f5d" +dependencies = [ + "async-attributes", + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + [[package]] name = "async-stream" version = "0.3.4" @@ -285,6 +392,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "async-task" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" + [[package]] name = "async-trait" version = "0.1.68" @@ -305,6 +418,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "atomic-waker" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "debc29dde2e69f9e47506b525f639ed42300fc014a3e007832592448fa8e4599" + [[package]] name = "atty" version = "0.2.14" @@ -389,6 +508,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c67b173a56acffd6d2326fb7ab938ba0b00a71480e14902b2591c87bc5741e8" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", +] + [[package]] name = "borsh" version = "0.10.3" @@ -535,6 +668,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "clap" +version = "3.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +dependencies = [ + "bitflags", + "clap_derive 3.2.18", + "clap_lex 0.2.4", + "indexmap", + "once_cell", + "textwrap", +] + [[package]] name = "clap" version = "4.1.10" @@ -542,14 +689,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce38afc168d8665cfc75c7b1dd9672e50716a137f433f070991619744a67342a" dependencies = [ "bitflags", - "clap_derive", - "clap_lex", + "clap_derive 4.1.9", + "clap_lex 0.3.3", "is-terminal", "once_cell", "strsim", "termcolor", ] +[[package]] +name = "clap_derive" +version = "3.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +dependencies = [ + "heck 0.4.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "clap_derive" version = "4.1.9" @@ -563,6 +723,15 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.3.3" @@ -593,6 +762,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "concurrent-queue" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c278839b831783b70278b14df4d45e1beb1aad306c07bb796637de9a0e323e8e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "const-oid" version = "0.9.1" @@ -679,6 +857,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.107", +] + [[package]] name = "ctrlc" version = "3.2.5" @@ -920,6 +1108,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys 0.45.0", +] + [[package]] name = "errno-dragonfly" version = "0.1.2" @@ -1050,6 +1249,21 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-sink" version = "0.3.26" @@ -1111,6 +1325,18 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "gloo-timers" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b995a66bb87bebce9a0f4a95aed01daca4872c050bfcb21653361c03bc35e5c" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.3.15" @@ -1366,7 +1592,7 @@ checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", - "rustix", + "rustix 0.36.10", "windows-sys 0.45.0", ] @@ -1403,6 +1629,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "language-tags" version = "0.3.2" @@ -1442,6 +1677,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + [[package]] name = "local-channel" version = "0.1.3" @@ -1477,6 +1718,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", + "value-bag", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", ] [[package]] @@ -1716,6 +1967,12 @@ dependencies = [ "libm", ] +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.11.2" @@ -1819,6 +2076,22 @@ version = "3.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d7ddaed09e0eb771a79ab0fd64609ba0afb0a8366421957936ad14cbd13630" +[[package]] +name = "polling" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e1f879b2998099c2d69ab9605d145d5b661195627eccc680002c4918a7fb6fa" +dependencies = [ + "autocfg", + "bitflags", + "cfg-if", + "concurrent-queue", + "libc", + "log", + "pin-project-lite", + "windows-sys 0.45.0", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1975,6 +2248,15 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.28" @@ -2101,10 +2383,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fe885c3a125aa45213b68cc1472a49880cb5923dc23f522ad2791b882228778" dependencies = [ "bitflags", - "errno", + "errno 0.2.8", "io-lifetimes", "libc", - "linux-raw-sys", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", +] + +[[package]] +name = "rustix" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b24138615de35e32031d041a09032ef3487a616d901ca4db224e7d557efae2" +dependencies = [ + "bitflags", + "errno 0.3.0", + "io-lifetimes", + "libc", + "linux-raw-sys 0.3.1", "windows-sys 0.45.0", ] @@ -2200,6 +2496,22 @@ dependencies = [ "uuid", ] +[[package]] +name = "sea-orm-cli" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ead9f7dac975f10447f17d08edbb2046daa087b5e0b50bbf8211f303459078c" +dependencies = [ + "chrono", + "clap 3.2.23", + "dotenvy", + "regex", + "sea-schema", + "tracing", + "tracing-subscriber", + "url", +] + [[package]] name = "sea-orm-macros" version = "0.11.2" @@ -2213,6 +2525,23 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "sea-orm-migration" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7a6123c1035b0530deb713820688f0234431ab6c1893b14dce493ade76af" +dependencies = [ + "async-trait", + "clap 3.2.23", + "dotenvy", + "futures", + "sea-orm", + "sea-orm-cli", + "sea-schema", + "tracing", + "tracing-subscriber", +] + [[package]] name = "sea-query" version = "0.28.3" @@ -2257,6 +2586,29 @@ dependencies = [ "thiserror", ] +[[package]] +name = "sea-schema" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeb2940bb5a10bc6cd05b450ce6cd3993e27fddd7eface2becb97fc5af3a040e" +dependencies = [ + "futures", + "sea-query", + "sea-schema-derive", +] + +[[package]] +name = "sea-schema-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56821b7076f5096b8f726e2791ad255a99c82498e08ec477a65a96c461ff1927" +dependencies = [ + "heck 0.3.3", + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "sea-strum" version = "0.23.0" @@ -2410,6 +2762,15 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -2676,7 +3037,7 @@ dependencies = [ "cfg-if", "fastrand", "redox_syscall", - "rustix", + "rustix 0.36.10", "windows-sys 0.42.0", ] @@ -2689,6 +3050,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "textwrap" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" + [[package]] name = "tfclient" version = "0.1.5" @@ -2696,7 +3063,7 @@ dependencies = [ "base64 0.21.0", "base64-serde", "chrono", - "clap", + "clap 4.1.10", "ctrlc", "dirs 5.0.0", "dnapi-rs", @@ -2738,6 +3105,16 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "time" version = "0.1.45" @@ -2940,6 +3317,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "once_cell", + "regex", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + [[package]] name = "trifid-api" version = "0.1.0" @@ -2953,6 +3345,8 @@ dependencies = [ "serde", "simple_logger", "toml 0.7.3", + "trifid_api_entities", + "trifid_api_migration", ] [[package]] @@ -2971,6 +3365,22 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "trifid_api_entities" +version = "0.1.0" +dependencies = [ + "sea-orm", +] + +[[package]] +name = "trifid_api_migration" +version = "0.1.0" +dependencies = [ + "async-std", + "async-trait", + "sea-orm-migration", +] + [[package]] name = "try-lock" version = "0.2.4" @@ -3060,6 +3470,16 @@ dependencies = [ "serde", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2209b78d1249f7e6f3293657c9779fe31ced465df091bbd433a1cf88e916ec55" +dependencies = [ + "ctor", + "version_check", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -3072,6 +3492,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "want" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 31bce56..9b3de10 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,8 @@ [workspace] members = [ "trifid-api", + "trifid-api/trifid_api_migration", + "trifid-api/trifid_api_entities", "tfclient", "trifid-pki", "dnapi-rs" diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index b9cfbd1..c351acc 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -17,6 +17,8 @@ log = "0.4" # Logging simple_logger = "4" # Logging sea-orm = { version = "^0", features = [ "sqlx-postgres", "runtime-actix-rustls", "macros" ]} # Database +trifid_api_migration = { version = "0.1.0", path = "trifid_api_migration" } # Database +trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } # Database rand = "0.8" # Misc. hex = "0.4" # Misc. \ No newline at end of file diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 6abdc18..fdb00c5 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -1,4 +1,5 @@ use std::fs; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use log::error; use once_cell::sync::Lazy; use serde::{Serialize, Deserialize}; @@ -23,7 +24,8 @@ pub static CONFIG: Lazy = Lazy::new(|| { #[derive(Serialize, Debug, Deserialize)] pub struct TrifidConfig { - pub database: TrifidConfigDatabase + pub database: TrifidConfigDatabase, + pub server: TrifidConfigServer } #[derive(Serialize, Deserialize, Debug)] @@ -45,7 +47,14 @@ pub struct TrifidConfigDatabase { pub sqlx_logging: bool } +#[derive(Serialize, Deserialize, Debug)] +pub struct TrifidConfigServer { + #[serde(default = "socketaddr_8080")] + pub bind: SocketAddr +} + fn max_connections_default() -> u32 { 100 } fn min_connections_default() -> u32 { 5 } fn time_defaults() -> u64 { 8 } -fn sqlx_logging_default() -> bool { true } \ No newline at end of file +fn sqlx_logging_default() -> bool { true } +fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } \ No newline at end of file diff --git a/trifid-api/src/error.rs b/trifid-api/src/error.rs new file mode 100644 index 0000000..4659084 --- /dev/null +++ b/trifid-api/src/error.rs @@ -0,0 +1,128 @@ +use actix_web::error::{JsonPayloadError, PayloadError}; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct APIErrorsResponse { + pub errors: Vec +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct APIError { + pub code: String, + pub message: String, + #[serde(skip_serializing_if = "is_none")] + #[serde(default)] + pub path: Option +} + +fn is_none(o: &Option) -> bool { o.is_none() } + +impl From<&JsonPayloadError> for APIError { + fn from(value: &JsonPayloadError) -> Self { + match value { + JsonPayloadError::OverflowKnownLength { length, limit } => { + APIError { + code: "ERR_PAYLOAD_OVERFLOW_KNOWN_LENGTH".to_string(), + message: format!("Payload size is bigger than allowed & content length header set. (length: {}, limit: {})", length, limit), + path: None + } + }, + JsonPayloadError::Overflow { limit } => { + APIError { + code: "ERR_PAYLOAD_OVERFLOW".to_string(), + message: format!("Payload size is bigger than allowed but no content-length header is set. (limit: {})", limit), + path: None + } + }, + JsonPayloadError::ContentType => { + APIError { + code: "ERR_NOT_JSON".to_string(), + message: "Content-Type header not set to expected application/json".to_string(), + path: None, + } + }, + JsonPayloadError::Deserialize(e) => { + APIError { + code: "ERR_JSON_DESERIALIZE".to_string(), + message: format!("Error deserializing JSON: {}", e), + path: None, + } + }, + JsonPayloadError::Serialize(e) => { + APIError { + code: "ERR_JSON_SERIALIZE".to_string(), + message: format!("Error serializing JSON: {}", e), + path: None, + } + }, + JsonPayloadError::Payload(e) => { + e.into() + }, + _ => { + APIError { + code: "ERR_UNKNOWN_ERROR".to_string(), + message: "An unknown error has occured".to_string(), + path: None, + } + } + } + } +} + +impl From<&PayloadError> for APIError { + fn from(value: &PayloadError) -> Self { + match value { + PayloadError::Incomplete(e) => { + APIError { + code: "ERR_UNEXPECTED_EOF".to_string(), + message: match e { + None => "Payload reached EOF but was incomplete".to_string(), + Some(e) => format!("Payload reached EOF but was incomplete: {}", e) + }, + path: None, + } + } + PayloadError::EncodingCorrupted => { + APIError { + code: "ERR_CORRUPTED_PAYLOAD".to_string(), + message: "Payload content encoding corrupted".to_string(), + path: None, + } + } + PayloadError::Overflow => { + APIError { + code: "ERR_PAYLOAD_OVERFLOW".to_string(), + message: "Payload reached size limit".to_string(), + path: None, + } + } + PayloadError::UnknownLength => { + APIError { + code: "ERR_PAYLOAD_UNKNOWN_LENGTH".to_string(), + message: "Unable to determine payload length".to_string(), + path: None, + } + } + PayloadError::Http2Payload(e) => { + APIError { + code: "ERR_HTTP2_ERROR".to_string(), + message: format!("HTTP/2 error: {}", e), + path: None, + } + } + PayloadError::Io(e) => { + APIError { + code: "ERR_IO_ERROR".to_string(), + message: format!("I/O error: {}", e), + path: None, + } + } + _ => { + APIError { + code: "ERR_UNKNOWN_ERROR".to_string(), + message: "An unknown error has occured".to_string(), + path: None, + } + } + } + } +} \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 0ef408b..388d5f2 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -4,10 +4,13 @@ use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfi use log::{error, info, Level}; use sea_orm::{ConnectOptions, Database, DatabaseConnection}; use serde::{Serialize, Deserialize}; +use trifid_api_migration::{Migrator, MigratorTrait}; use crate::config::CONFIG; - +use crate::error::{APIError, APIErrorsResponse}; pub mod config; +pub mod routes; +pub mod error; #[actix_web::main] async fn main() -> Result<(), Box> { @@ -27,9 +30,26 @@ async fn main() -> Result<(), Box> { let db = Database::connect(opt).await?; + info!("Performing database migration..."); + Migrator::up(&db, None).await?; + let data = Data::new(db); - + HttpServer::new(move || { + App::new() + .app_data(data.clone()) + .app_data(JsonConfig::default().error_handler(|err, _req| { + let api_error: APIError = (&err).into(); + actix_web::error::InternalError::from_response( + err, + HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + api_error + ], + }) + ).into() + })) + }).bind(CONFIG.server.bind)?.run().await?; Ok(()) } diff --git a/trifid-api/src/routes/mod.rs b/trifid-api/src/routes/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/trifid-api/trifid_api_entities/Cargo.toml b/trifid-api/trifid_api_entities/Cargo.toml new file mode 100644 index 0000000..73b20f4 --- /dev/null +++ b/trifid-api/trifid_api_entities/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "trifid_api_entities" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +sea-orm = { version = "^0" } \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/lib.rs b/trifid-api/trifid_api_entities/src/lib.rs new file mode 100644 index 0000000..35e436f --- /dev/null +++ b/trifid-api/trifid_api_entities/src/lib.rs @@ -0,0 +1,5 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub mod prelude; + +pub mod user; diff --git a/trifid-api/trifid_api_entities/src/prelude.rs b/trifid-api/trifid_api_entities/src/prelude.rs new file mode 100644 index 0000000..5a361f5 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/prelude.rs @@ -0,0 +1,3 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/user.rs b/trifid-api/trifid_api_entities/src/user.rs new file mode 100644 index 0000000..ab703b3 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/user.rs @@ -0,0 +1,17 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub email: String, + pub password_hash: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/Cargo.toml b/trifid-api/trifid_api_migration/Cargo.toml new file mode 100644 index 0000000..f090efb --- /dev/null +++ b/trifid-api/trifid_api_migration/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "trifid_api_migration" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +name = "trifid_api_migration" +path = "src/lib.rs" + +[dependencies] +async-std = { version = "1", features = ["attributes", "tokio1"] } +async-trait = "0.1.68" + +[dependencies.sea-orm-migration] +version = "0.11.0" +features = [ + # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run trifid_api_migration via CLI. + # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. + # e.g. + # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature + # "sqlx-postgres", # `DATABASE_DRIVER` feature + "sqlx-postgres", + "runtime-actix-rustls" +] diff --git a/trifid-api/trifid_api_migration/README.md b/trifid-api/trifid_api_migration/README.md new file mode 100644 index 0000000..b3ea53e --- /dev/null +++ b/trifid-api/trifid_api_migration/README.md @@ -0,0 +1,41 @@ +# Running Migrator CLI + +- Generate a new migration file + ```sh + cargo run -- migrate generate MIGRATION_NAME + ``` +- Apply all pending migrations + ```sh + cargo run + ``` + ```sh + cargo run -- up + ``` +- Apply first 10 pending migrations + ```sh + cargo run -- up -n 10 + ``` +- Rollback last applied migrations + ```sh + cargo run -- down + ``` +- Rollback last 10 applied migrations + ```sh + cargo run -- down -n 10 + ``` +- Drop all tables from the database, then reapply all migrations + ```sh + cargo run -- fresh + ``` +- Rollback all applied migrations, then reapply all migrations + ```sh + cargo run -- refresh + ``` +- Rollback all applied migrations + ```sh + cargo run -- reset + ``` +- Check the status of all migrations + ```sh + cargo run -- status + ``` diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs new file mode 100644 index 0000000..372443b --- /dev/null +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -0,0 +1,14 @@ +pub use sea_orm_migration::prelude::*; + +pub struct Migrator; + +pub mod m20230402_162601_create_table_users; + +#[async_trait::async_trait] +impl MigratorTrait for Migrator { + fn migrations() -> Vec> { + vec![ + Box::new(m20230402_162601_create_table_users::Migration) + ] + } +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs new file mode 100644 index 0000000..ce6457d --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs @@ -0,0 +1,33 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(User::Table) + .if_not_exists() + .col(ColumnDef::new(User::Id).string().not_null().primary_key()) + .col(ColumnDef::new(User::Email).string().not_null()) + .col(ColumnDef::new(User::PasswordHash).string().not_null()) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(User::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +enum User { + Table, + Id, + Email, + PasswordHash, +} diff --git a/trifid-api/trifid_api_migration/src/main.rs b/trifid-api/trifid_api_migration/src/main.rs new file mode 100644 index 0000000..84ac5bc --- /dev/null +++ b/trifid-api/trifid_api_migration/src/main.rs @@ -0,0 +1,6 @@ +use sea_orm_migration::prelude::*; + +#[async_std::main] +async fn main() { + cli::run_cli(trifid_api_migration::Migrator).await; +} From 3a702ed3a532c946f45115e3337516a767db3542 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 2 Apr 2023 15:25:52 -0400 Subject: [PATCH 03/39] /v1/auth/magic-link --- Cargo.lock | 37 ++++++++ trifid-api/Cargo.toml | 3 +- trifid-api/src/config.rs | 12 ++- trifid-api/src/main.rs | 13 ++- trifid-api/src/routes/auth/magic_link.rs | 90 +++++++++++++++++++ trifid-api/src/routes/auth/mod.rs | 1 + trifid-api/src/routes/mod.rs | 1 + trifid-api/src/timers.rs | 9 ++ trifid-api/src/tokens.rs | 33 +++++++ trifid-api/trifid_api_entities/src/lib.rs | 1 + .../trifid_api_entities/src/magic_link.rs | 32 +++++++ trifid-api/trifid_api_entities/src/prelude.rs | 1 + trifid-api/trifid_api_entities/src/user.rs | 12 ++- trifid-api/trifid_api_migration/src/lib.rs | 4 +- .../m20230402_162601_create_table_users.rs | 4 +- ...0230402_183515_create_table_magic_links.rs | 41 +++++++++ 16 files changed, 286 insertions(+), 8 deletions(-) create mode 100644 trifid-api/src/routes/auth/magic_link.rs create mode 100644 trifid-api/src/routes/auth/mod.rs create mode 100644 trifid-api/src/timers.rs create mode 100644 trifid-api/src/tokens.rs create mode 100644 trifid-api/trifid_api_entities/src/magic_link.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs diff --git a/Cargo.lock b/Cargo.lock index e04b255..0ca5eee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,6 +74,17 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "actix-request-identifier" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f620de7c806297b88cf39da5ef4293a59f7dc219a9838433e83238e53a9c7057" +dependencies = [ + "actix-web", + "futures", + "uuid", +] + [[package]] name = "actix-router" version = "0.5.1" @@ -1210,6 +1221,7 @@ checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" dependencies = [ "futures-channel", "futures-core", + "futures-executor", "futures-io", "futures-sink", "futures-task", @@ -1232,6 +1244,17 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-intrusive" version = "0.4.2" @@ -1264,6 +1287,17 @@ dependencies = [ "waker-fn", ] +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.107", +] + [[package]] name = "futures-sink" version = "0.3.26" @@ -1285,6 +1319,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -3336,6 +3371,7 @@ dependencies = [ name = "trifid-api" version = "0.1.0" dependencies = [ + "actix-request-identifier", "actix-web", "hex", "log", @@ -3467,6 +3503,7 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" dependencies = [ + "getrandom 0.2.8", "serde", ] diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index c351acc..ab1f364 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -6,7 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -actix-web = "4" # Web framework +actix-web = "4" # Web framework +actix-request-identifier = "4" # Web framework serde = { version = "1", features = ["derive"] } # Serialization and deserialization diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index fdb00c5..536a5d8 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -25,7 +25,8 @@ pub static CONFIG: Lazy = Lazy::new(|| { #[derive(Serialize, Debug, Deserialize)] pub struct TrifidConfig { pub database: TrifidConfigDatabase, - pub server: TrifidConfigServer + pub server: TrifidConfigServer, + pub tokens: TrifidConfigTokens } #[derive(Serialize, Deserialize, Debug)] @@ -53,8 +54,15 @@ pub struct TrifidConfigServer { pub bind: SocketAddr } +#[derive(Serialize, Deserialize, Debug)] +pub struct TrifidConfigTokens { + #[serde(default = "magic_link_expiry_time")] + pub magic_link_expiry_time_seconds: u64 +} + fn max_connections_default() -> u32 { 100 } fn min_connections_default() -> u32 { 5 } fn time_defaults() -> u64 { 8 } fn sqlx_logging_default() -> bool { true } -fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } \ No newline at end of file +fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } +fn magic_link_expiry_time() -> u64 { 3600 } \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 388d5f2..59694e7 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -1,5 +1,6 @@ use std::error::Error; use std::time::Duration; +use actix_request_identifier::RequestIdentifier; use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfig}}; use log::{error, info, Level}; use sea_orm::{ConnectOptions, Database, DatabaseConnection}; @@ -7,10 +8,17 @@ use serde::{Serialize, Deserialize}; use trifid_api_migration::{Migrator, MigratorTrait}; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; +use crate::tokens::random_id_no_id; pub mod config; pub mod routes; pub mod error; +pub mod tokens; +pub mod timers; + +pub struct AppState { + pub conn: DatabaseConnection +} #[actix_web::main] async fn main() -> Result<(), Box> { @@ -33,7 +41,9 @@ async fn main() -> Result<(), Box> { info!("Performing database migration..."); Migrator::up(&db, None).await?; - let data = Data::new(db); + let data = Data::new(AppState { + conn: db + }); HttpServer::new(move || { App::new() @@ -49,6 +59,7 @@ async fn main() -> Result<(), Box> { }) ).into() })) + .wrap(RequestIdentifier::with_generator(random_id_no_id)) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/auth/magic_link.rs b/trifid-api/src/routes/auth/magic_link.rs new file mode 100644 index 0000000..6c9bd4c --- /dev/null +++ b/trifid-api/src/routes/auth/magic_link.rs @@ -0,0 +1,90 @@ +use actix_web::{HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Serialize, Deserialize}; +use trifid_api_entities::user::Entity as UserEntity; +use trifid_api_entities::user; +use crate::AppState; +use crate::config::CONFIG; +use crate::error::{APIError, APIErrorsResponse}; +use crate::timers::expires_in_seconds; +use crate::tokens::random_token; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MagicLinkRequest { + pub email: String +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MagicLinkResponse { + pub data: MagicLinkResponseData, + pub metadata: MagicLinkResponseMetadata +} +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MagicLinkResponseData {} +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct MagicLinkResponseMetadata {} + +#[post("/v1/auth/magic-link")] +pub async fn magic_link_request(data: Data, req: Json) -> HttpResponse { + let user: Option = match UserEntity::find().filter(user::Column::Email.eq(&req.email)).one(&data.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + }; + + let user = match user { + Some(u) => u, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_USER_DOES_NOT_EXIST".to_string(), + message: "That user does not exist.".to_string(), + path: None, + } + ], + }) + } + }; + + let model = trifid_api_entities::magic_link::Model { + id: random_token("ml"), + user: user.id, + expires_on: expires_in_seconds(CONFIG.tokens.magic_link_expiry_time_seconds) as i64, + }; + + let active_model = model.into_active_model(); + + match active_model.insert(&data.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + HttpResponse::Ok().json(MagicLinkResponse { + data: MagicLinkResponseData {}, + metadata: MagicLinkResponseMetadata {} + }) +} \ No newline at end of file diff --git a/trifid-api/src/routes/auth/mod.rs b/trifid-api/src/routes/auth/mod.rs new file mode 100644 index 0000000..600b7fa --- /dev/null +++ b/trifid-api/src/routes/auth/mod.rs @@ -0,0 +1 @@ +pub mod magic_link; \ No newline at end of file diff --git a/trifid-api/src/routes/mod.rs b/trifid-api/src/routes/mod.rs index e69de29..5696e21 100644 --- a/trifid-api/src/routes/mod.rs +++ b/trifid-api/src/routes/mod.rs @@ -0,0 +1 @@ +pub mod auth; \ No newline at end of file diff --git a/trifid-api/src/timers.rs b/trifid-api/src/timers.rs new file mode 100644 index 0000000..522cd4c --- /dev/null +++ b/trifid-api/src/timers.rs @@ -0,0 +1,9 @@ +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +pub fn expires_in_seconds(seconds: u64) -> u64 { + (SystemTime::now() + Duration::from_secs(seconds)).duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() +} + +pub fn expired(time: u64) -> bool { + UNIX_EPOCH + Duration::from_secs(time) > SystemTime::now() +} \ No newline at end of file diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs new file mode 100644 index 0000000..a1c2475 --- /dev/null +++ b/trifid-api/src/tokens.rs @@ -0,0 +1,33 @@ +use actix_web::http::header::HeaderValue; +use rand::Rng; + +pub const ID_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; +pub const ID_LEN: u32 = 26; + +pub const TOKEN_CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; +pub const TOKEN_LEN: u32 = 43; + +// 26 +// format: [ID]-[26 chars] +pub fn random_id(identifier: &str) -> String { + format!("{}-{}", identifier, random_with_charset(ID_LEN, ID_CHARSET)) +} + +// 26 +// format: [ID]-[26 chars] +pub fn random_id_no_id() -> HeaderValue { + HeaderValue::from_str(&random_with_charset(ID_LEN, ID_CHARSET)).unwrap() +} + +// 43 +// format: [TYPE]-[43 chars] +pub fn random_token(identifier: &str) -> String { + format!("{}-{}", identifier, random_with_charset(TOKEN_LEN, TOKEN_CHARSET)) +} + +fn random_with_charset(len: u32, charset: &[u8]) -> String { + (0..len).map(|_| { + let idx = rand::thread_rng().gen_range(0..charset.len()); + charset[idx] as char + }).collect() +} \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/lib.rs b/trifid-api/trifid_api_entities/src/lib.rs index 35e436f..d1b689d 100644 --- a/trifid-api/trifid_api_entities/src/lib.rs +++ b/trifid-api/trifid_api_entities/src/lib.rs @@ -3,3 +3,4 @@ pub mod prelude; pub mod user; +pub mod magic_link; \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/magic_link.rs b/trifid-api/trifid_api_entities/src/magic_link.rs new file mode 100644 index 0000000..1707d7e --- /dev/null +++ b/trifid-api/trifid_api_entities/src/magic_link.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "magic_link")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/prelude.rs b/trifid-api/trifid_api_entities/src/prelude.rs index 5a361f5..527e6e0 100644 --- a/trifid-api/trifid_api_entities/src/prelude.rs +++ b/trifid-api/trifid_api_entities/src/prelude.rs @@ -1,3 +1,4 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +pub use super::magic_link::Entity as MagicLink; pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/user.rs b/trifid-api/trifid_api_entities/src/user.rs index ab703b3..91414d6 100644 --- a/trifid-api/trifid_api_entities/src/user.rs +++ b/trifid-api/trifid_api_entities/src/user.rs @@ -7,11 +7,21 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, + #[sea_orm(unique)] pub email: String, pub password_hash: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm(has_many = "super::magic_link::Entity")] + MagicLink, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MagicLink.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 372443b..77e9136 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -3,12 +3,14 @@ pub use sea_orm_migration::prelude::*; pub struct Migrator; pub mod m20230402_162601_create_table_users; +pub mod m20230402_183515_create_table_magic_links; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { vec![ - Box::new(m20230402_162601_create_table_users::Migration) + Box::new(m20230402_162601_create_table_users::Migration), + Box::new(m20230402_183515_create_table_magic_links::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs index ce6457d..3b54040 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs @@ -12,7 +12,7 @@ impl MigrationTrait for Migration { .table(User::Table) .if_not_exists() .col(ColumnDef::new(User::Id).string().not_null().primary_key()) - .col(ColumnDef::new(User::Email).string().not_null()) + .col(ColumnDef::new(User::Email).string().not_null().unique_key()) .col(ColumnDef::new(User::PasswordHash).string().not_null()) .to_owned() ).await @@ -25,7 +25,7 @@ impl MigrationTrait for Migration { /// Learn more at https://docs.rs/sea-query#iden #[derive(Iden)] -enum User { +pub enum User { Table, Id, Email, diff --git a/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs b/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs new file mode 100644 index 0000000..43c70df --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs @@ -0,0 +1,41 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(MagicLink::Table) + .if_not_exists() + .col(ColumnDef::new(MagicLink::Id).string().not_null().primary_key()) + .col(ColumnDef::new(MagicLink::User).string().not_null()) + .col(ColumnDef::new(MagicLink::ExpiresOn).big_integer().not_null()) + .foreign_key( + ForeignKey::create() + .name("fk_magiclink_user_users_id") + .from(MagicLink::Table, MagicLink::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(MagicLink::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum MagicLink { + Table, + Id, + User, + ExpiresOn +} From 2d7607bbc13b54a8f16848bc457f252721c0498d Mon Sep 17 00:00:00 2001 From: core Date: Sun, 2 Apr 2023 15:47:50 -0400 Subject: [PATCH 04/39] signup --- trifid-api/src/magic_link.rs | 9 ++ trifid-api/src/main.rs | 3 + trifid-api/src/routes/mod.rs | 2 +- .../src/routes/{ => v1}/auth/magic_link.rs | 23 +++- trifid-api/src/routes/{ => v1}/auth/mod.rs | 0 trifid-api/src/routes/v1/mod.rs | 2 + trifid-api/src/routes/v1/signup.rs | 128 ++++++++++++++++++ .../src/{ => entity}/magic_link.rs | 0 .../trifid_api_entities/src/entity/mod.rs | 6 + .../src/{ => entity}/prelude.rs | 0 .../src/{ => entity}/user.rs | 1 - trifid-api/trifid_api_entities/src/lib.rs | 7 +- .../m20230402_162601_create_table_users.rs | 4 +- 13 files changed, 171 insertions(+), 14 deletions(-) create mode 100644 trifid-api/src/magic_link.rs rename trifid-api/src/routes/{ => v1}/auth/magic_link.rs (79%) rename trifid-api/src/routes/{ => v1}/auth/mod.rs (100%) create mode 100644 trifid-api/src/routes/v1/mod.rs create mode 100644 trifid-api/src/routes/v1/signup.rs rename trifid-api/trifid_api_entities/src/{ => entity}/magic_link.rs (100%) create mode 100644 trifid-api/trifid_api_entities/src/entity/mod.rs rename trifid-api/trifid_api_entities/src/{ => entity}/prelude.rs (100%) rename trifid-api/trifid_api_entities/src/{ => entity}/user.rs (95%) diff --git a/trifid-api/src/magic_link.rs b/trifid-api/src/magic_link.rs new file mode 100644 index 0000000..a4b57e7 --- /dev/null +++ b/trifid-api/src/magic_link.rs @@ -0,0 +1,9 @@ +use std::error::Error; +use log::info; + +pub fn send_magic_link(token: &str) -> Result<(), Box> { + // TODO: actually do this + info!("sent magic link {}", token); + + Ok(()) +} \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 59694e7..ca641df 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -15,6 +15,7 @@ pub mod routes; pub mod error; pub mod tokens; pub mod timers; +pub mod magic_link; pub struct AppState { pub conn: DatabaseConnection @@ -60,6 +61,8 @@ async fn main() -> Result<(), Box> { ).into() })) .wrap(RequestIdentifier::with_generator(random_id_no_id)) + .service(routes::v1::auth::magic_link::magic_link_request) + .service(routes::v1::signup::signup_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/mod.rs b/trifid-api/src/routes/mod.rs index 5696e21..5dd9fd0 100644 --- a/trifid-api/src/routes/mod.rs +++ b/trifid-api/src/routes/mod.rs @@ -1 +1 @@ -pub mod auth; \ No newline at end of file +pub mod v1; \ No newline at end of file diff --git a/trifid-api/src/routes/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs similarity index 79% rename from trifid-api/src/routes/auth/magic_link.rs rename to trifid-api/src/routes/v1/auth/magic_link.rs index 6c9bd4c..c5ae562 100644 --- a/trifid-api/src/routes/auth/magic_link.rs +++ b/trifid-api/src/routes/v1/auth/magic_link.rs @@ -3,11 +3,12 @@ use actix_web::web::{Data, Json}; use log::error; use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter}; use serde::{Serialize, Deserialize}; -use trifid_api_entities::user::Entity as UserEntity; -use trifid_api_entities::user; +use trifid_api_entities::entity::user::Entity as UserEntity; +use trifid_api_entities::entity::user; use crate::AppState; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; +use crate::magic_link::send_magic_link; use crate::timers::expires_in_seconds; use crate::tokens::random_token; @@ -59,12 +60,28 @@ pub async fn magic_link_request(data: Data, req: Json (), + Err(e) => { + error!("error sending magic link: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_ML_ERROR".to_string(), + message: "There was an error sending the magic link email, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + let active_model = model.into_active_model(); match active_model.insert(&data.conn).await { diff --git a/trifid-api/src/routes/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs similarity index 100% rename from trifid-api/src/routes/auth/mod.rs rename to trifid-api/src/routes/v1/auth/mod.rs diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs new file mode 100644 index 0000000..becd02a --- /dev/null +++ b/trifid-api/src/routes/v1/mod.rs @@ -0,0 +1,2 @@ +pub mod auth; +pub mod signup; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs new file mode 100644 index 0000000..92b5ac8 --- /dev/null +++ b/trifid-api/src/routes/v1/signup.rs @@ -0,0 +1,128 @@ +use actix_web::{HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Serialize, Deserialize}; +use trifid_api_entities::entity::user::Entity as UserEntity; +use trifid_api_entities::entity::user; +use crate::AppState; +use crate::config::CONFIG; +use crate::error::{APIError, APIErrorsResponse}; +use crate::magic_link::send_magic_link; +use crate::timers::expires_in_seconds; +use crate::tokens::{random_id, random_token}; + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SignupRequest { + pub email: String +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SignupResponse { + pub data: Option, + pub metadata: SignupResponseMetadata +} +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SignupResponseData {} +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct SignupResponseMetadata {} + +#[post("/v1/signup")] +pub async fn signup_request(data: Data, req: Json) -> HttpResponse { + let user: Vec = match UserEntity::find().filter(user::Column::Email.eq(&req.email)).all(&data.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + }; + + if user.is_empty() { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_USER_EXISTS".to_string(), + message: "That user already exists.".to_string(), + path: None, + } + ], + }) + } + + let model = user::Model { + id: random_id("user"), + email: req.email.clone() + }; + let id = model.id.clone(); + + let active_model = model.into_active_model(); + + match active_model.insert(&data.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + let model = trifid_api_entities::entity::magic_link::Model { + id: random_token("ml"), + user: id, + expires_on: expires_in_seconds(CONFIG.tokens.magic_link_expiry_time_seconds) as i64, + }; + + match send_magic_link(&model.id) { + Ok(_) => (), + Err(e) => { + error!("error sending magic link: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_ML_ERROR".to_string(), + message: "There was an error sending the magic link email, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + let active_model = model.into_active_model(); + + match active_model.insert(&data.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + HttpResponse::Ok().json(SignupResponse { + data: None, + metadata: SignupResponseMetadata {} + }) +} \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/magic_link.rs b/trifid-api/trifid_api_entities/src/entity/magic_link.rs similarity index 100% rename from trifid-api/trifid_api_entities/src/magic_link.rs rename to trifid-api/trifid_api_entities/src/entity/magic_link.rs diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs new file mode 100644 index 0000000..3bd313d --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -0,0 +1,6 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub mod prelude; + +pub mod magic_link; +pub mod user; diff --git a/trifid-api/trifid_api_entities/src/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs similarity index 100% rename from trifid-api/trifid_api_entities/src/prelude.rs rename to trifid-api/trifid_api_entities/src/entity/prelude.rs diff --git a/trifid-api/trifid_api_entities/src/user.rs b/trifid-api/trifid_api_entities/src/entity/user.rs similarity index 95% rename from trifid-api/trifid_api_entities/src/user.rs rename to trifid-api/trifid_api_entities/src/entity/user.rs index 91414d6..f7d4230 100644 --- a/trifid-api/trifid_api_entities/src/user.rs +++ b/trifid-api/trifid_api_entities/src/entity/user.rs @@ -9,7 +9,6 @@ pub struct Model { pub id: String, #[sea_orm(unique)] pub email: String, - pub password_hash: String, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/trifid-api/trifid_api_entities/src/lib.rs b/trifid-api/trifid_api_entities/src/lib.rs index d1b689d..bccca66 100644 --- a/trifid-api/trifid_api_entities/src/lib.rs +++ b/trifid-api/trifid_api_entities/src/lib.rs @@ -1,6 +1 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -pub mod prelude; - -pub mod user; -pub mod magic_link; \ No newline at end of file +pub mod entity; \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs index 3b54040..1e04c6a 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs @@ -13,7 +13,6 @@ impl MigrationTrait for Migration { .if_not_exists() .col(ColumnDef::new(User::Id).string().not_null().primary_key()) .col(ColumnDef::new(User::Email).string().not_null().unique_key()) - .col(ColumnDef::new(User::PasswordHash).string().not_null()) .to_owned() ).await } @@ -28,6 +27,5 @@ impl MigrationTrait for Migration { pub enum User { Table, Id, - Email, - PasswordHash, + Email } From fcadd98701b163407286d87fbca8e51d6be63e71 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 2 Apr 2023 16:01:09 -0400 Subject: [PATCH 05/39] fix --- trifid-api/.env | 2 +- trifid-api/src/routes/v1/signup.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/trifid-api/.env b/trifid-api/.env index 54dc561..b4f6475 100644 --- a/trifid-api/.env +++ b/trifid-api/.env @@ -1 +1 @@ -DATABASE_URL=postgres://postgres@localhost/hotel \ No newline at end of file +DATABASE_URL=postgres://postgres@localhost/trifid \ No newline at end of file diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs index 92b5ac8..2dcaccd 100644 --- a/trifid-api/src/routes/v1/signup.rs +++ b/trifid-api/src/routes/v1/signup.rs @@ -45,7 +45,7 @@ pub async fn signup_request(data: Data, req: Json) -> H } }; - if user.is_empty() { + if !user.is_empty() { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { From 1453509dc477d1a3da82200cbd8c3afc0fa57504 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 19:12:08 -0400 Subject: [PATCH 06/39] /v1/auth/verify-magic-link --- trifid-api/src/config.rs | 7 +- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/auth/mod.rs | 3 +- .../src/routes/v1/auth/verify_magic_link.rs | 113 ++++++++++++++++++ .../trifid_api_entities/src/entity/mod.rs | 1 + .../trifid_api_entities/src/entity/prelude.rs | 1 + .../src/entity/session_token.rs | 32 +++++ .../trifid_api_entities/src/entity/user.rs | 8 ++ trifid-api/trifid_api_migration/src/lib.rs | 2 + ...0402_213712_create_table_session_tokens.rs | 40 +++++++ 10 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 trifid-api/src/routes/v1/auth/verify_magic_link.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/session_token.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 536a5d8..1a8d1d5 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -57,7 +57,9 @@ pub struct TrifidConfigServer { #[derive(Serialize, Deserialize, Debug)] pub struct TrifidConfigTokens { #[serde(default = "magic_link_expiry_time")] - pub magic_link_expiry_time_seconds: u64 + pub magic_link_expiry_time_seconds: u64, + #[serde(default = "session_token_expiry_time")] + pub session_token_expiry_time_seconds: u64 } fn max_connections_default() -> u32 { 100 } @@ -65,4 +67,5 @@ fn min_connections_default() -> u32 { 5 } fn time_defaults() -> u64 { 8 } fn sqlx_logging_default() -> bool { true } fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } -fn magic_link_expiry_time() -> u64 { 3600 } \ No newline at end of file +fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour +fn session_token_expiry_time() -> u64 { 15780000 } // 6 months \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index ca641df..18cd637 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -63,6 +63,7 @@ async fn main() -> Result<(), Box> { .wrap(RequestIdentifier::with_generator(random_id_no_id)) .service(routes::v1::auth::magic_link::magic_link_request) .service(routes::v1::signup::signup_request) + .service(routes::v1::auth::verify_magic_link::verify_magic_link_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs index 600b7fa..63d2a9f 100644 --- a/trifid-api/src/routes/v1/auth/mod.rs +++ b/trifid-api/src/routes/v1/auth/mod.rs @@ -1 +1,2 @@ -pub mod magic_link; \ No newline at end of file +pub mod magic_link; +pub mod verify_magic_link; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs new file mode 100644 index 0000000..0e08582 --- /dev/null +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -0,0 +1,113 @@ +use actix_web::{HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Serialize, Deserialize}; +use crate::AppState; +use trifid_api_entities::entity::magic_link; +use trifid_api_entities::entity::magic_link::Model; +use trifid_api_entities::entity::session_token; +use crate::config::CONFIG; +use crate::error::{APIError, APIErrorsResponse}; +use crate::timers::{expired, expires_in_seconds}; +use crate::tokens::random_token; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyMagicLinkRequest { + #[serde(rename = "magicLinkToken")] + pub magic_link_token: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyMagicLinkResponse { + pub data: VerifyMagicLinkResponseData, + pub metadata: VerifyMagicLinkResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyMagicLinkResponseData { + #[serde(rename = "sessionToken")] + pub session_token: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyMagicLinkResponseMetadata {} + +#[post("/v1/auth/verify-magic-link")] +pub async fn verify_magic_link_request(db: Data, req: Json) -> HttpResponse { + let link: Option = match magic_link::Entity::find().filter(magic_link::Column::Id.eq(&req.magic_link_token)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + }; + + let link = match link { + Some(l) => l, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None + } + ] + }) + } + }; + + if !expired(link.expires_on as u64) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_EXPIRED".to_string(), + message: "Magic link token expired".to_string(), + path: None + } + ] + }) + } + + let model = session_token::Model { + id: random_token("sess"), + user: link.user, + expires_on: expires_in_seconds(CONFIG.tokens.session_token_expiry_time_seconds) as i64, + }; + let token = model.id.clone(); + let active_model = model.into_active_model(); + + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + HttpResponse::Ok().json( + VerifyMagicLinkResponse { + data: VerifyMagicLinkResponseData { + session_token: token, + }, + metadata: VerifyMagicLinkResponseMetadata {}, + } + ) +} \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs index 3bd313d..fa37cff 100644 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -3,4 +3,5 @@ pub mod prelude; pub mod magic_link; +pub mod session_token; pub mod user; diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs index 527e6e0..b5b9410 100644 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -1,4 +1,5 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 pub use super::magic_link::Entity as MagicLink; +pub use super::session_token::Entity as SessionToken; pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/entity/session_token.rs b/trifid-api/trifid_api_entities/src/entity/session_token.rs new file mode 100644 index 0000000..a648966 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/session_token.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "session_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_entities/src/entity/user.rs index f7d4230..1ca5914 100644 --- a/trifid-api/trifid_api_entities/src/entity/user.rs +++ b/trifid-api/trifid_api_entities/src/entity/user.rs @@ -15,6 +15,8 @@ pub struct Model { pub enum Relation { #[sea_orm(has_many = "super::magic_link::Entity")] MagicLink, + #[sea_orm(has_many = "super::session_token::Entity")] + SessionToken, } impl Related for Entity { @@ -23,4 +25,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::SessionToken.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 77e9136..5992ac1 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -4,6 +4,7 @@ pub struct Migrator; pub mod m20230402_162601_create_table_users; pub mod m20230402_183515_create_table_magic_links; +pub mod m20230402_213712_create_table_session_tokens; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -11,6 +12,7 @@ impl MigratorTrait for Migrator { vec![ Box::new(m20230402_162601_create_table_users::Migration), Box::new(m20230402_183515_create_table_magic_links::Migration), + Box::new(m20230402_213712_create_table_session_tokens::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs b/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs new file mode 100644 index 0000000..8c51606 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs @@ -0,0 +1,40 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(SessionToken::Table) + .if_not_exists() + .col(ColumnDef::new(SessionToken::Id).string().not_null().primary_key()) + .col(ColumnDef::new(SessionToken::User).string().not_null()) + .col(ColumnDef::new(SessionToken::ExpiresOn).big_integer().not_null()) + .foreign_key( + ForeignKey::create() + .from(SessionToken::Table, SessionToken::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(SessionToken::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum SessionToken { + Table, + Id, + User, + ExpiresOn +} From 1e66d14710b694f273ea39cf119db15d62e10bf9 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 20:57:33 -0400 Subject: [PATCH 07/39] whoa, i forgot to commit and made another ton of stuff --- Cargo.lock | 35 +++++ trifid-api/Cargo.toml | 5 +- trifid-api/src/auth_tokens.rs | 136 +++++++++++++++++ trifid-api/src/config.rs | 7 +- trifid-api/src/main.rs | 2 + trifid-api/src/routes/v1/mod.rs | 3 +- .../src/routes/v1/totp_authenticators.rs | 141 ++++++++++++++++++ trifid-api/src/tokens.rs | 4 + .../trifid_api_entities/src/entity/api_key.rs | 41 +++++ .../src/entity/api_key_scope.rs | 32 ++++ .../src/entity/auth_token.rs | 32 ++++ .../trifid_api_entities/src/entity/mod.rs | 6 + .../trifid_api_entities/src/entity/network.rs | 33 ++++ .../src/entity/organization.rs | 49 ++++++ .../trifid_api_entities/src/entity/prelude.rs | 6 + .../src/entity/totp_authenticator.rs | 38 +++++ .../trifid_api_entities/src/entity/user.rs | 24 +++ trifid-api/trifid_api_migration/src/lib.rs | 12 ++ ...30402_232316_create_table_organizations.rs | 37 +++++ .../m20230402_232323_create_table_networks.rs | 38 +++++ .../m20230402_233043_create_table_api_keys.rs | 39 +++++ ...402_233047_create_table_api_keys_scopes.rs | 38 +++++ ...234025_create_table_totp_authenticators.rs | 44 ++++++ ...0230403_002256_create_table_auth_tokens.rs | 40 +++++ 24 files changed, 837 insertions(+), 5 deletions(-) create mode 100644 trifid-api/src/auth_tokens.rs create mode 100644 trifid-api/src/routes/v1/totp_authenticators.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/api_key.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/api_key_scope.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/auth_token.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/network.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/organization.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs diff --git a/Cargo.lock b/Cargo.lock index 0ca5eee..b132d83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,6 +465,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "base32" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" + [[package]] name = "base64" version = "0.13.1" @@ -788,6 +794,12 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +[[package]] +name = "constant_time_eq" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" + [[package]] name = "convert_case" version = "0.4.0" @@ -3313,6 +3325,22 @@ dependencies = [ "winnow", ] +[[package]] +name = "totp-rs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332e333b188e843cb4cc477b2911160a533bcfc6e9e488d7bef25011f9e2ba1b" +dependencies = [ + "base32", + "constant_time_eq", + "hmac", + "rand", + "sha1", + "sha2", + "url", + "urlencoding", +] + [[package]] name = "tower-service" version = "0.3.2" @@ -3381,6 +3409,7 @@ dependencies = [ "serde", "simple_logger", "toml 0.7.3", + "totp-rs", "trifid_api_entities", "trifid_api_migration", ] @@ -3497,6 +3526,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + [[package]] name = "uuid" version = "1.3.0" diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index ab1f364..5b08380 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -21,5 +21,6 @@ sea-orm = { version = "^0", features = [ "sqlx-postgres", "runtime-actix-rustls" trifid_api_migration = { version = "0.1.0", path = "trifid_api_migration" } # Database trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } # Database -rand = "0.8" # Misc. -hex = "0.4" # Misc. \ No newline at end of file +rand = "0.8" # Misc. +hex = "0.4" # Misc. +totp-rs = { version = "5.0.1", features = ["gen_secret", "otpauth"] } # Misc. \ No newline at end of file diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs new file mode 100644 index 0000000..167b3cd --- /dev/null +++ b/trifid-api/src/auth_tokens.rs @@ -0,0 +1,136 @@ +use std::error::Error; +use actix_web::HttpRequest; +use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; +use crate::tokens::get_token_type; +use trifid_api_entities::entity::{auth_token, session_token}; +use trifid_api_entities::entity::api_key; +use trifid_api_entities::entity::api_key_scope; +use trifid_api_entities::entity::user; +use crate::timers::expired; + +pub enum TokenInfo { + SessionToken(SessionTokenInfo), + AuthToken(AuthTokenInfo), + ApiToken(ApiTokenInfo) +} + +pub struct SessionTokenInfo { + pub token: String, + pub user: SessionTokenUser, + pub expires_at: i64 +} + +pub struct SessionTokenUser { + pub id: String, + pub email: String +} + +pub struct ApiTokenInfo { + pub scopes: Vec, + pub organization: String +} + +pub struct AuthTokenInfo { + pub token: String, + pub session_info: SessionTokenInfo +} + +pub async fn enforce_session(req: &HttpRequest, db: &DatabaseConnection) -> Result> { + let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let authorization = header.to_str()?; + + let authorization_split: Vec<&str> = authorization.split(' ').collect(); + if authorization_split[0] != "Bearer" { + return Err("Not a bearer token".into()) + } + let tokens = &authorization_split[1..]; + + let sess_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-sess") == "sess").copied().ok_or("Missing session token")?; + + let token: session_token::Model = session_token::Entity::find().filter(session_token::Column::Id.eq(sess_token)).one(db).await?.ok_or("Invalid session token")?; + + if expired(token.expires_on as u64) { + return Err("Token expired".into()); + } + + let user: user::Model = user::Entity::find().filter(user::Column::Id.eq(token.user)).one(db).await?.ok_or("Session token has a nonexistent user")?; + + Ok(TokenInfo::SessionToken(SessionTokenInfo { + token: token.id, + user: SessionTokenUser { + id: user.id, + email: user.email + }, + expires_at: token.expires_on, + })) +} + +pub async fn enforce_2fa(req: &HttpRequest, db: &DatabaseConnection) -> Result> { + let session_data = match enforce_session(req, db).await? { + TokenInfo::SessionToken(i) => i, + _ => unreachable!() + }; + + let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let authorization = header.to_str()?; + + let authorization_split: Vec<&str> = authorization.split(' ').collect(); + if authorization_split[0] != "Bearer" { + return Err("Not a bearer token".into()) + } + let tokens = &authorization_split[1..]; + + let auth_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-auth") == "auth").copied().ok_or("Missing auth token")?; + + let token: auth_token::Model = auth_token::Entity::find().filter(auth_token::Column::Id.eq(auth_token)).one(db).await?.ok_or("Invalid session token")?; + + if expired(token.expires_on as u64) { + return Err("Token expired".into()); + } + + Ok(TokenInfo::AuthToken(AuthTokenInfo { + token: token.id, + session_info: session_data, + })) +} + +pub async fn enforce_api_token(req: &HttpRequest, scopes: &[&str], db: &DatabaseConnection) -> Result> { + let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let authorization = header.to_str()?; + + let authorization_split: Vec<&str> = authorization.split(' ').collect(); + if authorization_split[0] != "Bearer" { + return Err("Not a bearer token".into()) + } + let tokens = &authorization_split[1..]; + + let api_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-tfkey") == "tfkey").copied().ok_or("Missing api token")?; + + // API tokens are special and have a different form than other keys. + // They follow the form: + // tfkey-[ID]-[TOKEN] + + let api_token_split: Vec<&str> = api_token.split('-').collect(); + if api_token_split.len() != 3 { + return Err("API token is missing key".into()); + } + let token_id = format!("{}-{}", api_token_split[0], api_token_split[1]); + let token_key = api_token_split[2].to_string(); + + let token: api_key::Model = api_key::Entity::find().filter( + Condition::all().add(api_key::Column::Id.eq(token_id)).add(api_key::Column::Key.eq(token_key)) + ).one(db).await?.ok_or("Invalid api token")?; + let token_scopes: Vec = api_key_scope::Entity::find().filter(api_key_scope::Column::ApiKey.eq(api_token)).all(db).await?; + let token_scopes: Vec<&str> = token_scopes.iter().map(|i| i.scope.as_str()).collect(); + + for scope in scopes { + if !token_scopes.contains(scope) { + return Err(format!("API token is missing scope {}", scope).into()); + } + } + + Ok(TokenInfo::ApiToken(ApiTokenInfo { + scopes: token_scopes.iter().map(|i| i.to_string()).collect(), + organization: token.organization, + })) +} \ No newline at end of file diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 1a8d1d5..b9a8420 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -59,7 +59,9 @@ pub struct TrifidConfigTokens { #[serde(default = "magic_link_expiry_time")] pub magic_link_expiry_time_seconds: u64, #[serde(default = "session_token_expiry_time")] - pub session_token_expiry_time_seconds: u64 + pub session_token_expiry_time_seconds: u64, + #[serde(default = "totp_setup_timeout_time")] + pub totp_setup_timeout_time_seconds: u64 } fn max_connections_default() -> u32 { 100 } @@ -68,4 +70,5 @@ fn time_defaults() -> u64 { 8 } fn sqlx_logging_default() -> bool { true } fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour -fn session_token_expiry_time() -> u64 { 15780000 } // 6 months \ No newline at end of file +fn session_token_expiry_time() -> u64 { 15780000 } // 6 months +fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 18cd637..4218f86 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -16,6 +16,7 @@ pub mod error; pub mod tokens; pub mod timers; pub mod magic_link; +pub mod auth_tokens; pub struct AppState { pub conn: DatabaseConnection @@ -64,6 +65,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::auth::magic_link::magic_link_request) .service(routes::v1::signup::signup_request) .service(routes::v1::auth::verify_magic_link::verify_magic_link_request) + .service(routes::v1::totp_authenticators::totp_authenticators_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index becd02a..65f6347 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -1,2 +1,3 @@ pub mod auth; -pub mod signup; \ No newline at end of file +pub mod signup; +pub mod totp_authenticators; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs new file mode 100644 index 0000000..8df66d2 --- /dev/null +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -0,0 +1,141 @@ +use serde::{Serialize, Deserialize}; +use actix_web::{HttpRequest, HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use totp_rs::{Algorithm, Secret, TOTP}; +use crate::AppState; +use crate::auth_tokens::{enforce_2fa, enforce_session, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use trifid_api_entities::entity::totp_authenticator; +use crate::config::CONFIG; +use crate::timers::expires_in_seconds; +use crate::tokens::random_id; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsRequest {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsResponseMetadata {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsResponseData { + #[serde(rename = "totpToken")] + pub totp_token: String, + pub secret: String, + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpAuthenticatorsResponse { + pub data: TotpAuthenticatorsResponseData, + pub metadata: TotpAuthenticatorsResponseMetadata, +} + +#[post("/v1/totp-authenticators")] +pub async fn totp_authenticators_request(db: Data, req_data: HttpRequest, req: Json) -> HttpResponse { + // require a user session + let session_token = match enforce_session(&req_data, &db.conn).await { + Ok(r) => { + match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!() + } + } + Err(e) => { + error!("error enforcing session: {}", e); + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }); + } + }; + + // determine if the user has a totp authenticator + let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + if auther.is_some() { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_ALREADY_HAS_TOTP".to_string(), + message: "This user already has a totp authenticator".to_string(), + path: None, + } + ] + }); + } + + let secret = Secret::generate_secret(); + let totpmachine = match TOTP::new(Algorithm::SHA1, 6, 1, 30, secret.to_bytes().expect("Invalid randomized data"), Some("trifid-api".to_string()), session_token.user.email) { + Ok(m) => m, + Err(e) => { + error!("totp machine create error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_SECRET_ERR".to_string(), + message: "There was an error configuring the authenticator, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let model = totp_authenticator::Model { + id: random_id("totp"), + secret: Secret::Raw(totpmachine.secret.clone()).to_encoded().to_string(), + url: totpmachine.get_url(), + verified: false, + expires_on: expires_in_seconds(CONFIG.tokens.totp_setup_timeout_time_seconds) as i64, + user: session_token.user.id, + }; + let id = model.id.clone(); + let secret = model.secret.clone(); + let url = model.url.clone(); + + let active_model = model.into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + HttpResponse::Ok().json(TotpAuthenticatorsResponse { + data: TotpAuthenticatorsResponseData { + totp_token: id, + secret, + url, + }, + metadata: TotpAuthenticatorsResponseMetadata {}, + }) +} \ No newline at end of file diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs index a1c2475..e441f9a 100644 --- a/trifid-api/src/tokens.rs +++ b/trifid-api/src/tokens.rs @@ -30,4 +30,8 @@ fn random_with_charset(len: u32, charset: &[u8]) -> String { let idx = rand::thread_rng().gen_range(0..charset.len()); charset[idx] as char }).collect() +} + +pub fn get_token_type(token: &str) -> Option<&str> { + token.split('-').collect::>().get(0).copied() } \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/api_key.rs b/trifid-api/trifid_api_entities/src/entity/api_key.rs new file mode 100644 index 0000000..04aa795 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/api_key.rs @@ -0,0 +1,41 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub key: String, + pub organization: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key_scope::Entity")] + ApiKeyScope, + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKeyScope.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs new file mode 100644 index 0000000..1deffbc --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key_scope")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub scope: String, + pub api_key: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::api_key::Entity", + from = "Column::ApiKey", + to = "super::api_key::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + ApiKey, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/auth_token.rs b/trifid-api/trifid_api_entities/src/entity/auth_token.rs new file mode 100644 index 0000000..db14039 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/auth_token.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "auth_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs index fa37cff..82f32b1 100644 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -2,6 +2,12 @@ pub mod prelude; +pub mod api_key; +pub mod api_key_scope; +pub mod auth_token; pub mod magic_link; +pub mod network; +pub mod organization; pub mod session_token; +pub mod totp_authenticator; pub mod user; diff --git a/trifid-api/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_entities/src/entity/network.rs new file mode 100644 index 0000000..93232d1 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/network.rs @@ -0,0 +1,33 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "network")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub organization: String, + pub ip_block: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_entities/src/entity/organization.rs new file mode 100644 index 0000000..f5f5026 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/organization.rs @@ -0,0 +1,49 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "organization")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + #[sea_orm(unique)] + pub owner: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key::Entity")] + ApiKey, + #[sea_orm(has_one = "super::network::Entity")] + Network, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::Owner", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs index b5b9410..d201e1a 100644 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -1,5 +1,11 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +pub use super::api_key::Entity as ApiKey; +pub use super::api_key_scope::Entity as ApiKeyScope; +pub use super::auth_token::Entity as AuthToken; pub use super::magic_link::Entity as MagicLink; +pub use super::network::Entity as Network; +pub use super::organization::Entity as Organization; pub use super::session_token::Entity as SessionToken; +pub use super::totp_authenticator::Entity as TotpAuthenticator; pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs new file mode 100644 index 0000000..5437596 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "totp_authenticator")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub secret: String, + #[sea_orm(unique)] + pub url: String, + pub verified: bool, + pub expires_on: i64, + #[sea_orm(unique)] + pub user: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_entities/src/entity/user.rs index 1ca5914..0d7b61f 100644 --- a/trifid-api/trifid_api_entities/src/entity/user.rs +++ b/trifid-api/trifid_api_entities/src/entity/user.rs @@ -13,10 +13,22 @@ pub struct Model { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { + #[sea_orm(has_many = "super::auth_token::Entity")] + AuthToken, #[sea_orm(has_many = "super::magic_link::Entity")] MagicLink, + #[sea_orm(has_one = "super::organization::Entity")] + Organization, #[sea_orm(has_many = "super::session_token::Entity")] SessionToken, + #[sea_orm(has_one = "super::totp_authenticator::Entity")] + TotpAuthenticator, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthToken.def() + } } impl Related for Entity { @@ -25,10 +37,22 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::SessionToken.def() } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::TotpAuthenticator.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 5992ac1..68d4c03 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -5,6 +5,12 @@ pub struct Migrator; pub mod m20230402_162601_create_table_users; pub mod m20230402_183515_create_table_magic_links; pub mod m20230402_213712_create_table_session_tokens; +pub mod m20230402_232316_create_table_organizations; +pub mod m20230402_232323_create_table_networks; +pub mod m20230402_233043_create_table_api_keys; +pub mod m20230402_233047_create_table_api_keys_scopes; +mod m20230402_234025_create_table_totp_authenticators; +mod m20230403_002256_create_table_auth_tokens; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -13,6 +19,12 @@ impl MigratorTrait for Migrator { Box::new(m20230402_162601_create_table_users::Migration), Box::new(m20230402_183515_create_table_magic_links::Migration), Box::new(m20230402_213712_create_table_session_tokens::Migration), + Box::new(m20230402_232316_create_table_organizations::Migration), + Box::new(m20230402_232323_create_table_networks::Migration), + Box::new(m20230402_233043_create_table_api_keys::Migration), + Box::new(m20230402_233047_create_table_api_keys_scopes::Migration), + Box::new(m20230402_234025_create_table_totp_authenticators::Migration), + Box::new(m20230403_002256_create_table_auth_tokens::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs b/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs new file mode 100644 index 0000000..86d18cd --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs @@ -0,0 +1,37 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create().table(Organization::Table) + .col(ColumnDef::new(Organization::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Organization::Name).string().not_null()) + .col(ColumnDef::new(Organization::Owner).string().not_null().unique_key()) + .foreign_key( + ForeignKey::create() + .from(Organization::Table, Organization::Owner) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(Organization::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum Organization { + Table, + Id, + Name, + Owner +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs b/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs new file mode 100644 index 0000000..668fdd4 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs @@ -0,0 +1,38 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_232316_create_table_organizations::Organization; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(Network::Table) + .col(ColumnDef::new(Network::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Network::Organization).string().not_null().unique_key()) + .col(ColumnDef::new(Network::IpBlock).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(Network::Table, Network::Organization) + .to(Organization::Table, Organization::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(Network::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum Network { + Table, + Id, + Organization, + IpBlock +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs b/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs new file mode 100644 index 0000000..c0d36c3 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs @@ -0,0 +1,39 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_232316_create_table_organizations::Organization; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(ApiKey::Table) + .col(ColumnDef::new(ApiKey::Id).string().not_null().primary_key()) + .col(ColumnDef::new(ApiKey::Key).string().not_null().unique_key()) + .col(ColumnDef::new(ApiKey::Organization).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(ApiKey::Table, ApiKey::Organization) + .to(Organization::Table, Organization::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(ApiKey::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum ApiKey { + Table, + Id, + Key, + Organization +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs b/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs new file mode 100644 index 0000000..359aa24 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs @@ -0,0 +1,38 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_233043_create_table_api_keys::ApiKey; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(ApiKeyScope::Table) + .col(ColumnDef::new(ApiKeyScope::Id).string().not_null().primary_key()) + .col(ColumnDef::new(ApiKeyScope::Scope).string().not_null()) + .col(ColumnDef::new(ApiKeyScope::ApiKey).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(ApiKeyScope::Table, ApiKeyScope::ApiKey) + .to(ApiKey::Table, ApiKey::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(ApiKeyScope::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum ApiKeyScope { + Table, + Id, + Scope, + ApiKey +} diff --git a/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs b/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs new file mode 100644 index 0000000..72b2379 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs @@ -0,0 +1,44 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(TotpAuthenticator::Table) + .col(ColumnDef::new(TotpAuthenticator::Id).string().not_null().primary_key()) + .col(ColumnDef::new(TotpAuthenticator::Secret).string().not_null().unique_key()) + .col(ColumnDef::new(TotpAuthenticator::Url).string().not_null().unique_key()) + .col(ColumnDef::new(TotpAuthenticator::Verified).boolean().not_null()) + .col(ColumnDef::new(TotpAuthenticator::ExpiresOn).big_integer().not_null()) + .col(ColumnDef::new(TotpAuthenticator::User).string().not_null().unique_key()) + .foreign_key( + ForeignKey::create() + .from(TotpAuthenticator::Table, TotpAuthenticator::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ).to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(TotpAuthenticator::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum TotpAuthenticator { + Table, + Id, + Secret, + Url, + Verified, + ExpiresOn, + User +} diff --git a/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs b/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs new file mode 100644 index 0000000..12a0a67 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs @@ -0,0 +1,40 @@ +use sea_orm_migration::prelude::*; +use crate::m20230402_162601_create_table_users::User; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(AuthToken::Table) + .if_not_exists() + .col(ColumnDef::new(AuthToken::Id).string().not_null().primary_key()) + .col(ColumnDef::new(AuthToken::User).string().not_null()) + .col(ColumnDef::new(AuthToken::ExpiresOn).big_integer().not_null()) + .foreign_key( + ForeignKey::create() + .from(AuthToken::Table, AuthToken::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(AuthToken::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum AuthToken { + Table, + Id, + User, + ExpiresOn +} From d70064f9987323156f1d8dd2ce6afa218139e666 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 21:11:27 -0400 Subject: [PATCH 08/39] finish /v1/totp-authenticators --- trifid-api/src/auth_tokens.rs | 3 +- .../src/routes/v1/auth/verify_magic_link.rs | 22 ++++++++++- .../src/routes/v1/totp_authenticators.rs | 39 +++++++++++++------ trifid-api/src/timers.rs | 2 +- 4 files changed, 51 insertions(+), 15 deletions(-) diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs index 167b3cd..efb48c4 100644 --- a/trifid-api/src/auth_tokens.rs +++ b/trifid-api/src/auth_tokens.rs @@ -1,5 +1,6 @@ use std::error::Error; use actix_web::HttpRequest; +use log::debug; use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; use crate::tokens::get_token_type; use trifid_api_entities::entity::{auth_token, session_token}; @@ -45,7 +46,7 @@ pub async fn enforce_session(req: &HttpRequest, db: &DatabaseConnection) -> Resu } let tokens = &authorization_split[1..]; - let sess_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-sess") == "sess").copied().ok_or("Missing session token")?; + let sess_token = tokens.iter().find(|i| get_token_type(i).unwrap_or("n-sess") == "sess").copied().ok_or("Missing session token")?; let token: session_token::Model = session_token::Entity::find().filter(session_token::Column::Id.eq(sess_token)).one(db).await?.ok_or("Invalid session token")?; diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs index 0e08582..cbd8240 100644 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -1,7 +1,7 @@ use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; use serde::{Serialize, Deserialize}; use crate::AppState; use trifid_api_entities::entity::magic_link; @@ -78,9 +78,27 @@ pub async fn verify_magic_link_request(db: Data, req: Json (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + let model = session_token::Model { id: random_token("sess"), - user: link.user, + user, expires_on: expires_in_seconds(CONFIG.tokens.session_token_expiry_time_seconds) as i64, }; let token = model.id.clone(); diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index 8df66d2..a24e66b 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize}; use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; use totp_rs::{Algorithm, Secret, TOTP}; use crate::AppState; use crate::auth_tokens::{enforce_2fa, enforce_session, TokenInfo}; @@ -72,16 +72,33 @@ pub async fn totp_authenticators_request(db: Data, req_data: HttpReque }); } }; - if auther.is_some() { - return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_ALREADY_HAS_TOTP".to_string(), - message: "This user already has a totp authenticator".to_string(), - path: None, - } - ] - }); + if let Some(auther) = auther { + if auther.verified { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_ALREADY_HAS_TOTP".to_string(), + message: "This user already has a totp authenticator".to_string(), + path: None, + } + ] + }); + } + match auther.delete(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }) + } + } } let secret = Secret::generate_secret(); diff --git a/trifid-api/src/timers.rs b/trifid-api/src/timers.rs index 522cd4c..5a77b83 100644 --- a/trifid-api/src/timers.rs +++ b/trifid-api/src/timers.rs @@ -5,5 +5,5 @@ pub fn expires_in_seconds(seconds: u64) -> u64 { } pub fn expired(time: u64) -> bool { - UNIX_EPOCH + Duration::from_secs(time) > SystemTime::now() + UNIX_EPOCH + Duration::from_secs(time) < SystemTime::now() } \ No newline at end of file From 87ddb07d5cb911094934ea744fee95c21653b483 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 21:47:32 -0400 Subject: [PATCH 09/39] finish /v1/verify-totp-authenticators --- trifid-api/src/config.rs | 7 +- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/mod.rs | 3 +- .../routes/v1/verify_totp_authenticators.rs | 198 ++++++++++++++++++ 4 files changed, 206 insertions(+), 3 deletions(-) create mode 100644 trifid-api/src/routes/v1/verify_totp_authenticators.rs diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index b9a8420..9b454d7 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -61,7 +61,9 @@ pub struct TrifidConfigTokens { #[serde(default = "session_token_expiry_time")] pub session_token_expiry_time_seconds: u64, #[serde(default = "totp_setup_timeout_time")] - pub totp_setup_timeout_time_seconds: u64 + pub totp_setup_timeout_time_seconds: u64, + #[serde(default = "mfa_tokens_expiry_time")] + pub mfa_tokens_expiry_time_seconds: u64 } fn max_connections_default() -> u32 { 100 } @@ -71,4 +73,5 @@ fn sqlx_logging_default() -> bool { true } fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour fn session_token_expiry_time() -> u64 { 15780000 } // 6 months -fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes \ No newline at end of file +fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes +fn mfa_tokens_expiry_time() -> u64 { 600 } // 10 minutes \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 4218f86..f54aca5 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -66,6 +66,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::signup::signup_request) .service(routes::v1::auth::verify_magic_link::verify_magic_link_request) .service(routes::v1::totp_authenticators::totp_authenticators_request) + .service(routes::v1::verify_totp_authenticators::verify_totp_authenticators_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index 65f6347..36a9ce5 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -1,3 +1,4 @@ pub mod auth; pub mod signup; -pub mod totp_authenticators; \ No newline at end of file +pub mod totp_authenticators; +pub mod verify_totp_authenticators; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs new file mode 100644 index 0000000..2228106 --- /dev/null +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -0,0 +1,198 @@ +use actix_web::{HttpRequest, HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use serde::{Serialize, Deserialize}; +use trifid_api_entities::entity::totp_authenticator; +use crate::AppState; +use crate::auth_tokens::{enforce_session, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait}; +use sea_orm::ActiveValue::Set; +use totp_rs::{Algorithm, Secret, TOTP}; +use trifid_api_entities::entity::auth_token; +use crate::config::CONFIG; +use crate::timers::expires_in_seconds; +use crate::tokens::random_token; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyTotpAuthenticatorsRequest { + #[serde(rename = "totpToken")] + pub totp_token: String, + pub code: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyTotpAuthenticatorsResponse { + pub data: VerifyTotpAuthenticatorsResponseData, + pub metadata: VerifyTotpAuthenticatorsResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyTotpAuthenticatorsResponseData { + #[serde(rename = "authToken")] + pub auth_token: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct VerifyTotpAuthenticatorsResponseMetadata {} + +#[post("/v1/verify-totp-authenticators")] +pub async fn verify_totp_authenticators_request(req: Json, req_data: HttpRequest, db: Data) -> HttpResponse { + // require a user session + let session_token = match enforce_session(&req_data, &db.conn).await { + Ok(r) => { + match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!() + } + } + Err(e) => { + error!("error enforcing session: {}", e); + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }); + } + }; + + // determine if the user has a totp authenticator + let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let auther = match auther { + Some(a) => { + if a.verified { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_ALREADY_HAS_TOTP".to_string(), + message: "This user already has a totp authenticator".to_string(), + path: None, + } + ] + }); + } + a + }, + None => { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_USER_NO_TOTP".to_string(), + message: "This user does not have a totp authenticator".to_string(), + path: None, + } + ] + }); + } + }; + + let secret = Secret::Encoded(auther.secret.clone()); + let totpmachine = match TOTP::from_url(auther.url.clone()) { + Ok(m) => m, + Err(e) => { + error!("totp url error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_SECRET_ERROR".to_string(), + message: "There was an error parsing the totpmachine. Please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let valid = match totpmachine.check_current(&req.totp_token) { + Ok(valid) => valid, + Err(e) => { + error!("system time error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_TIME_ERROR".to_string(), + message: "There was an with the server-side time clock.".to_string(), + path: None, + } + ], + }); + } + }; + + if !valid { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }) + } + + let mut active_model = auther.into_active_model(); + + active_model.verified = Set(true); + + match active_model.update(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error updating the totpmachine, please try again later.".to_string(), + path: None, + } + ], + }) + } + } + + let model: auth_token::Model = auth_token::Model { + id: random_token("auth"), + user: session_token.user.id, + expires_on: expires_in_seconds(CONFIG.tokens.mfa_tokens_expiry_time_seconds) as i64, + }; + let token = model.id.clone(); + let active_model = model.into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error issuing the authentication token.".to_string(), + path: None, + } + ], + }); + } + } + + HttpResponse::Ok().json(VerifyTotpAuthenticatorsResponse { + data: VerifyTotpAuthenticatorsResponseData { auth_token: token }, + metadata: VerifyTotpAuthenticatorsResponseMetadata {}, + }) +} \ No newline at end of file From 8edd9d2e66c4831dc73f15ced456375820407e56 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 21:49:45 -0400 Subject: [PATCH 10/39] and fix that --- trifid-api/src/routes/v1/totp_authenticators.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index a24e66b..955d02b 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -10,7 +10,7 @@ use crate::error::{APIError, APIErrorsResponse}; use trifid_api_entities::entity::totp_authenticator; use crate::config::CONFIG; use crate::timers::expires_in_seconds; -use crate::tokens::random_id; +use crate::tokens::{random_id, random_token}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpAuthenticatorsRequest {} @@ -119,7 +119,7 @@ pub async fn totp_authenticators_request(db: Data, req_data: HttpReque }; let model = totp_authenticator::Model { - id: random_id("totp"), + id: random_token("totp"), secret: Secret::Raw(totpmachine.secret.clone()).to_encoded().to_string(), url: totpmachine.get_url(), verified: false, From f0e36b303102846b089dd63288e95eab31fbd837 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Sun, 2 Apr 2023 21:55:16 -0400 Subject: [PATCH 11/39] dsssssssssssssss --- trifid-api/src/routes/v1/verify_totp_authenticators.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs index 2228106..51f4870 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticators.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -137,6 +137,7 @@ pub async fn verify_totp_authenticators_request(req: Json Date: Mon, 3 Apr 2023 08:41:47 -0400 Subject: [PATCH 12/39] fix and finish totp authenticators --- trifid-api/src/routes/v1/verify_totp_authenticators.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs index 51f4870..591e58c 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticators.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -1,6 +1,6 @@ use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; -use log::error; +use log::{debug, error}; use serde::{Serialize, Deserialize}; use trifid_api_entities::entity::totp_authenticator; use crate::AppState; @@ -120,7 +120,9 @@ pub async fn verify_totp_authenticators_request(req: Json valid, Err(e) => { error!("system time error: {}", e); @@ -137,6 +139,7 @@ pub async fn verify_totp_authenticators_request(req: Json Date: Mon, 3 Apr 2023 13:28:12 -0400 Subject: [PATCH 13/39] yo es un dumbass --- .idea/dataSources.xml | 4 +- .idea/trifid.iml | 2 + Cargo.lock | 105 ++++++++++- trifid-api/Cargo.toml | 5 +- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/auth/mod.rs | 3 +- trifid-api/src/routes/v1/auth/totp.rs | 169 ++++++++++++++++++ .../src/routes/v1/auth/verify_magic_link.rs | 2 +- .../routes/v1/verify_totp_authenticators.rs | 2 +- .../trifid_api_entities/src/entity/mod.rs | 1 + .../trifid_api_entities/src/entity/prelude.rs | 1 + .../src/entity/signing_ca.rs | 22 +++ trifid-api/trifid_api_migration/src/lib.rs | 2 + ...0230403_142517_create_table_signing_cas.rs | 37 ++++ 14 files changed, 348 insertions(+), 8 deletions(-) create mode 100644 trifid-api/src/routes/v1/auth/totp.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/signing_ca.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml index bbb37d6..7074b84 100644 --- a/.idea/dataSources.xml +++ b/.idea/dataSources.xml @@ -1,11 +1,11 @@ - + postgresql true org.postgresql.Driver - jdbc:postgresql://localhost:5432/trifidapi + jdbc:postgresql://localhost:5432/trifid $ProjectFileDir$ diff --git a/.idea/trifid.iml b/.idea/trifid.iml index ecc578a..2b729a4 100644 --- a/.idea/trifid.iml +++ b/.idea/trifid.iml @@ -6,6 +6,8 @@ + + diff --git a/Cargo.lock b/Cargo.lock index b132d83..b6904c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -206,6 +206,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[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 = "ahash" version = "0.7.6" @@ -669,6 +679,30 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.24" @@ -685,6 +719,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "clap" version = "3.2.23" @@ -877,6 +922,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1069,7 +1115,7 @@ dependencies = [ "reqwest", "serde", "serde_json", - "trifid-pki", + "trifid-pki 0.1.9", "url", ] @@ -1602,6 +1648,15 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -1920,6 +1975,12 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "openssl" version = "0.10.45" @@ -2139,6 +2200,17 @@ dependencies = [ "windows-sys 0.45.0", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -3128,7 +3200,7 @@ dependencies = [ "tar", "tempfile", "toml 0.7.3", - "trifid-pki", + "trifid-pki 0.1.9", "url", ] @@ -3401,6 +3473,7 @@ version = "0.1.0" dependencies = [ "actix-request-identifier", "actix-web", + "chacha20poly1305", "hex", "log", "once_cell", @@ -3410,6 +3483,7 @@ dependencies = [ "simple_logger", "toml 0.7.3", "totp-rs", + "trifid-pki 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "trifid_api_entities", "trifid_api_migration", ] @@ -3430,6 +3504,23 @@ dependencies = [ "x25519-dalek", ] +[[package]] +name = "trifid-pki" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20f0b2d11a5219f67b42a0af7f07b10f1c6c1ea57f8cb9f1115d394176b64c6b" +dependencies = [ + "ed25519-dalek", + "hex", + "ipnet", + "pem", + "quick-protobuf", + "rand", + "rand_core 0.6.4", + "sha2", + "x25519-dalek", +] + [[package]] name = "trifid_api_entities" version = "0.1.0" @@ -3503,6 +3594,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "universal-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "unsafe-libyaml" version = "0.2.7" diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index 5b08380..25034a5 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -23,4 +23,7 @@ trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } rand = "0.8" # Misc. hex = "0.4" # Misc. -totp-rs = { version = "5.0.1", features = ["gen_secret", "otpauth"] } # Misc. \ No newline at end of file +totp-rs = { version = "5.0.1", features = ["gen_secret", "otpauth"] } # Misc. + +trifid-pki = { version = "0.1.9" } # Cryptography +chacha20poly1305 = "0.10.1" # Cryptography \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index f54aca5..2c65ca2 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -67,6 +67,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::auth::verify_magic_link::verify_magic_link_request) .service(routes::v1::totp_authenticators::totp_authenticators_request) .service(routes::v1::verify_totp_authenticators::verify_totp_authenticators_request) + .service(routes::v1::auth::totp::totp_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs index 63d2a9f..29a43ea 100644 --- a/trifid-api/src/routes/v1/auth/mod.rs +++ b/trifid-api/src/routes/v1/auth/mod.rs @@ -1,2 +1,3 @@ pub mod magic_link; -pub mod verify_magic_link; \ No newline at end of file +pub mod verify_magic_link; +pub mod totp; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs new file mode 100644 index 0000000..bc95d2c --- /dev/null +++ b/trifid-api/src/routes/v1/auth/totp.rs @@ -0,0 +1,169 @@ +use actix_web::{HttpRequest, HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::{debug, error}; +use serde::{Serialize, Deserialize}; +use trifid_api_entities::entity::totp_authenticator; +use crate::AppState; +use crate::auth_tokens::{enforce_session, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait}; +use sea_orm::ActiveValue::Set; +use totp_rs::{Algorithm, Secret, TOTP}; +use trifid_api_entities::entity::auth_token; +use crate::config::CONFIG; +use crate::timers::expires_in_seconds; +use crate::tokens::random_token; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpRequest { + pub code: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpResponse { + pub data: TotpResponseData, + pub metadata: TotpResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpResponseData { + #[serde(rename = "authToken")] + pub auth_token: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct TotpResponseMetadata {} + +#[post("/v1/auth/totp")] +pub async fn totp_request(req: Json, req_data: HttpRequest, db: Data) -> HttpResponse { + // require a user session + let session_token = match enforce_session(&req_data, &db.conn).await { + Ok(r) => { + match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!() + } + } + Err(e) => { + error!("error enforcing session: {}", e); + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }); + } + }; + + // determine if the user has a totp authenticator + let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let auther = match auther { + Some(a) => { + a + }, + None => { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_USER_NO_TOTP".to_string(), + message: "This user does not have a totp authenticator".to_string(), + path: None, + } + ] + }); + } + }; + + let secret = Secret::Encoded(auther.secret.clone()); + let totpmachine = match TOTP::from_url(auther.url.clone()) { + Ok(m) => m, + Err(e) => { + error!("totp url error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_SECRET_ERROR".to_string(), + message: "There was an error parsing the totpmachine. Please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + + let valid = match totpmachine.check_current(&req.code) { + Ok(valid) => valid, + Err(e) => { + error!("system time error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_TIME_ERROR".to_string(), + message: "There was an with the server-side time clock.".to_string(), + path: None, + } + ], + }); + } + }; + + if !valid { + debug!("current: {}", totpmachine.generate_current().unwrap()); + error!("user send invalid totp code"); + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }) + } + + let model: auth_token::Model = auth_token::Model { + id: random_token("auth"), + user: session_token.user.id, + expires_on: expires_in_seconds(CONFIG.tokens.mfa_tokens_expiry_time_seconds) as i64, + }; + let token = model.id.clone(); + let active_model = model.into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error issuing the authentication token.".to_string(), + path: None, + } + ], + }); + } + } + + HttpResponse::Ok().json(TotpResponse { + data: TotpResponseData { auth_token: token }, + metadata: TotpResponseMetadata {}, + }) +} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs index cbd8240..d5eb770 100644 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -66,7 +66,7 @@ pub async fn verify_magic_link_request(db: Data, req: Json r, Err(e) => { error!("database error: {}", e); diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs index 82f32b1..194bbb3 100644 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -9,5 +9,6 @@ pub mod magic_link; pub mod network; pub mod organization; pub mod session_token; +pub mod signing_ca; pub mod totp_authenticator; pub mod user; diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs index d201e1a..2fdea32 100644 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -7,5 +7,6 @@ pub use super::magic_link::Entity as MagicLink; pub use super::network::Entity as Network; pub use super::organization::Entity as Organization; pub use super::session_token::Entity as SessionToken; +pub use super::signing_ca::Entity as SigningCa; pub use super::totp_authenticator::Entity as TotpAuthenticator; pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/entity/signing_ca.rs b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs new file mode 100644 index 0000000..2caba85 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs @@ -0,0 +1,22 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "signing_ca")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub organization: String, + pub cert: String, + #[sea_orm(unique)] + pub key: String, + pub expires: i64, + #[sea_orm(unique)] + pub nonce: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 68d4c03..3768bfc 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -11,6 +11,7 @@ pub mod m20230402_233043_create_table_api_keys; pub mod m20230402_233047_create_table_api_keys_scopes; mod m20230402_234025_create_table_totp_authenticators; mod m20230403_002256_create_table_auth_tokens; +mod m20230403_142517_create_table_signing_cas; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -25,6 +26,7 @@ impl MigratorTrait for Migrator { Box::new(m20230402_233047_create_table_api_keys_scopes::Migration), Box::new(m20230402_234025_create_table_totp_authenticators::Migration), Box::new(m20230403_002256_create_table_auth_tokens::Migration), + Box::new(m20230403_142517_create_table_signing_cas::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs b/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs new file mode 100644 index 0000000..084dcd0 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs @@ -0,0 +1,37 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(SigningCA::Table) + .col(ColumnDef::new(SigningCA::Id).string().not_null().primary_key()) + .col(ColumnDef::new(SigningCA::Organization).string().not_null()) + .col(ColumnDef::new(SigningCA::Cert).string().not_null()) + .col(ColumnDef::new(SigningCA::Key).string().not_null().unique_key()) + .col(ColumnDef::new(SigningCA::Expires).big_integer().not_null()) + .col(ColumnDef::new(SigningCA::Nonce).string().not_null().unique_key()) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.drop_table(Table::drop().table(SigningCA::Table).to_owned()).await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum SigningCA { + Table, + Id, + Organization, + Cert, + Key, + Expires, + Nonce +} From 90653796cc1ee451aef8e7335f0b7d5f0e0cfb47 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Mon, 3 Apr 2023 16:42:52 -0400 Subject: [PATCH 14/39] [tmp] --- trifid-api/src/routes/v1/mod.rs | 3 +- trifid-api/src/routes/v1/networks.rs | 46 +++++++++++++++++++ .../trifid_api_entities/src/entity/network.rs | 21 ++++++++- .../src/entity/signing_ca.rs | 11 ++++- trifid-api/trifid_api_migration/src/lib.rs | 10 ++-- ...m20230403_173431_create_table_networks.rs} | 23 ++++++++-- 6 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 trifid-api/src/routes/v1/networks.rs rename trifid-api/trifid_api_migration/src/{m20230402_232323_create_table_networks.rs => m20230403_173431_create_table_networks.rs} (56%) diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index 36a9ce5..b619472 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -1,4 +1,5 @@ pub mod auth; pub mod signup; pub mod totp_authenticators; -pub mod verify_totp_authenticators; \ No newline at end of file +pub mod verify_totp_authenticators; +pub mod networks; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs new file mode 100644 index 0000000..834dd0a --- /dev/null +++ b/trifid-api/src/routes/v1/networks.rs @@ -0,0 +1,46 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworksResponse { + pub data: Vec, + pub metadata: GetNetworksResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworksResponseData { + pub id: String, + pub cidr: String, + #[serde(rename = "organizationID")] + pub organization_id: String, + #[serde(rename = "signingCAID")] + pub signing_ca_id: String, + #[serde(rename = "createdAt")] + pub created_at: String, // 2023-03-22T18:55:47.009Z + pub name: String, + #[serde(rename = "lighthousesAsRelays")] + pub lighthouses_as_relays: bool +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworksResponseMetadata { + #[serde(rename = "totalCount")] + pub total_count: i64, + #[serde(rename = "hasNextPage")] + pub has_next_page: bool, + #[serde(rename = "hasPrevPage")] + pub has_prev_page: bool, + #[serde(default, skip_serializing_if = "is_none", rename = "prevCursor")] + pub prev_cursor: Option, + #[serde(default, skip_serializing_if = "is_none", rename = "nextCursor")] + pub next_cursor: Option, + #[serde(default, skip_serializing_if = "is_none")] + pub page: Option +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworksResponseMetadataPage { + pub count: i64, + pub start: i64 +} + +fn is_none(o: &Option) -> bool { o.is_none() } \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_entities/src/entity/network.rs index 93232d1..7d575e6 100644 --- a/trifid-api/trifid_api_entities/src/entity/network.rs +++ b/trifid-api/trifid_api_entities/src/entity/network.rs @@ -7,9 +7,14 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, + pub cidr: String, #[sea_orm(unique)] pub organization: String, - pub ip_block: String, + #[sea_orm(unique)] + pub signing_ca: String, + pub created_at: i64, + pub name: String, + pub lighthouses_as_relays: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] @@ -22,6 +27,14 @@ pub enum Relation { on_delete = "Cascade" )] Organization, + #[sea_orm( + belongs_to = "super::signing_ca::Entity", + from = "Column::SigningCa", + to = "super::signing_ca::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + SigningCa, } impl Related for Entity { @@ -30,4 +43,10 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::SigningCa.def() + } +} + impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/signing_ca.rs b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs index 2caba85..d0770a9 100644 --- a/trifid-api/trifid_api_entities/src/entity/signing_ca.rs +++ b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs @@ -17,6 +17,15 @@ pub struct Model { } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation {} +pub enum Relation { + #[sea_orm(has_one = "super::network::Entity")] + Network, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 3768bfc..35bee9d 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -6,12 +6,12 @@ pub mod m20230402_162601_create_table_users; pub mod m20230402_183515_create_table_magic_links; pub mod m20230402_213712_create_table_session_tokens; pub mod m20230402_232316_create_table_organizations; -pub mod m20230402_232323_create_table_networks; pub mod m20230402_233043_create_table_api_keys; pub mod m20230402_233047_create_table_api_keys_scopes; -mod m20230402_234025_create_table_totp_authenticators; -mod m20230403_002256_create_table_auth_tokens; -mod m20230403_142517_create_table_signing_cas; +pub mod m20230402_234025_create_table_totp_authenticators; +pub mod m20230403_002256_create_table_auth_tokens; +pub mod m20230403_142517_create_table_signing_cas; +pub mod m20230403_173431_create_table_networks; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -21,12 +21,12 @@ impl MigratorTrait for Migrator { Box::new(m20230402_183515_create_table_magic_links::Migration), Box::new(m20230402_213712_create_table_session_tokens::Migration), Box::new(m20230402_232316_create_table_organizations::Migration), - Box::new(m20230402_232323_create_table_networks::Migration), Box::new(m20230402_233043_create_table_api_keys::Migration), Box::new(m20230402_233047_create_table_api_keys_scopes::Migration), Box::new(m20230402_234025_create_table_totp_authenticators::Migration), Box::new(m20230403_002256_create_table_auth_tokens::Migration), Box::new(m20230403_142517_create_table_signing_cas::Migration), + Box::new(m20230403_173431_create_table_networks::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs b/trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs similarity index 56% rename from trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs rename to trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs index 668fdd4..a85fcd4 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_232323_create_table_networks.rs +++ b/trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs @@ -1,5 +1,6 @@ use sea_orm_migration::prelude::*; use crate::m20230402_232316_create_table_organizations::Organization; +use crate::m20230403_142517_create_table_signing_cas::SigningCA; #[derive(DeriveMigrationName)] pub struct Migration; @@ -11,15 +12,27 @@ impl MigrationTrait for Migration { Table::create() .table(Network::Table) .col(ColumnDef::new(Network::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Network::Cidr).string().not_null()) .col(ColumnDef::new(Network::Organization).string().not_null().unique_key()) - .col(ColumnDef::new(Network::IpBlock).string().not_null()) + .col(ColumnDef::new(Network::SigningCA).string().not_null().unique_key()) + .col(ColumnDef::new(Network::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Network::Name).string().not_null()) + .col(ColumnDef::new(Network::LighthousesAsRelays).boolean().not_null()) .foreign_key( ForeignKey::create() .from(Network::Table, Network::Organization) .to(Organization::Table, Organization::Id) .on_delete(ForeignKeyAction::Cascade) .on_update(ForeignKeyAction::Cascade) - ).to_owned() + ) + .foreign_key( + ForeignKey::create() + .from(Network::Table, Network::SigningCA) + .to(SigningCA::Table, SigningCA::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .to_owned() ).await } @@ -33,6 +46,10 @@ impl MigrationTrait for Migration { pub enum Network { Table, Id, + Cidr, Organization, - IpBlock + SigningCA, + CreatedAt, + Name, + LighthousesAsRelays } From f54d8a11a16d7fd2f321ac129df0236c1fb831b1 Mon Sep 17 00:00:00 2001 From: core Date: Mon, 3 Apr 2023 18:39:49 -0400 Subject: [PATCH 15/39] networks --- Cargo.lock | 3 + trifid-api/Cargo.toml | 5 +- trifid-api/src/auth_tokens.rs | 3 +- trifid-api/src/cursor.rs | 40 +++++ trifid-api/src/main.rs | 2 + trifid-api/src/routes/v1/networks.rs | 219 ++++++++++++++++++++++++++- 6 files changed, 262 insertions(+), 10 deletions(-) create mode 100644 trifid-api/src/cursor.rs diff --git a/Cargo.lock b/Cargo.lock index b6904c7..79b5ef3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3473,13 +3473,16 @@ version = "0.1.0" dependencies = [ "actix-request-identifier", "actix-web", + "base64 0.21.0", "chacha20poly1305", + "chrono", "hex", "log", "once_cell", "rand", "sea-orm", "serde", + "serde_json", "simple_logger", "toml 0.7.3", "totp-rs", diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index 25034a5..e80dbaa 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -10,9 +10,10 @@ actix-web = "4" # Web framework actix-request-identifier = "4" # Web framework serde = { version = "1", features = ["derive"] } # Serialization and deserialization +serde_json = "1.0.95" # Serialization and deserialization (cursors) once_cell = "1" # Config -toml = "0.7" # Config +toml = "0.7" # Config / Serialization and deserialization log = "0.4" # Logging simple_logger = "4" # Logging @@ -24,6 +25,8 @@ trifid_api_entities = { version = "0.1.0", path = "trifid_api_entities" } rand = "0.8" # Misc. hex = "0.4" # Misc. totp-rs = { version = "5.0.1", features = ["gen_secret", "otpauth"] } # Misc. +base64 = "0.21.0" # Misc. +chrono = "0.4.24" # Misc. trifid-pki = { version = "0.1.9" } # Cryptography chacha20poly1305 = "0.10.1" # Cryptography \ No newline at end of file diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs index efb48c4..ae89c91 100644 --- a/trifid-api/src/auth_tokens.rs +++ b/trifid-api/src/auth_tokens.rs @@ -12,7 +12,8 @@ use crate::timers::expired; pub enum TokenInfo { SessionToken(SessionTokenInfo), AuthToken(AuthTokenInfo), - ApiToken(ApiTokenInfo) + ApiToken(ApiTokenInfo), + NotPresent } pub struct SessionTokenInfo { diff --git a/trifid-api/src/cursor.rs b/trifid-api/src/cursor.rs new file mode 100644 index 0000000..8fb7335 --- /dev/null +++ b/trifid-api/src/cursor.rs @@ -0,0 +1,40 @@ +use std::error::Error; +use base64::Engine; +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Cursor { + pub page: u64 +} + +impl TryFrom for String { + type Error = Box; + + fn try_from(value: Cursor) -> Result { + // Serialize it to json + let json_str = serde_json::to_string(&value)?; + // Then base64-encode the json + let base64_str = base64::engine::general_purpose::STANDARD.encode(json_str); + Ok(base64_str) + } +} + +impl TryFrom for Cursor { + type Error = Box; + + fn try_from(value: String) -> Result { + if value.is_empty() { + // If empty, it's page 0 + return Ok(Cursor { + page: 0 + }) + } + // Base64-decode the value + let json_bytes = base64::engine::general_purpose::STANDARD.decode(value)?; + // Convert it into a string + let json_str = String::from_utf8(json_bytes)?; + // Deserialize it from json + let cursor = serde_json::from_str(&json_str)?; + Ok(cursor) + } +} \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 2c65ca2..2815883 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -17,6 +17,7 @@ pub mod tokens; pub mod timers; pub mod magic_link; pub mod auth_tokens; +pub mod cursor; pub struct AppState { pub conn: DatabaseConnection @@ -68,6 +69,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::totp_authenticators::totp_authenticators_request) .service(routes::v1::verify_totp_authenticators::verify_totp_authenticators_request) .service(routes::v1::auth::totp::totp_request) + .service(routes::v1::networks::get_networks) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 834dd0a..8af7f9d 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -1,4 +1,16 @@ use serde::{Serialize, Deserialize}; +use actix_web::{get, HttpRequest, HttpResponse}; +use actix_web::web::{Data, Query}; +use chacha20poly1305::consts::P1; +use chrono::{TimeZone, Utc}; +use log::error; +use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; +use crate::AppState; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, enforce_session, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use trifid_api_entities::entity::organization; +use trifid_api_entities::entity::network; +use crate::cursor::Cursor; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksResponse { @@ -15,7 +27,7 @@ pub struct GetNetworksResponseData { #[serde(rename = "signingCAID")] pub signing_ca_id: String, #[serde(rename = "createdAt")] - pub created_at: String, // 2023-03-22T18:55:47.009Z + pub created_at: String, // 2023-03-22T18:55:47.009Z, %Y-%m-%dT%H-%M-%S.%.3fZ pub name: String, #[serde(rename = "lighthousesAsRelays")] pub lighthouses_as_relays: bool @@ -24,23 +36,214 @@ pub struct GetNetworksResponseData { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksResponseMetadata { #[serde(rename = "totalCount")] - pub total_count: i64, + pub total_count: u64, #[serde(rename = "hasNextPage")] pub has_next_page: bool, #[serde(rename = "hasPrevPage")] pub has_prev_page: bool, - #[serde(default, skip_serializing_if = "is_none", rename = "prevCursor")] + #[serde(default, rename = "prevCursor")] pub prev_cursor: Option, - #[serde(default, skip_serializing_if = "is_none", rename = "nextCursor")] + #[serde(default, rename = "nextCursor")] pub next_cursor: Option, - #[serde(default, skip_serializing_if = "is_none")] + #[serde(default)] pub page: Option } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksResponseMetadataPage { - pub count: i64, - pub start: i64 + pub count: u64, + pub start: u64 } -fn is_none(o: &Option) -> bool { o.is_none() } \ No newline at end of file +fn u64_25() -> u64 { 25 } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworksQueryParams { + #[serde(default, rename = "includeCounts")] + pub include_counts: bool, + #[serde(default)] + pub cursor: String, + #[serde(default = "u64_25", rename = "pageSize")] + pub page_size: u64 +} + +#[get("/v1/networks")] +pub async fn get_networks(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with networks:list + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["networks:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the networks:list scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let cursor: Cursor = match opts.cursor.clone().try_into() { + Ok(r) => r, + Err(e) => { + error!("invalid cursor: {}", e); + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_INVALID_CURSOR".to_string(), + message: "The provided cursor was invalid, please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let network_pages = network::Entity::find().filter(network::Column::Organization.eq(org)).order_by_asc(network::Column::CreatedAt).paginate(&db.conn, opts.page_size); + + let total = match network_pages.num_items().await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let pages = match network_pages.num_pages().await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let models = match network_pages.fetch_page(cursor.page).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let models_mapped = models.iter().map(|u| { + GetNetworksResponseData { + id: u.id.clone(), + cidr: u.cidr.clone(), + organization_id: u.organization.clone(), + signing_ca_id: u.signing_ca.clone(), + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S.%.3fZ").to_string(), + name: u.name.clone(), + lighthouses_as_relays: u.lighthouses_as_relays, + } + }).collect(); + + HttpResponse::Ok().json(GetNetworksResponse { + data: models_mapped, + metadata: GetNetworksResponseMetadata { + total_count: total, + has_next_page: cursor.page != pages, + has_prev_page: cursor.page != 0, + prev_cursor: if cursor.page != 0 { + match (Cursor { page: cursor.page - 1 }).try_into() { + Ok(r) => Some(r), + Err(_) => None + } + } else { + None + }, + next_cursor: if cursor.page != pages { + match (Cursor { page: cursor.page + 1 }).try_into() { + Ok(r) => Some(r), + Err(_) => None + } + } else { + None + }, + page: if opts.include_counts { + Some(GetNetworksResponseMetadataPage { + count: opts.page_size, + start: opts.page_size * cursor.page, + }) + } else { None }, + }, + }) +} \ No newline at end of file From 8d4bd51b4e7615aec097c0de99695fe271cbc4ab Mon Sep 17 00:00:00 2001 From: core Date: Mon, 3 Apr 2023 21:53:14 -0400 Subject: [PATCH 16/39] networks and organizations --- Cargo.lock | 78 ++++--- trifid-api/Cargo.toml | 2 +- trifid-api/config.example.toml | 123 +++++++++++ trifid-api/src/config.rs | 8 +- trifid-api/src/crypto.rs | 27 +++ trifid-api/src/main.rs | 2 + trifid-api/src/routes/v1/mod.rs | 3 +- trifid-api/src/routes/v1/networks.rs | 13 +- trifid-api/src/routes/v1/organization.rs | 262 +++++++++++++++++++++++ 9 files changed, 480 insertions(+), 38 deletions(-) create mode 100644 trifid-api/config.example.toml create mode 100644 trifid-api/src/crypto.rs create mode 100644 trifid-api/src/routes/v1/organization.rs diff --git a/Cargo.lock b/Cargo.lock index 79b5ef3..4f37298 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,6 +216,31 @@ dependencies = [ "generic-array", ] +[[package]] +name = "aes" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -679,30 +704,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "chacha20" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" -dependencies = [ - "cfg-if", - "cipher", - "cpufeatures", -] - -[[package]] -name = "chacha20poly1305" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" -dependencies = [ - "aead", - "chacha20", - "cipher", - "poly1305", - "zeroize", -] - [[package]] name = "chrono" version = "0.4.24" @@ -727,7 +728,6 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", - "zeroize", ] [[package]] @@ -936,6 +936,15 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "ctrlc" version = "3.2.5" @@ -1418,6 +1427,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gloo-timers" version = "0.2.6" @@ -2201,11 +2220,12 @@ dependencies = [ ] [[package]] -name = "poly1305" -version = "0.8.0" +name = "polyval" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ + "cfg-if", "cpufeatures", "opaque-debug", "universal-hash", @@ -3473,8 +3493,8 @@ version = "0.1.0" dependencies = [ "actix-request-identifier", "actix-web", + "aes-gcm", "base64 0.21.0", - "chacha20poly1305", "chrono", "hex", "log", diff --git a/trifid-api/Cargo.toml b/trifid-api/Cargo.toml index e80dbaa..f3467c4 100644 --- a/trifid-api/Cargo.toml +++ b/trifid-api/Cargo.toml @@ -29,4 +29,4 @@ base64 = "0.21.0" # Misc. chrono = "0.4.24" # Misc. trifid-pki = { version = "0.1.9" } # Cryptography -chacha20poly1305 = "0.10.1" # Cryptography \ No newline at end of file +aes-gcm = "0.10.1" # Cryptography \ No newline at end of file diff --git a/trifid-api/config.example.toml b/trifid-api/config.example.toml new file mode 100644 index 0000000..975ec0f --- /dev/null +++ b/trifid-api/config.example.toml @@ -0,0 +1,123 @@ +########################## +# trifid-api config file # +########################## +# trifid-api, an open source reimplementation of the Defined Networking nebula management server. +# Copyright (C) 2023 c0repwn3r +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Please read this file in it's entirety to learn what options you do or don't need to change +# to get a functional trifid-api instance. + +#### [database] #### +# Options related to the PostgreSQL database connection. +[database] +# The PostgreSQL connection URL to connect to the database. +# Example: postgres://username:password@ip:port/database-name. +# The database provided must exist. Database migrations will be run automatically upon database startup. +# Url. Required. +url = "your-database-url-here" + +# The maximum number of connections that will be established to the database. +# This will effectively mean the amount of requests that trifid-api can process in parallel, as almost every +# request handler acquires a connection from the pool. +# Integer. Optional. Default: 100 +# max_connections = 100 + +# The minimum number of connections that will be established to the database. +# At least this number of connections will be created and kept idle until needed. If requests have a lot of latency +# due to acquiring connections from the database, raise this number. +# Integer. Optional. Default = 5 +# min_connections = 5 + +# The maximum amount of time (in seconds) that the database pool will wait in order to connect to the database. +# After this amount of time, the connection will return an error and trifid-api will exit. If you have a very high-latency +# database connection, raise this number. +# Integer. Optional. Default = 8 +# connect_timeout = 8 + +# The maximum amount of time (in seconds) that the database pool will wait in order to acquire a connection from the database pool. +# After this amount of time, the connection will return an error and trifid-api will exit. If you have a very high-latency +# database connection, raise this number. +# Integer. Optional. Default = 8 +# acquire_timeout = 8 + +# The maximum amount of time (in seconds) that a database connection will remain idle before the connection is closed. +# This only applies if closing this connection would not bring the number of connections below min_connections. +# Unless you are handling thousands of requests per second, you probably don't need to change this value. +# Integer. Optional. Default = 8 +# idle_timeout = 8 + +# The maximum amount of time (in seconds) that a database connection will remain active before it is closed and replaced with a new connection. +# It is unlikely you ever need to change this value, unless your database takes 5 or more seconds per query, in which case you +# need a better database. +# Integer. Optional. Default = 8 +# max_lifetime = 8 + +# Should sqlx query logging be enabled? +# Disable this if you are tired of the constant query spam in the logs. Enable for debugging. +# Boolean. Optional. Default = true +# sqlx_logging = true + +#### [server] #### +# Configure options for the trifid-api HTTP server. +[server] +# What IPs and ports should the trifid-api server listen on? +# This may need to be changed if you want to bind on a different port or interface. +# SocketAddr. Optional. Default = 0.0.0.0:8080 (all IPs, port 8080) +# bind = "0.0.0.0:8080" + +#### [tokens] #### +# Configure options related to the various tokens that may be issued by the trifid-api server. +[tokens] +# How long (in seconds) should magic link tokens be valid for? +# This controls how long links sent to user's email addresses will remain valid for login. +# The default of 3600 (1 hour) is a sane default and you likely do not need to change this. +# Integer. Optional. Default = 3600 +# magic_link_expiry_time_seconds = 3600 # 1 hour + +# How long (in seconds) should session tokens be valid for? +# This controls how long it will take before a user will need to re-log in with a magic link, if they do not explicitly +# log out first. +# The default of 15780000 (6 months) is a sane default and you likely do not need to change this. +# Integer. Optional. Default = 15780000 +# session_token_expiry_time_seconds = 15780000 # 6 months + +# How long (in seconds) should TOTP setup tokens be valid for? +# This controls how long a user will have to setup TOTP after starting the setup process before the token is invalidated +# and they need to try again. +# The default of 600 (10 minutes) is a sane default and you likely do not need to change this. +# Integer. Optional. Default = 600 +# totp_setup_timeout_time_seconds = 600 # 10 minutes + +# How long (in seconds) should MFA auth tokens be valid for? +# This controls how long a user will remain logged in before they need to re-input their 2FA code.. +# The default of 600 (10 minutes) is a sane default and you likely do not need to change this. +# Integer. Optional. Default = 600 +# mfa_tokens_expiry_time_seconds = 600 # 10 minutes + +#### [crypto] #### +# Configure settings related to the cryptography used inside trifid-api +[crypto] + +# The per-instance data encryption key to protect sensitive data in the instance. +# YOU ABSOLUTELY NEED TO CHANGE THIS. If you don't change anything else in this file, this should be the one thing you change. +# This should be a 32-byte hex value. Generate it with `openssl rand -hex 32`, or any other tool of your choice. +# If you get "InvalidLength" errors while trying to do anything involving organizations, that indicates that this +# value was improperly generated. +# +# ------- WARNING ------- +# Do not change this value in a production instance. It will make existing data inaccessible until changed back. +# ------- WARNING ------- +data-key = "edd600bcebea461381ea23791b6967c8667e12827ac8b94dc022f189a5dc59a2" \ No newline at end of file diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 9b454d7..6e5b4c0 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -26,7 +26,8 @@ pub static CONFIG: Lazy = Lazy::new(|| { pub struct TrifidConfig { pub database: TrifidConfigDatabase, pub server: TrifidConfigServer, - pub tokens: TrifidConfigTokens + pub tokens: TrifidConfigTokens, + pub crypto: TrifidConfigCryptography } #[derive(Serialize, Deserialize, Debug)] @@ -66,6 +67,11 @@ pub struct TrifidConfigTokens { pub mfa_tokens_expiry_time_seconds: u64 } +#[derive(Serialize, Deserialize, Debug)] +pub struct TrifidConfigCryptography { + pub data_encryption_key: String +} + fn max_connections_default() -> u32 { 100 } fn min_connections_default() -> u32 { 5 } fn time_defaults() -> u64 { 8 } diff --git a/trifid-api/src/crypto.rs b/trifid-api/src/crypto.rs new file mode 100644 index 0000000..a8b7313 --- /dev/null +++ b/trifid-api/src/crypto.rs @@ -0,0 +1,27 @@ +use std::error::Error; +use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; +use aes_gcm::aead::{Aead, Payload}; +use rand::Rng; +use trifid_pki::rand_core::OsRng; +use crate::config::TrifidConfig; + +pub fn get_cipher_from_config(config: &TrifidConfig) -> Result> { + let key_slice = hex::decode(&config.crypto.data_encryption_key)?; + Ok(Aes256Gcm::new_from_slice(&key_slice)?) +} + +pub fn encrypt_with_nonce(plaintext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result, aes_gcm::Error> { + let nonce = Nonce::from_slice(&nonce); + let ciphertext = cipher.encrypt(nonce, plaintext)?; + Ok(ciphertext) +} + +pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result, aes_gcm::Error> { + let nonce = Nonce::from_slice(&nonce); + let plaintext = cipher.decrypt(nonce, Payload::from(ciphertext))?; + Ok(plaintext) +} + +pub fn generate_random_iv() -> [u8; 12] { + OsRng.gen() +} \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 2815883..4c19649 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -18,6 +18,7 @@ pub mod timers; pub mod magic_link; pub mod auth_tokens; pub mod cursor; +pub mod crypto; pub struct AppState { pub conn: DatabaseConnection @@ -70,6 +71,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::verify_totp_authenticators::verify_totp_authenticators_request) .service(routes::v1::auth::totp::totp_request) .service(routes::v1::networks::get_networks) + .service(routes::v1::organization::create_org_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index b619472..248e152 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -2,4 +2,5 @@ pub mod auth; pub mod signup; pub mod totp_authenticators; pub mod verify_totp_authenticators; -pub mod networks; \ No newline at end of file +pub mod networks; +pub mod organization; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 8af7f9d..cb7b169 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -1,7 +1,6 @@ use serde::{Serialize, Deserialize}; use actix_web::{get, HttpRequest, HttpResponse}; use actix_web::web::{Data, Query}; -use chacha20poly1305::consts::P1; use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; @@ -204,23 +203,25 @@ pub async fn get_networks(opts: Query, req_info: HttpReq }); } }; - let models_mapped = models.iter().map(|u| { + let models_mapped: Vec = models.iter().map(|u| { GetNetworksResponseData { id: u.id.clone(), cidr: u.cidr.clone(), organization_id: u.organization.clone(), signing_ca_id: u.signing_ca.clone(), - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S.%.3fZ").to_string(), + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), name: u.name.clone(), lighthouses_as_relays: u.lighthouses_as_relays, } }).collect(); + let count = models_mapped.len() as u64; + HttpResponse::Ok().json(GetNetworksResponse { data: models_mapped, metadata: GetNetworksResponseMetadata { total_count: total, - has_next_page: cursor.page != pages, + has_next_page: cursor.page+1 != pages, has_prev_page: cursor.page != 0, prev_cursor: if cursor.page != 0 { match (Cursor { page: cursor.page - 1 }).try_into() { @@ -230,7 +231,7 @@ pub async fn get_networks(opts: Query, req_info: HttpReq } else { None }, - next_cursor: if cursor.page != pages { + next_cursor: if cursor.page+1 != pages { match (Cursor { page: cursor.page + 1 }).try_into() { Ok(r) => Some(r), Err(_) => None @@ -240,7 +241,7 @@ pub async fn get_networks(opts: Query, req_info: HttpReq }, page: if opts.include_counts { Some(GetNetworksResponseMetadataPage { - count: opts.page_size, + count, start: opts.page_size * cursor.page, }) } else { None }, diff --git a/trifid-api/src/routes/v1/organization.rs b/trifid-api/src/routes/v1/organization.rs new file mode 100644 index 0000000..70f791f --- /dev/null +++ b/trifid-api/src/routes/v1/organization.rs @@ -0,0 +1,262 @@ +// !! !! !! THIS IS NOT A DN-COMPATIBLE API! !! !! !! +// The organization create API has not yet been reverse engineered. This endpoint has nothing to do with the original API +// and is a complete fabrication for trifid. +// Help us out! Reverse engineer the actual org create mechanism and get us back to 100% parity! +// - trifid maintainers + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use actix_web::{HttpRequest, HttpResponse}; +use actix_web::web::{Data, Json}; +use serde::{Serialize, Deserialize}; +use crate::AppState; +use actix_web::post; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails, serialize_x25519_private}; +use trifid_pki::ed25519_dalek::SigningKey; +use trifid_pki::rand_core::OsRng; +use trifid_api_entities::entity::{network, organization, signing_ca}; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::config::CONFIG; +use crate::crypto::{encrypt_with_nonce, generate_random_iv, get_cipher_from_config}; +use crate::error::{APIError, APIErrorsResponse}; +use crate::tokens::random_id; + +#[derive(Serialize, Deserialize)] +pub struct OrgCreateRequest { + pub cidr: String +} + +#[derive(Serialize, Deserialize)] +pub struct OrgCreateResponse { + pub organization: String, + pub ca: String, + pub network: String +} + +#[post("/v1/organization")] +pub async fn create_org_request(req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you need to be a fully authenticated user + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + } + ], + }) + } + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(&user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if org.is_some() { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_USER_ALREADY_OWNS_ORG".to_string(), + message: "This user already owns an organization".to_string(), + path: None, + } + ], + }) + } + + let org = organization::Model { + id: random_id("org"), + name: format!("{}'s Organization", user.email), + owner: user.id.clone(), + }; + + // Generate the CA keypair + let private_key = SigningKey::generate(&mut OsRng); + let public_key = private_key.verifying_key(); + + let mut cert = NebulaCertificate { + details: NebulaCertificateDetails { + name: format!("{} Signing CA", org.name), + ips: vec![], + subnets: vec![], + groups: vec![], + not_before: SystemTime::now(), + not_after: SystemTime::now() + Duration::from_secs(31536000 * 3), // 3 years + public_key: public_key.to_bytes(), + is_ca: true, + issuer: "".to_string(), // Self-signed certificate! No issuer present + }, + signature: vec![], + }; + // Self-sign the CA certificate + match cert.sign(&private_key) { + Ok(_) => (), + Err(e) => { + error!("[security] certificate signature error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_CERT_SIGNING_ERROR".to_string(), + message: "There was an error signing the Certificate Authority on the server. Please try again later.".to_string(), + path: None, + } + ] + }); + } + } + + // PEM-encode the CA key + let ca_key_pem = serialize_x25519_private(&private_key.to_keypair_bytes()); + // PEM-encode the CA cert + let ca_cert_pem = match cert.serialize_to_pem() { + Ok(pem) => pem, + Err(e) => { + error!("[security] certificate encoding error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_CERT_ENCODING_ERROR".to_string(), + message: "There was an error encoding the certificate on the server. Please try again later.".to_string(), + path: None, + } + ] + }); + } + }; + + let iv = generate_random_iv(); // Generate a randomized IV to use for key encryption + let iv_hex = hex::encode(iv); + + let cipher = match get_cipher_from_config(&CONFIG) { + Ok(pem) => pem, + Err(e) => { + error!("[security] cipher fetch error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_CIPHER_ERROR".to_string(), + message: "There was an error encrypting the organization data. Please try again later.".to_string(), + path: None, + } + ] + }); + } + }; + + let ca_key_encrypted = match encrypt_with_nonce(&ca_key_pem, iv, &cipher) { + Ok(key) => hex::encode(key), + Err(e) => { + error!("[security] certificate encoding error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_CERT_ENCODING_ERROR".to_string(), + message: "There was an error encoding the certificate on the server. Please try again later.".to_string(), + path: None, + } + ] + }); + } + }; + + let ca_crt = hex::encode(ca_cert_pem); + + let signing_ca = signing_ca::Model { + id: random_id("ca"), + organization: org.id.clone(), + cert: ca_key_encrypted, + key: ca_crt, + expires: cert.details.not_after.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + nonce: iv_hex, + }; + + let network_model = network::Model { + id: random_id("network"), + cidr: req.cidr.clone(), + organization: org.id.clone(), + signing_ca: signing_ca.id.clone(), + created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + name: "Network1".to_string(), + lighthouses_as_relays: true, + }; + + let new_org_id = org.id.clone(); + let new_signing_ca_id = signing_ca.id.clone(); + let new_network_id = network_model.id.clone(); + + let org_active_model = org.into_active_model(); + let signing_ca_active_model = signing_ca.into_active_model(); + let network_active_model = network_model.into_active_model(); + + match org_active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + } + match signing_ca_active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + } + match network_active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + } + + HttpResponse::Ok().json(OrgCreateResponse { + organization: new_org_id, + ca: new_signing_ca_id, + network: new_network_id, + }) +} \ No newline at end of file From 359f5be936ec0d6ac8835a472cf5de30d1c5d076 Mon Sep 17 00:00:00 2001 From: core Date: Mon, 3 Apr 2023 21:53:32 -0400 Subject: [PATCH 17/39] cargo-fix --- trifid-api/src/auth_tokens.rs | 2 +- trifid-api/src/main.rs | 6 +++--- trifid-api/src/routes/v1/auth/magic_link.rs | 2 +- trifid-api/src/routes/v1/auth/totp.rs | 6 +++--- trifid-api/src/routes/v1/networks.rs | 2 +- trifid-api/src/routes/v1/organization.rs | 2 +- trifid-api/src/routes/v1/signup.rs | 2 +- trifid-api/src/routes/v1/totp_authenticators.rs | 6 +++--- trifid-api/src/routes/v1/verify_totp_authenticators.rs | 4 ++-- 9 files changed, 16 insertions(+), 16 deletions(-) diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs index ae89c91..4f249a6 100644 --- a/trifid-api/src/auth_tokens.rs +++ b/trifid-api/src/auth_tokens.rs @@ -1,6 +1,6 @@ use std::error::Error; use actix_web::HttpRequest; -use log::debug; + use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; use crate::tokens::get_token_type; use trifid_api_entities::entity::{auth_token, session_token}; diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 4c19649..f04b3ae 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -1,10 +1,10 @@ use std::error::Error; use std::time::Duration; use actix_request_identifier::RequestIdentifier; -use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfig}}; -use log::{error, info, Level}; +use actix_web::{App, HttpResponse, HttpServer, web::{Data, JsonConfig}}; +use log::{info, Level}; use sea_orm::{ConnectOptions, Database, DatabaseConnection}; -use serde::{Serialize, Deserialize}; + use trifid_api_migration::{Migrator, MigratorTrait}; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; diff --git a/trifid-api/src/routes/v1/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs index c5ae562..b42f1d7 100644 --- a/trifid-api/src/routes/v1/auth/magic_link.rs +++ b/trifid-api/src/routes/v1/auth/magic_link.rs @@ -1,7 +1,7 @@ use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; use serde::{Serialize, Deserialize}; use trifid_api_entities::entity::user::Entity as UserEntity; use trifid_api_entities::entity::user; diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs index bc95d2c..b50aa40 100644 --- a/trifid-api/src/routes/v1/auth/totp.rs +++ b/trifid-api/src/routes/v1/auth/totp.rs @@ -7,8 +7,8 @@ use crate::AppState; use crate::auth_tokens::{enforce_session, TokenInfo}; use crate::error::{APIError, APIErrorsResponse}; use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait}; -use sea_orm::ActiveValue::Set; -use totp_rs::{Algorithm, Secret, TOTP}; + +use totp_rs::{Secret, TOTP}; use trifid_api_entities::entity::auth_token; use crate::config::CONFIG; use crate::timers::expires_in_seconds; @@ -91,7 +91,7 @@ pub async fn totp_request(req: Json, req_data: HttpRequest, db: Dat } }; - let secret = Secret::Encoded(auther.secret.clone()); + let _secret = Secret::Encoded(auther.secret.clone()); let totpmachine = match TOTP::from_url(auther.url.clone()) { Ok(m) => m, Err(e) => { diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index cb7b169..7066710 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -5,7 +5,7 @@ use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; use crate::AppState; -use crate::auth_tokens::{enforce_2fa, enforce_api_token, enforce_session, TokenInfo}; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::error::{APIError, APIErrorsResponse}; use trifid_api_entities::entity::organization; use trifid_api_entities::entity::network; diff --git a/trifid-api/src/routes/v1/organization.rs b/trifid-api/src/routes/v1/organization.rs index 70f791f..af72249 100644 --- a/trifid-api/src/routes/v1/organization.rs +++ b/trifid-api/src/routes/v1/organization.rs @@ -16,7 +16,7 @@ use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails, serialize_x2 use trifid_pki::ed25519_dalek::SigningKey; use trifid_pki::rand_core::OsRng; use trifid_api_entities::entity::{network, organization, signing_ca}; -use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::auth_tokens::{enforce_2fa, TokenInfo}; use crate::config::CONFIG; use crate::crypto::{encrypt_with_nonce, generate_random_iv, get_cipher_from_config}; use crate::error::{APIError, APIErrorsResponse}; diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs index 2dcaccd..e619b20 100644 --- a/trifid-api/src/routes/v1/signup.rs +++ b/trifid-api/src/routes/v1/signup.rs @@ -1,7 +1,7 @@ use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, DatabaseConnection, EntityTrait, IntoActiveModel, QueryFilter}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; use serde::{Serialize, Deserialize}; use trifid_api_entities::entity::user::Entity as UserEntity; use trifid_api_entities::entity::user; diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index 955d02b..430c8c2 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -5,12 +5,12 @@ use log::error; use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; use totp_rs::{Algorithm, Secret, TOTP}; use crate::AppState; -use crate::auth_tokens::{enforce_2fa, enforce_session, TokenInfo}; +use crate::auth_tokens::{enforce_session, TokenInfo}; use crate::error::{APIError, APIErrorsResponse}; use trifid_api_entities::entity::totp_authenticator; use crate::config::CONFIG; use crate::timers::expires_in_seconds; -use crate::tokens::{random_id, random_token}; +use crate::tokens::{random_token}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpAuthenticatorsRequest {} @@ -33,7 +33,7 @@ pub struct TotpAuthenticatorsResponse { } #[post("/v1/totp-authenticators")] -pub async fn totp_authenticators_request(db: Data, req_data: HttpRequest, req: Json) -> HttpResponse { +pub async fn totp_authenticators_request(db: Data, req_data: HttpRequest, _req: Json) -> HttpResponse { // require a user session let session_token = match enforce_session(&req_data, &db.conn).await { Ok(r) => { diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs index 9b2f2b8..bd979fc 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticators.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -8,7 +8,7 @@ use crate::auth_tokens::{enforce_session, TokenInfo}; use crate::error::{APIError, APIErrorsResponse}; use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait}; use sea_orm::ActiveValue::Set; -use totp_rs::{Algorithm, Secret, TOTP}; +use totp_rs::{Secret, TOTP}; use trifid_api_entities::entity::auth_token; use crate::config::CONFIG; use crate::timers::expires_in_seconds; @@ -104,7 +104,7 @@ pub async fn verify_totp_authenticators_request(req: Json m, Err(e) => { From 6d978260504178c48ffdc4f159ecb4081aca42fa Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 4 Apr 2023 09:31:41 -0400 Subject: [PATCH 18/39] get specific network --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/networks.rs | 89 +++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 3 deletions(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index f04b3ae..32b683f 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -72,6 +72,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::auth::totp::totp_request) .service(routes::v1::networks::get_networks) .service(routes::v1::organization::create_org_request) + .service(routes::v1::networks::get_network_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 7066710..19f868e 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -1,6 +1,6 @@ use serde::{Serialize, Deserialize}; use actix_web::{get, HttpRequest, HttpResponse}; -use actix_web::web::{Data, Query}; +use actix_web::web::{Data, Path, Query}; use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; @@ -26,7 +26,7 @@ pub struct GetNetworksResponseData { #[serde(rename = "signingCAID")] pub signing_ca_id: String, #[serde(rename = "createdAt")] - pub created_at: String, // 2023-03-22T18:55:47.009Z, %Y-%m-%dT%H-%M-%S.%.3fZ + pub created_at: String, // 2023-03-22T18:55:47.009Z, %Y-%m-%dT%H-%M-%S%.3fZ pub name: String, #[serde(rename = "lighthousesAsRelays")] pub lighthouses_as_relays: bool @@ -247,4 +247,87 @@ pub async fn get_networks(opts: Query, req_info: HttpReq } else { None }, }, }) -} \ No newline at end of file +} + +#[get("/v1/networks/{network_id}")] +pub async fn get_network_request(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with networks:list + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["networks:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the networks:read scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let network: Option = match network::Entity::find().filter(network::Column::Id.eq(net.into_inner())).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(network) = network { + HttpResponse::Ok().json(GetNetworkResponse { + data: GetNetworksResponseData { + id: network.id, + cidr: network.cidr, + organization_id: network.organization, + signing_ca_id: network.signing_ca, + created_at: Utc.timestamp_opt(network.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + name: network.name, + lighthouses_as_relays: network.lighthouses_as_relays, + }, + metadata: GetNetworkResponseMetadata {}, + }) + } else { + HttpResponse::NotFound().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_MISSING_NETWORK".to_string(), + message: "Network does not exist".to_string(), + path: None, + } + ], + }) + + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworkResponse { + pub data: GetNetworksResponseData, + pub metadata: GetNetworkResponseMetadata +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetNetworkResponseMetadata {} \ No newline at end of file From 2afe00857321427a9fc85db4d70c88e1370ad796 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 4 Apr 2023 09:44:19 -0400 Subject: [PATCH 19/39] add some migs --- trifid-api/src/routes/v1/auth/magic_link.rs | 2 + trifid-api/trifid_api_migration/src/lib.rs | 4 ++ .../m20230404_133809_create_table_roles.rs | 41 ++++++++++++++++ ...0404_133813_create_table_firewall_rules.rs | 48 +++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs diff --git a/trifid-api/src/routes/v1/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs index b42f1d7..0dac832 100644 --- a/trifid-api/src/routes/v1/auth/magic_link.rs +++ b/trifid-api/src/routes/v1/auth/magic_link.rs @@ -1,3 +1,5 @@ + + use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index 35bee9d..cd1b240 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -12,6 +12,8 @@ pub mod m20230402_234025_create_table_totp_authenticators; pub mod m20230403_002256_create_table_auth_tokens; pub mod m20230403_142517_create_table_signing_cas; pub mod m20230403_173431_create_table_networks; +mod m20230404_133809_create_table_roles; +mod m20230404_133813_create_table_firewall_rules; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -27,6 +29,8 @@ impl MigratorTrait for Migrator { Box::new(m20230403_002256_create_table_auth_tokens::Migration), Box::new(m20230403_142517_create_table_signing_cas::Migration), Box::new(m20230403_173431_create_table_networks::Migration), + Box::new(m20230404_133809_create_table_roles::Migration), + Box::new(m20230404_133813_create_table_firewall_rules::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs new file mode 100644 index 0000000..b478620 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs @@ -0,0 +1,41 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(Role::Table) + .col(ColumnDef::new(Role::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Role::Name).string().not_null()) + .col(ColumnDef::new(Role::Description).string().not_null()) + .col(ColumnDef::new(Role::Organization).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(Role::Table, Role::Organization ) + ) + ) + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + + manager + .drop_table(Table::drop().table(Post::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum Role { + Table, + Id, + Name, + Description, + Organization +} diff --git a/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs b/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs new file mode 100644 index 0000000..b058244 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs @@ -0,0 +1,48 @@ +use sea_orm_migration::prelude::*; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + + manager + .create_table( + Table::create() + .table(Post::Table) + .if_not_exists() + .col( + ColumnDef::new(Post::Id) + .integer() + .not_null() + .auto_increment() + .primary_key(), + ) + .col(ColumnDef::new(Post::Title).string().not_null()) + .col(ColumnDef::new(Post::Text).string().not_null()) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + todo!(); + + manager + .drop_table(Table::drop().table(Post::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +enum Post { + Table, + Id, + Title, + Text, +} From 05704ffc11310a74ce7e88e05af638c5fef56d9d Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 4 Apr 2023 09:56:05 -0400 Subject: [PATCH 20/39] licensing --- trifid-api/src/auth_tokens.rs | 16 ++++++++++ trifid-api/src/config.rs | 16 ++++++++++ trifid-api/src/crypto.rs | 16 ++++++++++ trifid-api/src/cursor.rs | 16 ++++++++++ trifid-api/src/error.rs | 16 ++++++++++ trifid-api/src/magic_link.rs | 16 ++++++++++ trifid-api/src/main.rs | 16 ++++++++++ trifid-api/src/routes/v1/auth/magic_link.rs | 20 ++++++++++++- trifid-api/src/routes/v1/auth/totp.rs | 20 +++++++++++++ .../src/routes/v1/auth/verify_magic_link.rs | 20 +++++++++++++ trifid-api/src/routes/v1/networks.rs | 30 +++++++++++++++++-- trifid-api/src/routes/v1/organization.rs | 24 +++++++++++---- trifid-api/src/routes/v1/signup.rs | 20 +++++++++++++ .../src/routes/v1/totp_authenticators.rs | 20 +++++++++++++ .../routes/v1/verify_totp_authenticators.rs | 20 +++++++++++++ trifid-api/src/timers.rs | 16 ++++++++++ trifid-api/src/tokens.rs | 16 ++++++++++ 17 files changed, 309 insertions(+), 9 deletions(-) diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs index 4f249a6..f548044 100644 --- a/trifid-api/src/auth_tokens.rs +++ b/trifid-api/src/auth_tokens.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::error::Error; use actix_web::HttpRequest; diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 6e5b4c0..e3560fb 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::fs; use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use log::error; diff --git a/trifid-api/src/crypto.rs b/trifid-api/src/crypto.rs index a8b7313..9d8dc63 100644 --- a/trifid-api/src/crypto.rs +++ b/trifid-api/src/crypto.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::error::Error; use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; use aes_gcm::aead::{Aead, Payload}; diff --git a/trifid-api/src/cursor.rs b/trifid-api/src/cursor.rs index 8fb7335..91f0a07 100644 --- a/trifid-api/src/cursor.rs +++ b/trifid-api/src/cursor.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::error::Error; use base64::Engine; use serde::{Serialize, Deserialize}; diff --git a/trifid-api/src/error.rs b/trifid-api/src/error.rs index 4659084..4efa931 100644 --- a/trifid-api/src/error.rs +++ b/trifid-api/src/error.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use actix_web::error::{JsonPayloadError, PayloadError}; use serde::{Serialize, Deserialize}; diff --git a/trifid-api/src/magic_link.rs b/trifid-api/src/magic_link.rs index a4b57e7..19b6ff8 100644 --- a/trifid-api/src/magic_link.rs +++ b/trifid-api/src/magic_link.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::error::Error; use log::info; diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 32b683f..a53e854 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::error::Error; use std::time::Duration; use actix_request_identifier::RequestIdentifier; diff --git a/trifid-api/src/routes/v1/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs index 0dac832..42ea473 100644 --- a/trifid-api/src/routes/v1/auth/magic_link.rs +++ b/trifid-api/src/routes/v1/auth/magic_link.rs @@ -1,4 +1,22 @@ - +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/auth/magic-link t+parity:full t+type:reverse_engineered t+status:done +// This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs index b50aa40..5c1db57 100644 --- a/trifid-api/src/routes/v1/auth/totp.rs +++ b/trifid-api/src/routes/v1/auth/totp.rs @@ -1,3 +1,23 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/auth/totp t+parity:full t+type:reverse_engineered t+status:done +// This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. + use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; use log::{debug, error}; diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs index d5eb770..bf5f418 100644 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -1,3 +1,23 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/auth/verify-magic-link t+parity:full t+type:reverse_engineered t+status:done +// This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. + use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 19f868e..c28ccd7 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -1,3 +1,27 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#GET /v1/networks t+parity:full t+type:documented t+status:done +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// +//#GET /v1/networks/{network_id} t+parity:full t+type:documented t+status:done +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. + use serde::{Serialize, Deserialize}; use actix_web::{get, HttpRequest, HttpResponse}; use actix_web::web::{Data, Path, Query}; @@ -280,7 +304,7 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D ], }) } - + let network: Option = match network::Entity::find().filter(network::Column::Id.eq(net.into_inner())).one(&db.conn).await { Ok(r) => r, Err(e) => { @@ -296,7 +320,7 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D }); } }; - + if let Some(network) = network { HttpResponse::Ok().json(GetNetworkResponse { data: GetNetworksResponseData { @@ -320,7 +344,7 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D } ], }) - + } } diff --git a/trifid-api/src/routes/v1/organization.rs b/trifid-api/src/routes/v1/organization.rs index af72249..ddb8373 100644 --- a/trifid-api/src/routes/v1/organization.rs +++ b/trifid-api/src/routes/v1/organization.rs @@ -1,8 +1,22 @@ -// !! !! !! THIS IS NOT A DN-COMPATIBLE API! !! !! !! -// The organization create API has not yet been reverse engineered. This endpoint has nothing to do with the original API -// and is a complete fabrication for trifid. -// Help us out! Reverse engineer the actual org create mechanism and get us back to 100% parity! -// - trifid maintainers +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/organization t+parity:none t+type:fabricated t+status:done t+status:want-reveng +// This is NOT a DN-compatible API. The organization create API has not yet been reverse engineered. This endpoint is a complete fabrication of trifid-api. +// While this endpoint is considered done, help is wanted with reverse engineering the original API. Major features should not be added or removed unless it is replacing this endpoint with the correct, DN-compatible endpoint. use std::time::{Duration, SystemTime, UNIX_EPOCH}; use actix_web::{HttpRequest, HttpResponse}; diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs index e619b20..1245805 100644 --- a/trifid-api/src/routes/v1/signup.rs +++ b/trifid-api/src/routes/v1/signup.rs @@ -1,3 +1,23 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/signup t+parity:full t+type:reverse_engineered t+status:done +// This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. + use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; use log::error; diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index 430c8c2..876a1f4 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -1,3 +1,23 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/totp-authenticators t+parity:full t+type:reverse_engineered t+status:done +// This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. + use serde::{Serialize, Deserialize}; use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs index bd979fc..db9bbde 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticators.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -1,3 +1,23 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/verify-totp-authenticators t+parity:full t+type:reverse_engineered t+status:done +// This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. + use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; use log::{debug, error}; diff --git a/trifid-api/src/timers.rs b/trifid-api/src/timers.rs index 5a77b83..02b9799 100644 --- a/trifid-api/src/timers.rs +++ b/trifid-api/src/timers.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use std::time::{Duration, SystemTime, UNIX_EPOCH}; pub fn expires_in_seconds(seconds: u64) -> u64 { diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs index e441f9a..2ab6afa 100644 --- a/trifid-api/src/tokens.rs +++ b/trifid-api/src/tokens.rs @@ -1,3 +1,19 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + use actix_web::http::header::HeaderValue; use rand::Rng; From b8d2afe5c32f2c05b70774ed42dd3549e05dedd8 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 4 Apr 2023 10:14:26 -0400 Subject: [PATCH 21/39] add roles and firewall rules --- .../m20230404_133809_create_table_roles.rs | 17 +++-- ...0404_133813_create_table_firewall_rules.rs | 63 ++++++++++--------- 2 files changed, 42 insertions(+), 38 deletions(-) diff --git a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs index b478620..93dc9ad 100644 --- a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs +++ b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs @@ -1,4 +1,5 @@ use sea_orm_migration::prelude::*; +use crate::m20230402_232316_create_table_organizations::Organization; #[derive(DeriveMigrationName)] pub struct Migration; @@ -15,18 +16,16 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Role::Organization).string().not_null()) .foreign_key( ForeignKey::create() - .from(Role::Table, Role::Organization ) - ) - ) + .from(Role::Table, Role::Organization) + .to(Organization::Table, Organization::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ).to_owned() + ).await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Replace the sample below with your own migration scripts - todo!(); - - manager - .drop_table(Table::drop().table(Post::Table).to_owned()) - .await + manager.drop_table(Table::drop().table(Role::Table).to_owned()).await } } diff --git a/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs b/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs index b058244..a44f3b2 100644 --- a/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs +++ b/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs @@ -1,4 +1,5 @@ use sea_orm_migration::prelude::*; +use crate::m20230404_133809_create_table_roles::Role; #[derive(DeriveMigrationName)] pub struct Migration; @@ -6,43 +7,47 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Replace the sample below with your own migration scripts - todo!(); - - manager - .create_table( - Table::create() - .table(Post::Table) - .if_not_exists() - .col( - ColumnDef::new(Post::Id) - .integer() - .not_null() - .auto_increment() - .primary_key(), - ) - .col(ColumnDef::new(Post::Title).string().not_null()) - .col(ColumnDef::new(Post::Text).string().not_null()) - .to_owned(), - ) - .await + manager.create_table( + Table::create() + .table(FirewallRule::Table) + .col(ColumnDef::new(FirewallRule::Id).string().not_null().primary_key()) + .col(ColumnDef::new(FirewallRule::Role).string().not_null()) + .col(ColumnDef::new(FirewallRule::Protocol).string().not_null()) + .col(ColumnDef::new(FirewallRule::Description).string().not_null()) + .col(ColumnDef::new(FirewallRule::AllowedRoleID).string().null()) + .col(ColumnDef::new(FirewallRule::PortRangeFrom).integer().not_null()) + .col(ColumnDef::new(FirewallRule::PortRangeTo).integer().not_null()) + .foreign_key( + ForeignKey::create() + .from(FirewallRule::Table, FirewallRule::Role) + .to(Role::Table, Role::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .foreign_key( + ForeignKey::create() + .from(FirewallRule::Table, FirewallRule::AllowedRoleID) + .to(Role::Table, Role::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ).to_owned() + ).await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - // Replace the sample below with your own migration scripts - todo!(); - - manager - .drop_table(Table::drop().table(Post::Table).to_owned()) - .await + manager.drop_table(Table::drop().table(FirewallRule::Table).to_owned()).await } } /// Learn more at https://docs.rs/sea-query#iden #[derive(Iden)] -enum Post { +pub enum FirewallRule { Table, Id, - Title, - Text, + Role, + Protocol, + Description, + AllowedRoleID, + PortRangeFrom, + PortRangeTo } From 30432e216b52c2ff5607e0442262b888fee8b437 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 4 Apr 2023 10:15:47 -0400 Subject: [PATCH 22/39] add entity for roles and firewall rules --- .../src/entity/firewall_rule.rs | 38 +++++++++++++++++++ .../trifid_api_entities/src/entity/mod.rs | 2 + .../src/entity/organization.rs | 8 ++++ .../trifid_api_entities/src/entity/prelude.rs | 2 + .../trifid_api_entities/src/entity/role.rs | 33 ++++++++++++++++ 5 files changed, 83 insertions(+) create mode 100644 trifid-api/trifid_api_entities/src/entity/firewall_rule.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/role.rs diff --git a/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs b/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs new file mode 100644 index 0000000..6a8c64f --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "firewall_rule")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub role: String, + pub protocol: String, + pub description: String, + pub allowed_role_id: Option, + pub port_range_from: i32, + pub port_range_to: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::AllowedRoleId", + to = "super::role::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Role2, + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::Role", + to = "super::role::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Role1, +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs index 194bbb3..35ee697 100644 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -5,9 +5,11 @@ pub mod prelude; pub mod api_key; pub mod api_key_scope; pub mod auth_token; +pub mod firewall_rule; pub mod magic_link; pub mod network; pub mod organization; +pub mod role; pub mod session_token; pub mod signing_ca; pub mod totp_authenticator; diff --git a/trifid-api/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_entities/src/entity/organization.rs index f5f5026..25d4ff9 100644 --- a/trifid-api/trifid_api_entities/src/entity/organization.rs +++ b/trifid-api/trifid_api_entities/src/entity/organization.rs @@ -18,6 +18,8 @@ pub enum Relation { ApiKey, #[sea_orm(has_one = "super::network::Entity")] Network, + #[sea_orm(has_many = "super::role::Entity")] + Role, #[sea_orm( belongs_to = "super::user::Entity", from = "Column::Owner", @@ -40,6 +42,12 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Role.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::User.def() diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs index 2fdea32..93cce0c 100644 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -3,9 +3,11 @@ pub use super::api_key::Entity as ApiKey; pub use super::api_key_scope::Entity as ApiKeyScope; pub use super::auth_token::Entity as AuthToken; +pub use super::firewall_rule::Entity as FirewallRule; pub use super::magic_link::Entity as MagicLink; pub use super::network::Entity as Network; pub use super::organization::Entity as Organization; +pub use super::role::Entity as Role; pub use super::session_token::Entity as SessionToken; pub use super::signing_ca::Entity as SigningCa; pub use super::totp_authenticator::Entity as TotpAuthenticator; diff --git a/trifid-api/trifid_api_entities/src/entity/role.rs b/trifid-api/trifid_api_entities/src/entity/role.rs new file mode 100644 index 0000000..aa3f61f --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/role.rs @@ -0,0 +1,33 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "role")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + pub description: String, + pub organization: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} From 4f50aa1362e6cc45c017cc5562618a6b92001f11 Mon Sep 17 00:00:00 2001 From: core Date: Tue, 4 Apr 2023 20:50:25 -0400 Subject: [PATCH 23/39] migration changes --- trifid-api/src/routes/v1/mod.rs | 3 +- trifid-api/src/routes/v1/roles.rs | 165 ++++++++++++++++++ .../trifid_api_entities/src/entity/role.rs | 3 + .../m20230404_133809_create_table_roles.rs | 8 +- 4 files changed, 176 insertions(+), 3 deletions(-) create mode 100644 trifid-api/src/routes/v1/roles.rs diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index 248e152..8b72c27 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -3,4 +3,5 @@ pub mod signup; pub mod totp_authenticators; pub mod verify_totp_authenticators; pub mod networks; -pub mod organization; \ No newline at end of file +pub mod organization; +pub mod roles; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs new file mode 100644 index 0000000..85768c9 --- /dev/null +++ b/trifid-api/src/routes/v1/roles.rs @@ -0,0 +1,165 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#POST /v1/roles t+parity:full t+type:documented t+status:in_progress +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is in-progress and should not be expected to work, sometimes at all. + +use actix_web::{HttpRequest, HttpResponse, post}; +use actix_web::web::{Data, Json}; +use log::error; +use serde::{Deserialize, Serialize}; +use crate::AppState; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::error::{APIError, APIErrorsResponse}; +use trifid_api_entities::entity::organization; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CreateRoleRequest { + pub name: String, + #[serde(default)] + pub description: String, + #[serde(default, rename = "firewallRules")] + pub firewall_rules: Vec +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleFirewallRule { + pub protocol: RoleProtocol, + #[serde(default)] + pub description: String, + #[serde(rename = "allowedRoleID")] + pub allowed_role_id: Option, // Option is intentional here to prevent having to convert it anyway for SeaORM's types + #[serde(rename = "portRange")] + pub port_range: Option, // Option is intentional here, because we handle the null case in a way other than just "default 0" +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum RoleProtocol { + #[serde(rename = "ANY")] + Any, + #[serde(rename = "TCP")] + Tcp, + #[serde(rename = "UDP")] + Udp, + #[serde(rename = "ICMP")] + Icmp +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RolePortRange { + pub from: u16, + pub to: u16 +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleCreateResponse { + pub data: RoleCreateResponseData, + pub metadata: RoleCreateResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleCreateResponseData { + pub id: Option, + pub name: Option, + pub description: Option, + #[serde(rename = "firewallRules")] + pub firewall_rules: Vec, + #[serde(rename = "createdAt")] + pub created_at: String, + #[serde(rename = "modifiedAt")] + pub modified_at: String +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleCreateResponseMetadata {} + +#[post("/v1/roles")] +pub async fn create_role_request(req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with roles:create + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the roles:create scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + HttpResponse::Ok().finish() +} \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/role.rs b/trifid-api/trifid_api_entities/src/entity/role.rs index aa3f61f..b31b820 100644 --- a/trifid-api/trifid_api_entities/src/entity/role.rs +++ b/trifid-api/trifid_api_entities/src/entity/role.rs @@ -7,9 +7,12 @@ use sea_orm::entity::prelude::*; pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub id: String, + #[sea_orm(unique)] pub name: String, pub description: String, pub organization: String, + pub created_at: i64, + pub modified_at: i64, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs index 93dc9ad..1cecc8a 100644 --- a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs +++ b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs @@ -11,9 +11,11 @@ impl MigrationTrait for Migration { Table::create() .table(Role::Table) .col(ColumnDef::new(Role::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Role::Name).string().not_null()) + .col(ColumnDef::new(Role::Name).string().not_null().unique_key()) .col(ColumnDef::new(Role::Description).string().not_null()) .col(ColumnDef::new(Role::Organization).string().not_null()) + .col(ColumnDef::new(Role::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Role::ModifiedAt).big_integer().not_null()) .foreign_key( ForeignKey::create() .from(Role::Table, Role::Organization) @@ -36,5 +38,7 @@ pub enum Role { Id, Name, Description, - Organization + Organization, + CreatedAt, + ModifiedAt } From 8bdcb5683e684e864ac7c176cf097299caf690aa Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 5 Apr 2023 20:02:21 -0400 Subject: [PATCH 24/39] implement more roles stuff --- trifid-api/src/main.rs | 3 + trifid-api/src/routes/v1/roles.rs | 509 +++++++++++++++++++++++++++++- 2 files changed, 503 insertions(+), 9 deletions(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index a53e854..56797de 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -89,6 +89,9 @@ async fn main() -> Result<(), Box> { .service(routes::v1::networks::get_networks) .service(routes::v1::organization::create_org_request) .service(routes::v1::networks::get_network_request) + .service(routes::v1::roles::create_role_request) + .service(routes::v1::roles::get_roles) + .service(routes::v1::roles::get_role) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index 85768c9..33aa3d8 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -14,19 +14,33 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/roles t+parity:full t+type:documented t+status:in_progress +//#POST /v1/roles t+parity:full t+type:documented t+status:done // This endpoint has full parity with the original API. It has been recreated from the original API documentation. -// This endpoint is in-progress and should not be expected to work, sometimes at all. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// +//#GET /v1/roles t+parity:full t+type:documented t+status:done +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// +//#GET /v1/roles/{role_id} t+parity:full t+type:documented t+status:done +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. -use actix_web::{HttpRequest, HttpResponse, post}; -use actix_web::web::{Data, Json}; +use std::time::{SystemTime, UNIX_EPOCH}; +use actix_web::{get, HttpRequest, HttpResponse, post}; +use actix_web::web::{Data, Json, Path, Query}; +use chrono::{TimeZone, Utc}; use log::error; use serde::{Deserialize, Serialize}; use crate::AppState; use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::error::{APIError, APIErrorsResponse}; use trifid_api_entities::entity::organization; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait}; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait, QueryOrder, PaginatorTrait, ModelTrait}; +use trifid_api_entities::entity::firewall_rule; +use trifid_api_entities::entity::role; +use crate::cursor::Cursor; +use crate::tokens::random_id; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateRoleRequest { @@ -68,12 +82,12 @@ pub struct RolePortRange { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RoleCreateResponse { - pub data: RoleCreateResponseData, + pub data: RoleResponse, pub metadata: RoleCreateResponseMetadata } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RoleCreateResponseData { +pub struct RoleResponse { pub id: Option, pub name: Option, pub description: Option, @@ -161,5 +175,482 @@ pub async fn create_role_request(req: Json, req_info: HttpReq } }; - HttpResponse::Ok().finish() -} \ No newline at end of file + let new_role_model = role::Model { + id: random_id("role"), + name: req.name.clone(), + description: req.description.clone(), + organization: org, + created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + modified_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + }; + let firewall_rules: Vec = req.firewall_rules.iter().map(|i| { + firewall_rule::Model { + id: random_id("rule"), + role: new_role_model.id.clone(), + protocol: i.protocol.to_string(), + description: i.description.clone(), + allowed_role_id: i.allowed_role_id.clone(), + port_range_from: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).from as i32, + port_range_to: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).to as i32, + } + }).collect(); + + let new_role_model_clone = new_role_model.clone(); + let firewall_rules_clone = firewall_rules.clone(); + + let new_role_active_model = new_role_model.into_active_model(); + match new_role_active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later".to_string(), + path: None + } + ], + }) + } + } + + for rule in &firewall_rules_clone { + let active_model = rule.clone().into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later".to_string(), + path: None + } + ], + }) + } + } + } + + HttpResponse::Ok().json(RoleCreateResponse { + data: RoleResponse { + id: Some(new_role_model_clone.id.clone()), + name: Some(new_role_model_clone.name.clone()), + description: Some(new_role_model_clone.description), + firewall_rules: req.firewall_rules.clone(), + created_at: Utc.timestamp_opt(new_role_model_clone.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + modified_at: Utc.timestamp_opt(new_role_model_clone.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + }, + metadata: RoleCreateResponseMetadata {}, + }) +} + +impl ToString for RoleProtocol { + fn to_string(&self) -> String { + match self { + RoleProtocol::Any => "ANY".to_string(), + RoleProtocol::Tcp => "TCP".to_string(), + RoleProtocol::Udp => "UDP".to_string(), + RoleProtocol::Icmp => "ICMP".to_string() + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct ListRolesRequestOpts { + #[serde(default, rename = "includeCounts")] + pub include_counts: bool, + #[serde(default)] + pub cursor: String, + #[serde(default = "page_default", rename = "pageSize")] + pub page_size: u64 +} + +fn page_default() -> u64 { 25 } + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetRolesResponse { + pub data: Vec, + pub metadata: GetRolesResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetRolesResponseMetadata { + #[serde(rename = "totalCount")] + pub total_count: u64, + #[serde(rename = "hasNextPage")] + pub has_next_page: bool, + #[serde(rename = "hasPrevPage")] + pub has_prev_page: bool, + #[serde(default, rename = "prevCursor")] + pub prev_cursor: Option, + #[serde(default, rename = "nextCursor")] + pub next_cursor: Option, + #[serde(default)] + pub page: Option +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetRolesResponseMetadataPage { + pub count: u64, + pub start: u64 +} + +#[get("/v1/roles")] +pub async fn get_roles(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with roles:list + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the roles:list scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let cursor: Cursor = match opts.cursor.clone().try_into() { + Ok(r) => r, + Err(e) => { + error!("invalid cursor: {}", e); + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_INVALID_CURSOR".to_string(), + message: "The provided cursor was invalid, please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let network_pages = role::Entity::find().filter(role::Column::Organization.eq(org)).order_by_asc(role::Column::CreatedAt).paginate(&db.conn, opts.page_size); + + let total = match network_pages.num_items().await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let pages = match network_pages.num_pages().await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let models = match network_pages.fetch_page(cursor.page).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let mut models_mapped: Vec = vec![]; + + for u in models { + // fetch firewall rules + let rules = match firewall_rule::Entity::find().filter(firewall_rule::Column::Role.eq(&u.id)).all(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let rules: Vec = rules.iter().map(|r| { + let protocol = match r.protocol.as_str() { + "ANY" => RoleProtocol::Any, + "TCP" => RoleProtocol::Tcp, + "UDP" => RoleProtocol::Udp, + "ICMP" => RoleProtocol::Icmp, + _ => unreachable!("database has been corrupted or manually edited") + }; + + + + let port_range = if r.port_range_from == 0 && r.port_range_to == 65535 || matches!(protocol, RoleProtocol::Icmp) { + None + } else { + Some(RolePortRange { + from: r.port_range_from as u16, + to: r.port_range_to as u16, + }) + }; + + RoleFirewallRule { + protocol, + description: r.description.clone(), + allowed_role_id: r.allowed_role_id.clone(), + port_range, + } + }).collect(); + + models_mapped.push(RoleResponse { + id: Some(u.id.clone()), + name: Some(u.name), + description: Some(u.description), + firewall_rules: rules, + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + modified_at: Utc.timestamp_opt(u.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + }) + } + + let count = models_mapped.len() as u64; + + HttpResponse::Ok().json(GetRolesResponse { + data: models_mapped, + metadata: GetRolesResponseMetadata { + total_count: total, + has_next_page: cursor.page+1 != pages, + has_prev_page: cursor.page != 0, + prev_cursor: if cursor.page != 0 { + match (Cursor { page: cursor.page - 1 }).try_into() { + Ok(r) => Some(r), + Err(_) => None + } + } else { + None + }, + next_cursor: if cursor.page+1 != pages { + match (Cursor { page: cursor.page + 1 }).try_into() { + Ok(r) => Some(r), + Err(_) => None + } + } else { + None + }, + page: if opts.include_counts { + Some(GetRolesResponseMetadataPage { + count, + start: opts.page_size * cursor.page, + }) + } else { None }, + }, + }) +} + +#[get("/v1/roles/{role_id}")] +pub async fn get_role(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with roles:read + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the roles:read scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let role: Option = match role::Entity::find().filter(role::Column::Id.eq(net.into_inner())).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(role) = role {// fetch firewall rules + let rules = match firewall_rule::Entity::find().filter(firewall_rule::Column::Role.eq(&role.id)).all(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let rules: Vec = rules.iter().map(|r| { + let protocol = match r.protocol.as_str() { + "ANY" => RoleProtocol::Any, + "TCP" => RoleProtocol::Tcp, + "UDP" => RoleProtocol::Udp, + "ICMP" => RoleProtocol::Icmp, + _ => unreachable!("database has been corrupted or manually edited") + }; + + + + let port_range = if r.port_range_from == 0 && r.port_range_to == 65535 || matches!(protocol, RoleProtocol::Icmp) { + None + } else { + Some(RolePortRange { + from: r.port_range_from as u16, + to: r.port_range_to as u16, + }) + }; + + RoleFirewallRule { + protocol, + description: r.description.clone(), + allowed_role_id: r.allowed_role_id.clone(), + port_range, + } + }).collect(); + + + HttpResponse::Ok().json(GetRoleResponse { + data: RoleResponse { + id: Some(role.id.clone()), + name: Some(role.name.clone()), + description: Some(role.description.clone()), + firewall_rules: rules, + created_at: Utc.timestamp_opt(role.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + modified_at: Utc.timestamp_opt(role.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + }, + metadata: GetRoleResponseMetadata {}, + }) + } else { + HttpResponse::NotFound().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_MISSING_ROLE".to_string(), + message: "Role does not exist".to_string(), + path: None, + } + ], + }) + + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetRoleResponse { + pub data: RoleResponse, + pub metadata: GetRoleResponseMetadata +} +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct GetRoleResponseMetadata {} \ No newline at end of file From 2279608f963f4699b43e38b248353c97ce68f05e Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 5 Apr 2023 20:58:45 -0400 Subject: [PATCH 25/39] delete roles --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/roles.rs | 101 +++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 56797de..2be2a84 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -92,6 +92,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::roles::create_role_request) .service(routes::v1::roles::get_roles) .service(routes::v1::roles::get_role) + .service(routes::v1::roles::delete_role) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index 33aa3d8..daa0277 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -25,6 +25,10 @@ //#GET /v1/roles/{role_id} t+parity:full t+type:documented t+status:done // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// +//#DELETE /v1/roles/{role_id} t+parity:full t+type:documented t+status:done +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. use std::time::{SystemTime, UNIX_EPOCH}; use actix_web::{get, HttpRequest, HttpResponse, post}; @@ -41,6 +45,7 @@ use trifid_api_entities::entity::firewall_rule; use trifid_api_entities::entity::role; use crate::cursor::Cursor; use crate::tokens::random_id; +use actix_web::delete; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateRoleRequest { @@ -653,4 +658,98 @@ pub struct GetRoleResponse { pub metadata: GetRoleResponseMetadata } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct GetRoleResponseMetadata {} \ No newline at end of file +pub struct GetRoleResponseMetadata {} + +#[delete("/v1/roles/{role_id}")] +pub async fn delete_role(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with roles:delete + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the roles:delete scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let role: Option = match role::Entity::find().filter(role::Column::Id.eq(net.into_inner())).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(role) = role { + match role.delete(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + HttpResponse::Ok().json(RoleDeleteResponse { + data: RoleDeleteResponseData {}, + metadata: RoleDeleteResponseMetadata {}, + }) + } else { + HttpResponse::NotFound().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_MISSING_ROLE".to_string(), + message: "Role does not exist".to_string(), + path: None, + } + ], + }) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleDeleteResponse { + data: RoleDeleteResponseData, + metadata: RoleDeleteResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleDeleteResponseData {} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RoleDeleteResponseMetadata {} \ No newline at end of file From a5a21ba75b59770deac3a0afc9a8e499ed4eff0f Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 6 Apr 2023 11:41:41 -0400 Subject: [PATCH 26/39] i decided to change my comment thing again --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/auth/magic_link.rs | 3 +- trifid-api/src/routes/v1/auth/totp.rs | 3 +- .../src/routes/v1/auth/verify_magic_link.rs | 3 +- trifid-api/src/routes/v1/mod.rs | 3 +- trifid-api/src/routes/v1/networks.rs | 6 ++- trifid-api/src/routes/v1/organization.rs | 3 +- trifid-api/src/routes/v1/roles.rs | 6 ++- trifid-api/src/routes/v1/signup.rs | 3 +- .../src/routes/v1/totp_authenticators.rs | 3 +- trifid-api/src/routes/v1/trifid.rs | 48 +++++++++++++++++++ .../routes/v1/verify_totp_authenticators.rs | 3 +- 12 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 trifid-api/src/routes/v1/trifid.rs diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 2be2a84..94754f0 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -93,6 +93,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::roles::get_roles) .service(routes::v1::roles::get_role) .service(routes::v1::roles::delete_role) + .service(routes::v1::trifid::trifid_extensions) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs index 42ea473..a363600 100644 --- a/trifid-api/src/routes/v1/auth/magic_link.rs +++ b/trifid-api/src/routes/v1/auth/magic_link.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/auth/magic-link t+parity:full t+type:reverse_engineered t+status:done +//#POST /v1/auth/magic-link t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs index 5c1db57..a1302ff 100644 --- a/trifid-api/src/routes/v1/auth/totp.rs +++ b/trifid-api/src/routes/v1/auth/totp.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/auth/totp t+parity:full t+type:reverse_engineered t+status:done +//#POST /v1/auth/totp t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs index bf5f418..cd1e93b 100644 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/auth/verify-magic-link t+parity:full t+type:reverse_engineered t+status:done +//#POST /v1/auth/verify-magic-link t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index 8b72c27..ba01da3 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -4,4 +4,5 @@ pub mod totp_authenticators; pub mod verify_totp_authenticators; pub mod networks; pub mod organization; -pub mod roles; \ No newline at end of file +pub mod roles; +pub mod trifid; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index c28ccd7..22f6a6e 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#GET /v1/networks t+parity:full t+type:documented t+status:done +//#GET /v1/networks t+parity:full t+type:documented t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. // -//#GET /v1/networks/{network_id} t+parity:full t+type:documented t+status:done +//#GET /v1/networks/{network_id} t+parity:full t+type:documented t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use serde::{Serialize, Deserialize}; use actix_web::{get, HttpRequest, HttpResponse}; diff --git a/trifid-api/src/routes/v1/organization.rs b/trifid-api/src/routes/v1/organization.rs index ddb8373..1be91ca 100644 --- a/trifid-api/src/routes/v1/organization.rs +++ b/trifid-api/src/routes/v1/organization.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/organization t+parity:none t+type:fabricated t+status:done t+status:want-reveng +//#POST /v1/organization t+parity:none t+type:fabricated t+status:done t+status:want-reveng t+feature:definednetworking // This is NOT a DN-compatible API. The organization create API has not yet been reverse engineered. This endpoint is a complete fabrication of trifid-api. // While this endpoint is considered done, help is wanted with reverse engineering the original API. Major features should not be added or removed unless it is replacing this endpoint with the correct, DN-compatible endpoint. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use std::time::{Duration, SystemTime, UNIX_EPOCH}; use actix_web::{HttpRequest, HttpResponse}; diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index daa0277..3c21569 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -14,13 +14,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/roles t+parity:full t+type:documented t+status:done +//#POST /v1/roles t+parity:full t+type:documented t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. // -//#GET /v1/roles t+parity:full t+type:documented t+status:done +//#GET /v1/roles t+parity:full t+type:documented t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. // //#GET /v1/roles/{role_id} t+parity:full t+type:documented t+status:done // This endpoint has full parity with the original API. It has been recreated from the original API documentation. diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs index 1245805..ea18691 100644 --- a/trifid-api/src/routes/v1/signup.rs +++ b/trifid-api/src/routes/v1/signup.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/signup t+parity:full t+type:reverse_engineered t+status:done +//#POST /v1/signup t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use actix_web::{HttpResponse, post}; use actix_web::web::{Data, Json}; diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index 876a1f4..d20a621 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/totp-authenticators t+parity:full t+type:reverse_engineered t+status:done +//#POST /v1/totp-authenticators t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use serde::{Serialize, Deserialize}; use actix_web::{HttpRequest, HttpResponse, post}; diff --git a/trifid-api/src/routes/v1/trifid.rs b/trifid-api/src/routes/v1/trifid.rs new file mode 100644 index 0000000..3c735bb --- /dev/null +++ b/trifid-api/src/routes/v1/trifid.rs @@ -0,0 +1,48 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#GET /v1/trifid_extensions t+parity:none t+type:fabricated t+status:done t+status:special t+features:trifidextensions +// This is NOT a DN-compatible API. This endpoint is a fabrication of trifid-api to enable the TrifidExtensions codebase for clients. This allows clients to access additional functionality only available on servers that support it. +// This endpoint is considered done. It should not be modified unless for bugfixes. +// This endpoint is a special endpoint, and may impact what features client are able to access. +// This endpoint requires the `trifidextensions` extension to be enabled to be used. +// +// This endpoint implements the TrifidExtensions API extension framework. This allows the server to optionally provide extra features and endpoints to clients that support it, +// by providing an endpoint to allow the client to check which API extensions are enabled. +// The following extensions are available: +// - definednetworking - Base DN api, must be enabled on all servers compatible with the original DN api +// - trifidextensions - Enables the TrifidExtensions codebase +// - extended_roles - Enables extra actions when editing roles (see the list of special endpoints in roles.rs) +// - extended_hosts - Enables extra actions when editing hosts (see the list of special endpoints in hosts.rs) +// +// A client should GET /v1/trifid_extensions upon creating a new connection to an API server, to check which features it supports. +// If the request returns a non-200 response, or does not follow the typical TrifidExtensions schema, that server should be assumed to only support t+features:definednetworking. +// Endpoint specs (#REQTYPE) can indicate they require a feature by adding t+features:[feature] + +use actix_web::{HttpResponse, get}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct TrifidExtensionsResponse { + pub extensions: Vec +} + +#[get("/v1/trifid_extensions")] +pub async fn trifid_extensions() -> HttpResponse { + HttpResponse::Ok().json(TrifidExtensionsResponse { + extensions: vec!["definednetworking".to_string(), "trifidextensions".to_string(), "extended_roles".to_string(), "extended_hosts".to_string()], + }) +} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs index db9bbde..2199c1d 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticators.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/verify-totp-authenticators t+parity:full t+type:reverse_engineered t+status:done +//#POST /v1/verify-totp-authenticators t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use actix_web::{HttpRequest, HttpResponse, post}; use actix_web::web::{Data, Json}; From 458cd5751930f887e2ce84b2e00b8ee35181ba47 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 27 Apr 2023 12:53:10 -0400 Subject: [PATCH 27/39] PUT /v1/roles/roleid --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/roles.rs | 202 +++++++++++++++++++++++++++++- 2 files changed, 199 insertions(+), 4 deletions(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 94754f0..05a68bf 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -93,6 +93,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::roles::get_roles) .service(routes::v1::roles::get_role) .service(routes::v1::roles::delete_role) + .service(routes::v1::roles::update_role_request) .service(routes::v1::trifid::trifid_extensions) }).bind(CONFIG.server.bind)?.run().await?; diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index 3c21569..fd9d500 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -31,9 +31,12 @@ //#DELETE /v1/roles/{role_id} t+parity:full t+type:documented t+status:done // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +//#PUT /v1/roles/{role_id} t+parity:full t+type:documented t+status:done +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{get, HttpRequest, HttpResponse, post}; +use actix_web::{get, HttpRequest, HttpResponse, post, put}; use actix_web::web::{Data, Json, Path, Query}; use chrono::{TimeZone, Utc}; use log::error; @@ -51,6 +54,7 @@ use actix_web::delete; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateRoleRequest { + #[serde(default)] pub name: String, #[serde(default)] pub description: String, @@ -58,6 +62,15 @@ pub struct CreateRoleRequest { pub firewall_rules: Vec } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UpdateRoleRequest { + pub name: Option, + #[serde(default)] + pub description: String, + #[serde(default, rename = "firewallRules")] + pub firewall_rules: Vec +} + #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RoleFirewallRule { pub protocol: RoleProtocol, @@ -663,7 +676,7 @@ pub struct GetRoleResponse { pub struct GetRoleResponseMetadata {} #[delete("/v1/roles/{role_id}")] -pub async fn delete_role(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn delete_role(role: Path, req_info: HttpRequest, db: Data) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:delete let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); let api_token_info = enforce_api_token(&req_info, &["roles:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); @@ -694,7 +707,7 @@ pub async fn delete_role(net: Path, req_info: HttpRequest, db: Data = match role::Entity::find().filter(role::Column::Id.eq(net.into_inner())).one(&db.conn).await { + let role: Option = match role::Entity::find().filter(role::Column::Id.eq(role.into_inner())).one(&db.conn).await { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -754,4 +767,185 @@ pub struct RoleDeleteResponse { pub struct RoleDeleteResponseData {} #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct RoleDeleteResponseMetadata {} \ No newline at end of file +pub struct RoleDeleteResponseMetadata {} + + +#[put("/v1/roles/{role_id}")] +pub async fn update_role_request(role: Path, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with roles:create + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the roles:create scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let existing_rules: Vec = match firewall_rule::Entity::find().filter(firewall_rule::Column::Role.eq(role.clone())).all(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + for rule in &existing_rules { + match rule.clone().delete(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + } + + let new_role_model = role::Model { + id: role.into_inner(), + name: req.name.clone(), + description: req.description.clone(), + organization: org, + created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + modified_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + }; + + let firewall_rules: Vec = req.firewall_rules.iter().map(|i| { + firewall_rule::Model { + id: random_id("rule"), + role: new_role_model.id.clone(), + protocol: i.protocol.to_string(), + description: i.description.clone(), + allowed_role_id: i.allowed_role_id.clone(), + port_range_from: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).from as i32, + port_range_to: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).to as i32, + } + }).collect(); + + let new_role_model_clone = new_role_model.clone(); + let firewall_rules_clone = firewall_rules.clone(); + + let new_role_active_model = new_role_model.into_active_model(); + match new_role_active_model.update(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later".to_string(), + path: None + } + ], + }) + } + } + + for rule in &firewall_rules_clone { + let active_model = rule.clone().into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later".to_string(), + path: None + } + ], + }) + } + } + } + + HttpResponse::Ok().json(RoleCreateResponse { + data: RoleResponse { + id: Some(new_role_model_clone.id.clone()), + name: Some(new_role_model_clone.name.clone()), + description: Some(new_role_model_clone.description), + firewall_rules: req.firewall_rules.clone(), + created_at: Utc.timestamp_opt(new_role_model_clone.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + modified_at: Utc.timestamp_opt(new_role_model_clone.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + }, + metadata: RoleCreateResponseMetadata {}, + }) +} From 3f81779445ccc9e059f7472d4af3ea1052bbd434 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 27 Apr 2023 13:42:48 -0400 Subject: [PATCH 28/39] migrations and entities for hosts --- trifid-api/src/routes/v1/signup.rs | 2 +- .../trifid_api_entities/src/entity/api_key.rs | 41 ------------- .../src/entity/api_key_scope.rs | 32 ---------- .../src/entity/auth_token.rs | 32 ---------- .../src/entity/firewall_rule.rs | 38 ------------ .../src/entity/magic_link.rs | 32 ---------- .../trifid_api_entities/src/entity/mod.rs | 16 ----- .../trifid_api_entities/src/entity/network.rs | 52 ----------------- .../src/entity/organization.rs | 57 ------------------ .../trifid_api_entities/src/entity/prelude.rs | 14 ----- .../trifid_api_entities/src/entity/role.rs | 36 ------------ .../src/entity/session_token.rs | 32 ---------- .../src/entity/signing_ca.rs | 31 ---------- .../src/entity/totp_authenticator.rs | 38 ------------ .../trifid_api_entities/src/entity/user.rs | 58 ------------------- trifid-api/trifid_api_migration/src/lib.rs | 6 ++ 16 files changed, 7 insertions(+), 510 deletions(-) delete mode 100644 trifid-api/trifid_api_entities/src/entity/api_key.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/api_key_scope.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/auth_token.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/firewall_rule.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/magic_link.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/mod.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/network.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/organization.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/prelude.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/role.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/session_token.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/signing_ca.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs delete mode 100644 trifid-api/trifid_api_entities/src/entity/user.rs diff --git a/trifid-api/src/routes/v1/signup.rs b/trifid-api/src/routes/v1/signup.rs index ea18691..c40806b 100644 --- a/trifid-api/src/routes/v1/signup.rs +++ b/trifid-api/src/routes/v1/signup.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // -//#POST /v1/signup t+parity:full t+type:reverse_engineered t+status:done t+feature:definednetworking +//#POST /v1/signup t+parity:full t+type:reverse_engineered t+status:done t+features:definednetworking // This endpoint has full parity with the original API. It has been reverse-engineered from the original API as the original API docs do not have this item. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. diff --git a/trifid-api/trifid_api_entities/src/entity/api_key.rs b/trifid-api/trifid_api_entities/src/entity/api_key.rs deleted file mode 100644 index 04aa795..0000000 --- a/trifid-api/trifid_api_entities/src/entity/api_key.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "api_key")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - #[sea_orm(unique)] - pub key: String, - pub organization: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::api_key_scope::Entity")] - ApiKeyScope, - #[sea_orm( - belongs_to = "super::organization::Entity", - from = "Column::Organization", - to = "super::organization::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Organization, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ApiKeyScope.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Organization.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs deleted file mode 100644 index 1deffbc..0000000 --- a/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "api_key_scope")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub scope: String, - pub api_key: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::api_key::Entity", - from = "Column::ApiKey", - to = "super::api_key::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - ApiKey, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ApiKey.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/auth_token.rs b/trifid-api/trifid_api_entities/src/entity/auth_token.rs deleted file mode 100644 index db14039..0000000 --- a/trifid-api/trifid_api_entities/src/entity/auth_token.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "auth_token")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub user: String, - pub expires_on: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::User", - to = "super::user::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs b/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs deleted file mode 100644 index 6a8c64f..0000000 --- a/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "firewall_rule")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub role: String, - pub protocol: String, - pub description: String, - pub allowed_role_id: Option, - pub port_range_from: i32, - pub port_range_to: i32, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::role::Entity", - from = "Column::AllowedRoleId", - to = "super::role::Column::Id", - on_update = "NoAction", - on_delete = "Cascade" - )] - Role2, - #[sea_orm( - belongs_to = "super::role::Entity", - from = "Column::Role", - to = "super::role::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Role1, -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/magic_link.rs b/trifid-api/trifid_api_entities/src/entity/magic_link.rs deleted file mode 100644 index 1707d7e..0000000 --- a/trifid-api/trifid_api_entities/src/entity/magic_link.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "magic_link")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub user: String, - pub expires_on: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::User", - to = "super::user::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs deleted file mode 100644 index 35ee697..0000000 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ /dev/null @@ -1,16 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -pub mod prelude; - -pub mod api_key; -pub mod api_key_scope; -pub mod auth_token; -pub mod firewall_rule; -pub mod magic_link; -pub mod network; -pub mod organization; -pub mod role; -pub mod session_token; -pub mod signing_ca; -pub mod totp_authenticator; -pub mod user; diff --git a/trifid-api/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_entities/src/entity/network.rs deleted file mode 100644 index 7d575e6..0000000 --- a/trifid-api/trifid_api_entities/src/entity/network.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "network")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub cidr: String, - #[sea_orm(unique)] - pub organization: String, - #[sea_orm(unique)] - pub signing_ca: String, - pub created_at: i64, - pub name: String, - pub lighthouses_as_relays: bool, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::organization::Entity", - from = "Column::Organization", - to = "super::organization::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Organization, - #[sea_orm( - belongs_to = "super::signing_ca::Entity", - from = "Column::SigningCa", - to = "super::signing_ca::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - SigningCa, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Organization.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::SigningCa.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_entities/src/entity/organization.rs deleted file mode 100644 index 25d4ff9..0000000 --- a/trifid-api/trifid_api_entities/src/entity/organization.rs +++ /dev/null @@ -1,57 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "organization")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub name: String, - #[sea_orm(unique)] - pub owner: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::api_key::Entity")] - ApiKey, - #[sea_orm(has_one = "super::network::Entity")] - Network, - #[sea_orm(has_many = "super::role::Entity")] - Role, - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::Owner", - to = "super::user::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::ApiKey.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Network.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Role.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs deleted file mode 100644 index 93cce0c..0000000 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -pub use super::api_key::Entity as ApiKey; -pub use super::api_key_scope::Entity as ApiKeyScope; -pub use super::auth_token::Entity as AuthToken; -pub use super::firewall_rule::Entity as FirewallRule; -pub use super::magic_link::Entity as MagicLink; -pub use super::network::Entity as Network; -pub use super::organization::Entity as Organization; -pub use super::role::Entity as Role; -pub use super::session_token::Entity as SessionToken; -pub use super::signing_ca::Entity as SigningCa; -pub use super::totp_authenticator::Entity as TotpAuthenticator; -pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/entity/role.rs b/trifid-api/trifid_api_entities/src/entity/role.rs deleted file mode 100644 index b31b820..0000000 --- a/trifid-api/trifid_api_entities/src/entity/role.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "role")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - #[sea_orm(unique)] - pub name: String, - pub description: String, - pub organization: String, - pub created_at: i64, - pub modified_at: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::organization::Entity", - from = "Column::Organization", - to = "super::organization::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - Organization, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Organization.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/session_token.rs b/trifid-api/trifid_api_entities/src/entity/session_token.rs deleted file mode 100644 index a648966..0000000 --- a/trifid-api/trifid_api_entities/src/entity/session_token.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "session_token")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub user: String, - pub expires_on: i64, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::User", - to = "super::user::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/signing_ca.rs b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs deleted file mode 100644 index d0770a9..0000000 --- a/trifid-api/trifid_api_entities/src/entity/signing_ca.rs +++ /dev/null @@ -1,31 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "signing_ca")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - pub organization: String, - pub cert: String, - #[sea_orm(unique)] - pub key: String, - pub expires: i64, - #[sea_orm(unique)] - pub nonce: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_one = "super::network::Entity")] - Network, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Network.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs deleted file mode 100644 index 5437596..0000000 --- a/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs +++ /dev/null @@ -1,38 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "totp_authenticator")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - #[sea_orm(unique)] - pub secret: String, - #[sea_orm(unique)] - pub url: String, - pub verified: bool, - pub expires_on: i64, - #[sea_orm(unique)] - pub user: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm( - belongs_to = "super::user::Entity", - from = "Column::User", - to = "super::user::Column::Id", - on_update = "Cascade", - on_delete = "Cascade" - )] - User, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::User.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_entities/src/entity/user.rs deleted file mode 100644 index 0d7b61f..0000000 --- a/trifid-api/trifid_api_entities/src/entity/user.rs +++ /dev/null @@ -1,58 +0,0 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 - -use sea_orm::entity::prelude::*; - -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] -#[sea_orm(table_name = "user")] -pub struct Model { - #[sea_orm(primary_key, auto_increment = false)] - pub id: String, - #[sea_orm(unique)] - pub email: String, -} - -#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] -pub enum Relation { - #[sea_orm(has_many = "super::auth_token::Entity")] - AuthToken, - #[sea_orm(has_many = "super::magic_link::Entity")] - MagicLink, - #[sea_orm(has_one = "super::organization::Entity")] - Organization, - #[sea_orm(has_many = "super::session_token::Entity")] - SessionToken, - #[sea_orm(has_one = "super::totp_authenticator::Entity")] - TotpAuthenticator, -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::AuthToken.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::MagicLink.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::Organization.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::SessionToken.def() - } -} - -impl Related for Entity { - fn to() -> RelationDef { - Relation::TotpAuthenticator.def() - } -} - -impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index cd1b240..ef066c6 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -14,6 +14,9 @@ pub mod m20230403_142517_create_table_signing_cas; pub mod m20230403_173431_create_table_networks; mod m20230404_133809_create_table_roles; mod m20230404_133813_create_table_firewall_rules; +mod m20230427_170037_create_table_hosts; +mod m20230427_171517_create_table_hosts_static_addresses; +mod m20230427_171529_create_table_hosts_config_overrides; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -31,6 +34,9 @@ impl MigratorTrait for Migrator { Box::new(m20230403_173431_create_table_networks::Migration), Box::new(m20230404_133809_create_table_roles::Migration), Box::new(m20230404_133813_create_table_firewall_rules::Migration), + Box::new(m20230427_170037_create_table_hosts::Migration), + Box::new(m20230427_171517_create_table_hosts_static_addresses::Migration), + Box::new(m20230427_171529_create_table_hosts_config_overrides::Migration), ] } } From 26f5db6b61ba722bb7cb80e20d7152c0b40bf69c Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 27 Apr 2023 13:51:08 -0400 Subject: [PATCH 29/39] migrations and entities for hosts --- .../trifid_api_entities/src/entity/api_key.rs | 41 +++++++++++ .../src/entity/api_key_scope.rs | 32 +++++++++ .../src/entity/auth_token.rs | 32 +++++++++ .../src/entity/firewall_rule.rs | 38 ++++++++++ .../trifid_api_entities/src/entity/host.rs | 68 ++++++++++++++++++ .../src/entity/host_config_override.rs | 33 +++++++++ .../src/entity/host_static_address.rs | 32 +++++++++ .../src/entity/magic_link.rs | 32 +++++++++ .../trifid_api_entities/src/entity/mod.rs | 19 +++++ .../trifid_api_entities/src/entity/network.rs | 60 ++++++++++++++++ .../src/entity/organization.rs | 57 +++++++++++++++ .../trifid_api_entities/src/entity/prelude.rs | 17 +++++ .../trifid_api_entities/src/entity/role.rs | 44 ++++++++++++ .../src/entity/session_token.rs | 32 +++++++++ .../src/entity/signing_ca.rs | 31 +++++++++ .../src/entity/totp_authenticator.rs | 38 ++++++++++ .../trifid_api_entities/src/entity/user.rs | 58 ++++++++++++++++ .../m20230427_170037_create_table_hosts.rs | 69 +++++++++++++++++++ ...517_create_table_hosts_static_addresses.rs | 43 ++++++++++++ ...529_create_table_hosts_config_overrides.rs | 51 ++++++++++++++ 20 files changed, 827 insertions(+) create mode 100644 trifid-api/trifid_api_entities/src/entity/api_key.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/api_key_scope.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/auth_token.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/firewall_rule.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/host.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/host_config_override.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/host_static_address.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/magic_link.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/mod.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/network.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/organization.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/prelude.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/role.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/session_token.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/signing_ca.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs create mode 100644 trifid-api/trifid_api_entities/src/entity/user.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs diff --git a/trifid-api/trifid_api_entities/src/entity/api_key.rs b/trifid-api/trifid_api_entities/src/entity/api_key.rs new file mode 100644 index 0000000..04aa795 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/api_key.rs @@ -0,0 +1,41 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub key: String, + pub organization: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key_scope::Entity")] + ApiKeyScope, + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKeyScope.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs new file mode 100644 index 0000000..1deffbc --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/api_key_scope.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key_scope")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub scope: String, + pub api_key: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::api_key::Entity", + from = "Column::ApiKey", + to = "super::api_key::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + ApiKey, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/auth_token.rs b/trifid-api/trifid_api_entities/src/entity/auth_token.rs new file mode 100644 index 0000000..db14039 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/auth_token.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "auth_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs b/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs new file mode 100644 index 0000000..6a8c64f --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/firewall_rule.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "firewall_rule")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub role: String, + pub protocol: String, + pub description: String, + pub allowed_role_id: Option, + pub port_range_from: i32, + pub port_range_to: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::AllowedRoleId", + to = "super::role::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Role2, + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::Role", + to = "super::role::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Role1, +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/host.rs b/trifid-api/trifid_api_entities/src/entity/host.rs new file mode 100644 index 0000000..f2d689a --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/host.rs @@ -0,0 +1,68 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "host")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + pub network: String, + pub role: String, + pub ip: String, + pub listen_port: i32, + pub is_lighthouse: bool, + pub is_relay: bool, + pub counter: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::host_config_override::Entity")] + HostConfigOverride, + #[sea_orm(has_many = "super::host_static_address::Entity")] + HostStaticAddress, + #[sea_orm( + belongs_to = "super::network::Entity", + from = "Column::Network", + to = "super::network::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Network, + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::Role", + to = "super::role::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Role, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::HostConfigOverride.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::HostStaticAddress.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Role.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/host_config_override.rs b/trifid-api/trifid_api_entities/src/entity/host_config_override.rs new file mode 100644 index 0000000..bfe77d2 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/host_config_override.rs @@ -0,0 +1,33 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "host_config_override")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub key: String, + pub value: String, + pub host: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::host::Entity", + from = "Column::Host", + to = "super::host::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Host, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Host.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/host_static_address.rs b/trifid-api/trifid_api_entities/src/entity/host_static_address.rs new file mode 100644 index 0000000..9b74220 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/host_static_address.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "host_static_address")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub host: String, + pub address: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::host::Entity", + from = "Column::Host", + to = "super::host::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Host, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Host.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/magic_link.rs b/trifid-api/trifid_api_entities/src/entity/magic_link.rs new file mode 100644 index 0000000..1707d7e --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/magic_link.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "magic_link")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs new file mode 100644 index 0000000..d82ab21 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub mod prelude; + +pub mod api_key; +pub mod api_key_scope; +pub mod auth_token; +pub mod firewall_rule; +pub mod host; +pub mod host_config_override; +pub mod host_static_address; +pub mod magic_link; +pub mod network; +pub mod organization; +pub mod role; +pub mod session_token; +pub mod signing_ca; +pub mod totp_authenticator; +pub mod user; diff --git a/trifid-api/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_entities/src/entity/network.rs new file mode 100644 index 0000000..c747e2a --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/network.rs @@ -0,0 +1,60 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "network")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub cidr: String, + #[sea_orm(unique)] + pub organization: String, + #[sea_orm(unique)] + pub signing_ca: String, + pub created_at: i64, + pub name: String, + pub lighthouses_as_relays: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::host::Entity")] + Host, + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, + #[sea_orm( + belongs_to = "super::signing_ca::Entity", + from = "Column::SigningCa", + to = "super::signing_ca::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + SigningCa, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Host.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SigningCa.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_entities/src/entity/organization.rs new file mode 100644 index 0000000..25d4ff9 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/organization.rs @@ -0,0 +1,57 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "organization")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + #[sea_orm(unique)] + pub owner: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key::Entity")] + ApiKey, + #[sea_orm(has_one = "super::network::Entity")] + Network, + #[sea_orm(has_many = "super::role::Entity")] + Role, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::Owner", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Role.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs new file mode 100644 index 0000000..2042b24 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -0,0 +1,17 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub use super::api_key::Entity as ApiKey; +pub use super::api_key_scope::Entity as ApiKeyScope; +pub use super::auth_token::Entity as AuthToken; +pub use super::firewall_rule::Entity as FirewallRule; +pub use super::host::Entity as Host; +pub use super::host_config_override::Entity as HostConfigOverride; +pub use super::host_static_address::Entity as HostStaticAddress; +pub use super::magic_link::Entity as MagicLink; +pub use super::network::Entity as Network; +pub use super::organization::Entity as Organization; +pub use super::role::Entity as Role; +pub use super::session_token::Entity as SessionToken; +pub use super::signing_ca::Entity as SigningCa; +pub use super::totp_authenticator::Entity as TotpAuthenticator; +pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_entities/src/entity/role.rs b/trifid-api/trifid_api_entities/src/entity/role.rs new file mode 100644 index 0000000..d664163 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/role.rs @@ -0,0 +1,44 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "role")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub name: String, + pub description: String, + pub organization: String, + pub created_at: i64, + pub modified_at: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::host::Entity")] + Host, + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Host.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/session_token.rs b/trifid-api/trifid_api_entities/src/entity/session_token.rs new file mode 100644 index 0000000..a648966 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/session_token.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "session_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/signing_ca.rs b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs new file mode 100644 index 0000000..d0770a9 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/signing_ca.rs @@ -0,0 +1,31 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "signing_ca")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub organization: String, + pub cert: String, + #[sea_orm(unique)] + pub key: String, + pub expires: i64, + #[sea_orm(unique)] + pub nonce: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_one = "super::network::Entity")] + Network, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs new file mode 100644 index 0000000..5437596 --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/totp_authenticator.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "totp_authenticator")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub secret: String, + #[sea_orm(unique)] + pub url: String, + pub verified: bool, + pub expires_on: i64, + #[sea_orm(unique)] + pub user: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_entities/src/entity/user.rs new file mode 100644 index 0000000..0d7b61f --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/user.rs @@ -0,0 +1,58 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub email: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::auth_token::Entity")] + AuthToken, + #[sea_orm(has_many = "super::magic_link::Entity")] + MagicLink, + #[sea_orm(has_one = "super::organization::Entity")] + Organization, + #[sea_orm(has_many = "super::session_token::Entity")] + SessionToken, + #[sea_orm(has_one = "super::totp_authenticator::Entity")] + TotpAuthenticator, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthToken.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::MagicLink.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::SessionToken.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::TotpAuthenticator.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs new file mode 100644 index 0000000..9ad0b96 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs @@ -0,0 +1,69 @@ +use sea_orm_migration::prelude::*; +use crate::m20230403_173431_create_table_networks::Network; +use crate::m20230404_133809_create_table_roles::Role; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(Host::Table) + .col(ColumnDef::new(Host::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Host::Name).string().not_null()) + .col(ColumnDef::new(Host::Network).string().not_null()) + .col(ColumnDef::new(Host::Role).string().not_null()) + .col(ColumnDef::new(Host::IP).string().not_null()) + .col(ColumnDef::new(Host::ListenPort).unsigned().not_null()) + .col(ColumnDef::new(Host::IsLighthouse).boolean().not_null()) + .col(ColumnDef::new(Host::IsRelay).boolean().not_null()) + .col(ColumnDef::new(Host::Counter).unsigned().not_null()) + .foreign_key( + ForeignKey::create() + .from(Host::Table, Host::Network) + .to(Network::Table, Network::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ) + .foreign_key( + ForeignKey::create() + .from(Host::Table, Host::Role) + .to(Role::Table, Role::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ) + .index( + Index::create() + .name("idx-hosts-id-name-unique") + .table(Host::Table) + .col(Host::Id) + .col(Host::Name) + .unique() + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(Host::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum Host { + Table, + Id, + Name, + Network, + Role, + IP, + ListenPort, + IsLighthouse, + IsRelay, + Counter +} diff --git a/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs b/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs new file mode 100644 index 0000000..7de7d76 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs @@ -0,0 +1,43 @@ +use sea_orm_migration::prelude::*; +use crate::m20230427_170037_create_table_hosts::Host; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(HostStaticAddress::Table) + .col(ColumnDef::new(HostStaticAddress::Id).string().not_null().primary_key()) + .col(ColumnDef::new(HostStaticAddress::Host).string().not_null()) + .col(ColumnDef::new(HostStaticAddress::Address).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(HostStaticAddress::Table, HostStaticAddress::Host) + .to(Host::Table, Host::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(HostStaticAddress::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum HostStaticAddress { + Table, + Id, + Host, + Address +} diff --git a/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs b/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs new file mode 100644 index 0000000..86ee883 --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs @@ -0,0 +1,51 @@ +use sea_orm_migration::prelude::*; +use crate::m20230427_170037_create_table_hosts::Host; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager.create_table( + Table::create() + .table(HostConfigOverride::Table) + .col(ColumnDef::new(HostConfigOverride::Id).string().not_null().primary_key()) + .col(ColumnDef::new(HostConfigOverride::Key).string().not_null()) + .col(ColumnDef::new(HostConfigOverride::Value).string().not_null()) + .col(ColumnDef::new(HostConfigOverride::Host).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(HostConfigOverride::Table, HostConfigOverride::Host) + .to(Host::Table, Host::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade) + ) + .index( + Index::create() + .name("idx_hosts_config_overrides-key-host-unique") + .table(HostConfigOverride::Table) + .col(HostConfigOverride::Key) + .col(HostConfigOverride::Id) + .unique() + ) + .to_owned() + ).await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(HostConfigOverride::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum HostConfigOverride { + Table, + Host, + Id, + Key, + Value, +} From fe67c34fd54f25579b323c6c898568ba09bfb1cc Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 27 Apr 2023 20:40:56 -0400 Subject: [PATCH 30/39] /v1/hosts --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 358 ++++++++++++++++++ trifid-api/src/routes/v1/mod.rs | 3 +- .../trifid_api_entities/src/entity/host.rs | 1 + .../m20230427_170037_create_table_hosts.rs | 4 +- 5 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 trifid-api/src/routes/v1/hosts.rs diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 05a68bf..12365dd 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -95,6 +95,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::roles::delete_role) .service(routes::v1::roles::update_role_request) .service(routes::v1::trifid::trifid_extensions) + .service(routes::v1::hosts::get_hosts) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs new file mode 100644 index 0000000..04a783d --- /dev/null +++ b/trifid-api/src/routes/v1/hosts.rs @@ -0,0 +1,358 @@ +// trifid-api, an open source reimplementation of the Defined Networking nebula management server. +// Copyright (C) 2023 c0repwn3r +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . +// +//#GET /v1/hosts t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. + +use std::net::SocketAddrV4; +use std::str::FromStr; +use actix_web::{HttpRequest, HttpResponse, get}; +use actix_web::web::{Data, Query}; +use log::error; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait}; +use serde::{Serialize, Deserialize}; +use trifid_api_entities::entity::{host, host_static_address, network, organization}; +use crate::AppState; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::cursor::Cursor; +use crate::error::{APIError, APIErrorsResponse}; + +#[derive(Serialize, Deserialize)] +pub struct ListHostsRequestOpts { + #[serde(default, rename = "includeCounts")] + pub include_counts: bool, + #[serde(default)] + pub cursor: String, + #[serde(default = "page_default", rename = "pageSize")] + pub page_size: u64 +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ListHostsResponse { + pub data: Vec, + pub metadata: ListHostsResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ListHostsResponseMetadata { + #[serde(rename = "totalCount")] + pub total_count: u64, + #[serde(rename = "hasNextPage")] + pub has_next_page: bool, + #[serde(rename = "hasPrevPage")] + pub has_prev_page: bool, + #[serde(default, rename = "prevCursor")] + pub prev_cursor: Option, + #[serde(default, rename = "nextCursor")] + pub next_cursor: Option, + #[serde(default)] + pub page: Option +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ListHostsResponseMetadataPage { + pub count: u64, + pub start: u64 +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct HostResponse { + pub id: String, + #[serde(rename = "organizationID")] + pub organization_id: String, + #[serde(rename = "networkID")] + pub network_id: String, + #[serde(rename = "roleID")] + pub role_id: String, + pub name: String, + #[serde(rename = "ipAddress")] + pub ip_address: String, + #[serde(rename = "staticAddresses")] + pub static_addresses: Vec, + #[serde(rename = "listenPort")] + pub listen_port: u16, + #[serde(rename = "isLighthouse")] + pub is_lighthouse: bool, + #[serde(rename = "isRelay")] + pub is_relay: bool, + #[serde(rename = "createdAt")] + pub created_at: String, + #[serde(rename = "isBlocked")] + pub is_blocked: bool, + pub metadata: HostResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct HostResponseMetadata { + #[serde(rename = "lastSeenAt")] + pub last_seen_at: Option, + pub version: String, + pub platform: String, + #[serde(rename = "updateAvailable")] + pub update_available: bool +} + +fn page_default() -> u64 { 25 } + + +#[get("/v1/hosts")] +pub async fn get_hosts(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with roles:list + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:list scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let net_id; + + let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + + let cursor: Cursor = match opts.cursor.clone().try_into() { + Ok(r) => r, + Err(e) => { + error!("invalid cursor: {}", e); + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_INVALID_CURSOR".to_string(), + message: "The provided cursor was invalid, please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let host_pages = host::Entity::find().filter(host::Column::Network.eq(net_id)).order_by_asc(host::Column::CreatedAt).paginate(&db.conn, opts.page_size); + + let total = match host_pages.num_items().await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + let pages = match host_pages.num_pages().await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let models = match host_pages.fetch_page(cursor.page).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let mut models_mapped: Vec = vec![]; + + for u in models { + // fetch static addresses + let ips = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&u.id)).all(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + models_mapped.push(HostResponse { + id: u.id, + organization_id: org_id.clone(), + network_id: u.network, + role_id: u.role, + name: u.name, + ip_address: u.ip, + static_addresses: ips.iter().map(|u| SocketAddrV4::from_str(&u.address).unwrap()).collect(), + + listen_port: u.listen_port as u16, + is_lighthouse: false, + is_relay: false, + created_at: "".to_string(), + is_blocked: false, + metadata: HostResponseMetadata { + last_seen_at: None, + version: "".to_string(), + platform: "".to_string(), + update_available: false, + }, + }) + } + + let count = models_mapped.len() as u64; + + HttpResponse::Ok().json(ListHostsResponse { + data: models_mapped, + metadata: ListHostsResponseMetadata { + total_count: total, + has_next_page: cursor.page+1 != pages, + has_prev_page: cursor.page != 0, + prev_cursor: if cursor.page != 0 { + match (Cursor { page: cursor.page - 1 }).try_into() { + Ok(r) => Some(r), + Err(_) => None + } + } else { + None + }, + next_cursor: if cursor.page+1 != pages { + match (Cursor { page: cursor.page + 1 }).try_into() { + Ok(r) => Some(r), + Err(_) => None + } + } else { + None + }, + page: if opts.include_counts { + Some(ListHostsResponseMetadataPage { + count, + start: opts.page_size * cursor.page, + }) + } else { None }, + }, + }) +} \ No newline at end of file diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index ba01da3..011b224 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -5,4 +5,5 @@ pub mod verify_totp_authenticators; pub mod networks; pub mod organization; pub mod roles; -pub mod trifid; \ No newline at end of file +pub mod trifid; +pub mod hosts; \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/host.rs b/trifid-api/trifid_api_entities/src/entity/host.rs index f2d689a..6dc6594 100644 --- a/trifid-api/trifid_api_entities/src/entity/host.rs +++ b/trifid-api/trifid_api_entities/src/entity/host.rs @@ -15,6 +15,7 @@ pub struct Model { pub is_lighthouse: bool, pub is_relay: bool, pub counter: i32, + pub created_at: i64, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs index 9ad0b96..c867ef6 100644 --- a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs +++ b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs @@ -20,6 +20,7 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Host::IsLighthouse).boolean().not_null()) .col(ColumnDef::new(Host::IsRelay).boolean().not_null()) .col(ColumnDef::new(Host::Counter).unsigned().not_null()) + .col(ColumnDef::new(Host::CreatedAt).big_integer().not_null()) .foreign_key( ForeignKey::create() .from(Host::Table, Host::Network) @@ -65,5 +66,6 @@ pub enum Host { ListenPort, IsLighthouse, IsRelay, - Counter + Counter, + CreatedAt } From 5bb1f8ec71089e4a12273b1700c7c2b52759e9ed Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 27 Apr 2023 21:47:18 -0400 Subject: [PATCH 31/39] POST /v1/hosts --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 296 +++++++++++++++++- .../trifid_api_entities/src/entity/host.rs | 5 + .../m20230427_170037_create_table_hosts.rs | 12 +- .../trifid_api_entities/src/entity/api_key.rs | 41 +++ .../src/entity/api_key_scope.rs | 32 ++ .../src/entity/auth_token.rs | 32 ++ .../src/entity/firewall_rule.rs | 38 +++ .../src/entity/magic_link.rs | 32 ++ .../trifid_api_entities/src/entity/mod.rs | 16 + .../trifid_api_entities/src/entity/network.rs | 15 + .../src/entity/organization.rs | 19 ++ .../trifid_api_entities/src/entity/prelude.rs | 14 + .../trifid_api_entities/src/entity/role.rs | 13 + .../src/entity/session_token.rs | 13 + .../src/entity/signing_ca.rs | 13 + .../src/entity/totp_authenticator.rs | 13 + .../trifid_api_entities/src/entity/user.rs | 21 ++ 18 files changed, 610 insertions(+), 16 deletions(-) create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key_scope.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/auth_token.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/firewall_rule.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/magic_link.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs create mode 100644 trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 12365dd..e3f7c7f 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -96,6 +96,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::roles::update_role_request) .service(routes::v1::trifid::trifid_extensions) .service(routes::v1::hosts::get_hosts) + .service(routes::v1::hosts::create_hosts_request) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index 04a783d..c047638 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -18,19 +18,27 @@ // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. +// +//#POST /v1/hosts t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. -use std::net::SocketAddrV4; +use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; -use actix_web::{HttpRequest, HttpResponse, get}; -use actix_web::web::{Data, Query}; +use std::time::{SystemTime, UNIX_EPOCH}; +use actix_web::{HttpRequest, HttpResponse, get, post}; +use actix_web::web::{Data, Json, Query}; +use chrono::{TimeZone, Utc}; use log::error; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait}; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait}; use serde::{Serialize, Deserialize}; use trifid_api_entities::entity::{host, host_static_address, network, organization}; use crate::AppState; use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::cursor::Cursor; use crate::error::{APIError, APIErrorsResponse}; +use crate::tokens::random_id; #[derive(Serialize, Deserialize)] pub struct ListHostsRequestOpts { @@ -112,7 +120,7 @@ fn page_default() -> u64 { 25 } #[get("/v1/hosts")] pub async fn get_hosts(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { - // For this endpoint, you either need to be a fully authenticated user OR a token with roles:list + // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:list let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); let api_token_info = enforce_api_token(&req_info, &["hosts:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); @@ -231,7 +239,7 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, } }; - let host_pages = host::Entity::find().filter(host::Column::Network.eq(net_id)).order_by_asc(host::Column::CreatedAt).paginate(&db.conn, opts.page_size); + let host_pages = host::Entity::find().filter(host::Column::Network.eq(&net_id)).order_by_asc(host::Column::CreatedAt).paginate(&db.conn, opts.page_size); let total = match host_pages.num_items().await { Ok(r) => r, @@ -308,17 +316,16 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, name: u.name, ip_address: u.ip, static_addresses: ips.iter().map(|u| SocketAddrV4::from_str(&u.address).unwrap()).collect(), - listen_port: u.listen_port as u16, - is_lighthouse: false, - is_relay: false, - created_at: "".to_string(), - is_blocked: false, + is_lighthouse: u.is_lighthouse, + is_relay: u.is_relay, + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + is_blocked: u.is_blocked, metadata: HostResponseMetadata { - last_seen_at: None, - version: "".to_string(), - platform: "".to_string(), - update_available: false, + last_seen_at: Some(Utc.timestamp_opt(u.last_seen_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string()), + version: u.last_version.to_string(), + platform: u.last_platform, + update_available: u.last_out_of_date, }, }) } @@ -355,4 +362,263 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, } else { None }, }, }) +} + +#[derive(Serialize, Deserialize)] +pub struct CreateHostRequest { + pub name: String, + #[serde(rename = "networkID")] + pub network_id: String, + #[serde(rename = "roleID", default)] + pub role_id: Option, + #[serde(rename = "ipAddress")] + pub ip_address: Ipv4Addr, + #[serde(rename = "staticAddresses", default)] + pub static_addresses: Vec, + #[serde(rename = "listenPort")] + pub listen_port: u16, + #[serde(rename = "isLighthouse")] + pub is_lighthouse: bool, + #[serde(rename = "isRelay")] + pub is_relay: bool +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CreateHostResponse { + pub data: HostResponse, + pub metadata: CreateHostResponseMetadata +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct CreateHostResponseMetadata {} + +#[post("/v1/hosts")] +pub async fn create_hosts_request(req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:create + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:create scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let net_id; + + let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + + if net_id != req.network_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_WRONG_NET".to_string(), + message: "The network on the request does not match the network associated with this token or user.".to_string(), + path: None + } + ], + }) + } + + if req.is_lighthouse && req.is_relay { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_CANNOT_BE_RELAY_AND_LIGHTHOUSE".to_string(), + message: "A host cannot be a relay and a lighthouse at the same time.".to_string(), + path: None + } + ], + }) + } + + if req.is_lighthouse || req.is_relay && req.static_addresses.is_empty() { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NEEDS_STATIC_ADDR".to_string(), + message: "A relay or lighthouse requires at least one static address.".to_string(), + path: None + } + ], + }) + } + + let new_host_model = host::Model { + id: random_id("host"), + name: req.name.clone(), + network: net_id.clone(), + role: req.role_id.clone().unwrap_or("".to_string()), + ip: req.ip_address.to_string(), + listen_port: req.listen_port as i32, + is_lighthouse: req.is_lighthouse, + is_relay: req.is_relay, + counter: 0, + created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs() as i64, + is_blocked: false, + last_seen_at: 0, + last_version: 0, + last_platform: "".to_string(), + last_out_of_date: false, + }; + let static_addresses: Vec = req.static_addresses.iter().map(|u| { + host_static_address::Model { + id: random_id("hsaddress"), + host: new_host_model.id.clone(), + address: u.to_string(), + } + }).collect(); + + let new_host_model_clone = new_host_model.clone(); + let static_addresses_clone = static_addresses.clone(); + + let new_host_active_model = new_host_model.into_active_model(); + match new_host_active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later".to_string(), + path: None + } + ], + }) + } + } + + for rule in &static_addresses_clone { + let active_model = rule.clone().into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later".to_string(), + path: None + } + ], + }) + } + } + } + + HttpResponse::Ok().json(CreateHostResponse { + data: HostResponse { + id: new_host_model_clone.id, + organization_id: org_id, + network_id: net_id, + role_id: new_host_model_clone.role, + name: new_host_model_clone.name, + ip_address: req.ip_address.to_string(), + static_addresses: req.static_addresses.clone(), + listen_port: req.listen_port, + is_lighthouse: req.is_lighthouse, + is_relay: req.is_relay, + created_at: "".to_string(), + is_blocked: false, + metadata: HostResponseMetadata { + last_seen_at: Some(Utc.timestamp_opt(new_host_model_clone.last_seen_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string()), + version: new_host_model_clone.last_version.to_string(), + platform: new_host_model_clone.last_platform, + update_available: new_host_model_clone.last_out_of_date, + }, + }, + metadata: CreateHostResponseMetadata {}, + }) } \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/host.rs b/trifid-api/trifid_api_entities/src/entity/host.rs index 6dc6594..73c4cf7 100644 --- a/trifid-api/trifid_api_entities/src/entity/host.rs +++ b/trifid-api/trifid_api_entities/src/entity/host.rs @@ -16,6 +16,11 @@ pub struct Model { pub is_relay: bool, pub counter: i32, pub created_at: i64, + pub is_blocked: bool, + pub last_seen_at: i64, + pub last_version: i32, + pub last_platform: String, + pub last_out_of_date: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs index c867ef6..fc6bfe5 100644 --- a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs +++ b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs @@ -21,6 +21,11 @@ impl MigrationTrait for Migration { .col(ColumnDef::new(Host::IsRelay).boolean().not_null()) .col(ColumnDef::new(Host::Counter).unsigned().not_null()) .col(ColumnDef::new(Host::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Host::IsBlocked).boolean().not_null()) + .col(ColumnDef::new(Host::LastSeenAt).big_integer().not_null()) + .col(ColumnDef::new(Host::LastVersion).integer().not_null()) + .col(ColumnDef::new(Host::LastPlatform).string().not_null()) + .col(ColumnDef::new(Host::LastOutOfDate).boolean().not_null()) .foreign_key( ForeignKey::create() .from(Host::Table, Host::Network) @@ -67,5 +72,10 @@ pub enum Host { IsLighthouse, IsRelay, Counter, - CreatedAt + CreatedAt, + IsBlocked, + LastSeenAt, + LastVersion, + LastPlatform, + LastOutOfDate } diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key.rs new file mode 100644 index 0000000..04aa795 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key.rs @@ -0,0 +1,41 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub key: String, + pub organization: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key_scope::Entity")] + ApiKeyScope, + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKeyScope.def() + } +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key_scope.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key_scope.rs new file mode 100644 index 0000000..1deffbc --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/api_key_scope.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "api_key_scope")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub scope: String, + pub api_key: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::api_key::Entity", + from = "Column::ApiKey", + to = "super::api_key::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + ApiKey, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/auth_token.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/auth_token.rs new file mode 100644 index 0000000..db14039 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/auth_token.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "auth_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/firewall_rule.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/firewall_rule.rs new file mode 100644 index 0000000..6a8c64f --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/firewall_rule.rs @@ -0,0 +1,38 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "firewall_rule")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub role: String, + pub protocol: String, + pub description: String, + pub allowed_role_id: Option, + pub port_range_from: i32, + pub port_range_to: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::AllowedRoleId", + to = "super::role::Column::Id", + on_update = "NoAction", + on_delete = "Cascade" + )] + Role2, + #[sea_orm( + belongs_to = "super::role::Entity", + from = "Column::Role", + to = "super::role::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Role1, +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/magic_link.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/magic_link.rs new file mode 100644 index 0000000..1707d7e --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/magic_link.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "magic_link")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs new file mode 100644 index 0000000..41382c0 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs @@ -0,0 +1,16 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub mod prelude ; + +pub mod api_key ; +pub mod api_key_scope ; +pub mod auth_token ; +pub mod firewall_rule ; +pub mod magic_link ; +pub mod network ; +pub mod organization ; +pub mod role ; +pub mod session_token ; +pub mod signing_ca ; +pub mod totp_authenticator ; +pub mod user ; \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs new file mode 100644 index 0000000..832f0e8 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs @@ -0,0 +1,15 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "network")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub cidr : String , # [sea_orm (unique)] pub organization : String , # [sea_orm (unique)] pub signing_ca : String , pub created_at : i64 , pub name : String , pub lighthouses_as_relays : bool , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::organization::Entity" , from = "Column::Organization" , to = "super::organization::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] Organization , # [sea_orm (belongs_to = "super::signing_ca::Entity" , from = "Column::SigningCa" , to = "super::signing_ca::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] SigningCa , } + +impl Related < super :: organization :: Entity > for Entity { fn to () -> RelationDef { Relation :: Organization . def () } } + +impl Related < super :: signing_ca :: Entity > for Entity { fn to () -> RelationDef { Relation :: SigningCa . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs new file mode 100644 index 0000000..fdaf8e3 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs @@ -0,0 +1,19 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "organization")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub name : String , # [sea_orm (unique)] pub owner : String , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (has_many = "super::api_key::Entity")] ApiKey , # [sea_orm (has_one = "super::network::Entity")] Network , # [sea_orm (has_many = "super::role::Entity")] Role , # [sea_orm (belongs_to = "super::user::Entity" , from = "Column::Owner" , to = "super::user::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] User , } + +impl Related < super :: api_key :: Entity > for Entity { fn to () -> RelationDef { Relation :: ApiKey . def () } } + +impl Related < super :: network :: Entity > for Entity { fn to () -> RelationDef { Relation :: Network . def () } } + +impl Related < super :: role :: Entity > for Entity { fn to () -> RelationDef { Relation :: Role . def () } } + +impl Related < super :: user :: Entity > for Entity { fn to () -> RelationDef { Relation :: User . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs new file mode 100644 index 0000000..2314fbc --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs @@ -0,0 +1,14 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +pub use super :: api_key :: Entity as ApiKey ; +pub use super :: api_key_scope :: Entity as ApiKeyScope ; +pub use super :: auth_token :: Entity as AuthToken ; +pub use super :: firewall_rule :: Entity as FirewallRule ; +pub use super :: magic_link :: Entity as MagicLink ; +pub use super :: network :: Entity as Network ; +pub use super :: organization :: Entity as Organization ; +pub use super :: role :: Entity as Role ; +pub use super :: session_token :: Entity as SessionToken ; +pub use super :: signing_ca :: Entity as SigningCa ; +pub use super :: totp_authenticator :: Entity as TotpAuthenticator ; +pub use super :: user :: Entity as User ; \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs new file mode 100644 index 0000000..9ce3a13 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs @@ -0,0 +1,13 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "role")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , # [sea_orm (unique)] pub name : String , pub description : String , pub organization : String , pub created_at : i64 , pub modified_at : i64 , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::organization::Entity" , from = "Column::Organization" , to = "super::organization::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] Organization , } + +impl Related < super :: organization :: Entity > for Entity { fn to () -> RelationDef { Relation :: Organization . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs new file mode 100644 index 0000000..23d37ba --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs @@ -0,0 +1,13 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "session_token")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub user : String , pub expires_on : i64 , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::user::Entity" , from = "Column::User" , to = "super::user::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] User , } + +impl Related < super :: user :: Entity > for Entity { fn to () -> RelationDef { Relation :: User . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs new file mode 100644 index 0000000..72516a6 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs @@ -0,0 +1,13 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "signing_ca")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub organization : String , pub cert : String , # [sea_orm (unique)] pub key : String , pub expires : i64 , # [sea_orm (unique)] pub nonce : String , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (has_one = "super::network::Entity")] Network , } + +impl Related < super :: network :: Entity > for Entity { fn to () -> RelationDef { Relation :: Network . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs new file mode 100644 index 0000000..630c391 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs @@ -0,0 +1,13 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "totp_authenticator")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , # [sea_orm (unique)] pub secret : String , # [sea_orm (unique)] pub url : String , pub verified : bool , pub expires_on : i64 , # [sea_orm (unique)] pub user : String , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::user::Entity" , from = "Column::User" , to = "super::user::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] User , } + +impl Related < super :: user :: Entity > for Entity { fn to () -> RelationDef { Relation :: User . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs new file mode 100644 index 0000000..23fc564 --- /dev/null +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs @@ -0,0 +1,21 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + + + +use sea_orm :: entity :: prelude :: * ; + +# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "user")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , # [sea_orm (unique)] pub email : String , } + +# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (has_many = "super::auth_token::Entity")] AuthToken , # [sea_orm (has_many = "super::magic_link::Entity")] MagicLink , # [sea_orm (has_one = "super::organization::Entity")] Organization , # [sea_orm (has_many = "super::session_token::Entity")] SessionToken , # [sea_orm (has_one = "super::totp_authenticator::Entity")] TotpAuthenticator , } + +impl Related < super :: auth_token :: Entity > for Entity { fn to () -> RelationDef { Relation :: AuthToken . def () } } + +impl Related < super :: magic_link :: Entity > for Entity { fn to () -> RelationDef { Relation :: MagicLink . def () } } + +impl Related < super :: organization :: Entity > for Entity { fn to () -> RelationDef { Relation :: Organization . def () } } + +impl Related < super :: session_token :: Entity > for Entity { fn to () -> RelationDef { Relation :: SessionToken . def () } } + +impl Related < super :: totp_authenticator :: Entity > for Entity { fn to () -> RelationDef { Relation :: TotpAuthenticator . def () } } + +impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file From d421abdb7a69bea93ed53713033dcfe9e99f236e Mon Sep 17 00:00:00 2001 From: core Date: Mon, 8 May 2023 20:56:31 -0400 Subject: [PATCH 32/39] add tmpexec.bin to gitignore in case client buildscript fails --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3208d77..959c695 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ target -pg_data \ No newline at end of file +pg_data +tfclient/tmpexec.bin \ No newline at end of file From 4ca0b5468630a76e68534a1d2a86d95bab418711 Mon Sep 17 00:00:00 2001 From: core Date: Mon, 8 May 2023 20:57:08 -0400 Subject: [PATCH 33/39] add /v1/hosts/host - bugfix! correct time formatting and use constant for it --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 213 ++++++++++++++++++++++++++- trifid-api/src/routes/v1/networks.rs | 7 +- trifid-api/src/routes/v1/roles.rs | 17 ++- trifid-api/src/timers.rs | 2 + 5 files changed, 224 insertions(+), 16 deletions(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index e3f7c7f..a3b39f9 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -97,6 +97,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::trifid::trifid_extensions) .service(routes::v1::hosts::get_hosts) .service(routes::v1::hosts::create_hosts_request) + .service(routes::v1::hosts::get_host) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index c047638..698a40b 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -23,12 +23,17 @@ // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. +// +//#GET /v1/hosts/{host_id} t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use actix_web::{HttpRequest, HttpResponse, get, post}; -use actix_web::web::{Data, Json, Query}; +use actix_web::web::{Data, Json, Path, Query}; use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait}; @@ -38,6 +43,7 @@ use crate::AppState; use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::cursor::Cursor; use crate::error::{APIError, APIErrorsResponse}; +use crate::timers::TIME_FORMAT; use crate::tokens::random_id; #[derive(Serialize, Deserialize)] @@ -319,10 +325,10 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, listen_port: u.listen_port as u16, is_lighthouse: u.is_lighthouse, is_relay: u.is_relay, - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), is_blocked: u.is_blocked, metadata: HostResponseMetadata { - last_seen_at: Some(Utc.timestamp_opt(u.last_seen_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string()), + last_seen_at: Some(Utc.timestamp_opt(u.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), version: u.last_version.to_string(), platform: u.last_platform, update_available: u.last_out_of_date, @@ -610,10 +616,10 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe listen_port: req.listen_port, is_lighthouse: req.is_lighthouse, is_relay: req.is_relay, - created_at: "".to_string(), + created_at: Utc.timestamp_opt(new_host_model_clone.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), is_blocked: false, metadata: HostResponseMetadata { - last_seen_at: Some(Utc.timestamp_opt(new_host_model_clone.last_seen_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string()), + last_seen_at: Some(Utc.timestamp_opt(new_host_model_clone.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), version: new_host_model_clone.last_version.to_string(), platform: new_host_model_clone.last_platform, update_available: new_host_model_clone.last_out_of_date, @@ -621,4 +627,201 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe }, metadata: CreateHostResponseMetadata {}, }) +} + +#[derive(Serialize, Deserialize)] +pub struct GetHostResponse { + pub data: HostResponse, + pub metadata: GetHostResponseMetadata +} +#[derive(Serialize, Deserialize)] +pub struct GetHostResponseMetadata {} + +#[get("/v1/hosts/{host_id}")] +pub async fn get_host(id: Path, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:read scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let net_id; + + let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + + let host = match host::Entity::find().filter(host::Column::Id.eq(id.into_inner())).one(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let host = match host { + Some(h) => h, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ], + }) + } + }; + + if host.network != net_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ], + }) + } + + let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + HttpResponse::Ok().json(GetHostResponse { + data: HostResponse { + id: host.id, + organization_id: org_id, + network_id: net_id, + role_id: host.role, + name: host.name, + ip_address: host.ip.to_string(), + static_addresses: static_addresses.iter().map(|u| SocketAddrV4::from_str(&u.address).unwrap()).collect(), + listen_port: host.listen_port as u16, + is_lighthouse: host.is_lighthouse, + is_relay: host.is_relay, + created_at: Utc.timestamp_opt(host.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + is_blocked: host.is_blocked, + metadata: HostResponseMetadata { + last_seen_at: Some(Utc.timestamp_opt(host.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), + version: host.last_version.to_string(), + platform: host.last_platform, + update_available: host.last_out_of_date, + }, + }, + metadata: GetHostResponseMetadata {}, + }) } \ No newline at end of file diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 22f6a6e..8a2c010 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -36,6 +36,7 @@ use crate::error::{APIError, APIErrorsResponse}; use trifid_api_entities::entity::organization; use trifid_api_entities::entity::network; use crate::cursor::Cursor; +use crate::timers::TIME_FORMAT; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksResponse { @@ -52,7 +53,7 @@ pub struct GetNetworksResponseData { #[serde(rename = "signingCAID")] pub signing_ca_id: String, #[serde(rename = "createdAt")] - pub created_at: String, // 2023-03-22T18:55:47.009Z, %Y-%m-%dT%H-%M-%S%.3fZ + pub created_at: String, // 2023-03-22T18:55:47.009Z pub name: String, #[serde(rename = "lighthousesAsRelays")] pub lighthouses_as_relays: bool @@ -235,7 +236,7 @@ pub async fn get_networks(opts: Query, req_info: HttpReq cidr: u.cidr.clone(), organization_id: u.organization.clone(), signing_ca_id: u.signing_ca.clone(), - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), name: u.name.clone(), lighthouses_as_relays: u.lighthouses_as_relays, } @@ -330,7 +331,7 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D cidr: network.cidr, organization_id: network.organization, signing_ca_id: network.signing_ca, - created_at: Utc.timestamp_opt(network.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + created_at: Utc.timestamp_opt(network.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), name: network.name, lighthouses_as_relays: network.lighthouses_as_relays, }, diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index fd9d500..fcb9fbe 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -51,6 +51,7 @@ use trifid_api_entities::entity::role; use crate::cursor::Cursor; use crate::tokens::random_id; use actix_web::delete; +use crate::timers::TIME_FORMAT; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateRoleRequest { @@ -260,8 +261,8 @@ pub async fn create_role_request(req: Json, req_info: HttpReq name: Some(new_role_model_clone.name.clone()), description: Some(new_role_model_clone.description), firewall_rules: req.firewall_rules.clone(), - created_at: Utc.timestamp_opt(new_role_model_clone.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), - modified_at: Utc.timestamp_opt(new_role_model_clone.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + created_at: Utc.timestamp_opt(new_role_model_clone.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + modified_at: Utc.timestamp_opt(new_role_model_clone.modified_at, 0).unwrap().format(TIME_FORMAT).to_string(), }, metadata: RoleCreateResponseMetadata {}, }) @@ -509,8 +510,8 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, name: Some(u.name), description: Some(u.description), firewall_rules: rules, - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), - modified_at: Utc.timestamp_opt(u.modified_at, 0).unwrap().format("%Y-%m-%dT%H-%M-%S%.3fZ").to_string(), + created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + modified_at: Utc.timestamp_opt(u.modified_at, 0).unwrap().format(TIME_FORMAT).to_string(), }) } @@ -648,8 +649,8 @@ pub async fn get_role(net: Path, req_info: HttpRequest, db: Data, req: Json u64 { (SystemTime::now() + Duration::from_secs(seconds)).duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() } From eaa1e86fef5e78126291d00ed4c0599a3a225be3 Mon Sep 17 00:00:00 2001 From: core Date: Mon, 8 May 2023 21:46:33 -0400 Subject: [PATCH 34/39] DELETE /v1/hosts/id --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 223 +++++++++++++++++++++++++++++- 2 files changed, 222 insertions(+), 2 deletions(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index a3b39f9..0f8b998 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -98,6 +98,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::hosts::get_hosts) .service(routes::v1::hosts::create_hosts_request) .service(routes::v1::hosts::get_host) + .service(routes::v1::hosts::delete_host) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index 698a40b..05e48fc 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -28,15 +28,20 @@ // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. +// +//#DELETE /v1/hosts/{host_id} t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{HttpRequest, HttpResponse, get, post}; +use actix_web::{HttpRequest, HttpResponse, get, post, delete}; use actix_web::web::{Data, Json, Path, Query}; use chrono::{TimeZone, Utc}; use log::error; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait}; +use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait, ModelTrait}; use serde::{Serialize, Deserialize}; use trifid_api_entities::entity::{host, host_static_address, network, organization}; use crate::AppState; @@ -824,4 +829,218 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:delete + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:delete scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let net_id; + + let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + + let host = match host::Entity::find().filter(host::Column::Id.eq(id.into_inner())).one(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let host = match host { + Some(h) => h, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ], + }) + } + }; + + if host.network != net_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ], + }) + } + + let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + match host.delete(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + } + + for address in static_addresses { + match address.delete(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + } + } + + HttpResponse::Ok().json(DeleteHostResponse { + data: DeleteHostData {}, + metadata: DeleteHostMetadata {}, + }) } \ No newline at end of file From a068741986ca7750b5a7c6e0896c71a635f4f5fa Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Tue, 9 May 2023 10:38:31 -0400 Subject: [PATCH 35/39] fix updating, PUT /v1/hosts/host --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 307 +++++++++++++++++- trifid-api/src/routes/v1/roles.rs | 96 ++++-- trifid-api/src/routes/v1/trifid.rs | 4 +- .../m20230427_170037_create_table_hosts.rs | 12 +- 5 files changed, 383 insertions(+), 37 deletions(-) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 0f8b998..1679f4d 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -99,6 +99,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::hosts::create_hosts_request) .service(routes::v1::hosts::get_host) .service(routes::v1::hosts::delete_host) + .service(routes::v1::hosts::edit_host) }).bind(CONFIG.server.bind)?.run().await?; Ok(()) diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index 05e48fc..152ae12 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -33,21 +33,29 @@ // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. +// +//#PUT /v1/hosts/{host_id} t+parity:full t+type:documented t+status:done t+feature:definednetworking t+ext:t+feature:extended_hosts +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. +// This endpoint has additional functionality enabled by the extended_hosts feature flag. use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{HttpRequest, HttpResponse, get, post, delete}; +use actix_web::{HttpRequest, HttpResponse, get, post, delete, put}; use actix_web::web::{Data, Json, Path, Query}; use chrono::{TimeZone, Utc}; -use log::error; +use log::{debug, error}; use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait, ModelTrait}; +use sea_orm::ActiveValue::Set; use serde::{Serialize, Deserialize}; use trifid_api_entities::entity::{host, host_static_address, network, organization}; use crate::AppState; use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::cursor::Cursor; use crate::error::{APIError, APIErrorsResponse}; +use crate::routes::v1::trifid::SUPPORTED_EXTENSIONS; use crate::timers::TIME_FORMAT; use crate::tokens::random_id; @@ -1043,4 +1051,299 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data, + #[serde(rename = "listenPort")] + pub listen_port: u16, + // t+features:extended_hosts + pub name: Option, + // t+features:extended_hosts + pub ip: Option +} + +#[derive(Serialize, Deserialize)] +pub struct EditHostExtensionQuery { + pub extension: Option +} + +#[derive(Serialize, Deserialize)] +pub struct EditHostResponse { + pub data: HostResponse, + pub metadata: EditHostResponseMetadata +} + +#[derive(Serialize, Deserialize)] +pub struct EditHostResponseMetadata {} + +#[put("/v1/hosts/{host_id}")] +pub async fn edit_host(id: Path, query: Query, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:edit + let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:edit"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:edit scope".to_string(), + path: None, + } + ], + }) + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }) + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!() + }; + + let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + } + }; + + let net_id; + + let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead.".to_string(), + path: None + } + ], + }) + } + + let host = match host::Entity::find().filter(host::Column::Id.eq(id.into_inner())).one(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let host = match host { + Some(h) => h, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ], + }) + } + }; + + if host.network != net_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ], + }) + } + + let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + for address in static_addresses { + match address.delete(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + } + } + + let mut host_clone = host.clone(); + let mut host_active_model = host_clone.into_active_model(); + + host_active_model.listen_port = Set(req.listen_port as i32); + + debug!("{:?} {} {:?} {:?} {}", query.extension, SUPPORTED_EXTENSIONS.contains(&"extended_hosts"), req.name, req.ip, query.extension == Some("extended_hosts".to_string())); + + if query.extension == Some("extended_hosts".to_string()) && SUPPORTED_EXTENSIONS.contains(&"extended_hosts") { + if let Some(new_host_name) = req.name.clone() { + debug!("updated host name"); + host_active_model.name = Set(new_host_name); + } + if let Some(new_host_ip) = req.ip { + debug!("updated host ip"); + host_active_model.ip = Set(new_host_ip.to_string()); + } + } + + + let host = match host_active_model.update(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later.".to_string(), + path: None + } + ], + }) + } + }; + + let static_addresses: Vec = req.static_addresses.iter().map(|u| { + host_static_address::Model { + id: random_id("hsaddress"), + host: host.id.clone(), + address: u.to_string(), + } + }).collect(); + + for rule in &static_addresses { + let active_model = rule.clone().into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later".to_string(), + path: None + } + ], + }) + } + } + } + + HttpResponse::Ok().json(EditHostResponse { + data: HostResponse { + id: host.id, + organization_id: org_id, + network_id: net_id, + role_id: host.role, + name: host.name, + ip_address: host.ip.to_string(), + static_addresses: req.static_addresses.clone(), + listen_port: host.listen_port as u16, + is_lighthouse: host.is_lighthouse, + is_relay: host.is_relay, + created_at: Utc.timestamp_opt(host.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + is_blocked: host.is_blocked, + metadata: HostResponseMetadata { + last_seen_at: Some(Utc.timestamp_opt(host.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), + version: host.last_version.to_string(), + platform: host.last_platform, + update_available: host.last_out_of_date, + }, + }, + metadata: EditHostResponseMetadata {}, + }) } \ No newline at end of file diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index fcb9fbe..990dfb9 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -51,6 +51,7 @@ use trifid_api_entities::entity::role; use crate::cursor::Cursor; use crate::tokens::random_id; use actix_web::delete; +use sea_orm::ActiveValue::Set; use crate::timers::TIME_FORMAT; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -770,9 +771,15 @@ pub struct RoleDeleteResponseData {} #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RoleDeleteResponseMetadata {} +#[derive(Serialize, Deserialize)] +pub struct RoleUpdateRequest { + pub description: String, + #[serde(rename = "firewallRules")] + pub firewall_rules: Vec +} #[put("/v1/roles/{role_id}")] -pub async fn update_role_request(role: Path, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn update_role_request(role: Path, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:create let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); @@ -878,19 +885,62 @@ pub async fn update_role_request(role: Path, req: Json r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + let role = match role { + Some(r) => r, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it.".to_string(), + path: None + } + ] + }) + } + }; + + let mut role_active_model = role.clone().into_active_model(); + + role_active_model.modified_at = Set(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64); + role_active_model.description = Set(req.description.clone()); + + let role = match role_active_model.update(&db.conn).await { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } }; let firewall_rules: Vec = req.firewall_rules.iter().map(|i| { firewall_rule::Model { id: random_id("rule"), - role: new_role_model.id.clone(), + role: role.id.clone(), protocol: i.protocol.to_string(), description: i.description.clone(), allowed_role_id: i.allowed_role_id.clone(), @@ -899,26 +949,8 @@ pub async fn update_role_request(role: Path, req: Json (), - Err(e) => { - error!("database error: {}", e); - return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new role. Please try again later".to_string(), - path: None - } - ], - }) - } - } - for rule in &firewall_rules_clone { let active_model = rule.clone().into_active_model(); match active_model.insert(&db.conn).await { @@ -940,12 +972,12 @@ pub async fn update_role_request(role: Path, req: Json @@ -43,6 +45,6 @@ pub struct TrifidExtensionsResponse { #[get("/v1/trifid_extensions")] pub async fn trifid_extensions() -> HttpResponse { HttpResponse::Ok().json(TrifidExtensionsResponse { - extensions: vec!["definednetworking".to_string(), "trifidextensions".to_string(), "extended_roles".to_string(), "extended_hosts".to_string()], + extensions: SUPPORTED_EXTENSIONS.iter().map(|u| u.to_string()).collect(), }) } \ No newline at end of file diff --git a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs index fc6bfe5..c1de66f 100644 --- a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs +++ b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs @@ -42,12 +42,20 @@ impl MigrationTrait for Migration { ) .index( Index::create() - .name("idx-hosts-id-name-unique") + .name("idx-hosts-net-name-unique") .table(Host::Table) - .col(Host::Id) + .col(Host::Network) .col(Host::Name) .unique() ) + .index( + Index::create() + .name("idx-hosts-net-ip-unique") + .table(Host::Table) + .col(Host::Network) + .col(Host::IP) + .unique() + ) .to_owned() ).await } From 6d01d8703b718b87675a7455eaac55fe386c89c7 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Wed, 10 May 2023 20:32:19 -0400 Subject: [PATCH 36/39] host blocking + general code cleanup --- trifid-api/build.rs | 2 +- trifid-api/src/auth_tokens.rs | 114 +- trifid-api/src/config.rs | 52 +- trifid-api/src/crypto.rs | 24 +- trifid-api/src/cursor.rs | 12 +- trifid-api/src/error.rs | 102 +- trifid-api/src/magic_link.rs | 4 +- trifid-api/src/main.rs | 46 +- trifid-api/src/routes/mod.rs | 2 +- trifid-api/src/routes/v1/auth/magic_link.rs | 90 +- trifid-api/src/routes/v1/auth/mod.rs | 2 +- trifid-api/src/routes/v1/auth/totp.rs | 142 +-- .../src/routes/v1/auth/verify_magic_link.rs | 127 +- trifid-api/src/routes/v1/hosts.rs | 1103 ++++++++++++----- trifid-api/src/routes/v1/mod.rs | 8 +- trifid-api/src/routes/v1/networks.rs | 179 ++- trifid-api/src/routes/v1/organization.rs | 85 +- trifid-api/src/routes/v1/roles.rs | 612 +++++---- trifid-api/src/routes/v1/signup.rs | 110 +- .../src/routes/v1/totp_authenticators.rs | 144 ++- trifid-api/src/routes/v1/trifid.rs | 13 +- .../routes/v1/verify_totp_authenticators.rs | 171 ++- trifid-api/src/timers.rs | 7 +- trifid-api/src/tokens.rs | 23 +- trifid-api/trifid_api_entities/src/lib.rs | 2 +- .../m20230402_162601_create_table_users.rs | 11 +- ...0230402_183515_create_table_magic_links.rs | 53 +- ...0402_213712_create_table_session_tokens.rs | 51 +- ...30402_232316_create_table_organizations.rs | 48 +- .../m20230402_233043_create_table_api_keys.rs | 40 +- ...402_233047_create_table_api_keys_scopes.rs | 44 +- ...234025_create_table_totp_authenticators.rs | 73 +- ...0230403_002256_create_table_auth_tokens.rs | 51 +- ...0230403_142517_create_table_signing_cas.rs | 45 +- .../m20230403_173431_create_table_networks.rs | 81 +- .../m20230404_133809_create_table_roles.rs | 45 +- ...0404_133813_create_table_firewall_rules.rs | 78 +- .../m20230427_170037_create_table_hosts.rs | 106 +- ...517_create_table_hosts_static_addresses.rs | 19 +- ...529_create_table_hosts_config_overrides.rs | 61 +- .../trifid_api_entities/src/entity/mod.rs | 26 +- .../trifid_api_entities/src/entity/network.rs | 53 +- .../src/entity/organization.rs | 58 +- .../trifid_api_entities/src/entity/prelude.rs | 24 +- .../trifid_api_entities/src/entity/role.rs | 37 +- .../src/entity/session_token.rs | 33 +- .../src/entity/signing_ca.rs | 32 +- .../src/entity/totp_authenticator.rs | 39 +- .../trifid_api_entities/src/entity/user.rs | 59 +- 49 files changed, 2745 insertions(+), 1598 deletions(-) diff --git a/trifid-api/build.rs b/trifid-api/build.rs index 3c284bb..b8a52dd 100644 --- a/trifid-api/build.rs +++ b/trifid-api/build.rs @@ -1,3 +1,3 @@ fn main() { println!("cargo:rerun-if-changed=migrations/"); -} \ No newline at end of file +} diff --git a/trifid-api/src/auth_tokens.rs b/trifid-api/src/auth_tokens.rs index f548044..abfe8cb 100644 --- a/trifid-api/src/auth_tokens.rs +++ b/trifid-api/src/auth_tokens.rs @@ -14,93 +14,125 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::error::Error; use actix_web::HttpRequest; +use std::error::Error; -use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; +use crate::timers::expired; use crate::tokens::get_token_type; -use trifid_api_entities::entity::{auth_token, session_token}; +use sea_orm::{ColumnTrait, Condition, DatabaseConnection, EntityTrait, QueryFilter}; use trifid_api_entities::entity::api_key; use trifid_api_entities::entity::api_key_scope; use trifid_api_entities::entity::user; -use crate::timers::expired; +use trifid_api_entities::entity::{auth_token, session_token}; pub enum TokenInfo { SessionToken(SessionTokenInfo), AuthToken(AuthTokenInfo), ApiToken(ApiTokenInfo), - NotPresent + NotPresent, } pub struct SessionTokenInfo { pub token: String, pub user: SessionTokenUser, - pub expires_at: i64 + pub expires_at: i64, } pub struct SessionTokenUser { pub id: String, - pub email: String + pub email: String, } pub struct ApiTokenInfo { pub scopes: Vec, - pub organization: String + pub organization: String, } pub struct AuthTokenInfo { pub token: String, - pub session_info: SessionTokenInfo + pub session_info: SessionTokenInfo, } -pub async fn enforce_session(req: &HttpRequest, db: &DatabaseConnection) -> Result> { - let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; +pub async fn enforce_session( + req: &HttpRequest, + db: &DatabaseConnection, +) -> Result> { + let header = req + .headers() + .get("Authorization") + .ok_or("Missing authorization header")?; let authorization = header.to_str()?; let authorization_split: Vec<&str> = authorization.split(' ').collect(); if authorization_split[0] != "Bearer" { - return Err("Not a bearer token".into()) + return Err("Not a bearer token".into()); } let tokens = &authorization_split[1..]; - let sess_token = tokens.iter().find(|i| get_token_type(i).unwrap_or("n-sess") == "sess").copied().ok_or("Missing session token")?; + let sess_token = tokens + .iter() + .find(|i| get_token_type(i).unwrap_or("n-sess") == "sess") + .copied() + .ok_or("Missing session token")?; - let token: session_token::Model = session_token::Entity::find().filter(session_token::Column::Id.eq(sess_token)).one(db).await?.ok_or("Invalid session token")?; + let token: session_token::Model = session_token::Entity::find() + .filter(session_token::Column::Id.eq(sess_token)) + .one(db) + .await? + .ok_or("Invalid session token")?; if expired(token.expires_on as u64) { return Err("Token expired".into()); } - let user: user::Model = user::Entity::find().filter(user::Column::Id.eq(token.user)).one(db).await?.ok_or("Session token has a nonexistent user")?; + let user: user::Model = user::Entity::find() + .filter(user::Column::Id.eq(token.user)) + .one(db) + .await? + .ok_or("Session token has a nonexistent user")?; Ok(TokenInfo::SessionToken(SessionTokenInfo { token: token.id, user: SessionTokenUser { id: user.id, - email: user.email + email: user.email, }, expires_at: token.expires_on, })) } -pub async fn enforce_2fa(req: &HttpRequest, db: &DatabaseConnection) -> Result> { +pub async fn enforce_2fa( + req: &HttpRequest, + db: &DatabaseConnection, +) -> Result> { let session_data = match enforce_session(req, db).await? { TokenInfo::SessionToken(i) => i, - _ => unreachable!() + _ => unreachable!(), }; - let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; + let header = req + .headers() + .get("Authorization") + .ok_or("Missing authorization header")?; let authorization = header.to_str()?; let authorization_split: Vec<&str> = authorization.split(' ').collect(); if authorization_split[0] != "Bearer" { - return Err("Not a bearer token".into()) + return Err("Not a bearer token".into()); } let tokens = &authorization_split[1..]; - let auth_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-auth") == "auth").copied().ok_or("Missing auth token")?; + let auth_token = tokens + .iter() + .find(|i| get_token_type(i).unwrap_or("n-auth") == "auth") + .copied() + .ok_or("Missing auth token")?; - let token: auth_token::Model = auth_token::Entity::find().filter(auth_token::Column::Id.eq(auth_token)).one(db).await?.ok_or("Invalid session token")?; + let token: auth_token::Model = auth_token::Entity::find() + .filter(auth_token::Column::Id.eq(auth_token)) + .one(db) + .await? + .ok_or("Invalid session token")?; if expired(token.expires_on as u64) { return Err("Token expired".into()); @@ -112,17 +144,28 @@ pub async fn enforce_2fa(req: &HttpRequest, db: &DatabaseConnection) -> Result Result> { - let header = req.headers().get("Authorization").ok_or("Missing authorization header")?; +pub async fn enforce_api_token( + req: &HttpRequest, + scopes: &[&str], + db: &DatabaseConnection, +) -> Result> { + let header = req + .headers() + .get("Authorization") + .ok_or("Missing authorization header")?; let authorization = header.to_str()?; let authorization_split: Vec<&str> = authorization.split(' ').collect(); if authorization_split[0] != "Bearer" { - return Err("Not a bearer token".into()) + return Err("Not a bearer token".into()); } let tokens = &authorization_split[1..]; - let api_token = tokens.iter().find(|i| get_token_type(**i).unwrap_or("n-tfkey") == "tfkey").copied().ok_or("Missing api token")?; + let api_token = tokens + .iter() + .find(|i| get_token_type(i).unwrap_or("n-tfkey") == "tfkey") + .copied() + .ok_or("Missing api token")?; // API tokens are special and have a different form than other keys. // They follow the form: @@ -135,10 +178,19 @@ pub async fn enforce_api_token(req: &HttpRequest, scopes: &[&str], db: &Database let token_id = format!("{}-{}", api_token_split[0], api_token_split[1]); let token_key = api_token_split[2].to_string(); - let token: api_key::Model = api_key::Entity::find().filter( - Condition::all().add(api_key::Column::Id.eq(token_id)).add(api_key::Column::Key.eq(token_key)) - ).one(db).await?.ok_or("Invalid api token")?; - let token_scopes: Vec = api_key_scope::Entity::find().filter(api_key_scope::Column::ApiKey.eq(api_token)).all(db).await?; + let token: api_key::Model = api_key::Entity::find() + .filter( + Condition::all() + .add(api_key::Column::Id.eq(token_id)) + .add(api_key::Column::Key.eq(token_key)), + ) + .one(db) + .await? + .ok_or("Invalid api token")?; + let token_scopes: Vec = api_key_scope::Entity::find() + .filter(api_key_scope::Column::ApiKey.eq(api_token)) + .all(db) + .await?; let token_scopes: Vec<&str> = token_scopes.iter().map(|i| i.scope.as_str()).collect(); for scope in scopes { @@ -151,4 +203,4 @@ pub async fn enforce_api_token(req: &HttpRequest, scopes: &[&str], db: &Database scopes: token_scopes.iter().map(|i| i.to_string()).collect(), organization: token.organization, })) -} \ No newline at end of file +} diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index e3560fb..e1d3f95 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -14,11 +14,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::fs; -use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; use log::error; use once_cell::sync::Lazy; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4}; pub static CONFIG: Lazy = Lazy::new(|| { let config_str = match fs::read_to_string("/etc/trifid/config.toml") { @@ -43,7 +43,7 @@ pub struct TrifidConfig { pub database: TrifidConfigDatabase, pub server: TrifidConfigServer, pub tokens: TrifidConfigTokens, - pub crypto: TrifidConfigCryptography + pub crypto: TrifidConfigCryptography, } #[derive(Serialize, Deserialize, Debug)] @@ -62,13 +62,13 @@ pub struct TrifidConfigDatabase { #[serde(default = "time_defaults")] pub max_lifetime: u64, #[serde(default = "sqlx_logging_default")] - pub sqlx_logging: bool + pub sqlx_logging: bool, } #[derive(Serialize, Deserialize, Debug)] pub struct TrifidConfigServer { #[serde(default = "socketaddr_8080")] - pub bind: SocketAddr + pub bind: SocketAddr, } #[derive(Serialize, Deserialize, Debug)] @@ -80,20 +80,38 @@ pub struct TrifidConfigTokens { #[serde(default = "totp_setup_timeout_time")] pub totp_setup_timeout_time_seconds: u64, #[serde(default = "mfa_tokens_expiry_time")] - pub mfa_tokens_expiry_time_seconds: u64 + pub mfa_tokens_expiry_time_seconds: u64, } #[derive(Serialize, Deserialize, Debug)] pub struct TrifidConfigCryptography { - pub data_encryption_key: String + pub data_encryption_key: String, } -fn max_connections_default() -> u32 { 100 } -fn min_connections_default() -> u32 { 5 } -fn time_defaults() -> u64 { 8 } -fn sqlx_logging_default() -> bool { true } -fn socketaddr_8080() -> SocketAddr { SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) } -fn magic_link_expiry_time() -> u64 { 3600 } // 1 hour -fn session_token_expiry_time() -> u64 { 15780000 } // 6 months -fn totp_setup_timeout_time() -> u64 { 600 } // 10 minutes -fn mfa_tokens_expiry_time() -> u64 { 600 } // 10 minutes \ No newline at end of file +fn max_connections_default() -> u32 { + 100 +} +fn min_connections_default() -> u32 { + 5 +} +fn time_defaults() -> u64 { + 8 +} +fn sqlx_logging_default() -> bool { + true +} +fn socketaddr_8080() -> SocketAddr { + SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080)) +} +fn magic_link_expiry_time() -> u64 { + 3600 +} // 1 hour +fn session_token_expiry_time() -> u64 { + 15780000 +} // 6 months +fn totp_setup_timeout_time() -> u64 { + 600 +} // 10 minutes +fn mfa_tokens_expiry_time() -> u64 { + 600 +} // 10 minutes diff --git a/trifid-api/src/crypto.rs b/trifid-api/src/crypto.rs index 9d8dc63..4add179 100644 --- a/trifid-api/src/crypto.rs +++ b/trifid-api/src/crypto.rs @@ -14,25 +14,33 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::error::Error; -use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; -use aes_gcm::aead::{Aead, Payload}; -use rand::Rng; -use trifid_pki::rand_core::OsRng; use crate::config::TrifidConfig; +use aes_gcm::aead::{Aead, Payload}; +use aes_gcm::{Aes256Gcm, KeyInit, Nonce}; +use rand::Rng; +use std::error::Error; +use trifid_pki::rand_core::OsRng; pub fn get_cipher_from_config(config: &TrifidConfig) -> Result> { let key_slice = hex::decode(&config.crypto.data_encryption_key)?; Ok(Aes256Gcm::new_from_slice(&key_slice)?) } -pub fn encrypt_with_nonce(plaintext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result, aes_gcm::Error> { +pub fn encrypt_with_nonce( + plaintext: &[u8], + nonce: [u8; 12], + cipher: &Aes256Gcm, +) -> Result, aes_gcm::Error> { let nonce = Nonce::from_slice(&nonce); let ciphertext = cipher.encrypt(nonce, plaintext)?; Ok(ciphertext) } -pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result, aes_gcm::Error> { +pub fn decrypt_with_nonce( + ciphertext: &[u8], + nonce: [u8; 12], + cipher: &Aes256Gcm, +) -> Result, aes_gcm::Error> { let nonce = Nonce::from_slice(&nonce); let plaintext = cipher.decrypt(nonce, Payload::from(ciphertext))?; Ok(plaintext) @@ -40,4 +48,4 @@ pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm pub fn generate_random_iv() -> [u8; 12] { OsRng.gen() -} \ No newline at end of file +} diff --git a/trifid-api/src/cursor.rs b/trifid-api/src/cursor.rs index 91f0a07..14a486f 100644 --- a/trifid-api/src/cursor.rs +++ b/trifid-api/src/cursor.rs @@ -14,13 +14,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::error::Error; use base64::Engine; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use std::error::Error; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct Cursor { - pub page: u64 + pub page: u64, } impl TryFrom for String { @@ -41,9 +41,7 @@ impl TryFrom for Cursor { fn try_from(value: String) -> Result { if value.is_empty() { // If empty, it's page 0 - return Ok(Cursor { - page: 0 - }) + return Ok(Cursor { page: 0 }); } // Base64-decode the value let json_bytes = base64::engine::general_purpose::STANDARD.decode(value)?; @@ -53,4 +51,4 @@ impl TryFrom for Cursor { let cursor = serde_json::from_str(&json_str)?; Ok(cursor) } -} \ No newline at end of file +} diff --git a/trifid-api/src/error.rs b/trifid-api/src/error.rs index 4efa931..73017f9 100644 --- a/trifid-api/src/error.rs +++ b/trifid-api/src/error.rs @@ -15,11 +15,11 @@ // along with this program. If not, see . use actix_web::error::{JsonPayloadError, PayloadError}; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct APIErrorsResponse { - pub errors: Vec + pub errors: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct APIError { @@ -27,10 +27,12 @@ pub struct APIError { pub message: String, #[serde(skip_serializing_if = "is_none")] #[serde(default)] - pub path: Option + pub path: Option, } -fn is_none(o: &Option) -> bool { o.is_none() } +fn is_none(o: &Option) -> bool { + o.is_none() +} impl From<&JsonPayloadError> for APIError { fn from(value: &JsonPayloadError) -> Self { @@ -87,58 +89,44 @@ impl From<&JsonPayloadError> for APIError { impl From<&PayloadError> for APIError { fn from(value: &PayloadError) -> Self { match value { - PayloadError::Incomplete(e) => { - APIError { - code: "ERR_UNEXPECTED_EOF".to_string(), - message: match e { - None => "Payload reached EOF but was incomplete".to_string(), - Some(e) => format!("Payload reached EOF but was incomplete: {}", e) - }, - path: None, - } - } - PayloadError::EncodingCorrupted => { - APIError { - code: "ERR_CORRUPTED_PAYLOAD".to_string(), - message: "Payload content encoding corrupted".to_string(), - path: None, - } - } - PayloadError::Overflow => { - APIError { - code: "ERR_PAYLOAD_OVERFLOW".to_string(), - message: "Payload reached size limit".to_string(), - path: None, - } - } - PayloadError::UnknownLength => { - APIError { - code: "ERR_PAYLOAD_UNKNOWN_LENGTH".to_string(), - message: "Unable to determine payload length".to_string(), - path: None, - } - } - PayloadError::Http2Payload(e) => { - APIError { - code: "ERR_HTTP2_ERROR".to_string(), - message: format!("HTTP/2 error: {}", e), - path: None, - } - } - PayloadError::Io(e) => { - APIError { - code: "ERR_IO_ERROR".to_string(), - message: format!("I/O error: {}", e), - path: None, - } - } - _ => { - APIError { - code: "ERR_UNKNOWN_ERROR".to_string(), - message: "An unknown error has occured".to_string(), - path: None, - } - } + PayloadError::Incomplete(e) => APIError { + code: "ERR_UNEXPECTED_EOF".to_string(), + message: match e { + None => "Payload reached EOF but was incomplete".to_string(), + Some(e) => format!("Payload reached EOF but was incomplete: {}", e), + }, + path: None, + }, + PayloadError::EncodingCorrupted => APIError { + code: "ERR_CORRUPTED_PAYLOAD".to_string(), + message: "Payload content encoding corrupted".to_string(), + path: None, + }, + PayloadError::Overflow => APIError { + code: "ERR_PAYLOAD_OVERFLOW".to_string(), + message: "Payload reached size limit".to_string(), + path: None, + }, + PayloadError::UnknownLength => APIError { + code: "ERR_PAYLOAD_UNKNOWN_LENGTH".to_string(), + message: "Unable to determine payload length".to_string(), + path: None, + }, + PayloadError::Http2Payload(e) => APIError { + code: "ERR_HTTP2_ERROR".to_string(), + message: format!("HTTP/2 error: {}", e), + path: None, + }, + PayloadError::Io(e) => APIError { + code: "ERR_IO_ERROR".to_string(), + message: format!("I/O error: {}", e), + path: None, + }, + _ => APIError { + code: "ERR_UNKNOWN_ERROR".to_string(), + message: "An unknown error has occured".to_string(), + path: None, + }, } } -} \ No newline at end of file +} diff --git a/trifid-api/src/magic_link.rs b/trifid-api/src/magic_link.rs index 19b6ff8..e09261a 100644 --- a/trifid-api/src/magic_link.rs +++ b/trifid-api/src/magic_link.rs @@ -14,12 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::error::Error; use log::info; +use std::error::Error; pub fn send_magic_link(token: &str) -> Result<(), Box> { // TODO: actually do this info!("sent magic link {}", token); Ok(()) -} \ No newline at end of file +} diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index 1679f4d..a77ee9d 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -14,30 +14,33 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use std::error::Error; -use std::time::Duration; use actix_request_identifier::RequestIdentifier; -use actix_web::{App, HttpResponse, HttpServer, web::{Data, JsonConfig}}; +use actix_web::{ + web::{Data, JsonConfig}, + App, HttpResponse, HttpServer, +}; use log::{info, Level}; use sea_orm::{ConnectOptions, Database, DatabaseConnection}; +use std::error::Error; +use std::time::Duration; -use trifid_api_migration::{Migrator, MigratorTrait}; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; use crate::tokens::random_id_no_id; +use trifid_api_migration::{Migrator, MigratorTrait}; -pub mod config; -pub mod routes; -pub mod error; -pub mod tokens; -pub mod timers; -pub mod magic_link; pub mod auth_tokens; -pub mod cursor; +pub mod config; pub mod crypto; +pub mod cursor; +pub mod error; +pub mod magic_link; +pub mod routes; +pub mod timers; +pub mod tokens; pub struct AppState { - pub conn: DatabaseConnection + pub conn: DatabaseConnection, } #[actix_web::main] @@ -61,9 +64,7 @@ async fn main() -> Result<(), Box> { info!("Performing database migration..."); Migrator::up(&db, None).await?; - let data = Data::new(AppState { - conn: db - }); + let data = Data::new(AppState { conn: db }); HttpServer::new(move || { App::new() @@ -73,11 +74,10 @@ async fn main() -> Result<(), Box> { actix_web::error::InternalError::from_response( err, HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - api_error - ], - }) - ).into() + errors: vec![api_error], + }), + ) + .into() })) .wrap(RequestIdentifier::with_generator(random_id_no_id)) .service(routes::v1::auth::magic_link::magic_link_request) @@ -100,7 +100,11 @@ async fn main() -> Result<(), Box> { .service(routes::v1::hosts::get_host) .service(routes::v1::hosts::delete_host) .service(routes::v1::hosts::edit_host) - }).bind(CONFIG.server.bind)?.run().await?; + .service(routes::v1::hosts::block_host) + }) + .bind(CONFIG.server.bind)? + .run() + .await?; Ok(()) } diff --git a/trifid-api/src/routes/mod.rs b/trifid-api/src/routes/mod.rs index 5dd9fd0..a3a6d96 100644 --- a/trifid-api/src/routes/mod.rs +++ b/trifid-api/src/routes/mod.rs @@ -1 +1 @@ -pub mod v1; \ No newline at end of file +pub mod v1; diff --git a/trifid-api/src/routes/v1/auth/magic_link.rs b/trifid-api/src/routes/v1/auth/magic_link.rs index a363600..0c29e5f 100644 --- a/trifid-api/src/routes/v1/auth/magic_link.rs +++ b/trifid-api/src/routes/v1/auth/magic_link.rs @@ -19,29 +19,29 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use actix_web::{HttpResponse, post}; -use actix_web::web::{Data, Json}; -use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; -use serde::{Serialize, Deserialize}; -use trifid_api_entities::entity::user::Entity as UserEntity; -use trifid_api_entities::entity::user; -use crate::AppState; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; use crate::magic_link::send_magic_link; use crate::timers::expires_in_seconds; use crate::tokens::random_token; +use crate::AppState; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpResponse}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Deserialize, Serialize}; +use trifid_api_entities::entity::user; +use trifid_api_entities::entity::user::Entity as UserEntity; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct MagicLinkRequest { - pub email: String + pub email: String, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct MagicLinkResponse { pub data: MagicLinkResponseData, - pub metadata: MagicLinkResponseMetadata + pub metadata: MagicLinkResponseMetadata, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct MagicLinkResponseData {} @@ -50,19 +50,23 @@ pub struct MagicLinkResponseMetadata {} #[post("/v1/auth/magic-link")] pub async fn magic_link_request(data: Data, req: Json) -> HttpResponse { - let user: Option = match UserEntity::find().filter(user::Column::Email.eq(&req.email)).one(&data.conn).await { + let user: Option = match UserEntity::find() + .filter(user::Column::Email.eq(&req.email)) + .one(&data.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -70,13 +74,11 @@ pub async fn magic_link_request(data: Data, req: Json u, None => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_USER_DOES_NOT_EXIST".to_string(), - message: "That user does not exist.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_USER_DOES_NOT_EXIST".to_string(), + message: "That user does not exist.".to_string(), + path: None, + }], }) } }; @@ -92,14 +94,14 @@ pub async fn magic_link_request(data: Data, req: Json { error!("error sending magic link: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_ML_ERROR".to_string(), - message: "There was an error sending the magic link email, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_ML_ERROR".to_string(), + message: + "There was an error sending the magic link email, please try again later." + .to_string(), + path: None, + }], + }); } } @@ -110,19 +112,19 @@ pub async fn magic_link_request(data: Data, req: Json { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } HttpResponse::Ok().json(MagicLinkResponse { data: MagicLinkResponseData {}, - metadata: MagicLinkResponseMetadata {} + metadata: MagicLinkResponseMetadata {}, }) -} \ No newline at end of file +} diff --git a/trifid-api/src/routes/v1/auth/mod.rs b/trifid-api/src/routes/v1/auth/mod.rs index 29a43ea..d4db6ee 100644 --- a/trifid-api/src/routes/v1/auth/mod.rs +++ b/trifid-api/src/routes/v1/auth/mod.rs @@ -1,3 +1,3 @@ pub mod magic_link; +pub mod totp; pub mod verify_magic_link; -pub mod totp; \ No newline at end of file diff --git a/trifid-api/src/routes/v1/auth/totp.rs b/trifid-api/src/routes/v1/auth/totp.rs index a1302ff..045734b 100644 --- a/trifid-api/src/routes/v1/auth/totp.rs +++ b/trifid-api/src/routes/v1/auth/totp.rs @@ -19,95 +19,95 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use actix_web::{HttpRequest, HttpResponse, post}; -use actix_web::web::{Data, Json}; -use log::{debug, error}; -use serde::{Serialize, Deserialize}; -use trifid_api_entities::entity::totp_authenticator; -use crate::AppState; use crate::auth_tokens::{enforce_session, TokenInfo}; use crate::error::{APIError, APIErrorsResponse}; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait}; +use crate::AppState; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpRequest, HttpResponse}; +use log::{debug, error}; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Deserialize, Serialize}; +use trifid_api_entities::entity::totp_authenticator; -use totp_rs::{Secret, TOTP}; -use trifid_api_entities::entity::auth_token; use crate::config::CONFIG; use crate::timers::expires_in_seconds; use crate::tokens::random_token; +use totp_rs::{Secret, TOTP}; +use trifid_api_entities::entity::auth_token; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpRequest { - pub code: String + pub code: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpResponse { pub data: TotpResponseData, - pub metadata: TotpResponseMetadata + pub metadata: TotpResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpResponseData { #[serde(rename = "authToken")] - pub auth_token: String + pub auth_token: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpResponseMetadata {} #[post("/v1/auth/totp")] -pub async fn totp_request(req: Json, req_data: HttpRequest, db: Data) -> HttpResponse { +pub async fn totp_request( + req: Json, + req_data: HttpRequest, + db: Data, +) -> HttpResponse { // require a user session let session_token = match enforce_session(&req_data, &db.conn).await { - Ok(r) => { - match r { - TokenInfo::SessionToken(i) => i, - _ => unreachable!() - } - } + Ok(r) => match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!(), + }, Err(e) => { error!("error enforcing session: {}", e); return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Unauthorized".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + }], }); } }; // determine if the user has a totp authenticator - let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await { + let auther = match totp_authenticator::Entity::find() + .filter(totp_authenticator::Column::User.eq(&session_token.user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], }); } }; let auther = match auther { - Some(a) => { - a - }, + Some(a) => a, None => { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_USER_NO_TOTP".to_string(), - message: "This user does not have a totp authenticator".to_string(), - path: None, - } - ] + errors: vec![APIError { + code: "ERR_USER_NO_TOTP".to_string(), + message: "This user does not have a totp authenticator".to_string(), + path: None, + }], }); } }; @@ -118,30 +118,26 @@ pub async fn totp_request(req: Json, req_data: HttpRequest, db: Dat Err(e) => { error!("totp url error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_SECRET_ERROR".to_string(), - message: "There was an error parsing the totpmachine. Please try again later.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_SECRET_ERROR".to_string(), + message: "There was an error parsing the totpmachine. Please try again later." + .to_string(), + path: None, + }], }); } }; - let valid = match totpmachine.check_current(&req.code) { Ok(valid) => valid, Err(e) => { error!("system time error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_TIME_ERROR".to_string(), - message: "There was an with the server-side time clock.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_TIME_ERROR".to_string(), + message: "There was an with the server-side time clock.".to_string(), + path: None, + }], }); } }; @@ -150,14 +146,12 @@ pub async fn totp_request(req: Json, req_data: HttpRequest, db: Dat debug!("current: {}", totpmachine.generate_current().unwrap()); error!("user send invalid totp code"); return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Unauthorized".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + }], + }); } let model: auth_token::Model = auth_token::Model { @@ -172,13 +166,11 @@ pub async fn totp_request(req: Json, req_data: HttpRequest, db: Dat Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error issuing the authentication token.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error issuing the authentication token.".to_string(), + path: None, + }], }); } } @@ -187,4 +179,4 @@ pub async fn totp_request(req: Json, req_data: HttpRequest, db: Dat data: TotpResponseData { auth_token: token }, metadata: TotpResponseMetadata {}, }) -} \ No newline at end of file +} diff --git a/trifid-api/src/routes/v1/auth/verify_magic_link.rs b/trifid-api/src/routes/v1/auth/verify_magic_link.rs index cd1e93b..884ad75 100644 --- a/trifid-api/src/routes/v1/auth/verify_magic_link.rs +++ b/trifid-api/src/routes/v1/auth/verify_magic_link.rs @@ -19,56 +19,65 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use actix_web::{HttpResponse, post}; -use actix_web::web::{Data, Json}; -use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; -use serde::{Serialize, Deserialize}; -use crate::AppState; -use trifid_api_entities::entity::magic_link; -use trifid_api_entities::entity::magic_link::Model; -use trifid_api_entities::entity::session_token; use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; use crate::timers::{expired, expires_in_seconds}; use crate::tokens::random_token; +use crate::AppState; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpResponse}; +use log::error; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, +}; +use serde::{Deserialize, Serialize}; +use trifid_api_entities::entity::magic_link; +use trifid_api_entities::entity::magic_link::Model; +use trifid_api_entities::entity::session_token; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyMagicLinkRequest { #[serde(rename = "magicLinkToken")] - pub magic_link_token: String + pub magic_link_token: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyMagicLinkResponse { pub data: VerifyMagicLinkResponseData, - pub metadata: VerifyMagicLinkResponseMetadata + pub metadata: VerifyMagicLinkResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyMagicLinkResponseData { #[serde(rename = "sessionToken")] - pub session_token: String + pub session_token: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyMagicLinkResponseMetadata {} #[post("/v1/auth/verify-magic-link")] -pub async fn verify_magic_link_request(db: Data, req: Json) -> HttpResponse { - let link: Option = match magic_link::Entity::find().filter(magic_link::Column::Id.eq(&req.magic_link_token)).one(&db.conn).await { +pub async fn verify_magic_link_request( + db: Data, + req: Json, +) -> HttpResponse { + let link: Option = match magic_link::Entity::find() + .filter(magic_link::Column::Id.eq(&req.magic_link_token)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -76,27 +85,23 @@ pub async fn verify_magic_link_request(db: Data, req: Json l, None => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Unauthorized".to_string(), - path: None - } - ] + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + }], }) } }; if expired(link.expires_on as u64) { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_EXPIRED".to_string(), - message: "Magic link token expired".to_string(), - path: None - } - ] - }) + errors: vec![APIError { + code: "ERR_EXPIRED".to_string(), + message: "Magic link token expired".to_string(), + path: None, + }], + }); } let user = link.user.clone(); @@ -106,14 +111,14 @@ pub async fn verify_magic_link_request(db: Data, req: Json { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } @@ -130,23 +135,21 @@ pub async fn verify_magic_link_request(db: Data, req: Json { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } - HttpResponse::Ok().json( - VerifyMagicLinkResponse { - data: VerifyMagicLinkResponseData { - session_token: token, - }, - metadata: VerifyMagicLinkResponseMetadata {}, - } - ) -} \ No newline at end of file + HttpResponse::Ok().json(VerifyMagicLinkResponse { + data: VerifyMagicLinkResponseData { + session_token: token, + }, + metadata: VerifyMagicLinkResponseMetadata {}, + }) +} diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index 152ae12..bf8a26e 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -40,24 +40,27 @@ // This endpoint requires the `definednetworking` extension to be enabled to be used. // This endpoint has additional functionality enabled by the extended_hosts feature flag. -use std::net::{Ipv4Addr, SocketAddrV4}; -use std::str::FromStr; -use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{HttpRequest, HttpResponse, get, post, delete, put}; -use actix_web::web::{Data, Json, Path, Query}; -use chrono::{TimeZone, Utc}; -use log::{debug, error}; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, QueryOrder, PaginatorTrait, IntoActiveModel, ActiveModelTrait, ModelTrait}; -use sea_orm::ActiveValue::Set; -use serde::{Serialize, Deserialize}; -use trifid_api_entities::entity::{host, host_static_address, network, organization}; -use crate::AppState; use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::cursor::Cursor; use crate::error::{APIError, APIErrorsResponse}; use crate::routes::v1::trifid::SUPPORTED_EXTENSIONS; use crate::timers::TIME_FORMAT; use crate::tokens::random_id; +use crate::AppState; +use actix_web::web::{Data, Json, Path, Query}; +use actix_web::{delete, get, post, put, HttpRequest, HttpResponse}; +use chrono::{TimeZone, Utc}; +use log::{debug, error}; +use sea_orm::ActiveValue::Set; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, PaginatorTrait, + QueryFilter, QueryOrder, +}; +use serde::{Deserialize, Serialize}; +use std::net::{Ipv4Addr, SocketAddrV4}; +use std::str::FromStr; +use std::time::{SystemTime, UNIX_EPOCH}; +use trifid_api_entities::entity::{host, host_static_address, network, organization}; #[derive(Serialize, Deserialize)] pub struct ListHostsRequestOpts { @@ -66,13 +69,13 @@ pub struct ListHostsRequestOpts { #[serde(default)] pub cursor: String, #[serde(default = "page_default", rename = "pageSize")] - pub page_size: u64 + pub page_size: u64, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ListHostsResponse { pub data: Vec, - pub metadata: ListHostsResponseMetadata + pub metadata: ListHostsResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -88,13 +91,13 @@ pub struct ListHostsResponseMetadata { #[serde(default, rename = "nextCursor")] pub next_cursor: Option, #[serde(default)] - pub page: Option + pub page: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct ListHostsResponseMetadataPage { pub count: u64, - pub start: u64 + pub start: u64, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -121,7 +124,7 @@ pub struct HostResponse { pub created_at: String, #[serde(rename = "isBlocked")] pub is_blocked: bool, - pub metadata: HostResponseMetadata + pub metadata: HostResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -131,20 +134,31 @@ pub struct HostResponseMetadata { pub version: String, pub platform: String, #[serde(rename = "updateAvailable")] - pub update_available: bool + pub update_available: bool, } -fn page_default() -> u64 { 25 } - +fn page_default() -> u64 { + 25 +} #[get("/v1/hosts")] -pub async fn get_hosts(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn get_hosts( + opts: Query, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:list - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["hosts:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:list"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -153,11 +167,13 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -166,7 +182,7 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, path: None } ], - }) + }); } let org_id = match api_token_info { @@ -175,10 +191,14 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -205,14 +225,18 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, path: None } ], - }) + }); } } }; let net_id; - let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + let net = match network::Entity::find() + .filter(network::Column::Organization.eq(&org_id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -232,14 +256,13 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, net_id = net.id; } else { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_NO_NET".to_string(), - message: "This user does not own any networks. Try using an API token instead.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead." + .to_string(), + path: None, + }], + }); } let cursor: Cursor = match opts.cursor.clone().try_into() { @@ -247,18 +270,19 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, Err(e) => { error!("invalid cursor: {}", e); return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_CURSOR".to_string(), - message: "The provided cursor was invalid, please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_INVALID_CURSOR".to_string(), + message: "The provided cursor was invalid, please try again later.".to_string(), + path: None, + }], + }); } }; - let host_pages = host::Entity::find().filter(host::Column::Network.eq(&net_id)).order_by_asc(host::Column::CreatedAt).paginate(&db.conn, opts.page_size); + let host_pages = host::Entity::find() + .filter(host::Column::Network.eq(&net_id)) + .order_by_asc(host::Column::CreatedAt) + .paginate(&db.conn, opts.page_size); let total = match host_pages.num_items().await { Ok(r) => r, @@ -311,7 +335,11 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, for u in models { // fetch static addresses - let ips = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&u.id)).all(&db.conn).await { + let ips = match host_static_address::Entity::find() + .filter(host_static_address::Column::Host.eq(&u.id)) + .all(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -334,14 +362,26 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, role_id: u.role, name: u.name, ip_address: u.ip, - static_addresses: ips.iter().map(|u| SocketAddrV4::from_str(&u.address).unwrap()).collect(), + static_addresses: ips + .iter() + .map(|u| SocketAddrV4::from_str(&u.address).unwrap()) + .collect(), listen_port: u.listen_port as u16, is_lighthouse: u.is_lighthouse, is_relay: u.is_relay, - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(u.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), is_blocked: u.is_blocked, metadata: HostResponseMetadata { - last_seen_at: Some(Utc.timestamp_opt(u.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), + last_seen_at: Some( + Utc.timestamp_opt(u.last_seen_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + ), version: u.last_version.to_string(), platform: u.last_platform, update_available: u.last_out_of_date, @@ -355,20 +395,28 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, data: models_mapped, metadata: ListHostsResponseMetadata { total_count: total, - has_next_page: cursor.page+1 != pages, + has_next_page: cursor.page + 1 != pages, has_prev_page: cursor.page != 0, prev_cursor: if cursor.page != 0 { - match (Cursor { page: cursor.page - 1 }).try_into() { + match (Cursor { + page: cursor.page - 1, + }) + .try_into() + { Ok(r) => Some(r), - Err(_) => None + Err(_) => None, } } else { None }, - next_cursor: if cursor.page+1 != pages { - match (Cursor { page: cursor.page + 1 }).try_into() { + next_cursor: if cursor.page + 1 != pages { + match (Cursor { + page: cursor.page + 1, + }) + .try_into() + { Ok(r) => Some(r), - Err(_) => None + Err(_) => None, } } else { None @@ -378,7 +426,9 @@ pub async fn get_hosts(opts: Query, req_info: HttpRequest, count, start: opts.page_size * cursor.page, }) - } else { None }, + } else { + None + }, }, }) } @@ -399,26 +449,36 @@ pub struct CreateHostRequest { #[serde(rename = "isLighthouse")] pub is_lighthouse: bool, #[serde(rename = "isRelay")] - pub is_relay: bool + pub is_relay: bool, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateHostResponse { pub data: HostResponse, - pub metadata: CreateHostResponseMetadata + pub metadata: CreateHostResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateHostResponseMetadata {} #[post("/v1/hosts")] -pub async fn create_hosts_request(req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn create_hosts_request( + req: Json, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:create - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["hosts:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:create"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -427,11 +487,13 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -440,20 +502,23 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe path: None } ], - }) + }); } - let org_id = match api_token_info { TokenInfo::ApiToken(tkn) => tkn.organization, _ => { // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -480,14 +545,18 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe path: None } ], - }) + }); } } }; let net_id; - let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + let net = match network::Entity::find() + .filter(network::Column::Organization.eq(&org_id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -507,14 +576,13 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe net_id = net.id; } else { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_NO_NET".to_string(), - message: "This user does not own any networks. Try using an API token instead.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead." + .to_string(), + path: None, + }], + }); } if net_id != req.network_id { @@ -526,31 +594,27 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe path: None } ], - }) + }); } if req.is_lighthouse && req.is_relay { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_CANNOT_BE_RELAY_AND_LIGHTHOUSE".to_string(), - message: "A host cannot be a relay and a lighthouse at the same time.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_CANNOT_BE_RELAY_AND_LIGHTHOUSE".to_string(), + message: "A host cannot be a relay and a lighthouse at the same time.".to_string(), + path: None, + }], + }); } if req.is_lighthouse || req.is_relay && req.static_addresses.is_empty() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_NEEDS_STATIC_ADDR".to_string(), - message: "A relay or lighthouse requires at least one static address.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_NEEDS_STATIC_ADDR".to_string(), + message: "A relay or lighthouse requires at least one static address.".to_string(), + path: None, + }], + }); } let new_host_model = host::Model { @@ -563,20 +627,25 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe is_lighthouse: req.is_lighthouse, is_relay: req.is_relay, counter: 0, - created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("time went backwards").as_secs() as i64, + created_at: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time went backwards") + .as_secs() as i64, is_blocked: false, last_seen_at: 0, last_version: 0, last_platform: "".to_string(), last_out_of_date: false, }; - let static_addresses: Vec = req.static_addresses.iter().map(|u| { - host_static_address::Model { + let static_addresses: Vec = req + .static_addresses + .iter() + .map(|u| host_static_address::Model { id: random_id("hsaddress"), host: new_host_model.id.clone(), address: u.to_string(), - } - }).collect(); + }) + .collect(); let new_host_model_clone = new_host_model.clone(); let static_addresses_clone = static_addresses.clone(); @@ -587,14 +656,13 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new host. Please try again later".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later" + .to_string(), + path: None, + }], + }); } } @@ -605,14 +673,13 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new host. Please try again later".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later" + .to_string(), + path: None, + }], + }); } } } @@ -629,10 +696,19 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe listen_port: req.listen_port, is_lighthouse: req.is_lighthouse, is_relay: req.is_relay, - created_at: Utc.timestamp_opt(new_host_model_clone.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(new_host_model_clone.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), is_blocked: false, metadata: HostResponseMetadata { - last_seen_at: Some(Utc.timestamp_opt(new_host_model_clone.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), + last_seen_at: Some( + Utc.timestamp_opt(new_host_model_clone.last_seen_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + ), version: new_host_model_clone.last_version.to_string(), platform: new_host_model_clone.last_platform, update_available: new_host_model_clone.last_out_of_date, @@ -645,7 +721,7 @@ pub async fn create_hosts_request(req: Json, req_info: HttpRe #[derive(Serialize, Deserialize)] pub struct GetHostResponse { pub data: HostResponse, - pub metadata: GetHostResponseMetadata + pub metadata: GetHostResponseMetadata, } #[derive(Serialize, Deserialize)] pub struct GetHostResponseMetadata {} @@ -653,11 +729,17 @@ pub struct GetHostResponseMetadata {} #[get("/v1/hosts/{host_id}")] pub async fn get_host(id: Path, req_info: HttpRequest, db: Data) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:read - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:read"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -666,11 +748,13 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -718,14 +806,18 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data r, Err(e) => { error!("database error: {}", e); @@ -745,29 +837,31 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -775,42 +869,44 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data h, None => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: + "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], }) } }; if host.network != net_id { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }); } - let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await { + let static_addresses = match host_static_address::Entity::find() + .filter(host_static_address::Column::Host.eq(&host.id)) + .all(&db.conn) + .await + { Ok(h) => h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -822,14 +918,26 @@ pub async fn get_host(id: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn delete_host( + id: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:delete - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["hosts:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:delete"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -865,11 +983,13 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -917,14 +1041,18 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data r, Err(e) => { error!("database error: {}", e); @@ -944,29 +1072,31 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -974,42 +1104,44 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data h, None => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: + "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], }) } }; if host.network != net_id { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }); } - let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await { + let static_addresses = match host_static_address::Entity::find() + .filter(host_static_address::Column::Host.eq(&host.id)) + .all(&db.conn) + .await + { Ok(h) => h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -1018,14 +1150,13 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } } @@ -1035,14 +1166,14 @@ pub async fn delete_host(id: Path, req_info: HttpRequest, db: Data { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } } } @@ -1062,31 +1193,43 @@ pub struct EditHostRequest { // t+features:extended_hosts pub name: Option, // t+features:extended_hosts - pub ip: Option + pub ip: Option, } #[derive(Serialize, Deserialize)] pub struct EditHostExtensionQuery { - pub extension: Option + pub extension: Option, } #[derive(Serialize, Deserialize)] pub struct EditHostResponse { pub data: HostResponse, - pub metadata: EditHostResponseMetadata + pub metadata: EditHostResponseMetadata, } #[derive(Serialize, Deserialize)] pub struct EditHostResponseMetadata {} #[put("/v1/hosts/{host_id}")] -pub async fn edit_host(id: Path, query: Query, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn edit_host( + id: Path, + query: Query, + req: Json, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:edit - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["hosts:edit"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:edit"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -1095,11 +1238,13 @@ pub async fn edit_host(id: Path, query: Query, r path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -1108,7 +1253,7 @@ pub async fn edit_host(id: Path, query: Query, r path: None } ], - }) + }); } let org_id = match api_token_info { @@ -1117,10 +1262,14 @@ pub async fn edit_host(id: Path, query: Query, r // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -1147,14 +1296,18 @@ pub async fn edit_host(id: Path, query: Query, r path: None } ], - }) + }); } } }; let net_id; - let net = match network::Entity::find().filter(network::Column::Organization.eq(&org_id)).one(&db.conn).await { + let net = match network::Entity::find() + .filter(network::Column::Organization.eq(&org_id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -1174,29 +1327,31 @@ pub async fn edit_host(id: Path, query: Query, r net_id = net.id; } else { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_NO_NET".to_string(), - message: "This user does not own any networks. Try using an API token instead.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead." + .to_string(), + path: None, + }], + }); } - let host = match host::Entity::find().filter(host::Column::Id.eq(id.into_inner())).one(&db.conn).await { + let host = match host::Entity::find() + .filter(host::Column::Id.eq(id.into_inner())) + .one(&db.conn) + .await + { Ok(h) => h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -1204,42 +1359,44 @@ pub async fn edit_host(id: Path, query: Query, r Some(h) => h, None => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: + "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], }) } }; if host.network != net_id { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }); } - let static_addresses = match host_static_address::Entity::find().filter(host_static_address::Column::Host.eq(&host.id)).all(&db.conn).await { + let static_addresses = match host_static_address::Entity::find() + .filter(host_static_address::Column::Host.eq(&host.id)) + .all(&db.conn) + .await + { Ok(h) => h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; @@ -1249,26 +1406,35 @@ pub async fn edit_host(id: Path, query: Query, r Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } } } - let mut host_clone = host.clone(); + let host_clone = host.clone(); let mut host_active_model = host_clone.into_active_model(); host_active_model.listen_port = Set(req.listen_port as i32); - debug!("{:?} {} {:?} {:?} {}", query.extension, SUPPORTED_EXTENSIONS.contains(&"extended_hosts"), req.name, req.ip, query.extension == Some("extended_hosts".to_string())); + debug!( + "{:?} {} {:?} {:?} {}", + query.extension, + SUPPORTED_EXTENSIONS.contains(&"extended_hosts"), + req.name, + req.ip, + query.extension == Some("extended_hosts".to_string()) + ); - if query.extension == Some("extended_hosts".to_string()) && SUPPORTED_EXTENSIONS.contains(&"extended_hosts") { + if query.extension == Some("extended_hosts".to_string()) + && SUPPORTED_EXTENSIONS.contains(&"extended_hosts") + { if let Some(new_host_name) = req.name.clone() { debug!("updated host name"); host_active_model.name = Set(new_host_name); @@ -1279,30 +1445,30 @@ pub async fn edit_host(id: Path, query: Query, r } } - let host = match host_active_model.update(&db.conn).await { Ok(h) => h, Err(e) => { error!("Database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database query. Please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); } }; - let static_addresses: Vec = req.static_addresses.iter().map(|u| { - host_static_address::Model { + let static_addresses: Vec = req + .static_addresses + .iter() + .map(|u| host_static_address::Model { id: random_id("hsaddress"), host: host.id.clone(), address: u.to_string(), - } - }).collect(); + }) + .collect(); for rule in &static_addresses { let active_model = rule.clone().into_active_model(); @@ -1311,14 +1477,13 @@ pub async fn edit_host(id: Path, query: Query, r Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new host. Please try again later".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later" + .to_string(), + path: None, + }], + }); } } } @@ -1335,10 +1500,19 @@ pub async fn edit_host(id: Path, query: Query, r listen_port: host.listen_port as u16, is_lighthouse: host.is_lighthouse, is_relay: host.is_relay, - created_at: Utc.timestamp_opt(host.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(host.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), is_blocked: host.is_blocked, metadata: HostResponseMetadata { - last_seen_at: Some(Utc.timestamp_opt(host.last_seen_at, 0).unwrap().format(TIME_FORMAT).to_string()), + last_seen_at: Some( + Utc.timestamp_opt(host.last_seen_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + ), version: host.last_version.to_string(), platform: host.last_platform, update_available: host.last_out_of_date, @@ -1346,4 +1520,263 @@ pub async fn edit_host(id: Path, query: Query, r }, metadata: EditHostResponseMetadata {}, }) -} \ No newline at end of file +} + +#[derive(Serialize, Deserialize)] +pub struct BlockHostResponse { + pub data: BlockHostResponseData, + pub metadata: BlockHostResponseMetadata, +} + +#[derive(Serialize, Deserialize)] +pub struct BlockHostResponseData { + pub host: HostResponse, +} + +#[derive(Serialize, Deserialize)] +pub struct BlockHostResponseMetadata {} + +#[post("/v1/hosts/{host_id}/block")] +pub async fn block_host( + id: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:block"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:block scope".to_string(), + path: None, + } + ], + }); + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }); + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!(), + }; + + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }); + } + } + }; + + let net_id; + + let net = match network::Entity::find() + .filter(network::Column::Organization.eq(&org_id)) + .one(&db.conn) + .await + { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead." + .to_string(), + path: None, + }], + }); + } + + let host = match host::Entity::find() + .filter(host::Column::Id.eq(id.into_inner())) + .one(&db.conn) + .await + { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); + } + }; + + let host = match host { + Some(h) => h, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: + "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }) + } + }; + + if host.network != net_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }); + } + + let mut host_active = host.into_active_model(); + + host_active.is_blocked = Set(true); + + let host = match host_active.update(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); + } + }; + + let static_addresses = match host_static_address::Entity::find() + .filter(host_static_address::Column::Host.eq(&host.id)) + .all(&db.conn) + .await + { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); + } + }; + + HttpResponse::Ok().json(BlockHostResponse { + data: BlockHostResponseData { + host: HostResponse { + id: host.id, + organization_id: org_id, + network_id: net_id, + role_id: host.role, + name: host.name, + ip_address: host.ip.to_string(), + static_addresses: static_addresses + .iter() + .map(|u| SocketAddrV4::from_str(&u.address).unwrap()) + .collect(), + listen_port: host.listen_port as u16, + is_lighthouse: host.is_lighthouse, + is_relay: host.is_relay, + created_at: Utc + .timestamp_opt(host.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + is_blocked: host.is_blocked, + metadata: HostResponseMetadata { + last_seen_at: Some( + Utc.timestamp_opt(host.last_seen_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + ), + version: host.last_version.to_string(), + platform: host.last_platform, + update_available: host.last_out_of_date, + }, + }, + }, + metadata: BlockHostResponseMetadata {}, + }) +} diff --git a/trifid-api/src/routes/v1/mod.rs b/trifid-api/src/routes/v1/mod.rs index 011b224..8b65d8c 100644 --- a/trifid-api/src/routes/v1/mod.rs +++ b/trifid-api/src/routes/v1/mod.rs @@ -1,9 +1,9 @@ pub mod auth; -pub mod signup; -pub mod totp_authenticators; -pub mod verify_totp_authenticators; +pub mod hosts; pub mod networks; pub mod organization; pub mod roles; +pub mod signup; +pub mod totp_authenticators; pub mod trifid; -pub mod hosts; \ No newline at end of file +pub mod verify_totp_authenticators; diff --git a/trifid-api/src/routes/v1/networks.rs b/trifid-api/src/routes/v1/networks.rs index 8a2c010..9a0497a 100644 --- a/trifid-api/src/routes/v1/networks.rs +++ b/trifid-api/src/routes/v1/networks.rs @@ -24,24 +24,24 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use serde::{Serialize, Deserialize}; -use actix_web::{get, HttpRequest, HttpResponse}; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::cursor::Cursor; +use crate::error::{APIError, APIErrorsResponse}; +use crate::timers::TIME_FORMAT; +use crate::AppState; use actix_web::web::{Data, Path, Query}; +use actix_web::{get, HttpRequest, HttpResponse}; use chrono::{TimeZone, Utc}; use log::error; use sea_orm::{ColumnTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder}; -use crate::AppState; -use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; -use crate::error::{APIError, APIErrorsResponse}; -use trifid_api_entities::entity::organization; +use serde::{Deserialize, Serialize}; use trifid_api_entities::entity::network; -use crate::cursor::Cursor; -use crate::timers::TIME_FORMAT; +use trifid_api_entities::entity::organization; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksResponse { pub data: Vec, - pub metadata: GetNetworksResponseMetadata + pub metadata: GetNetworksResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -56,7 +56,7 @@ pub struct GetNetworksResponseData { pub created_at: String, // 2023-03-22T18:55:47.009Z pub name: String, #[serde(rename = "lighthousesAsRelays")] - pub lighthouses_as_relays: bool + pub lighthouses_as_relays: bool, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -72,16 +72,18 @@ pub struct GetNetworksResponseMetadata { #[serde(default, rename = "nextCursor")] pub next_cursor: Option, #[serde(default)] - pub page: Option + pub page: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksResponseMetadataPage { pub count: u64, - pub start: u64 + pub start: u64, } -fn u64_25() -> u64 { 25 } +fn u64_25() -> u64 { + 25 +} #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworksQueryParams { @@ -90,17 +92,27 @@ pub struct GetNetworksQueryParams { #[serde(default)] pub cursor: String, #[serde(default = "u64_25", rename = "pageSize")] - pub page_size: u64 + pub page_size: u64, } #[get("/v1/networks")] -pub async fn get_networks(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn get_networks( + opts: Query, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with networks:list - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["networks:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["networks:list"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -109,11 +121,13 @@ pub async fn get_networks(opts: Query, req_info: HttpReq path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -122,7 +136,7 @@ pub async fn get_networks(opts: Query, req_info: HttpReq path: None } ], - }) + }); } let org = match api_token_info { @@ -131,10 +145,14 @@ pub async fn get_networks(opts: Query, req_info: HttpReq // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -161,7 +179,7 @@ pub async fn get_networks(opts: Query, req_info: HttpReq path: None } ], - }) + }); } } }; @@ -171,18 +189,19 @@ pub async fn get_networks(opts: Query, req_info: HttpReq Err(e) => { error!("invalid cursor: {}", e); return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_CURSOR".to_string(), - message: "The provided cursor was invalid, please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_INVALID_CURSOR".to_string(), + message: "The provided cursor was invalid, please try again later.".to_string(), + path: None, + }], + }); } }; - let network_pages = network::Entity::find().filter(network::Column::Organization.eq(org)).order_by_asc(network::Column::CreatedAt).paginate(&db.conn, opts.page_size); + let network_pages = network::Entity::find() + .filter(network::Column::Organization.eq(org)) + .order_by_asc(network::Column::CreatedAt) + .paginate(&db.conn, opts.page_size); let total = match network_pages.num_items().await { Ok(r) => r, @@ -230,17 +249,22 @@ pub async fn get_networks(opts: Query, req_info: HttpReq }); } }; - let models_mapped: Vec = models.iter().map(|u| { - GetNetworksResponseData { + let models_mapped: Vec = models + .iter() + .map(|u| GetNetworksResponseData { id: u.id.clone(), cidr: u.cidr.clone(), organization_id: u.organization.clone(), signing_ca_id: u.signing_ca.clone(), - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(u.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), name: u.name.clone(), lighthouses_as_relays: u.lighthouses_as_relays, - } - }).collect(); + }) + .collect(); let count = models_mapped.len() as u64; @@ -248,20 +272,28 @@ pub async fn get_networks(opts: Query, req_info: HttpReq data: models_mapped, metadata: GetNetworksResponseMetadata { total_count: total, - has_next_page: cursor.page+1 != pages, + has_next_page: cursor.page + 1 != pages, has_prev_page: cursor.page != 0, prev_cursor: if cursor.page != 0 { - match (Cursor { page: cursor.page - 1 }).try_into() { + match (Cursor { + page: cursor.page - 1, + }) + .try_into() + { Ok(r) => Some(r), - Err(_) => None + Err(_) => None, } } else { None }, - next_cursor: if cursor.page+1 != pages { - match (Cursor { page: cursor.page + 1 }).try_into() { + next_cursor: if cursor.page + 1 != pages { + match (Cursor { + page: cursor.page + 1, + }) + .try_into() + { Ok(r) => Some(r), - Err(_) => None + Err(_) => None, } } else { None @@ -271,19 +303,31 @@ pub async fn get_networks(opts: Query, req_info: HttpReq count, start: opts.page_size * cursor.page, }) - } else { None }, + } else { + None + }, }, }) } #[get("/v1/networks/{network_id}")] -pub async fn get_network_request(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn get_network_request( + net: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with networks:list - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["networks:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["networks:read"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -292,11 +336,13 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -305,10 +351,14 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D path: None } ], - }) + }); } - let network: Option = match network::Entity::find().filter(network::Column::Id.eq(net.into_inner())).one(&db.conn).await { + let network: Option = match network::Entity::find() + .filter(network::Column::Id.eq(net.into_inner())) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -331,7 +381,11 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D cidr: network.cidr, organization_id: network.organization, signing_ca_id: network.signing_ca, - created_at: Utc.timestamp_opt(network.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(network.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), name: network.name, lighthouses_as_relays: network.lighthouses_as_relays, }, @@ -339,22 +393,19 @@ pub async fn get_network_request(net: Path, req_info: HttpRequest, db: D }) } else { HttpResponse::NotFound().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_MISSING_NETWORK".to_string(), - message: "Network does not exist".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_MISSING_NETWORK".to_string(), + message: "Network does not exist".to_string(), + path: None, + }], }) - } } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetNetworkResponse { pub data: GetNetworksResponseData, - pub metadata: GetNetworkResponseMetadata + pub metadata: GetNetworkResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct GetNetworkResponseMetadata {} \ No newline at end of file +pub struct GetNetworkResponseMetadata {} diff --git a/trifid-api/src/routes/v1/organization.rs b/trifid-api/src/routes/v1/organization.rs index 1be91ca..bd11080 100644 --- a/trifid-api/src/routes/v1/organization.rs +++ b/trifid-api/src/routes/v1/organization.rs @@ -19,59 +19,66 @@ // While this endpoint is considered done, help is wanted with reverse engineering the original API. Major features should not be added or removed unless it is replacing this endpoint with the correct, DN-compatible endpoint. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use actix_web::{HttpRequest, HttpResponse}; -use actix_web::web::{Data, Json}; -use serde::{Serialize, Deserialize}; -use crate::AppState; -use actix_web::post; -use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; -use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails, serialize_x25519_private}; -use trifid_pki::ed25519_dalek::SigningKey; -use trifid_pki::rand_core::OsRng; -use trifid_api_entities::entity::{network, organization, signing_ca}; use crate::auth_tokens::{enforce_2fa, TokenInfo}; use crate::config::CONFIG; use crate::crypto::{encrypt_with_nonce, generate_random_iv, get_cipher_from_config}; use crate::error::{APIError, APIErrorsResponse}; use crate::tokens::random_id; +use crate::AppState; +use actix_web::post; +use actix_web::web::{Data, Json}; +use actix_web::{HttpRequest, HttpResponse}; +use log::error; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use trifid_api_entities::entity::{network, organization, signing_ca}; +use trifid_pki::cert::{serialize_x25519_private, NebulaCertificate, NebulaCertificateDetails}; +use trifid_pki::ed25519_dalek::SigningKey; +use trifid_pki::rand_core::OsRng; #[derive(Serialize, Deserialize)] pub struct OrgCreateRequest { - pub cidr: String + pub cidr: String, } #[derive(Serialize, Deserialize)] pub struct OrgCreateResponse { pub organization: String, pub ca: String, - pub network: String + pub network: String, } #[post("/v1/organization")] -pub async fn create_org_request(req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn create_org_request( + req: Json, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you need to be a fully authenticated user - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, _ => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Unauthorized".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + }], }) } }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(&user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(&user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -89,14 +96,12 @@ pub async fn create_org_request(req: Json, req_info: HttpReque if org.is_some() { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_USER_ALREADY_OWNS_ORG".to_string(), - message: "This user already owns an organization".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_USER_ALREADY_OWNS_ORG".to_string(), + message: "This user already owns an organization".to_string(), + path: None, + }], + }); } let org = organization::Model { @@ -201,7 +206,12 @@ pub async fn create_org_request(req: Json, req_info: HttpReque organization: org.id.clone(), cert: ca_key_encrypted, key: ca_crt, - expires: cert.details.not_after.duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + expires: cert + .details + .not_after + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64, nonce: iv_hex, }; @@ -210,7 +220,10 @@ pub async fn create_org_request(req: Json, req_info: HttpReque cidr: req.cidr.clone(), organization: org.id.clone(), signing_ca: signing_ca.id.clone(), - created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + created_at: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64, name: "Network1".to_string(), lighthouses_as_relays: true, }; @@ -274,4 +287,4 @@ pub async fn create_org_request(req: Json, req_info: HttpReque ca: new_signing_ca_id, network: new_network_id, }) -} \ No newline at end of file +} diff --git a/trifid-api/src/routes/v1/roles.rs b/trifid-api/src/routes/v1/roles.rs index 990dfb9..b1a5c0a 100644 --- a/trifid-api/src/routes/v1/roles.rs +++ b/trifid-api/src/routes/v1/roles.rs @@ -35,24 +35,27 @@ // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. -use std::time::{SystemTime, UNIX_EPOCH}; -use actix_web::{get, HttpRequest, HttpResponse, post, put}; +use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::cursor::Cursor; +use crate::error::{APIError, APIErrorsResponse}; +use crate::timers::TIME_FORMAT; +use crate::tokens::random_id; +use crate::AppState; +use actix_web::delete; use actix_web::web::{Data, Json, Path, Query}; +use actix_web::{get, post, put, HttpRequest, HttpResponse}; use chrono::{TimeZone, Utc}; use log::error; -use serde::{Deserialize, Serialize}; -use crate::AppState; -use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; -use crate::error::{APIError, APIErrorsResponse}; -use trifid_api_entities::entity::organization; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait, QueryOrder, PaginatorTrait, ModelTrait}; -use trifid_api_entities::entity::firewall_rule; -use trifid_api_entities::entity::role; -use crate::cursor::Cursor; -use crate::tokens::random_id; -use actix_web::delete; use sea_orm::ActiveValue::Set; -use crate::timers::TIME_FORMAT; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, PaginatorTrait, + QueryFilter, QueryOrder, +}; +use serde::{Deserialize, Serialize}; +use std::time::{SystemTime, UNIX_EPOCH}; +use trifid_api_entities::entity::firewall_rule; +use trifid_api_entities::entity::organization; +use trifid_api_entities::entity::role; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct CreateRoleRequest { @@ -61,7 +64,7 @@ pub struct CreateRoleRequest { #[serde(default)] pub description: String, #[serde(default, rename = "firewallRules")] - pub firewall_rules: Vec + pub firewall_rules: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -70,7 +73,7 @@ pub struct UpdateRoleRequest { #[serde(default)] pub description: String, #[serde(default, rename = "firewallRules")] - pub firewall_rules: Vec + pub firewall_rules: Vec, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -93,19 +96,19 @@ pub enum RoleProtocol { #[serde(rename = "UDP")] Udp, #[serde(rename = "ICMP")] - Icmp + Icmp, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RolePortRange { pub from: u16, - pub to: u16 + pub to: u16, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RoleCreateResponse { pub data: RoleResponse, - pub metadata: RoleCreateResponseMetadata + pub metadata: RoleCreateResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -118,20 +121,30 @@ pub struct RoleResponse { #[serde(rename = "createdAt")] pub created_at: String, #[serde(rename = "modifiedAt")] - pub modified_at: String + pub modified_at: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct RoleCreateResponseMetadata {} #[post("/v1/roles")] -pub async fn create_role_request(req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn create_role_request( + req: Json, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:create - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -140,11 +153,13 @@ pub async fn create_role_request(req: Json, req_info: HttpReq path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -153,7 +168,7 @@ pub async fn create_role_request(req: Json, req_info: HttpReq path: None } ], - }) + }); } let org = match api_token_info { @@ -162,10 +177,14 @@ pub async fn create_role_request(req: Json, req_info: HttpReq // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -192,7 +211,7 @@ pub async fn create_role_request(req: Json, req_info: HttpReq path: None } ], - }) + }); } } }; @@ -202,20 +221,36 @@ pub async fn create_role_request(req: Json, req_info: HttpReq name: req.name.clone(), description: req.description.clone(), organization: org, - created_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, - modified_at: SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64, + created_at: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64, + modified_at: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64, }; - let firewall_rules: Vec = req.firewall_rules.iter().map(|i| { - firewall_rule::Model { + let firewall_rules: Vec = req + .firewall_rules + .iter() + .map(|i| firewall_rule::Model { id: random_id("rule"), role: new_role_model.id.clone(), protocol: i.protocol.to_string(), description: i.description.clone(), allowed_role_id: i.allowed_role_id.clone(), - port_range_from: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).from as i32, - port_range_to: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).to as i32, - } - }).collect(); + port_range_from: i + .port_range + .as_ref() + .unwrap_or(&RolePortRange { from: 0, to: 65535 }) + .from as i32, + port_range_to: i + .port_range + .as_ref() + .unwrap_or(&RolePortRange { from: 0, to: 65535 }) + .to as i32, + }) + .collect(); let new_role_model_clone = new_role_model.clone(); let firewall_rules_clone = firewall_rules.clone(); @@ -226,14 +261,13 @@ pub async fn create_role_request(req: Json, req_info: HttpReq Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new role. Please try again later".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later" + .to_string(), + path: None, + }], + }); } } @@ -244,14 +278,13 @@ pub async fn create_role_request(req: Json, req_info: HttpReq Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new role. Please try again later".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later" + .to_string(), + path: None, + }], + }); } } } @@ -262,8 +295,16 @@ pub async fn create_role_request(req: Json, req_info: HttpReq name: Some(new_role_model_clone.name.clone()), description: Some(new_role_model_clone.description), firewall_rules: req.firewall_rules.clone(), - created_at: Utc.timestamp_opt(new_role_model_clone.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), - modified_at: Utc.timestamp_opt(new_role_model_clone.modified_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(new_role_model_clone.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + modified_at: Utc + .timestamp_opt(new_role_model_clone.modified_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), }, metadata: RoleCreateResponseMetadata {}, }) @@ -275,7 +316,7 @@ impl ToString for RoleProtocol { RoleProtocol::Any => "ANY".to_string(), RoleProtocol::Tcp => "TCP".to_string(), RoleProtocol::Udp => "UDP".to_string(), - RoleProtocol::Icmp => "ICMP".to_string() + RoleProtocol::Icmp => "ICMP".to_string(), } } } @@ -287,15 +328,17 @@ pub struct ListRolesRequestOpts { #[serde(default)] pub cursor: String, #[serde(default = "page_default", rename = "pageSize")] - pub page_size: u64 + pub page_size: u64, } -fn page_default() -> u64 { 25 } +fn page_default() -> u64 { + 25 +} #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetRolesResponse { pub data: Vec, - pub metadata: GetRolesResponseMetadata + pub metadata: GetRolesResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -311,23 +354,33 @@ pub struct GetRolesResponseMetadata { #[serde(default, rename = "nextCursor")] pub next_cursor: Option, #[serde(default)] - pub page: Option + pub page: Option, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GetRolesResponseMetadataPage { pub count: u64, - pub start: u64 + pub start: u64, } #[get("/v1/roles")] -pub async fn get_roles(opts: Query, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn get_roles( + opts: Query, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:list - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["roles:list"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:list"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -336,11 +389,13 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, path: None, } ], - }) + }); } // If both are present, throw an error - if matches!(session_info, TokenInfo::AuthToken(_)) && matches!(api_token_info, TokenInfo::ApiToken(_)) { + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { return HttpResponse::BadRequest().json(APIErrorsResponse { errors: vec![ APIError { @@ -349,7 +404,7 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, path: None } ], - }) + }); } let org = match api_token_info { @@ -358,10 +413,14 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -388,7 +447,7 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, path: None } ], - }) + }); } } }; @@ -398,18 +457,19 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, Err(e) => { error!("invalid cursor: {}", e); return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_INVALID_CURSOR".to_string(), - message: "The provided cursor was invalid, please try again later.".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_INVALID_CURSOR".to_string(), + message: "The provided cursor was invalid, please try again later.".to_string(), + path: None, + }], + }); } }; - let network_pages = role::Entity::find().filter(role::Column::Organization.eq(org)).order_by_asc(role::Column::CreatedAt).paginate(&db.conn, opts.page_size); + let network_pages = role::Entity::find() + .filter(role::Column::Organization.eq(org)) + .order_by_asc(role::Column::CreatedAt) + .paginate(&db.conn, opts.page_size); let total = match network_pages.num_items().await { Ok(r) => r, @@ -462,7 +522,11 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, for u in models { // fetch firewall rules - let rules = match firewall_rule::Entity::find().filter(firewall_rule::Column::Role.eq(&u.id)).all(&db.conn).await { + let rules = match firewall_rule::Entity::find() + .filter(firewall_rule::Column::Role.eq(&u.id)) + .all(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -478,41 +542,52 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, } }; - let rules: Vec = rules.iter().map(|r| { - let protocol = match r.protocol.as_str() { - "ANY" => RoleProtocol::Any, - "TCP" => RoleProtocol::Tcp, - "UDP" => RoleProtocol::Udp, - "ICMP" => RoleProtocol::Icmp, - _ => unreachable!("database has been corrupted or manually edited") - }; + let rules: Vec = rules + .iter() + .map(|r| { + let protocol = match r.protocol.as_str() { + "ANY" => RoleProtocol::Any, + "TCP" => RoleProtocol::Tcp, + "UDP" => RoleProtocol::Udp, + "ICMP" => RoleProtocol::Icmp, + _ => unreachable!("database has been corrupted or manually edited"), + }; + let port_range = if r.port_range_from == 0 && r.port_range_to == 65535 + || matches!(protocol, RoleProtocol::Icmp) + { + None + } else { + Some(RolePortRange { + from: r.port_range_from as u16, + to: r.port_range_to as u16, + }) + }; - - let port_range = if r.port_range_from == 0 && r.port_range_to == 65535 || matches!(protocol, RoleProtocol::Icmp) { - None - } else { - Some(RolePortRange { - from: r.port_range_from as u16, - to: r.port_range_to as u16, - }) - }; - - RoleFirewallRule { - protocol, - description: r.description.clone(), - allowed_role_id: r.allowed_role_id.clone(), - port_range, - } - }).collect(); + RoleFirewallRule { + protocol, + description: r.description.clone(), + allowed_role_id: r.allowed_role_id.clone(), + port_range, + } + }) + .collect(); models_mapped.push(RoleResponse { id: Some(u.id.clone()), name: Some(u.name), description: Some(u.description), firewall_rules: rules, - created_at: Utc.timestamp_opt(u.created_at, 0).unwrap().format(TIME_FORMAT).to_string(), - modified_at: Utc.timestamp_opt(u.modified_at, 0).unwrap().format(TIME_FORMAT).to_string(), + created_at: Utc + .timestamp_opt(u.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + modified_at: Utc + .timestamp_opt(u.modified_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), }) } @@ -522,20 +597,28 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, data: models_mapped, metadata: GetRolesResponseMetadata { total_count: total, - has_next_page: cursor.page+1 != pages, + has_next_page: cursor.page + 1 != pages, has_prev_page: cursor.page != 0, prev_cursor: if cursor.page != 0 { - match (Cursor { page: cursor.page - 1 }).try_into() { + match (Cursor { + page: cursor.page - 1, + }) + .try_into() + { Ok(r) => Some(r), - Err(_) => None + Err(_) => None, } } else { None }, - next_cursor: if cursor.page+1 != pages { - match (Cursor { page: cursor.page + 1 }).try_into() { + next_cursor: if cursor.page + 1 != pages { + match (Cursor { + page: cursor.page + 1, + }) + .try_into() + { Ok(r) => Some(r), - Err(_) => None + Err(_) => None, } } else { None @@ -545,19 +628,31 @@ pub async fn get_roles(opts: Query, req_info: HttpRequest, count, start: opts.page_size * cursor.page, }) - } else { None }, + } else { + None + }, }, }) } #[get("/v1/roles/{role_id}")] -pub async fn get_role(net: Path, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn get_role( + net: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:read - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["roles:read"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:read"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -566,11 +661,13 @@ pub async fn get_role(net: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data = match role::Entity::find().filter(role::Column::Id.eq(net.into_inner())).one(&db.conn).await { + let role: Option = match role::Entity::find() + .filter(role::Column::Id.eq(net.into_inner())) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -598,8 +699,13 @@ pub async fn get_role(net: Path, req_info: HttpRequest, db: Data r, Err(e) => { error!("database error: {}", e); @@ -615,34 +721,36 @@ pub async fn get_role(net: Path, req_info: HttpRequest, db: Data = rules.iter().map(|r| { - let protocol = match r.protocol.as_str() { - "ANY" => RoleProtocol::Any, - "TCP" => RoleProtocol::Tcp, - "UDP" => RoleProtocol::Udp, - "ICMP" => RoleProtocol::Icmp, - _ => unreachable!("database has been corrupted or manually edited") - }; + let rules: Vec = rules + .iter() + .map(|r| { + let protocol = match r.protocol.as_str() { + "ANY" => RoleProtocol::Any, + "TCP" => RoleProtocol::Tcp, + "UDP" => RoleProtocol::Udp, + "ICMP" => RoleProtocol::Icmp, + _ => unreachable!("database has been corrupted or manually edited"), + }; + let port_range = if r.port_range_from == 0 && r.port_range_to == 65535 + || matches!(protocol, RoleProtocol::Icmp) + { + None + } else { + Some(RolePortRange { + from: r.port_range_from as u16, + to: r.port_range_to as u16, + }) + }; - - let port_range = if r.port_range_from == 0 && r.port_range_to == 65535 || matches!(protocol, RoleProtocol::Icmp) { - None - } else { - Some(RolePortRange { - from: r.port_range_from as u16, - to: r.port_range_to as u16, - }) - }; - - RoleFirewallRule { - protocol, - description: r.description.clone(), - allowed_role_id: r.allowed_role_id.clone(), - port_range, - } - }).collect(); - + RoleFirewallRule { + protocol, + description: r.description.clone(), + allowed_role_id: r.allowed_role_id.clone(), + port_range, + } + }) + .collect(); HttpResponse::Ok().json(GetRoleResponse { data: RoleResponse { @@ -650,41 +758,56 @@ pub async fn get_role(net: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn delete_role( + role: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:delete - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["roles:delete"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:delete"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -693,11 +816,13 @@ pub async fn delete_role(role: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data = match role::Entity::find().filter(role::Column::Id.eq(role.into_inner())).one(&db.conn).await { + let role: Option = match role::Entity::find() + .filter(role::Column::Id.eq(role.into_inner())) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -748,13 +877,11 @@ pub async fn delete_role(role: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data + pub firewall_rules: Vec, } #[put("/v1/roles/{role_id}")] -pub async fn update_role_request(role: Path, req: Json, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn update_role_request( + role: Path, + req: Json, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { // For this endpoint, you either need to be a fully authenticated user OR a token with roles:create - let session_info = enforce_2fa(&req_info, &db.conn).await.unwrap_or(TokenInfo::NotPresent); - let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn).await.unwrap_or(TokenInfo::NotPresent); + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["roles:create"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); // If neither are present, throw an error - if matches!(session_info, TokenInfo::NotPresent) && matches!(api_token_info, TokenInfo::NotPresent) { + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![ APIError { @@ -794,11 +932,13 @@ pub async fn update_role_request(role: Path, req: Json, req: Json tkn.organization, + match api_token_info { + TokenInfo::ApiToken(_) => (), _ => { // we have a session token, which means we have to do a db request to get the organization that this user owns let user = match session_info { TokenInfo::AuthToken(tkn) => tkn.session_info.user, - _ => unreachable!() + _ => unreachable!(), }; - let org = match organization::Entity::find().filter(organization::Column::Owner.eq(user.id)).one(&db.conn).await { + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -835,9 +979,7 @@ pub async fn update_role_request(role: Path, req: Json, req: Json = match firewall_rule::Entity::find().filter(firewall_rule::Column::Role.eq(role.clone())).all(&db.conn).await { + let existing_rules: Vec = match firewall_rule::Entity::find() + .filter(firewall_rule::Column::Role.eq(role.clone())) + .all(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); @@ -885,7 +1031,11 @@ pub async fn update_role_request(role: Path, req: Json r, Err(e) => { error!("database error: {}", e); @@ -905,20 +1055,23 @@ pub async fn update_role_request(role: Path, req: Json r, None => { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "This resource does not exist or you do not have permission to access it.".to_string(), - path: None - } - ] + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: + "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], }) } }; let mut role_active_model = role.clone().into_active_model(); - role_active_model.modified_at = Set(SystemTime::now().duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() as i64); + role_active_model.modified_at = Set(SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() as i64); role_active_model.description = Set(req.description.clone()); let role = match role_active_model.update(&db.conn).await { @@ -937,17 +1090,27 @@ pub async fn update_role_request(role: Path, req: Json = req.firewall_rules.iter().map(|i| { - firewall_rule::Model { + let firewall_rules: Vec = req + .firewall_rules + .iter() + .map(|i| firewall_rule::Model { id: random_id("rule"), role: role.id.clone(), protocol: i.protocol.to_string(), description: i.description.clone(), allowed_role_id: i.allowed_role_id.clone(), - port_range_from: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).from as i32, - port_range_to: i.port_range.as_ref().unwrap_or(&RolePortRange { from: 0, to: 65535 }).to as i32, - } - }).collect(); + port_range_from: i + .port_range + .as_ref() + .unwrap_or(&RolePortRange { from: 0, to: 65535 }) + .from as i32, + port_range_to: i + .port_range + .as_ref() + .unwrap_or(&RolePortRange { from: 0, to: 65535 }) + .to as i32, + }) + .collect(); let firewall_rules_clone = firewall_rules.clone(); @@ -958,14 +1121,13 @@ pub async fn update_role_request(role: Path, req: Json { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error creating the new role. Please try again later".to_string(), - path: None - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new role. Please try again later" + .to_string(), + path: None, + }], + }); } } } @@ -976,8 +1138,16 @@ pub async fn update_role_request(role: Path, req: Json, - pub metadata: SignupResponseMetadata + pub metadata: SignupResponseMetadata, } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct SignupResponseData {} @@ -50,37 +50,39 @@ pub struct SignupResponseMetadata {} #[post("/v1/signup")] pub async fn signup_request(data: Data, req: Json) -> HttpResponse { - let user: Vec = match UserEntity::find().filter(user::Column::Email.eq(&req.email)).all(&data.conn).await { + let user: Vec = match UserEntity::find() + .filter(user::Column::Email.eq(&req.email)) + .all(&data.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } }; if !user.is_empty() { return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_USER_EXISTS".to_string(), - message: "That user already exists.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_USER_EXISTS".to_string(), + message: "That user already exists.".to_string(), + path: None, + }], + }); } let model = user::Model { id: random_id("user"), - email: req.email.clone() + email: req.email.clone(), }; let id = model.id.clone(); @@ -91,14 +93,14 @@ pub async fn signup_request(data: Data, req: Json) -> H Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } @@ -113,14 +115,14 @@ pub async fn signup_request(data: Data, req: Json) -> H Err(e) => { error!("error sending magic link: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_ML_ERROR".to_string(), - message: "There was an error sending the magic link email, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_ML_ERROR".to_string(), + message: + "There was an error sending the magic link email, please try again later." + .to_string(), + path: None, + }], + }); } } @@ -131,19 +133,19 @@ pub async fn signup_request(data: Data, req: Json) -> H Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } HttpResponse::Ok().json(SignupResponse { data: None, - metadata: SignupResponseMetadata {} + metadata: SignupResponseMetadata {}, }) -} \ No newline at end of file +} diff --git a/trifid-api/src/routes/v1/totp_authenticators.rs b/trifid-api/src/routes/v1/totp_authenticators.rs index d20a621..e80b3dc 100644 --- a/trifid-api/src/routes/v1/totp_authenticators.rs +++ b/trifid-api/src/routes/v1/totp_authenticators.rs @@ -19,19 +19,21 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use serde::{Serialize, Deserialize}; -use actix_web::{HttpRequest, HttpResponse, post}; -use actix_web::web::{Data, Json}; -use log::error; -use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter}; -use totp_rs::{Algorithm, Secret, TOTP}; -use crate::AppState; use crate::auth_tokens::{enforce_session, TokenInfo}; -use crate::error::{APIError, APIErrorsResponse}; -use trifid_api_entities::entity::totp_authenticator; use crate::config::CONFIG; +use crate::error::{APIError, APIErrorsResponse}; use crate::timers::expires_in_seconds; -use crate::tokens::{random_token}; +use crate::tokens::random_token; +use crate::AppState; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpRequest, HttpResponse}; +use log::error; +use sea_orm::{ + ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, ModelTrait, QueryFilter, +}; +use serde::{Deserialize, Serialize}; +use totp_rs::{Algorithm, Secret, TOTP}; +use trifid_api_entities::entity::totp_authenticator; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct TotpAuthenticatorsRequest {} @@ -54,55 +56,57 @@ pub struct TotpAuthenticatorsResponse { } #[post("/v1/totp-authenticators")] -pub async fn totp_authenticators_request(db: Data, req_data: HttpRequest, _req: Json) -> HttpResponse { +pub async fn totp_authenticators_request( + db: Data, + req_data: HttpRequest, + _req: Json, +) -> HttpResponse { // require a user session let session_token = match enforce_session(&req_data, &db.conn).await { - Ok(r) => { - match r { - TokenInfo::SessionToken(i) => i, - _ => unreachable!() - } - } + Ok(r) => match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!(), + }, Err(e) => { error!("error enforcing session: {}", e); return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Unauthorized".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + }], }); } }; // determine if the user has a totp authenticator - let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::User.eq(&session_token.user.id)).one(&db.conn).await { + let auther = match totp_authenticator::Entity::find() + .filter(totp_authenticator::Column::User.eq(&session_token.user.id)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], }); } }; if let Some(auther) = auther { if auther.verified { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_ALREADY_HAS_TOTP".to_string(), - message: "This user already has a totp authenticator".to_string(), - path: None, - } - ] + errors: vec![APIError { + code: "ERR_ALREADY_HAS_TOTP".to_string(), + message: "This user already has a totp authenticator".to_string(), + path: None, + }], }); } match auther.delete(&db.conn).await { @@ -110,38 +114,48 @@ pub async fn totp_authenticators_request(db: Data, req_data: HttpReque Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } } let secret = Secret::generate_secret(); - let totpmachine = match TOTP::new(Algorithm::SHA1, 6, 1, 30, secret.to_bytes().expect("Invalid randomized data"), Some("trifid-api".to_string()), session_token.user.email) { + let totpmachine = match TOTP::new( + Algorithm::SHA1, + 6, + 1, + 30, + secret.to_bytes().expect("Invalid randomized data"), + Some("trifid-api".to_string()), + session_token.user.email, + ) { Ok(m) => m, Err(e) => { error!("totp machine create error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_SECRET_ERR".to_string(), - message: "There was an error configuring the authenticator, please try again later.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_SECRET_ERR".to_string(), + message: + "There was an error configuring the authenticator, please try again later." + .to_string(), + path: None, + }], }); } }; let model = totp_authenticator::Model { id: random_token("totp"), - secret: Secret::Raw(totpmachine.secret.clone()).to_encoded().to_string(), + secret: Secret::Raw(totpmachine.secret.clone()) + .to_encoded() + .to_string(), url: totpmachine.get_url(), verified: false, expires_on: expires_in_seconds(CONFIG.tokens.totp_setup_timeout_time_seconds) as i64, @@ -157,14 +171,14 @@ pub async fn totp_authenticators_request(db: Data, req_data: HttpReque Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], + }); } } @@ -176,4 +190,4 @@ pub async fn totp_authenticators_request(db: Data, req_data: HttpReque }, metadata: TotpAuthenticatorsResponseMetadata {}, }) -} \ No newline at end of file +} diff --git a/trifid-api/src/routes/v1/trifid.rs b/trifid-api/src/routes/v1/trifid.rs index a7f9fb7..3445887 100644 --- a/trifid-api/src/routes/v1/trifid.rs +++ b/trifid-api/src/routes/v1/trifid.rs @@ -32,14 +32,19 @@ // If the request returns a non-200 response, or does not follow the typical TrifidExtensions schema, that server should be assumed to only support t+features:definednetworking. // Endpoint specs (#REQTYPE) can indicate they require a feature by adding t+features:[feature] -use actix_web::{HttpResponse, get}; +use actix_web::{get, HttpResponse}; use serde::{Deserialize, Serialize}; -pub const SUPPORTED_EXTENSIONS: &[&str] = &["definednetworking", "trifidextensions", "extended_roles", "extended_hosts"]; +pub const SUPPORTED_EXTENSIONS: &[&str] = &[ + "definednetworking", + "trifidextensions", + "extended_roles", + "extended_hosts", +]; #[derive(Serialize, Deserialize)] pub struct TrifidExtensionsResponse { - pub extensions: Vec + pub extensions: Vec, } #[get("/v1/trifid_extensions")] @@ -47,4 +52,4 @@ pub async fn trifid_extensions() -> HttpResponse { HttpResponse::Ok().json(TrifidExtensionsResponse { extensions: SUPPORTED_EXTENSIONS.iter().map(|u| u.to_string()).collect(), }) -} \ No newline at end of file +} diff --git a/trifid-api/src/routes/v1/verify_totp_authenticators.rs b/trifid-api/src/routes/v1/verify_totp_authenticators.rs index 2199c1d..418fead 100644 --- a/trifid-api/src/routes/v1/verify_totp_authenticators.rs +++ b/trifid-api/src/routes/v1/verify_totp_authenticators.rs @@ -19,81 +19,85 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. -use actix_web::{HttpRequest, HttpResponse, post}; -use actix_web::web::{Data, Json}; -use log::{debug, error}; -use serde::{Serialize, Deserialize}; -use trifid_api_entities::entity::totp_authenticator; -use crate::AppState; use crate::auth_tokens::{enforce_session, TokenInfo}; -use crate::error::{APIError, APIErrorsResponse}; -use sea_orm::{EntityTrait, QueryFilter, ColumnTrait, IntoActiveModel, ActiveModelTrait}; -use sea_orm::ActiveValue::Set; -use totp_rs::{Secret, TOTP}; -use trifid_api_entities::entity::auth_token; use crate::config::CONFIG; +use crate::error::{APIError, APIErrorsResponse}; use crate::timers::expires_in_seconds; use crate::tokens::random_token; +use crate::AppState; +use actix_web::web::{Data, Json}; +use actix_web::{post, HttpRequest, HttpResponse}; +use log::{debug, error}; +use sea_orm::ActiveValue::Set; +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; +use serde::{Deserialize, Serialize}; +use totp_rs::{Secret, TOTP}; +use trifid_api_entities::entity::auth_token; +use trifid_api_entities::entity::totp_authenticator; #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyTotpAuthenticatorsRequest { #[serde(rename = "totpToken")] pub totp_token: String, - pub code: String + pub code: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyTotpAuthenticatorsResponse { pub data: VerifyTotpAuthenticatorsResponseData, - pub metadata: VerifyTotpAuthenticatorsResponseMetadata + pub metadata: VerifyTotpAuthenticatorsResponseMetadata, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyTotpAuthenticatorsResponseData { #[serde(rename = "authToken")] - pub auth_token: String + pub auth_token: String, } #[derive(Serialize, Deserialize, Debug, Clone)] pub struct VerifyTotpAuthenticatorsResponseMetadata {} #[post("/v1/verify-totp-authenticators")] -pub async fn verify_totp_authenticators_request(req: Json, req_data: HttpRequest, db: Data) -> HttpResponse { +pub async fn verify_totp_authenticators_request( + req: Json, + req_data: HttpRequest, + db: Data, +) -> HttpResponse { // require a user session let session_token = match enforce_session(&req_data, &db.conn).await { - Ok(r) => { - match r { - TokenInfo::SessionToken(i) => i, - _ => unreachable!() - } - } + Ok(r) => match r { + TokenInfo::SessionToken(i) => i, + _ => unreachable!(), + }, Err(e) => { error!("error enforcing session: {}", e); return HttpResponse::Unauthorized().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_UNAUTHORIZED".to_string(), - message: "Unauthorized".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "Unauthorized".to_string(), + path: None, + }], }); } }; // determine if the user has a totp authenticator - let auther = match totp_authenticator::Entity::find().filter(totp_authenticator::Column::Id.eq(&req.totp_token)).one(&db.conn).await { + let auther = match totp_authenticator::Entity::find() + .filter(totp_authenticator::Column::Id.eq(&req.totp_token)) + .one(&db.conn) + .await + { Ok(r) => r, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error with the database request, please try again later.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: + "There was an error with the database request, please try again later." + .to_string(), + path: None, + }], }); } }; @@ -101,26 +105,22 @@ pub async fn verify_totp_authenticators_request(req: Json { if a.verified { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_ALREADY_HAS_TOTP".to_string(), - message: "This user already has a totp authenticator".to_string(), - path: None, - } - ] + errors: vec![APIError { + code: "ERR_ALREADY_HAS_TOTP".to_string(), + message: "This user already has a totp authenticator".to_string(), + path: None, + }], }); } a - }, + } None => { return HttpResponse::BadRequest().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_USER_NO_TOTP".to_string(), - message: "This user does not have a totp authenticator".to_string(), - path: None, - } - ] + errors: vec![APIError { + code: "ERR_USER_NO_TOTP".to_string(), + message: "This user does not have a totp authenticator".to_string(), + path: None, + }], }); } }; @@ -131,30 +131,26 @@ pub async fn verify_totp_authenticators_request(req: Json { error!("totp url error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_SECRET_ERROR".to_string(), - message: "There was an error parsing the totpmachine. Please try again later.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_SECRET_ERROR".to_string(), + message: "There was an error parsing the totpmachine. Please try again later." + .to_string(), + path: None, + }], }); } }; - let valid = match totpmachine.check_current(&req.code) { Ok(valid) => valid, Err(e) => { error!("system time error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_TIME_ERROR".to_string(), - message: "There was an with the server-side time clock.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_TIME_ERROR".to_string(), + message: "There was an with the server-side time clock.".to_string(), + path: None, + }], }); } }; @@ -163,14 +159,12 @@ pub async fn verify_totp_authenticators_request(req: Json { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error updating the totpmachine, please try again later.".to_string(), - path: None, - } - ], - }) + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error updating the totpmachine, please try again later." + .to_string(), + path: None, + }], + }); } } @@ -205,13 +198,11 @@ pub async fn verify_totp_authenticators_request(req: Json { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { - errors: vec![ - APIError { - code: "ERR_DB_ERROR".to_string(), - message: "There was an error issuing the authentication token.".to_string(), - path: None, - } - ], + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error issuing the authentication token.".to_string(), + path: None, + }], }); } } @@ -220,4 +211,4 @@ pub async fn verify_totp_authenticators_request(req: Json u64 { - (SystemTime::now() + Duration::from_secs(seconds)).duration_since(UNIX_EPOCH).expect("Time went backwards").as_secs() + (SystemTime::now() + Duration::from_secs(seconds)) + .duration_since(UNIX_EPOCH) + .expect("Time went backwards") + .as_secs() } pub fn expired(time: u64) -> bool { UNIX_EPOCH + Duration::from_secs(time) < SystemTime::now() -} \ No newline at end of file +} diff --git a/trifid-api/src/tokens.rs b/trifid-api/src/tokens.rs index 2ab6afa..cb276f1 100644 --- a/trifid-api/src/tokens.rs +++ b/trifid-api/src/tokens.rs @@ -20,7 +20,8 @@ use rand::Rng; pub const ID_CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; pub const ID_LEN: u32 = 26; -pub const TOKEN_CHARSET: &[u8] = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; +pub const TOKEN_CHARSET: &[u8] = + b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_"; pub const TOKEN_LEN: u32 = 43; // 26 @@ -38,16 +39,22 @@ pub fn random_id_no_id() -> HeaderValue { // 43 // format: [TYPE]-[43 chars] pub fn random_token(identifier: &str) -> String { - format!("{}-{}", identifier, random_with_charset(TOKEN_LEN, TOKEN_CHARSET)) + format!( + "{}-{}", + identifier, + random_with_charset(TOKEN_LEN, TOKEN_CHARSET) + ) } fn random_with_charset(len: u32, charset: &[u8]) -> String { - (0..len).map(|_| { - let idx = rand::thread_rng().gen_range(0..charset.len()); - charset[idx] as char - }).collect() + (0..len) + .map(|_| { + let idx = rand::thread_rng().gen_range(0..charset.len()); + charset[idx] as char + }) + .collect() } pub fn get_token_type(token: &str) -> Option<&str> { - token.split('-').collect::>().get(0).copied() -} \ No newline at end of file + token.split('-').collect::>().first().copied() +} diff --git a/trifid-api/trifid_api_entities/src/lib.rs b/trifid-api/trifid_api_entities/src/lib.rs index bccca66..e8c3d6a 100644 --- a/trifid-api/trifid_api_entities/src/lib.rs +++ b/trifid-api/trifid_api_entities/src/lib.rs @@ -1 +1 @@ -pub mod entity; \ No newline at end of file +pub mod entity; diff --git a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs index 1e04c6a..abbe7dd 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_162601_create_table_users.rs @@ -13,12 +13,15 @@ impl MigrationTrait for Migration { .if_not_exists() .col(ColumnDef::new(User::Id).string().not_null().primary_key()) .col(ColumnDef::new(User::Email).string().not_null().unique_key()) - .to_owned() - ).await + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(User::Table).to_owned()).await + manager + .drop_table(Table::drop().table(User::Table).to_owned()) + .await } } @@ -27,5 +30,5 @@ impl MigrationTrait for Migration { pub enum User { Table, Id, - Email + Email, } diff --git a/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs b/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs index 43c70df..4cd2bb6 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_183515_create_table_magic_links.rs @@ -1,6 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_162601_create_table_users::User; - +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -8,26 +7,40 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(MagicLink::Table) - .if_not_exists() - .col(ColumnDef::new(MagicLink::Id).string().not_null().primary_key()) - .col(ColumnDef::new(MagicLink::User).string().not_null()) - .col(ColumnDef::new(MagicLink::ExpiresOn).big_integer().not_null()) - .foreign_key( - ForeignKey::create() - .name("fk_magiclink_user_users_id") - .from(MagicLink::Table, MagicLink::User) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ).to_owned() - ).await + manager + .create_table( + Table::create() + .table(MagicLink::Table) + .if_not_exists() + .col( + ColumnDef::new(MagicLink::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(MagicLink::User).string().not_null()) + .col( + ColumnDef::new(MagicLink::ExpiresOn) + .big_integer() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .name("fk_magiclink_user_users_id") + .from(MagicLink::Table, MagicLink::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(MagicLink::Table).to_owned()).await + manager + .drop_table(Table::drop().table(MagicLink::Table).to_owned()) + .await } } @@ -37,5 +50,5 @@ pub enum MagicLink { Table, Id, User, - ExpiresOn + ExpiresOn, } diff --git a/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs b/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs index 8c51606..128d7e5 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_213712_create_table_session_tokens.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_162601_create_table_users::User; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,26 +7,39 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(SessionToken::Table) - .if_not_exists() - .col(ColumnDef::new(SessionToken::Id).string().not_null().primary_key()) - .col(ColumnDef::new(SessionToken::User).string().not_null()) - .col(ColumnDef::new(SessionToken::ExpiresOn).big_integer().not_null()) - .foreign_key( - ForeignKey::create() - .from(SessionToken::Table, SessionToken::User) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(SessionToken::Table) + .if_not_exists() + .col( + ColumnDef::new(SessionToken::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(SessionToken::User).string().not_null()) + .col( + ColumnDef::new(SessionToken::ExpiresOn) + .big_integer() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .from(SessionToken::Table, SessionToken::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(SessionToken::Table).to_owned()).await + manager + .drop_table(Table::drop().table(SessionToken::Table).to_owned()) + .await } } @@ -36,5 +49,5 @@ pub enum SessionToken { Table, Id, User, - ExpiresOn + ExpiresOn, } diff --git a/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs b/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs index 86d18cd..90e82c0 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_232316_create_table_organizations.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_162601_create_table_users::User; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,23 +7,39 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create().table(Organization::Table) - .col(ColumnDef::new(Organization::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Organization::Name).string().not_null()) - .col(ColumnDef::new(Organization::Owner).string().not_null().unique_key()) - .foreign_key( - ForeignKey::create() - .from(Organization::Table, Organization::Owner) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ).to_owned() - ).await + manager + .create_table( + Table::create() + .table(Organization::Table) + .col( + ColumnDef::new(Organization::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Organization::Name).string().not_null()) + .col( + ColumnDef::new(Organization::Owner) + .string() + .not_null() + .unique_key(), + ) + .foreign_key( + ForeignKey::create() + .from(Organization::Table, Organization::Owner) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(Organization::Table).to_owned()).await + manager + .drop_table(Table::drop().table(Organization::Table).to_owned()) + .await } } @@ -33,5 +49,5 @@ pub enum Organization { Table, Id, Name, - Owner + Owner, } diff --git a/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs b/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs index c0d36c3..250545b 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_233043_create_table_api_keys.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_232316_create_table_organizations::Organization; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,25 +7,29 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(ApiKey::Table) - .col(ColumnDef::new(ApiKey::Id).string().not_null().primary_key()) - .col(ColumnDef::new(ApiKey::Key).string().not_null().unique_key()) - .col(ColumnDef::new(ApiKey::Organization).string().not_null()) - .foreign_key( - ForeignKey::create() - .from(ApiKey::Table, ApiKey::Organization) - .to(Organization::Table, Organization::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(ApiKey::Table) + .col(ColumnDef::new(ApiKey::Id).string().not_null().primary_key()) + .col(ColumnDef::new(ApiKey::Key).string().not_null().unique_key()) + .col(ColumnDef::new(ApiKey::Organization).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(ApiKey::Table, ApiKey::Organization) + .to(Organization::Table, Organization::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(ApiKey::Table).to_owned()).await + manager + .drop_table(Table::drop().table(ApiKey::Table).to_owned()) + .await } } @@ -35,5 +39,5 @@ pub enum ApiKey { Table, Id, Key, - Organization + Organization, } diff --git a/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs b/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs index 359aa24..233c6b4 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_233047_create_table_api_keys_scopes.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_233043_create_table_api_keys::ApiKey; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,24 +7,34 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(ApiKeyScope::Table) - .col(ColumnDef::new(ApiKeyScope::Id).string().not_null().primary_key()) - .col(ColumnDef::new(ApiKeyScope::Scope).string().not_null()) - .col(ColumnDef::new(ApiKeyScope::ApiKey).string().not_null()) - .foreign_key( - ForeignKey::create() - .from(ApiKeyScope::Table, ApiKeyScope::ApiKey) - .to(ApiKey::Table, ApiKey::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ).to_owned() - ).await + manager + .create_table( + Table::create() + .table(ApiKeyScope::Table) + .col( + ColumnDef::new(ApiKeyScope::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(ApiKeyScope::Scope).string().not_null()) + .col(ColumnDef::new(ApiKeyScope::ApiKey).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(ApiKeyScope::Table, ApiKeyScope::ApiKey) + .to(ApiKey::Table, ApiKey::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(ApiKeyScope::Table).to_owned()).await + manager + .drop_table(Table::drop().table(ApiKeyScope::Table).to_owned()) + .await } } @@ -34,5 +44,5 @@ pub enum ApiKeyScope { Table, Id, Scope, - ApiKey + ApiKey, } diff --git a/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs b/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs index 72b2379..31d5324 100644 --- a/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs +++ b/trifid-api/trifid_api_migration/src/m20230402_234025_create_table_totp_authenticators.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_162601_create_table_users::User; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,27 +7,60 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(TotpAuthenticator::Table) - .col(ColumnDef::new(TotpAuthenticator::Id).string().not_null().primary_key()) - .col(ColumnDef::new(TotpAuthenticator::Secret).string().not_null().unique_key()) - .col(ColumnDef::new(TotpAuthenticator::Url).string().not_null().unique_key()) - .col(ColumnDef::new(TotpAuthenticator::Verified).boolean().not_null()) - .col(ColumnDef::new(TotpAuthenticator::ExpiresOn).big_integer().not_null()) - .col(ColumnDef::new(TotpAuthenticator::User).string().not_null().unique_key()) - .foreign_key( - ForeignKey::create() - .from(TotpAuthenticator::Table, TotpAuthenticator::User) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ).to_owned() - ).await + manager + .create_table( + Table::create() + .table(TotpAuthenticator::Table) + .col( + ColumnDef::new(TotpAuthenticator::Id) + .string() + .not_null() + .primary_key(), + ) + .col( + ColumnDef::new(TotpAuthenticator::Secret) + .string() + .not_null() + .unique_key(), + ) + .col( + ColumnDef::new(TotpAuthenticator::Url) + .string() + .not_null() + .unique_key(), + ) + .col( + ColumnDef::new(TotpAuthenticator::Verified) + .boolean() + .not_null(), + ) + .col( + ColumnDef::new(TotpAuthenticator::ExpiresOn) + .big_integer() + .not_null(), + ) + .col( + ColumnDef::new(TotpAuthenticator::User) + .string() + .not_null() + .unique_key(), + ) + .foreign_key( + ForeignKey::create() + .from(TotpAuthenticator::Table, TotpAuthenticator::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(TotpAuthenticator::Table).to_owned()).await + manager + .drop_table(Table::drop().table(TotpAuthenticator::Table).to_owned()) + .await } } @@ -40,5 +73,5 @@ pub enum TotpAuthenticator { Url, Verified, ExpiresOn, - User + User, } diff --git a/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs b/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs index 12a0a67..03c4c89 100644 --- a/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs +++ b/trifid-api/trifid_api_migration/src/m20230403_002256_create_table_auth_tokens.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_162601_create_table_users::User; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,26 +7,39 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(AuthToken::Table) - .if_not_exists() - .col(ColumnDef::new(AuthToken::Id).string().not_null().primary_key()) - .col(ColumnDef::new(AuthToken::User).string().not_null()) - .col(ColumnDef::new(AuthToken::ExpiresOn).big_integer().not_null()) - .foreign_key( - ForeignKey::create() - .from(AuthToken::Table, AuthToken::User) - .to(User::Table, User::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(AuthToken::Table) + .if_not_exists() + .col( + ColumnDef::new(AuthToken::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(AuthToken::User).string().not_null()) + .col( + ColumnDef::new(AuthToken::ExpiresOn) + .big_integer() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .from(AuthToken::Table, AuthToken::User) + .to(User::Table, User::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(AuthToken::Table).to_owned()).await + manager + .drop_table(Table::drop().table(AuthToken::Table).to_owned()) + .await } } @@ -36,5 +49,5 @@ pub enum AuthToken { Table, Id, User, - ExpiresOn + ExpiresOn, } diff --git a/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs b/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs index 084dcd0..8e02a78 100644 --- a/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs +++ b/trifid-api/trifid_api_migration/src/m20230403_142517_create_table_signing_cas.rs @@ -6,21 +6,40 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(SigningCA::Table) - .col(ColumnDef::new(SigningCA::Id).string().not_null().primary_key()) - .col(ColumnDef::new(SigningCA::Organization).string().not_null()) - .col(ColumnDef::new(SigningCA::Cert).string().not_null()) - .col(ColumnDef::new(SigningCA::Key).string().not_null().unique_key()) - .col(ColumnDef::new(SigningCA::Expires).big_integer().not_null()) - .col(ColumnDef::new(SigningCA::Nonce).string().not_null().unique_key()) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(SigningCA::Table) + .col( + ColumnDef::new(SigningCA::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(SigningCA::Organization).string().not_null()) + .col(ColumnDef::new(SigningCA::Cert).string().not_null()) + .col( + ColumnDef::new(SigningCA::Key) + .string() + .not_null() + .unique_key(), + ) + .col(ColumnDef::new(SigningCA::Expires).big_integer().not_null()) + .col( + ColumnDef::new(SigningCA::Nonce) + .string() + .not_null() + .unique_key(), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(SigningCA::Table).to_owned()).await + manager + .drop_table(Table::drop().table(SigningCA::Table).to_owned()) + .await } } @@ -33,5 +52,5 @@ pub enum SigningCA { Cert, Key, Expires, - Nonce + Nonce, } diff --git a/trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs b/trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs index a85fcd4..696ba62 100644 --- a/trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs +++ b/trifid-api/trifid_api_migration/src/m20230403_173431_create_table_networks.rs @@ -1,6 +1,6 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_232316_create_table_organizations::Organization; use crate::m20230403_142517_create_table_signing_cas::SigningCA; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -8,36 +8,59 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(Network::Table) - .col(ColumnDef::new(Network::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Network::Cidr).string().not_null()) - .col(ColumnDef::new(Network::Organization).string().not_null().unique_key()) - .col(ColumnDef::new(Network::SigningCA).string().not_null().unique_key()) - .col(ColumnDef::new(Network::CreatedAt).big_integer().not_null()) - .col(ColumnDef::new(Network::Name).string().not_null()) - .col(ColumnDef::new(Network::LighthousesAsRelays).boolean().not_null()) - .foreign_key( - ForeignKey::create() - .from(Network::Table, Network::Organization) - .to(Organization::Table, Organization::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .from(Network::Table, Network::SigningCA) - .to(SigningCA::Table, SigningCA::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(Network::Table) + .col( + ColumnDef::new(Network::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(Network::Cidr).string().not_null()) + .col( + ColumnDef::new(Network::Organization) + .string() + .not_null() + .unique_key(), + ) + .col( + ColumnDef::new(Network::SigningCA) + .string() + .not_null() + .unique_key(), + ) + .col(ColumnDef::new(Network::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Network::Name).string().not_null()) + .col( + ColumnDef::new(Network::LighthousesAsRelays) + .boolean() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .from(Network::Table, Network::Organization) + .to(Organization::Table, Organization::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .from(Network::Table, Network::SigningCA) + .to(SigningCA::Table, SigningCA::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(Network::Table).to_owned()).await + manager + .drop_table(Table::drop().table(Network::Table).to_owned()) + .await } } @@ -51,5 +74,5 @@ pub enum Network { SigningCA, CreatedAt, Name, - LighthousesAsRelays + LighthousesAsRelays, } diff --git a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs index 1cecc8a..eaa340b 100644 --- a/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs +++ b/trifid-api/trifid_api_migration/src/m20230404_133809_create_table_roles.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230402_232316_create_table_organizations::Organization; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,27 +7,32 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(Role::Table) - .col(ColumnDef::new(Role::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Role::Name).string().not_null().unique_key()) - .col(ColumnDef::new(Role::Description).string().not_null()) - .col(ColumnDef::new(Role::Organization).string().not_null()) - .col(ColumnDef::new(Role::CreatedAt).big_integer().not_null()) - .col(ColumnDef::new(Role::ModifiedAt).big_integer().not_null()) - .foreign_key( - ForeignKey::create() - .from(Role::Table, Role::Organization) - .to(Organization::Table, Organization::Id) - .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) - ).to_owned() - ).await + manager + .create_table( + Table::create() + .table(Role::Table) + .col(ColumnDef::new(Role::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Role::Name).string().not_null().unique_key()) + .col(ColumnDef::new(Role::Description).string().not_null()) + .col(ColumnDef::new(Role::Organization).string().not_null()) + .col(ColumnDef::new(Role::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Role::ModifiedAt).big_integer().not_null()) + .foreign_key( + ForeignKey::create() + .from(Role::Table, Role::Organization) + .to(Organization::Table, Organization::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(Role::Table).to_owned()).await + manager + .drop_table(Table::drop().table(Role::Table).to_owned()) + .await } } @@ -40,5 +45,5 @@ pub enum Role { Description, Organization, CreatedAt, - ModifiedAt + ModifiedAt, } diff --git a/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs b/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs index a44f3b2..ec3a5aa 100644 --- a/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs +++ b/trifid-api/trifid_api_migration/src/m20230404_133813_create_table_firewall_rules.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230404_133809_create_table_roles::Role; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,35 +7,57 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(FirewallRule::Table) - .col(ColumnDef::new(FirewallRule::Id).string().not_null().primary_key()) - .col(ColumnDef::new(FirewallRule::Role).string().not_null()) - .col(ColumnDef::new(FirewallRule::Protocol).string().not_null()) - .col(ColumnDef::new(FirewallRule::Description).string().not_null()) - .col(ColumnDef::new(FirewallRule::AllowedRoleID).string().null()) - .col(ColumnDef::new(FirewallRule::PortRangeFrom).integer().not_null()) - .col(ColumnDef::new(FirewallRule::PortRangeTo).integer().not_null()) - .foreign_key( - ForeignKey::create() - .from(FirewallRule::Table, FirewallRule::Role) - .to(Role::Table, Role::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .from(FirewallRule::Table, FirewallRule::AllowedRoleID) - .to(Role::Table, Role::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) - ).to_owned() - ).await + manager + .create_table( + Table::create() + .table(FirewallRule::Table) + .col( + ColumnDef::new(FirewallRule::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(FirewallRule::Role).string().not_null()) + .col(ColumnDef::new(FirewallRule::Protocol).string().not_null()) + .col( + ColumnDef::new(FirewallRule::Description) + .string() + .not_null(), + ) + .col(ColumnDef::new(FirewallRule::AllowedRoleID).string().null()) + .col( + ColumnDef::new(FirewallRule::PortRangeFrom) + .integer() + .not_null(), + ) + .col( + ColumnDef::new(FirewallRule::PortRangeTo) + .integer() + .not_null(), + ) + .foreign_key( + ForeignKey::create() + .from(FirewallRule::Table, FirewallRule::Role) + .to(Role::Table, Role::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .from(FirewallRule::Table, FirewallRule::AllowedRoleID) + .to(Role::Table, Role::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.drop_table(Table::drop().table(FirewallRule::Table).to_owned()).await + manager + .drop_table(Table::drop().table(FirewallRule::Table).to_owned()) + .await } } @@ -49,5 +71,5 @@ pub enum FirewallRule { Description, AllowedRoleID, PortRangeFrom, - PortRangeTo + PortRangeTo, } diff --git a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs index c1de66f..b3ce88c 100644 --- a/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs +++ b/trifid-api/trifid_api_migration/src/m20230427_170037_create_table_hosts.rs @@ -1,6 +1,6 @@ -use sea_orm_migration::prelude::*; use crate::m20230403_173431_create_table_networks::Network; use crate::m20230404_133809_create_table_roles::Role; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -8,56 +8,58 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(Host::Table) - .col(ColumnDef::new(Host::Id).string().not_null().primary_key()) - .col(ColumnDef::new(Host::Name).string().not_null()) - .col(ColumnDef::new(Host::Network).string().not_null()) - .col(ColumnDef::new(Host::Role).string().not_null()) - .col(ColumnDef::new(Host::IP).string().not_null()) - .col(ColumnDef::new(Host::ListenPort).unsigned().not_null()) - .col(ColumnDef::new(Host::IsLighthouse).boolean().not_null()) - .col(ColumnDef::new(Host::IsRelay).boolean().not_null()) - .col(ColumnDef::new(Host::Counter).unsigned().not_null()) - .col(ColumnDef::new(Host::CreatedAt).big_integer().not_null()) - .col(ColumnDef::new(Host::IsBlocked).boolean().not_null()) - .col(ColumnDef::new(Host::LastSeenAt).big_integer().not_null()) - .col(ColumnDef::new(Host::LastVersion).integer().not_null()) - .col(ColumnDef::new(Host::LastPlatform).string().not_null()) - .col(ColumnDef::new(Host::LastOutOfDate).boolean().not_null()) - .foreign_key( - ForeignKey::create() - .from(Host::Table, Host::Network) - .to(Network::Table, Network::Id) - .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) - ) - .foreign_key( - ForeignKey::create() - .from(Host::Table, Host::Role) - .to(Role::Table, Role::Id) - .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) - ) - .index( - Index::create() - .name("idx-hosts-net-name-unique") - .table(Host::Table) - .col(Host::Network) - .col(Host::Name) - .unique() - ) - .index( - Index::create() - .name("idx-hosts-net-ip-unique") - .table(Host::Table) - .col(Host::Network) - .col(Host::IP) - .unique() - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(Host::Table) + .col(ColumnDef::new(Host::Id).string().not_null().primary_key()) + .col(ColumnDef::new(Host::Name).string().not_null()) + .col(ColumnDef::new(Host::Network).string().not_null()) + .col(ColumnDef::new(Host::Role).string().not_null()) + .col(ColumnDef::new(Host::IP).string().not_null()) + .col(ColumnDef::new(Host::ListenPort).unsigned().not_null()) + .col(ColumnDef::new(Host::IsLighthouse).boolean().not_null()) + .col(ColumnDef::new(Host::IsRelay).boolean().not_null()) + .col(ColumnDef::new(Host::Counter).unsigned().not_null()) + .col(ColumnDef::new(Host::CreatedAt).big_integer().not_null()) + .col(ColumnDef::new(Host::IsBlocked).boolean().not_null()) + .col(ColumnDef::new(Host::LastSeenAt).big_integer().not_null()) + .col(ColumnDef::new(Host::LastVersion).integer().not_null()) + .col(ColumnDef::new(Host::LastPlatform).string().not_null()) + .col(ColumnDef::new(Host::LastOutOfDate).boolean().not_null()) + .foreign_key( + ForeignKey::create() + .from(Host::Table, Host::Network) + .to(Network::Table, Network::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), + ) + .foreign_key( + ForeignKey::create() + .from(Host::Table, Host::Role) + .to(Role::Table, Role::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), + ) + .index( + Index::create() + .name("idx-hosts-net-name-unique") + .table(Host::Table) + .col(Host::Network) + .col(Host::Name) + .unique(), + ) + .index( + Index::create() + .name("idx-hosts-net-ip-unique") + .table(Host::Table) + .col(Host::Network) + .col(Host::IP) + .unique(), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { @@ -85,5 +87,5 @@ pub enum Host { LastSeenAt, LastVersion, LastPlatform, - LastOutOfDate + LastOutOfDate, } diff --git a/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs b/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs index 7de7d76..f0d9166 100644 --- a/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs +++ b/trifid-api/trifid_api_migration/src/m20230427_171517_create_table_hosts_static_addresses.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230427_170037_create_table_hosts::Host; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -11,15 +11,24 @@ impl MigrationTrait for Migration { .create_table( Table::create() .table(HostStaticAddress::Table) - .col(ColumnDef::new(HostStaticAddress::Id).string().not_null().primary_key()) + .col( + ColumnDef::new(HostStaticAddress::Id) + .string() + .not_null() + .primary_key(), + ) .col(ColumnDef::new(HostStaticAddress::Host).string().not_null()) - .col(ColumnDef::new(HostStaticAddress::Address).string().not_null()) + .col( + ColumnDef::new(HostStaticAddress::Address) + .string() + .not_null(), + ) .foreign_key( ForeignKey::create() .from(HostStaticAddress::Table, HostStaticAddress::Host) .to(Host::Table, Host::Id) .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), ) .to_owned(), ) @@ -39,5 +48,5 @@ pub enum HostStaticAddress { Table, Id, Host, - Address + Address, } diff --git a/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs b/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs index 86ee883..082d3a6 100644 --- a/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs +++ b/trifid-api/trifid_api_migration/src/m20230427_171529_create_table_hosts_config_overrides.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230427_170037_create_table_hosts::Host; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -7,30 +7,41 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - manager.create_table( - Table::create() - .table(HostConfigOverride::Table) - .col(ColumnDef::new(HostConfigOverride::Id).string().not_null().primary_key()) - .col(ColumnDef::new(HostConfigOverride::Key).string().not_null()) - .col(ColumnDef::new(HostConfigOverride::Value).string().not_null()) - .col(ColumnDef::new(HostConfigOverride::Host).string().not_null()) - .foreign_key( - ForeignKey::create() - .from(HostConfigOverride::Table, HostConfigOverride::Host) - .to(Host::Table, Host::Id) - .on_delete(ForeignKeyAction::Cascade) - .on_update(ForeignKeyAction::Cascade) - ) - .index( - Index::create() - .name("idx_hosts_config_overrides-key-host-unique") - .table(HostConfigOverride::Table) - .col(HostConfigOverride::Key) - .col(HostConfigOverride::Id) - .unique() - ) - .to_owned() - ).await + manager + .create_table( + Table::create() + .table(HostConfigOverride::Table) + .col( + ColumnDef::new(HostConfigOverride::Id) + .string() + .not_null() + .primary_key(), + ) + .col(ColumnDef::new(HostConfigOverride::Key).string().not_null()) + .col( + ColumnDef::new(HostConfigOverride::Value) + .string() + .not_null(), + ) + .col(ColumnDef::new(HostConfigOverride::Host).string().not_null()) + .foreign_key( + ForeignKey::create() + .from(HostConfigOverride::Table, HostConfigOverride::Host) + .to(Host::Table, Host::Id) + .on_delete(ForeignKeyAction::Cascade) + .on_update(ForeignKeyAction::Cascade), + ) + .index( + Index::create() + .name("idx_hosts_config_overrides-key-host-unique") + .table(HostConfigOverride::Table) + .col(HostConfigOverride::Key) + .col(HostConfigOverride::Id) + .unique(), + ) + .to_owned(), + ) + .await } async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs index 41382c0..35ee697 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/mod.rs @@ -1,16 +1,16 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 -pub mod prelude ; +pub mod prelude; -pub mod api_key ; -pub mod api_key_scope ; -pub mod auth_token ; -pub mod firewall_rule ; -pub mod magic_link ; -pub mod network ; -pub mod organization ; -pub mod role ; -pub mod session_token ; -pub mod signing_ca ; -pub mod totp_authenticator ; -pub mod user ; \ No newline at end of file +pub mod api_key; +pub mod api_key_scope; +pub mod auth_token; +pub mod firewall_rule; +pub mod magic_link; +pub mod network; +pub mod organization; +pub mod role; +pub mod session_token; +pub mod signing_ca; +pub mod totp_authenticator; +pub mod user; diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs index 832f0e8..7d575e6 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/network.rs @@ -1,15 +1,52 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "network")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub cidr: String, + #[sea_orm(unique)] + pub organization: String, + #[sea_orm(unique)] + pub signing_ca: String, + pub created_at: i64, + pub name: String, + pub lighthouses_as_relays: bool, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, + #[sea_orm( + belongs_to = "super::signing_ca::Entity", + from = "Column::SigningCa", + to = "super::signing_ca::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + SigningCa, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "network")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub cidr : String , # [sea_orm (unique)] pub organization : String , # [sea_orm (unique)] pub signing_ca : String , pub created_at : i64 , pub name : String , pub lighthouses_as_relays : bool , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::organization::Entity" , from = "Column::Organization" , to = "super::organization::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] Organization , # [sea_orm (belongs_to = "super::signing_ca::Entity" , from = "Column::SigningCa" , to = "super::signing_ca::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] SigningCa , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::SigningCa.def() + } +} -impl Related < super :: organization :: Entity > for Entity { fn to () -> RelationDef { Relation :: Organization . def () } } - -impl Related < super :: signing_ca :: Entity > for Entity { fn to () -> RelationDef { Relation :: SigningCa . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs index fdaf8e3..25d4ff9 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/organization.rs @@ -1,19 +1,57 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "organization")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub name: String, + #[sea_orm(unique)] + pub owner: String, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::api_key::Entity")] + ApiKey, + #[sea_orm(has_one = "super::network::Entity")] + Network, + #[sea_orm(has_many = "super::role::Entity")] + Role, + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::Owner", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "organization")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub name : String , # [sea_orm (unique)] pub owner : String , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::ApiKey.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (has_many = "super::api_key::Entity")] ApiKey , # [sea_orm (has_one = "super::network::Entity")] Network , # [sea_orm (has_many = "super::role::Entity")] Role , # [sea_orm (belongs_to = "super::user::Entity" , from = "Column::Owner" , to = "super::user::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] User , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} -impl Related < super :: api_key :: Entity > for Entity { fn to () -> RelationDef { Relation :: ApiKey . def () } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Role.def() + } +} -impl Related < super :: network :: Entity > for Entity { fn to () -> RelationDef { Relation :: Network . def () } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} -impl Related < super :: role :: Entity > for Entity { fn to () -> RelationDef { Relation :: Role . def () } } - -impl Related < super :: user :: Entity > for Entity { fn to () -> RelationDef { Relation :: User . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs index 2314fbc..93cce0c 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/prelude.rs @@ -1,14 +1,14 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 -pub use super :: api_key :: Entity as ApiKey ; -pub use super :: api_key_scope :: Entity as ApiKeyScope ; -pub use super :: auth_token :: Entity as AuthToken ; -pub use super :: firewall_rule :: Entity as FirewallRule ; -pub use super :: magic_link :: Entity as MagicLink ; -pub use super :: network :: Entity as Network ; -pub use super :: organization :: Entity as Organization ; -pub use super :: role :: Entity as Role ; -pub use super :: session_token :: Entity as SessionToken ; -pub use super :: signing_ca :: Entity as SigningCa ; -pub use super :: totp_authenticator :: Entity as TotpAuthenticator ; -pub use super :: user :: Entity as User ; \ No newline at end of file +pub use super::api_key::Entity as ApiKey; +pub use super::api_key_scope::Entity as ApiKeyScope; +pub use super::auth_token::Entity as AuthToken; +pub use super::firewall_rule::Entity as FirewallRule; +pub use super::magic_link::Entity as MagicLink; +pub use super::network::Entity as Network; +pub use super::organization::Entity as Organization; +pub use super::role::Entity as Role; +pub use super::session_token::Entity as SessionToken; +pub use super::signing_ca::Entity as SigningCa; +pub use super::totp_authenticator::Entity as TotpAuthenticator; +pub use super::user::Entity as User; diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs index 9ce3a13..b31b820 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/role.rs @@ -1,13 +1,36 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "role")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub name: String, + pub description: String, + pub organization: String, + pub created_at: i64, + pub modified_at: i64, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::organization::Entity", + from = "Column::Organization", + to = "super::organization::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Organization, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "role")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , # [sea_orm (unique)] pub name : String , pub description : String , pub organization : String , pub created_at : i64 , pub modified_at : i64 , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::organization::Entity" , from = "Column::Organization" , to = "super::organization::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] Organization , } - -impl Related < super :: organization :: Entity > for Entity { fn to () -> RelationDef { Relation :: Organization . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs index 23d37ba..a648966 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/session_token.rs @@ -1,13 +1,32 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "session_token")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub user: String, + pub expires_on: i64, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "session_token")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub user : String , pub expires_on : i64 , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::user::Entity" , from = "Column::User" , to = "super::user::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] User , } - -impl Related < super :: user :: Entity > for Entity { fn to () -> RelationDef { Relation :: User . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs index 72516a6..d0770a9 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/signing_ca.rs @@ -1,13 +1,31 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "signing_ca")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub organization: String, + pub cert: String, + #[sea_orm(unique)] + pub key: String, + pub expires: i64, + #[sea_orm(unique)] + pub nonce: String, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_one = "super::network::Entity")] + Network, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "signing_ca")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , pub organization : String , pub cert : String , # [sea_orm (unique)] pub key : String , pub expires : i64 , # [sea_orm (unique)] pub nonce : String , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Network.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (has_one = "super::network::Entity")] Network , } - -impl Related < super :: network :: Entity > for Entity { fn to () -> RelationDef { Relation :: Network . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs index 630c391..5437596 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/totp_authenticator.rs @@ -1,13 +1,38 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "totp_authenticator")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub secret: String, + #[sea_orm(unique)] + pub url: String, + pub verified: bool, + pub expires_on: i64, + #[sea_orm(unique)] + pub user: String, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::user::Entity", + from = "Column::User", + to = "super::user::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + User, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "totp_authenticator")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , # [sea_orm (unique)] pub secret : String , # [sea_orm (unique)] pub url : String , pub verified : bool , pub expires_on : i64 , # [sea_orm (unique)] pub user : String , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::User.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (belongs_to = "super::user::Entity" , from = "Column::User" , to = "super::user::Column::Id" , on_update = "Cascade" , on_delete = "Cascade" ,)] User , } - -impl Related < super :: user :: Entity > for Entity { fn to () -> RelationDef { Relation :: User . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs index 23fc564..0d7b61f 100644 --- a/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs +++ b/trifid-api/trifid_api_migration/trifid_api_entities/src/entity/user.rs @@ -1,21 +1,58 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 +use sea_orm::entity::prelude::*; +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "user")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + #[sea_orm(unique)] + pub email: String, +} -use sea_orm :: entity :: prelude :: * ; +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm(has_many = "super::auth_token::Entity")] + AuthToken, + #[sea_orm(has_many = "super::magic_link::Entity")] + MagicLink, + #[sea_orm(has_one = "super::organization::Entity")] + Organization, + #[sea_orm(has_many = "super::session_token::Entity")] + SessionToken, + #[sea_orm(has_one = "super::totp_authenticator::Entity")] + TotpAuthenticator, +} -# [derive (Clone , Debug , PartialEq , DeriveEntityModel , Eq)] # [sea_orm (table_name = "user")] pub struct Model { # [sea_orm (primary_key , auto_increment = false)] pub id : String , # [sea_orm (unique)] pub email : String , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::AuthToken.def() + } +} -# [derive (Copy , Clone , Debug , EnumIter , DeriveRelation)] pub enum Relation { # [sea_orm (has_many = "super::auth_token::Entity")] AuthToken , # [sea_orm (has_many = "super::magic_link::Entity")] MagicLink , # [sea_orm (has_one = "super::organization::Entity")] Organization , # [sea_orm (has_many = "super::session_token::Entity")] SessionToken , # [sea_orm (has_one = "super::totp_authenticator::Entity")] TotpAuthenticator , } +impl Related for Entity { + fn to() -> RelationDef { + Relation::MagicLink.def() + } +} -impl Related < super :: auth_token :: Entity > for Entity { fn to () -> RelationDef { Relation :: AuthToken . def () } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::Organization.def() + } +} -impl Related < super :: magic_link :: Entity > for Entity { fn to () -> RelationDef { Relation :: MagicLink . def () } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::SessionToken.def() + } +} -impl Related < super :: organization :: Entity > for Entity { fn to () -> RelationDef { Relation :: Organization . def () } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::TotpAuthenticator.def() + } +} -impl Related < super :: session_token :: Entity > for Entity { fn to () -> RelationDef { Relation :: SessionToken . def () } } - -impl Related < super :: totp_authenticator :: Entity > for Entity { fn to () -> RelationDef { Relation :: TotpAuthenticator . def () } } - -impl ActiveModelBehavior for ActiveModel { } \ No newline at end of file +impl ActiveModelBehavior for ActiveModel {} From 7e1627e16535ca41166e255585f52faf1259cf80 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 11 May 2023 13:13:30 -0400 Subject: [PATCH 37/39] enrollment codes --- trifid-api/src/config.rs | 5 + trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 229 +++++++++++++++++- .../trifid_api_entities/src/entity/host.rs | 8 + .../src/entity/host_enrollment_code.rs | 32 +++ .../trifid_api_entities/src/entity/mod.rs | 1 + .../trifid_api_entities/src/entity/prelude.rs | 1 + trifid-api/trifid_api_migration/src/lib.rs | 2 + ...0511_create_table_host_enrollment_codes.rs | 43 ++++ 9 files changed, 320 insertions(+), 2 deletions(-) create mode 100644 trifid-api/trifid_api_entities/src/entity/host_enrollment_code.rs create mode 100644 trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index e1d3f95..5f26f5e 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -81,6 +81,8 @@ pub struct TrifidConfigTokens { pub totp_setup_timeout_time_seconds: u64, #[serde(default = "mfa_tokens_expiry_time")] pub mfa_tokens_expiry_time_seconds: u64, + #[serde(default = "enrollment_tokens_expiry_time")] + pub enrollment_tokens_expiry_time: u64 } #[derive(Serialize, Deserialize, Debug)] @@ -115,3 +117,6 @@ fn totp_setup_timeout_time() -> u64 { fn mfa_tokens_expiry_time() -> u64 { 600 } // 10 minutes +fn enrollment_tokens_expiry_time() -> u64 { + 600 +} // 10 minutes \ No newline at end of file diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index a77ee9d..ffaea11 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -101,6 +101,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::hosts::delete_host) .service(routes::v1::hosts::edit_host) .service(routes::v1::hosts::block_host) + .service(routes::v1::hosts::enroll_host) }) .bind(CONFIG.server.bind)? .run() diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index bf8a26e..0da947f 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -39,13 +39,23 @@ // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. // This endpoint has additional functionality enabled by the extended_hosts feature flag. +// +//#POST /v1/hosts/{host_id}/block t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. +// +//#POST /v1/hosts/{host_id}/enrollment-code t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::cursor::Cursor; use crate::error::{APIError, APIErrorsResponse}; use crate::routes::v1::trifid::SUPPORTED_EXTENSIONS; -use crate::timers::TIME_FORMAT; -use crate::tokens::random_id; +use crate::timers::{expires_in_seconds, TIME_FORMAT}; +use crate::tokens::{random_id, random_token}; use crate::AppState; use actix_web::web::{Data, Json, Path, Query}; use actix_web::{delete, get, post, put, HttpRequest, HttpResponse}; @@ -61,6 +71,7 @@ use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use trifid_api_entities::entity::{host, host_static_address, network, organization}; +use crate::config::CONFIG; #[derive(Serialize, Deserialize)] pub struct ListHostsRequestOpts { @@ -1780,3 +1791,217 @@ pub async fn block_host( metadata: BlockHostResponseMetadata {}, }) } + +#[derive(Serialize, Deserialize)] +pub struct CodeResponse { + pub code: String, + #[serde(rename = "lifetimeSeconds")] + pub lifetime_seconds: u64 +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollmentCodeResponse { + pub data: EnrollmentCodeResponseData, + pub metadata: EnrollmentCodeResponseMetadata +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollmentCodeResponseData { + #[serde(rename = "enrollmentCode")] + pub enrollment_code: CodeResponse +} + +#[derive(Serialize, Deserialize)] +pub struct EnrollmentCodeResponseMetadata {} + +#[post("/v1/hosts/{host_id}/enrollment-code")] +pub async fn enroll_host(id: Path, req_info: HttpRequest, db: Data) -> HttpResponse { + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:enroll"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:enroll scope".to_string(), + path: None, + } + ], + }); + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }); + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!(), + }; + + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }); + } + } + }; + + let net_id; + + let net = match network::Entity::find() + .filter(network::Column::Organization.eq(&org_id)) + .one(&db.conn) + .await + { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead." + .to_string(), + path: None, + }], + }); + } + + let host = match host::Entity::find() + .filter(host::Column::Id.eq(id.into_inner())) + .one(&db.conn) + .await + { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); + } + }; + + let host = match host { + Some(h) => h, + None => { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: + "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }) + } + }; + + if host.network != net_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This resource does not exist or you do not have permission to access it." + .to_string(), + path: None, + }], + }); + } + + let enrollment_code = trifid_api_entities::entity::host_enrollment_code::Model { + id: random_token("ec"), + host: host.id, + expires_on: expires_in_seconds(CONFIG.tokens.enrollment_tokens_expiry_time) as i64, + }; + let ec_am = enrollment_code.into_active_model(); + + let code = match ec_am.insert(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); + } + }; + + HttpResponse::Ok().json(EnrollmentCodeResponse { + data: EnrollmentCodeResponseData { enrollment_code: CodeResponse { code: code.id, lifetime_seconds: CONFIG.tokens.enrollment_tokens_expiry_time } }, + metadata: EnrollmentCodeResponseMetadata {}, + }) +} \ No newline at end of file diff --git a/trifid-api/trifid_api_entities/src/entity/host.rs b/trifid-api/trifid_api_entities/src/entity/host.rs index 73c4cf7..be78625 100644 --- a/trifid-api/trifid_api_entities/src/entity/host.rs +++ b/trifid-api/trifid_api_entities/src/entity/host.rs @@ -27,6 +27,8 @@ pub struct Model { pub enum Relation { #[sea_orm(has_many = "super::host_config_override::Entity")] HostConfigOverride, + #[sea_orm(has_many = "super::host_enrollment_code::Entity")] + HostEnrollmentCode, #[sea_orm(has_many = "super::host_static_address::Entity")] HostStaticAddress, #[sea_orm( @@ -53,6 +55,12 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + Relation::HostEnrollmentCode.def() + } +} + impl Related for Entity { fn to() -> RelationDef { Relation::HostStaticAddress.def() diff --git a/trifid-api/trifid_api_entities/src/entity/host_enrollment_code.rs b/trifid-api/trifid_api_entities/src/entity/host_enrollment_code.rs new file mode 100644 index 0000000..8f8c7ab --- /dev/null +++ b/trifid-api/trifid_api_entities/src/entity/host_enrollment_code.rs @@ -0,0 +1,32 @@ +//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 + +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] +#[sea_orm(table_name = "host_enrollment_code")] +pub struct Model { + #[sea_orm(primary_key, auto_increment = false)] + pub id: String, + pub host: String, + pub expires_on: i64, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation { + #[sea_orm( + belongs_to = "super::host::Entity", + from = "Column::Host", + to = "super::host::Column::Id", + on_update = "Cascade", + on_delete = "Cascade" + )] + Host, +} + +impl Related for Entity { + fn to() -> RelationDef { + Relation::Host.def() + } +} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/trifid-api/trifid_api_entities/src/entity/mod.rs b/trifid-api/trifid_api_entities/src/entity/mod.rs index d82ab21..1fbb5ce 100644 --- a/trifid-api/trifid_api_entities/src/entity/mod.rs +++ b/trifid-api/trifid_api_entities/src/entity/mod.rs @@ -8,6 +8,7 @@ pub mod auth_token; pub mod firewall_rule; pub mod host; pub mod host_config_override; +pub mod host_enrollment_code; pub mod host_static_address; pub mod magic_link; pub mod network; diff --git a/trifid-api/trifid_api_entities/src/entity/prelude.rs b/trifid-api/trifid_api_entities/src/entity/prelude.rs index 2042b24..b0b0d3b 100644 --- a/trifid-api/trifid_api_entities/src/entity/prelude.rs +++ b/trifid-api/trifid_api_entities/src/entity/prelude.rs @@ -6,6 +6,7 @@ pub use super::auth_token::Entity as AuthToken; pub use super::firewall_rule::Entity as FirewallRule; pub use super::host::Entity as Host; pub use super::host_config_override::Entity as HostConfigOverride; +pub use super::host_enrollment_code::Entity as HostEnrollmentCode; pub use super::host_static_address::Entity as HostStaticAddress; pub use super::magic_link::Entity as MagicLink; pub use super::network::Entity as Network; diff --git a/trifid-api/trifid_api_migration/src/lib.rs b/trifid-api/trifid_api_migration/src/lib.rs index ef066c6..c3ba411 100644 --- a/trifid-api/trifid_api_migration/src/lib.rs +++ b/trifid-api/trifid_api_migration/src/lib.rs @@ -17,6 +17,7 @@ mod m20230404_133813_create_table_firewall_rules; mod m20230427_170037_create_table_hosts; mod m20230427_171517_create_table_hosts_static_addresses; mod m20230427_171529_create_table_hosts_config_overrides; +mod m20230511_120511_create_table_host_enrollment_codes; #[async_trait::async_trait] impl MigratorTrait for Migrator { @@ -37,6 +38,7 @@ impl MigratorTrait for Migrator { Box::new(m20230427_170037_create_table_hosts::Migration), Box::new(m20230427_171517_create_table_hosts_static_addresses::Migration), Box::new(m20230427_171529_create_table_hosts_config_overrides::Migration), + Box::new(m20230511_120511_create_table_host_enrollment_codes::Migration), ] } } diff --git a/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs b/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs new file mode 100644 index 0000000..aca855f --- /dev/null +++ b/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs @@ -0,0 +1,43 @@ +use sea_orm_migration::prelude::*; +use crate::m20230427_170037_create_table_hosts::Host; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + Table::create() + .table(HostEnrollmentCode::Table) + .col(ColumnDef::new(HostEnrollmentCode::Id).string().not_null().primary_key()) + .col(ColumnDef::new(HostEnrollmentCode::Host).string().not_null()) + .col(ColumnDef::new(HostEnrollmentCode::ExpiresOn).big_integer().not_null()) + .foreign_key( + ForeignKey::create() + .from(HostEnrollmentCode::Table, HostEnrollmentCode::Host) + .to(Host::Table, Host::Id) + .on_update(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade) + ) + .to_owned(), + ) + .await + } + + async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .drop_table(Table::drop().table(HostEnrollmentCode::Table).to_owned()) + .await + } +} + +/// Learn more at https://docs.rs/sea-query#iden +#[derive(Iden)] +pub enum HostEnrollmentCode { + Table, + Id, + Host, + ExpiresOn +} From 68c120a5ab517ff7417fa9b3fbee1e716da251e8 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 11 May 2023 13:32:33 -0400 Subject: [PATCH 38/39] host and enrollment codes --- trifid-api/src/main.rs | 1 + trifid-api/src/routes/v1/hosts.rs | 307 ++++++++++++++++++++++++++++++ 2 files changed, 308 insertions(+) diff --git a/trifid-api/src/main.rs b/trifid-api/src/main.rs index ffaea11..9e588f6 100644 --- a/trifid-api/src/main.rs +++ b/trifid-api/src/main.rs @@ -102,6 +102,7 @@ async fn main() -> Result<(), Box> { .service(routes::v1::hosts::edit_host) .service(routes::v1::hosts::block_host) .service(routes::v1::hosts::enroll_host) + .service(routes::v1::hosts::create_host_and_enrollment_code) }) .bind(CONFIG.server.bind)? .run() diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index 0da947f..a0978e2 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -49,6 +49,11 @@ // This endpoint has full parity with the original API. It has been recreated from the original API documentation. // This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. // This endpoint requires the `definednetworking` extension to be enabled to be used. +// +//#POST /v1/host-and-enrollment-code t+parity:full t+type:documented t+status:done t+feature:definednetworking +// This endpoint has full parity with the original API. It has been recreated from the original API documentation. +// This endpoint is considered done. No major features should be added or removed, unless it fixes bugs. +// This endpoint requires the `definednetworking` extension to be enabled to be used. use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; use crate::cursor::Cursor; @@ -2004,4 +2009,306 @@ pub async fn enroll_host(id: Path, req_info: HttpRequest, db: Data, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { + // For this endpoint, you either need to be a fully authenticated user OR a token with hosts:create and hosts:enroll + let session_info = enforce_2fa(&req_info, &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + let api_token_info = enforce_api_token(&req_info, &["hosts:create", "hosts:enroll"], &db.conn) + .await + .unwrap_or(TokenInfo::NotPresent); + + // If neither are present, throw an error + if matches!(session_info, TokenInfo::NotPresent) + && matches!(api_token_info, TokenInfo::NotPresent) + { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_UNAUTHORIZED".to_string(), + message: "This endpoint requires either a fully authenticated user or a token with the hosts:create and hosts:enroll scopes".to_string(), + path: None, + } + ], + }); + } + + // If both are present, throw an error + if matches!(session_info, TokenInfo::AuthToken(_)) + && matches!(api_token_info, TokenInfo::ApiToken(_)) + { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_AMBIGUOUS_AUTHENTICATION".to_string(), + message: "Both a user token and an API token with the proper scope was provided. Please only provide one.".to_string(), + path: None + } + ], + }); + } + + let org_id = match api_token_info { + TokenInfo::ApiToken(tkn) => tkn.organization, + _ => { + // we have a session token, which means we have to do a db request to get the organization that this user owns + let user = match session_info { + TokenInfo::AuthToken(tkn) => tkn.session_info.user, + _ => unreachable!(), + }; + + let org = match organization::Entity::find() + .filter(organization::Column::Owner.eq(user.id)) + .one(&db.conn) + .await + { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(org) = org { + org.id + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_NO_ORG".to_string(), + message: "This user does not own any organizations. Try using an API token instead.".to_string(), + path: None + } + ], + }); + } + } + }; + + let net_id; + + let net = match network::Entity::find() + .filter(network::Column::Organization.eq(&org_id)) + .one(&db.conn) + .await + { + Ok(r) => r, + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error performing the database request, please try again later.".to_string(), + path: None, + } + ], + }); + } + }; + + if let Some(net) = net { + net_id = net.id; + } else { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_NO_NET".to_string(), + message: "This user does not own any networks. Try using an API token instead." + .to_string(), + path: None, + }], + }); + } + + if net_id != req.network_id { + return HttpResponse::Unauthorized().json(APIErrorsResponse { + errors: vec![ + APIError { + code: "ERR_WRONG_NET".to_string(), + message: "The network on the request does not match the network associated with this token or user.".to_string(), + path: None + } + ], + }); + } + + if req.is_lighthouse && req.is_relay { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_CANNOT_BE_RELAY_AND_LIGHTHOUSE".to_string(), + message: "A host cannot be a relay and a lighthouse at the same time.".to_string(), + path: None, + }], + }); + } + + if req.is_lighthouse || req.is_relay && req.static_addresses.is_empty() { + return HttpResponse::BadRequest().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_NEEDS_STATIC_ADDR".to_string(), + message: "A relay or lighthouse requires at least one static address.".to_string(), + path: None, + }], + }); + } + + let new_host_model = host::Model { + id: random_id("host"), + name: req.name.clone(), + network: net_id.clone(), + role: req.role_id.clone().unwrap_or("".to_string()), + ip: req.ip_address.to_string(), + listen_port: req.listen_port as i32, + is_lighthouse: req.is_lighthouse, + is_relay: req.is_relay, + counter: 0, + created_at: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("time went backwards") + .as_secs() as i64, + is_blocked: false, + last_seen_at: 0, + last_version: 0, + last_platform: "".to_string(), + last_out_of_date: false, + }; + let static_addresses: Vec = req + .static_addresses + .iter() + .map(|u| host_static_address::Model { + id: random_id("hsaddress"), + host: new_host_model.id.clone(), + address: u.to_string(), + }) + .collect(); + + let new_host_model_clone = new_host_model.clone(); + let static_addresses_clone = static_addresses.clone(); + + let new_host_active_model = new_host_model.into_active_model(); + match new_host_active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later" + .to_string(), + path: None, + }], + }); + } + } + + for rule in &static_addresses_clone { + let active_model = rule.clone().into_active_model(); + match active_model.insert(&db.conn).await { + Ok(_) => (), + Err(e) => { + error!("database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error creating the new host. Please try again later" + .to_string(), + path: None, + }], + }); + } + } + } + + let enrollment_code = trifid_api_entities::entity::host_enrollment_code::Model { + id: random_token("ec"), + host: new_host_model_clone.id.clone(), + expires_on: expires_in_seconds(CONFIG.tokens.enrollment_tokens_expiry_time) as i64, + }; + let ec_am = enrollment_code.into_active_model(); + + let code = match ec_am.insert(&db.conn).await { + Ok(h) => h, + Err(e) => { + error!("Database error: {}", e); + return HttpResponse::InternalServerError().json(APIErrorsResponse { + errors: vec![APIError { + code: "ERR_DB_ERROR".to_string(), + message: "There was an error with the database query. Please try again later." + .to_string(), + path: None, + }], + }); + } + }; + + HttpResponse::Ok().json(CreateHostAndCodeResponse { + data: CreateHostAndCodeResponseData { + host: HostResponse { + id: new_host_model_clone.id, + organization_id: org_id, + network_id: net_id, + role_id: new_host_model_clone.role, + name: new_host_model_clone.name, + ip_address: req.ip_address.to_string(), + static_addresses: req.static_addresses.clone(), + listen_port: req.listen_port, + is_lighthouse: req.is_lighthouse, + is_relay: req.is_relay, + created_at: Utc + .timestamp_opt(new_host_model_clone.created_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + is_blocked: false, + metadata: HostResponseMetadata { + last_seen_at: Some( + Utc.timestamp_opt(new_host_model_clone.last_seen_at, 0) + .unwrap() + .format(TIME_FORMAT) + .to_string(), + ), + version: new_host_model_clone.last_version.to_string(), + platform: new_host_model_clone.last_platform, + update_available: new_host_model_clone.last_out_of_date, + }, + }, + enrollment_code: CodeResponse { + code: code.id, + lifetime_seconds: CONFIG.tokens.enrollment_tokens_expiry_time, + } + }, + metadata: CreateHostAndCodeResponseMetadata {}, + }) } \ No newline at end of file From b3e83f45862be097b23a8ca423350c2fd235f7e7 Mon Sep 17 00:00:00 2001 From: c0repwn3r Date: Thu, 11 May 2023 13:35:10 -0400 Subject: [PATCH 39/39] code cleanup --- trifid-api/src/config.rs | 4 +-- trifid-api/src/routes/v1/hosts.rs | 34 ++++++++++++------- ...0511_create_table_host_enrollment_codes.rs | 19 ++++++++--- 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/trifid-api/src/config.rs b/trifid-api/src/config.rs index 5f26f5e..9d06334 100644 --- a/trifid-api/src/config.rs +++ b/trifid-api/src/config.rs @@ -82,7 +82,7 @@ pub struct TrifidConfigTokens { #[serde(default = "mfa_tokens_expiry_time")] pub mfa_tokens_expiry_time_seconds: u64, #[serde(default = "enrollment_tokens_expiry_time")] - pub enrollment_tokens_expiry_time: u64 + pub enrollment_tokens_expiry_time: u64, } #[derive(Serialize, Deserialize, Debug)] @@ -119,4 +119,4 @@ fn mfa_tokens_expiry_time() -> u64 { } // 10 minutes fn enrollment_tokens_expiry_time() -> u64 { 600 -} // 10 minutes \ No newline at end of file +} // 10 minutes diff --git a/trifid-api/src/routes/v1/hosts.rs b/trifid-api/src/routes/v1/hosts.rs index a0978e2..b195133 100644 --- a/trifid-api/src/routes/v1/hosts.rs +++ b/trifid-api/src/routes/v1/hosts.rs @@ -56,6 +56,7 @@ // This endpoint requires the `definednetworking` extension to be enabled to be used. use crate::auth_tokens::{enforce_2fa, enforce_api_token, TokenInfo}; +use crate::config::CONFIG; use crate::cursor::Cursor; use crate::error::{APIError, APIErrorsResponse}; use crate::routes::v1::trifid::SUPPORTED_EXTENSIONS; @@ -76,7 +77,6 @@ use std::net::{Ipv4Addr, SocketAddrV4}; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; use trifid_api_entities::entity::{host, host_static_address, network, organization}; -use crate::config::CONFIG; #[derive(Serialize, Deserialize)] pub struct ListHostsRequestOpts { @@ -1801,26 +1801,30 @@ pub async fn block_host( pub struct CodeResponse { pub code: String, #[serde(rename = "lifetimeSeconds")] - pub lifetime_seconds: u64 + pub lifetime_seconds: u64, } #[derive(Serialize, Deserialize)] pub struct EnrollmentCodeResponse { pub data: EnrollmentCodeResponseData, - pub metadata: EnrollmentCodeResponseMetadata + pub metadata: EnrollmentCodeResponseMetadata, } #[derive(Serialize, Deserialize)] pub struct EnrollmentCodeResponseData { #[serde(rename = "enrollmentCode")] - pub enrollment_code: CodeResponse + pub enrollment_code: CodeResponse, } #[derive(Serialize, Deserialize)] pub struct EnrollmentCodeResponseMetadata {} #[post("/v1/hosts/{host_id}/enrollment-code")] -pub async fn enroll_host(id: Path, req_info: HttpRequest, db: Data) -> HttpResponse { +pub async fn enroll_host( + id: Path, + req_info: HttpRequest, + db: Data, +) -> HttpResponse { let session_info = enforce_2fa(&req_info, &db.conn) .await .unwrap_or(TokenInfo::NotPresent); @@ -1964,8 +1968,8 @@ pub async fn enroll_host(id: Path, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data, req_info: HttpRequest, db: Data, @@ -2307,8 +2315,8 @@ pub async fn create_host_and_enrollment_code( enrollment_code: CodeResponse { code: code.id, lifetime_seconds: CONFIG.tokens.enrollment_tokens_expiry_time, - } + }, }, metadata: CreateHostAndCodeResponseMetadata {}, }) -} \ No newline at end of file +} diff --git a/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs b/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs index aca855f..8d8fd7e 100644 --- a/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs +++ b/trifid-api/trifid_api_migration/src/m20230511_120511_create_table_host_enrollment_codes.rs @@ -1,5 +1,5 @@ -use sea_orm_migration::prelude::*; use crate::m20230427_170037_create_table_hosts::Host; +use sea_orm_migration::prelude::*; #[derive(DeriveMigrationName)] pub struct Migration; @@ -11,15 +11,24 @@ impl MigrationTrait for Migration { .create_table( Table::create() .table(HostEnrollmentCode::Table) - .col(ColumnDef::new(HostEnrollmentCode::Id).string().not_null().primary_key()) + .col( + ColumnDef::new(HostEnrollmentCode::Id) + .string() + .not_null() + .primary_key(), + ) .col(ColumnDef::new(HostEnrollmentCode::Host).string().not_null()) - .col(ColumnDef::new(HostEnrollmentCode::ExpiresOn).big_integer().not_null()) + .col( + ColumnDef::new(HostEnrollmentCode::ExpiresOn) + .big_integer() + .not_null(), + ) .foreign_key( ForeignKey::create() .from(HostEnrollmentCode::Table, HostEnrollmentCode::Host) .to(Host::Table, Host::Id) .on_update(ForeignKeyAction::Cascade) - .on_delete(ForeignKeyAction::Cascade) + .on_delete(ForeignKeyAction::Cascade), ) .to_owned(), ) @@ -39,5 +48,5 @@ pub enum HostEnrollmentCode { Table, Id, Host, - ExpiresOn + ExpiresOn, }