mirror of
https://github.com/Xevion/xevion.dev.git
synced 2026-01-31 10:26:52 -06:00
feat: add PGP public key page with multiple access endpoints
- Add dedicated /pgp page with key viewer and download options - Support CLI-friendly endpoints (/publickey.asc, /pgp.asc, /.well-known/pgpkey.asc) - Detect user-agent to serve raw key to curl/wget or HTML to browsers - Add modal component for quick key access from homepage - Embed static key file in Rust assets for efficient serving
This commit is contained in:
@@ -55,6 +55,20 @@ fn serve_asset_by_path(path: &str) -> Response {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a static file from the embedded CLIENT_ASSETS.
|
||||
///
|
||||
/// Static files are served from web/static/ and embedded at compile time.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `path` - Path to the file (e.g., "publickey.asc")
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Some(&[u8])` - File content if file exists
|
||||
/// * `None` - If file not found
|
||||
pub fn get_static_file(path: &str) -> Option<&'static [u8]> {
|
||||
CLIENT_ASSETS.get_file(path).map(|f| f.contents())
|
||||
}
|
||||
|
||||
/// Get prerendered error page HTML for a given status code.
|
||||
///
|
||||
/// Error pages are prerendered by SvelteKit and embedded at compile time.
|
||||
|
||||
+82
@@ -211,6 +211,11 @@ async fn main() {
|
||||
"/_app/{*path}",
|
||||
axum::routing::get(serve_embedded_asset).head(serve_embedded_asset),
|
||||
)
|
||||
.route("/pgp", axum::routing::get(handle_pgp_route))
|
||||
.route("/publickey.asc", axum::routing::get(serve_pgp_key))
|
||||
.route("/pgp.asc", axum::routing::get(serve_pgp_key))
|
||||
.route("/.well-known/pgpkey.asc", axum::routing::get(serve_pgp_key))
|
||||
.route("/keys", axum::routing::get(redirect_to_pgp))
|
||||
}
|
||||
|
||||
fn apply_middleware(
|
||||
@@ -409,6 +414,44 @@ fn accepts_html(headers: &HeaderMap) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
/// Determines if request prefers raw content (CLI tools) over HTML
|
||||
fn prefers_raw_content(headers: &HeaderMap) -> bool {
|
||||
// Check User-Agent for known CLI tools first (most reliable)
|
||||
if let Some(ua) = headers.get(axum::http::header::USER_AGENT) {
|
||||
if let Ok(ua_str) = ua.to_str() {
|
||||
let ua_lower = ua_str.to_lowercase();
|
||||
if ua_lower.starts_with("curl/")
|
||||
|| ua_lower.starts_with("wget/")
|
||||
|| ua_lower.starts_with("httpie/")
|
||||
|| ua_lower.contains("curlie")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check Accept header - if it explicitly prefers text/html, serve HTML
|
||||
if let Some(accept) = headers.get(axum::http::header::ACCEPT) {
|
||||
if let Ok(accept_str) = accept.to_str() {
|
||||
// If text/html appears before */* in the list, they prefer HTML
|
||||
if let Some(html_pos) = accept_str.find("text/html") {
|
||||
if let Some(wildcard_pos) = accept_str.find("*/*") {
|
||||
return html_pos > wildcard_pos;
|
||||
}
|
||||
// Has text/html but no */* → prefers HTML
|
||||
return false;
|
||||
}
|
||||
// Has */* but no text/html → probably a CLI tool
|
||||
if accept_str.contains("*/*") && !accept_str.contains("text/html") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No Accept header → assume browser (safer default)
|
||||
false
|
||||
}
|
||||
|
||||
fn serve_error_page(status: StatusCode) -> Response {
|
||||
let status_code = status.as_u16();
|
||||
|
||||
@@ -460,6 +503,45 @@ async fn health_handler(State(state): State<Arc<AppState>>) -> impl IntoResponse
|
||||
}
|
||||
}
|
||||
|
||||
async fn serve_pgp_key() -> impl IntoResponse {
|
||||
if let Some(content) = assets::get_static_file("publickey.asc") {
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
axum::http::HeaderValue::from_static("application/pgp-keys"),
|
||||
);
|
||||
headers.insert(
|
||||
axum::http::header::CONTENT_DISPOSITION,
|
||||
axum::http::HeaderValue::from_static("attachment; filename=\"publickey.asc\""),
|
||||
);
|
||||
headers.insert(
|
||||
axum::http::header::CACHE_CONTROL,
|
||||
axum::http::HeaderValue::from_static("public, max-age=86400"),
|
||||
);
|
||||
(StatusCode::OK, headers, content).into_response()
|
||||
} else {
|
||||
(StatusCode::NOT_FOUND, "PGP key not found").into_response()
|
||||
}
|
||||
}
|
||||
|
||||
async fn redirect_to_pgp() -> impl IntoResponse {
|
||||
axum::response::Redirect::permanent("/pgp")
|
||||
}
|
||||
|
||||
async fn handle_pgp_route(
|
||||
State(state): State<Arc<AppState>>,
|
||||
headers: HeaderMap,
|
||||
req: Request,
|
||||
) -> Response {
|
||||
if prefers_raw_content(&headers) {
|
||||
// Serve raw .asc file for CLI tools
|
||||
serve_pgp_key().await.into_response()
|
||||
} else {
|
||||
// Proxy to Bun for HTML page
|
||||
isr_handler(State(state), req).await
|
||||
}
|
||||
}
|
||||
|
||||
async fn api_404_and_method_handler(req: Request) -> impl IntoResponse {
|
||||
let method = req.method();
|
||||
let uri = req.uri();
|
||||
|
||||
Reference in New Issue
Block a user