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}