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 token = match crate::auth::extract_bearer_token_from_header(
31 headers.get("Authorization").and_then(|h| h.to_str().ok())
32 ) {
33 Some(t) => t,
34 None => {
35 return (
36 StatusCode::UNAUTHORIZED,
37 Json(json!({"error": "AuthenticationRequired"})),
38 )
39 .into_response();
40 }
41 };
42
43 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
44 let (did, key_bytes) = match auth_result {
45 Ok(user) => {
46 let kb = match user.key_bytes {
47 Some(kb) => kb,
48 None => {
49 return (
50 StatusCode::UNAUTHORIZED,
51 Json(json!({"error": "AuthenticationFailed", "message": "OAuth tokens cannot create service auth"})),
52 )
53 .into_response();
54 }
55 };
56 (user.did, kb)
57 }
58 Err(e) => {
59 return (
60 StatusCode::UNAUTHORIZED,
61 Json(json!({"error": e})),
62 )
63 .into_response();
64 }
65 };
66
67 let lxm = params.lxm.as_deref().unwrap_or("*");
68
69 let service_token = match crate::auth::create_service_token(&did, ¶ms.aud, lxm, &key_bytes)
70 {
71 Ok(t) => t,
72 Err(e) => {
73 error!("Failed to create service token: {:?}", e);
74 return (
75 StatusCode::INTERNAL_SERVER_ERROR,
76 Json(json!({"error": "InternalError"})),
77 )
78 .into_response();
79 }
80 };
81
82 (StatusCode::OK, Json(GetServiceAuthOutput { token: service_token })).into_response()
83}
84
85#[derive(Deserialize)]
86pub struct CreateSessionInput {
87 pub identifier: String,
88 pub password: String,
89}
90
91#[derive(Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct CreateSessionOutput {
94 pub access_jwt: String,
95 pub refresh_jwt: String,
96 pub handle: String,
97 pub did: String,
98}
99
100pub async fn create_session(
101 State(state): State<AppState>,
102 Json(input): Json<CreateSessionInput>,
103) -> Response {
104 info!("create_session: identifier='{}'", input.identifier);
105
106 let user_row = sqlx::query!(
107 "SELECT u.id, u.did, u.handle, u.password_hash, k.key_bytes, k.encryption_version FROM users u JOIN user_keys k ON u.id = k.user_id WHERE u.handle = $1 OR u.email = $1",
108 input.identifier
109 )
110 .fetch_optional(&state.db)
111 .await;
112
113 match user_row {
114 Ok(Some(row)) => {
115 let user_id = row.id;
116 let stored_hash = &row.password_hash;
117 let did = &row.did;
118 let handle = &row.handle;
119 let key_bytes = match crate::config::decrypt_key(&row.key_bytes, row.encryption_version) {
120 Ok(k) => k,
121 Err(e) => {
122 error!("Failed to decrypt user key: {:?}", e);
123 return (
124 StatusCode::INTERNAL_SERVER_ERROR,
125 Json(json!({"error": "InternalError"})),
126 )
127 .into_response();
128 }
129 };
130
131 let password_valid = if verify(&input.password, stored_hash).unwrap_or(false) {
132 true
133 } else {
134 let app_pass_rows = sqlx::query!("SELECT password_hash FROM app_passwords WHERE user_id = $1", user_id)
135 .fetch_all(&state.db)
136 .await
137 .unwrap_or_default();
138
139 app_pass_rows.iter().any(|row| {
140 verify(&input.password, &row.password_hash).unwrap_or(false)
141 })
142 };
143
144 if password_valid {
145 let access_meta = match crate::auth::create_access_token_with_metadata(did, &key_bytes) {
146 Ok(m) => m,
147 Err(e) => {
148 error!("Failed to create access token: {:?}", e);
149 return (
150 StatusCode::INTERNAL_SERVER_ERROR,
151 Json(json!({"error": "InternalError"})),
152 )
153 .into_response();
154 }
155 };
156
157 let refresh_meta = match crate::auth::create_refresh_token_with_metadata(did, &key_bytes) {
158 Ok(m) => m,
159 Err(e) => {
160 error!("Failed to create refresh token: {:?}", e);
161 return (
162 StatusCode::INTERNAL_SERVER_ERROR,
163 Json(json!({"error": "InternalError"})),
164 )
165 .into_response();
166 }
167 };
168
169 let session_insert = sqlx::query!(
170 "INSERT INTO session_tokens (did, access_jti, refresh_jti, access_expires_at, refresh_expires_at) VALUES ($1, $2, $3, $4, $5)",
171 did,
172 access_meta.jti,
173 refresh_meta.jti,
174 access_meta.expires_at,
175 refresh_meta.expires_at
176 )
177 .execute(&state.db)
178 .await;
179
180 match session_insert {
181 Ok(_) => {
182 return (
183 StatusCode::OK,
184 Json(CreateSessionOutput {
185 access_jwt: access_meta.token,
186 refresh_jwt: refresh_meta.token,
187 handle: handle.clone(),
188 did: did.clone(),
189 }),
190 )
191 .into_response();
192 }
193 Err(e) => {
194 error!("Failed to insert session: {:?}", e);
195 return (
196 StatusCode::INTERNAL_SERVER_ERROR,
197 Json(json!({"error": "InternalError"})),
198 )
199 .into_response();
200 }
201 }
202 } else {
203 warn!(
204 "Password verification failed for identifier: {}",
205 input.identifier
206 );
207 }
208 }
209 Ok(None) => {
210 warn!("User not found for identifier: {}", input.identifier);
211 }
212 Err(e) => {
213 error!("Database error fetching user: {:?}", e);
214 return (
215 StatusCode::INTERNAL_SERVER_ERROR,
216 Json(json!({"error": "InternalError"})),
217 )
218 .into_response();
219 }
220 }
221
222 (
223 StatusCode::UNAUTHORIZED,
224 Json(json!({"error": "AuthenticationFailed", "message": "Invalid identifier or password"})),
225 )
226 .into_response()
227}
228
229pub async fn get_session(
230 State(state): State<AppState>,
231 headers: axum::http::HeaderMap,
232) -> Response {
233 let token = match crate::auth::extract_bearer_token_from_header(
234 headers.get("Authorization").and_then(|h| h.to_str().ok())
235 ) {
236 Some(t) => t,
237 None => {
238 return (
239 StatusCode::UNAUTHORIZED,
240 Json(json!({"error": "AuthenticationRequired", "message": "Invalid Authorization header format"})),
241 )
242 .into_response();
243 }
244 };
245
246 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
247 let did = match auth_result {
248 Ok(user) => user.did,
249 Err(e) => {
250 return (
251 StatusCode::UNAUTHORIZED,
252 Json(json!({"error": e})),
253 )
254 .into_response();
255 }
256 };
257
258 let user = sqlx::query!(
259 "SELECT handle, email FROM users WHERE did = $1",
260 did
261 )
262 .fetch_optional(&state.db)
263 .await;
264
265 match user {
266 Ok(Some(row)) => {
267 return (
268 StatusCode::OK,
269 Json(json!({
270 "handle": row.handle,
271 "did": did,
272 "email": row.email,
273 "didDoc": {}
274 })),
275 )
276 .into_response();
277 }
278 Ok(None) => {
279 return (
280 StatusCode::UNAUTHORIZED,
281 Json(json!({"error": "AuthenticationFailed"})),
282 )
283 .into_response();
284 }
285 Err(e) => {
286 error!("Database error in get_session: {:?}", e);
287 return (
288 StatusCode::INTERNAL_SERVER_ERROR,
289 Json(json!({"error": "InternalError"})),
290 )
291 .into_response();
292 }
293 }
294}
295
296pub async fn delete_session(
297 State(state): State<AppState>,
298 headers: axum::http::HeaderMap,
299) -> Response {
300 let token = match crate::auth::extract_bearer_token_from_header(
301 headers.get("Authorization").and_then(|h| h.to_str().ok())
302 ) {
303 Some(t) => t,
304 None => {
305 return (
306 StatusCode::UNAUTHORIZED,
307 Json(json!({"error": "AuthenticationRequired"})),
308 )
309 .into_response();
310 }
311 };
312
313 let jti = match crate::auth::get_did_from_token(&token) {
314 Ok(_) => {
315 let parts: Vec<&str> = token.split('.').collect();
316 if parts.len() != 3 {
317 return (
318 StatusCode::UNAUTHORIZED,
319 Json(json!({"error": "AuthenticationFailed"})),
320 )
321 .into_response();
322 }
323 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
324 let claims_json = match URL_SAFE_NO_PAD.decode(parts[1]) {
325 Ok(bytes) => bytes,
326 Err(_) => {
327 return (
328 StatusCode::UNAUTHORIZED,
329 Json(json!({"error": "AuthenticationFailed"})),
330 )
331 .into_response();
332 }
333 };
334 let claims: serde_json::Value = match serde_json::from_slice(&claims_json) {
335 Ok(c) => c,
336 Err(_) => {
337 return (
338 StatusCode::UNAUTHORIZED,
339 Json(json!({"error": "AuthenticationFailed"})),
340 )
341 .into_response();
342 }
343 };
344 match claims.get("jti").and_then(|j| j.as_str()) {
345 Some(jti) => jti.to_string(),
346 None => {
347 return (
348 StatusCode::UNAUTHORIZED,
349 Json(json!({"error": "AuthenticationFailed"})),
350 )
351 .into_response();
352 }
353 }
354 }
355 Err(_) => {
356 return (
357 StatusCode::UNAUTHORIZED,
358 Json(json!({"error": "AuthenticationFailed"})),
359 )
360 .into_response();
361 }
362 };
363
364 let result = sqlx::query!("DELETE FROM session_tokens WHERE access_jti = $1", jti)
365 .execute(&state.db)
366 .await;
367
368 match result {
369 Ok(res) => {
370 if res.rows_affected() > 0 {
371 return (StatusCode::OK, Json(json!({}))).into_response();
372 }
373 }
374 Err(e) => {
375 error!("Database error in delete_session: {:?}", e);
376 }
377 }
378
379 (
380 StatusCode::UNAUTHORIZED,
381 Json(json!({"error": "AuthenticationFailed"})),
382 )
383 .into_response()
384}
385
386pub async fn refresh_session(
387 State(state): State<AppState>,
388 headers: axum::http::HeaderMap,
389) -> Response {
390 use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD};
391
392 let refresh_token = match crate::auth::extract_bearer_token_from_header(
393 headers.get("Authorization").and_then(|h| h.to_str().ok())
394 ) {
395 Some(t) => t,
396 None => {
397 return (
398 StatusCode::UNAUTHORIZED,
399 Json(json!({"error": "AuthenticationRequired"})),
400 )
401 .into_response();
402 }
403 };
404
405 let refresh_jti = {
406 let parts: Vec<&str> = refresh_token.split('.').collect();
407 if parts.len() != 3 {
408 return (
409 StatusCode::UNAUTHORIZED,
410 Json(json!({"error": "AuthenticationFailed", "message": "Invalid token format"})),
411 )
412 .into_response();
413 }
414 let claims_bytes = match URL_SAFE_NO_PAD.decode(parts[1]) {
415 Ok(b) => b,
416 Err(_) => {
417 return (
418 StatusCode::UNAUTHORIZED,
419 Json(json!({"error": "AuthenticationFailed"})),
420 )
421 .into_response();
422 }
423 };
424 let claims: serde_json::Value = match serde_json::from_slice(&claims_bytes) {
425 Ok(c) => c,
426 Err(_) => {
427 return (
428 StatusCode::UNAUTHORIZED,
429 Json(json!({"error": "AuthenticationFailed"})),
430 )
431 .into_response();
432 }
433 };
434 match claims.get("jti").and_then(|j| j.as_str()) {
435 Some(jti) => jti.to_string(),
436 None => {
437 return (
438 StatusCode::UNAUTHORIZED,
439 Json(json!({"error": "AuthenticationFailed"})),
440 )
441 .into_response();
442 }
443 }
444 };
445
446 let reuse_check = sqlx::query_scalar!(
447 "SELECT session_id FROM used_refresh_tokens WHERE refresh_jti = $1",
448 refresh_jti
449 )
450 .fetch_optional(&state.db)
451 .await;
452
453 if let Ok(Some(session_id)) = reuse_check {
454 warn!("Refresh token reuse detected! Revoking token family for session_id: {}", session_id);
455 let _ = sqlx::query!("DELETE FROM session_tokens WHERE id = $1", session_id)
456 .execute(&state.db)
457 .await;
458 return (
459 StatusCode::UNAUTHORIZED,
460 Json(json!({"error": "ExpiredToken", "message": "Refresh token has been revoked due to suspected compromise"})),
461 )
462 .into_response();
463 }
464
465 let session = sqlx::query!(
466 r#"SELECT st.id, st.did, k.key_bytes, k.encryption_version
467 FROM session_tokens st
468 JOIN users u ON st.did = u.did
469 JOIN user_keys k ON u.id = k.user_id
470 WHERE st.refresh_jti = $1 AND st.refresh_expires_at > NOW()"#,
471 refresh_jti
472 )
473 .fetch_optional(&state.db)
474 .await;
475
476 match session {
477 Ok(Some(session_row)) => {
478 let session_id = session_row.id;
479 let did = &session_row.did;
480 let key_bytes = match crate::config::decrypt_key(&session_row.key_bytes, session_row.encryption_version) {
481 Ok(k) => k,
482 Err(e) => {
483 error!("Failed to decrypt user key: {:?}", e);
484 return (
485 StatusCode::INTERNAL_SERVER_ERROR,
486 Json(json!({"error": "InternalError"})),
487 )
488 .into_response();
489 }
490 };
491
492 if let Err(_) = crate::auth::verify_refresh_token(&refresh_token, &key_bytes) {
493 return (StatusCode::UNAUTHORIZED, Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"}))).into_response();
494 }
495
496 let new_access_meta = match crate::auth::create_access_token_with_metadata(did, &key_bytes) {
497 Ok(m) => m,
498 Err(e) => {
499 error!("Failed to create access token: {:?}", e);
500 return (
501 StatusCode::INTERNAL_SERVER_ERROR,
502 Json(json!({"error": "InternalError"})),
503 )
504 .into_response();
505 }
506 };
507 let new_refresh_meta = match crate::auth::create_refresh_token_with_metadata(did, &key_bytes) {
508 Ok(m) => m,
509 Err(e) => {
510 error!("Failed to create refresh token: {:?}", e);
511 return (
512 StatusCode::INTERNAL_SERVER_ERROR,
513 Json(json!({"error": "InternalError"})),
514 )
515 .into_response();
516 }
517 };
518
519 let mut tx = match state.db.begin().await {
520 Ok(tx) => tx,
521 Err(e) => {
522 error!("Failed to begin transaction: {:?}", e);
523 return (
524 StatusCode::INTERNAL_SERVER_ERROR,
525 Json(json!({"error": "InternalError"})),
526 )
527 .into_response();
528 }
529 };
530
531 if let Err(e) = sqlx::query!(
532 "INSERT INTO used_refresh_tokens (refresh_jti, session_id) VALUES ($1, $2)",
533 refresh_jti,
534 session_id
535 )
536 .execute(&mut *tx)
537 .await
538 {
539 error!("Failed to record used refresh token: {:?}", e);
540 return (
541 StatusCode::INTERNAL_SERVER_ERROR,
542 Json(json!({"error": "InternalError"})),
543 )
544 .into_response();
545 }
546
547 if let Err(e) = sqlx::query!(
548 "UPDATE session_tokens SET access_jti = $1, refresh_jti = $2, access_expires_at = $3, refresh_expires_at = $4, updated_at = NOW() WHERE id = $5",
549 new_access_meta.jti,
550 new_refresh_meta.jti,
551 new_access_meta.expires_at,
552 new_refresh_meta.expires_at,
553 session_id
554 )
555 .execute(&mut *tx)
556 .await
557 {
558 error!("Database error updating session: {:?}", e);
559 return (
560 StatusCode::INTERNAL_SERVER_ERROR,
561 Json(json!({"error": "InternalError"})),
562 )
563 .into_response();
564 }
565
566 if let Err(e) = tx.commit().await {
567 error!("Failed to commit transaction: {:?}", e);
568 return (
569 StatusCode::INTERNAL_SERVER_ERROR,
570 Json(json!({"error": "InternalError"})),
571 )
572 .into_response();
573 }
574
575 let user = sqlx::query!("SELECT handle FROM users WHERE did = $1", did)
576 .fetch_optional(&state.db)
577 .await;
578
579 match user {
580 Ok(Some(u)) => {
581 return (
582 StatusCode::OK,
583 Json(json!({
584 "accessJwt": new_access_meta.token,
585 "refreshJwt": new_refresh_meta.token,
586 "handle": u.handle,
587 "did": did
588 })),
589 )
590 .into_response();
591 }
592 Ok(None) => {
593 error!("User not found for existing session: {}", did);
594 return (
595 StatusCode::INTERNAL_SERVER_ERROR,
596 Json(json!({"error": "InternalError"})),
597 )
598 .into_response();
599 }
600 Err(e) => {
601 error!("Database error fetching user: {:?}", e);
602 return (
603 StatusCode::INTERNAL_SERVER_ERROR,
604 Json(json!({"error": "InternalError"})),
605 )
606 .into_response();
607 }
608 }
609 }
610 Ok(None) => {
611 return (
612 StatusCode::UNAUTHORIZED,
613 Json(json!({"error": "AuthenticationFailed", "message": "Invalid refresh token"})),
614 )
615 .into_response();
616 }
617 Err(e) => {
618 error!("Database error fetching session: {:?}", e);
619 return (
620 StatusCode::INTERNAL_SERVER_ERROR,
621 Json(json!({"error": "InternalError"})),
622 )
623 .into_response();
624 }
625 }
626}