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}