this repo has no description
1use crate::api::ApiError; 2use crate::auth::BearerAuth; 3use crate::state::AppState; 4use crate::util::get_user_id_by_did; 5use axum::{ 6 Json, 7 extract::State, 8 response::{IntoResponse, Response}, 9}; 10use serde::{Deserialize, Serialize}; 11use serde_json::json; 12use tracing::error; 13 14#[derive(Serialize)] 15#[serde(rename_all = "camelCase")] 16pub struct AppPassword { 17 pub name: String, 18 pub created_at: String, 19 pub privileged: bool, 20} 21 22#[derive(Serialize)] 23pub struct ListAppPasswordsOutput { 24 pub passwords: Vec<AppPassword>, 25} 26 27pub async fn list_app_passwords( 28 State(state): State<AppState>, 29 BearerAuth(auth_user): BearerAuth, 30) -> Response { 31 let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await { 32 Ok(id) => id, 33 Err(e) => return ApiError::from(e).into_response(), 34 }; 35 36 match sqlx::query!( 37 "SELECT name, created_at, privileged FROM app_passwords WHERE user_id = $1 ORDER BY created_at DESC", 38 user_id 39 ) 40 .fetch_all(&state.db) 41 .await 42 { 43 Ok(rows) => { 44 let passwords: Vec<AppPassword> = rows 45 .iter() 46 .map(|row| AppPassword { 47 name: row.name.clone(), 48 created_at: row.created_at.to_rfc3339(), 49 privileged: row.privileged, 50 }) 51 .collect(); 52 53 Json(ListAppPasswordsOutput { passwords }).into_response() 54 } 55 Err(e) => { 56 error!("DB error listing app passwords: {:?}", e); 57 ApiError::InternalError.into_response() 58 } 59 } 60} 61 62#[derive(Deserialize)] 63pub struct CreateAppPasswordInput { 64 pub name: String, 65 pub privileged: Option<bool>, 66} 67 68#[derive(Serialize)] 69#[serde(rename_all = "camelCase")] 70pub struct CreateAppPasswordOutput { 71 pub name: String, 72 pub password: String, 73 pub created_at: String, 74 pub privileged: bool, 75} 76 77pub async fn create_app_password( 78 State(state): State<AppState>, 79 BearerAuth(auth_user): BearerAuth, 80 Json(input): Json<CreateAppPasswordInput>, 81) -> Response { 82 let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await { 83 Ok(id) => id, 84 Err(e) => return ApiError::from(e).into_response(), 85 }; 86 87 let name = input.name.trim(); 88 if name.is_empty() { 89 return ApiError::InvalidRequest("name is required".into()).into_response(); 90 } 91 92 let existing = sqlx::query!( 93 "SELECT id FROM app_passwords WHERE user_id = $1 AND name = $2", 94 user_id, 95 name 96 ) 97 .fetch_optional(&state.db) 98 .await; 99 100 if let Ok(Some(_)) = existing { 101 return ApiError::DuplicateAppPassword.into_response(); 102 } 103 104 let password: String = (0..4) 105 .map(|_| { 106 use rand::Rng; 107 let mut rng = rand::thread_rng(); 108 let chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz234567".chars().collect(); 109 (0..4) 110 .map(|_| chars[rng.gen_range(0..chars.len())]) 111 .collect::<String>() 112 }) 113 .collect::<Vec<String>>() 114 .join("-"); 115 116 let password_hash = match bcrypt::hash(&password, bcrypt::DEFAULT_COST) { 117 Ok(h) => h, 118 Err(e) => { 119 error!("Failed to hash password: {:?}", e); 120 return ApiError::InternalError.into_response(); 121 } 122 }; 123 124 let privileged = input.privileged.unwrap_or(false); 125 let created_at = chrono::Utc::now(); 126 127 match sqlx::query!( 128 "INSERT INTO app_passwords (user_id, name, password_hash, created_at, privileged) VALUES ($1, $2, $3, $4, $5)", 129 user_id, 130 name, 131 password_hash, 132 created_at, 133 privileged 134 ) 135 .execute(&state.db) 136 .await 137 { 138 Ok(_) => Json(CreateAppPasswordOutput { 139 name: name.to_string(), 140 password, 141 created_at: created_at.to_rfc3339(), 142 privileged, 143 }) 144 .into_response(), 145 Err(e) => { 146 error!("DB error creating app password: {:?}", e); 147 ApiError::InternalError.into_response() 148 } 149 } 150} 151 152#[derive(Deserialize)] 153pub struct RevokeAppPasswordInput { 154 pub name: String, 155} 156 157pub async fn revoke_app_password( 158 State(state): State<AppState>, 159 BearerAuth(auth_user): BearerAuth, 160 Json(input): Json<RevokeAppPasswordInput>, 161) -> Response { 162 let user_id = match get_user_id_by_did(&state.db, &auth_user.did).await { 163 Ok(id) => id, 164 Err(e) => return ApiError::from(e).into_response(), 165 }; 166 167 let name = input.name.trim(); 168 if name.is_empty() { 169 return ApiError::InvalidRequest("name is required".into()).into_response(); 170 } 171 172 match sqlx::query!( 173 "DELETE FROM app_passwords WHERE user_id = $1 AND name = $2", 174 user_id, 175 name 176 ) 177 .execute(&state.db) 178 .await 179 { 180 Ok(r) => { 181 if r.rows_affected() == 0 { 182 return ApiError::AppPasswordNotFound.into_response(); 183 } 184 Json(json!({})).into_response() 185 } 186 Err(e) => { 187 error!("DB error revoking app password: {:?}", e); 188 ApiError::InternalError.into_response() 189 } 190 } 191}