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