OXYGENHUB

Coverage / crates/hub/src/web/secrets.rs 71.1%

27 of 38 lines run, 5 of 12 functions · run #530 on main · run missed

1
//! The Flow secrets panel (hub spec §13.2, oxygenhub#107) shared by the
2
//! administration, organisation and repository settings pages: names
3
//! only, values typed once and never shown.
4
5
use crate::auth::Viewer;
6
use crate::db::accounts;
7
use crate::levels::At;
8
use crate::secrets::{self, Seen};
9
use crate::Shared;
10
use serde::Deserialize;
11
12
#[derive(Deserialize)]
13
pub struct SecretForm {
14
    #[serde(rename = "_csrf")]
15
    pub csrf_token: String,
16
    #[serde(default)]
17
    action: String,
18
    #[serde(default)]
19
    name: String,
20
    #[serde(default)]
21
    value: String,
22
}
23
24
pub struct Panel {
25
    pub own: Vec<Seen>,
26
    pub inherited: Vec<Seen>,
27
}
28
29260×
pub async fn panel(app: &Shared, at: &At) -> sqlx::Result<Panel> {
30260×
    let (own, inherited): (Vec<_>, Vec<_>) =
31260×
        secrets::seen(&app.db, at.scope, &at.owner).await?.into_iter().partition(|v| v.from.is_empty());
32260×
    Ok(Panel { own, inherited })
33260×
}
34
35
/// Apply the panel's form: `Ok(notice)` or `Err(message for the user)`.
363×
pub async fn apply(app: &Shared, viewer: &Viewer, at: &At, f: SecretForm) -> sqlx::Result<Result<String, String>> {
373×
    let name = f.name.trim();
383×
    if !secrets::name_ok(name) {
391×
        return Ok(Err("A secret's name is 1 to 64 of A-Z a-z 0-9 . _ -, as the pipeline's `secret` block names it.".into()));
402×
    }
412×
    Ok(Ok(match f.action.as_str() {
422×
        "set" => {
432×
            let value = f.value.replace("\r\n", "\n");
442×
            if value.is_empty() {
450×
                return Ok(Err("Type the secret's value.".into()));
462×
            }
472×
            if value.len() > secrets::MAX_VALUE {
480×
                return Ok(Err(format!("A value is up to {} bytes.", secrets::MAX_VALUE)));
492×
            }
502×
            let own = secrets::list(&app.db, at.scope).await?;
512×
            if own.len() >= secrets::MAX_PER_LEVEL && !own.iter().any(|s| s.name == name) {
520×
                return Ok(Err(format!("Up to {} secrets here.", secrets::MAX_PER_LEVEL)));
532×
            }
542×
            secrets::set(&app.db, at.scope, name, &app.secret_key.seal(&value)).await?;
552×
            accounts::audit(&app.db, viewer.account(), "flow.secret.set", &at.label, name).await.ok();
562×
            format!("{name} set.")
57
        }
580×
        "delete" => {
590×
            if !secrets::delete(&app.db, at.scope, name).await? {
600×
                return Ok(Err(format!("There is no secret {name} here.")));
610×
            }
620×
            accounts::audit(&app.db, viewer.account(), "flow.secret.deleted", &at.label, name).await.ok();
630×
            format!("{name} removed.")
64
        }
650×
        _ => return Ok(Err("Unknown action.".into())),
66
    }))
673×
}