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