acme_proxy/webadmin/pages/
eab.rs1use axum::extract::{Path, State};
10use axum::http::StatusCode;
11use axum::response::{Html, IntoResponse, Response};
12use serde::Deserialize;
13use serde_json::{Map, Value};
14
15use crate::admin;
16use crate::sqlite::eab::Eab;
17use crate::webadmin::AdminState;
18use crate::webadmin::pages::auth::{PageSession, PageSessionWrite};
19use crate::webadmin::pages::error::PageError;
20use crate::webadmin::pages::{chrome, flash, respond, respond_fragment};
21
22#[derive(Debug, Deserialize, Default)]
23pub struct CreateForm {
24 #[serde(default)]
27 pub label: String,
28 #[serde(default)]
31 pub profile: String,
32}
33
34pub async fn list_eab(
43 State(state): State<AdminState>,
44 session: PageSession,
45) -> Result<Html<String>, PageError> {
46 let mut context = chrome(&session, "eab", "External Account Binding");
47 context.insert("items".to_string(), Value::Array(rows(&state).await?));
48 context.insert(
49 "profiles".to_string(),
50 Value::Array(crate::webadmin::handlers::misc::profile_rows(&state)),
51 );
52
53 respond(
54 &state,
55 session.hx,
56 "eab/list.html",
57 "eab/_table.html",
58 context,
59 )
60}
61
62pub async fn get_eab(
64 State(state): State<AdminState>,
65 Path(kid): Path<String>,
66 session: PageSession,
67) -> Result<Html<String>, PageError> {
68 let eab = load(&kid, &state).await?;
69
70 let mut context = chrome(&session, "eab", "Credential");
71 context.insert("eab".to_string(), eab);
72
73 respond(
74 &state,
75 session.hx,
76 "eab/detail.html",
77 "eab/_card.html",
78 context,
79 )
80}
81
82pub async fn create_eab(
88 State(state): State<AdminState>,
89 session: PageSessionWrite,
90 axum::Form(form): axum::Form<CreateForm>,
93) -> Result<Response, PageError> {
94 let label = non_empty(&form.label);
95 let profile = non_empty(&form.profile);
96
97 super::super::handlers::eab::require_mounted_profile(
98 &state,
99 profile.as_deref(),
100 "leave it unset",
101 )?;
102
103 let eab = Eab::create(label, profile, &state.database).await?;
104 tracing::info!(event = "admin_eab_created",
105 outcome = "success",
106 surface = "ui",
107 kid = %eab.kid,
108 username = %session.auth.user.username);
109
110 let mut context = Map::new();
111 context.insert("eab".to_string(), admin::render_eab_created_json(&eab));
112 context.insert("items".to_string(), Value::Array(rows(&state).await?));
113 context.insert("oob".to_string(), Value::Bool(true));
117
118 let body = respond_fragment(&state, "eab/_created.html", context)?;
119 Ok((StatusCode::CREATED, body).into_response())
120}
121
122pub async fn revoke_eab(
124 State(state): State<AdminState>,
125 Path(kid): Path<String>,
126 session: PageSessionWrite,
127) -> Result<Html<String>, PageError> {
128 if !Eab::revoke(&kid, &state.database).await? {
131 return Err(not_found(&kid));
132 }
133
134 tracing::info!(event = "admin_eab_revoked",
135 outcome = "success",
136 surface = "ui",
137 kid = %kid,
138 username = %session.auth.user.username);
139
140 let eab = load(&kid, &state).await?;
141 let mut context = Map::new();
142 context.insert(
143 "csrf_token".to_string(),
144 Value::String(session.auth.session.csrf_token.clone()),
145 );
146 context.insert("eab".to_string(), eab);
147 context.insert(
148 "flash".to_string(),
149 flash(
150 "ok",
151 "Credential revoked. Registrations using it fail from now on.",
152 ),
153 );
154 respond_fragment(&state, "eab/_card.html", context)
155}
156
157async fn rows(state: &AdminState) -> Result<Vec<Value>, PageError> {
158 Ok(Eab::list_all(&state.database)
159 .await?
160 .iter()
161 .map(admin::render_eab_json)
162 .collect())
163}
164
165async fn load(kid: &str, state: &AdminState) -> Result<Value, PageError> {
166 let eab = Eab::find_any_by_kid(kid, &state.database)
167 .await?
168 .ok_or_else(|| not_found(kid))?;
169 Ok(admin::render_eab_json(&eab))
170}
171
172fn non_empty(raw: &str) -> Option<String> {
174 let trimmed = raw.trim();
175 (!trimmed.is_empty()).then(|| trimmed.to_string())
176}
177
178fn not_found(kid: &str) -> PageError {
179 PageError::not_found(format!("no such EAB credential: {kid}"))
180}