mirror of
https://github.com/aljazceru/cdk.git
synced 2026-01-14 02:15:50 +01:00
* WIP: Introduce a SignatoryManager service. The SignatoryManager manager provides an API to interact with keysets, private keys, and all key-related operations, offering segregation between the mint and the most sensible part of the mind: the private keys. Although the default signatory runs in memory, it is completely isolated from the rest of the system and can only be communicated through the interface offered by the signatory manager. Only messages can be sent from the mintd to the Signatory trait through the Signatory Manager. This pull request sets the foundation for eventually being able to run the Signatory and all the key-related operations in a separate service, possibly in a foreign service, to offload risks, as described in #476. The Signatory manager is concurrent and deferred any mechanism needed to handle concurrency to the Signatory trait. * Fixed missing default feature for signatory * Do not read keys from the DB * Removed KeysDatabase Trait from MintDatabase All Keys operations should be done through the signatory * Make sure signatory has all the keys in memory Drop also foreign constraints on sqlite * Fix race condition * Adding debug info to failing test * Add `sleep` in test * Fixed issue with active auth keyset * Fixed dependency * Move all keys and keysets to an ArcSwap. Since the keys and keysets exist in RAM, most wrapping functions are infallible and synchronous, improving performance and adding breaking API changes. The signatory will provide this information on the boot and update when the `rotate_keyset` is executed. Todo: Implement a subscription key to reload the keys when the GRPC server changes the keys. For the embedded mode, that makes no sense since there is a single way to rotate keys, and that bit is already covered. * Implementing https://github.com/cashubtc/nuts/pull/250 * Add CLI for cdk-signatory to spawn an external signatory Add to the pipeline the external signatory * Update tests * Apply suggestions from code review Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com> Co-authored-by: thesimplekid <tsk@thesimplekid.com> * Minor change * Update proto buf to use the newest format * Rename binary * Add instrumentations * Add more comments * Use a single database for the signatory Store all keys, even auth keys, in a single database. Leave the MintAuthDatabse trait implementation for the CDK but not the signagtory This commit also moves the cli mod to its own file * Update dep * Add `test_mint_keyset_gen` test --------- Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com> Co-authored-by: thesimplekid <tsk@thesimplekid.com>
190 lines
5.9 KiB
Rust
190 lines
5.9 KiB
Rust
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 AuthHeader
|
|
where
|
|
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)
|
|
}
|