auth.rs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. use std::str::FromStr;
  2. use axum::extract::{FromRequestParts, State};
  3. use axum::http::request::Parts;
  4. use axum::http::StatusCode;
  5. use axum::response::Response;
  6. use axum::routing::{get, post};
  7. use axum::{Json, Router};
  8. #[cfg(feature = "swagger")]
  9. use cdk::error::ErrorResponse;
  10. use cdk::nuts::{
  11. AuthToken, BlindAuthToken, KeysResponse, KeysetResponse, MintAuthRequest, MintResponse,
  12. };
  13. use serde::{Deserialize, Serialize};
  14. use crate::{get_keyset_pubkeys, into_response, MintState};
  15. const CLEAR_AUTH_KEY: &str = "Clear-auth";
  16. const BLIND_AUTH_KEY: &str = "Blind-auth";
  17. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
  18. pub enum AuthHeader {
  19. /// Clear Auth token
  20. Clear(String),
  21. /// Blind Auth token
  22. Blind(BlindAuthToken),
  23. /// No auth
  24. None,
  25. }
  26. impl From<AuthHeader> for Option<AuthToken> {
  27. fn from(value: AuthHeader) -> Option<AuthToken> {
  28. match value {
  29. AuthHeader::Clear(token) => Some(AuthToken::ClearAuth(token)),
  30. AuthHeader::Blind(token) => Some(AuthToken::BlindAuth(token)),
  31. AuthHeader::None => None,
  32. }
  33. }
  34. }
  35. impl<S> FromRequestParts<S> for AuthHeader
  36. where
  37. S: Send + Sync,
  38. {
  39. type Rejection = (StatusCode, String);
  40. async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
  41. // Check for Blind-auth header
  42. if let Some(bat) = parts.headers.get(BLIND_AUTH_KEY) {
  43. let token = bat
  44. .to_str()
  45. .map_err(|_| {
  46. (
  47. StatusCode::BAD_REQUEST,
  48. "Invalid Blind-auth header value".to_string(),
  49. )
  50. })?
  51. .to_string();
  52. let token = BlindAuthToken::from_str(&token).map_err(|_| {
  53. (
  54. StatusCode::BAD_REQUEST,
  55. "Invalid Blind-auth header value".to_string(),
  56. )
  57. })?;
  58. return Ok(AuthHeader::Blind(token));
  59. }
  60. // Check for Clear-auth header
  61. if let Some(cat) = parts.headers.get(CLEAR_AUTH_KEY) {
  62. let token = cat
  63. .to_str()
  64. .map_err(|_| {
  65. (
  66. StatusCode::BAD_REQUEST,
  67. "Invalid Clear-auth header value".to_string(),
  68. )
  69. })?
  70. .to_string();
  71. return Ok(AuthHeader::Clear(token));
  72. }
  73. // No authentication headers found - this is now valid
  74. Ok(AuthHeader::None)
  75. }
  76. }
  77. #[cfg_attr(feature = "swagger", utoipa::path(
  78. get,
  79. context_path = "/v1/auth/blind",
  80. path = "/keysets",
  81. responses(
  82. (status = 200, description = "Successful response", body = KeysetResponse, content_type = "application/json"),
  83. (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
  84. )
  85. ))]
  86. /// Get all active keyset IDs of the mint
  87. ///
  88. /// This endpoint returns a list of keysets that the mint currently supports and will accept tokens from.
  89. pub async fn get_auth_keysets(
  90. State(state): State<MintState>,
  91. ) -> Result<Json<KeysetResponse>, Response> {
  92. Ok(Json(state.mint.auth_keysets()))
  93. }
  94. #[cfg_attr(feature = "swagger", utoipa::path(
  95. get,
  96. context_path = "/v1/auth/blind",
  97. path = "/keys",
  98. responses(
  99. (status = 200, description = "Successful response", body = KeysResponse, content_type = "application/json")
  100. )
  101. ))]
  102. /// Get the public keys of the newest blind auth mint keyset
  103. ///
  104. /// This endpoint returns a dictionary of all supported token values of the mint and their associated public key.
  105. pub async fn get_blind_auth_keys(
  106. State(state): State<MintState>,
  107. ) -> Result<Json<KeysResponse>, Response> {
  108. let pubkeys = state.mint.auth_pubkeys().map_err(|err| {
  109. tracing::error!("Could not get keys: {}", err);
  110. into_response(err)
  111. })?;
  112. Ok(Json(pubkeys))
  113. }
  114. /// Mint tokens by paying a BOLT11 Lightning invoice.
  115. ///
  116. /// Requests the minting of tokens belonging to a paid payment request.
  117. ///
  118. /// Call this endpoint after `POST /v1/mint/quote`.
  119. #[cfg_attr(feature = "swagger", utoipa::path(
  120. post,
  121. context_path = "/v1/auth",
  122. path = "/blind/mint",
  123. request_body(content = MintAuthRequest, description = "Request params", content_type = "application/json"),
  124. responses(
  125. (status = 200, description = "Successful response", body = MintResponse, content_type = "application/json"),
  126. (status = 500, description = "Server error", body = ErrorResponse, content_type = "application/json")
  127. )
  128. ))]
  129. pub async fn post_mint_auth(
  130. auth: AuthHeader,
  131. State(state): State<MintState>,
  132. Json(payload): Json<MintAuthRequest>,
  133. ) -> Result<Json<MintResponse>, Response> {
  134. let auth_token = match auth {
  135. AuthHeader::Clear(cat) => {
  136. if cat.is_empty() {
  137. tracing::debug!("Received blind auth mint request without cat");
  138. return Err(into_response(cdk::Error::ClearAuthRequired));
  139. }
  140. AuthToken::ClearAuth(cat)
  141. }
  142. _ => {
  143. tracing::debug!("Received blind auth mint request without cat");
  144. return Err(into_response(cdk::Error::ClearAuthRequired));
  145. }
  146. };
  147. let res = state
  148. .mint
  149. .mint_blind_auth(auth_token, payload)
  150. .await
  151. .map_err(|err| {
  152. tracing::error!("Could not process blind auth mint: {}", err);
  153. into_response(err)
  154. })?;
  155. Ok(Json(res))
  156. }
  157. pub fn create_auth_router(state: MintState) -> Router<MintState> {
  158. Router::new()
  159. .nest(
  160. "/auth/blind",
  161. Router::new()
  162. .route("/keys", get(get_blind_auth_keys))
  163. .route("/keysets", get(get_auth_keysets))
  164. .route("/keys/{keyset_id}", get(get_keyset_pubkeys))
  165. .route("/mint", post(post_mint_auth)),
  166. )
  167. .with_state(state)
  168. }