use tracing::instrument;
use super::nut21::ProtectedEndpoint;
use super::{
AuthProof, AuthRequired, AuthToken, BlindAuthToken, BlindSignature, BlindedMessage, Error,
Mint, State,
};
impl Mint {
#[instrument(skip(self), fields(endpoint = ?method))]
pub async fn is_protected(
&self,
method: &ProtectedEndpoint,
) -> Result<Option<AuthRequired>, Error> {
if let Some(auth_db) = self.auth_localstore.as_ref() {
Ok(auth_db.get_auth_for_endpoint(method.clone()).await?)
} else {
Ok(None)
}
}
#[instrument(skip_all, fields(token_len = token.len()))]
pub async fn verify_clear_auth(&self, token: String) -> Result<(), Error> {
Ok(self
.oidc_client
.as_ref()
.ok_or(Error::OidcNotSet)?
.verify_cat(&token)
.await?)
}
#[instrument(skip(self, token))]
pub async fn verify_blind_auth(&self, token: &BlindAuthToken) -> Result<(), Error> {
self.signatory
.verify_proofs(vec![token.auth_proof.clone().into()])
.await
}
#[instrument(skip_all)]
pub async fn verify_auth(
&self,
auth_token: Option<AuthToken>,
endpoint: &ProtectedEndpoint,
) -> Result<(), Error> {
let auth_required = if let Some(auth_required) = self.is_protected(endpoint).await? {
tracing::trace!(
"Auth required for endpoint: {:?}, type: {:?}",
endpoint,
auth_required
);
auth_required
} else {
tracing::trace!("No auth required for endpoint: {:?}", endpoint);
return Ok(());
};
tracing::info!(
"Auth required for endpoint: {:?}, type: {:?}",
endpoint,
auth_required
);
let auth_token = match auth_token {
Some(token) => token,
None => match auth_required {
AuthRequired::Clear => {
tracing::warn!(
"No auth token provided for protected endpoint: {:?}, expected clear auth.",
endpoint
);
return Err(Error::ClearAuthRequired);
}
AuthRequired::Blind => {
tracing::warn!(
"No auth token provided for protected endpoint: {:?}, expected blind auth.",
endpoint
);
return Err(Error::BlindAuthRequired);
}
},
};
match (auth_required, auth_token) {
(AuthRequired::Clear, AuthToken::ClearAuth(token)) => {
tracing::debug!("Verifying clear auth token");
match self.verify_clear_auth(token.clone()).await {
Ok(_) => tracing::info!("Clear auth verification successful"),
Err(e) => {
tracing::error!("Clear auth verification failed: {:?}", e);
return Err(e);
}
}
}
(AuthRequired::Blind, AuthToken::BlindAuth(token)) => {
tracing::debug!(
"Verifying blind auth token with keyset_id: {:?}",
token.auth_proof.keyset_id
);
match self.verify_blind_auth(&token).await {
Ok(_) => tracing::debug!("Blind auth signature verification successful"),
Err(e) => {
tracing::error!("Blind auth verification failed: {:?}", e);
return Err(e);
}
}
let auth_proof = token.auth_proof;
self.check_blind_auth_proof_spendable(auth_proof)
.await
.map_err(|err| {
tracing::error!("Failed to spend blind auth proof: {:?}", err);
err
})?;
}
(AuthRequired::Blind, other) => {
tracing::warn!(
"Blind auth required but received different auth type: {:?}",
other
);
return Err(Error::BlindAuthRequired);
}
(AuthRequired::Clear, other) => {
tracing::warn!(
"Clear auth required but received different auth type: {:?}",
other
);
return Err(Error::ClearAuthRequired);
}
}
tracing::debug!("Auth verification completed successfully");
Ok(())
}
#[instrument(skip_all)]
pub async fn check_blind_auth_proof_spendable(&self, proof: AuthProof) -> Result<(), Error> {
tracing::trace!(
"Checking if blind auth proof is spendable for keyset ID: {:?}",
proof.keyset_id
);
let auth_localstore = match self.auth_localstore.as_ref() {
Some(store) => store,
None => {
tracing::error!("Auth localstore is not configured");
return Err(Error::AmountKey);
}
};
let y = proof.y().map_err(|err| {
tracing::error!("Failed to calculate Y value for proof: {:?}", err);
err
})?;
let mut tx = auth_localstore.begin_transaction().await?;
tx.add_proof(proof.clone()).await.map_err(|err| {
tracing::error!("Failed to add proof to database: {:?}", err);
err
})?;
let state = match tx.update_proof_state(&y, State::Spent).await {
Ok(state) => {
tracing::debug!(
"Successfully updated proof state to SPENT, previous state: {:?}",
state
);
state
}
Err(e) => {
tracing::error!("Failed to update proof state: {:?}", e);
return Err(e.into());
}
};
match state {
Some(State::Spent) => {
tracing::warn!("Token already spent: {:?}", y);
return Err(Error::TokenAlreadySpent);
}
Some(State::Pending) => {
tracing::warn!("Token is pending: {:?}", y);
return Err(Error::TokenPending);
}
Some(other_state) => {
tracing::trace!("Token was in state {:?}, now marked as spent", other_state);
}
None => {
tracing::trace!("Token was in state None, now marked as spent");
}
};
tx.commit().await?;
Ok(())
}
#[instrument(skip_all)]
pub async fn auth_blind_sign(
&self,
blinded_message: &BlindedMessage,
) -> Result<BlindSignature, Error> {
self.signatory
.blind_sign(vec![blinded_message.to_owned()])
.await?
.pop()
.ok_or(Error::Internal)
}
}