Skip to main content

x_twitter_sdk/
auth.rs

1use crate::types::XError;
2use oauth1_request::signature_method::hmac_sha1::HmacSha1;
3use oauth1_request::{Credentials as OAuthCredentials, Token as OAuthToken};
4use reqwest::Request;
5
6/// OAuth 1.0a credentials for Twitter/X API
7#[derive(Debug, Clone)]
8pub struct Credentials {
9    /// App Key (Consumer Key)
10    pub app_key: String,
11    /// App Secret (Consumer Secret)
12    pub app_secret: String,
13    /// Access Token
14    pub access_token: String,
15    /// Access Token Secret
16    pub access_secret: String,
17}
18
19impl Credentials {
20    /// Create new credentials
21    pub fn new(
22        app_key: impl Into<String>,
23        app_secret: impl Into<String>,
24        access_token: impl Into<String>,
25        access_secret: impl Into<String>,
26    ) -> Self {
27        Self {
28            app_key: app_key.into(),
29            app_secret: app_secret.into(),
30            access_token: access_token.into(),
31            access_secret: access_secret.into(),
32        }
33    }
34
35    /// Validate that all credentials are non-empty
36    pub fn validate(&self) -> Result<(), XError> {
37        if self.app_key.is_empty()
38            || self.app_secret.is_empty()
39            || self.access_token.is_empty()
40            || self.access_secret.is_empty()
41        {
42            return Err(XError::AuthenticationFailed(
43                "All credentials must be non-empty".to_string(),
44            ));
45        }
46        Ok(())
47    }
48}
49
50/// OAuth 1.0a signature generator
51#[derive(Clone)]
52pub struct OAuth {
53    credentials: Credentials,
54}
55
56impl OAuth {
57    /// Create a new OAuth instance
58    pub fn new(credentials: Credentials) -> Result<Self, XError> {
59        credentials.validate()?;
60        Ok(Self { credentials })
61    }
62
63    /// Sign a request with OAuth 1.0a
64    pub fn sign_request(&self, request: &mut Request) -> Result<(), XError> {
65        let method = request.method().as_str();
66        let url = request.url().as_str();
67
68        // Convert to oauth1_request types
69        let client = OAuthCredentials::new(&self.credentials.app_key, &self.credentials.app_secret);
70        let token_creds = OAuthCredentials::new(
71            &self.credentials.access_token,
72            &self.credentials.access_secret,
73        );
74        let token = OAuthToken::new(client, token_creds);
75
76        // Generate OAuth authorization header with empty request parameters
77        let auth_header = oauth1_request::authorize(method, url, &(), &token, HmacSha1::new());
78
79        // Add the authorization header to the request
80        request.headers_mut().insert(
81            "Authorization",
82            auth_header.parse().map_err(|e| {
83                XError::OAuthError(format!("Failed to parse authorization header: {}", e))
84            })?,
85        );
86
87        Ok(())
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn test_credentials_new() {
97        let creds = Credentials::new("key", "secret", "token", "token_secret");
98        assert_eq!(creds.app_key, "key");
99        assert_eq!(creds.app_secret, "secret");
100        assert_eq!(creds.access_token, "token");
101        assert_eq!(creds.access_secret, "token_secret");
102    }
103
104    #[test]
105    fn test_credentials_validate_success() {
106        let creds = Credentials::new("key", "secret", "token", "token_secret");
107        assert!(creds.validate().is_ok());
108    }
109
110    #[test]
111    fn test_credentials_validate_empty() {
112        let creds = Credentials::new("", "secret", "token", "token_secret");
113        assert!(creds.validate().is_err());
114    }
115}