1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
use super::config::{LOGIN_ENDPOINT, SESSION_ENDPOINT};
use super::http_client::HttpClientWithCookies;
use crate::RemoteClientError;
use isahc::{AsyncReadResponseExt, Request};
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct LoginRequest {
auth_token: String,
remember_me: bool,
}
#[derive(Deserialize)]
pub struct SessionResponse {
pub web_client_id: String,
}
pub async fn authenticate(
server_base_url: &str,
auth_token: &str,
remember_me: bool,
ca_cert: Option<&std::path::Path>,
insecure: bool,
) -> Result<(String, HttpClientWithCookies, Option<String>), RemoteClientError> {
let http_client = HttpClientWithCookies::new(ca_cert, insecure)
.map_err(|e| RemoteClientError::Other(Box::new(e)))?;
// Step 1: Login with auth token
let login_url = format!("{}{}", server_base_url, LOGIN_ENDPOINT);
let login_request = LoginRequest {
auth_token: auth_token.to_string(),
remember_me,
};
let response = http_client
.send_with_cookies(
Request::post(login_url)
.header("Content-Type", "application/json")
.header("User-Agent", "http-terminal-client/1.0")
.header("Accept", "application/json")
.body(
serde_json::to_vec(&login_request)
.map_err(|e| RemoteClientError::Other(Box::new(e)))?,
)
.map_err(|e| RemoteClientError::Other(Box::new(e)))?,
)
.await
.map_err(|e| RemoteClientError::ConnectionFailed(e.to_string()))?;
// Handle HTTP status codes
match response.status().as_u16() {
401 => return Err(RemoteClientError::InvalidAuthToken),
status if !response.status().is_success() => {
return Err(RemoteClientError::ConnectionFailed(format!(
"Server returned status {}",
status
)));
},
_ => {},
}
// Step 2: Get session/client ID
let session_url = format!("{}{}", server_base_url, SESSION_ENDPOINT);
let mut session_response = http_client
.send_with_cookies(
Request::post(session_url)
.header("Content-Type", "application/json")
.header("User-Agent", "http-terminal-client/1.0")
.header("Accept", "application/json")
.body("{}".as_bytes().to_vec())
.map_err(|e| RemoteClientError::Other(Box::new(e)))?,
)
.await
.map_err(|e| RemoteClientError::ConnectionFailed(e.to_string()))?;
// Handle session response
match session_response.status().as_u16() {
401 => return Err(RemoteClientError::Unauthorized),
status if !session_response.status().is_success() => {
return Err(RemoteClientError::ConnectionFailed(format!(
"Server returned status {}",
status
)));
},
_ => {},
}
let response_body = session_response
.text()
.await
.map_err(|e| RemoteClientError::Other(Box::new(e)))?;
let session_data: SessionResponse =
serde_json::from_str(&response_body).map_err(|e| RemoteClientError::Other(Box::new(e)))?;
// Extract session_token if remember_me was true
let session_token = if remember_me {
http_client.get_cookie("session_token")
} else {
None
};
Ok((session_data.web_client_id, http_client, session_token))
}
pub async fn validate_session_token(
server_base_url: &str,
session_token: &str,
ca_cert: Option<&std::path::Path>,
insecure: bool,
) -> Result<(String, HttpClientWithCookies), RemoteClientError> {
let http_client = HttpClientWithCookies::new(ca_cert, insecure)
.map_err(|e| RemoteClientError::Other(Box::new(e)))?;
// Pre-populate the session_token cookie
http_client.set_cookie("session_token".to_string(), session_token.to_string());
// Skip /login, go directly to /session endpoint
let session_url = format!("{}{}", server_base_url, SESSION_ENDPOINT);
let mut session_response = http_client
.send_with_cookies(
Request::post(session_url)
.header("Content-Type", "application/json")
.header("User-Agent", "http-terminal-client/1.0")
.header("Accept", "application/json")
.body("{}".as_bytes().to_vec())
.map_err(|e| RemoteClientError::Other(Box::new(e)))?,
)
.await
.map_err(|e| RemoteClientError::ConnectionFailed(e.to_string()))?;
match session_response.status().as_u16() {
401 => Err(RemoteClientError::SessionTokenExpired),
status if !session_response.status().is_success() => Err(
RemoteClientError::ConnectionFailed(format!("Server returned status {}", status)),
),
_ => {
let response_body = session_response
.text()
.await
.map_err(|e| RemoteClientError::Other(Box::new(e)))?;
let session_data: SessionResponse = serde_json::from_str(&response_body)
.map_err(|e| RemoteClientError::Other(Box::new(e)))?;
Ok((session_data.web_client_id, http_client))
},
}
}
|