nonce_auth/nonce/
verifier.rs

1use hmac::Mac;
2
3use crate::NonceCredential;
4use crate::nonce::{NonceError, NonceServer};
5use crate::storage::NonceStorage;
6
7/// A verifier for `NonceCredential`s, created by `NonceServer::credential_verifier`.
8///
9/// This builder-like struct provides a safe and ergonomic API for verifying credentials.
10#[must_use = "The verifier does nothing unless one of the `verify` methods is called."]
11pub struct CredentialVerifier<'a, S: NonceStorage> {
12    server: &'a NonceServer<S>,
13    credential: &'a NonceCredential,
14    context: Option<&'a str>,
15    secret: Option<&'a [u8]>,
16}
17
18impl<'a, S: NonceStorage> CredentialVerifier<'a, S> {
19    /// Creates a new verifier.
20    pub(crate) fn new(server: &'a NonceServer<S>, credential: &'a NonceCredential) -> Self {
21        Self {
22            server,
23            credential,
24            context: None,
25            secret: None,
26        }
27    }
28
29    /// Sets the context for this verification operation.
30    ///
31    /// The context provides an additional layer of isolation for nonces.
32    pub fn with_context(mut self, context: Option<&'a str>) -> Self {
33        self.context = context;
34        self
35    }
36
37    /// Sets the secret key for this verification operation.
38    ///
39    /// This is required for signature verification. Each user/client may have a different secret.
40    pub fn with_secret(mut self, secret: &'a [u8]) -> Self {
41        self.secret = Some(secret);
42        self
43    }
44
45    /// Verifies the credential against a standard payload.
46    ///
47    /// This is the recommended verification method for most use cases. It assumes the
48    /// signature was created on the client using `sign(payload)`.
49    ///
50    /// # Arguments
51    ///
52    /// * `payload`: The payload that was signed on the client side.
53    pub async fn verify(self, payload: &[u8]) -> Result<(), NonceError> {
54        let timestamp_str = self.credential.timestamp.to_string();
55        let nonce_str = &self.credential.nonce;
56
57        self.verify_with(|mac| {
58            mac.update(timestamp_str.as_bytes());
59            mac.update(nonce_str.as_bytes());
60            mac.update(payload);
61        })
62        .await
63    }
64
65    /// Verifies the credential using custom signature-reconstruction logic.
66    ///
67    /// This method is for advanced scenarios where the signature includes more than
68    /// just the standard payload.
69    ///
70    /// # Warning
71    ///
72    /// The logic in the `signature_builder` closure must exactly match the logic
73    /// used on the client side in `sign_with`, otherwise verification will fail.
74    ///
75    /// # Arguments
76    ///
77    /// * `signature_builder`: A closure to reconstruct the signed data.
78    pub async fn verify_with<F>(self, signature_builder: F) -> Result<(), NonceError>
79    where
80        F: FnOnce(&mut hmac::Hmac<sha2::Sha256>),
81    {
82        let secret = self.secret.ok_or_else(|| {
83            NonceError::CryptoError("Secret key must be provided using with_secret()".to_string())
84        })?;
85
86        self.server.verify_timestamp(self.credential.timestamp)?;
87        NonceServer::<S>::verify_signature(secret, &self.credential.signature, signature_builder)?;
88        self.server
89            .verify_and_consume_nonce(&self.credential.nonce, self.context)
90            .await
91    }
92}