Skip to main content

authkestra_core/
lib.rs

1//! # Authkestra Core
2//!
3//! `authkestra-core` provides the foundational traits and types for the Authkestra authentication framework.
4//! It defines the core abstractions for identities, authentication flows and providers that are used across the entire ecosystem.
5
6#![warn(missing_docs)]
7
8use async_trait::async_trait;
9use serde::{Deserialize, Serialize};
10
11/// PKCE (Proof Key for Code Exchange) utilities.
12pub mod pkce;
13
14/// Strategy-based authentication.
15pub mod strategy;
16
17/// Errors that can occur during the authentication process.
18pub mod error;
19use crate::error::AuthError;
20
21/// A unified identity structure returned by all providers.
22pub mod state;
23use crate::state::{Identity, OAuthToken};
24
25/// Discovery utilities for OAuth2 providers.
26pub mod discovery;
27
28/// Controls whether a cookie is sent with cross-site requests.
29#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
30pub enum SameSite {
31    /// The cookie is sent with "safe" cross-site requests (e.g., following a link).
32    Lax,
33    /// The cookie is only sent for same-site requests.
34    Strict,
35    /// The cookie is sent with all requests, including cross-site. Requires `Secure`.
36    None,
37}
38
39/// Trait for an OAuth2-compatible provider.
40#[async_trait]
41pub trait OAuthProvider: Send + Sync {
42    /// Get the provider identifier.
43    fn provider_id(&self) -> &str;
44
45    /// Helper to get the authorization URL.
46    fn get_authorization_url(
47        &self,
48        state: &str,
49        scopes: &[&str],
50        code_challenge: Option<&str>,
51    ) -> String;
52
53    /// Exchange an authorization code for an Identity.
54    async fn exchange_code_for_identity(
55        &self,
56        code: &str,
57        code_verifier: Option<&str>,
58    ) -> Result<(Identity, OAuthToken), AuthError>;
59
60    /// Refresh an access token using a refresh token.
61    async fn refresh_token(&self, _refresh_token: &str) -> Result<OAuthToken, AuthError> {
62        Err(AuthError::Provider(
63            "Token refresh not supported by this provider".into(),
64        ))
65    }
66
67    /// Revoke an access token.
68    async fn revoke_token(&self, _token: &str) -> Result<(), AuthError> {
69        Err(AuthError::Provider(
70            "Token revocation not supported by this provider".into(),
71        ))
72    }
73}
74
75/// Trait for a Credentials-based provider (e.g., Email/Password).
76#[async_trait]
77pub trait CredentialsProvider: Send + Sync {
78    /// The type of credentials accepted by this provider.
79    type Credentials;
80
81    /// Validate credentials and return an Identity.
82    async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError>;
83}
84
85/// Trait for mapping a provider identity to a local user.
86#[async_trait]
87pub trait UserMapper: Send + Sync {
88    /// The type of the local user object.
89    type LocalUser: Send + Sync;
90
91    /// Map an identity to a local user.
92    /// This could involve creating a new user or finding an existing one.
93    async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError>;
94}
95
96/// Orchestrates the Authorization Code flow.
97#[async_trait]
98pub trait ErasedOAuthFlow: Send + Sync {
99    /// Get the provider identifier.
100    fn provider_id(&self) -> String;
101    /// Generates the redirect URL and CSRF state.
102    fn initiate_login(&self, scopes: &[&str], pkce_challenge: Option<&str>) -> (String, String);
103    /// Completes the flow by exchanging the code.
104    async fn finalize_login(
105        &self,
106        code: &str,
107        received_state: &str,
108        expected_state: &str,
109        pkce_verifier: Option<&str>,
110    ) -> Result<(Identity, OAuthToken), AuthError>;
111}
112
113#[async_trait]
114impl UserMapper for () {
115    type LocalUser = ();
116    async fn map_user(&self, _identity: &Identity) -> Result<Self::LocalUser, AuthError> {
117        Ok(())
118    }
119}
120
121#[async_trait]
122impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for std::sync::Arc<T> {
123    fn provider_id(&self) -> String {
124        (**self).provider_id()
125    }
126
127    fn initiate_login(&self, scopes: &[&str], pkce_challenge: Option<&str>) -> (String, String) {
128        (**self).initiate_login(scopes, pkce_challenge)
129    }
130
131    async fn finalize_login(
132        &self,
133        code: &str,
134        received_state: &str,
135        expected_state: &str,
136        pkce_verifier: Option<&str>,
137    ) -> Result<(Identity, OAuthToken), AuthError> {
138        (**self)
139            .finalize_login(code, received_state, expected_state, pkce_verifier)
140            .await
141    }
142}
143
144#[async_trait]
145impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for Box<T> {
146    fn provider_id(&self) -> String {
147        (**self).provider_id()
148    }
149
150    fn initiate_login(&self, scopes: &[&str], pkce_challenge: Option<&str>) -> (String, String) {
151        (**self).initiate_login(scopes, pkce_challenge)
152    }
153
154    async fn finalize_login(
155        &self,
156        code: &str,
157        received_state: &str,
158        expected_state: &str,
159        pkce_verifier: Option<&str>,
160    ) -> Result<(Identity, OAuthToken), AuthError> {
161        (**self)
162            .finalize_login(code, received_state, expected_state, pkce_verifier)
163            .await
164    }
165}