Skip to main content

tuitbot_core/source/connector/
mod.rs

1//! Remote connector module for OAuth-based source linking.
2//!
3//! Provides the `RemoteConnector` trait and implementations for
4//! linking, refreshing, and revoking user-owned connections to
5//! remote services (e.g. Google Drive). Credentials are encrypted
6//! at rest via AES-256-GCM (see `crypto` submodule).
7
8pub mod crypto;
9pub mod google_drive;
10#[cfg(test)]
11mod tests;
12
13use async_trait::async_trait;
14
15// ---------------------------------------------------------------------------
16// Error type
17// ---------------------------------------------------------------------------
18
19/// Errors from remote connector operations.
20#[derive(Debug, thiserror::Error)]
21pub enum ConnectorError {
22    #[error("connector not configured: {0}")]
23    NotConfigured(String),
24
25    #[error("invalid OAuth state")]
26    InvalidState,
27
28    #[error("token exchange failed: {0}")]
29    TokenExchange(String),
30
31    #[error("token refresh failed: {0}")]
32    TokenRefresh(String),
33
34    #[error("revocation failed: {0}")]
35    Revocation(String),
36
37    #[error("encryption error: {0}")]
38    Encryption(String),
39
40    #[error("storage error: {source}")]
41    Storage {
42        #[from]
43        source: crate::error::StorageError,
44    },
45
46    #[error("network error: {0}")]
47    Network(String),
48}
49
50// ---------------------------------------------------------------------------
51// Token types
52// ---------------------------------------------------------------------------
53
54/// Tokens received from an initial OAuth code exchange.
55pub struct TokenSet {
56    pub access_token: String,
57    pub refresh_token: String,
58    pub expires_in_secs: i64,
59    pub scope: String,
60}
61
62/// A refreshed access token (refresh token stays the same).
63pub struct RefreshedToken {
64    pub access_token: String,
65    pub expires_in_secs: i64,
66}
67
68/// Basic user identity from the OAuth provider.
69pub struct UserInfo {
70    pub email: String,
71    pub display_name: Option<String>,
72}
73
74// ---------------------------------------------------------------------------
75// Trait
76// ---------------------------------------------------------------------------
77
78/// Abstraction over OAuth-based remote connectors.
79///
80/// Each connector knows how to build authorization URLs, exchange
81/// codes for tokens, refresh access tokens, revoke connections,
82/// and fetch basic user info.
83#[async_trait]
84pub trait RemoteConnector: Send + Sync {
85    /// Returns the connector type identifier (e.g. `"google_drive"`).
86    fn connector_type(&self) -> &str;
87
88    /// Build the OAuth authorization URL the user should visit.
89    fn authorization_url(
90        &self,
91        state: &str,
92        code_challenge: &str,
93    ) -> Result<String, ConnectorError>;
94
95    /// Exchange an authorization code for tokens.
96    async fn exchange_code(
97        &self,
98        code: &str,
99        code_verifier: &str,
100    ) -> Result<TokenSet, ConnectorError>;
101
102    /// Refresh an access token using encrypted refresh-token material.
103    async fn refresh_access_token(
104        &self,
105        encrypted_refresh: &[u8],
106        key: &[u8],
107    ) -> Result<RefreshedToken, ConnectorError>;
108
109    /// Revoke a connection (best-effort).
110    async fn revoke(&self, encrypted_refresh: &[u8], key: &[u8]) -> Result<(), ConnectorError>;
111
112    /// Fetch basic user identity from the provider.
113    async fn user_info(&self, access_token: &str) -> Result<UserInfo, ConnectorError>;
114}