dmsc/auth/oauth.rs
1//! Copyright © 2025 Wenze Wei. All Rights Reserved.
2//!
3//! This file is part of DMSC.
4//! The DMSC project belongs to the Dunimd Team.
5//!
6//! Licensed under the Apache License, Version 2.0 (the "License");
7//! You may not use this file except in compliance with the License.
8//! You may obtain a copy of the License at
9//!
10//! http://www.apache.org/licenses/LICENSE-2.0
11//!
12//! Unless required by applicable law or agreed to in writing, software
13//! distributed under the License is distributed on an "AS IS" BASIS,
14//! WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15//! See the License for the specific language governing permissions and
16//! limitations under the License.
17
18//! OAuth 2.0 authentication implementation for DMSC.
19//!
20//! This module provides OAuth 2.0 authentication functionality, including support for
21//! multiple identity providers, token management, and user information retrieval.
22//! It implements the OAuth 2.0 authorization code flow and supports token refresh and revocation.
23//!
24//! # Design Principles
25//! - **Multi-Provider Support**: Allows registration of multiple OAuth providers
26//! - **Thread Safety**: Uses RwLock for concurrent access to provider configuration
27//! - **Caching**: Integrates with DMSC cache for token storage
28//! - **Async Operations**: All network operations are asynchronous
29//! - **Extensibility**: Designed to support additional OAuth flows and providers
30//!
31//! # Usage Examples
32//! ```rust
33//! // Create an OAuth manager with a cache
34//! let cache = Arc::new(crate::cache::backends::memory_backend::DMSCMemoryCache::new());
35//! let oauth_manager = DMSCOAuthManager::new(cache);
36//!
37//! // Register a Google OAuth provider
38//! let google_provider = DMSCOAuthProvider {
39//! id: "google".to_string(),
40//! name: "Google".to_string(),
41//! client_id: "client_id".to_string(),
42//! client_secret: "client_secret".to_string(),
43//! auth_url: "https://accounts.google.com/o/oauth2/auth".to_string(),
44//! token_url: "https://oauth2.googleapis.com/token".to_string(),
45//! user_info_url: "https://www.googleapis.com/oauth2/v3/userinfo".to_string(),
46//! scopes: vec!["openid", "email", "profile"].iter().map(|s| s.to_string()).collect(),
47//! enabled: true,
48//! };
49//! oauth_manager.register_provider(google_provider).await?;
50//!
51//! // Get authentication URL for a provider
52//! let auth_url = oauth_manager.get_auth_url("google", "state123").await?;
53//!
54//! // Exchange authorization code for token
55//! let token = oauth_manager.exchange_code_for_token(
56//! "google",
57//! "auth_code",
58//! "http://localhost:8080/auth/callback"
59//! ).await?;
60//!
61//! // Get user information
62//! if let Some(token) = token {
63//! let user_info = oauth_manager.get_user_info("google", &token.access_token).await?;
64//! }
65//! ```
66
67#![allow(non_snake_case)]
68
69use serde::{Deserialize, Serialize};
70use std::collections::HashMap;
71use std::sync::Arc;
72use tokio::sync::RwLock;
73#[cfg(feature = "pyo3")]
74use tokio::runtime::Runtime;
75
76#[cfg(feature = "pyo3")]
77use pyo3::PyResult;
78
79#[cfg(feature = "auth")]
80extern crate urlencoding;
81
82/// OAuth provider configuration.
83///
84/// This struct defines the configuration for an OAuth identity provider,
85/// including client credentials, endpoints, scopes, and redirect URI.
86#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct DMSCOAuthProvider {
89 /// Unique identifier for the OAuth provider
90 pub id: String,
91 /// Human-readable name of the provider (e.g., "Google", "GitHub")
92 pub name: String,
93 /// OAuth client ID issued by the provider
94 pub client_id: String,
95 /// OAuth client secret issued by the provider
96 pub client_secret: String,
97 /// Authorization endpoint URL for initiating OAuth flow
98 pub auth_url: String,
99 /// Token endpoint URL for exchanging authorization codes
100 pub token_url: String,
101 /// User information endpoint URL for retrieving user details
102 pub user_info_url: String,
103 /// Requested OAuth scopes (e.g., "openid", "email", "profile")
104 pub scopes: Vec<String>,
105 /// Whether the provider is enabled for authentication
106 pub enabled: bool,
107 /// Redirect URI for OAuth callback (defaults to "http://localhost:8080/auth/callback" if not set)
108 pub redirect_uri: Option<String>,
109}
110
111#[cfg(feature = "pyo3")]
112#[pyo3::prelude::pymethods]
113impl DMSCOAuthProvider {
114 #[new]
115 fn py_new(
116 id: String,
117 name: String,
118 client_id: String,
119 client_secret: String,
120 auth_url: String,
121 token_url: String,
122 user_info_url: String,
123 scopes: Vec<String>,
124 enabled: bool,
125 redirect_uri: Option<String>,
126 ) -> Self {
127 Self {
128 id,
129 name,
130 client_id,
131 client_secret,
132 auth_url,
133 token_url,
134 user_info_url,
135 scopes,
136 enabled,
137 redirect_uri,
138 }
139 }
140}
141
142/// OAuth token response.
143///
144/// This struct represents the token response from an OAuth provider,
145/// including access token, refresh token, and expiration information.
146#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
147#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct DMSCOAuthToken {
149 /// Access token for making authenticated API requests
150 pub access_token: String,
151 /// Refresh token for obtaining new access tokens when expired
152 pub refresh_token: Option<String>,
153 /// Token expiration time in seconds from issuance
154 pub expires_in: Option<i64>,
155 /// Token type (typically "Bearer")
156 pub token_type: String,
157 /// Granted scopes (may differ from requested scopes)
158 pub scope: Option<String>,
159}
160
161#[cfg(feature = "pyo3")]
162#[pyo3::prelude::pymethods]
163impl DMSCOAuthToken {
164 #[new]
165 fn py_new(
166 access_token: String,
167 token_type: String,
168 refresh_token: Option<String>,
169 scope: Option<String>,
170 expires_in: Option<i64>,
171 ) -> Self {
172 Self {
173 access_token,
174 token_type,
175 refresh_token,
176 scope,
177 expires_in,
178 }
179 }
180}
181
182/// OAuth user information.
183///
184/// This struct represents the user information retrieved from an OAuth provider,
185/// including user ID, email, name, and profile information.
186#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass(get_all, set_all))]
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct DMSCOAuthUserInfo {
189 /// Unique user identifier from the OAuth provider
190 pub id: String,
191 /// User's email address from the provider
192 pub email: String,
193 /// User's full name from the provider
194 pub name: Option<String>,
195 /// URL to user's avatar profile image
196 pub avatar_url: Option<String>,
197 /// Name of the OAuth provider that authenticated the user
198 pub provider: String,
199}
200
201#[cfg(feature = "pyo3")]
202#[pyo3::prelude::pymethods]
203impl DMSCOAuthUserInfo {
204 #[new]
205 fn py_new(
206 id: String,
207 email: String,
208 name: Option<String>,
209 avatar_url: Option<String>,
210 provider: String,
211 ) -> Self {
212 Self {
213 id,
214 email,
215 name,
216 avatar_url,
217 provider,
218 }
219 }
220}
221
222/// OAuth manager for handling multiple identity providers.
223///
224/// This struct manages OAuth providers, handles token exchange, and retrieves user information.
225/// It supports concurrent access through RwLock and integrates with the DMSC cache system
226/// for token storage.
227#[cfg_attr(feature = "pyo3", pyo3::prelude::pyclass)]
228pub struct DMSCOAuthManager {
229 /// Hash map of registered OAuth providers indexed by provider ID
230 providers: RwLock<HashMap<String, DMSCOAuthProvider>>,
231 /// Cache implementation for storing OAuth tokens
232 _token_cache: Arc<dyn crate::cache::DMSCCache>,
233}
234
235impl DMSCOAuthManager {
236 /// Creates a new OAuth manager with the specified cache.
237 ///
238 /// # Parameters
239 /// - `cache`: Cache implementation for storing tokens
240 ///
241 /// # Returns
242 /// A new instance of `DMSCOAuthManager`
243 pub fn new(cache: Arc<dyn crate::cache::DMSCCache>) -> Self {
244 Self {
245 providers: RwLock::new(HashMap::new()),
246 _token_cache: cache,
247 }
248 }
249
250 /// Registers a new OAuth provider.
251 ///
252 /// # Parameters
253 /// - `provider`: OAuth provider configuration
254 ///
255 /// # Returns
256 /// `Ok(())` if the provider was successfully registered
257 pub async fn register_provider(&self, provider: DMSCOAuthProvider) -> crate::core::DMSCResult<()> {
258 let mut providers = self.providers.write().await;
259 providers.insert(provider.id.clone(), provider);
260 Ok(())
261 }
262
263 /// Gets an OAuth provider by ID.
264 ///
265 /// # Parameters
266 /// - `provider_id`: Unique identifier of the provider
267 ///
268 /// # Returns
269 /// `Some(DMSCOAuthProvider)` if the provider exists, otherwise `None`
270 pub async fn get_provider(&self, provider_id: &str) -> crate::core::DMSCResult<Option<DMSCOAuthProvider>> {
271 let providers = self.providers.read().await;
272 Ok(providers.get(provider_id).cloned())
273 }
274
275 /// Gets the authentication URL for a provider.
276 ///
277 /// # Parameters
278 /// - `provider_id`: Unique identifier of the provider
279 /// - `state`: State parameter for CSRF protection
280 ///
281 /// # Returns
282 /// `Some(String)` containing the authentication URL if the provider is enabled, otherwise `None`
283 #[cfg(feature = "auth")]
284 pub async fn get_auth_url(&self, provider_id: &str, state: &str) -> crate::core::DMSCResult<Option<String>> {
285 let providers = self.providers.read().await;
286
287 if let Some(provider) = providers.get(provider_id) {
288 if !provider.enabled {
289 return Ok(None);
290 }
291
292 let scope = provider.scopes.join(" ");
293 let encoded_scope = scope.clone();
294 let redirect_uri = provider.redirect_uri.as_deref()
295 .unwrap_or("http://localhost:8080/auth/callback");
296 let auth_url = format!(
297 "{}?client_id={}&redirect_uri={}&response_type=code&scope={}&state={}",
298 provider.auth_url,
299 provider.client_id,
300 urlencoding::encode(redirect_uri),
301 encoded_scope,
302 state
303 );
304
305 Ok(Some(auth_url))
306 } else {
307 Ok(None)
308 }
309 }
310
311 #[cfg(not(feature = "auth"))]
312 /// Gets the authentication URL for a provider.
313 ///
314 /// This method requires the `auth` feature to be enabled.
315 /// Without this feature, calling this method returns an error.
316 pub async fn get_auth_url(&self, _provider_id: &str, _state: &str) -> crate::core::DMSCResult<Option<String>> {
317 Err(crate::core::DMSCError::Other("Auth feature is not enabled. Enable the 'auth' feature to use OAuth functionality.".to_string()))
318 }
319
320 /// Exchanges an authorization code for an access token.
321 ///
322 /// # Parameters
323 /// - `provider_id`: Unique identifier of the provider
324 /// - `code`: Authorization code from the provider
325 /// - `redirect_uri`: Redirect URI used in the authentication request
326 ///
327 /// # Returns
328 /// `Some(DMSCOAuthToken)` if the code exchange was successful, otherwise `None`
329 #[cfg(feature = "http_client")]
330 pub async fn exchange_code_for_token(
331 &self,
332 provider_id: &str,
333 code: &str,
334 redirect_uri: &str,
335 ) -> crate::core::DMSCResult<Option<DMSCOAuthToken>> {
336 let providers = self.providers.read().await;
337
338 if let Some(provider) = providers.get(provider_id) {
339 if !provider.enabled {
340 return Ok(None);
341 }
342
343 let client = reqwest::Client::new();
344 let params = [
345 ("grant_type", "authorization_code"),
346 ("code", code),
347 ("redirect_uri", redirect_uri),
348 ("client_id", &provider.client_id),
349 ("client_secret", &provider.client_secret),
350 ];
351
352 let response = client
353 .post(&provider.token_url)
354 .form(¶ms)
355 .send()
356 .await
357 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
358
359 if response.status().is_success() {
360 let token_data: serde_json::Value = response.json().await
361 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
362
363 let token = DMSCOAuthToken {
364 access_token: token_data["access_token"]
365 .as_str()
366 .ok_or_else(|| crate::core::DMSCError::ExternalError("Missing access_token".to_string()))?
367 .to_string(),
368 refresh_token: token_data["refresh_token"].as_str().map(String::from),
369 expires_in: token_data["expires_in"].as_i64(),
370 token_type: token_data["token_type"]
371 .as_str()
372 .unwrap_or("Bearer")
373 .to_string(),
374 scope: token_data["scope"].as_str().map(String::from),
375 };
376
377 Ok(Some(token))
378 } else {
379 Ok(None)
380 }
381 } else {
382 Ok(None)
383 }
384 }
385
386 #[cfg(not(feature = "http_client"))]
387 /// Exchanges an authorization code for an access token.
388 ///
389 /// This method requires the `http_client` feature to be enabled.
390 /// Without this feature, calling this method returns an error.
391 ///
392 /// # Parameters
393 ///
394 /// - `_provider_id`: Unique identifier of the provider (not used when feature is disabled)
395 /// - `_code`: Authorization code from the provider (not used when feature is disabled)
396 /// - `_redirect_uri`: Redirect URI used in the authentication request (not used when feature is disabled)
397 ///
398 /// # Returns
399 ///
400 /// A Result containing an error indicating the http_client feature is not enabled
401 pub async fn exchange_code_for_token(
402 &self,
403 _provider_id: &str,
404 _code: &str,
405 _redirect_uri: &str,
406 ) -> crate::core::DMSCResult<Option<DMSCOAuthToken>> {
407 Err(crate::core::DMSCError::Other("HTTP client is not enabled. Enable the 'http_client' feature to use OAuth functionality.".to_string()))
408 }
409
410 /// Retrieves user information from an OAuth provider.
411 ///
412 /// # Parameters
413 /// - `provider_id`: Unique identifier of the provider
414 /// - `access_token`: Access token for authentication
415 ///
416 /// # Returns
417 /// `Some(DMSCOAuthUserInfo)` if the user information was successfully retrieved, otherwise `None`
418 #[cfg(feature = "http_client")]
419 pub async fn get_user_info(
420 &self,
421 provider_id: &str,
422 access_token: &str,
423 ) -> crate::core::DMSCResult<Option<DMSCOAuthUserInfo>> {
424 let providers = self.providers.read().await;
425
426 if let Some(provider) = providers.get(provider_id) {
427 if !provider.enabled {
428 return Ok(None);
429 }
430
431 let client = reqwest::Client::new();
432 let response = client
433 .get(&provider.user_info_url)
434 .bearer_auth(access_token)
435 .send()
436 .await
437 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
438
439 if response.status().is_success() {
440 let user_data: serde_json::Value = response.json().await
441 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
442
443 let user_info = DMSCOAuthUserInfo {
444 id: user_data["id"]
445 .as_str()
446 .ok_or_else(|| crate::core::DMSCError::ExternalError("Missing user id".to_string()))?
447 .to_string(),
448 email: user_data["email"]
449 .as_str()
450 .ok_or_else(|| crate::core::DMSCError::ExternalError("Missing email".to_string()))?
451 .to_string(),
452 name: user_data["name"].as_str().map(String::from),
453 avatar_url: user_data["avatar_url"].as_str().map(String::from),
454 provider: provider_id.to_string(),
455 };
456
457 Ok(Some(user_info))
458 } else {
459 Ok(None)
460 }
461 } else {
462 Ok(None)
463 }
464 }
465
466 #[cfg(not(feature = "http_client"))]
467 /// Retrieves user information from an OAuth provider.
468 ///
469 /// This method requires the `http_client` feature to be enabled.
470 /// Without this feature, calling this method returns an error.
471 ///
472 /// # Parameters
473 ///
474 /// - `_provider_id`: Unique identifier of the provider (not used when feature is disabled)
475 /// - `_access_token`: Access token for authentication (not used when feature is disabled)
476 ///
477 /// # Returns
478 ///
479 /// A Result containing an error indicating the http_client feature is not enabled
480 pub async fn get_user_info(
481 &self,
482 _provider_id: &str,
483 _access_token: &str,
484 ) -> crate::core::DMSCResult<Option<DMSCOAuthUserInfo>> {
485 Err(crate::core::DMSCError::Other("HTTP client is not enabled. Enable the 'http_client' feature to use OAuth functionality.".to_string()))
486 }
487
488 /// Refreshes an access token using a refresh token.
489 ///
490 /// # Parameters
491 /// - `provider_id`: Unique identifier of the provider
492 /// - `refresh_token`: Refresh token for obtaining a new access token
493 ///
494 /// # Returns
495 /// `Some(DMSCOAuthToken)` if the token refresh was successful, otherwise `None`
496 #[cfg(feature = "http_client")]
497 pub async fn refresh_token(
498 &self,
499 provider_id: &str,
500 refresh_token: &str,
501 ) -> crate::core::DMSCResult<Option<DMSCOAuthToken>> {
502 let providers = self.providers.read().await;
503
504 if let Some(provider) = providers.get(provider_id) {
505 if !provider.enabled {
506 return Ok(None);
507 }
508
509 let client = reqwest::Client::new();
510 let params = [
511 ("grant_type", "refresh_token"),
512 ("refresh_token", refresh_token),
513 ("client_id", &provider.client_id),
514 ("client_secret", &provider.client_secret),
515 ];
516
517 let response = client
518 .post(&provider.token_url)
519 .form(¶ms)
520 .send()
521 .await
522 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
523
524 if response.status().is_success() {
525 let token_data: serde_json::Value = response.json().await
526 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
527
528 let token = DMSCOAuthToken {
529 access_token: token_data["access_token"]
530 .as_str()
531 .ok_or_else(|| crate::core::DMSCError::ExternalError("Missing access_token".to_string()))?
532 .to_string(),
533 refresh_token: token_data["refresh_token"].as_str().map(String::from),
534 expires_in: token_data["expires_in"].as_i64(),
535 token_type: token_data["token_type"]
536 .as_str()
537 .unwrap_or("Bearer")
538 .to_string(),
539 scope: token_data["scope"].as_str().map(String::from),
540 };
541
542 Ok(Some(token))
543 } else {
544 Ok(None)
545 }
546 } else {
547 Ok(None)
548 }
549 }
550
551 #[cfg(not(feature = "http_client"))]
552 /// Refreshes an access token using a refresh token.
553 ///
554 /// This method requires the `http_client` feature to be enabled.
555 /// Without this feature, calling this method returns an error.
556 ///
557 /// # Parameters
558 ///
559 /// - `_provider_id`: Unique identifier of the provider (not used when feature is disabled)
560 /// - `_refresh_token`: Refresh token for obtaining a new access token (not used when feature is disabled)
561 ///
562 /// # Returns
563 ///
564 /// A Result containing an error indicating the http_client feature is not enabled
565 pub async fn refresh_token(
566 &self,
567 _provider_id: &str,
568 _refresh_token: &str,
569 ) -> crate::core::DMSCResult<Option<DMSCOAuthToken>> {
570 Err(crate::core::DMSCError::Other("HTTP client is not enabled. Enable the 'http_client' feature to use OAuth functionality.".to_string()))
571 }
572
573 /// Revokes an access token.
574 ///
575 /// # Parameters
576 /// - `provider_id`: Unique identifier of the provider
577 /// - `access_token`: Access token to revoke
578 ///
579 /// # Returns
580 /// `true` if the token was successfully revoked, otherwise `false`
581 #[cfg(feature = "http_client")]
582 pub async fn revoke_token(
583 &self,
584 provider_id: &str,
585 access_token: &str,
586 ) -> crate::core::DMSCResult<bool> {
587 let providers = self.providers.read().await;
588
589 if let Some(provider) = providers.get(provider_id) {
590 if !provider.enabled {
591 return Ok(false);
592 }
593
594 let client = reqwest::Client::new();
595 let params = [
596 ("token", access_token),
597 ("client_id", &provider.client_id),
598 ("client_secret", &provider.client_secret),
599 ];
600
601 let response = client
602 .post(format!("{}/revoke", provider.token_url))
603 .form(¶ms)
604 .send()
605 .await
606 .map_err(|e| crate::core::DMSCError::ExternalError(e.to_string()))?;
607
608 Ok(response.status().is_success())
609 } else {
610 Ok(false)
611 }
612 }
613
614 #[cfg(not(feature = "http_client"))]
615 /// Revokes an access token.
616 ///
617 /// This method requires the `http_client` feature to be enabled.
618 /// Without this feature, calling this method returns an error.
619 ///
620 /// # Parameters
621 ///
622 /// - `_provider_id`: Unique identifier of the provider (not used when feature is disabled)
623 /// - `_access_token`: Access token to revoke (not used when feature is disabled)
624 ///
625 /// # Returns
626 ///
627 /// A Result containing an error indicating the http_client feature is not enabled
628 pub async fn revoke_token(
629 &self,
630 _provider_id: &str,
631 _access_token: &str,
632 ) -> crate::core::DMSCResult<bool> {
633 Err(crate::core::DMSCError::Other("HTTP client is not enabled. Enable the 'http_client' feature to use OAuth functionality.".to_string()))
634 }
635
636 /// Lists all registered OAuth providers.
637 ///
638 /// # Returns
639 /// A vector of all registered OAuth providers
640 pub async fn list_providers(&self) -> crate::core::DMSCResult<Vec<DMSCOAuthProvider>> {
641 let providers = self.providers.read().await;
642 Ok(providers.values().cloned().collect())
643 }
644
645 /// Disables an OAuth provider.
646 ///
647 /// # Parameters
648 /// - `provider_id`: Unique identifier of the provider
649 ///
650 /// # Returns
651 /// `true` if the provider was successfully disabled, otherwise `false`
652 pub async fn disable_provider(&self, provider_id: &str) -> crate::core::DMSCResult<bool> {
653 let mut providers = self.providers.write().await;
654
655 if let Some(provider) = providers.get_mut(provider_id) {
656 provider.enabled = false;
657 Ok(true)
658 } else {
659 Ok(false)
660 }
661 }
662
663 /// Enables an OAuth provider.
664 ///
665 /// # Parameters
666 /// - `provider_id`: Unique identifier of the provider
667 ///
668 /// # Returns
669 /// `true` if the provider was successfully enabled, otherwise `false`
670 pub async fn enable_provider(&self, provider_id: &str) -> crate::core::DMSCResult<bool> {
671 let mut providers = self.providers.write().await;
672
673 if let Some(provider) = providers.get_mut(provider_id) {
674 provider.enabled = true;
675 Ok(true)
676 } else {
677 Ok(false)
678 }
679 }
680}
681
682#[cfg(feature = "pyo3")]
683/// Python bindings for the OAuth Manager.
684///
685/// This module provides Python interface to DMSC OAuth functionality,
686/// enabling Python applications to integrate with OAuth identity providers.
687///
688/// ## Supported Operations
689///
690/// - Provider registration and management
691/// - Authentication URL generation for OAuth flows
692/// - Token exchange with authorization codes
693/// - User information retrieval from OAuth providers
694/// - Token refresh and revocation
695///
696/// ## Python Usage Example
697///
698/// ```python
699/// from dmsc import DMSCOAuthProvider, DMSCOAuthManager
700///
701/// # Create OAuth manager
702/// oauth_manager = DMSCOAuthManager()
703///
704/// # Register a provider
705/// provider = DMSCOAuthProvider(
706/// id="google",
707/// name="Google",
708/// client_id="your_client_id",
709/// client_secret="your_client_secret",
710/// auth_url="https://accounts.google.com/o/oauth2/auth",
711/// token_url="https://oauth2.googleapis.com/token",
712/// user_info_url="https://www.googleapis.com/oauth2/v3/userinfo",
713/// scopes=["openid", "email", "profile"],
714/// enabled=True,
715/// )
716/// # Note: Async operations require Python 3.7+ with asyncio
717/// ```
718#[pyo3::prelude::pymethods]
719impl DMSCOAuthManager {
720 #[new]
721 fn py_new() -> PyResult<Self> {
722 let cache = Arc::new(crate::cache::DMSCMemoryCache::new());
723 Ok(Self::new(cache))
724 }
725
726 #[pyo3(name = "register_provider")]
727 fn register_provider_impl(&self, provider: DMSCOAuthProvider) -> PyResult<bool> {
728 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
729 rt.block_on(async {
730 self.register_provider(provider).await?;
731 Ok(true)
732 })
733 }
734
735 #[pyo3(name = "get_provider")]
736 fn get_provider_impl(&self, provider_id: String) -> PyResult<Option<DMSCOAuthProvider>> {
737 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
738 rt.block_on(async {
739 self.get_provider(&provider_id).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
740 })
741 }
742
743 #[pyo3(name = "get_auth_url")]
744 fn get_auth_url_impl(&self, provider_id: String, state: String) -> PyResult<Option<String>> {
745 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
746 rt.block_on(async {
747 self.get_auth_url(&provider_id, &state).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
748 })
749 }
750
751 #[pyo3(name = "exchange_code_for_token")]
752 fn exchange_code_for_token_impl(&self, provider_id: String, code: String, redirect_uri: String) -> PyResult<Option<DMSCOAuthToken>> {
753 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
754 rt.block_on(async {
755 self.exchange_code_for_token(&provider_id, &code, &redirect_uri).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
756 })
757 }
758
759 #[pyo3(name = "get_user_info")]
760 fn get_user_info_impl(&self, provider_id: String, access_token: String) -> PyResult<Option<DMSCOAuthUserInfo>> {
761 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
762 rt.block_on(async {
763 self.get_user_info(&provider_id, &access_token).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
764 })
765 }
766
767 #[pyo3(name = "refresh_token")]
768 fn refresh_token_impl(&self, provider_id: String, refresh_token: String) -> PyResult<Option<DMSCOAuthToken>> {
769 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
770 rt.block_on(async {
771 self.refresh_token(&provider_id, &refresh_token).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
772 })
773 }
774
775 #[pyo3(name = "revoke_token")]
776 fn revoke_token_impl(&self, provider_id: String, access_token: String) -> PyResult<bool> {
777 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
778 rt.block_on(async {
779 self.revoke_token(&provider_id, &access_token).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
780 })
781 }
782
783 #[pyo3(name = "list_providers")]
784 fn list_providers_impl(&self) -> PyResult<Vec<DMSCOAuthProvider>> {
785 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
786 rt.block_on(async {
787 self.list_providers().await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
788 })
789 }
790
791 #[pyo3(name = "disable_provider")]
792 fn disable_provider_impl(&self, provider_id: String) -> PyResult<bool> {
793 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
794 rt.block_on(async {
795 self.disable_provider(&provider_id).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
796 })
797 }
798
799 #[pyo3(name = "enable_provider")]
800 fn enable_provider_impl(&self, provider_id: String) -> PyResult<bool> {
801 let rt = Runtime::new().map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))?;
802 rt.block_on(async {
803 self.enable_provider(&provider_id).await.map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e.to_string()))
804 })
805 }
806}