dimo_rust_sdk/rest/auth/
auth.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
use crate::utils::request::{make_request, RequestParams};
use hex;
use reqwest::Method;
use secp256k1::{Message, Secp256k1, SecretKey};
use serde::Deserialize;
use serde_json::Value;
use sha3::{Digest, Keccak256};
use std::collections::HashMap;
use std::error::Error;

pub struct AuthClient {
    base_url: String,
}

#[derive(Debug, Deserialize)]
pub struct ChallengeResponse {
    pub challenge: String,
    pub state: String,
}

#[derive(Deserialize, Debug)]
pub struct AccessToken {
    access_token: String,
    id_token: String,
    token_type: String,
    expires_in: i32,
}

impl AuthClient {
    pub fn new(base_url: String) -> Self {
        Self { base_url }
    }

    pub async fn generate_challenge(
        &self,
        client_id: &str,
        domain: &str,
    ) -> Result<ChallengeResponse, Box<dyn Error>> {
        let mut query_params: HashMap<String, String> = HashMap::new();
        query_params.insert("client_id".to_string(), client_id.to_string());
        query_params.insert("domain".to_string(), domain.to_string());
        query_params.insert("scope".to_string(), "openid email".to_string());
        query_params.insert("response_type".to_string(), "code".to_string());
        query_params.insert("address".to_string(), client_id.to_string());

        let params = RequestParams {
            method: Method::POST,
            base_url: self.base_url.clone(),
            path: "/auth/web3/generate_challenge".to_string(),
            query_params: Some(query_params),
            body: None,
            headers: None,
        };

        let response = make_request(params).await?;
        let challenge_response: ChallengeResponse = serde_json::from_value(response)?;
        Ok(challenge_response)
    }

    pub fn sign_challenge(
        &self,
        message: &str,
        private_key: &str,
    ) -> Result<String, Box<dyn Error>> {
        let secp = Secp256k1::new();
        let private_key_bytes = hex::decode(private_key.strip_prefix("0x").unwrap_or(private_key))?;
        let secret_key = SecretKey::from_slice(&private_key_bytes)?;

        let prefix = format!("\x19Ethereum Signed Message:\n{}", message.len());
        let prefixed_message = [prefix.as_bytes(), message.as_bytes()].concat();
        let digest = Keccak256::digest(&prefixed_message);
        let message = Message::from_digest_slice(&digest)?;
        let signature = secp.sign_ecdsa_recoverable(&message, &secret_key);

        let (recovery_id, signature_bytes) = signature.serialize_compact();
        let v = recovery_id.to_i32() as u8 + 27;
        let mut signature_full = Vec::with_capacity(65);
        signature_full.extend_from_slice(&signature_bytes);
        signature_full.push(v);

        Ok(hex::encode(signature_full))
    }

    pub async fn submit_challenge(
        &self,
        client_id: &str,
        domain: &str,
        state: &str,
        signature: &str,
    ) -> Result<AccessToken, Box<dyn Error>> {
        let mut body_params: HashMap<String, Value> = HashMap::new();
        body_params.insert(
            "client_id".to_string(),
            Value::String(client_id.to_string()),
        );
        body_params.insert("domain".to_string(), Value::String(domain.to_string()));
        body_params.insert(
            "grant_type".to_string(),
            Value::String("authorization_code".to_string()),
        );
        body_params.insert("state".to_string(), Value::String(state.to_string()));
        body_params.insert(
            "signature".to_string(),
            Value::String(format!("0x{}", signature.to_string())),
        );

        let mut headers: HashMap<String, String> = HashMap::new();
        headers.insert(
            "Content-Type".to_string(),
            "x-www-form-urlencoded".to_string(),
        );

        let params = RequestParams {
            method: Method::POST,
            base_url: self.base_url.clone(),
            path: "/auth/web3/submit_challenge".to_string(),
            query_params: None,
            body: Some(body_params),
            headers: Some(headers),
        };

        let response = make_request(params).await.expect("could not submit");
        let token: AccessToken =
            serde_json::from_value(response).expect("could not parse response token");

        Ok(token)
    }
}