haystack_server/ops/
about.rs1use actix_web::http::StatusCode;
37use actix_web::{HttpMessage, HttpRequest, HttpResponse, web};
38
39use haystack_core::auth::{AuthHeader, parse_auth_header};
40use haystack_core::data::{HCol, HDict, HGrid};
41use haystack_core::kinds::Kind;
42
43use crate::content;
44use crate::error::error_grid;
45use crate::state::AppState;
46
47pub async fn handle(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
54 let accept = req
55 .headers()
56 .get("Accept")
57 .and_then(|v| v.to_str().ok())
58 .unwrap_or("");
59
60 if !state.auth.is_enabled() {
62 return respond_about_grid(accept);
63 }
64
65 let auth_header = req
66 .headers()
67 .get("Authorization")
68 .and_then(|v| v.to_str().ok());
69
70 match auth_header {
71 None => {
72 HttpResponse::Unauthorized()
74 .insert_header(("WWW-Authenticate", "HELLO"))
75 .body("Authentication required")
76 }
77 Some(header) => {
78 match parse_auth_header(header) {
79 Ok(AuthHeader::Hello { username, data }) => {
80 match state.auth.handle_hello(&username, data.as_deref()) {
82 Ok(www_auth) => HttpResponse::Unauthorized()
83 .insert_header(("WWW-Authenticate", www_auth))
84 .body(""),
85 Err(e) => {
86 log::warn!("HELLO failed for {username}: {e}");
87 let grid = error_grid(&format!("authentication failed: {e}"));
88 respond_error_grid(&grid, accept, StatusCode::FORBIDDEN)
89 }
90 }
91 }
92 Ok(AuthHeader::Scram {
93 handshake_token,
94 data,
95 }) => {
96 match state.auth.handle_scram(&handshake_token, &data) {
98 Ok((_auth_token, auth_info)) => HttpResponse::Ok()
99 .insert_header(("Authentication-Info", auth_info))
100 .body(""),
101 Err(e) => {
102 log::warn!("SCRAM verification failed: {e}");
103 let grid = error_grid("authentication failed");
104 respond_error_grid(&grid, accept, StatusCode::FORBIDDEN)
105 }
106 }
107 }
108 Ok(AuthHeader::Bearer { auth_token }) => {
109 match state.auth.validate_token(&auth_token) {
111 Some(_user) => respond_about_grid(accept),
112 None => {
113 let grid = error_grid("invalid or expired auth token");
114 respond_error_grid(&grid, accept, StatusCode::UNAUTHORIZED)
115 }
116 }
117 }
118 Err(e) => {
119 log::warn!("Invalid Authorization header: {e}");
120 HttpResponse::BadRequest().body(format!("Invalid Authorization header: {e}"))
121 }
122 }
123 }
124 }
125}
126
127pub async fn handle_close(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
132 let accept = req
133 .headers()
134 .get("Accept")
135 .and_then(|v| v.to_str().ok())
136 .unwrap_or("");
137
138 if let Some(user) = req.extensions().get::<crate::auth::AuthUser>() {
139 if let Some(auth_header) = req
141 .headers()
142 .get("Authorization")
143 .and_then(|v| v.to_str().ok())
144 && let Ok(AuthHeader::Bearer { auth_token }) = parse_auth_header(auth_header)
145 {
146 state.auth.revoke_token(&auth_token);
147 }
148 log::info!("User {} logged out", user.username);
149 }
150
151 let grid = HGrid::new();
153 match content::encode_response_grid(&grid, accept) {
154 Ok((body, ct)) => HttpResponse::Ok().content_type(ct).body(body),
155 Err(_) => HttpResponse::InternalServerError().body("encoding error"),
156 }
157}
158
159fn respond_about_grid(accept: &str) -> HttpResponse {
161 let mut row = HDict::new();
162 row.set("haystackVersion", Kind::Str("4.0".to_string()));
163 row.set("serverName", Kind::Str("rusty-haystack".to_string()));
164 row.set("serverVersion", Kind::Str("0.6.3".to_string()));
165 row.set("productName", Kind::Str("rusty-haystack".to_string()));
166 row.set(
167 "productUri",
168 Kind::Uri(haystack_core::kinds::Uri::new(
169 "https://github.com/jscott3201/rusty-haystack",
170 )),
171 );
172 row.set("moduleName", Kind::Str("haystack-server".to_string()));
173 row.set("moduleVersion", Kind::Str("0.6.3".to_string()));
174
175 let cols = vec![
176 HCol::new("haystackVersion"),
177 HCol::new("serverName"),
178 HCol::new("serverVersion"),
179 HCol::new("productName"),
180 HCol::new("productUri"),
181 HCol::new("moduleName"),
182 HCol::new("moduleVersion"),
183 ];
184
185 let grid = HGrid::from_parts(HDict::new(), cols, vec![row]);
186 match content::encode_response_grid(&grid, accept) {
187 Ok((body, ct)) => HttpResponse::Ok().content_type(ct).body(body),
188 Err(e) => {
189 log::error!("Failed to encode about grid: {e}");
190 HttpResponse::InternalServerError().body("encoding error")
191 }
192 }
193}
194
195fn respond_error_grid(grid: &HGrid, accept: &str, status: StatusCode) -> HttpResponse {
197 match content::encode_response_grid(grid, accept) {
198 Ok((body, ct)) => HttpResponse::build(status).content_type(ct).body(body),
199 Err(_) => HttpResponse::build(status).body("error"),
200 }
201}