···11+use crate::api::ApiError;
22+use crate::plc::{signing_key_to_did_key, validate_plc_operation, PlcClient};
33+use crate::state::AppState;
44+use axum::{
55+ extract::State,
66+ http::StatusCode,
77+ response::{IntoResponse, Response},
88+ Json,
99+};
1010+use k256::ecdsa::SigningKey;
1111+use serde::Deserialize;
1212+use serde_json::{json, Value};
1313+use tracing::{error, info, warn};
1414+1515+#[derive(Debug, Deserialize)]
1616+pub struct SubmitPlcOperationInput {
1717+ pub operation: Value,
1818+}
1919+2020+pub async fn submit_plc_operation(
2121+ State(state): State<AppState>,
2222+ headers: axum::http::HeaderMap,
2323+ Json(input): Json<SubmitPlcOperationInput>,
2424+) -> Response {
2525+ let bearer = match crate::auth::extract_bearer_token_from_header(
2626+ headers.get("Authorization").and_then(|h| h.to_str().ok()),
2727+ ) {
2828+ Some(t) => t,
2929+ None => return ApiError::AuthenticationRequired.into_response(),
3030+ };
3131+3232+ let auth_user = match crate::auth::validate_bearer_token(&state.db, &bearer).await {
3333+ Ok(user) => user,
3434+ Err(e) => return ApiError::from(e).into_response(),
3535+ };
3636+3737+ let did = &auth_user.did;
3838+3939+ if let Err(e) = validate_plc_operation(&input.operation) {
4040+ return ApiError::InvalidRequest(format!("Invalid operation: {}", e)).into_response();
4141+ }
4242+4343+ let op = &input.operation;
4444+ let hostname = std::env::var("PDS_HOSTNAME").unwrap_or_else(|_| "localhost".to_string());
4545+ let public_url = format!("https://{}", hostname);
4646+4747+ let user = match sqlx::query!("SELECT id, handle FROM users WHERE did = $1", did)
4848+ .fetch_optional(&state.db)
4949+ .await
5050+ {
5151+ Ok(Some(row)) => row,
5252+ _ => {
5353+ return (
5454+ StatusCode::NOT_FOUND,
5555+ Json(json!({"error": "AccountNotFound"})),
5656+ )
5757+ .into_response();
5858+ }
5959+ };
6060+6161+ let key_row = match sqlx::query!(
6262+ "SELECT key_bytes, encryption_version FROM user_keys WHERE user_id = $1",
6363+ user.id
6464+ )
6565+ .fetch_optional(&state.db)
6666+ .await
6767+ {
6868+ Ok(Some(row)) => row,
6969+ _ => {
7070+ return (
7171+ StatusCode::INTERNAL_SERVER_ERROR,
7272+ Json(json!({"error": "InternalError", "message": "User signing key not found"})),
7373+ )
7474+ .into_response();
7575+ }
7676+ };
7777+7878+ let key_bytes = match crate::config::decrypt_key(&key_row.key_bytes, key_row.encryption_version)
7979+ {
8080+ Ok(k) => k,
8181+ Err(e) => {
8282+ error!("Failed to decrypt user key: {}", e);
8383+ return (
8484+ StatusCode::INTERNAL_SERVER_ERROR,
8585+ Json(json!({"error": "InternalError"})),
8686+ )
8787+ .into_response();
8888+ }
8989+ };
9090+9191+ let signing_key = match SigningKey::from_slice(&key_bytes) {
9292+ Ok(k) => k,
9393+ Err(e) => {
9494+ error!("Failed to create signing key: {:?}", e);
9595+ return (
9696+ StatusCode::INTERNAL_SERVER_ERROR,
9797+ Json(json!({"error": "InternalError"})),
9898+ )
9999+ .into_response();
100100+ }
101101+ };
102102+103103+ let user_did_key = signing_key_to_did_key(&signing_key);
104104+105105+ if let Some(rotation_keys) = op.get("rotationKeys").and_then(|v| v.as_array()) {
106106+ let server_rotation_key =
107107+ std::env::var("PLC_ROTATION_KEY").unwrap_or_else(|_| user_did_key.clone());
108108+109109+ let has_server_key = rotation_keys
110110+ .iter()
111111+ .any(|k| k.as_str() == Some(&server_rotation_key));
112112+113113+ if !has_server_key {
114114+ return (
115115+ StatusCode::BAD_REQUEST,
116116+ Json(json!({
117117+ "error": "InvalidRequest",
118118+ "message": "Rotation keys do not include server's rotation key"
119119+ })),
120120+ )
121121+ .into_response();
122122+ }
123123+ }
124124+125125+ if let Some(services) = op.get("services").and_then(|v| v.as_object()) {
126126+ if let Some(pds) = services.get("atproto_pds").and_then(|v| v.as_object()) {
127127+ let service_type = pds.get("type").and_then(|v| v.as_str());
128128+ let endpoint = pds.get("endpoint").and_then(|v| v.as_str());
129129+130130+ if service_type != Some("AtprotoPersonalDataServer") {
131131+ return (
132132+ StatusCode::BAD_REQUEST,
133133+ Json(json!({
134134+ "error": "InvalidRequest",
135135+ "message": "Incorrect type on atproto_pds service"
136136+ })),
137137+ )
138138+ .into_response();
139139+ }
140140+141141+ if endpoint != Some(&public_url) {
142142+ return (
143143+ StatusCode::BAD_REQUEST,
144144+ Json(json!({
145145+ "error": "InvalidRequest",
146146+ "message": "Incorrect endpoint on atproto_pds service"
147147+ })),
148148+ )
149149+ .into_response();
150150+ }
151151+ }
152152+ }
153153+154154+ if let Some(verification_methods) = op.get("verificationMethods").and_then(|v| v.as_object()) {
155155+ if let Some(atproto_key) = verification_methods.get("atproto").and_then(|v| v.as_str()) {
156156+ if atproto_key != user_did_key {
157157+ return (
158158+ StatusCode::BAD_REQUEST,
159159+ Json(json!({
160160+ "error": "InvalidRequest",
161161+ "message": "Incorrect signing key in verificationMethods"
162162+ })),
163163+ )
164164+ .into_response();
165165+ }
166166+ }
167167+ }
168168+169169+ if let Some(also_known_as) = op.get("alsoKnownAs").and_then(|v| v.as_array()) {
170170+ let expected_handle = format!("at://{}", user.handle);
171171+ let first_aka = also_known_as.first().and_then(|v| v.as_str());
172172+173173+ if first_aka != Some(&expected_handle) {
174174+ return (
175175+ StatusCode::BAD_REQUEST,
176176+ Json(json!({
177177+ "error": "InvalidRequest",
178178+ "message": "Incorrect handle in alsoKnownAs"
179179+ })),
180180+ )
181181+ .into_response();
182182+ }
183183+ }
184184+185185+ let plc_client = PlcClient::new(None);
186186+ if let Err(e) = plc_client.send_operation(did, &input.operation).await {
187187+ error!("Failed to submit PLC operation: {:?}", e);
188188+ return (
189189+ StatusCode::BAD_GATEWAY,
190190+ Json(json!({
191191+ "error": "UpstreamError",
192192+ "message": format!("Failed to submit to PLC directory: {}", e)
193193+ })),
194194+ )
195195+ .into_response();
196196+ }
197197+198198+ if let Err(e) = sqlx::query!(
199199+ "INSERT INTO repo_seq (did, event_type) VALUES ($1, 'identity')",
200200+ did
201201+ )
202202+ .execute(&state.db)
203203+ .await
204204+ {
205205+ warn!("Failed to sequence identity event: {:?}", e);
206206+ }
207207+208208+ info!("Submitted PLC operation for user {}", did);
209209+210210+ (StatusCode::OK, Json(json!({}))).into_response()
211211+}
+4
src/api/mod.rs
···11pub mod actor;
22pub mod admin;
33+pub mod error;
34pub mod feed;
45pub mod identity;
56pub mod moderation;
67pub mod proxy;
78pub mod repo;
89pub mod server;
1010+pub mod validation;
1111+1212+pub use error::ApiError;
···294294 };
295295296296 let new_mst = if existing_cid.is_some() {
297297- mst.update(&key, record_cid).await.unwrap()
297297+ match mst.update(&key, record_cid).await {
298298+ Ok(m) => m,
299299+ Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to update MST"}))).into_response(),
300300+ }
298301 } else {
299299- mst.add(&key, record_cid).await.unwrap()
302302+ match mst.add(&key, record_cid).await {
303303+ Ok(m) => m,
304304+ Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to add to MST"}))).into_response(),
305305+ }
306306+ };
307307+ let new_mst_root = match new_mst.persist().await {
308308+ Ok(c) => c,
309309+ Err(_) => return (StatusCode::INTERNAL_SERVER_ERROR, Json(json!({"error": "InternalError", "message": "Failed to persist MST"}))).into_response(),
300310 };
301301- let new_mst_root = new_mst.persist().await.unwrap();
302311303312 let op = if existing_cid.is_some() {
304313 RecordOp::Update { collection: input.collection.clone(), rkey: input.rkey.clone(), cid: record_cid }
+13-64
src/api/server/account_status.rs
···11+use crate::api::ApiError;
12use crate::state::AppState;
23use axum::{
34 Json,
···3435 headers.get("Authorization").and_then(|h| h.to_str().ok())
3536 ) {
3637 Some(t) => t,
3737- None => {
3838- return (
3939- StatusCode::UNAUTHORIZED,
4040- Json(json!({"error": "AuthenticationRequired"})),
4141- )
4242- .into_response();
4343- }
3838+ None => return ApiError::AuthenticationRequired.into_response(),
4439 };
45404646- let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await;
4747- let did = match auth_result {
4141+ let did = match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await {
4842 Ok(user) => user.did,
4949- Err(e) => {
5050- return (
5151- StatusCode::UNAUTHORIZED,
5252- Json(json!({"error": e})),
5353- )
5454- .into_response();
5555- }
4343+ Err(e) => return ApiError::from(e).into_response(),
5644 };
57455846 let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
···127115 headers.get("Authorization").and_then(|h| h.to_str().ok())
128116 ) {
129117 Some(t) => t,
130130- None => {
131131- return (
132132- StatusCode::UNAUTHORIZED,
133133- Json(json!({"error": "AuthenticationRequired"})),
134134- )
135135- .into_response();
136136- }
118118+ None => return ApiError::AuthenticationRequired.into_response(),
137119 };
138120139139- let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await;
140140- let did = match auth_result {
121121+ let did = match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await {
141122 Ok(user) => user.did,
142142- Err(e) => {
143143- return (
144144- StatusCode::UNAUTHORIZED,
145145- Json(json!({"error": e})),
146146- )
147147- .into_response();
148148- }
123123+ Err(e) => return ApiError::from(e).into_response(),
149124 };
150125151126 let result = sqlx::query!("UPDATE users SET deactivated_at = NULL WHERE did = $1", did)
···180155 headers.get("Authorization").and_then(|h| h.to_str().ok())
181156 ) {
182157 Some(t) => t,
183183- None => {
184184- return (
185185- StatusCode::UNAUTHORIZED,
186186- Json(json!({"error": "AuthenticationRequired"})),
187187- )
188188- .into_response();
189189- }
158158+ None => return ApiError::AuthenticationRequired.into_response(),
190159 };
191160192192- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
193193- let did = match auth_result {
161161+ let did = match crate::auth::validate_bearer_token(&state.db, &token).await {
194162 Ok(user) => user.did,
195195- Err(e) => {
196196- return (
197197- StatusCode::UNAUTHORIZED,
198198- Json(json!({"error": e})),
199199- )
200200- .into_response();
201201- }
163163+ Err(e) => return ApiError::from(e).into_response(),
202164 };
203165204166 let result = sqlx::query!("UPDATE users SET deactivated_at = NOW() WHERE did = $1", did)
···226188 headers.get("Authorization").and_then(|h| h.to_str().ok())
227189 ) {
228190 Some(t) => t,
229229- None => {
230230- return (
231231- StatusCode::UNAUTHORIZED,
232232- Json(json!({"error": "AuthenticationRequired"})),
233233- )
234234- .into_response();
235235- }
191191+ None => return ApiError::AuthenticationRequired.into_response(),
236192 };
237193238238- let auth_result = crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await;
239239- let did = match auth_result {
194194+ let did = match crate::auth::validate_bearer_token_allow_deactivated(&state.db, &token).await {
240195 Ok(user) => user.did,
241241- Err(e) => {
242242- return (
243243- StatusCode::UNAUTHORIZED,
244244- Json(json!({"error": e})),
245245- )
246246- .into_response();
247247- }
196196+ Err(e) => return ApiError::from(e).into_response(),
248197 };
249198250199 let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
+63-190
src/api/server/app_password.rs
···11+use crate::api::ApiError;
22+use crate::auth::BearerAuth;
13use crate::state::AppState;
44+use crate::util::get_user_id_by_did;
25use axum::{
36 Json,
47 extract::State,
55- http::StatusCode,
68 response::{IntoResponse, Response},
79};
810use serde::{Deserialize, Serialize};
···24262527pub async fn list_app_passwords(
2628 State(state): State<AppState>,
2727- headers: axum::http::HeaderMap,
2929+ BearerAuth(auth_user): BearerAuth,
2830) -> Response {
2929- let token = match crate::auth::extract_bearer_token_from_header(
3030- headers.get("Authorization").and_then(|h| h.to_str().ok())
3131- ) {
3232- Some(t) => t,
3333- None => {
3434- return (
3535- StatusCode::UNAUTHORIZED,
3636- Json(json!({"error": "AuthenticationRequired"})),
3737- )
3838- .into_response();
3939- }
4040- };
4141-4242- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
4343- let did = match auth_result {
4444- Ok(user) => user.did,
4545- Err(e) => {
4646- return (
4747- StatusCode::UNAUTHORIZED,
4848- Json(json!({"error": e})),
4949- )
5050- .into_response();
5151- }
3131+ let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await {
3232+ Ok(id) => id,
3333+ Err(e) => return ApiError::from(e).into_response(),
5234 };
53355454- let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
5555- .fetch_optional(&state.db)
5656- .await
3636+ match sqlx::query!(
3737+ "SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC",
3838+ user_id
3939+ )
4040+ .fetch_all(&state.db)
4141+ .await
5742 {
5858- Ok(Some(id)) => id,
5959- _ => {
6060- return (
6161- StatusCode::INTERNAL_SERVER_ERROR,
6262- Json(json!({"error": "InternalError"})),
6363- )
6464- .into_response();
6565- }
6666- };
6767-6868- let result = sqlx::query!("SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", user_id)
6969- .fetch_all(&state.db)
7070- .await;
7171-7272- match result {
7343 Ok(rows) => {
7444 let passwords: Vec<AppPassword> = rows
7545 .iter()
7676- .map(|row| {
7777- AppPassword {
7878- name: row.name.clone(),
7979- created_at: row.created_at.to_rfc3339(),
8080- privileged: row.privileged,
8181- }
4646+ .map(|row| AppPassword {
4747+ name: row.name.clone(),
4848+ created_at: row.created_at.to_rfc3339(),
4949+ privileged: row.privileged,
8250 })
8351 .collect();
84528585- (StatusCode::OK, Json(ListAppPasswordsOutput { passwords })).into_response()
5353+ Json(ListAppPasswordsOutput { passwords }).into_response()
8654 }
8755 Err(e) => {
8856 error!("DB error listing app passwords: {:?}", e);
8989- (
9090- StatusCode::INTERNAL_SERVER_ERROR,
9191- Json(json!({"error": "InternalError"})),
9292- )
9393- .into_response()
5757+ ApiError::InternalError.into_response()
9458 }
9559 }
9660}
···1127611377pub async fn create_app_password(
11478 State(state): State<AppState>,
115115- headers: axum::http::HeaderMap,
7979+ BearerAuth(auth_user): BearerAuth,
11680 Json(input): Json<CreateAppPasswordInput>,
11781) -> Response {
118118- let token = match crate::auth::extract_bearer_token_from_header(
119119- headers.get("Authorization").and_then(|h| h.to_str().ok())
120120- ) {
121121- Some(t) => t,
122122- None => {
123123- return (
124124- StatusCode::UNAUTHORIZED,
125125- Json(json!({"error": "AuthenticationRequired"})),
126126- )
127127- .into_response();
128128- }
129129- };
130130-131131- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
132132- let did = match auth_result {
133133- Ok(user) => user.did,
134134- Err(e) => {
135135- return (
136136- StatusCode::UNAUTHORIZED,
137137- Json(json!({"error": e})),
138138- )
139139- .into_response();
140140- }
141141- };
142142-143143- let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
144144- .fetch_optional(&state.db)
145145- .await
146146- {
147147- Ok(Some(id)) => id,
148148- _ => {
149149- return (
150150- StatusCode::INTERNAL_SERVER_ERROR,
151151- Json(json!({"error": "InternalError"})),
152152- )
153153- .into_response();
154154- }
8282+ let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await {
8383+ Ok(id) => id,
8484+ Err(e) => return ApiError::from(e).into_response(),
15585 };
1568615787 let name = input.name.trim();
15888 if name.is_empty() {
159159- return (
160160- StatusCode::BAD_REQUEST,
161161- Json(json!({"error": "InvalidRequest", "message": "name is required"})),
162162- )
163163- .into_response();
8989+ return ApiError::InvalidRequest("name is required".into()).into_response();
16490 }
16591166166- let existing = sqlx::query!("SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2", user_id, name)
167167- .fetch_optional(&state.db)
168168- .await;
9292+ let existing = sqlx::query!(
9393+ "SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2",
9494+ user_id,
9595+ name
9696+ )
9797+ .fetch_optional(&state.db)
9898+ .await;
16999170100 if let Ok(Some(_)) = existing {
171171- return (
172172- StatusCode::BAD_REQUEST,
173173- Json(json!({"error": "DuplicateAppPassword", "message": "App password with this name already exists"})),
174174- )
175175- .into_response();
101101+ return ApiError::DuplicateAppPassword.into_response();
176102 }
177103178104 let password: String = (0..4)
···180106 use rand::Rng;
181107 let mut rng = rand::thread_rng();
182108 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz234567".chars().collect();
183183- (0..4).map(|_| chars[rng.gen_range(0..chars.len())]).collect::<String>()
109109+ (0..4)
110110+ .map(|_| chars[rng.gen_range(0..chars.len())])
111111+ .collect::<String>()
184112 })
185113 .collect::<Vec<String>>()
186114 .join("-");
···189117 Ok(h) => h,
190118 Err(e) => {
191119 error!("Failed to hash password: {:?}", e);
192192- return (
193193- StatusCode::INTERNAL_SERVER_ERROR,
194194- Json(json!({"error": "InternalError"})),
195195- )
196196- .into_response();
120120+ return ApiError::InternalError.into_response();
197121 }
198122 };
199123200124 let privileged = input.privileged.unwrap_or(false);
201125 let created_at = chrono::Utc::now();
202126203203- let result = sqlx::query!(
127127+ match sqlx::query!(
204128 "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged) VALUES ($1, $2, $3, $4, $5)",
205129 user_id,
206130 name,
···209133 privileged
210134 )
211135 .execute(&state.db)
212212- .await;
213213-214214- match result {
215215- Ok(_) => (
216216- StatusCode::OK,
217217- Json(CreateAppPasswordOutput {
218218- name: name.to_string(),
219219- password,
220220- created_at: created_at.to_rfc3339(),
221221- privileged,
222222- }),
223223- )
224224- .into_response(),
136136+ .await
137137+ {
138138+ Ok(_) => Json(CreateAppPasswordOutput {
139139+ name: name.to_string(),
140140+ password,
141141+ created_at: created_at.to_rfc3339(),
142142+ privileged,
143143+ })
144144+ .into_response(),
225145 Err(e) => {
226146 error!("DB error creating app password: {:?}", e);
227227- (
228228- StatusCode::INTERNAL_SERVER_ERROR,
229229- Json(json!({"error": "InternalError"})),
230230- )
231231- .into_response()
147147+ ApiError::InternalError.into_response()
232148 }
233149 }
234150}
···240156241157pub async fn revoke_app_password(
242158 State(state): State<AppState>,
243243- headers: axum::http::HeaderMap,
159159+ BearerAuth(auth_user): BearerAuth,
244160 Json(input): Json<RevokeAppPasswordInput>,
245161) -> Response {
246246- let token = match crate::auth::extract_bearer_token_from_header(
247247- headers.get("Authorization").and_then(|h| h.to_str().ok())
248248- ) {
249249- Some(t) => t,
250250- None => {
251251- return (
252252- StatusCode::UNAUTHORIZED,
253253- Json(json!({"error": "AuthenticationRequired"})),
254254- )
255255- .into_response();
256256- }
257257- };
258258-259259- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
260260- let did = match auth_result {
261261- Ok(user) => user.did,
262262- Err(e) => {
263263- return (
264264- StatusCode::UNAUTHORIZED,
265265- Json(json!({"error": e})),
266266- )
267267- .into_response();
268268- }
269269- };
270270-271271- let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
272272- .fetch_optional(&state.db)
273273- .await
274274- {
275275- Ok(Some(id)) => id,
276276- _ => {
277277- return (
278278- StatusCode::INTERNAL_SERVER_ERROR,
279279- Json(json!({"error": "InternalError"})),
280280- )
281281- .into_response();
282282- }
162162+ let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await {
163163+ Ok(id) => id,
164164+ Err(e) => return ApiError::from(e).into_response(),
283165 };
284166285167 let name = input.name.trim();
286168 if name.is_empty() {
287287- return (
288288- StatusCode::BAD_REQUEST,
289289- Json(json!({"error": "InvalidRequest", "message": "name is required"})),
290290- )
291291- .into_response();
169169+ return ApiError::InvalidRequest("name is required".into()).into_response();
292170 }
293171294294- let result = sqlx::query!("DELETE FROM app_passwords WHERE user_id = $1 AND name = $2", user_id, name)
295295- .execute(&state.db)
296296- .await;
297297-298298- match result {
172172+ match sqlx::query!(
173173+ "DELETE FROM app_passwords WHERE user_id = $1 AND name = $2",
174174+ user_id,
175175+ name
176176+ )
177177+ .execute(&state.db)
178178+ .await
179179+ {
299180 Ok(r) => {
300181 if r.rows_affected() == 0 {
301301- return (
302302- StatusCode::NOT_FOUND,
303303- Json(json!({"error": "AppPasswordNotFound", "message": "App password not found"})),
304304- )
305305- .into_response();
182182+ return ApiError::AppPasswordNotFound.into_response();
306183 }
307307- (StatusCode::OK, Json(json!({}))).into_response()
184184+ Json(json!({})).into_response()
308185 }
309186 Err(e) => {
310187 error!("DB error revoking app password: {:?}", e);
311311- (
312312- StatusCode::INTERNAL_SERVER_ERROR,
313313- Json(json!({"error": "InternalError"})),
314314- )
315315- .into_response()
188188+ ApiError::InternalError.into_response()
316189 }
317190 }
318191}
+47-54
src/api/server/email.rs
···11+use crate::api::ApiError;
12use crate::state::AppState;
23use axum::{
34 Json,
···67 response::{IntoResponse, Response},
78};
89use chrono::{Duration, Utc};
99-use rand::Rng;
1010use serde::Deserialize;
1111use serde_json::json;
1212use tracing::{error, info, warn};
13131414fn generate_confirmation_code() -> String {
1515- let mut rng = rand::thread_rng();
1616- let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz234567".chars().collect();
1717- let part1: String = (0..5).map(|_| chars[rng.gen_range(0..chars.len())]).collect();
1818- let part2: String = (0..5).map(|_| chars[rng.gen_range(0..chars.len())]).collect();
1919- format!("{}-{}", part1, part2)
1515+ crate::util::generate_token_code()
2016}
21172218#[derive(Deserialize)]
···4642 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
4743 let did = match auth_result {
4844 Ok(user) => user.did,
4949- Err(e) => {
5050- return (
5151- StatusCode::UNAUTHORIZED,
5252- Json(json!({"error": e})),
5353- )
5454- .into_response();
5555- }
4545+ Err(e) => return ApiError::from(e).into_response(),
5646 };
57475848 let user = match sqlx::query!("SELECT id, handle FROM users WHERE did = $1", did)
···7262 let handle = user.handle;
73637464 let email = input.email.trim().to_lowercase();
7575- if email.is_empty() {
6565+ if !crate::api::validation::is_valid_email(&email) {
7666 return (
7767 StatusCode::BAD_REQUEST,
7878- Json(json!({"error": "InvalidRequest", "message": "email is required"})),
6868+ Json(json!({"error": "InvalidEmail", "message": "Invalid email format"})),
7969 )
8070 .into_response();
8171 }
···161151 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
162152 let did = match auth_result {
163153 Ok(user) => user.did,
164164- Err(e) => {
165165- return (
166166- StatusCode::UNAUTHORIZED,
167167- Json(json!({"error": e})),
168168- )
169169- .into_response();
170170- }
154154+ Err(e) => return ApiError::from(e).into_response(),
171155 };
172156173157 let user = match sqlx::query!(
···194178 let email = input.email.trim().to_lowercase();
195179 let confirmation_code = input.token.trim();
196180197197- if email_pending_verification.is_none() || stored_code.is_none() || expires_at.is_none() {
198198- return (
199199- StatusCode::BAD_REQUEST,
200200- Json(json!({"error": "InvalidRequest", "message": "No pending email update found"})),
201201- )
202202- .into_response();
203203- }
181181+ let (pending_email, saved_code, expiry) = match (email_pending_verification, stored_code, expires_at) {
182182+ (Some(p), Some(c), Some(e)) => (p, c, e),
183183+ _ => {
184184+ return (
185185+ StatusCode::BAD_REQUEST,
186186+ Json(json!({"error": "InvalidRequest", "message": "No pending email update found"})),
187187+ )
188188+ .into_response();
189189+ }
190190+ };
204191205205- let email_pending_verification = email_pending_verification.unwrap();
206206- if email_pending_verification != email {
192192+ if pending_email != email {
207193 return (
208194 StatusCode::BAD_REQUEST,
209195 Json(json!({"error": "InvalidRequest", "message": "Email does not match pending update"})),
···211197 .into_response();
212198 }
213199214214- if stored_code.unwrap() != confirmation_code {
200200+ if saved_code != confirmation_code {
215201 return (
216202 StatusCode::BAD_REQUEST,
217203 Json(json!({"error": "InvalidToken", "message": "Invalid token"})),
···219205 .into_response();
220206 }
221207222222- if Utc::now() > expires_at.unwrap() {
208208+ if Utc::now() > expiry {
223209 return (
224210 StatusCode::BAD_REQUEST,
225211 Json(json!({"error": "ExpiredToken", "message": "Token has expired"})),
···229215230216 let update = sqlx::query!(
231217 "UPDATE users SET email = $1, email_pending_verification = NULL, email_confirmation_code = NULL, email_confirmation_code_expires_at = NULL WHERE id = $2",
232232- email_pending_verification,
218218+ pending_email,
233219 user_id
234220 )
235221 .execute(&state.db)
···287273 let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
288274 let did = match auth_result {
289275 Ok(user) => user.did,
290290- Err(e) => {
291291- return (
292292- StatusCode::UNAUTHORIZED,
293293- Json(json!({"error": e})),
294294- )
295295- .into_response();
296296- }
276276+ Err(e) => return ApiError::from(e).into_response(),
297277 };
298278299279 let user = match sqlx::query!(
···319299 let email_pending_verification = user.email_pending_verification;
320300321301 let new_email = input.email.trim().to_lowercase();
322322- if new_email.is_empty() {
302302+ if !crate::api::validation::is_valid_email(&new_email) {
323303 return (
324304 StatusCode::BAD_REQUEST,
325325- Json(json!({"error": "InvalidRequest", "message": "email is required"})),
326326- )
327327- .into_response();
328328- }
329329-330330- if !new_email.contains('@') || !new_email.contains('.') {
331331- return (
332332- StatusCode::BAD_REQUEST,
333333- Json(json!({"error": "InvalidRequest", "message": "Invalid email format"})),
305305+ Json(json!({"error": "InvalidEmail", "message": "Invalid email format"})),
334306 )
335307 .into_response();
336308 }
···353325 }
354326 };
355327356356- let pending_email = email_pending_verification.unwrap();
328328+ let pending_email = match email_pending_verification {
329329+ Some(p) => p,
330330+ None => {
331331+ return (
332332+ StatusCode::BAD_REQUEST,
333333+ Json(json!({"error": "InvalidRequest", "message": "No pending email update found"})),
334334+ )
335335+ .into_response();
336336+ }
337337+ };
338338+357339 if pending_email.to_lowercase() != new_email {
358340 return (
359341 StatusCode::BAD_REQUEST,
···362344 .into_response();
363345 }
364346365365- if stored_code.unwrap() != confirmation_token {
347347+ let saved_code = match stored_code {
348348+ Some(c) => c,
349349+ None => {
350350+ return (
351351+ StatusCode::BAD_REQUEST,
352352+ Json(json!({"error": "InvalidRequest", "message": "No pending email update found"})),
353353+ )
354354+ .into_response();
355355+ }
356356+ };
357357+358358+ if saved_code != confirmation_token {
366359 return (
367360 StatusCode::BAD_REQUEST,
368361 Json(json!({"error": "InvalidToken", "message": "Invalid token"})),
···415408416409 match update {
417410 Ok(_) => {
418418- info!("Email updated to {} for user {}", new_email, user_id);
411411+ info!("Email updated for user {}", user_id);
419412 (StatusCode::OK, Json(json!({}))).into_response()
420413 }
421414 Err(e) => {
+61-209
src/api/server/invite.rs
···11+use crate::api::ApiError;
22+use crate::auth::BearerAuth;
13use crate::state::AppState;
44+use crate::util::get_user_id_by_did;
25use axum::{
36 Json,
47 extract::State,
55- http::StatusCode,
68 response::{IntoResponse, Response},
79};
810use serde::{Deserialize, Serialize};
99-use serde_json::json;
1011use tracing::error;
1112use uuid::Uuid;
1213···24252526pub async fn create_invite_code(
2627 State(state): State<AppState>,
2727- headers: axum::http::HeaderMap,
2828+ BearerAuth(auth_user): BearerAuth,
2829 Json(input): Json<CreateInviteCodeInput>,
2930) -> Response {
3030- let token = match crate::auth::extract_bearer_token_from_header(
3131- headers.get("Authorization").and_then(|h| h.to_str().ok())
3232- ) {
3333- Some(t) => t,
3434- None => {
3535- return (
3636- StatusCode::UNAUTHORIZED,
3737- Json(json!({"error": "AuthenticationRequired"})),
3838- )
3939- .into_response();
4040- }
4141- };
4242-4331 if input.use_count < 1 {
4444- return (
4545- StatusCode::BAD_REQUEST,
4646- Json(json!({"error": "InvalidRequest", "message": "useCount must be at least 1"})),
4747- )
4848- .into_response();
3232+ return ApiError::InvalidRequest("useCount must be at least 1".into()).into_response();
4933 }
50345151- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
5252- let did = match auth_result {
5353- Ok(user) => user.did,
5454- Err(e) => {
5555- return (
5656- StatusCode::UNAUTHORIZED,
5757- Json(json!({"error": e})),
5858- )
5959- .into_response();
6060- }
6161- };
6262-6363- let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
6464- .fetch_optional(&state.db)
6565- .await
6666- {
6767- Ok(Some(id)) => id,
6868- _ => {
6969- return (
7070- StatusCode::INTERNAL_SERVER_ERROR,
7171- Json(json!({"error": "InternalError"})),
7272- )
7373- .into_response();
7474- }
3535+ let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await {
3636+ Ok(id) => id,
3737+ Err(e) => return ApiError::from(e).into_response(),
7538 };
76397740 let creator_user_id = if let Some(for_account) = &input.for_account {
7878- let target = sqlx::query!("SELECT id FROM users WHERE did = $1", for_account)
4141+ match sqlx::query!("SELECT id FROM users WHERE did = $1", for_account)
7942 .fetch_optional(&state.db)
8080- .await;
8181-8282- match target {
4343+ .await
4444+ {
8345 Ok(Some(row)) => row.id,
8484- Ok(None) => {
8585- return (
8686- StatusCode::NOT_FOUND,
8787- Json(json!({"error": "AccountNotFound", "message": "Target account not found"})),
8888- )
8989- .into_response();
9090- }
4646+ Ok(None) => return ApiError::AccountNotFound.into_response(),
9147 Err(e) => {
9248 error!("DB error looking up target account: {:?}", e);
9393- return (
9494- StatusCode::INTERNAL_SERVER_ERROR,
9595- Json(json!({"error": "InternalError"})),
9696- )
9797- .into_response();
4949+ return ApiError::InternalError.into_response();
9850 }
9951 }
10052 } else {
···1035510456 let user_invites_disabled = sqlx::query_scalar!(
10557 "SELECT invites_disabled FROM users WHERE did = $1",
106106- did
5858+ auth_user.did
10759 )
10860 .fetch_optional(&state.db)
10961 .await
6262+ .map_err(|e| {
6363+ error!("DB error checking invites_disabled: {:?}", e);
6464+ ApiError::InternalError
6565+ })
11066 .ok()
11167 .flatten()
11268 .flatten()
11369 .unwrap_or(false);
1147011571 if user_invites_disabled {
116116- return (
117117- StatusCode::FORBIDDEN,
118118- Json(json!({"error": "InvitesDisabled", "message": "Invites are disabled for this account"})),
119119- )
120120- .into_response();
7272+ return ApiError::InvitesDisabled.into_response();
12173 }
1227412375 let code = Uuid::new_v4().to_string();
12476125125- let result = sqlx::query!(
7777+ match sqlx::query!(
12678 "INSERT INTO invite_codes (code, available_uses, created_by_user) VALUES ($1, $2, $3)",
12779 code,
12880 input.use_count,
12981 creator_user_id
13082 )
13183 .execute(&state.db)
132132- .await;
133133-134134- match result {
135135- Ok(_) => (StatusCode::OK, Json(CreateInviteCodeOutput { code })).into_response(),
8484+ .await
8585+ {
8686+ Ok(_) => Json(CreateInviteCodeOutput { code }).into_response(),
13687 Err(e) => {
13788 error!("DB error creating invite code: {:?}", e);
138138- (
139139- StatusCode::INTERNAL_SERVER_ERROR,
140140- Json(json!({"error": "InternalError"})),
141141- )
142142- .into_response()
8989+ ApiError::InternalError.into_response()
14390 }
14491 }
14592}
···165112166113pub async fn create_invite_codes(
167114 State(state): State<AppState>,
168168- headers: axum::http::HeaderMap,
115115+ BearerAuth(auth_user): BearerAuth,
169116 Json(input): Json<CreateInviteCodesInput>,
170117) -> Response {
171171- let token = match crate::auth::extract_bearer_token_from_header(
172172- headers.get("Authorization").and_then(|h| h.to_str().ok())
173173- ) {
174174- Some(t) => t,
175175- None => {
176176- return (
177177- StatusCode::UNAUTHORIZED,
178178- Json(json!({"error": "AuthenticationRequired"})),
179179- )
180180- .into_response();
181181- }
182182- };
183183-184118 if input.use_count < 1 {
185185- return (
186186- StatusCode::BAD_REQUEST,
187187- Json(json!({"error": "InvalidRequest", "message": "useCount must be at least 1"})),
188188- )
189189- .into_response();
119119+ return ApiError::InvalidRequest("useCount must be at least 1".into()).into_response();
190120 }
191121192192- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
193193- let did = match auth_result {
194194- Ok(user) => user.did,
195195- Err(e) => {
196196- return (
197197- StatusCode::UNAUTHORIZED,
198198- Json(json!({"error": e})),
199199- )
200200- .into_response();
201201- }
202202- };
203203-204204- let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
205205- .fetch_optional(&state.db)
206206- .await
207207- {
208208- Ok(Some(id)) => id,
209209- _ => {
210210- return (
211211- StatusCode::INTERNAL_SERVER_ERROR,
212212- Json(json!({"error": "InternalError"})),
213213- )
214214- .into_response();
215215- }
122122+ let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await {
123123+ Ok(id) => id,
124124+ Err(e) => return ApiError::from(e).into_response(),
216125 };
217126218127 let code_count = input.code_count.unwrap_or(1).max(1);
···225134 for _ in 0..code_count {
226135 let code = Uuid::new_v4().to_string();
227136228228- let insert = sqlx::query!(
137137+ if let Err(e) = sqlx::query!(
229138 "INSERT INTO invite_codes (code, available_uses, created_by_user) VALUES ($1, $2, $3)",
230139 code,
231140 input.use_count,
232141 user_id
233142 )
234143 .execute(&state.db)
235235- .await;
236236-237237- if let Err(e) = insert {
144144+ .await
145145+ {
238146 error!("DB error creating invite code: {:?}", e);
239239- return (
240240- StatusCode::INTERNAL_SERVER_ERROR,
241241- Json(json!({"error": "InternalError"})),
242242- )
243243- .into_response();
147147+ return ApiError::InternalError.into_response();
244148 }
245149246150 codes.push(code);
···252156 });
253157 } else {
254158 for account_did in for_accounts {
255255- let target = sqlx::query!("SELECT id FROM users WHERE did = $1", account_did)
159159+ let target_user_id = match sqlx::query!("SELECT id FROM users WHERE did = $1", account_did)
256160 .fetch_optional(&state.db)
257257- .await;
258258-259259- let target_user_id = match target {
161161+ .await
162162+ {
260163 Ok(Some(row)) => row.id,
261261- Ok(None) => {
262262- continue;
263263- }
164164+ Ok(None) => continue,
264165 Err(e) => {
265166 error!("DB error looking up target account: {:?}", e);
266266- return (
267267- StatusCode::INTERNAL_SERVER_ERROR,
268268- Json(json!({"error": "InternalError"})),
269269- )
270270- .into_response();
167167+ return ApiError::InternalError.into_response();
271168 }
272169 };
273170···275172 for _ in 0..code_count {
276173 let code = Uuid::new_v4().to_string();
277174278278- let insert = sqlx::query!(
175175+ if let Err(e) = sqlx::query!(
279176 "INSERT INTO invite_codes (code, available_uses, created_by_user) VALUES ($1, $2, $3)",
280177 code,
281178 input.use_count,
282179 target_user_id
283180 )
284181 .execute(&state.db)
285285- .await;
286286-287287- if let Err(e) = insert {
182182+ .await
183183+ {
288184 error!("DB error creating invite code: {:?}", e);
289289- return (
290290- StatusCode::INTERNAL_SERVER_ERROR,
291291- Json(json!({"error": "InternalError"})),
292292- )
293293- .into_response();
185185+ return ApiError::InternalError.into_response();
294186 }
295187296188 codes.push(code);
···303195 }
304196 }
305197306306- (StatusCode::OK, Json(CreateInviteCodesOutput { codes: result_codes })).into_response()
198198+ Json(CreateInviteCodesOutput { codes: result_codes }).into_response()
307199}
308200309201#[derive(Deserialize)]
···339231340232pub async fn get_account_invite_codes(
341233 State(state): State<AppState>,
342342- headers: axum::http::HeaderMap,
234234+ BearerAuth(auth_user): BearerAuth,
343235 axum::extract::Query(params): axum::extract::Query<GetAccountInviteCodesParams>,
344236) -> Response {
345345- let token = match crate::auth::extract_bearer_token_from_header(
346346- headers.get("Authorization").and_then(|h| h.to_str().ok())
347347- ) {
348348- Some(t) => t,
349349- None => {
350350- return (
351351- StatusCode::UNAUTHORIZED,
352352- Json(json!({"error": "AuthenticationRequired"})),
353353- )
354354- .into_response();
355355- }
356356- };
357357-358358- let auth_result = crate::auth::validate_bearer_token(&state.db, &token).await;
359359- let did = match auth_result {
360360- Ok(user) => user.did,
361361- Err(e) => {
362362- return (
363363- StatusCode::UNAUTHORIZED,
364364- Json(json!({"error": e})),
365365- )
366366- .into_response();
367367- }
368368- };
369369-370370- let user_id = match sqlx::query_scalar!("SELECT id FROM users WHERE did = $1", did)
371371- .fetch_optional(&state.db)
372372- .await
373373- {
374374- Ok(Some(id)) => id,
375375- _ => {
376376- return (
377377- StatusCode::INTERNAL_SERVER_ERROR,
378378- Json(json!({"error": "InternalError"})),
379379- )
380380- .into_response();
381381- }
237237+ let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await {
238238+ Ok(id) => id,
239239+ Err(e) => return ApiError::from(e).into_response(),
382240 };
383241384242 let include_used = params.include_used.unwrap_or(true);
385243386386- let codes_result = sqlx::query!(
244244+ let codes_rows = match sqlx::query!(
387245 r#"
388246 SELECT code, available_uses, created_at, disabled
389247 FROM invite_codes
···393251 user_id
394252 )
395253 .fetch_all(&state.db)
396396- .await;
397397-398398- let codes_rows = match codes_result {
254254+ .await
255255+ {
399256 Ok(rows) => {
400257 if include_used {
401258 rows
···405262 }
406263 Err(e) => {
407264 error!("DB error fetching invite codes: {:?}", e);
408408- return (
409409- StatusCode::INTERNAL_SERVER_ERROR,
410410- Json(json!({"error": "InternalError"})),
411411- )
412412- .into_response();
265265+ return ApiError::InternalError.into_response();
413266 }
414267 };
415268416269 let mut codes = Vec::new();
417270 for row in codes_rows {
418418- let uses_result = sqlx::query!(
271271+ let uses = sqlx::query!(
419272 r#"
420273 SELECT u.did, icu.used_at
421274 FROM invite_code_uses icu
···426279 row.code
427280 )
428281 .fetch_all(&state.db)
429429- .await;
430430-431431- let uses = match uses_result {
432432- Ok(use_rows) => use_rows
282282+ .await
283283+ .map(|use_rows| {
284284+ use_rows
433285 .iter()
434286 .map(|u| InviteCodeUse {
435287 used_by: u.did.clone(),
436288 used_at: u.used_at.to_rfc3339(),
437289 })
438438- .collect(),
439439- Err(_) => Vec::new(),
440440- };
290290+ .collect()
291291+ })
292292+ .unwrap_or_default();
441293442294 codes.push(InviteCode {
443295 code: row.code,
444296 available: row.available_uses,
445297 disabled: row.disabled.unwrap_or(false),
446446- for_account: did.clone(),
447447- created_by: did.clone(),
298298+ for_account: auth_user.did.clone(),
299299+ created_by: auth_user.did.clone(),
448300 created_at: row.created_at.to_rfc3339(),
449301 uses,
450302 });
451303 }
452304453453- (StatusCode::OK, Json(GetAccountInviteCodesOutput { codes })).into_response()
305305+ Json(GetAccountInviteCodesOutput { codes }).into_response()
454306}
+3-3
src/api/server/mod.rs
···44pub mod invite;
55pub mod meta;
66pub mod password;
77+pub mod service_auth;
78pub mod session;
89pub mod signing_key;
910···1617pub use invite::{create_invite_code, create_invite_codes, get_account_invite_codes};
1718pub use meta::{describe_server, health};
1819pub use password::{request_password_reset, reset_password};
1919-pub use session::{
2020- create_session, delete_session, get_service_auth, get_session, refresh_session,
2121-};
2020+pub use service_auth::get_service_auth;
2121+pub use session::{create_session, delete_session, get_session, refresh_session};
2222pub use signing_key::reserve_signing_key;
+43-17
src/api/server/password.rs
···77};
88use bcrypt::{hash, DEFAULT_COST};
99use chrono::{Duration, Utc};
1010-use rand::Rng;
1110use serde::Deserialize;
1211use serde_json::json;
1312use tracing::{error, info, warn};
14131514fn generate_reset_code() -> String {
1616- let mut rng = rand::thread_rng();
1717- let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz234567".chars().collect();
1818- let part1: String = (0..5).map(|_| chars[rng.gen_range(0..chars.len())]).collect();
1919- let part2: String = (0..5).map(|_| chars[rng.gen_range(0..chars.len())]).collect();
2020- format!("{}-{}", part1, part2)
1515+ crate::util::generate_token_code()
2116}
22172318#[derive(Deserialize)]
···4540 let user_id = match user {
4641 Ok(Some(row)) => row.id,
4742 Ok(None) => {
4848- info!("Password reset requested for unknown email: {}", email);
4343+ info!("Password reset requested for unknown email");
4944 return (StatusCode::OK, Json(json!({}))).into_response();
5045 }
5146 Err(e) => {
···151146152147 if let Some(exp) = expires_at {
153148 if Utc::now() > exp {
154154- let _ = sqlx::query!(
149149+ if let Err(e) = sqlx::query!(
155150 "UPDATE users SET password_reset_code = NULL, password_reset_code_expires_at = NULL WHERE id = $1",
156151 user_id
157152 )
158153 .execute(&state.db)
159159- .await;
154154+ .await
155155+ {
156156+ error!("Failed to clear expired reset code: {:?}", e);
157157+ }
160158161159 return (
162160 StatusCode::BAD_REQUEST,
···184182 }
185183 };
186184187187- let update = sqlx::query!(
185185+ let mut tx = match state.db.begin().await {
186186+ Ok(tx) => tx,
187187+ Err(e) => {
188188+ error!("Failed to begin transaction: {:?}", e);
189189+ return (
190190+ StatusCode::INTERNAL_SERVER_ERROR,
191191+ Json(json!({"error": "InternalError"})),
192192+ )
193193+ .into_response();
194194+ }
195195+ };
196196+197197+ if let Err(e) = sqlx::query!(
188198 "UPDATE users SET password_hash = $1, password_reset_code = NULL, password_reset_code_expires_at = NULL WHERE id = $2",
189199 password_hash,
190200 user_id
191201 )
192192- .execute(&state.db)
193193- .await;
194194-195195- if let Err(e) = update {
202202+ .execute(&mut *tx)
203203+ .await
204204+ {
196205 error!("DB error updating password: {:?}", e);
197206 return (
198207 StatusCode::INTERNAL_SERVER_ERROR,
···201210 .into_response();
202211 }
203212204204- let _ = sqlx::query!("DELETE FROM session_tokens WHERE did = (SELECT did FROM users WHERE id = $1)", user_id)
205205- .execute(&state.db)
206206- .await;
213213+ if let Err(e) = sqlx::query!("DELETE FROM session_tokens WHERE did = (SELECT did FROM users WHERE id = $1)", user_id)
214214+ .execute(&mut *tx)
215215+ .await
216216+ {
217217+ error!("Failed to invalidate sessions after password reset: {:?}", e);
218218+ return (
219219+ StatusCode::INTERNAL_SERVER_ERROR,
220220+ Json(json!({"error": "InternalError"})),
221221+ )
222222+ .into_response();
223223+ }
224224+225225+ if let Err(e) = tx.commit().await {
226226+ error!("Failed to commit password reset transaction: {:?}", e);
227227+ return (
228228+ StatusCode::INTERNAL_SERVER_ERROR,
229229+ Json(json!({"error": "InternalError"})),
230230+ )
231231+ .into_response();
232232+ }
207233208234 info!("Password reset completed for user {}", user_id);
209235
···6262 let seed = hasher.finalize();
63636464 let signing_key = SigningKey::from_slice(&seed)
6565- .expect("Failed to create signing key from seed");
6565+ .unwrap_or_else(|e| panic!("Failed to create signing key from seed: {}. This is a bug.", e));
66666767 let verifying_key = signing_key.verifying_key();
6868 let point = verifying_key.to_encoded_point(false);
69697070- let signing_key_x = URL_SAFE_NO_PAD.encode(point.x().unwrap());
7171- let signing_key_y = URL_SAFE_NO_PAD.encode(point.y().unwrap());
7070+ let signing_key_x = URL_SAFE_NO_PAD.encode(
7171+ point.x().expect("EC point missing X coordinate - this should never happen")
7272+ );
7373+ let signing_key_y = URL_SAFE_NO_PAD.encode(
7474+ point.y().expect("EC point missing Y coordinate - this should never happen")
7575+ );
72767377 let mut kid_hasher = Sha256::new();
7478 kid_hasher.update(signing_key_x.as_bytes());
+1
src/lib.rs
···88pub mod state;
99pub mod storage;
1010pub mod sync;
1111+pub mod util;
11121213use axum::{
1314 Router,
+43-15
src/main.rs
···11use bspds::notifications::{EmailSender, NotificationService};
22use bspds::state::AppState;
33use std::net::SocketAddr;
44+use std::process::ExitCode;
45use tokio::sync::watch;
55-use tracing::{info, warn};
66+use tracing::{error, info, warn};
6778#[tokio::main]
88-async fn main() {
99+async fn main() -> ExitCode {
910 dotenvy::dotenv().ok();
1011 tracing_subscriber::fmt::init();
11121212- let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
1313+ match run().await {
1414+ Ok(()) => ExitCode::SUCCESS,
1515+ Err(e) => {
1616+ error!("Fatal error: {}", e);
1717+ ExitCode::FAILURE
1818+ }
1919+ }
2020+}
2121+2222+async fn run() -> Result<(), Box<dyn std::error::Error>> {
2323+ let database_url = std::env::var("DATABASE_URL")
2424+ .map_err(|_| "DATABASE_URL environment variable must be set")?;
13251426 let pool = sqlx::postgres::PgPoolOptions::new()
1515- .max_connections(5)
2727+ .max_connections(20)
2828+ .min_connections(2)
2929+ .acquire_timeout(std::time::Duration::from_secs(10))
3030+ .idle_timeout(std::time::Duration::from_secs(300))
3131+ .max_lifetime(std::time::Duration::from_secs(1800))
1632 .connect(&database_url)
1733 .await
1818- .expect("Failed to connect to Postgres");
3434+ .map_err(|e| format!("Failed to connect to Postgres: {}", e))?;
19352036 sqlx::migrate!("./migrations")
2137 .run(&pool)
2238 .await
2323- .expect("Failed to run migrations");
3939+ .map_err(|e| format!("Failed to run migrations: {}", e))?;
24402541 let state = AppState::new(pool.clone()).await;
2642···50665167 let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
5268 info!("listening on {}", addr);
5353- let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
6969+ let listener = tokio::net::TcpListener::bind(addr)
7070+ .await
7171+ .map_err(|e| format!("Failed to bind to {}: {}", addr, e))?;
54725573 let server_result = axum::serve(listener, app)
5674 .with_graceful_shutdown(shutdown_signal(shutdown_tx))
···5977 notification_handle.await.ok();
60786179 if let Err(e) = server_result {
6262- tracing::error!("Server error: {}", e);
8080+ return Err(format!("Server error: {}", e).into());
6381 }
8282+8383+ Ok(())
6484}
65856686async fn shutdown_signal(shutdown_tx: watch::Sender<bool>) {
6787 let ctrl_c = async {
6868- tokio::signal::ctrl_c()
6969- .await
7070- .expect("Failed to install Ctrl+C handler");
8888+ match tokio::signal::ctrl_c().await {
8989+ Ok(()) => {}
9090+ Err(e) => {
9191+ error!("Failed to install Ctrl+C handler: {}", e);
9292+ }
9393+ }
7194 };
72957396 #[cfg(unix)]
7497 let terminate = async {
7575- tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
7676- .expect("Failed to install signal handler")
7777- .recv()
7878- .await;
9898+ match tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()) {
9999+ Ok(mut signal) => {
100100+ signal.recv().await;
101101+ }
102102+ Err(e) => {
103103+ error!("Failed to install SIGTERM handler: {}", e);
104104+ std::future::pending::<()>().await;
105105+ }
106106+ }
79107 };
8010881109 #[cfg(not(unix))]
···132132 .into_response();
133133 }
134134135135- let limit = params.limit.unwrap_or(500).min(1000);
135135+ let limit = params.limit.unwrap_or(500).clamp(1, 1000);
136136 let cursor_cid = params.cursor.as_deref().unwrap_or("");
137137138138 let user_result = sqlx::query!("SELECT id FROM users WHERE did = $1", did)
+5-4
src/sync/car.rs
···2323 Ok(())
2424}
25252626-pub fn encode_car_header(root_cid: &Cid) -> Vec<u8> {
2626+pub fn encode_car_header(root_cid: &Cid) -> Result<Vec<u8>, String> {
2727 let header = CarHeader::new_v1(vec![root_cid.clone()]);
2828- let header_cbor = header.encode().unwrap_or_default();
2828+ let header_cbor = header.encode().map_err(|e| format!("Failed to encode CAR header: {:?}", e))?;
29293030 let mut result = Vec::new();
3131- write_varint(&mut result, header_cbor.len() as u64).unwrap();
3131+ write_varint(&mut result, header_cbor.len() as u64)
3232+ .expect("Writing to Vec<u8> should never fail");
3233 result.extend_from_slice(&header_cbor);
3333- result
3434+ Ok(result)
3435}
+1-1
src/sync/commit.rs
···9898 State(state): State<AppState>,
9999 Query(params): Query<ListReposParams>,
100100) -> Response {
101101- let limit = params.limit.unwrap_or(50).min(1000);
101101+ let limit = params.limit.unwrap_or(50).clamp(1, 1000);
102102 let cursor_did = params.cursor.as_deref().unwrap_or("");
103103104104 let result = sqlx::query!(