Skip to main content

authkestra_engine/auth/
mod.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;
19pub use error::AuthError;
20
21/// A unified identity structure returned by all providers.
22pub mod state;
23pub use state::{Identity, OAuth2State, OAuthToken};
24
25/// Discovery utilities for OAuth2 providers.
26pub mod discovery;
27
28/// Session management traits and types.
29pub mod session;
30pub use session::{Session, SessionConfig, SessionStore};
31
32/// Represents the input for an authentication method.
33#[derive(Debug, Clone, Serialize, Deserialize)]
34#[serde(tag = "type", content = "data")]
35pub enum AuthInput {
36    /// Password-based authentication.
37    Password {
38        /// The username or email.
39        identifier: String,
40        /// The secret password.
41        password: String,
42    },
43    /// OAuth2/OIDC authorization code.
44    OAuthCode {
45        /// The authorization code.
46        code: String,
47        /// Optional PKCE verifier.
48        code_verifier: Option<String>,
49    },
50    /// Token-based authentication (e.g., Bearer token).
51    Token(String),
52    /// Custom input for extensible methods.
53    Custom(serde_json::Value),
54}
55
56/// A mechanism used to authenticate a user.
57#[async_trait]
58pub trait AuthMethod: Send + Sync {
59    /// Returns the name of the authentication method.
60    fn name(&self) -> &str;
61
62    /// Authenticate a user with the given input.
63    async fn authenticate(&self, input: AuthInput) -> Result<Identity, AuthError>;
64}
65
66/// Configuration for an identity provider.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct ProviderConfig {
69    /// The unique identifier for the provider.
70    pub id: String,
71    /// The display name of the provider.
72    pub name: String,
73    /// Additional configuration parameters.
74    pub extra: std::collections::HashMap<String, String>,
75}
76
77/// An external identity source (e.g., Google, GitHub).
78/// Providers should contain zero business logic—only configuration and mapping.
79#[async_trait]
80pub trait Provider: Send + Sync {
81    /// Returns the provider configuration.
82    async fn config(&self) -> ProviderConfig;
83}
84
85/// Controls whether a cookie is sent with cross-site requests.
86#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
87pub enum SameSite {
88    /// The cookie is sent with "safe" cross-site requests (e.g., following a link).
89    Lax,
90    /// The cookie is only sent for same-site requests.
91    Strict,
92    /// The cookie is sent with all requests, including cross-site. Requires `Secure`.
93    None,
94}
95
96/// Trait for an OAuth2-compatible provider.
97#[async_trait]
98pub trait OAuthProvider: Provider {
99    /// Get the provider identifier.
100    fn provider_id(&self) -> &str;
101
102    /// Helper to get the authorization URL.
103    fn get_authorization_url(
104        &self,
105        state: &str,
106        scopes: &[&str],
107        code_challenge: Option<&str>,
108        nonce: Option<&str>,
109    ) -> String;
110
111    /// Exchange an authorization code for an Identity.
112    async fn exchange_code_for_identity(
113        &self,
114        code: &str,
115        code_verifier: Option<&str>,
116        nonce: Option<&str>,
117    ) -> Result<(Identity, OAuthToken), AuthError>;
118
119    /// Refresh an access token using a refresh token.
120    async fn refresh_token(&self, _refresh_token: &str) -> Result<OAuthToken, AuthError> {
121        Err(AuthError::Provider(
122            "Token refresh not supported by this provider".into(),
123        ))
124    }
125
126    /// Revoke an access token.
127    async fn revoke_token(&self, _token: &str) -> Result<(), AuthError> {
128        Err(AuthError::Provider(
129            "Token revocation not supported by this provider".into(),
130        ))
131    }
132}
133
134/// Trait for a Credentials-based provider (e.g., Email/Password).
135#[async_trait]
136pub trait CredentialsProvider: Send + Sync {
137    /// The type of credentials accepted by this provider.
138    type Credentials;
139
140    /// Validate credentials and return an Identity.
141    async fn authenticate(&self, creds: Self::Credentials) -> Result<Identity, AuthError>;
142}
143
144/// Trait for mapping a provider identity to a local user.
145#[async_trait]
146pub trait UserMapper: Send + Sync {
147    /// The type of the local user object.
148    type LocalUser: Send + Sync;
149
150    /// Map an identity to a local user.
151    /// This could involve creating a new user or finding an existing one.
152    async fn map_user(&self, identity: &Identity) -> Result<Self::LocalUser, AuthError>;
153}
154
155/// Orchestrates the Authorization Code flow.
156#[async_trait]
157pub trait ErasedOAuthFlow: Send + Sync {
158    /// Get the provider identifier.
159    fn provider_id(&self) -> String;
160    /// Generates the redirect URL and CSRF state.
161    fn initiate_login(
162        &self,
163        scopes: &[&str],
164        pkce_challenge: Option<&str>,
165    ) -> (String, OAuth2State);
166    /// Completes the flow by exchanging the code.
167    async fn finalize_login(
168        &self,
169        code: &str,
170        received_state: &str,
171        expected_state: &OAuth2State,
172    ) -> Result<(Identity, OAuthToken), AuthError>;
173}
174
175#[async_trait]
176impl UserMapper for () {
177    type LocalUser = ();
178    async fn map_user(&self, _identity: &Identity) -> Result<Self::LocalUser, AuthError> {
179        Ok(())
180    }
181}
182
183#[async_trait]
184impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for std::sync::Arc<T> {
185    fn provider_id(&self) -> String {
186        (**self).provider_id()
187    }
188
189    fn initiate_login(
190        &self,
191        scopes: &[&str],
192        pkce_challenge: Option<&str>,
193    ) -> (String, OAuth2State) {
194        (**self).initiate_login(scopes, pkce_challenge)
195    }
196
197    async fn finalize_login(
198        &self,
199        code: &str,
200        received_state: &str,
201        expected_state: &OAuth2State,
202    ) -> Result<(Identity, OAuthToken), AuthError> {
203        (**self)
204            .finalize_login(code, received_state, expected_state)
205            .await
206    }
207}
208
209#[async_trait]
210impl<T: ErasedOAuthFlow + ?Sized> ErasedOAuthFlow for Box<T> {
211    fn provider_id(&self) -> String {
212        (**self).provider_id()
213    }
214
215    fn initiate_login(
216        &self,
217        scopes: &[&str],
218        pkce_challenge: Option<&str>,
219    ) -> (String, OAuth2State) {
220        (**self).initiate_login(scopes, pkce_challenge)
221    }
222
223    async fn finalize_login(
224        &self,
225        code: &str,
226        received_state: &str,
227        expected_state: &OAuth2State,
228    ) -> Result<(Identity, OAuthToken), AuthError> {
229        (**self)
230            .finalize_login(code, received_state, expected_state)
231            .await
232    }
233}