| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 | use std::str::FromStr;use axum::extract::{FromRequestParts, State};use axum::http::request::Parts;use axum::http::StatusCode;use axum::response::Response;use axum::routing::{get, post};use axum::{Json, Router};#[cfg(feature = "swagger")]use cdk::error::ErrorResponse;use cdk::nuts::{    AuthToken, BlindAuthToken, KeysResponse, KeysetResponse, MintAuthRequest, MintResponse,};use serde::{Deserialize, Serialize};#[cfg(feature = "auth")]use crate::{get_keyset_pubkeys, into_response, MintState};const CLEAR_AUTH_KEY: &str = "Clear-auth";const BLIND_AUTH_KEY: &str = "Blind-auth";#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]pub enum AuthHeader {    /// Clear Auth token    Clear(String),    /// Blind Auth token    Blind(BlindAuthToken),    /// No auth    None,}impl From<AuthHeader> for Option<AuthToken> {    fn from(value: AuthHeader) -> Option<AuthToken> {        match value {            AuthHeader::Clear(token) => Some(AuthToken::ClearAuth(token)),            AuthHeader::Blind(token) => Some(AuthToken::BlindAuth(token)),            AuthHeader::None => None,        }    }}impl<S> FromRequestParts<S> for AuthHeaderwhere    S: Send + Sync,{    type Rejection = (StatusCode, String);    async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {        // Check for Blind-auth header        if let Some(bat) = parts.headers.get(BLIND_AUTH_KEY) {            let token = bat                .to_str()                .map_err(|_| {                    (                        StatusCode::BAD_REQUEST,                        "Invalid Blind-auth header value".to_string(),                    )                })?                .to_string();            let token = BlindAuthToken::from_str(&token).map_err(|_| {                (                    StatusCode::BAD_REQUEST,                    "Invalid Blind-auth header value".to_string(),                )            })?;            return Ok(AuthHeader::Blind(token));        }        // Check for Clear-auth header        if let Some(cat) = parts.headers.get(CLEAR_AUTH_KEY) {            let token = cat                .to_str()                .map_err(|_| {                    (                        StatusCode::BAD_REQUEST,                        "Invalid Clear-auth header value".to_string(),                    )                })?                .to_string();            return Ok(AuthHeader::Clear(token));        }        // No authentication headers found - this is now valid        Ok(AuthHeader::None)    }}#[cfg_attr(feature = "swagger", utoipa::path(    get,    context_path = "/v1/auth/blind",    path = "/keysets",    responses(        (status = 200, description = "Successful response", body = KeysetResponse, content_type = "application/json"),        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")    )))]/// Get all active keyset IDs of the mint////// This endpoint returns a list of keysets that the mint currently supports and will accept tokens from.#[cfg(feature = "auth")]pub async fn get_auth_keysets(    State(state): State<MintState>,) -> Result<Json<KeysetResponse>, Response> {    Ok(Json(state.mint.auth_keysets()))}#[cfg_attr(feature = "swagger", utoipa::path(    get,    context_path = "/v1/auth/blind",    path = "/keys",    responses(        (status = 200, description = "Successful response", body = KeysResponse, content_type = "application/json")    )))]/// Get the public keys of the newest blind auth mint keyset////// This endpoint returns a dictionary of all supported token values of the mint and their associated public key.pub async fn get_blind_auth_keys(    State(state): State<MintState>,) -> Result<Json<KeysResponse>, Response> {    let pubkeys = state.mint.auth_pubkeys().map_err(|err| {        tracing::error!("Could not get keys: {}", err);        into_response(err)    })?;    Ok(Json(pubkeys))}/// Mint tokens by paying a BOLT11 Lightning invoice.////// Requests the minting of tokens belonging to a paid payment request.////// Call this endpoint after `POST /v1/mint/quote`.#[cfg_attr(feature = "swagger", utoipa::path(    post,    context_path = "/v1/auth",    path = "/blind/mint",    request_body(content = MintAuthRequest, description = "Request params", content_type = "application/json"),    responses(        (status = 200, description = "Successful response", body = MintResponse, content_type = "application/json"),        (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")    )))]pub async fn post_mint_auth(    auth: AuthHeader,    State(state): State<MintState>,    Json(payload): Json<MintAuthRequest>,) -> Result<Json<MintResponse>, Response> {    let auth_token = match auth {        AuthHeader::Clear(cat) => {            if cat.is_empty() {                tracing::debug!("Received blind auth mint request without cat");                return Err(into_response(cdk::Error::ClearAuthRequired));            }            AuthToken::ClearAuth(cat)        }        _ => {            tracing::debug!("Received blind auth mint request without cat");            return Err(into_response(cdk::Error::ClearAuthRequired));        }    };    let res = state        .mint        .mint_blind_auth(auth_token, payload)        .await        .map_err(|err| {            tracing::error!("Could not process blind auth mint: {}", err);            into_response(err)        })?;    Ok(Json(res))}pub fn create_auth_router(state: MintState) -> Router<MintState> {    Router::new()        .nest(            "/auth/blind",            Router::new()                .route("/keys", get(get_blind_auth_keys))                .route("/keysets", get(get_auth_keysets))                .route("/keys/{keyset_id}", get(get_keyset_pubkeys))                .route("/mint", post(post_mint_auth)),        )        .with_state(state)}
 |