auth.rs 6.1 KB

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