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(
39 State(state): State<AdminState>,
40 session: PageSession,
41) -> Result<Html<String>, PageError> {
42 let mut context = chrome(&session, "eab", "External Account Binding");
43 context.insert("items".to_string(), Value::Array(rows(&state).await?));
44 context.insert(
45 "profiles".to_string(),
46 Value::Array(crate::webadmin::handlers::misc::profile_rows(&state)),
47 );
48
49 respond(
50 &state,
51 session.hx,
52 "eab/list.html",
53 "eab/_table.html",
54 context,
55 )
56}
57
58pub async fn get_eab(
60 State(state): State<AdminState>,
61 Path(kid): Path<String>,
62 session: PageSession,
63) -> Result<Html<String>, PageError> {
64 let eab = load(&kid, &state).await?;
65
66 let mut context = chrome(&session, "eab", "Credential");
67 context.insert("eab".to_string(), eab);
68
69 respond(
70 &state,
71 session.hx,
72 "eab/detail.html",
73 "eab/_card.html",
74 context,
75 )
76}
77
78pub async fn create_eab(
84 State(state): State<AdminState>,
85 session: PageSessionWrite,
86 axum::Form(form): axum::Form<CreateForm>,
89) -> Result<Response, PageError> {
90 let label = non_empty(&form.label);
91 let profile = non_empty(&form.profile);
92
93 super::super::handlers::eab::require_mounted_profile(
94 &state,
95 profile.as_deref(),
96 "leave it unset",
97 )?;
98
99 let eab = Eab::create(label, profile, &state.database).await?;
100 tracing::info!(event = "admin_eab_created",
101 outcome = "success",
102 surface = "ui",
103 kid = %eab.kid,
104 username = %session.auth.user.username);
105
106 let mut context = Map::new();
107 context.insert("eab".to_string(), admin::render_eab_created_json(&eab));
108 context.insert("items".to_string(), Value::Array(rows(&state).await?));
109 context.insert("oob".to_string(), Value::Bool(true));
113
114 let body = respond_fragment(&state, "eab/_created.html", context)?;
115 Ok((StatusCode::CREATED, body).into_response())
116}
117
118pub async fn revoke_eab(
120 State(state): State<AdminState>,
121 Path(kid): Path<String>,
122 session: PageSessionWrite,
123) -> Result<Html<String>, PageError> {
124 if !Eab::revoke(&kid, &state.database).await? {
127 return Err(not_found(&kid));
128 }
129
130 tracing::info!(event = "admin_eab_revoked",
131 outcome = "success",
132 surface = "ui",
133 kid = %kid,
134 username = %session.auth.user.username);
135
136 let eab = load(&kid, &state).await?;
137 let mut context = Map::new();
138 context.insert(
139 "csrf_token".to_string(),
140 Value::String(session.auth.session.csrf_token.clone()),
141 );
142 context.insert("eab".to_string(), eab);
143 context.insert(
144 "flash".to_string(),
145 flash(
146 "ok",
147 "Credential revoked. Registrations using it fail from now on.",
148 ),
149 );
150 respond_fragment(&state, "eab/_card.html", context)
151}
152
153async fn rows(state: &AdminState) -> Result<Vec<Value>, PageError> {
154 Ok(Eab::list_all(&state.database)
155 .await?
156 .iter()
157 .map(admin::render_eab_json)
158 .collect())
159}
160
161async fn load(kid: &str, state: &AdminState) -> Result<Value, PageError> {
162 let eab = Eab::find_any_by_kid(kid, &state.database)
163 .await?
164 .ok_or_else(|| not_found(kid))?;
165 Ok(admin::render_eab_json(&eab))
166}
167
168fn non_empty(raw: &str) -> Option<String> {
170 let trimmed = raw.trim();
171 (!trimmed.is_empty()).then(|| trimmed.to_string())
172}
173
174fn not_found(kid: &str) -> PageError {
175 PageError::not_found(format!("no such EAB credential: {kid}"))
176}