From 44c1d14d3e7ab5cf72fe98d7c4caa8cdfcfd0302 Mon Sep 17 00:00:00 2001 From: Haaroon Yousaf Date: Thu, 30 May 2024 18:13:33 +0100 Subject: [PATCH 1/6] squashed all my commits for adding authentication to graphql --- .env.example | 4 + Cargo.lock | 681 +++++++++++++++--- Cargo.toml | 25 +- raphtory-graphql/Cargo.toml | 7 + raphtory-graphql/src/azure_auth/common.rs | 267 +++++++ raphtory-graphql/src/azure_auth/mod.rs | 3 + .../src/azure_auth/token_middleware.rs | 120 +++ raphtory-graphql/src/lib.rs | 2 + raphtory-graphql/src/main.rs | 10 +- raphtory-graphql/src/model/graph/edge.rs | 1 - raphtory-graphql/src/routes.rs | 4 +- raphtory-graphql/src/server.rs | 182 ++++- raphtory/src/db/api/storage/edges/edges.rs | 1 + raphtory/src/db/api/storage/storage_ops.rs | 2 +- 14 files changed, 1185 insertions(+), 124 deletions(-) create mode 100644 .env.example create mode 100644 raphtory-graphql/src/azure_auth/common.rs create mode 100644 raphtory-graphql/src/azure_auth/mod.rs create mode 100644 raphtory-graphql/src/azure_auth/token_middleware.rs diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000..514ca385a0 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +CLIENT_ID= +CLIENT_SECRET= +TENANT_ID= +AUTHORITY=https://login.microsoftonline.com/TENANT_ID \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index bd9dc4cfbb..067db695cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,6 +27,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 = "aes" version = "0.8.4" @@ -38,6 +48,20 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.11" @@ -515,23 +539,23 @@ dependencies = [ [[package]] name = "async-graphql" -version = "6.0.11" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298a5d587d6e6fdb271bf56af2dc325a80eb291fd0fc979146584b9a05494a8c" +checksum = "d0808e3dc8be9cee801000b9261081cd7caae9935658be7e52d4df3d5c22bdce" dependencies = [ "async-graphql-derive", "async-graphql-parser", "async-graphql-value", "async-stream", "async-trait", - "base64 0.13.1", + "base64 0.22.1", "bytes", "chrono", "fast_chemail", "fnv", "futures-util", "handlebars", - "http", + "http 1.1.0", "indexmap", "mime", "multer", @@ -542,33 +566,33 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "static_assertions", + "static_assertions_next", "tempfile", "thiserror", ] [[package]] name = "async-graphql-derive" -version = "6.0.11" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f329c7eb9b646a72f70c9c4b516c70867d356ec46cb00dcac8ad343fd006b0" +checksum = "ae80fb7b67deeae84441a9eb156359b99be7902d2d706f43836156eec69a8226" dependencies = [ "Inflector", "async-graphql-parser", "darling 0.20.9", - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", - "strum 0.25.0", + "strum", "syn 2.0.65", "thiserror", ] [[package]] name = "async-graphql-parser" -version = "6.0.11" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6139181845757fd6a73fbb8839f3d036d7150b798db0e9bb3c6e83cdd65bd53b" +checksum = "8f0fffb19cd96eb084428289f4568b5ad48df32f782f891f709db96384fbdeb2" dependencies = [ "async-graphql-value", "pest", @@ -578,12 +602,13 @@ dependencies = [ [[package]] name = "async-graphql-poem" -version = "6.0.11" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c665e1051f0c73fdb2f143a341c531249aedd35473d0f5d8e6e32627502f45cd" +checksum = "ee8483d34d012aea467463c4de9a3bddee965fac6f99ed4a4a859171e4ecadb4" dependencies = [ "async-graphql", "futures-util", + "http 1.1.0", "mime", "poem", "serde_json", @@ -594,9 +619,9 @@ dependencies = [ [[package]] name = "async-graphql-value" -version = "6.0.11" +version = "7.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323a5143f5bdd2030f45e3f2e0c821c9b1d36e79cf382129c64299c50a7f3750" +checksum = "c224c93047a7197fe0f1d6eee98245ba6049706c6c04a372864557fb61495e94" dependencies = [ "bytes", "indexmap", @@ -668,6 +693,12 @@ version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ae037714f313c1353189ead58ef9eec30a8e8dc101b2622d461418fd59e28a9" +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -721,6 +752,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-compat" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a8d4d2746f89841e49230dd26917df1876050f95abafafbe34f47cb534b88d7" +dependencies = [ + "byteorder", +] + [[package]] name = "bincode" version = "1.3.3" @@ -918,6 +958,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd16c4719339c4530435d38e511904438d07cce7950afa3718a84ac36c10e89e" + [[package]] name = "chrono" version = "0.4.38" @@ -1053,8 +1099,8 @@ version = "7.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b34115915337defe99b2aff5c2ce6771e5fbc4079f4b506301f5cf394c8452f7" dependencies = [ - "strum 0.26.2", - "strum_macros 0.26.2", + "strum", + "strum_macros", "unicode-width", ] @@ -1065,7 +1111,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" dependencies = [ "async-trait", - "convert_case", + "convert_case 0.6.0", "json5", "lazy_static", "nom", @@ -1114,6 +1160,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "convert_case" version = "0.6.0" @@ -1123,6 +1175,24 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "aes-gcm", + "base64 0.22.1", + "hkdf", + "hmac", + "percent-encoding", + "rand 0.8.5", + "sha2", + "subtle", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -1255,6 +1325,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1279,6 +1350,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.14.4" @@ -1472,8 +1552,8 @@ dependencies = [ "datafusion-common", "paste", "sqlparser", - "strum 0.26.2", - "strum_macros 0.26.2", + "strum", + "strum_macros", ] [[package]] @@ -1681,6 +1761,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 1.0.109", +] + [[package]] name = "diff" version = "0.1.13" @@ -1751,8 +1844,6 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "dynamic-graphql" version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb2817847d7b2712a7faf8c1c36627a19e87195b23c161d10ae8c07d43a1b7f" dependencies = [ "async-graphql", "dynamic-graphql-derive", @@ -1762,8 +1853,6 @@ dependencies = [ [[package]] name = "dynamic-graphql-derive" version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611b88e975cd2d3399abe6164d1a96bf80c3488818aaa8847a4aabcd53f96803" dependencies = [ "Inflector", "darling 0.20.9", @@ -1931,6 +2020,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign_vec" version = "0.1.0" @@ -2104,6 +2208,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.28.1" @@ -2133,7 +2247,26 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -2154,9 +2287,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "4.5.0" +version = "5.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faa67bab9ff362228eb3d00bd024a4965d8231bbb7921167f0cfa66c6626b225" +checksum = "d08485b96a0e6393e9e4d1b8d48cf74ad6c063cd905eb33f42c1ce3f0377539b" dependencies = [ "log", "pest", @@ -2185,14 +2318,14 @@ dependencies = [ [[package]] name = "headers" -version = "0.3.9" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http", + "http 1.1.0", "httpdate", "mime", "sha1", @@ -2200,11 +2333,11 @@ dependencies = [ [[package]] name = "headers-core" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http", + "http 1.1.0", ] [[package]] @@ -2231,6 +2364,15 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -2257,6 +2399,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -2264,7 +2417,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -2296,9 +2472,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -2310,6 +2486,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -2317,13 +2513,41 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.28", "rustls", "tokio", "tokio-rustls", ] +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.28", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "tokio", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -2504,6 +2728,20 @@ dependencies = [ "serde", ] +[[package]] +name = "jsonwebtoken" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" +dependencies = [ + "base64 0.21.7", + "pem", + "ring 0.16.20", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "kdam" version = "0.5.1" @@ -2824,19 +3062,19 @@ dependencies = [ [[package]] name = "multer" -version = "2.1.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.1.0", "httparse", - "log", "memchr", "mime", "spin 0.9.8", + "tokio", "version_check", ] @@ -2868,6 +3106,24 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "neo4rs" version = "0.6.2" @@ -2910,12 +3166,13 @@ dependencies = [ [[package]] name = "nix" -version = "0.27.1" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" +checksum = "ab2156c4fce2f8df6c499cc1c763e4394b7482525bf2a9701c9d79d215f519e4" dependencies = [ "bitflags 2.5.0", "cfg-if 1.0.0", + "cfg_aliases", "libc", ] @@ -3029,6 +3286,26 @@ dependencies = [ "libc", ] +[[package]] +name = "oauth2" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" +dependencies = [ + "base64 0.13.1", + "chrono", + "getrandom 0.2.15", + "http 0.2.12", + "rand 0.8.5", + "reqwest", + "serde", + "serde_json", + "serde_path_to_error", + "sha2", + "thiserror", + "url", +] + [[package]] name = "object" version = "0.32.2" @@ -3080,12 +3357,56 @@ version = "11.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if 1.0.0", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.65", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.21.0" @@ -3312,6 +3633,15 @@ dependencies = [ "hmac", ] +[[package]] +name = "pem" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" +dependencies = [ + "base64 0.13.1", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3468,31 +3798,41 @@ dependencies = [ [[package]] name = "poem" -version = "1.3.59" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504774c97b0744c1ee108a37e5a65a9745a4725c4c06277521dabc28eb53a904" +checksum = "e88b6912ed1e8833d7c22c9c986c517f4518d7d37e3c04566d917c789aaea591" dependencies = [ - "async-trait", - "base64 0.21.7", + "base64 0.22.1", "bytes", + "chrono", + "cookie", "futures-util", "headers", - "http", - "hyper", + "http 1.1.0", + "http-body-util", + "hyper 1.3.1", + "hyper-util", "mime", + "multer", "nix", "parking_lot", "percent-encoding", "pin-project-lite", "poem-derive", + "quick-xml", "regex", "rfc7239", "serde", "serde_json", "serde_urlencoded", + "serde_yaml", "smallvec", + "sync_wrapper 1.0.1", + "tempfile", "thiserror", + "time", "tokio", + "tokio-stream", "tokio-tungstenite", "tokio-util", "tracing", @@ -3501,14 +3841,57 @@ dependencies = [ [[package]] name = "poem-derive" -version = "1.3.59" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2b961d58a6c53380c20236394381d9292fda03577f902b158f1638932964dcf" +dependencies = [ + "proc-macro-crate 3.1.0", + "proc-macro2", + "quote", + "syn 2.0.65", +] + +[[package]] +name = "poem-openapi" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6445b50be2e26f142d4e554d15773fc1e7510b994083c9625a65eba0d3f4287" +dependencies = [ + "base64 0.22.1", + "bytes", + "derive_more", + "futures-util", + "indexmap", + "mime", + "num-traits", + "poem", + "poem-openapi-derive", + "quick-xml", + "regex", + "serde", + "serde_json", + "serde_urlencoded", + "serde_yaml", + "thiserror", + "tokio", +] + +[[package]] +name = "poem-openapi-derive" +version = "5.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ddcf4680d8d867e1e375116203846acb088483fa2070244f90589f458bbb31" +checksum = "e890165626ff447a1ff3d6f2293e6ccacbf7fcbdd4c94086aa548de655735b03" dependencies = [ - "proc-macro-crate 2.0.0", + "darling 0.20.9", + "http 1.1.0", + "indexmap", + "mime", + "proc-macro-crate 3.1.0", "proc-macro2", "quote", + "regex", "syn 2.0.65", + "thiserror", ] [[package]] @@ -3610,6 +3993,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if 1.0.0", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.6.0" @@ -3650,11 +4045,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "2.0.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8366a6159044a37876a2b9817124296703c586a5c92e2c53751fa06d8d43e8" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" dependencies = [ - "toml_edit 0.20.7", + "toml_edit 0.21.1", ] [[package]] @@ -3785,6 +4180,16 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quickcheck" version = "0.9.2" @@ -4012,6 +4417,27 @@ dependencies = [ name = "raphtory-arrow" version = "0.8.1" +[[package]] +name = "raphtory-auth" +version = "0.8.1" +dependencies = [ + "base64-compat", + "chrono", + "dotenv", + "futures-util", + "jsonwebtoken", + "oauth2", + "poem", + "poem-openapi", + "reqwest", + "serde", + "serde_json", + "time", + "tokio", + "url", + "uuid", +] + [[package]] name = "raphtory-benchmark" version = "0.8.1" @@ -4071,6 +4497,7 @@ dependencies = [ "async-graphql-poem", "async-stream", "base64 0.21.7", + "base64-compat", "bincode", "chrono", "config", @@ -4078,6 +4505,8 @@ dependencies = [ "dynamic-graphql", "futures-util", "itertools 0.12.1", + "jsonwebtoken", + "oauth2", "once_cell", "opentelemetry", "opentelemetry-jaeger", @@ -4085,16 +4514,20 @@ dependencies = [ "ordered-float 4.2.0", "parking_lot", "poem", + "poem-openapi", "raphtory", + "reqwest", "serde", "serde_json", "tempfile", "thiserror", + "time", "tokio", "toml", "tracing", "tracing-opentelemetry", "tracing-subscriber", + "url", "uuid", "walkdir", ] @@ -4228,16 +4661,18 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls", + "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", + "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4247,9 +4682,10 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", @@ -4603,6 +5039,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +dependencies = [ + "itoa", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.6" @@ -4624,6 +5070,19 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "sha1" version = "0.10.6" @@ -4676,6 +5135,18 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -4822,6 +5293,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_assertions_next" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7beae5182595e9a8b683fa98c4317f956c9a2dec3b9716990d20023cc60c766" + [[package]] name = "streaming-decompression" version = "0.1.2" @@ -4864,35 +5341,13 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.25.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" -dependencies = [ - "strum_macros 0.25.3", -] - [[package]] name = "strum" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d8cec3501a5194c432b2b7976db6b7d10ec95c253208b45f83f7136aa985e29" dependencies = [ - "strum_macros 0.26.2", -] - -[[package]] -name = "strum_macros" -version = "0.25.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 2.0.65", + "strum_macros", ] [[package]] @@ -4942,6 +5397,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "system-configuration" version = "0.5.1" @@ -5295,6 +5759,16 @@ dependencies = [ "syn 2.0.65", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.24.1" @@ -5318,9 +5792,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", @@ -5376,9 +5850,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.20.7" +version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" dependencies = [ "indexmap", "toml_datetime", @@ -5497,14 +5971,14 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 1.1.0", "httparse", "log", "rand 0.8.5", @@ -5600,6 +6074,22 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.7.1" @@ -5621,6 +6111,7 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -5663,6 +6154,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index b0c51900e5..7839fa5646 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ members = [ "python", "js-raphtory", "raphtory-graphql", + "raphtory-auth", ] default-members = ["raphtory"] resolver = "2" @@ -30,12 +31,12 @@ inherits = "release" debug = true [workspace.dependencies] -async-graphql = { version = "6.0.11", features = ["dynamic-schema"] } -async-graphql-poem = "6.0.11" -dynamic-graphql = "0.8.1" -reqwest = { version = "0.11.22", default-features = false, features = ["rustls-tls"] } -serde = { version = "1.0.197", features = ["derive", "rc"] } -serde_json = "1.0.114" +async-graphql = { version = "7.0.5", features = ["dynamic-schema"] } +async-graphql-poem = "7.0.5" +dynamic-graphql = { path = "/Users/haaroony/Documents/dev/dynamic-graphql" } +reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "json"] } +serde = { version = "1.0", features = ["derive", "rc"] } +serde_json = "1.0" pyo3 = { version = "0.20.0", features = ["multiple-pymethods", "chrono"] } pyo3-asyncio = { version = "0.20.0", features = ["tokio-runtime"] } pyo3-build-config = "0.20.0" @@ -47,7 +48,7 @@ tokio = { version = "1.36.0", features = ["full"] } once_cell = "1.19.0" parking_lot = { version = "0.12.1", features = ["serde", "arc_lock", "send_guard"] } ordered-float = "4.2.0" -chrono = { version = "0.4.37", features = ["serde"] } +chrono = { version = "0.4.38", features = ["serde"] } tempfile = "3.10.0" futures-util = "0.3.30" thiserror = "1.0.57" @@ -72,6 +73,8 @@ bzip2 = "0.4.4" tantivy = "0.22" async-trait = "0.1.77" async-openai = "0.17.1" +oauth2 = "4.0" +jsonwebtoken = "8.0" num = "0.4.1" display-error-chain = "0.2.0" polars-arrow = "0.39.2" @@ -87,7 +90,8 @@ proptest = "1.4.0" criterion = "0.5.1" crossbeam-channel = "0.5.11" base64 = "0.21.7" -poem = "1.3.59" +poem = "3.0.1" +poem-openapi = "5.0.2" async-stream = "0.3.5" opentelemetry = "0.21.0" opentelemetry_sdk = { version = "0.21.0", features = ["rt-tokio"] } @@ -96,7 +100,7 @@ tracing = "0.1.37" tracing-opentelemetry = "0.22.0" tracing-subscriber = { version = "0.3.16", features = ["std", "env-filter"] } walkdir = "2" -uuid = "1.7.0" +uuid = { version = "1.0", features = ["v4"] } config = "0.14.0" either = "=1.11.0" toml = "0.8.10" @@ -116,6 +120,9 @@ bytemuck = { version = "1.15.0" } rpds = { version = "1.1.0", features = ["serde"] } thread_local = "1.1.8" ouroboros = "0.18.3" +url = "2.2" +base64-compat = { package = "base64-compat", version = "1.0.0" } +time = "0.3.36" lazy_static = "1.4.0" pest = "2.7.8" diff --git a/raphtory-graphql/Cargo.toml b/raphtory-graphql/Cargo.toml index b787fcc79b..4e27e8775b 100644 --- a/raphtory-graphql/Cargo.toml +++ b/raphtory-graphql/Cargo.toml @@ -23,6 +23,8 @@ serde = { workspace = true } serde_json = { workspace = true } once_cell = { workspace = true } poem = { workspace = true } +poem-openapi = { workspace = true } +oauth2 = { workspace = true } tokio = { workspace = true } async-graphql = { workspace = true, features=["apollo_tracing"] } dynamic-graphql = { workspace = true } @@ -30,6 +32,7 @@ async-graphql-poem = { workspace = true } parking_lot = { workspace = true } futures-util = { workspace = true } async-stream = { workspace = true } +jsonwebtoken = { workspace = true } opentelemetry = { workspace = true } opentelemetry_sdk = { workspace = true } opentelemetry-jaeger = { workspace = true } @@ -42,6 +45,10 @@ uuid = { workspace = true } chrono = { workspace = true } config = { workspace = true } toml = { workspace = true } +url = { workspace = true } +base64-compat = { workspace = true } +time = { workspace = true } +reqwest = { workspace = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/raphtory-graphql/src/azure_auth/common.rs b/raphtory-graphql/src/azure_auth/common.rs new file mode 100644 index 0000000000..89b9216128 --- /dev/null +++ b/raphtory-graphql/src/azure_auth/common.rs @@ -0,0 +1,267 @@ +use base64_compat::{decode_config, URL_SAFE_NO_PAD}; +use chrono::{Duration, Utc}; +use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation}; +use oauth2::{ + basic::BasicClient, reqwest::async_http_client, AuthorizationCode, CsrfToken, + PkceCodeChallenge, PkceCodeVerifier, Scope, TokenResponse, +}; +use poem::{ + handler, + http::StatusCode, + web::{ + cookie::{Cookie, CookieJar}, + Data, Json, Query, Redirect, + }, + IntoResponse, Response, +}; +use reqwest::Client; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::{ + collections::HashMap, + env, + error::Error, + sync::{Arc, Mutex}, +}; + +#[derive(Deserialize, Serialize)] +struct AuthRequest { + code: String, + state: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Jwks { + pub(crate) keys: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Jwk { + pub(crate) kid: String, + kty: String, + #[serde(rename = "use")] + use_: String, + pub(crate) n: String, + pub(crate) e: String, +} + +#[derive(Clone)] +pub struct AppState { + pub(crate) oauth_client: Arc, + pub(crate) csrf_state: Arc>>, + pub(crate) pkce_verifier: Arc>>, + pub(crate) jwks: Arc, +} + +#[handler] +pub async fn login(data: Data<&AppState>, jar: &CookieJar) -> Redirect { + let session_id = uuid::Uuid::new_v4().to_string(); + let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256(); + let (authorize_url, csrf_state) = data + .oauth_client + .authorize_url(CsrfToken::new_random) + .set_pkce_challenge(pkce_challenge) + .add_scope(Scope::new("openid".to_string())) + .add_scope(Scope::new("email".to_string())) + .add_scope(Scope::new("offline_access".to_string())) + .add_scope(Scope::new( + "a10e734e-cb36-46ca-bbfd-c298e15b6327/public-scope".to_string(), + )) + .url(); + + data.csrf_state + .lock() + .unwrap() + .insert(session_id.clone(), csrf_state); + data.pkce_verifier + .lock() + .unwrap() + .insert(session_id.clone(), pkce_verifier); + + let mut session_cookie = Cookie::new("session_id", session_id); + session_cookie.set_path("/"); + session_cookie.set_http_only(true); + jar.add(session_cookie); + + Redirect::temporary(authorize_url.to_string().as_str()) +} + +#[handler] +pub async fn auth_callback( + data: Data<&AppState>, + query: Query, + jar: &CookieJar, +) -> impl IntoResponse { + println!("Running callback"); + if let Some(session_cookie) = jar.get("session_id") { + println!("Got session ID"); + let session_id = session_cookie.value::().unwrap(); + + let code = AuthorizationCode::new(query.0.code.clone()); + let pkce_verifier = data + .pkce_verifier + .lock() + .unwrap() + .remove(&session_id) + .unwrap(); + println!("Getting token result"); + let token_result = data + .oauth_client + .exchange_code(code) + .set_pkce_verifier(pkce_verifier) + .request_async(async_http_client) + .await; + + match token_result { + Ok(token) => { + println!("Getting access token"); + let access_token = token.access_token(); + let expires_in = token + .expires_in() + .unwrap_or(core::time::Duration::from_secs(60 * 60 * 24)); + let expiration = Utc::now() + Duration::from_std(expires_in).unwrap(); + println!("Set token data"); + let token_data = json!({ + "access_token_secret": access_token.secret(), + "expires_at": expiration.to_rfc3339() + }); + + println!("Makign auth cookie"); + let mut auth_cookie = Cookie::new("auth_token", token_data.to_string()); + auth_cookie.set_expires(expiration); + auth_cookie.set_path("/"); + auth_cookie.set_http_only(true); + println!("Storing in jar"); + jar.add(auth_cookie); + return Redirect::temporary("/").into_response(); + } + Err(_err) => { + println!("Token error"); + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .content_type("application/json") + .body(json!({"error": "Login failed"}).to_string()); + } + } + } else { + println!("Session not found. Please login again."); + return Response::builder() + .status(StatusCode::UNAUTHORIZED) + .content_type("application/json") + .body(json!({"error": "Session not found. Please login again"}).to_string()); + } +} + +pub fn decode_base64_urlsafe(base64_str: &str) -> Result, Box> { + let decoded = decode_config(base64_str, URL_SAFE_NO_PAD)?; + Ok(decoded) +} + +#[handler] +pub async fn secure_endpoint() -> Json { + println!("Entering secure endpoint"); // Debugging line + Json(serde_json::json!({ + "message": "Secured" + })) +} + +#[handler] +pub async fn logout(jar: &CookieJar) -> String { + if let Some(mut cookie) = jar.get("auth_token") { + cookie.set_expires(Utc::now() - Duration::days(1)); + jar.remove("auth_token"); + } + if let Some(mut cookie) = jar.get("session_id") { + cookie.set_expires(Utc::now() - Duration::days(1)); + jar.remove("session_id"); + } + "You have been logged out.".to_string() +} + +#[handler] +pub async fn verify(data: Data<&AppState>, jar: &CookieJar) -> Json { + println!("Checking for session_id"); + if let Some(_session_cookie) = jar.get("session_id") { + println!("Checking for auth_token"); + if let Some(cookie) = jar.get("auth_token") { + println!("Getting cookie value"); + let cookie_value = cookie.value::().expect("Unable to find cookie"); + println!("Getting token data"); + let token_data: serde_json::Value = + serde_json::from_str(&cookie_value).expect("Invalid cookie format"); + println!("Getting secrets"); + let token = token_data["access_token_secret"] + .as_str() + .expect("No access token found"); + let expires_at_str = token_data["expires_at"] + .as_str() + .expect("No expiration time found"); + + let expires_at = chrono::DateTime::parse_from_rfc3339(expires_at_str) + .expect("Invalid expiration format"); + println!("Checking expiry"); + if Utc::now() > expires_at { + return Json(serde_json::json!({ + "message": "Access token expired, please login again" + })); + } + println!("Getting headers, kid and jwk"); + let header = decode_header(token).expect("Unable to decode header"); + let kid = header.kid.expect("Token header does not have a kid field"); + + let jwk = data + .jwks + .keys + .iter() + .find(|&jwk| jwk.kid == kid) + .expect("Key ID not found in JWKS"); + println!("Decoding RSA"); + let n = decode_base64_urlsafe(&jwk.n).unwrap(); + let e = decode_base64_urlsafe(&jwk.e).unwrap(); + + let decoding_key = DecodingKey::from_rsa_raw_components(&n, &e); + + let validation = Validation::new(Algorithm::RS256); + println!("Decoding token"); + let token_data = + decode::>(token, &decoding_key, &validation); + + match token_data { + Ok(dc) => { + println!("valid token"); + println!("{:?}", dc); + Json(serde_json::json!({ + "message": "Valid access token", + })) + } + Err(err) => { + println!("{:?} Cant authorise token.", err); // Debugging line + println!("cookie_value : {:?}", cookie_value); + println!("token : {:?}", token); + Json(serde_json::json!({ + "message": "No valid auth token found", + })) + } + } + } else { + println!("No valid auth token found in cookies, please login."); // Debugging line + Json(serde_json::json!({ + "message": "No cookie auth_token found, please login" + })) + } + } else { + println!("No session_id found, please login."); // Debugging line + Json(serde_json::json!({ + "message": "No session_id found, please login" + })) + } +} + +pub async fn get_jwks() -> Result> { + let authority = env::var("AUTHORITY").expect("AUTHORITY not set"); + let jwks_url = format!("{}/discovery/v2.0/keys", authority); + let client = Client::new(); + let response = client.get(&jwks_url).send().await?; + let jwks = response.json::().await?; + Ok(jwks) +} diff --git a/raphtory-graphql/src/azure_auth/mod.rs b/raphtory-graphql/src/azure_auth/mod.rs new file mode 100644 index 0000000000..423dcd51fb --- /dev/null +++ b/raphtory-graphql/src/azure_auth/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod common; + +pub(crate) mod token_middleware; diff --git a/raphtory-graphql/src/azure_auth/token_middleware.rs b/raphtory-graphql/src/azure_auth/token_middleware.rs new file mode 100644 index 0000000000..a4b9730742 --- /dev/null +++ b/raphtory-graphql/src/azure_auth/token_middleware.rs @@ -0,0 +1,120 @@ +use crate::azure_auth::common::{decode_base64_urlsafe, AppState}; +use jsonwebtoken::{decode, decode_header, Algorithm, DecodingKey, Validation}; +use poem::{ + http::StatusCode, web::Redirect, Endpoint, Error, IntoResponse, Middleware, Request, Response, + Result, +}; +use std::{collections::HashMap, sync::Arc}; + +#[derive(Clone)] +pub struct TokenMiddleware { + app_state: Arc, +} + +impl TokenMiddleware { + pub fn new(app_state: Arc) -> Self { + TokenMiddleware { app_state } + } +} + +impl Middleware for TokenMiddleware { + type Output = TokenMiddlewareImpl; + + fn transform(&self, ep: E) -> Self::Output { + TokenMiddlewareImpl { + ep, + app_state: self.app_state.clone(), + } + } +} + +pub struct TokenMiddlewareImpl { + ep: E, + app_state: Arc, +} + +#[allow(dead_code)] +#[derive(Clone)] +struct Token(String); + +impl Endpoint for TokenMiddlewareImpl { + type Output = Response; + + async fn call(&self, mut req: Request) -> Result { + println!("Running call"); + let jar = req.cookie().clone(); + println!("Checking for session_id"); + if let Some(_session_cookie) = jar.get("session_id") { + println!("Checking for auth_token"); + if let Some(auth_cookie) = jar.get("auth_token") { + println!("Gotten cookie, decoding data"); + let token_data: serde_json::Value = serde_json::from_str( + &auth_cookie + .value::() + .expect("Unable to find cookie"), + ) + .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; + println!("Getting access token"); + let access_token = token_data["access_token_secret"] + .as_str() + .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; + println!("Expiry"); + let expires_at_str = token_data["expires_at"] + .as_str() + .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; + println!("Gotten access token"); + let expires_at = chrono::DateTime::parse_from_rfc3339(expires_at_str) + .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; + if chrono::Utc::now() > expires_at { + return Err(Error::from_status(StatusCode::UNAUTHORIZED)); + } + + println!("Getting headers and kit"); + let header = decode_header(access_token) + .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; + let kid = header + .kid + .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; + + println!("Finding jwk"); + let jwk = self + .app_state + .jwks + .keys + .iter() + .find(|&jwk| jwk.kid == kid) + .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; + + println!("Decoding n and e"); + let n = decode_base64_urlsafe(&jwk.n) + .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; + let e = decode_base64_urlsafe(&jwk.e) + .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; + + println!("Decoding key"); + let decoding_key = DecodingKey::from_rsa_raw_components(&n, &e); + + println!("validating"); + let validation = Validation::new(Algorithm::RS256); + decode::>( + access_token, + &decoding_key, + &validation, + ) + .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; + + println!("Inserting token into request"); + // Insert token data to extensions of request. + req.extensions_mut().insert(Token(access_token.to_string())); + + println!("Token is valid, proceed with the request"); + return self.ep.call(req).await.map(IntoResponse::into_response); + } + println!("unable to find auth_token cookie"); + Ok(Redirect::temporary("/login").into_response()) + } else { + println!("Unable to find session_id cookie"); + Ok(Redirect::temporary("/login").into_response()) + } + } +} diff --git a/raphtory-graphql/src/lib.rs b/raphtory-graphql/src/lib.rs index c6372c3748..f22d8a502f 100644 --- a/raphtory-graphql/src/lib.rs +++ b/raphtory-graphql/src/lib.rs @@ -7,6 +7,8 @@ mod observability; mod routes; pub mod server; +pub mod azure_auth; + mod data; #[derive(thiserror::Error, Debug)] pub enum UrlDecodeError { diff --git a/raphtory-graphql/src/main.rs b/raphtory-graphql/src/main.rs index 185c36bd7e..2e172cbbc2 100644 --- a/raphtory-graphql/src/main.rs +++ b/raphtory-graphql/src/main.rs @@ -1,19 +1,27 @@ use crate::server::RaphtoryServer; use std::env; +mod azure_auth; mod data; mod model; mod observability; mod routes; mod server; +extern crate base64_compat as base64_compat; + #[tokio::main] async fn main() { let graph_directory = env::var("GRAPH_DIRECTORY").unwrap_or("/tmp/graphs".to_string()); let config_path = "config.toml"; + // RaphtoryServer::from_directory(&graph_directory) + // .run(config_path, false) + // .await + // .unwrap() + RaphtoryServer::from_directory(&graph_directory) - .run(config_path, false) + .run_with_auth(config_path, false) .await .unwrap() } diff --git a/raphtory-graphql/src/model/graph/edge.rs b/raphtory-graphql/src/model/graph/edge.rs index cb59c1589e..a513531c0a 100644 --- a/raphtory-graphql/src/model/graph/edge.rs +++ b/raphtory-graphql/src/model/graph/edge.rs @@ -1,5 +1,4 @@ use crate::model::graph::{node::Node, property::GqlProperties}; -use async_graphql::Error; use dynamic_graphql::{ResolvedObject, ResolvedObjectFields}; use itertools::Itertools; use raphtory::{ diff --git a/raphtory-graphql/src/routes.rs b/raphtory-graphql/src/routes.rs index 22865da9c4..cd90a4832f 100644 --- a/raphtory-graphql/src/routes.rs +++ b/raphtory-graphql/src/routes.rs @@ -21,6 +21,8 @@ pub(crate) async fn health() -> impl IntoResponse { #[handler] pub(crate) async fn graphql_playground() -> impl IntoResponse { Html(playground_source( - GraphQLPlaygroundConfig::new("/").subscription_endpoint("/ws"), + GraphQLPlaygroundConfig::new("/") + .subscription_endpoint("/ws") + .with_setting("request.credentials", "include"), )) } diff --git a/raphtory-graphql/src/server.rs b/raphtory-graphql/src/server.rs index bc40d85aee..d24f8bd538 100644 --- a/raphtory-graphql/src/server.rs +++ b/raphtory-graphql/src/server.rs @@ -1,5 +1,9 @@ #![allow(dead_code)] use crate::{ + azure_auth::{ + common::{auth_callback, get_jwks, login, logout, verify, AppState}, + token_middleware::TokenMiddleware, + }, data::Data, model::{ algorithms::{algorithm::Algorithm, algorithm_entry_point::AlgorithmEntryPoint}, @@ -10,8 +14,15 @@ use crate::{ }; use async_graphql::extensions::ApolloTracing; use async_graphql_poem::GraphQL; +use dotenv::dotenv; use itertools::Itertools; -use poem::{get, listener::TcpListener, middleware::Cors, EndpointExt, Route, Server}; +use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; +use poem::{ + get, + listener::TcpListener, + middleware::{CookieJarManager, CookieJarManagerEndpoint, Cors, CorsEndpoint}, + EndpointExt, Route, Server, +}; use raphtory::{ db::api::view::{DynamicGraph, IntoDynamic, MaterializedGraph}, vectors::{ @@ -21,7 +32,12 @@ use raphtory::{ }, }; use serde::{Deserialize, Serialize}; -use std::{collections::HashMap, fs, path::Path, sync::Arc}; +use std::{ + collections::HashMap, + env, fs, + path::Path, + sync::{Arc, Mutex}, +}; use tokio::{ io::Result as IoResult, signal, @@ -138,16 +154,23 @@ impl RaphtoryServer { } /// Start the server on the default port and return a handle to it. - pub fn start(self, log_config_or_level: &str, enable_tracing: bool) -> RunningRaphtoryServer { - self.start_with_port(1736, log_config_or_level, enable_tracing) + pub async fn start( + self, + log_config_or_level: &str, + enable_tracing: bool, + enable_auth: bool, + ) -> RunningRaphtoryServer { + self.start_with_port(1736, log_config_or_level, enable_tracing, enable_auth) + .await } /// Start the server on the port `port` and return a handle to it. - pub fn start_with_port( + pub async fn start_with_port( self, port: u16, log_config_or_level: &str, enable_tracing: bool, + enable_auth: bool, ) -> RunningRaphtoryServer { fn parse_log_level(input: &str) -> Option { // Parse log level from string @@ -212,6 +235,32 @@ impl RaphtoryServer { .unwrap_or(()); // it is important that this runs after algorithms have been pushed to PLUGIN_ALGOS static variable + + let app: CorsEndpoint> = if enable_auth { + println!("Generating endpoint with auth"); + self.generate_microsoft_endpoint_with_auth(enable_tracing, port) + .await + } else { + self.generate_endpoint(enable_tracing).await + }; + + let (signal_sender, signal_receiver) = mpsc::channel(1); + + println!("Playground: http://localhost:{port}"); + let server_task = Server::new(TcpListener::bind(format!("127.0.0.1:{port}"))) + .run_with_graceful_shutdown(app, server_termination(signal_receiver), None); + let server_result = tokio::spawn(server_task); + + RunningRaphtoryServer { + signal_sender, + server_result, + } + } + + async fn generate_endpoint( + self, + enable_tracing: bool, + ) -> CorsEndpoint> { let schema_builder = App::create_schema(); let schema_builder = schema_builder.data(self.data); let schema = if enable_tracing { @@ -220,27 +269,121 @@ impl RaphtoryServer { } else { schema_builder.finish().unwrap() }; + let app = Route::new() .at("/", get(graphql_playground).post(GraphQL::new(schema))) .at("/health", get(health)) + .with(CookieJarManager::new()) .with(Cors::new()); + app + } - let (signal_sender, signal_receiver) = mpsc::channel(1); + async fn generate_microsoft_endpoint_with_auth( + self, + enable_tracing: bool, + port: u16, + ) -> CorsEndpoint> { + let schema_builder = App::create_schema(); + let schema_builder = schema_builder.data(self.data); + let schema = if enable_tracing { + let schema_builder = schema_builder.extension(ApolloTracing); + schema_builder.finish().unwrap() + } else { + schema_builder.finish().unwrap() + }; - println!("Playground: http://localhost:{port}"); - let server_task = Server::new(TcpListener::bind(format!("0.0.0.0:{port}"))) - .run_with_graceful_shutdown(app, server_termination(signal_receiver), None); - let server_result = tokio::spawn(server_task); + dotenv().ok(); + println!("Loading env"); + let client_id_str = env::var("CLIENT_ID").expect("CLIENT_ID not set"); + let client_secret_str = env::var("CLIENT_SECRET").expect("CLIENT_SECRET not set"); + let tenant_id_str = env::var("TENANT_ID").expect("TENANT_ID not set"); + + let client_id = ClientId::new(client_id_str); + let client_secret = ClientSecret::new(client_secret_str); + + let auth_url = AuthUrl::new(format!( + "https://login.microsoftonline.com/{}/oauth2/v2.0/authorize", + tenant_id_str.clone() + )) + .expect("Invalid authorization endpoint URL"); + let token_url = TokenUrl::new(format!( + "https://login.microsoftonline.com/{}/oauth2/v2.0/token", + tenant_id_str.clone() + )) + .expect("Invalid token endpoint URL"); + + println!("Loading client"); + let client = BasicClient::new( + client_id.clone(), + Some(client_secret.clone()), + auth_url, + Some(token_url), + ) + .set_redirect_uri( + RedirectUrl::new(format!( + "http://localhost:{}/auth/callback", + port.to_string() + )) + .expect("Invalid redirect URL"), + ); + + println!("Fetching JWKS"); + let jwks = get_jwks().await.expect("Failed to fetch JWKS"); + + let app_state = AppState { + oauth_client: Arc::new(client), + csrf_state: Arc::new(Mutex::new(HashMap::new())), + pkce_verifier: Arc::new(Mutex::new(HashMap::new())), + jwks: Arc::new(jwks), + }; - RunningRaphtoryServer { - signal_sender, - server_result, - } + let token_middleware = TokenMiddleware::new(Arc::new(app_state.clone())); + + println!("Making app"); + let app = Route::new() + .at( + "/", + get(graphql_playground) + .post(GraphQL::new(schema)) + .with(token_middleware.clone()), + ) + .at("/health", get(health)) + .at("/login", login.data(app_state.clone())) + .at("/auth/callback", auth_callback.data(app_state.clone())) + .at( + "/verify", + verify + .data(app_state.clone()) + .with(token_middleware.clone()), + ) + // .at( + // "/secure_endpoint", + // secure_endpoint.with(token_middleware.clone()), + // ) + .at("/logout", logout.with(token_middleware.clone())) + .with(CookieJarManager::new()) + .with(Cors::new()); + println!("App done"); + app } /// Run the server on the default port until completion. pub async fn run(self, log_config_or_level: &str, enable_tracing: bool) -> IoResult<()> { - self.start(log_config_or_level, enable_tracing).wait().await + self.start(log_config_or_level, enable_tracing, false) + .await + .wait() + .await + } + + pub async fn run_with_auth( + self, + log_config_or_level: &str, + enable_tracing: bool, + ) -> IoResult<()> { + self.start(log_config_or_level, enable_tracing, true) + .await + .wait() + .await } /// Run the server on the port `port` until completion. @@ -250,7 +393,8 @@ impl RaphtoryServer { log_config_or_level: &str, enable_tracing: bool, ) -> IoResult<()> { - self.start_with_port(port, log_config_or_level, enable_tracing) + self.start_with_port(port, log_config_or_level, enable_tracing, false) + .await .wait() .await } @@ -340,10 +484,10 @@ mod server_tests { let graphs = HashMap::from([("test".to_owned(), g)]); let server = RaphtoryServer::from_map(graphs); println!("calling start at time {}", Local::now()); - let handler = server.start_with_port(0, "info", false); + let handler = server.start_with_port(0, "info", false, false); sleep(Duration::from_secs(1)).await; println!("Calling stop at time {}", Local::now()); - handler.stop().await; - handler.wait().await.unwrap() + handler.await.stop().await; + handler.await.wait().await.unwrap() } } diff --git a/raphtory/src/db/api/storage/edges/edges.rs b/raphtory/src/db/api/storage/edges/edges.rs index 64ca63f915..11f67569a9 100644 --- a/raphtory/src/db/api/storage/edges/edges.rs +++ b/raphtory/src/db/api/storage/edges/edges.rs @@ -41,6 +41,7 @@ pub enum EdgesStorageRef<'a> { } impl<'a> EdgesStorageRef<'a> { + #[allow(unused_variables)] pub fn get_layer(self, eid: EID, layer_id: usize) -> EdgeStorageRef<'a> { match self { EdgesStorageRef::Mem(storage) => EdgeStorageRef::Mem(storage.get(eid)), diff --git a/raphtory/src/db/api/storage/storage_ops.rs b/raphtory/src/db/api/storage/storage_ops.rs index 6ef7e45ad0..7704c19c71 100644 --- a/raphtory/src/db/api/storage/storage_ops.rs +++ b/raphtory/src/db/api/storage/storage_ops.rs @@ -43,7 +43,7 @@ use crate::{ }; use itertools::Itertools; use rayon::prelude::*; -use std::{iter, sync::Arc}; +use std::{iter}; #[derive(Debug, Clone)] pub enum GraphStorage { From f3a834f9e13275ee2caa1122439a5bb5b7b5d95f Mon Sep 17 00:00:00 2001 From: Haaroon Yousaf Date: Mon, 3 Jun 2024 17:40:31 +0100 Subject: [PATCH 2/6] add readme, remove printlns, fix pointer --- Cargo.lock | 132 +----------------- Cargo.toml | 2 +- raphtory-graphql/readme.md | 108 ++++++++++++++ raphtory-graphql/src/azure_auth/common.rs | 36 ++--- .../src/azure_auth/token_middleware.rs | 17 --- raphtory-graphql/src/main.rs | 21 +-- 6 files changed, 136 insertions(+), 180 deletions(-) create mode 100644 raphtory-graphql/readme.md diff --git a/Cargo.lock b/Cargo.lock index 1a0da42a48..3e85224e46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1843,7 +1843,9 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" [[package]] name = "dynamic-graphql" -version = "0.8.1" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd104a4f23fc94d666917fdf6a8a9c38bec87c7f21741daadbd5282d63072924" dependencies = [ "async-graphql", "dynamic-graphql-derive", @@ -1852,7 +1854,9 @@ dependencies = [ [[package]] name = "dynamic-graphql-derive" -version = "0.8.1" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8b5d72ddb5add27d0e071039643c894ebdfd14fac88eed61d9111af8395d27" dependencies = [ "Inflector", "darling 0.20.9", @@ -2020,21 +2024,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "foreign_vec" version = "0.1.0" @@ -2520,19 +2509,6 @@ dependencies = [ "tokio-rustls", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.28", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-util" version = "0.1.5" @@ -3106,24 +3082,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2195bf6aa996a481483b29d62a7663eed3fe39600c460e323f8ff41e90bdd89b" -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "neo4rs" version = "0.6.2" @@ -3363,50 +3321,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" -[[package]] -name = "openssl" -version = "0.10.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" -dependencies = [ - "bitflags 2.5.0", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.65", -] - [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "openssl-sys" -version = "0.9.101" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "opentelemetry" version = "0.21.0" @@ -4427,27 +4347,6 @@ dependencies = [ name = "raphtory-arrow" version = "0.8.1" -[[package]] -name = "raphtory-auth" -version = "0.8.1" -dependencies = [ - "base64-compat", - "chrono", - "dotenv", - "futures-util", - "jsonwebtoken", - "oauth2", - "poem", - "poem-openapi", - "reqwest", - "serde", - "serde_json", - "time", - "tokio", - "url", - "uuid", -] - [[package]] name = "raphtory-benchmark" version = "0.8.1" @@ -4676,13 +4575,11 @@ dependencies = [ "http-body 0.4.6", "hyper 0.14.28", "hyper-rustls", - "hyper-tls", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", @@ -4695,7 +4592,6 @@ dependencies = [ "sync_wrapper 0.1.2", "system-configuration", "tokio", - "tokio-native-tls", "tokio-rustls", "tokio-util", "tower-service", @@ -5769,16 +5665,6 @@ dependencies = [ "syn 2.0.65", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.24.1" @@ -6164,12 +6050,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index f27d7c4e44..a8d0826019 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ raphtory-arrow = { version = "0.8.1", path = "raphtory-arrow" } # raphtory-arrow = { path = "raphtory-arrow-private", package = "raphtory-arrow-private" } async-graphql = { version = "7.0.5", features = ["dynamic-schema"] } async-graphql-poem = "7.0.5" -dynamic-graphql = { path = "/Users/haaroony/Documents/dev/dynamic-graphql" } +dynamic-graphql = "0.9.0" reqwest = { version = "0.11", default-features = false, features = ["rustls-tls", "json"] } serde = { version = "1.0", features = ["derive", "rc"] } serde_json = "1.0" diff --git a/raphtory-graphql/readme.md b/raphtory-graphql/readme.md new file mode 100644 index 0000000000..b94cbc0306 --- /dev/null +++ b/raphtory-graphql/readme.md @@ -0,0 +1,108 @@ + +# Raphtory-GraphQL + +## Overview + +Raphtory-GraphQL is part of the Raphtory project, an in-memory vectorized graph database designed for high performance and scalability. This module provides GraphQL support for Raphtory, allowing users to interact with their graph data through GraphQL queries. + +## Features + +- **In-Memory Graph Database:** Offers high-speed data processing and querying capabilities. +- **GraphQL Integration:** Allows seamless integration of graph data with web applications through a GraphQL API. +- **Authentication Support:** Includes options to run the server with authentication, ensuring secure access to the graph data. + +## Installation + +Clone the repository and navigate to the `raphtory-graphql` directory: +```bash +git clone https://github.com/Pometry/Raphtory.git +cd Raphtory/raphtory-graphql +``` + +## Configuration + +Ensure you have the required environment variables set up. For example, set the `GRAPH_DIRECTORY` environment variable: +```bash +export GRAPH_DIRECTORY=/path/to/your/graph_directory +``` + +Create a `config.toml` file with your specific configuration settings. + +## Running the Server + +By default, the server runs without authentication. To run the server, use the following command: +```bash +cargo run +``` + +This command starts the Raphtory server using `from_directory.run`. + +## Running the Server with Authentication (Microsoft) + +### Setting up Authentication + +To enable authentication for the Raphtory-GraphQL server, you need to set up a `.env` file with specific properties from Microsoft. This file should include the following properties: + +- `CLIENT_ID` +- `CLIENT_SECRET` +- `TENANT_ID` +- `AUTHORITY` + +#### Steps + +1. **Azure Portal Registration:** + - Go to the [Azure Portal](https://portal.azure.com/). + - Navigate to "Azure Active Directory" in the left-hand menu. + +2. **Register a New Application:** + - Click on "App registrations" and then "New registration." + - Enter a name for your application. + - Select the supported account types (typically "Accounts in this organizational directory only"). + - Click "Register." + +3. **Get the Client ID and Tenant ID:** + - After registration, you will be taken to the application's overview page. + - Copy the `Application (client) ID` and `Directory (tenant) ID` values. These are your `CLIENT_ID` and `TENANT_ID`, respectively. + +4. **Create a Client Secret:** + - In the left-hand menu, select "Certificates & secrets." + - Click on "New client secret." + - Provide a description and set an expiry period. + - Click "Add." + - Copy the value of the client secret. This is your `CLIENT_SECRET`. + +5. **Set the Authority:** + - The `AUTHORITY` is typically in the format `https://login.microsoftonline.com/{TENANT_ID}`. + +#### Example .env File + +Create a `.env` file in the root directory of your project and add the obtained properties: + +```env +CLIENT_ID=your_client_id +CLIENT_SECRET=your_client_secret +TENANT_ID=your_tenant_id +AUTHORITY=https://login.microsoftonline.com/your_tenant_id +``` + +Ensure that this file is included in your `.gitignore` to prevent sensitive information from being exposed. + +With these settings configured, your Raphtory-GraphQL server will be able to use Microsoft authentication. + +### Running the Auth server + +To run the server with authentication, pass the `--server` argument: +```bash +cargo run -- --server +``` + +This command starts the Raphtory server using `run_with_auth`, which includes authentication mechanisms to secure access. + + + + +## Conclusion + +Raphtory-GraphQL offers powerful graph data management capabilities with the added benefit of GraphQL integration and optional authentication. By following the steps above, you can set up and run your Raphtory server to manage and query your graph data efficiently. + +For more detailed information and examples, you can visit the [Raphtory GitHub repository](https://github.com/Pometry/Raphtory/tree/feature/gql-auth/raphtory-graphql). diff --git a/raphtory-graphql/src/azure_auth/common.rs b/raphtory-graphql/src/azure_auth/common.rs index 89b9216128..bad814cce1 100644 --- a/raphtory-graphql/src/azure_auth/common.rs +++ b/raphtory-graphql/src/azure_auth/common.rs @@ -92,9 +92,7 @@ pub async fn auth_callback( query: Query, jar: &CookieJar, ) -> impl IntoResponse { - println!("Running callback"); if let Some(session_cookie) = jar.get("session_id") { - println!("Got session ID"); let session_id = session_cookie.value::().unwrap(); let code = AuthorizationCode::new(query.0.code.clone()); @@ -104,7 +102,7 @@ pub async fn auth_callback( .unwrap() .remove(&session_id) .unwrap(); - println!("Getting token result"); + let token_result = data .oauth_client .exchange_code(code) @@ -114,29 +112,25 @@ pub async fn auth_callback( match token_result { Ok(token) => { - println!("Getting access token"); let access_token = token.access_token(); let expires_in = token .expires_in() .unwrap_or(core::time::Duration::from_secs(60 * 60 * 24)); let expiration = Utc::now() + Duration::from_std(expires_in).unwrap(); - println!("Set token data"); + let token_data = json!({ "access_token_secret": access_token.secret(), "expires_at": expiration.to_rfc3339() }); - println!("Makign auth cookie"); let mut auth_cookie = Cookie::new("auth_token", token_data.to_string()); auth_cookie.set_expires(expiration); auth_cookie.set_path("/"); auth_cookie.set_http_only(true); - println!("Storing in jar"); jar.add(auth_cookie); return Redirect::temporary("/").into_response(); } Err(_err) => { - println!("Token error"); return Response::builder() .status(StatusCode::UNAUTHORIZED) .content_type("application/json") @@ -144,7 +138,6 @@ pub async fn auth_callback( } } } else { - println!("Session not found. Please login again."); return Response::builder() .status(StatusCode::UNAUTHORIZED) .content_type("application/json") @@ -159,7 +152,6 @@ pub fn decode_base64_urlsafe(base64_str: &str) -> Result, Box #[handler] pub async fn secure_endpoint() -> Json { - println!("Entering secure endpoint"); // Debugging line Json(serde_json::json!({ "message": "Secured" })) @@ -180,16 +172,11 @@ pub async fn logout(jar: &CookieJar) -> String { #[handler] pub async fn verify(data: Data<&AppState>, jar: &CookieJar) -> Json { - println!("Checking for session_id"); if let Some(_session_cookie) = jar.get("session_id") { - println!("Checking for auth_token"); if let Some(cookie) = jar.get("auth_token") { - println!("Getting cookie value"); let cookie_value = cookie.value::().expect("Unable to find cookie"); - println!("Getting token data"); let token_data: serde_json::Value = serde_json::from_str(&cookie_value).expect("Invalid cookie format"); - println!("Getting secrets"); let token = token_data["access_token_secret"] .as_str() .expect("No access token found"); @@ -199,13 +186,13 @@ pub async fn verify(data: Data<&AppState>, jar: &CookieJar) -> Json expires_at { return Json(serde_json::json!({ "message": "Access token expired, please login again" })); } - println!("Getting headers, kid and jwk"); + let header = decode_header(token).expect("Unable to decode header"); let kid = header.kid.expect("Token header does not have a kid field"); @@ -215,42 +202,35 @@ pub async fn verify(data: Data<&AppState>, jar: &CookieJar) -> Json>(token, &decoding_key, &validation); match token_data { - Ok(dc) => { - println!("valid token"); - println!("{:?}", dc); + Ok(_dc) => { Json(serde_json::json!({ "message": "Valid access token", })) } - Err(err) => { - println!("{:?} Cant authorise token.", err); // Debugging line - println!("cookie_value : {:?}", cookie_value); - println!("token : {:?}", token); + Err(_err) => { Json(serde_json::json!({ "message": "No valid auth token found", })) } } } else { - println!("No valid auth token found in cookies, please login."); // Debugging line Json(serde_json::json!({ "message": "No cookie auth_token found, please login" })) } } else { - println!("No session_id found, please login."); // Debugging line Json(serde_json::json!({ "message": "No session_id found, please login" })) diff --git a/raphtory-graphql/src/azure_auth/token_middleware.rs b/raphtory-graphql/src/azure_auth/token_middleware.rs index a4b9730742..f5366c3407 100644 --- a/raphtory-graphql/src/azure_auth/token_middleware.rs +++ b/raphtory-graphql/src/azure_auth/token_middleware.rs @@ -41,42 +41,33 @@ impl Endpoint for TokenMiddlewareImpl { type Output = Response; async fn call(&self, mut req: Request) -> Result { - println!("Running call"); let jar = req.cookie().clone(); - println!("Checking for session_id"); if let Some(_session_cookie) = jar.get("session_id") { - println!("Checking for auth_token"); if let Some(auth_cookie) = jar.get("auth_token") { - println!("Gotten cookie, decoding data"); let token_data: serde_json::Value = serde_json::from_str( &auth_cookie .value::() .expect("Unable to find cookie"), ) .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Getting access token"); let access_token = token_data["access_token_secret"] .as_str() .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Expiry"); let expires_at_str = token_data["expires_at"] .as_str() .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Gotten access token"); let expires_at = chrono::DateTime::parse_from_rfc3339(expires_at_str) .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; if chrono::Utc::now() > expires_at { return Err(Error::from_status(StatusCode::UNAUTHORIZED)); } - println!("Getting headers and kit"); let header = decode_header(access_token) .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; let kid = header .kid .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Finding jwk"); let jwk = self .app_state .jwks @@ -85,16 +76,13 @@ impl Endpoint for TokenMiddlewareImpl { .find(|&jwk| jwk.kid == kid) .ok_or_else(|| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Decoding n and e"); let n = decode_base64_urlsafe(&jwk.n) .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; let e = decode_base64_urlsafe(&jwk.e) .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Decoding key"); let decoding_key = DecodingKey::from_rsa_raw_components(&n, &e); - println!("validating"); let validation = Validation::new(Algorithm::RS256); decode::>( access_token, @@ -103,17 +91,12 @@ impl Endpoint for TokenMiddlewareImpl { ) .map_err(|_| Error::from_status(StatusCode::UNAUTHORIZED))?; - println!("Inserting token into request"); - // Insert token data to extensions of request. req.extensions_mut().insert(Token(access_token.to_string())); - println!("Token is valid, proceed with the request"); return self.ep.call(req).await.map(IntoResponse::into_response); } - println!("unable to find auth_token cookie"); Ok(Redirect::temporary("/login").into_response()) } else { - println!("Unable to find session_id cookie"); Ok(Redirect::temporary("/login").into_response()) } } diff --git a/raphtory-graphql/src/main.rs b/raphtory-graphql/src/main.rs index 2e172cbbc2..be7ff95714 100644 --- a/raphtory-graphql/src/main.rs +++ b/raphtory-graphql/src/main.rs @@ -15,13 +15,18 @@ async fn main() { let graph_directory = env::var("GRAPH_DIRECTORY").unwrap_or("/tmp/graphs".to_string()); let config_path = "config.toml"; - // RaphtoryServer::from_directory(&graph_directory) - // .run(config_path, false) - // .await - // .unwrap() + let args: Vec = env::args().collect(); + let use_auth = args.contains(&"--server".to_string()); - RaphtoryServer::from_directory(&graph_directory) - .run_with_auth(config_path, false) - .await - .unwrap() + if use_auth { + RaphtoryServer::from_directory(&graph_directory) + .run_with_auth(config_path, false) + .await + .unwrap(); + } else { + RaphtoryServer::from_directory(&graph_directory) + .run(config_path, false) + .await + .unwrap(); + } } From 3c739ef265ecf02cd91b0853a91b3a33b3fe26f8 Mon Sep 17 00:00:00 2001 From: Haaroon Yousaf Date: Mon, 3 Jun 2024 17:41:40 +0100 Subject: [PATCH 3/6] fmt --- raphtory-graphql/src/azure_auth/common.rs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/raphtory-graphql/src/azure_auth/common.rs b/raphtory-graphql/src/azure_auth/common.rs index bad814cce1..28050797f4 100644 --- a/raphtory-graphql/src/azure_auth/common.rs +++ b/raphtory-graphql/src/azure_auth/common.rs @@ -214,16 +214,12 @@ pub async fn verify(data: Data<&AppState>, jar: &CookieJar) -> Json>(token, &decoding_key, &validation); match token_data { - Ok(_dc) => { - Json(serde_json::json!({ - "message": "Valid access token", - })) - } - Err(_err) => { - Json(serde_json::json!({ - "message": "No valid auth token found", - })) - } + Ok(_dc) => Json(serde_json::json!({ + "message": "Valid access token", + })), + Err(_err) => Json(serde_json::json!({ + "message": "No valid auth token found", + })), } } else { Json(serde_json::json!({ From cd5444dd12513eb3899e5b1884021d153fa69daa Mon Sep 17 00:00:00 2001 From: Haaroon Y Date: Mon, 3 Jun 2024 17:56:50 +0100 Subject: [PATCH 4/6] Update readme.md --- raphtory-graphql/readme.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/raphtory-graphql/readme.md b/raphtory-graphql/readme.md index b94cbc0306..d2b82941f6 100644 --- a/raphtory-graphql/readme.md +++ b/raphtory-graphql/readme.md @@ -97,12 +97,3 @@ cargo run -- --server ``` This command starts the Raphtory server using `run_with_auth`, which includes authentication mechanisms to secure access. - - - - -## Conclusion - -Raphtory-GraphQL offers powerful graph data management capabilities with the added benefit of GraphQL integration and optional authentication. By following the steps above, you can set up and run your Raphtory server to manage and query your graph data efficiently. - -For more detailed information and examples, you can visit the [Raphtory GitHub repository](https://github.com/Pometry/Raphtory/tree/feature/gql-auth/raphtory-graphql). From aff0c1c4ba24fa0c76384bf4ef4fe602751c8787 Mon Sep 17 00:00:00 2001 From: Haaroon Yousaf Date: Mon, 3 Jun 2024 18:09:13 +0100 Subject: [PATCH 5/6] fix issues with tests --- Cargo.lock | 1 - python/src/graphql.rs | 17 +++++++++++------ raphtory-graphql/src/server.rs | 7 +------ 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 34a477c9bf..3e85224e46 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3842,7 +3842,6 @@ dependencies = [ "polars-error", "polars-utils", "ryu", - "serde", "simdutf8", "streaming-iterator", "strength_reduce", diff --git a/python/src/graphql.rs b/python/src/graphql.rs index c3aabfb205..cb5714a122 100644 --- a/python/src/graphql.rs +++ b/python/src/graphql.rs @@ -343,12 +343,13 @@ impl PyRaphtoryServer { /// /// Arguments: /// * `port`: the port to use (defaults to 1736). - #[pyo3(signature = (port = 1736, log_level="INFO".to_string(),enable_tracing=false))] + #[pyo3(signature = (port = 1736, log_level="INFO".to_string(),enable_tracing=false,enable_auth=false))] pub fn start( slf: PyRefMut, port: u16, log_level: String, enable_tracing: bool, + enable_auth: bool, ) -> PyResult { let (sender, receiver) = crossbeam_channel::bounded::(1); let server = take_server_ownership(slf)?; @@ -361,8 +362,10 @@ impl PyRaphtoryServer { .build() .unwrap() .block_on(async move { - let handler = server.start_with_port(port, &log_level, enable_tracing); - let tokio_sender = handler._get_sender().clone(); + let handler = + server.start_with_port(port, &log_level, enable_tracing, enable_auth); + let running_server = handler.await; + let tokio_sender = running_server._get_sender().clone(); tokio::task::spawn_blocking(move || { match receiver.recv().expect("Failed to wait for cancellation") { BridgeCommand::StopServer => tokio_sender @@ -371,7 +374,7 @@ impl PyRaphtoryServer { BridgeCommand::StopListening => (), } }); - let result = handler.wait().await; + let result = running_server.wait().await; _ = cloned_sender.send(BridgeCommand::StopListening); result }) @@ -384,15 +387,17 @@ impl PyRaphtoryServer { /// /// Arguments: /// * `port`: the port to use (defaults to 1736). - #[pyo3(signature = (port = 1736, log_level="INFO".to_string(),enable_tracing=false))] + #[pyo3(signature = (port = 1736, log_level="INFO".to_string(),enable_tracing=false,enable_auth=false))] pub fn run( slf: PyRefMut, py: Python, port: u16, log_level: String, enable_tracing: bool, + enable_auth: bool, ) -> PyResult<()> { - let mut server = Self::start(slf, port, log_level, enable_tracing)?.server_handler; + let mut server = + Self::start(slf, port, log_level, enable_tracing, enable_auth)?.server_handler; py.allow_threads(|| wait_server(&mut server)) } } diff --git a/raphtory-graphql/src/server.rs b/raphtory-graphql/src/server.rs index cafffa4261..73e9639b5e 100644 --- a/raphtory-graphql/src/server.rs +++ b/raphtory-graphql/src/server.rs @@ -356,10 +356,6 @@ impl RaphtoryServer { .data(app_state.clone()) .with(token_middleware.clone()), ) - // .at( - // "/secure_endpoint", - // secure_endpoint.with(token_middleware.clone()), - // ) .at("/logout", logout.with(token_middleware.clone())) .with(CookieJarManager::new()) .with(Cors::new()); @@ -487,7 +483,6 @@ mod server_tests { let handler = server.start_with_port(0, "info", false, false); sleep(Duration::from_secs(1)).await; println!("Calling stop at time {}", Local::now()); - handler.await.stop().await; - handler.await.wait().await.unwrap() + handler.await.stop().await } } From 8c4f55980f4048851223d76586127be53f9f84e0 Mon Sep 17 00:00:00 2001 From: Haaroon Yousaf Date: Tue, 4 Jun 2024 09:47:13 +0100 Subject: [PATCH 6/6] fix cargo files after bad merge --- Cargo.lock | 2 ++ Cargo.toml | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d89f42df75..a748d61762 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3924,6 +3924,8 @@ dependencies = [ "opaque-debug", "universal-hash", ] + +[[package]] name = "pometry-storage" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 8f9cff0654..8d8916160f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,10 +30,10 @@ inherits = "release" debug = true [workspace.dependencies] -#[public-arrow] -raphtory-arrow = { version = "0.8.1", path = "raphtory-arrow" } -#[private-arrow] -# raphtory-arrow = { path = "raphtory-arrow-private", package = "raphtory-arrow-private" } +#[public-storage] +pometry-storage = { version = "0.8.1", path = "pometry-storage" } +#[private-storage] +# pometry-storage = { path = "pometry-storage-private", package = "pometry-storage-private" } async-graphql = { version = "7.0.5", features = ["dynamic-schema"] } async-graphql-poem = "7.0.5" dynamic-graphql = "0.9.0"