this repo has no description
1use crate::auth::BearerAuthAdmin; 2use crate::state::AppState; 3use axum::{ 4 Json, 5 extract::State, 6 http::StatusCode, 7 response::{IntoResponse, Response}, 8}; 9use serde::Deserialize; 10use serde_json::json; 11use tracing::{error, warn}; 12 13#[derive(Deserialize)] 14pub struct UpdateAccountEmailInput { 15 pub account: String, 16 pub email: String, 17} 18 19pub async fn update_account_email( 20 State(state): State<AppState>, 21 _auth: BearerAuthAdmin, 22 Json(input): Json<UpdateAccountEmailInput>, 23) -> Response { 24 let account = input.account.trim(); 25 let email = input.email.trim(); 26 if account.is_empty() || email.is_empty() { 27 return ( 28 StatusCode::BAD_REQUEST, 29 Json(json!({"error": "InvalidRequest", "message": "account and email are required"})), 30 ) 31 .into_response(); 32 } 33 let result = sqlx::query!("UPDATE users SET email = $1 WHERE did = $2", email, account) 34 .execute(&state.db) 35 .await; 36 match result { 37 Ok(r) => { 38 if r.rows_affected() == 0 { 39 return ( 40 StatusCode::NOT_FOUND, 41 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 42 ) 43 .into_response(); 44 } 45 (StatusCode::OK, Json(json!({}))).into_response() 46 } 47 Err(e) => { 48 error!("DB error updating email: {:?}", e); 49 ( 50 StatusCode::INTERNAL_SERVER_ERROR, 51 Json(json!({"error": "InternalError"})), 52 ) 53 .into_response() 54 } 55 } 56} 57 58#[derive(Deserialize)] 59pub struct UpdateAccountHandleInput { 60 pub did: String, 61 pub handle: String, 62} 63 64pub async fn update_account_handle( 65 State(state): State<AppState>, 66 _auth: BearerAuthAdmin, 67 Json(input): Json<UpdateAccountHandleInput>, 68) -> Response { 69 let did = input.did.trim(); 70 let input_handle = input.handle.trim(); 71 if did.is_empty() || input_handle.is_empty() { 72 return ( 73 StatusCode::BAD_REQUEST, 74 Json(json!({"error": "InvalidRequest", "message": "did and handle are required"})), 75 ) 76 .into_response(); 77 } 78 if !input_handle 79 .chars() 80 .all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '-' || c == '_') 81 { 82 return ( 83 StatusCode::BAD_REQUEST, 84 Json( 85 json!({"error": "InvalidHandle", "message": "Handle contains invalid characters"}), 86 ), 87 ) 88 .into_response(); 89 } 90 let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string()); 91 let handle = if !input_handle.contains('.') { 92 format!("{}.{}", input_handle, hostname) 93 } else { 94 input_handle.to_string() 95 }; 96 let old_handle = sqlx::query_scalar!("SELECT handle FROM users WHERE did = $1", did) 97 .fetch_optional(&state.db) 98 .await 99 .ok() 100 .flatten(); 101 let existing = sqlx::query!( 102 "SELECT id FROM users WHERE handle = $1 AND did != $2", 103 handle, 104 did 105 ) 106 .fetch_optional(&state.db) 107 .await; 108 if let Ok(Some(_)) = existing { 109 return ( 110 StatusCode::BAD_REQUEST, 111 Json(json!({"error": "HandleTaken", "message": "Handle is already in use"})), 112 ) 113 .into_response(); 114 } 115 let result = sqlx::query!("UPDATE users SET handle = $1 WHERE did = $2", handle, did) 116 .execute(&state.db) 117 .await; 118 match result { 119 Ok(r) => { 120 if r.rows_affected() == 0 { 121 return ( 122 StatusCode::NOT_FOUND, 123 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 124 ) 125 .into_response(); 126 } 127 if let Some(old) = old_handle { 128 let _ = state.cache.delete(&format!("handle:{}", old)).await; 129 } 130 let _ = state.cache.delete(&format!("handle:{}", handle)).await; 131 if let Err(e) = 132 crate::api::repo::record::sequence_identity_event(&state, did, Some(&handle)).await 133 { 134 warn!( 135 "Failed to sequence identity event for admin handle update: {}", 136 e 137 ); 138 } 139 if let Err(e) = crate::api::identity::did::update_plc_handle(&state, did, &handle).await 140 { 141 warn!("Failed to update PLC handle for admin handle update: {}", e); 142 } 143 (StatusCode::OK, Json(json!({}))).into_response() 144 } 145 Err(e) => { 146 error!("DB error updating handle: {:?}", e); 147 ( 148 StatusCode::INTERNAL_SERVER_ERROR, 149 Json(json!({"error": "InternalError"})), 150 ) 151 .into_response() 152 } 153 } 154} 155 156#[derive(Deserialize)] 157pub struct UpdateAccountPasswordInput { 158 pub did: String, 159 pub password: String, 160} 161 162pub async fn update_account_password( 163 State(state): State<AppState>, 164 _auth: BearerAuthAdmin, 165 Json(input): Json<UpdateAccountPasswordInput>, 166) -> Response { 167 let did = input.did.trim(); 168 let password = input.password.trim(); 169 if did.is_empty() || password.is_empty() { 170 return ( 171 StatusCode::BAD_REQUEST, 172 Json(json!({"error": "InvalidRequest", "message": "did and password are required"})), 173 ) 174 .into_response(); 175 } 176 let password_hash = match bcrypt::hash(password, bcrypt::DEFAULT_COST) { 177 Ok(h) => h, 178 Err(e) => { 179 error!("Failed to hash password: {:?}", e); 180 return ( 181 StatusCode::INTERNAL_SERVER_ERROR, 182 Json(json!({"error": "InternalError"})), 183 ) 184 .into_response(); 185 } 186 }; 187 let result = sqlx::query!( 188 "UPDATE users SET password_hash = $1 WHERE did = $2", 189 password_hash, 190 did 191 ) 192 .execute(&state.db) 193 .await; 194 match result { 195 Ok(r) => { 196 if r.rows_affected() == 0 { 197 return ( 198 StatusCode::NOT_FOUND, 199 Json(json!({"error": "AccountNotFound", "message": "Account not found"})), 200 ) 201 .into_response(); 202 } 203 (StatusCode::OK, Json(json!({}))).into_response() 204 } 205 Err(e) => { 206 error!("DB error updating password: {:?}", e); 207 ( 208 StatusCode::INTERNAL_SERVER_ERROR, 209 Json(json!({"error": "InternalError"})), 210 ) 211 .into_response() 212 } 213 } 214}