Skip to main content

better_auth_core/
plugin.rs

1use async_trait::async_trait;
2use std::collections::HashMap;
3use std::sync::Arc;
4
5use crate::adapters::DatabaseAdapter;
6use crate::config::AuthConfig;
7use crate::email::EmailProvider;
8use crate::error::{AuthError, AuthResult};
9use crate::types::{AuthRequest, AuthResponse, HttpMethod};
10
11/// Plugin trait that all authentication plugins must implement.
12///
13/// Generic over `DB` so that lifecycle hooks receive the adapter's concrete
14/// entity types (e.g., `DB::User`, `DB::Session`).
15#[async_trait]
16pub trait AuthPlugin<DB: DatabaseAdapter>: Send + Sync {
17    /// Plugin name - should be unique
18    fn name(&self) -> &'static str;
19
20    /// Routes that this plugin handles
21    fn routes(&self) -> Vec<AuthRoute>;
22
23    /// Called when the plugin is initialized
24    async fn on_init(&self, ctx: &mut AuthContext<DB>) -> AuthResult<()> {
25        let _ = ctx;
26        Ok(())
27    }
28
29    /// Called for each request - return Some(response) to handle, None to pass through
30    async fn on_request(
31        &self,
32        req: &AuthRequest,
33        ctx: &AuthContext<DB>,
34    ) -> AuthResult<Option<AuthResponse>>;
35
36    /// Called after a user is created
37    async fn on_user_created(&self, user: &DB::User, ctx: &AuthContext<DB>) -> AuthResult<()> {
38        let _ = (user, ctx);
39        Ok(())
40    }
41
42    /// Called after a session is created
43    async fn on_session_created(
44        &self,
45        session: &DB::Session,
46        ctx: &AuthContext<DB>,
47    ) -> AuthResult<()> {
48        let _ = (session, ctx);
49        Ok(())
50    }
51
52    /// Called before a user is deleted
53    async fn on_user_deleted(&self, user_id: &str, ctx: &AuthContext<DB>) -> AuthResult<()> {
54        let _ = (user_id, ctx);
55        Ok(())
56    }
57
58    /// Called before a session is deleted
59    async fn on_session_deleted(
60        &self,
61        session_token: &str,
62        ctx: &AuthContext<DB>,
63    ) -> AuthResult<()> {
64        let _ = (session_token, ctx);
65        Ok(())
66    }
67}
68
69/// Route definition for plugins
70#[derive(Debug, Clone)]
71pub struct AuthRoute {
72    pub path: String,
73    pub method: HttpMethod,
74    /// Identifier used as the OpenAPI `operationId` for this route.
75    pub operation_id: String,
76}
77
78/// Context passed to plugin methods
79pub struct AuthContext<DB: DatabaseAdapter> {
80    pub config: Arc<AuthConfig>,
81    pub database: Arc<DB>,
82    pub email_provider: Option<Arc<dyn EmailProvider>>,
83    pub metadata: HashMap<String, serde_json::Value>,
84}
85
86impl AuthRoute {
87    pub fn new(
88        method: HttpMethod,
89        path: impl Into<String>,
90        operation_id: impl Into<String>,
91    ) -> Self {
92        Self {
93            path: path.into(),
94            method,
95            operation_id: operation_id.into(),
96        }
97    }
98
99    pub fn get(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
100        Self::new(HttpMethod::Get, path, operation_id)
101    }
102
103    pub fn post(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
104        Self::new(HttpMethod::Post, path, operation_id)
105    }
106
107    pub fn put(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
108        Self::new(HttpMethod::Put, path, operation_id)
109    }
110
111    pub fn delete(path: impl Into<String>, operation_id: impl Into<String>) -> Self {
112        Self::new(HttpMethod::Delete, path, operation_id)
113    }
114}
115
116impl<DB: DatabaseAdapter> AuthContext<DB> {
117    pub fn new(config: Arc<AuthConfig>, database: Arc<DB>) -> Self {
118        let email_provider = config.email_provider.clone();
119        Self {
120            config,
121            database,
122            email_provider,
123            metadata: HashMap::new(),
124        }
125    }
126
127    pub fn set_metadata(&mut self, key: impl Into<String>, value: serde_json::Value) {
128        self.metadata.insert(key.into(), value);
129    }
130
131    pub fn get_metadata(&self, key: &str) -> Option<&serde_json::Value> {
132        self.metadata.get(key)
133    }
134
135    /// Get the email provider, returning an error if none is configured.
136    pub fn email_provider(&self) -> AuthResult<&dyn EmailProvider> {
137        self.email_provider
138            .as_deref()
139            .ok_or_else(|| AuthError::config("No email provider configured"))
140    }
141}