acme_proxy/webadmin/pages/
eab.rs1use axum::extract::{Path, Query, 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::handlers::paging::{Page, PageParams};
19use crate::webadmin::handlers::params::non_empty;
20use crate::webadmin::pages::auth::{PageSession, PageSessionWrite};
21use crate::webadmin::pages::error::PageError;
22use crate::webadmin::pages::{chrome, flash, page_value, pager, respond, respond_fragment};
23
24#[derive(Debug, Deserialize, Default)]
25pub struct CreateForm {
26 #[serde(default)]
29 pub label: String,
30 #[serde(default)]
33 pub profile: String,
34}
35
36pub async fn list_eab(
45 State(state): State<AdminState>,
46 Query(params): Query<PageParams>,
47 session: PageSession,
48) -> Result<Html<String>, PageError> {
49 let page = params.resolve(&state.config);
50 let (items, total) = rows(page, &state).await?;
51
52 let mut context = chrome(&session, "eab", "External Account Binding");
53 context.insert("page".to_string(), page_value(items, total));
54 context.insert(
55 "pager".to_string(),
56 pager(page, total, "/ui/eab", &[], "#eab-table"),
57 );
58 context.insert(
59 "profiles".to_string(),
60 Value::Array(crate::webadmin::handlers::misc::profile_rows(&state)),
61 );
62
63 respond(
64 &state,
65 session.hx,
66 "eab/list.html",
67 "eab/_table.html",
68 context,
69 )
70}
71
72pub async fn get_eab(
74 State(state): State<AdminState>,
75 Path(kid): Path<String>,
76 session: PageSession,
77) -> Result<Html<String>, PageError> {
78 let eab = load(&kid, &state).await?;
79
80 let mut context = chrome(&session, "eab", "Credential");
81 context.insert("eab".to_string(), eab);
82
83 respond(
84 &state,
85 session.hx,
86 "eab/detail.html",
87 "eab/_card.html",
88 context,
89 )
90}
91
92pub async fn create_eab(
98 State(state): State<AdminState>,
99 session: PageSessionWrite,
100 axum::Form(form): axum::Form<CreateForm>,
103) -> Result<Response, PageError> {
104 let label = non_empty(&form.label);
105 let profile = non_empty(&form.profile);
106
107 super::super::handlers::eab::require_mounted_profile(
108 &state,
109 profile.as_deref(),
110 "leave it unset",
111 )?;
112
113 let eab = Eab::create(label, profile, &state.database).await?;
114 tracing::info!(event = "admin_eab_created",
115 outcome = "success",
116 surface = "ui",
117 kid = %eab.kid,
118 username = %session.auth.user.username);
119
120 let page = PageParams::default().resolve(&state.config);
127 let (items, total) = rows(page, &state).await?;
128
129 let mut context = Map::new();
130 context.insert("eab".to_string(), admin::render_eab_created_json(&eab));
131 context.insert("page".to_string(), page_value(items, total));
132 context.insert(
133 "pager".to_string(),
134 pager(page, total, "/ui/eab", &[], "#eab-table"),
135 );
136 context.insert("oob".to_string(), Value::Bool(true));
140
141 let body = respond_fragment(&state, "eab/_created.html", context)?;
142 Ok((StatusCode::CREATED, body).into_response())
143}
144
145pub async fn revoke_eab(
147 State(state): State<AdminState>,
148 Path(kid): Path<String>,
149 session: PageSessionWrite,
150) -> Result<Html<String>, PageError> {
151 if !Eab::revoke(&kid, &state.database).await? {
154 return Err(not_found(&kid));
155 }
156
157 tracing::info!(event = "admin_eab_revoked",
158 outcome = "success",
159 surface = "ui",
160 kid = %kid,
161 username = %session.auth.user.username);
162
163 let eab = load(&kid, &state).await?;
164 let mut context = Map::new();
165 context.insert(
166 "csrf_token".to_string(),
167 Value::String(session.auth.session.csrf_token.clone()),
168 );
169 context.insert("eab".to_string(), eab);
170 context.insert(
171 "flash".to_string(),
172 flash(
173 "ok",
174 "Credential revoked. Registrations using it fail from now on.",
175 ),
176 );
177 respond_fragment(&state, "eab/_card.html", context)
178}
179
180async fn rows(page: Page, state: &AdminState) -> Result<(Vec<Value>, i64), PageError> {
181 let (keys, total) = Eab::search(page.limit, page.offset, &state.database).await?;
182 Ok((keys.iter().map(admin::render_eab_json).collect(), total))
183}
184
185async fn load(kid: &str, state: &AdminState) -> Result<Value, PageError> {
186 let eab = Eab::find_any_by_kid(kid, &state.database)
187 .await?
188 .ok_or_else(|| not_found(kid))?;
189 Ok(admin::render_eab_json(&eab))
190}
191
192fn not_found(kid: &str) -> PageError {
193 PageError::not_found(format!("no such EAB credential: {kid}"))
194}