this repo has no description
1use axum::{
2 extract::State,
3 Json,
4 response::{IntoResponse, Response},
5 http::StatusCode,
6};
7use serde::{Deserialize, Serialize};
8use serde_json::json;
9use crate::state::AppState;
10use sqlx::Row;
11use bcrypt::verify;
12use tracing::{info, error, warn};
13
14pub async fn describe_server() -> impl IntoResponse {
15 let domains_str = std::env::var("AVAILABLE_USER_DOMAINS").unwrap_or_else(|_| "example.com".to_string());
16 let domains: Vec<&str> = domains_str.split(',').map(|s| s.trim()).collect();
17
18 Json(json!({
19 "availableUserDomains": domains
20 }))
21}
22
23pub async fn health(State(state): State<AppState>) -> impl IntoResponse {
24 match sqlx::query("SELECT 1").execute(&state.db).await {
25 Ok(_) => (StatusCode::OK, "OK"),
26 Err(e) => {
27 error!("Health check failed: {:?}", e);
28 (StatusCode::SERVICE_UNAVAILABLE, "Service Unavailable")
29 }
30 }
31}
32
33#[derive(Deserialize)]
34pub struct CreateSessionInput {
35 pub identifier: String,
36 pub password: String,
37}
38
39#[derive(Serialize)]
40#[serde(rename_all = "camelCase")]
41pub struct CreateSessionOutput {
42 pub access_jwt: String,
43 pub refresh_jwt: String,
44 pub handle: String,
45 pub did: String,
46}
47
48pub async fn create_session(
49 State(state): State<AppState>,
50 Json(input): Json<CreateSessionInput>,
51) -> Response {
52 info!("create_session: identifier='{}'", input.identifier);
53
54 let user_row = sqlx::query("SELECT u.did, u.handle, u.password_hash, k.key_bytes FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1")
55 .bind(&input.identifier)
56 .fetch_optional(&state.db)
57 .await;
58
59 match user_row {
60 Ok(Some(row)) => {
61 let stored_hash: String = row.get("password_hash");
62
63 if verify(&input.password, &stored_hash).unwrap_or(false) {
64 let did: String = row.get("did");
65 let handle: String = row.get("handle");
66 let key_bytes: Vec<u8> = row.get("key_bytes");
67
68 let access_jwt = match crate::auth::create_access_token(&did, &key_bytes) {
69 Ok(t) => t,
70 Err(e) => {
71 error!("Failed to create access token: {:?}", e);
72 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
73 }
74 };
75
76 let refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) {
77 Ok(t) => t,
78 Err(e) => {
79 error!("Failed to create refresh token: {:?}", e);
80 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
81 }
82 };
83
84 let session_insert = sqlx::query("INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)")
85 .bind(&access_jwt)
86 .bind(&refresh_jwt)
87 .bind(&did)
88 .execute(&state.db)
89 .await;
90
91 match session_insert {
92 Ok(_) => {
93 return (StatusCode::OK, Json(CreateSessionOutput {
94 access_jwt,
95 refresh_jwt,
96 handle,
97 did,
98 })).into_response();
99 },
100 Err(e) => {
101 error!("Failed to insert session: {:?}", e);
102 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
103 }
104 }
105 } else {
106 warn!("Password verification failed for identifier: {}", input.identifier);
107 }
108 },
109 Ok(None) => {
110 warn!("User not found for identifier: {}", input.identifier);
111 },
112 Err(e) => {
113 error!("Database error fetching user: {:?}", e);
114 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
115 }
116 }
117
118 (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid identifier or password"}))).into_response()
119}
120
121pub async fn get_session(
122 State(state): State<AppState>,
123 headers: axum::http::HeaderMap,
124) -> Response {
125 let auth_header = headers.get("Authorization");
126 if auth_header.is_none() {
127 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationRequired"}))).into_response();
128 }
129
130 let token = auth_header.unwrap().to_str().unwrap_or("").replace("Bearer ", "");
131
132 let result = sqlx::query(
133 r#"
134 SELECT u.handle, u.did, u.email, k.key_bytes
135 FROM sessions s
136 JOIN users u ON s.did = u.did
137 JOIN user_keys k ON u.id = k.user_id
138 WHERE s.access_jwt = $1
139 "#
140 )
141 .bind(&token)
142 .fetch_optional(&state.db)
143 .await;
144
145 match result {
146 Ok(Some(row)) => {
147 let handle: String = row.get("handle");
148 let did: String = row.get("did");
149 let email: String = row.get("email");
150 let key_bytes: Vec<u8> = row.get("key_bytes");
151
152 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
153 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response();
154 }
155
156 return (StatusCode::OK, Json(json!({
157 "handle": handle,
158 "did": did,
159 "email": email,
160 "didDoc": {}
161 }))).into_response();
162 },
163 Ok(None) => {
164 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed"}))).into_response();
165 },
166 Err(e) => {
167 error!("Database error in get_session: {:?}", e);
168 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
169 }
170 }
171}
172
173pub async fn delete_session(
174 State(state): State<AppState>,
175 headers: axum::http::HeaderMap,
176) -> Response {
177 let auth_header = headers.get("Authorization");
178 if auth_header.is_none() {
179 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationRequired"}))).into_response();
180 }
181
182 let token = auth_header.unwrap().to_str().unwrap_or("").replace("Bearer ", "");
183
184 let result = sqlx::query("DELETE FROM sessions WHERE access_jwt = $1")
185 .bind(token)
186 .execute(&state.db)
187 .await;
188
189 match result {
190 Ok(res) => {
191 if res.rows_affected() > 0 {
192 return (StatusCode::OK, Json(json!({}))).into_response();
193 }
194 },
195 Err(e) => {
196 error!("Database error in delete_session: {:?}", e);
197 }
198 }
199
200 (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed"}))).into_response()
201}
202
203pub async fn refresh_session(
204 State(state): State<AppState>,
205 headers: axum::http::HeaderMap,
206) -> Response {
207 let auth_header = headers.get("Authorization");
208 if auth_header.is_none() {
209 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationRequired"}))).into_response();
210 }
211
212 let refresh_token = auth_header.unwrap().to_str().unwrap_or("").replace("Bearer ", "");
213
214 let session = sqlx::query(
215 "SELECT s.did, k.key_bytes FROM sessions s JOIN users u ON s.did = u.did JOIN user_keys k ON u.id = k.user_id WHERE s.refresh_jwt = $1"
216 )
217 .bind(&refresh_token)
218 .fetch_optional(&state.db)
219 .await;
220
221 match session {
222 Ok(Some(session_row)) => {
223 let did: String = session_row.get("did");
224 let key_bytes: Vec<u8> = session_row.get("key_bytes");
225
226 if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) {
227 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response();
228 }
229
230 let new_access_jwt = match crate::auth::create_access_token(&did, &key_bytes) {
231 Ok(t) => t,
232 Err(e) => {
233 error!("Failed to create access token: {:?}", e);
234 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
235 }
236 };
237 let new_refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) {
238 Ok(t) => t,
239 Err(e) => {
240 error!("Failed to create refresh token: {:?}", e);
241 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
242 }
243 };
244
245 let update = sqlx::query("UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3")
246 .bind(&new_access_jwt)
247 .bind(&new_refresh_jwt)
248 .bind(&refresh_token)
249 .execute(&state.db)
250 .await;
251
252 match update {
253 Ok(_) => {
254 let user = sqlx::query("SELECT handle FROM users WHERE did = $1")
255 .bind(&did)
256 .fetch_optional(&state.db)
257 .await;
258
259 match user {
260 Ok(Some(u)) => {
261 let handle: String = u.get("handle");
262 return (StatusCode::OK, Json(json!({
263 "accessJwt": new_access_jwt,
264 "refreshJwt": new_refresh_jwt,
265 "handle": handle,
266 "did": did
267 }))).into_response();
268 },
269 Ok(None) => {
270 error!("User not found for existing session: {}", did);
271 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
272 },
273 Err(e) => {
274 error!("Database error fetching user: {:?}", e);
275 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
276 }
277 }
278 },
279 Err(e) => {
280 error!("Database error updating session: {:?}", e);
281 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
282 }
283 }
284 },
285 Ok(None) => {
286 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"}))).into_response();
287 },
288 Err(e) => {
289 error!("Database error fetching session: {:?}", e);
290 return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError"}))).into_response();
291 }
292 }
293}
294