this repo has no description
1use crate::state::AppState;
2use axum::{
3 Json,
4 extract::{Query, State},
5 http::StatusCode,
6 response::{IntoResponse, Response},
7};
8use bcrypt::verify;
9use serde::{Deserialize, Serialize};
10use serde_json::json;
11use tracing::{error, info, warn};
12
13#[derive(Deserialize)]
14pub struct GetServiceAuthParams {
15 pub aud: String,
16 pub lxm: Option<String>,
17 pub exp: Option<i64>,
18}
19
20#[derive(Serialize)]
21pub struct GetServiceAuthOutput {
22 pub token: String,
23}
24
25pub async fn get_service_auth(
26 State(state): State<AppState>,
27 headers: axum::http::HeaderMap,
28 Query(params): Query<GetServiceAuthParams>,
29) -> Response {
30 let auth_header = headers.get("Authorization");
31 if auth_header.is_none() {
32 return (
33 StatusCode::UNAUTHORIZED,
34 Json(json!({"error": "AuthenticationRequired"})),
35 )
36 .into_response();
37 }
38
39 let token = auth_header
40 .unwrap()
41 .to_str()
42 .unwrap_or("")
43 .replace("Bearer ", "");
44
45 let session = sqlx::query!(
46 r#"
47 SELECT s.did, k.key_bytes
48 FROM sessions s
49 JOIN users u ON s.did = u.did
50 JOIN user_keys k ON u.id = k.user_id
51 WHERE s.access_jwt = $1
52 "#,
53 token
54 )
55 .fetch_optional(&state.db)
56 .await;
57
58 let (did, key_bytes) = match session {
59 Ok(Some(row)) => (row.did, row.key_bytes),
60 Ok(None) => {
61 return (
62 StatusCode::UNAUTHORIZED,
63 Json(json!({"error": "AuthenticationFailed"})),
64 )
65 .into_response();
66 }
67 Err(e) => {
68 error!("DB error in get_service_auth: {:?}", e);
69 return (
70 StatusCode::INTERNAL_SERVER_ERROR,
71 Json(json!({"error": "InternalError"})),
72 )
73 .into_response();
74 }
75 };
76
77 if let Err(_) = crate::auth::verify_token(&token, &key_bytes) {
78 return (
79 StatusCode::UNAUTHORIZED,
80 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"})),
81 )
82 .into_response();
83 }
84
85 let lxm = params.lxm.as_deref().unwrap_or("*");
86
87 let service_token = match crate::auth::create_service_token(&did, ¶ms.aud, lxm, &key_bytes)
88 {
89 Ok(t) => t,
90 Err(e) => {
91 error!("Failed to create service token: {:?}", e);
92 return (
93 StatusCode::INTERNAL_SERVER_ERROR,
94 Json(json!({"error": "InternalError"})),
95 )
96 .into_response();
97 }
98 };
99
100 (StatusCode::OK, Json(GetServiceAuthOutput { token: service_token })).into_response()
101}
102
103#[derive(Deserialize)]
104pub struct CreateSessionInput {
105 pub identifier: String,
106 pub password: String,
107}
108
109#[derive(Serialize)]
110#[serde(rename_all = "camelCase")]
111pub struct CreateSessionOutput {
112 pub access_jwt: String,
113 pub refresh_jwt: String,
114 pub handle: String,
115 pub did: String,
116}
117
118pub async fn create_session(
119 State(state): State<AppState>,
120 Json(input): Json<CreateSessionInput>,
121) -> Response {
122 info!("create_session: identifier='{}'", input.identifier);
123
124 let user_row = sqlx::query!(
125 "SELECT u.id, 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",
126 input.identifier
127 )
128 .fetch_optional(&state.db)
129 .await;
130
131 match user_row {
132 Ok(Some(row)) => {
133 let user_id = row.id;
134 let stored_hash = &row.password_hash;
135 let did = &row.did;
136 let handle = &row.handle;
137 let key_bytes = &row.key_bytes;
138
139 let password_valid = if verify(&input.password, stored_hash).unwrap_or(false) {
140 true
141 } else {
142 let app_pass_rows = sqlx::query!("SELECT password_hash FROM app_passwords WHERE user_id = $1", user_id)
143 .fetch_all(&state.db)
144 .await
145 .unwrap_or_default();
146
147 app_pass_rows.iter().any(|row| {
148 verify(&input.password, &row.password_hash).unwrap_or(false)
149 })
150 };
151
152 if password_valid {
153 let access_jwt = match crate::auth::create_access_token(&did, &key_bytes) {
154 Ok(t) => t,
155 Err(e) => {
156 error!("Failed to create access token: {:?}", e);
157 return (
158 StatusCode::INTERNAL_SERVER_ERROR,
159 Json(json!({"error": "InternalError"})),
160 )
161 .into_response();
162 }
163 };
164
165 let refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) {
166 Ok(t) => t,
167 Err(e) => {
168 error!("Failed to create refresh token: {:?}", e);
169 return (
170 StatusCode::INTERNAL_SERVER_ERROR,
171 Json(json!({"error": "InternalError"})),
172 )
173 .into_response();
174 }
175 };
176
177 let session_insert = sqlx::query!(
178 "INSERT INTO sessions (access_jwt, refresh_jwt, did) VALUES ($1, $2, $3)",
179 access_jwt,
180 refresh_jwt,
181 did
182 )
183 .execute(&state.db)
184 .await;
185
186 match session_insert {
187 Ok(_) => {
188 return (
189 StatusCode::OK,
190 Json(CreateSessionOutput {
191 access_jwt,
192 refresh_jwt,
193 handle: handle.clone(),
194 did: did.clone(),
195 }),
196 )
197 .into_response();
198 }
199 Err(e) => {
200 error!("Failed to insert session: {:?}", e);
201 return (
202 StatusCode::INTERNAL_SERVER_ERROR,
203 Json(json!({"error": "InternalError"})),
204 )
205 .into_response();
206 }
207 }
208 } else {
209 warn!(
210 "Password verification failed for identifier: {}",
211 input.identifier
212 );
213 }
214 }
215 Ok(None) => {
216 warn!("User not found for identifier: {}", input.identifier);
217 }
218 Err(e) => {
219 error!("Database error fetching user: {:?}", e);
220 return (
221 StatusCode::INTERNAL_SERVER_ERROR,
222 Json(json!({"error": "InternalError"})),
223 )
224 .into_response();
225 }
226 }
227
228 (
229 StatusCode::UNAUTHORIZED,
230 Json(json!({"error": "AuthenticationFailed", "message": "Invalid identifier or password"})),
231 )
232 .into_response()
233}
234
235pub async fn get_session(
236 State(state): State<AppState>,
237 headers: axum::http::HeaderMap,
238) -> Response {
239 let auth_header = headers.get("Authorization");
240 if auth_header.is_none() {
241 return (
242 StatusCode::UNAUTHORIZED,
243 Json(json!({"error": "AuthenticationRequired"})),
244 )
245 .into_response();
246 }
247
248 let token = auth_header
249 .unwrap()
250 .to_str()
251 .unwrap_or("")
252 .replace("Bearer ", "");
253
254 let result = sqlx::query!(
255 r#"
256 SELECT u.handle, u.did, u.email, k.key_bytes
257 FROM sessions s
258 JOIN users u ON s.did = u.did
259 JOIN user_keys k ON u.id = k.user_id
260 WHERE s.access_jwt = $1
261 "#,
262 token
263 )
264 .fetch_optional(&state.db)
265 .await;
266
267 match result {
268 Ok(Some(row)) => {
269 if let Err(_) = crate::auth::verify_token(&token, &row.key_bytes) {
270 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid token signature"}))).into_response();
271 }
272
273 return (
274 StatusCode::OK,
275 Json(json!({
276 "handle": row.handle,
277 "did": row.did,
278 "email": row.email,
279 "didDoc": {}
280 })),
281 )
282 .into_response();
283 }
284 Ok(None) => {
285 return (
286 StatusCode::UNAUTHORIZED,
287 Json(json!({"error": "AuthenticationFailed"})),
288 )
289 .into_response();
290 }
291 Err(e) => {
292 error!("Database error in get_session: {:?}", e);
293 return (
294 StatusCode::INTERNAL_SERVER_ERROR,
295 Json(json!({"error": "InternalError"})),
296 )
297 .into_response();
298 }
299 }
300}
301
302pub async fn delete_session(
303 State(state): State<AppState>,
304 headers: axum::http::HeaderMap,
305) -> Response {
306 let auth_header = headers.get("Authorization");
307 if auth_header.is_none() {
308 return (
309 StatusCode::UNAUTHORIZED,
310 Json(json!({"error": "AuthenticationRequired"})),
311 )
312 .into_response();
313 }
314
315 let token = auth_header
316 .unwrap()
317 .to_str()
318 .unwrap_or("")
319 .replace("Bearer ", "");
320
321 let result = sqlx::query!("DELETE FROM sessions WHERE access_jwt = $1", token)
322 .execute(&state.db)
323 .await;
324
325 match result {
326 Ok(res) => {
327 if res.rows_affected() > 0 {
328 return (StatusCode::OK, Json(json!({}))).into_response();
329 }
330 }
331 Err(e) => {
332 error!("Database error in delete_session: {:?}", e);
333 }
334 }
335
336 (
337 StatusCode::UNAUTHORIZED,
338 Json(json!({"error": "AuthenticationFailed"})),
339 )
340 .into_response()
341}
342
343pub async fn refresh_session(
344 State(state): State<AppState>,
345 headers: axum::http::HeaderMap,
346) -> Response {
347 let auth_header = headers.get("Authorization");
348 if auth_header.is_none() {
349 return (
350 StatusCode::UNAUTHORIZED,
351 Json(json!({"error": "AuthenticationRequired"})),
352 )
353 .into_response();
354 }
355
356 let refresh_token = auth_header
357 .unwrap()
358 .to_str()
359 .unwrap_or("")
360 .replace("Bearer ", "");
361
362 let session = sqlx::query!(
363 "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",
364 refresh_token
365 )
366 .fetch_optional(&state.db)
367 .await;
368
369 match session {
370 Ok(Some(session_row)) => {
371 let did = &session_row.did;
372 let key_bytes = &session_row.key_bytes;
373
374 if let Err(_) = crate::auth::verify_token(&refresh_token, &key_bytes) {
375 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token signature"}))).into_response();
376 }
377
378 let new_access_jwt = match crate::auth::create_access_token(&did, &key_bytes) {
379 Ok(t) => t,
380 Err(e) => {
381 error!("Failed to create access token: {:?}", e);
382 return (
383 StatusCode::INTERNAL_SERVER_ERROR,
384 Json(json!({"error": "InternalError"})),
385 )
386 .into_response();
387 }
388 };
389 let new_refresh_jwt = match crate::auth::create_refresh_token(&did, &key_bytes) {
390 Ok(t) => t,
391 Err(e) => {
392 error!("Failed to create refresh token: {:?}", e);
393 return (
394 StatusCode::INTERNAL_SERVER_ERROR,
395 Json(json!({"error": "InternalError"})),
396 )
397 .into_response();
398 }
399 };
400
401 let update = sqlx::query!(
402 "UPDATE sessions SET access_jwt = $1, refresh_jwt = $2 WHERE refresh_jwt = $3",
403 new_access_jwt,
404 new_refresh_jwt,
405 refresh_token
406 )
407 .execute(&state.db)
408 .await;
409
410 match update {
411 Ok(_) => {
412 let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did)
413 .fetch_optional(&state.db)
414 .await;
415
416 match user {
417 Ok(Some(u)) => {
418 return (
419 StatusCode::OK,
420 Json(json!({
421 "accessJwt": new_access_jwt,
422 "refreshJwt": new_refresh_jwt,
423 "handle": u.handle,
424 "did": did
425 })),
426 )
427 .into_response();
428 }
429 Ok(None) => {
430 error!("User not found for existing session: {}", did);
431 return (
432 StatusCode::INTERNAL_SERVER_ERROR,
433 Json(json!({"error": "InternalError"})),
434 )
435 .into_response();
436 }
437 Err(e) => {
438 error!("Database error fetching user: {:?}", e);
439 return (
440 StatusCode::INTERNAL_SERVER_ERROR,
441 Json(json!({"error": "InternalError"})),
442 )
443 .into_response();
444 }
445 }
446 }
447 Err(e) => {
448 error!("Database error updating session: {:?}", e);
449 return (
450 StatusCode::INTERNAL_SERVER_ERROR,
451 Json(json!({"error": "InternalError"})),
452 )
453 .into_response();
454 }
455 }
456 }
457 Ok(None) => {
458 return (
459 StatusCode::UNAUTHORIZED,
460 Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"})),
461 )
462 .into_response();
463 }
464 Err(e) => {
465 error!("Database error fetching session: {:?}", e);
466 return (
467 StatusCode::INTERNAL_SERVER_ERROR,
468 Json(json!({"error": "InternalError"})),
469 )
470 .into_response();
471 }
472 }
473}