acme_proxy/webadmin/handlers/
eab.rs1use axum::Json;
4use axum::extract::{Path, Query, 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::handlers::paging::{PageParams, page_envelope};
15use crate::webadmin::handlers::params::empty_is_absent;
16use crate::webadmin::session::{Authenticated, AuthenticatedWrite};
17
18#[derive(Debug, Deserialize, Default)]
19pub struct CreateEab {
20 #[serde(default, deserialize_with = "empty_is_absent")]
21 pub label: Option<String>,
22 #[serde(default, deserialize_with = "empty_is_absent")]
24 pub profile: Option<String>,
25}
26
27pub async fn list_eab(
34 State(state): State<AdminState>,
35 Query(params): Query<PageParams>,
36 _auth: Authenticated,
37) -> Result<Json<Value>, AdminError> {
38 let page = params.resolve(&state.config);
39 let (keys, total) = Eab::search(page.limit, page.offset, &state.database).await?;
40 let items: Vec<Value> = keys.iter().map(admin::render_eab_json).collect();
41 Ok(Json(page_envelope(items, total, page)))
42}
43
44pub async fn get_eab(
46 State(state): State<AdminState>,
47 Path(kid): Path<String>,
48 _auth: Authenticated,
49) -> Result<Json<Value>, AdminError> {
50 let eab = Eab::find_any_by_kid(&kid, &state.database)
51 .await?
52 .ok_or_else(|| not_found(&kid))?;
53 Ok(Json(admin::render_eab_json(&eab)))
54}
55
56pub async fn create_eab(
63 State(state): State<AdminState>,
64 AuthenticatedWrite(auth): AuthenticatedWrite,
65 body: Option<Json<CreateEab>>,
66) -> Result<Response, AdminError> {
67 let Json(body) = body.unwrap_or_default();
68
69 require_mounted_profile(&state, body.profile.as_deref(), "omit `profile`")?;
70
71 let eab = Eab::create(body.label, body.profile, &state.database).await?;
72 tracing::info!(event = "admin_eab_created",
73 outcome = "success",
74 surface = "api",
75 kid = %eab.kid,
76 profile = ?eab.profile,
77 username = %auth.user.username);
78
79 Ok((
80 StatusCode::CREATED,
81 Json(admin::render_eab_created_json(&eab)),
82 )
83 .into_response())
84}
85
86pub async fn revoke_eab(
92 State(state): State<AdminState>,
93 Path(kid): Path<String>,
94 AuthenticatedWrite(auth): AuthenticatedWrite,
95) -> Result<StatusCode, AdminError> {
96 if !Eab::revoke(&kid, &state.database).await? {
97 return Err(not_found(&kid));
98 }
99 tracing::info!(event = "admin_eab_revoked", outcome = "success", surface = "api", kid = %kid, username = %auth.user.username);
100 Ok(StatusCode::NO_CONTENT)
101}
102
103pub(crate) fn require_mounted_profile(
111 state: &AdminState,
112 profile: Option<&str>,
113 hint: &str,
114) -> Result<(), AdminError> {
115 if let Some(name) = profile
116 && !state.profiles.contains_key(name)
117 {
118 return Err(AdminError::bad_request(format!(
119 "no profile named `{name}` is mounted; {hint} for a credential valid at \
120 every endpoint"
121 )));
122 }
123 Ok(())
124}
125
126fn not_found(kid: &str) -> AdminError {
127 AdminError::not_found(format!("no such EAB credential: {kid}"))
128}