···11+ALTER TABLE session_tokens ADD COLUMN app_password_name TEXT;
22+CREATE INDEX idx_session_tokens_app_password ON session_tokens(did, app_password_name) WHERE app_password_name IS NOT NULL;
+27-11
src/api/server/app_password.rs
···232232 if name.is_empty() {
233233 return ApiError::InvalidRequest("name is required".into()).into_response();
234234 }
235235- match sqlx::query!(
235235+ let sessions_to_invalidate = sqlx::query_scalar!(
236236+ "SELECT access_jti FROM session_tokens WHERE did = $1 AND app_password_name = $2",
237237+ auth_user.did,
238238+ name
239239+ )
240240+ .fetch_all(&state.db)
241241+ .await
242242+ .unwrap_or_default();
243243+ if let Err(e) = sqlx::query!(
244244+ "DELETE FROM session_tokens WHERE did = $1 AND app_password_name = $2",
245245+ auth_user.did,
246246+ name
247247+ )
248248+ .execute(&state.db)
249249+ .await
250250+ {
251251+ error!("DB error revoking sessions for app password: {:?}", e);
252252+ return ApiError::InternalError.into_response();
253253+ }
254254+ for jti in &sessions_to_invalidate {
255255+ let cache_key = format!("auth:session:{}:{}", auth_user.did, jti);
256256+ let _ = state.cache.delete(&cache_key).await;
257257+ }
258258+ if let Err(e) = sqlx::query!(
236259 "DELETE FROM app_passwords WHERE user_id = $1 AND name = $2",
237260 user_id,
238261 name
···240263 .execute(&state.db)
241264 .await
242265 {
243243- Ok(r) => {
244244- if r.rows_affected() == 0 {
245245- return ApiError::AppPasswordNotFound.into_response();
246246- }
247247- Json(json!({})).into_response()
248248- }
249249- Err(e) => {
250250- error!("DB error revoking app password: {:?}", e);
251251- ApiError::InternalError.into_response()
252252- }
266266+ error!("DB error revoking app password: {:?}", e);
267267+ return ApiError::InternalError.into_response();
253268 }
269269+ Json(json!({})).into_response()
254270}
+13-7
src/api/server/meta.rs
···3535 let privacy_policy = std::env::var("PRIVACY_POLICY_URL").ok();
3636 let terms_of_service = std::env::var("TERMS_OF_SERVICE_URL").ok();
3737 let contact_email = std::env::var("CONTACT_EMAIL").ok();
3838+ let mut links = serde_json::Map::new();
3939+ if let Some(pp) = privacy_policy {
4040+ links.insert("privacyPolicy".to_string(), json!(pp));
4141+ }
4242+ if let Some(tos) = terms_of_service {
4343+ links.insert("termsOfService".to_string(), json!(tos));
4444+ }
4545+ let mut contact = serde_json::Map::new();
4646+ if let Some(email) = contact_email {
4747+ contact.insert("email".to_string(), json!(email));
4848+ }
3849 Json(json!({
3950 "availableUserDomains": domains,
4051 "inviteCodeRequired": invite_code_required,
4152 "did": format!("did:web:{}", pds_hostname),
4242- "links": {
4343- "privacyPolicy": privacy_policy,
4444- "termsOfService": terms_of_service
4545- },
4646- "contact": {
4747- "email": contact_email
4848- },
5353+ "links": links,
5454+ "contact": contact,
4955 "version": env!("CARGO_PKG_VERSION"),
5056 "availableCommsChannels": get_available_comms_channels()
5157 }))