diff --git a/.vscode/settings.json b/.vscode/settings.json index 21cd088..ce8c14c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,7 @@ "protoc", "protos", "repr", + "reqwest", "rtss", "sqlx", "sysinfo", diff --git a/Cargo.lock b/Cargo.lock index 4cffc9c..3b3eb42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -288,6 +288,12 @@ dependencies = [ "num-traits", ] +[[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" @@ -776,6 +782,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -1072,6 +1088,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 = "form_urlencoded" version = "1.2.1" @@ -1213,6 +1244,25 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "handlebars" version = "5.1.2" @@ -1377,6 +1427,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", + "h2", "http", "http-body", "httparse", @@ -1385,6 +1436,40 @@ dependencies = [ "pin-project-lite", "smallvec", "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] @@ -1394,12 +1479,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" dependencies = [ "bytes", + "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", + "socket2", "tokio", + "tower", + "tower-service", + "tracing", ] [[package]] @@ -1452,6 +1542,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ipnet" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1649,6 +1745,23 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "7.1.3" @@ -1746,6 +1859,50 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "openssl" +version = "0.10.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +dependencies = [ + "bitflags 2.6.0", + "cfg-if", + "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", +] + +[[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.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "ordered-multimap" version = "0.6.0" @@ -2143,6 +2300,64 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "ron" version = "0.8.1" @@ -2187,6 +2402,7 @@ dependencies = [ "base64 0.22.1", "bevy_ecs", "chrono", + "reqwest", "rtss_db", "rtss_dto", "rtss_log", @@ -2318,6 +2534,46 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.23.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.17" @@ -2330,12 +2586,44 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schannel" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "serde" version = "1.0.210" @@ -2787,6 +3075,9 @@ name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] [[package]] name = "sysinfo" @@ -2802,6 +3093,27 @@ dependencies = [ "windows", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" version = "3.12.0" @@ -2896,6 +3208,27 @@ dependencies = [ "syn", ] +[[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.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.15" @@ -3093,6 +3426,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tungstenite" version = "0.21.0" @@ -3169,6 +3508,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.2" @@ -3219,6 +3564,15 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -3376,7 +3730,7 @@ checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" dependencies = [ "windows-implement", "windows-interface", - "windows-result", + "windows-result 0.1.2", "windows-targets 0.52.6", ] @@ -3402,6 +3756,17 @@ dependencies = [ "syn", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -3411,6 +3776,25 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result 0.2.0", + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/conf/default.toml b/conf/default.toml index 7ba1be5..58b6188 100644 --- a/conf/default.toml +++ b/conf/default.toml @@ -5,3 +5,9 @@ port = 8765 [log] level = "debug" + +[sso] +base_url = "http://localhost:8080" +login_url = "/api/login" +logout_url = "/api/login/logout" +user_info_url = "/api/login/getUserInfo" diff --git a/conf/dev.toml b/conf/dev.toml index 3f2297a..5490cc1 100644 --- a/conf/dev.toml +++ b/conf/dev.toml @@ -1,2 +1,5 @@ [database] url = "postgresql://joylink:Joylink@0503@localhost:5432/joylink" + +[sso] +base_url = "https://joylink.club/jlcloud" diff --git a/conf/local_test.toml b/conf/local_test.toml index 3d01135..c876abf 100644 --- a/conf/local_test.toml +++ b/conf/local_test.toml @@ -2,4 +2,7 @@ url = "postgresql://joylink:Joylink@0503@10.11.11.2:5432/joylink" [log] -level = "info" +level = "debug" + +[sso] +base_url = "http://192.168.33.233/rtss-server" diff --git a/crates/rtss_api/Cargo.toml b/crates/rtss_api/Cargo.toml index 4ffd1ef..a4d77fb 100644 --- a/crates/rtss_api/Cargo.toml +++ b/crates/rtss_api/Cargo.toml @@ -17,6 +17,7 @@ async-graphql = { version = "7.0.7", features = ["chrono", "dataloader"] } async-graphql-axum = "7.0.6" base64 = "0.22.1" sysinfo = "0.31.3" +reqwest = { version = "0.12.7", features = ["json"] } bevy_ecs = { workspace = true } rtss_log = { path = "../rtss_log" } diff --git a/crates/rtss_api/src/apis/draft_data.rs b/crates/rtss_api/src/apis/draft_data.rs index 7175ec0..921ec22 100644 --- a/crates/rtss_api/src/apis/draft_data.rs +++ b/crates/rtss_api/src/apis/draft_data.rs @@ -8,11 +8,13 @@ use rtss_db::RtssDbAccessor; use rtss_dto::common::DataType; use serde_json::Value; -use crate::apis::PageQueryDto; +use crate::apis::{PageDto, PageQueryDto}; use super::common::{DataOptions, IscsDataOptions}; use super::release_data::ReleaseDataId; -use super::{PageDto, RtssDbLoader}; +use crate::RtssDbLoader; + +use crate::user_auth::{Role, RoleGuard, UserInfoDto}; #[derive(Default)] pub struct DraftDataQuery; @@ -23,6 +25,7 @@ pub struct DraftDataMutation; #[Object] impl DraftDataQuery { /// 分页查询所有草稿数据(系统管理用) + #[graphql(guard = "RoleGuard::new(Role::Admin)")] async fn draft_data_paging<'ctx>( &self, ctx: &Context<'ctx>, @@ -36,20 +39,24 @@ impl DraftDataQuery { Ok(paging_result.into()) } /// 分页查询用户的草稿ISCS数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn user_draft_iscs_data_paging<'ctx>( &self, ctx: &Context<'ctx>, paging: PageQueryDto, mut query: UserDraftDataFilterDto, ) -> async_graphql::Result> { - let db_accessor = ctx.data::()?; + let user = ctx.data::()?; + query.user_id = user.id_i32(); query.data_type = Some(DataType::Iscs); + let db_accessor = ctx.data::()?; let paging_result = db_accessor .query_draft_data(query.into(), paging.into()) .await?; Ok(paging_result.into()) } /// 分页查询共享的草稿ISCS数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn shared_draft_iscs_data_paging<'ctx>( &self, ctx: &Context<'ctx>, @@ -64,18 +71,21 @@ impl DraftDataQuery { Ok(paging_result.into()) } /// 根据id获取草稿数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn draft_data(&self, ctx: &Context<'_>, id: i32) -> async_graphql::Result { let db_accessor = ctx.data::()?; let draft_data = db_accessor.query_draft_data_by_id(id).await?; Ok(draft_data.into()) } /// 查询是否已经存在同一用户下的同名草稿数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn draft_data_exist( &self, ctx: &Context<'_>, - user_id: i32, name: String, ) -> async_graphql::Result { + let user = ctx.data::()?; + let user_id = user.id_i32(); let db_accessor = ctx.data::()?; let exist = db_accessor.is_draft_data_exist(user_id, &name).await?; Ok(exist) @@ -85,16 +95,20 @@ impl DraftDataQuery { #[Object] impl DraftDataMutation { /// 创建草稿ISCS数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn create_draft_iscs_data( &self, ctx: &Context<'_>, - input: CreateDraftDataDto, + mut input: CreateDraftDataDto, ) -> async_graphql::Result { + let user = ctx.data::()?; + input = input.with_user_id(user.id_i32()); let db_accessor = ctx.data::()?; let draft_data = db_accessor.create_draft_data(input.into()).await?; Ok(draft_data.into()) } /// 更新草稿数据name + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn update_draft_data_name( &self, ctx: &Context<'_>, @@ -107,6 +121,7 @@ impl DraftDataMutation { } /// 更新草稿数据data /// data为base64编码的字符串 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn update_draft_data_data( &self, ctx: &Context<'_>, @@ -121,6 +136,7 @@ impl DraftDataMutation { Ok(draft_data.into()) } /// 更新草稿数据共享状态 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn update_draft_data_shared( &self, ctx: &Context<'_>, @@ -132,6 +148,7 @@ impl DraftDataMutation { Ok(draft_data.into()) } /// 删除草稿数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn delete_draft_data( &self, ctx: &Context<'_>, @@ -142,6 +159,7 @@ impl DraftDataMutation { Ok(true) } /// 设置草稿数据的默认发布数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn set_default_release_data_id( &self, ctx: &Context<'_>, @@ -155,13 +173,15 @@ impl DraftDataMutation { Ok(draft_data.into()) } /// 草稿数据另存为 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn save_as_new_draft_data( &self, ctx: &Context<'_>, id: i32, name: String, - user_id: i32, ) -> async_graphql::Result { + let user = ctx.data::()?; + let user_id = user.id_i32(); let db_accessor = ctx.data::()?; let draft_data = db_accessor.save_as_new_draft(id, &name, user_id).await?; Ok(draft_data.into()) @@ -173,12 +193,24 @@ impl DraftDataMutation { pub struct CreateDraftDataDto { pub name: String, pub options: Option, - pub user_id: i32, + #[graphql(skip)] + pub user_id: Option, +} + +impl CreateDraftDataDto { + pub fn with_user_id(mut self, id: i32) -> Self { + self.user_id = Some(id); + self + } } impl From> for rtss_db::CreateDraftData { fn from(value: CreateDraftDataDto) -> Self { - let cdd = Self::new(&value.name, DataType::Iscs, value.user_id); + let cdd = Self::new( + &value.name, + DataType::Iscs, + value.user_id.expect("CreateDraftDataDto need user_id"), + ); if value.options.is_some() { cdd.with_options(serde_json::to_value(value.options).unwrap()) } else { diff --git a/crates/rtss_api/src/apis/mod.rs b/crates/rtss_api/src/apis/mod.rs index 23b4e19..154b39b 100644 --- a/crates/rtss_api/src/apis/mod.rs +++ b/crates/rtss_api/src/apis/mod.rs @@ -1,11 +1,10 @@ -use async_graphql::dataloader::DataLoader; -use async_graphql::{EmptySubscription, MergedObject, Schema}; -use async_graphql::{Enum, InputObject, OutputType, SimpleObject}; +use async_graphql::{Enum, InputObject, MergedObject, OutputType, SimpleObject}; use draft_data::{DraftDataMutation, DraftDataQuery}; use release_data::{ReleaseDataMutation, ReleaseDataQuery}; -use crate::simulation_definition::MutexSimulationManager; -use crate::ServerConfig; +mod simulation_definition; +mod sys_info; +use simulation_definition::*; mod common; mod draft_data; @@ -74,25 +73,3 @@ impl> From> for PageDto ) } } - -pub struct RtssDbLoader { - pub(crate) db_accessor: rtss_db::RtssDbAccessor, -} - -impl RtssDbLoader { - pub fn new(db_accessor: rtss_db::RtssDbAccessor) -> Self { - Self { db_accessor } - } -} - -pub type RtssAppSchema = Schema; - -pub async fn new_schema(config: &ServerConfig) -> RtssAppSchema { - let dba = rtss_db::get_db_accessor(&config.database_url).await; - let loader = RtssDbLoader::new(dba.clone()); - Schema::build(Query::default(), Mutation::default(), EmptySubscription) - .data(dba) - .data(DataLoader::new(loader, tokio::spawn)) - .data(MutexSimulationManager::default()) - .finish() -} diff --git a/crates/rtss_api/src/apis/release_data.rs b/crates/rtss_api/src/apis/release_data.rs index 0aa82fc..32c53d6 100644 --- a/crates/rtss_api/src/apis/release_data.rs +++ b/crates/rtss_api/src/apis/release_data.rs @@ -12,9 +12,12 @@ use rtss_dto::common::DataType; use serde_json::Value; use crate::apis::draft_data::DraftDataDto; +use crate::RtssDbLoader; use super::common::{DataOptions, IscsDataOptions}; -use super::{PageDto, PageQueryDto, RtssDbLoader}; +use super::{PageDto, PageQueryDto}; + +use crate::user_auth::{Role, RoleGuard, UserInfoDto}; #[derive(Default)] pub struct ReleaseDataQuery; @@ -25,6 +28,7 @@ pub struct ReleaseDataMutation; #[Object] impl ReleaseDataQuery { /// 分页查询所有发布数据(系统管理用) + #[graphql(guard = "RoleGuard::new(Role::Admin)")] async fn release_data_paging( &self, ctx: &Context<'_>, @@ -39,6 +43,7 @@ impl ReleaseDataQuery { } /// 分页查询发布的ISCS数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn release_iscs_data_paging( &self, ctx: &Context<'_>, @@ -54,6 +59,7 @@ impl ReleaseDataQuery { } /// id查询发布数据及当前使用的版本数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn release_data( &self, ctx: &Context<'_>, @@ -65,6 +71,7 @@ impl ReleaseDataQuery { } /// 是否已经存在相同name的发布数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn is_release_data_name_exists( &self, ctx: &Context<'_>, @@ -79,6 +86,7 @@ impl ReleaseDataQuery { } /// 查询发布数据的版本 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn release_data_version_paging( &self, ctx: &Context<'_>, @@ -93,6 +101,7 @@ impl ReleaseDataQuery { } /// 根据id获取发布数据版本详情 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn release_data_version( &self, ctx: &Context<'_>, @@ -109,6 +118,7 @@ impl ReleaseDataQuery { #[Object] impl ReleaseDataMutation { /// 发布到新的发布数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn release_new_from_draft( &self, ctx: &Context<'_>, @@ -116,28 +126,34 @@ impl ReleaseDataMutation { name: String, description: String, ) -> async_graphql::Result { + let user = ctx.data::()?; + let user_id = user.id_i32(); let db_accessor = ctx.data::()?; let result = db_accessor - .release_new_from_draft(draft_id, &name, &description) + .release_new_from_draft(draft_id, &name, &description, Some(user_id)) .await?; Ok(result.into()) } /// 发布到默认发布数据,需要草稿数据发布过或设置了默认发布数据id + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn release_to_default_release_data( &self, ctx: &Context<'_>, draft_id: i32, description: String, ) -> async_graphql::Result { + let user = ctx.data::()?; + let user_id = user.id_i32(); let db_accessor = ctx.data::()?; let result = db_accessor - .release_to_existing(draft_id, &description) + .release_to_existing(draft_id, &description, Some(user_id)) .await?; Ok(result.into()) } /// 更新发布数据name + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn update_release_data_name( &self, ctx: &Context<'_>, @@ -150,6 +166,7 @@ impl ReleaseDataMutation { } /// 上下架发布数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn update_release_data_published( &self, ctx: &Context<'_>, @@ -164,6 +181,7 @@ impl ReleaseDataMutation { } /// 更新发布数据使用的版本 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn update_release_data_used_version( &self, ctx: &Context<'_>, @@ -178,12 +196,14 @@ impl ReleaseDataMutation { } /// 从发布数据版本中创建草稿数据 + #[graphql(guard = "RoleGuard::new(Role::User)")] async fn create_draft_data_from_release_data_version( &self, ctx: &Context<'_>, version_id: i32, - user_id: i32, ) -> async_graphql::Result { + let user = ctx.data::()?; + let user_id = user.id_i32(); let db_accessor = ctx.data::()?; let result = db_accessor .create_draft_from_release_version(version_id, user_id) diff --git a/crates/rtss_api/src/apis/simulation.rs b/crates/rtss_api/src/apis/simulation.rs index 8d7a0f8..7dfd557 100644 --- a/crates/rtss_api/src/apis/simulation.rs +++ b/crates/rtss_api/src/apis/simulation.rs @@ -1,7 +1,7 @@ use async_graphql::{Context, InputObject, Object}; use rtss_sim_manage::{AvailablePlugins, SimulationBuilder}; -use crate::simulation_definition::{MutexSimulationManager, SimulationOperation}; +use super::{MutexSimulationManager, SimulationOperation}; #[derive(Default)] pub struct SimulationQuery; diff --git a/crates/rtss_api/src/simulation_definition.rs b/crates/rtss_api/src/apis/simulation_definition.rs similarity index 100% rename from crates/rtss_api/src/simulation_definition.rs rename to crates/rtss_api/src/apis/simulation_definition.rs diff --git a/crates/rtss_api/src/sys_info.rs b/crates/rtss_api/src/apis/sys_info.rs similarity index 100% rename from crates/rtss_api/src/sys_info.rs rename to crates/rtss_api/src/apis/sys_info.rs diff --git a/crates/rtss_api/src/lib.rs b/crates/rtss_api/src/lib.rs index 3f8d1c4..9f9be01 100644 --- a/crates/rtss_api/src/lib.rs +++ b/crates/rtss_api/src/lib.rs @@ -1,7 +1,6 @@ // mod jwt_auth; mod apis; mod server; -mod simulation_definition; -mod sys_info; +mod user_auth; pub use server::*; diff --git a/crates/rtss_api/src/server.rs b/crates/rtss_api/src/server.rs index 9199104..f861e6b 100644 --- a/crates/rtss_api/src/server.rs +++ b/crates/rtss_api/src/server.rs @@ -8,16 +8,22 @@ use axum::{ routing::get, Router, }; +use dataloader::DataLoader; use http::{playground_source, GraphQLPlaygroundConfig}; use rtss_log::tracing::{debug, info}; use tokio::net::TcpListener; use tower_http::cors::CorsLayer; -use crate::apis::RtssAppSchema; +use crate::apis::{Mutation, Query}; +use crate::user_auth; +pub use crate::user_auth::UserAuthClient; + +#[derive(Clone)] pub struct ServerConfig { pub database_url: String, pub port: u16, + pub user_auth_client: Option, } impl ServerConfig { @@ -25,16 +31,22 @@ impl ServerConfig { Self { database_url: database_url.to_string(), port, + user_auth_client: None, } } + pub fn with_user_auth_client(mut self, user_auth_client: user_auth::UserAuthClient) -> Self { + self.user_auth_client = Some(user_auth_client); + self + } + pub fn to_socket_addr(&self) -> String { format!("0.0.0.0:{}", self.port) } } pub async fn serve(config: ServerConfig) -> anyhow::Result<()> { - let schema = crate::apis::new_schema(&config).await; + let schema = new_schema(config.clone()).await; let app = Router::new() .route("/", get(graphiql).post(graphql_handler)) @@ -58,23 +70,45 @@ pub async fn serve(config: ServerConfig) -> anyhow::Result<()> { async fn graphql_handler( State(schema): State, - _headers: HeaderMap, + headers: HeaderMap, req: GraphQLRequest, ) -> GraphQLResponse { - let req = req.into_inner(); - // let mut req = req.into_inner(); - // let token = jwt_auth::get_token_from_headers(headers); - // match token { - // Ok(token) => { - // req = req.data(token); - // } - // Err(e) => { - // error!("Error getting token from headers: {:?}", e); - // } - // } + let mut req = req.into_inner(); + let token = user_auth::get_token_from_headers(&headers); + if let Some(token) = token { + req = req.data(token); + } schema.execute(req).await.into() } async fn graphiql() -> impl IntoResponse { Html(playground_source(GraphQLPlaygroundConfig::new("/"))) } + +pub struct RtssDbLoader { + pub(crate) db_accessor: rtss_db::RtssDbAccessor, +} + +impl RtssDbLoader { + pub fn new(db_accessor: rtss_db::RtssDbAccessor) -> Self { + Self { db_accessor } + } +} + +pub type RtssAppSchema = Schema; + +pub async fn new_schema(config: ServerConfig) -> RtssAppSchema { + let user_info_cache = crate::user_auth::UserAuthCache::new( + config + .user_auth_client + .expect("user auth client not configured"), + ); + let dba = rtss_db::get_db_accessor(&config.database_url).await; + let loader = RtssDbLoader::new(dba.clone()); + Schema::build(Query::default(), Mutation::default(), EmptySubscription) + .data(user_info_cache) + .data(dba) + .data(DataLoader::new(loader, tokio::spawn)) + // .data(MutexSimulationManager::default()) + .finish() +} diff --git a/crates/rtss_api/src/user_auth/mod.rs b/crates/rtss_api/src/user_auth/mod.rs new file mode 100644 index 0000000..2ee3493 --- /dev/null +++ b/crates/rtss_api/src/user_auth/mod.rs @@ -0,0 +1,247 @@ +use std::{ + collections::HashMap, + sync::{Arc, Mutex}, +}; + +use async_graphql::Guard; +use axum::http::HeaderMap; +use rtss_log::tracing::error; +use serde::{Deserialize, Serialize}; + +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum Role { + Admin, + User, +} + +pub struct RoleGuard { + role: Role, +} + +impl RoleGuard { + pub fn new(role: Role) -> Self { + Self { role } + } +} + +impl Guard for RoleGuard { + async fn check(&self, ctx: &async_graphql::Context<'_>) -> async_graphql::Result<()> { + if let Some(token) = ctx.data_opt::() { + // 从ctx中获取UserAuthCache, 从cache中获取用户信息 + let user_auth_cache = ctx.data::().unwrap(); + let user_info = user_auth_cache.query_user(&token.0).await?; + // 判断用户角色 + if user_info.roles().contains(&self.role) { + return Ok(()); + } + } + Err(async_graphql::Error::new("Unauthorized")) + } +} + +pub struct UserAuthCache { + // TODO: 使用 LRU 等缓存策略,而不是简单的 HashMap + cache: Arc>>, + client: UserAuthClient, +} + +impl UserAuthCache { + pub fn new(user_auth_client: UserAuthClient) -> Self { + Self { + cache: Arc::new(Mutex::new(HashMap::with_capacity(100))), + client: user_auth_client, + } + } +} + +pub struct Token(pub String); + +pub fn get_token_from_headers(headers: &HeaderMap) -> Option { + headers + .get("Token") + .and_then(|token| token.to_str().map(|s| Token(s.to_string())).ok()) +} + +#[derive(Debug, Clone)] +pub struct UserAuthClient { + pub base_url: String, + pub login_url: String, + pub logout_url: String, + pub user_info_url: String, +} + +impl UserAuthClient { + #[allow(dead_code)] + async fn login(&self, login_info: LoginInfo) -> anyhow::Result { + let url = format!("{}{}", self.base_url, self.login_url); + let response = reqwest::Client::new() + .post(&url) + .json(&login_info) + .send() + .await?; + + let common = response.json::().await?; + + if common.code != 200 { + // 记录详细日志 + error!("Login failed with code {}: {}", common.code, common.message); + return Err(anyhow::anyhow!(common.message)); + } + + // 安全地处理 Option 类型 + match common.data { + Some(data) => { + if let Some(token_str) = data.as_str() { + Ok(token_str.to_string()) + } else { + // 记录详细日志 + error!("Data is not a string"); + Err(anyhow::anyhow!("Data is not a string")) + } + } + None => { + // 记录详细日志 + error!("No data returned"); + Err(anyhow::anyhow!("No data returned")) + } + } + } + + async fn query_user_info(&self, token: &str) -> anyhow::Result { + let url = format!("{}{}?token={token}", self.base_url, self.user_info_url); + let response = reqwest::get(url).await?; + let common = response.json::().await?; + if common.code != 200 { + return Err(anyhow::anyhow!(common.message)); + } + let user_info = serde_json::from_value(common.data.unwrap())?; + Ok(user_info) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LoginInfo { + pub account: String, + pub password: String, + #[serde(rename = "clientId")] + pub client_id: String, + pub secret: String, + pub project: String, +} + +impl Default for LoginInfo { + fn default() -> Self { + Self { + account: "17791995809".to_string(), + password: "e10adc3949ba59abbe56e057f20f883e".to_string(), + client_id: "1".to_string(), + secret: "joylink".to_string(), + project: "DEFAULT".to_string(), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CommonResponseDto { + pub code: i32, + pub message: String, + pub data: Option, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct UserInfoDto { + pub id: String, + pub name: Option, + pub nickname: Option, + pub roles: Vec, +} + +impl UserInfoDto { + pub fn id_i32(&self) -> i32 { + self.id + .parse::() + .expect("parse UserInfoDto.id to i32 failed") + } + + pub fn roles(&self) -> Vec { + self.roles + .iter() + .filter_map(|role| match role.as_str() { + "04" | "05" => Some(Role::Admin), + "01" | "03" => Some(Role::User), + _ => None, + }) + .collect() + } +} + +impl UserAuthCache { + #[allow(dead_code)] + pub fn len(&self) -> usize { + let cache = self.cache.lock().unwrap(); + cache.len() + } + + #[allow(dead_code)] + pub fn get_all(&self) -> HashMap { + let cache = self.cache.lock().unwrap(); + cache.clone() + } + + fn insert(&self, key: String, value: UserInfoDto) { + let mut cache = self.cache.lock().unwrap(); + cache.insert(key, value); + } + + fn get(&self, key: &str) -> Option { + let cache = self.cache.lock().unwrap(); + cache.get(key).cloned() + } + + pub async fn query_user(&self, token: &str) -> anyhow::Result { + if let Some(user_info) = self.get(token) { + Ok(user_info) + } else { + let user_info = self.client.query_user_info(token).await?; + self.insert(token.to_string(), user_info.clone()); + Ok(user_info) + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Ok; + use rtss_log::tracing::Level; + + use super::*; + + #[test] + fn test_login_info_serialize() { + let login_info = LoginInfo::default(); + let json = serde_json::to_string(&login_info).unwrap(); + println!("{}", json); + assert_eq!( + json, + r#"{"account":"17791995809","password":"e10adc3949ba59abbe56e057f20f883e","clientId":"1","secret":"joylink","project":"DEFAULT"}"# + ); + let login_info: LoginInfo = serde_json::from_str(&json).unwrap(); + assert_eq!(login_info.account, "17791995809"); + } + + #[tokio::test] + async fn test_user_auth_cache() -> anyhow::Result<()> { + rtss_log::Logging::default().with_level(Level::DEBUG).init(); + let cache = UserAuthCache::new(UserAuthClient { + base_url: "https://joylink.club/jlcloud".to_string(), + login_url: "/api/login".to_string(), + logout_url: "/api/login/logout".to_string(), + user_info_url: "/api/login/getUserInfo".to_string(), + }); + let token = cache.client.login(LoginInfo::default()).await?; + let user = cache.query_user(&token).await?; + println!("token: {}, {:?}", token, user); + assert_eq!(cache.len(), 1); + Ok(()) + } +} diff --git a/crates/rtss_db/src/db_access/release_data.rs b/crates/rtss_db/src/db_access/release_data.rs index 52dfc1b..778c65e 100644 --- a/crates/rtss_db/src/db_access/release_data.rs +++ b/crates/rtss_db/src/db_access/release_data.rs @@ -21,12 +21,14 @@ pub trait ReleaseDataAccessor { draft_id: i32, name: &str, description: &str, + user_id: Option, ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError>; /// 从草稿发布到已有草稿默认的release data的新version,并使用新version async fn release_to_existing( &self, draft_id: i32, description: &str, + user_id: Option, ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError>; /// 分页查询发布数据列表 async fn query_release_data_list( @@ -241,6 +243,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { draft_id: i32, name: &str, description: &str, + user_id: Option, ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError> { // 查询草稿数据 let draft = self.query_draft_data_by_id(draft_id).await?; @@ -272,7 +275,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { .bind(name) .bind(draft.data_type as i32) .bind(draft.options.clone()) - .bind(draft.user_id) + .bind(user_id.or(Some(draft.user_id))) .fetch_one(&mut *tx) .await?; // 创建发布数据版本 @@ -283,7 +286,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { options: draft.options.clone(), data: draft.data.unwrap(), description: description.to_string(), - user_id: draft.user_id, + user_id: user_id.unwrap_or(draft.user_id), }, &mut *tx, ) @@ -310,6 +313,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { &self, draft_id: i32, description: &str, + user_id: Option, ) -> Result<(ReleaseDataModel, ReleaseDataVersionModel), DbAccessError> { // 查询草稿数据 let draft = self.query_draft_data_by_id(draft_id).await?; @@ -336,7 +340,7 @@ impl ReleaseDataAccessor for RtssDbAccessor { options: draft.options.clone(), data: draft.data.unwrap(), description: description.to_string(), - user_id: draft.user_id, + user_id: user_id.unwrap_or(draft.user_id), }, &mut *tx, ) @@ -709,7 +713,9 @@ mod tests { let name = "test_release"; let description = "test release"; // 发布到默认发布数据 - let result = accessor.release_to_existing(draft.id, description).await; + let result = accessor + .release_to_existing(draft.id, description, None) + .await; assert!(result.is_err()); if let Some(e) = result.err() { match e { @@ -722,7 +728,7 @@ mod tests { // 发布新版本 let (release_data, version1) = accessor - .release_new_from_draft(draft.id, name, &description) + .release_new_from_draft(draft.id, name, &description, None) .await?; assert_eq!(release_data.name, name); // 检查使用版本是刚刚发布的版本 @@ -752,7 +758,9 @@ mod tests { let data = "test2".as_bytes(); accessor.update_draft_data_data(draft.id, data).await?; // 发布到已存在发布数据成功测试 - let (release_data, version2) = accessor.release_to_existing(draft.id, description).await?; + let (release_data, version2) = accessor + .release_to_existing(draft.id, description, None) + .await?; assert_eq!(release_data.name, name); // 检查使用版本是刚刚发布的版本 assert_eq!(release_data.used_version_id, Some(version2.id)); @@ -828,12 +836,12 @@ mod tests { ) .await?; let (release_data, _) = accessor - .release_new_from_draft(draft.id, &name, &description) + .release_new_from_draft(draft.id, &name, &description, None) .await?; assert_eq!(release_data.name, name); let another_name = format!("{}_another", name); let (release_data, _) = accessor - .release_new_from_draft(draft.id, &another_name, &description) + .release_new_from_draft(draft.id, &another_name, &description, None) .await?; assert_eq!(release_data.name, another_name); } diff --git a/src/app_config.rs b/src/app_config.rs index a0ab76c..5ea7b7c 100644 --- a/src/app_config.rs +++ b/src/app_config.rs @@ -29,12 +29,22 @@ impl From for rtss_log::Logging { } } +#[derive(Debug, Deserialize)] +#[allow(unused)] +pub struct Sso { + pub base_url: String, + pub login_url: String, + pub logout_url: String, + pub user_info_url: String, +} + #[derive(Debug, Deserialize)] #[allow(unused)] pub struct AppConfig { pub server: Server, pub log: Log, pub database: Database, + pub sso: Sso, } impl AppConfig { diff --git a/src/cmd.rs b/src/cmd.rs index a3952ff..bf2b142 100644 --- a/src/cmd.rs +++ b/src/cmd.rs @@ -31,10 +31,15 @@ impl CmdExecutor for ServerOpts { app_config::AppConfig::new(&self.config_path).expect("Failed to load app config"); let log: rtss_log::Logging = app_config.log.into(); log.init(); - rtss_api::serve(rtss_api::ServerConfig::new( - &app_config.database.url, - app_config.server.port, - )) + rtss_api::serve( + rtss_api::ServerConfig::new(&app_config.database.url, app_config.server.port) + .with_user_auth_client(rtss_api::UserAuthClient { + base_url: app_config.sso.base_url, + login_url: app_config.sso.login_url, + logout_url: app_config.sso.logout_url, + user_info_url: app_config.sso.user_info_url, + }), + ) .await } }