spark_sdk/wallet/internal_handlers/implementations/
authenticate.rs

1use std::str::FromStr;
2
3use crate::error::SparkSdkError;
4use crate::rpc::connections::connection::SparkConnection;
5use crate::rpc::traits::SparkRpcConnection;
6use crate::signer::traits::SparkSigner;
7use crate::wallet::client::Session;
8use crate::wallet::internal_handlers::traits::authenticate::AuthenticateInternalHandlers;
9use crate::SparkSdk;
10use prost::Message;
11use spark_protos::authn::GetChallengeRequest;
12use spark_protos::authn::VerifyChallengeRequest;
13use tonic::async_trait;
14use tonic::transport::Uri;
15use tonic::Request;
16
17#[async_trait]
18impl<S: SparkSigner + Send + Sync + Clone + 'static> AuthenticateInternalHandlers<S>
19    for SparkSdk<S>
20{
21    async fn authenticate(&self) -> Result<Vec<Session>, SparkSdkError> {
22        let spark_operators = self.config.spark_config.spark_operators.clone();
23
24        let mut operator_sessions = Vec::new();
25
26        for operator in spark_operators {
27            let uri = Uri::from_str(&operator.address)?;
28            let client = SparkConnection::establish_connection(uri).await?;
29            let mut auth_client = client.get_new_spark_authn_service_connection()?;
30
31            // prepare the request using the identity public key
32            let pk_bytes = self.get_identity_public_key();
33            let challenge_req = GetChallengeRequest {
34                public_key: pk_bytes.to_vec(),
35            };
36
37            // get the challenge from Spark Authn Service
38            let spark_authn_response = auth_client
39                .get_challenge(Request::new(challenge_req))
40                .await?
41                .into_inner();
42
43            let protected_challenge = spark_authn_response.protected_challenge.ok_or(
44                SparkSdkError::AuthenticationError(
45                    "No ProtectedChallenge returned by server".to_string(),
46                ),
47            )?;
48
49            // sign the challenge
50            let challenge =
51                protected_challenge
52                    .challenge
53                    .clone()
54                    .ok_or(SparkSdkError::AuthenticationError(
55                        "Missing Challenge within ProtectedChallenge".to_string(),
56                    ))?;
57
58            // Serialize the challenge to match what the server uses
59            let challenge_bytes = challenge.encode_to_vec(); // This is the same as proto.Marshal in Go.
60            let serialized_signature = self
61                .signer
62                .sign_message_ecdsa_with_identity_key(&challenge_bytes, true)?;
63
64            let verify_req = VerifyChallengeRequest {
65                protected_challenge: Some(protected_challenge),
66                signature: serialized_signature,
67                public_key: pk_bytes.to_vec(),
68            };
69
70            let verify_resp = auth_client
71                .verify_challenge(Request::new(verify_req))
72                .await?
73                .into_inner();
74
75            let session = Session {
76                session_token: verify_resp.session_token,
77                expiration_timestamp: verify_resp.expiration_timestamp,
78            };
79
80            operator_sessions.push(session);
81        }
82
83        Ok(operator_sessions)
84    }
85}