haystack_server/ops/
about.rs1use actix_web::http::StatusCode;
8use actix_web::{HttpMessage, HttpRequest, HttpResponse, web};
9
10use haystack_core::auth::{AuthHeader, parse_auth_header};
11use haystack_core::data::{HCol, HDict, HGrid};
12use haystack_core::kinds::Kind;
13
14use crate::content;
15use crate::error::error_grid;
16use crate::state::AppState;
17
18pub async fn handle(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
25 let accept = req
26 .headers()
27 .get("Accept")
28 .and_then(|v| v.to_str().ok())
29 .unwrap_or("");
30
31 if !state.auth.is_enabled() {
33 return respond_about_grid(accept);
34 }
35
36 let auth_header = req
37 .headers()
38 .get("Authorization")
39 .and_then(|v| v.to_str().ok());
40
41 match auth_header {
42 None => {
43 HttpResponse::Unauthorized()
45 .insert_header(("WWW-Authenticate", "HELLO"))
46 .body("Authentication required")
47 }
48 Some(header) => {
49 match parse_auth_header(header) {
50 Ok(AuthHeader::Hello { username, data }) => {
51 match state.auth.handle_hello(&username, data.as_deref()) {
53 Ok(www_auth) => HttpResponse::Unauthorized()
54 .insert_header(("WWW-Authenticate", www_auth))
55 .body(""),
56 Err(e) => {
57 log::warn!("HELLO failed for {username}: {e}");
58 let grid = error_grid(&format!("authentication failed: {e}"));
59 respond_error_grid(&grid, accept, StatusCode::FORBIDDEN)
60 }
61 }
62 }
63 Ok(AuthHeader::Scram {
64 handshake_token,
65 data,
66 }) => {
67 match state.auth.handle_scram(&handshake_token, &data) {
69 Ok((_auth_token, auth_info)) => HttpResponse::Ok()
70 .insert_header(("Authentication-Info", auth_info))
71 .body(""),
72 Err(e) => {
73 log::warn!("SCRAM verification failed: {e}");
74 let grid = error_grid("authentication failed");
75 respond_error_grid(&grid, accept, StatusCode::FORBIDDEN)
76 }
77 }
78 }
79 Ok(AuthHeader::Bearer { auth_token }) => {
80 match state.auth.validate_token(&auth_token) {
82 Some(_user) => respond_about_grid(accept),
83 None => {
84 let grid = error_grid("invalid or expired auth token");
85 respond_error_grid(&grid, accept, StatusCode::UNAUTHORIZED)
86 }
87 }
88 }
89 Err(e) => {
90 log::warn!("Invalid Authorization header: {e}");
91 HttpResponse::BadRequest().body(format!("Invalid Authorization header: {e}"))
92 }
93 }
94 }
95 }
96}
97
98pub async fn handle_close(req: HttpRequest, state: web::Data<AppState>) -> HttpResponse {
100 let accept = req
101 .headers()
102 .get("Accept")
103 .and_then(|v| v.to_str().ok())
104 .unwrap_or("");
105
106 if let Some(user) = req.extensions().get::<crate::auth::AuthUser>() {
107 if let Some(auth_header) = req
109 .headers()
110 .get("Authorization")
111 .and_then(|v| v.to_str().ok())
112 && let Ok(AuthHeader::Bearer { auth_token }) = parse_auth_header(auth_header)
113 {
114 state.auth.revoke_token(&auth_token);
115 }
116 log::info!("User {} logged out", user.username);
117 }
118
119 let grid = HGrid::new();
121 match content::encode_response_grid(&grid, accept) {
122 Ok((body, ct)) => HttpResponse::Ok().content_type(ct).body(body),
123 Err(_) => HttpResponse::InternalServerError().body("encoding error"),
124 }
125}
126
127fn respond_about_grid(accept: &str) -> HttpResponse {
129 let mut row = HDict::new();
130 row.set("haystackVersion", Kind::Str("4.0".to_string()));
131 row.set("serverName", Kind::Str("rusty-haystack".to_string()));
132 row.set("serverVersion", Kind::Str("0.1.0".to_string()));
133 row.set("productName", Kind::Str("rusty-haystack".to_string()));
134 row.set(
135 "productUri",
136 Kind::Uri(haystack_core::kinds::Uri::new(
137 "https://github.com/example/rusty-haystack",
138 )),
139 );
140 row.set("moduleName", Kind::Str("haystack-server".to_string()));
141 row.set("moduleVersion", Kind::Str("0.1.0".to_string()));
142
143 let cols = vec![
144 HCol::new("haystackVersion"),
145 HCol::new("serverName"),
146 HCol::new("serverVersion"),
147 HCol::new("productName"),
148 HCol::new("productUri"),
149 HCol::new("moduleName"),
150 HCol::new("moduleVersion"),
151 ];
152
153 let grid = HGrid::from_parts(HDict::new(), cols, vec![row]);
154 match content::encode_response_grid(&grid, accept) {
155 Ok((body, ct)) => HttpResponse::Ok().content_type(ct).body(body),
156 Err(e) => {
157 log::error!("Failed to encode about grid: {e}");
158 HttpResponse::InternalServerError().body("encoding error")
159 }
160 }
161}
162
163fn respond_error_grid(grid: &HGrid, accept: &str, status: StatusCode) -> HttpResponse {
165 match content::encode_response_grid(grid, accept) {
166 Ok((body, ct)) => HttpResponse::build(status).content_type(ct).body(body),
167 Err(_) => HttpResponse::build(status).body("error"),
168 }
169}