lejianwen 1 год назад
Родитель
Сommit
bc980a3b84
7 измененных файлов с 107 добавлено и 11 удалено
  1. 3 2
      Cargo.lock
  2. 1 1
      Cargo.toml
  3. 1 0
      README.md
  4. 54 0
      src/jwt.rs
  5. 1 0
      src/lib.rs
  6. 23 8
      src/rendezvous_server.rs
  7. 24 0
      tests/jwt_tests.rs

+ 3 - 2
Cargo.lock

@@ -1,6 +1,6 @@
1 1
 # This file is automatically @generated by Cargo.
2 2
 # It is not intended for manual editing.
3
-version = 3
3
+version = 4
4 4
 
5 5
 [[package]]
6 6
 name = "ahash"
@@ -225,6 +225,7 @@ dependencies = [
225 225
  "libc",
226 226
  "num-integer",
227 227
  "num-traits",
228
+ "serde",
228 229
  "time 0.1.43",
229 230
  "winapi",
230 231
 ]
@@ -779,7 +780,7 @@ dependencies = [
779 780
 
780 781
 [[package]]
781 782
 name = "hbbs"
782
-version = "1.1.12"
783
+version = "1.1.13"
783 784
 dependencies = [
784 785
  "async-speed-limit",
785 786
  "async-trait",

+ 1 - 1
Cargo.toml

@@ -36,7 +36,7 @@ async-trait = "0.1"
36 36
 async-speed-limit = { git = "https://github.com/open-trade/async-speed-limit" }
37 37
 uuid = { version = "1.0", features = ["v4"] }
38 38
 bcrypt = "0.13"
39
-chrono = "0.4"
39
+chrono = { version = "0.4", features = ["serde"] }
40 40
 jsonwebtoken = "8"
41 41
 headers = "0.3"
42 42
 once_cell = "1.8"

+ 1 - 0
README.md

@@ -8,6 +8,7 @@
8 8
 - 解决当客户端登录了`Api`账号时链接超时的问题
9 9
 - s6镜像添加了`Api`支持,`Api`开源地址 https://github.com/lejianwen/rustdesk-api
10 10
 - 是否必须登录才能链接, `MUST_LOGIN` 默认为 `N`,设置为 `Y` 则必须登录才能链接
11
+- `RUSTDESK_API_JWT_KEY`,设置后会通过`JWT`校验token的合法性
11 12
 
12 13
 ## docker镜像地址
13 14
 

+ 54 - 0
src/jwt.rs

@@ -0,0 +1,54 @@
1
+use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
2
+use once_cell::sync::Lazy;
3
+use serde::{Deserialize, Serialize};
4
+use std::env;
5
+pub static SECRET: Lazy<String> =
6
+    Lazy::new(|| env::var("RUSTDESK_API_JWT_KEY").unwrap_or_else(|_| "".to_string()));
7
+
8
+// 定义一个结构体来表示 JWT 的 payload
9
+#[derive(Debug, Serialize, Deserialize)]
10
+pub struct Claims {
11
+    user_id: u32,
12
+    exp: usize,
13
+}
14
+
15
+pub fn generate_token(user_id: u32, exp: i64) -> Result<String, String> {
16
+    println!("secret: {:}", SECRET.to_string());
17
+    let claims = Claims {
18
+        user_id,
19
+        exp: (chrono::Utc::now() + chrono::Duration::seconds(exp)).timestamp() as usize,
20
+    };
21
+
22
+    let token = encode(
23
+        &Header::default(),
24
+        &claims,
25
+        &EncodingKey::from_secret(SECRET.as_ref()),
26
+    );
27
+
28
+    match token {
29
+        Ok(t) => Ok(t),
30
+        Err(e) => Err(e.to_string()),
31
+    }
32
+}
33
+// 验证 JWT 的函数
34
+pub fn verify_token(token: &str) -> Result<Claims, String> {
35
+    // 解码 JWT
36
+    let validation = Validation::new(Algorithm::HS256);
37
+
38
+    let decoded = decode::<Claims>(
39
+        &token,
40
+        &DecodingKey::from_secret(SECRET.as_ref()),
41
+        &validation,
42
+    );
43
+    match decoded {
44
+        Ok(token_data) => {
45
+            let now = chrono::Utc::now().timestamp() as usize;
46
+            if token_data.claims.exp > now {
47
+                Ok(token_data.claims)
48
+            } else {
49
+                Err("Token status invalid or expired".to_string())
50
+            }
51
+        }
52
+        Err(_) => Err("Invalid token".to_string()),
53
+    }
54
+}

+ 1 - 0
src/lib.rs

@@ -4,3 +4,4 @@ pub mod common;
4 4
 mod database;
5 5
 mod peer;
6 6
 mod version;
7
+pub mod jwt;

+ 23 - 8
src/rendezvous_server.rs

@@ -46,6 +46,7 @@ use std::{
46 46
     sync::Arc,
47 47
     time::Instant,
48 48
 };
49
+use crate::jwt;
49 50
 
50 51
 #[derive(Clone, Debug)]
51 52
 enum Data {
@@ -776,14 +777,28 @@ impl RendezvousServer {
776 777
             });
777 778
             return Ok((msg_out, None));
778 779
         }
779
-        // Todo check token by jwt
780
-        if ph.token.is_empty() && MUST_LOGIN.load(Ordering::SeqCst) {
781
-            let mut msg_out = RendezvousMessage::new();
782
-            msg_out.set_punch_hole_response(PunchHoleResponse {
783
-                other_failure: String::from("Connection failed, please login first"),
784
-                ..Default::default()
785
-            });
786
-            return Ok((msg_out, None));
780
+        // if secret is not empty check token by jwt
781
+        if MUST_LOGIN.load(Ordering::SeqCst) {
782
+            if ph.token.is_empty() {
783
+                let mut msg_out = RendezvousMessage::new();
784
+                msg_out.set_punch_hole_response(PunchHoleResponse {
785
+                    other_failure: String::from("Connection failed, please login!"),
786
+                    ..Default::default()
787
+                });
788
+                return Ok((msg_out, None));
789
+            } else if !jwt::SECRET.is_empty() {
790
+                let token = ph.token;
791
+                let token = jwt::verify_token(token.as_str());
792
+                if token.is_err() {
793
+                    let mut msg_out = RendezvousMessage::new();
794
+                    msg_out.set_punch_hole_response(PunchHoleResponse {
795
+                        //提示重新登录
796
+                        other_failure: String::from("Token error, please log out and log back in!"),
797
+                        ..Default::default()
798
+                    });
799
+                    return Ok((msg_out, None));
800
+                }
801
+            }
787 802
         }
788 803
         let id = ph.id;
789 804
         // punch hole request from A, relay to B,

+ 24 - 0
tests/jwt_tests.rs

@@ -0,0 +1,24 @@
1
+use hbb_common::tokio;
2
+use hbbs::jwt;
3
+
4
+#[test]
5
+fn test_generate_token() {
6
+    std::env::set_var("RUSTDESK_API_JWT_KEY", "testjwt");
7
+    let token = jwt::generate_token(1, 3600).unwrap();
8
+    println!("Generated Token: {}", token);
9
+    assert!(!token.is_empty(), "Generated token should not be empty");
10
+}
11
+
12
+#[tokio::test]
13
+async fn test_verify_token() {
14
+    std::env::set_var("RUSTDESK_API_JWT_KEY", "testjwt");
15
+    let token = jwt::generate_token(1, 2).unwrap();
16
+    // let token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJleHAiOjE3MzY4NzA4NjF9.u5pxmwNMrYUwtkspF1FuZj-R5ANAR9WT9_dMHuQhV0Y";
17
+    println!("Token : {:?}, now: {:?}", token, chrono::Utc::now().timestamp());
18
+
19
+    // hbb_common::sleep(3f32).await;
20
+    // println!("Token : {:?}, now: {:?}", token, chrono::Utc::now().timestamp());
21
+    let result = jwt::verify_token(&token);
22
+    println!("Token Verification Result: {:?}", result);
23
+    assert!(result.is_ok(), "Token should be valid");
24
+}