acme_proxy/webadmin/handlers/
eab.rs1use axum::Json;
4use axum::extract::{Path, State};
5use axum::http::StatusCode;
6use axum::response::{IntoResponse, Response};
7use serde::Deserialize;
8use serde_json::Value;
9
10use crate::admin;
11use crate::sqlite::eab::Eab;
12use crate::webadmin::AdminState;
13use crate::webadmin::error::AdminError;
14use crate::webadmin::session::{Authenticated, AuthenticatedWrite};
15
16#[derive(Debug, Deserialize, Default)]
17pub struct CreateEab {
18 pub label: Option<String>,
19 pub profile: Option<String>,
21}
22
23pub async fn list_eab(
25 State(state): State<AdminState>,
26 _auth: Authenticated,
27) -> Result<Json<Value>, AdminError> {
28 let keys = Eab::list_all(&state.database).await?;
29 let items: Vec<Value> = keys.iter().map(admin::render_eab_json).collect();
30 Ok(Json(Value::Array(items)))
31}
32
33pub async fn get_eab(
35 State(state): State<AdminState>,
36 Path(kid): Path<String>,
37 _auth: Authenticated,
38) -> Result<Json<Value>, AdminError> {
39 let eab = Eab::find_any_by_kid(&kid, &state.database)
40 .await?
41 .ok_or_else(|| not_found(&kid))?;
42 Ok(Json(admin::render_eab_json(&eab)))
43}
44
45pub async fn create_eab(
52 State(state): State<AdminState>,
53 AuthenticatedWrite(auth): AuthenticatedWrite,
54 body: Option<Json<CreateEab>>,
55) -> Result<Response, AdminError> {
56 let Json(body) = body.unwrap_or_default();
57
58 require_mounted_profile(&state, body.profile.as_deref(), "omit `profile`")?;
59
60 let eab = Eab::create(body.label, body.profile, &state.database).await?;
61 tracing::info!(event = "admin_eab_created",
62 outcome = "success",
63 surface = "api",
64 kid = %eab.kid,
65 profile = ?eab.profile,
66 username = %auth.user.username);
67
68 Ok((
69 StatusCode::CREATED,
70 Json(admin::render_eab_created_json(&eab)),
71 )
72 .into_response())
73}
74
75pub async fn revoke_eab(
81 State(state): State<AdminState>,
82 Path(kid): Path<String>,
83 AuthenticatedWrite(auth): AuthenticatedWrite,
84) -> Result<StatusCode, AdminError> {
85 if !Eab::revoke(&kid, &state.database).await? {
86 return Err(not_found(&kid));
87 }
88 tracing::info!(event = "admin_eab_revoked", outcome = "success", surface = "api", kid = %kid, username = %auth.user.username);
89 Ok(StatusCode::NO_CONTENT)
90}
91
92pub(crate) fn require_mounted_profile(
100 state: &AdminState,
101 profile: Option<&str>,
102 hint: &str,
103) -> Result<(), AdminError> {
104 if let Some(name) = profile
105 && !state.profiles.contains_key(name)
106 {
107 return Err(AdminError::bad_request(format!(
108 "no profile named `{name}` is mounted; {hint} for a credential valid at \
109 every endpoint"
110 )));
111 }
112 Ok(())
113}
114
115fn not_found(kid: &str) -> AdminError {
116 AdminError::not_found(format!("no such EAB credential: {kid}"))
117}