Skip to main content

authly_flow/
lib.rs

1use authly_core::{
2    AuthError, CredentialsProvider, Identity, OAuthProvider, OAuthToken, UserMapper,
3};
4
5pub mod client_credentials_flow;
6pub mod device_flow;
7
8pub use client_credentials_flow::ClientCredentialsFlow;
9pub use device_flow::{DeviceAuthorizationResponse, DeviceFlow};
10
11/// Orchestrates the Authorization Code flow.
12pub struct OAuth2Flow<P: OAuthProvider, M: UserMapper = ()> {
13    provider: P,
14    mapper: Option<M>,
15}
16
17impl<P: OAuthProvider> OAuth2Flow<P, ()> {
18    pub fn new(provider: P) -> Self {
19        Self {
20            provider,
21            mapper: None,
22        }
23    }
24}
25
26impl<P: OAuthProvider, M: UserMapper> OAuth2Flow<P, M> {
27    pub fn with_mapper(provider: P, mapper: M) -> Self {
28        Self {
29            provider,
30            mapper: Some(mapper),
31        }
32    }
33
34    /// Generates the redirect URL and CSRF state.
35    pub fn initiate_login(
36        &self,
37        scopes: &[&str],
38        pkce_challenge: Option<&str>,
39    ) -> (String, String) {
40        let state = uuid::Uuid::new_v4().to_string();
41        let url = self
42            .provider
43            .get_authorization_url(&state, scopes, pkce_challenge);
44        (url, state)
45    }
46
47    /// Completes the flow by exchanging the code.
48    /// If a mapper is provided, it will also map the identity to a local user.
49    pub async fn finalize_login(
50        &self,
51        code: &str,
52        received_state: &str,
53        expected_state: &str,
54        pkce_verifier: Option<&str>,
55    ) -> Result<(Identity, OAuthToken, Option<M::LocalUser>), AuthError> {
56        if received_state != expected_state {
57            return Err(AuthError::CsrfMismatch);
58        }
59        let (identity, token) = self
60            .provider
61            .exchange_code_for_identity(code, pkce_verifier)
62            .await?;
63
64        let local_user = if let Some(mapper) = &self.mapper {
65            Some(mapper.map_user(&identity).await?)
66        } else {
67            None
68        };
69
70        Ok((identity, token, local_user))
71    }
72
73    /// Refresh an access token using a refresh token.
74    pub async fn refresh_access_token(&self, refresh_token: &str) -> Result<OAuthToken, AuthError> {
75        self.provider.refresh_token(refresh_token).await
76    }
77
78    /// Revoke an access token.
79    pub async fn revoke_token(&self, token: &str) -> Result<(), AuthError> {
80        self.provider.revoke_token(token).await
81    }
82}
83
84/// Orchestrates a direct credentials flow.
85pub struct CredentialsFlow<P: CredentialsProvider, M: UserMapper = ()> {
86    provider: P,
87    mapper: Option<M>,
88}
89
90impl<P: CredentialsProvider> CredentialsFlow<P, ()> {
91    pub fn new(provider: P) -> Self {
92        Self {
93            provider,
94            mapper: None,
95        }
96    }
97}
98
99impl<P: CredentialsProvider, M: UserMapper> CredentialsFlow<P, M> {
100    pub fn with_mapper(provider: P, mapper: M) -> Self {
101        Self {
102            provider,
103            mapper: Some(mapper),
104        }
105    }
106
107    pub async fn authenticate(
108        &self,
109        creds: P::Credentials,
110    ) -> Result<(Identity, Option<M::LocalUser>), AuthError> {
111        let identity = self.provider.authenticate(creds).await?;
112
113        let local_user = if let Some(mapper) = &self.mapper {
114            Some(mapper.map_user(&identity).await?)
115        } else {
116            None
117        };
118
119        Ok((identity, local_user))
120    }
121}