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