Skip to content

Commit c8ff257

Browse files
committed
feat: mint blind auth (untested)
feat: wallet blind auth (untested) feat: auth on restore and check
1 parent eb8719d commit c8ff257

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+2183
-264
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,6 @@ jobs:
113113
uses: DeterminateSystems/magic-nix-cache-action@v6
114114
- name: Rust Cache
115115
uses: Swatinem/rust-cache@v2
116-
- name: Clippy
117-
run: nix develop -i -L .#stable --command cargo clippy ${{ matrix.build-args }} -- -D warnings
118116
- name: Test
119117
run: nix develop -i -L .#stable --command just itest ${{ matrix.database }}
120118

@@ -143,9 +141,11 @@ jobs:
143141
- name: Rust Cache
144142
uses: Swatinem/rust-cache@v2
145143
- name: Clippy
146-
run: nix develop -i -L .#stable --command cargo clippy ${{ matrix.build-args }} -- -D warnings
144+
run: nix develop -i -L .#stable --command cargo clippy -- -D warnings
147145
- name: Test fake mint
148146
run: nix develop -i -L .#stable --command just fake-mint-itest ${{ matrix.database }}
147+
- name: Test Mint tests
148+
run: nix develop -i -L .#stable --command cargo test -p cdk-integration-tests --test mint
149149

150150
msrv-build:
151151
name: "MSRV build"

.helix/languages.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,8 @@
11
[language-server.rust-analyzer.config]
22
cargo = { features = ["wallet", "mint", "swagger", "redis"] }
3+
inlayHints.bindingModeHints.enable = true
4+
inlayHints.closingBraceHints.enable = true
5+
inlayHints.chainingHints.enable = true
6+
inlayHints.parameterHints.enable = true
7+
inlayHints.typeHints.enable = true
8+
command = "rust-analyzer"

crates/cdk-axum/src/auth.rs

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

crates/cdk-axum/src/lib.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
use std::sync::Arc;
77

88
use anyhow::Result;
9+
use auth::create_auth_router;
910
use axum::routing::{get, post};
1011
use axum::Router;
1112
use cache::HttpCache;
1213
use cdk::mint::Mint;
1314
use router_handlers::*;
1415

16+
mod auth;
1517
pub mod cache;
1618
mod router_handlers;
1719
mod ws;
@@ -169,7 +171,12 @@ pub async fn create_mint_router_with_custom_cache(
169171
.route("/info", get(get_mint_info))
170172
.route("/restore", post(post_restore));
171173

172-
let mint_router = Router::new().nest("/v1", v1_router).with_state(state);
174+
let auth_router = create_auth_router(state.clone());
175+
176+
let mint_router = Router::new()
177+
.nest("/v1", v1_router)
178+
.nest("/v1", auth_router)
179+
.with_state(state);
173180

174181
Ok(mint_router)
175182
}

0 commit comments

Comments
 (0)