SSH keys in account settings; wrappers for agents, terminal and git
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
247ddcf14a
commit
6e8e739906
16 files changed
+517
-6
No files matched your search
@@ -157,6 +157,8 @@ pub fn router(app: AppState) -> Router {
|
||||
.route("/images/{id}", delete(delete_image))
|
||||
.route("/images/{id}/file", get(image_file))
|
||||
.route("/account/git", get(get_git_settings).put(put_git_settings))
|
||||
.route("/account/ssh", get(get_ssh_keys).post(add_ssh_key))
|
||||
.route("/account/ssh/{id}", delete(delete_ssh_key))
|
||||
.route("/account/snippets", get(crate::cli_api::get_snippets).put(crate::cli_api::put_snippets))
|
||||
.route("/account/prefs", get(crate::cli_api::get_prefs).put(crate::cli_api::put_prefs))
|
||||
.route("/account/sidebar", get(crate::cli_api::get_sidebar).put(crate::cli_api::put_sidebar))
|
||||
@@ -2426,6 +2428,57 @@ async fn run_job(Extension(user): Extension<User>, State(app): State<AppState>,
|
||||
|
||||
// --- git credentials ---------------------------------------------------------------
|
||||
|
||||
// --- SSH keys -----------------------------------------------------------------
|
||||
|
||||
async fn get_ssh_keys(Extension(user): Extension<User>, State(app): State<AppState>) -> ApiResult {
|
||||
let s: crate::sshkeys::SshSettings = app.store.user_setting(&user.id, "ssh")?;
|
||||
Ok(Json(json!(crate::sshkeys::masked(&s))))
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct NewSshKey {
|
||||
name: String,
|
||||
/// An existing private key to import; a new key is generated when missing.
|
||||
private: Option<String>,
|
||||
}
|
||||
|
||||
async fn add_ssh_key(Extension(user): Extension<User>, State(app): State<AppState>, Json(b): Json<NewSshKey>) -> ApiResult {
|
||||
let name = b.name.trim().to_string();
|
||||
if name.is_empty() || name.chars().count() > 60 {
|
||||
return Err(ApiError(StatusCode::BAD_REQUEST, "give the key a name (up to 60 characters)".into()));
|
||||
}
|
||||
let mut s: crate::sshkeys::SshSettings = app.store.user_setting(&user.id, "ssh")?;
|
||||
if s.keys.len() >= 10 {
|
||||
return Err(ApiError(StatusCode::BAD_REQUEST, "at most 10 keys".into()));
|
||||
}
|
||||
let key = match b.private.as_deref().filter(|p| !p.trim().is_empty()) {
|
||||
Some(p) => crate::sshkeys::import(&name, p).await,
|
||||
None => crate::sshkeys::generate(&name).await,
|
||||
}
|
||||
.map_err(|e| ApiError(StatusCode::BAD_REQUEST, format!("{e:#}")))?;
|
||||
if s.keys.iter().any(|k| k.fingerprint == key.fingerprint && !key.fingerprint.is_empty()) {
|
||||
return Err(ApiError(StatusCode::CONFLICT, "this key is already stored".into()));
|
||||
}
|
||||
s.keys.push(key);
|
||||
crate::sshkeys::write_files(&app, &user, &s).map_err(|e| ApiError(StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#}")))?;
|
||||
app.store.set_user_setting(&user.id, "ssh", &s)?;
|
||||
app.audit(Some(&user), "ssh.key.add", &name, "");
|
||||
Ok(Json(json!(crate::sshkeys::masked(&s))))
|
||||
}
|
||||
|
||||
async fn delete_ssh_key(Extension(user): Extension<User>, State(app): State<AppState>, Path(id): Path<String>) -> ApiResult {
|
||||
let mut s: crate::sshkeys::SshSettings = app.store.user_setting(&user.id, "ssh")?;
|
||||
let before = s.keys.len();
|
||||
s.keys.retain(|k| k.id != id);
|
||||
if s.keys.len() == before {
|
||||
return Err(not_found("key"));
|
||||
}
|
||||
crate::sshkeys::write_files(&app, &user, &s).map_err(|e| ApiError(StatusCode::INTERNAL_SERVER_ERROR, format!("{e:#}")))?;
|
||||
app.store.set_user_setting(&user.id, "ssh", &s)?;
|
||||
app.audit(Some(&user), "ssh.key.delete", &id, "");
|
||||
Ok(Json(json!(crate::sshkeys::masked(&s))))
|
||||
}
|
||||
|
||||
async fn get_git_settings(Extension(user): Extension<User>, State(app): State<AppState>) -> ApiResult {
|
||||
let s: crate::gitcreds::GitSettings = app.store.user_setting(&user.id, "git")?;
|
||||
Ok(Json(json!(crate::gitcreds::masked(&s))))
|
||||
|
||||
Reference in new issue
Block a user