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