Skip to main content

better_auth_core/
entity.rs

1//! Entity traits for the Better Auth framework.
2//!
3//! These traits define the interface that entity types must implement.
4//! The framework uses trait methods and native field extraction to read entity fields.
5//! Custom models may use their own field names and additional fields.
6//!
7//! Implement these traits manually for any custom types used inside the auth
8//! runtime.
9
10use std::borrow::Cow;
11
12use chrono::Utc;
13use serde::Serialize;
14
15use crate::{SchemaValue, types::InvitationStatus};
16
17/// Extract native adapter fields without converting object values to JSON.
18pub trait AuthRecordFields {
19    fn field_values(&self) -> crate::AuthResult<crate::FieldMap>;
20
21    /// Deep-copy runtime objects through one graph context while retaining record state.
22    fn structured_clone(
23        &self,
24        context: &mut crate::StructuredCloneContext,
25    ) -> crate::AuthResult<Self>
26    where
27        Self: FromFieldMap,
28    {
29        Self::from_field_values(context.clone_map(&self.field_values()?))
30    }
31}
32
33/// Reconstruct a runtime record from native adapter fields.
34pub trait FromFieldMap: Sized {
35    fn from_field_values(fields: crate::FieldMap) -> crate::AuthResult<Self>;
36}
37
38impl AuthRecordFields for crate::FieldMap {
39    fn field_values(&self) -> crate::AuthResult<Self> {
40        Ok(self.clone())
41    }
42
43    fn structured_clone(
44        &self,
45        context: &mut crate::StructuredCloneContext,
46    ) -> crate::AuthResult<Self> {
47        Ok(context.clone_map(self))
48    }
49}
50
51impl FromFieldMap for crate::FieldMap {
52    fn from_field_values(fields: Self) -> crate::AuthResult<Self> {
53        Ok(fields)
54    }
55}
56
57/// Trait representing a user entity.
58///
59/// The framework reads `name`, `image`, and nullable `twoFactorEnabled` from native field extraction.
60/// Other core fields use getters. The `two_factor_enabled` getter supplies truthiness to runtime guards.
61/// Custom types must provide all framework fields and may have additional fields.
62/// If serialized keys differ, override [`Self::serialized_field_name`].
63/// `AuthEntity` generates the serialized field mapping for derived models.
64pub trait AuthUser:
65    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
66{
67    /// Field presence for runtime records and signed snapshots. Database models use `None`.
68    /// A missing optional core field differs from a present field containing JSON null.
69    fn field_presence(&self) -> Option<&std::collections::BTreeSet<String>> {
70        None
71    }
72    /// Already projected application fields, for views reconstructed from session caches.
73    /// Storage models return `None` so the output transform runs exactly once per database read.
74    fn projected_fields(&self) -> Option<&crate::FieldMap> {
75        None
76    }
77    /// Plugin fields that the entity and store can read and persist.
78    ///
79    /// `AuthEntity` derives this list. Manual implementations must declare each
80    /// supported field by its Rust name. An empty list supports core fields only.
81    const PLUGIN_FIELDS: &'static [&'static str] = &[];
82
83    /// Reject a plugin whose required fields are absent from this entity.
84    fn require_plugin_fields(plugin: &str, required: &[&str]) -> crate::AuthResult<()> {
85        require_plugin_fields(plugin, "user", Self::PLUGIN_FIELDS, required)
86    }
87
88    /// Resolve canonical field names to serialized application model fields.
89    fn serialized_field_name(name: &str) -> &str {
90        name
91    }
92
93    fn id(&self) -> SchemaValue<Cow<'_, str>>;
94    fn email(&self) -> Option<&str>;
95    fn email_verified(&self) -> bool;
96    fn created_at(&self) -> crate::FieldDate;
97    fn updated_at(&self) -> crate::FieldDate;
98    fn is_anonymous(&self) -> Option<bool> {
99        None
100    }
101    fn phone_number(&self) -> Option<&str> {
102        None
103    }
104    fn phone_number_verified(&self) -> Option<bool> {
105        None
106    }
107    fn username(&self) -> Option<&str>;
108    fn display_username(&self) -> Option<&str>;
109    fn two_factor_enabled(&self) -> bool;
110    fn role(&self) -> Option<&str>;
111    fn banned(&self) -> bool;
112    fn ban_reason(&self) -> Option<&str>;
113    fn ban_expires(&self) -> Option<crate::FieldDate>;
114}
115
116/// Trait representing a session entity.
117pub trait AuthSession:
118    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
119{
120    /// Optional field presence for runtime records; database models expose every mapped column.
121    fn field_presence(&self) -> Option<&std::collections::BTreeSet<String>> {
122        None
123    }
124    /// Resolve an application field alias to the model's serialized key.
125    fn serialized_field_name(name: &str) -> &str {
126        name
127    }
128    /// Already projected fields from a cached session view.
129    fn projected_fields(&self) -> Option<&crate::FieldMap> {
130        None
131    }
132
133    /// Plugin fields that the entity and store can read and persist.
134    ///
135    /// `AuthEntity` derives this list. Manual implementations must declare each
136    /// supported field by its Rust name. An empty list supports core fields only.
137    const PLUGIN_FIELDS: &'static [&'static str] = &[];
138
139    /// Reject a plugin whose required fields are absent from this entity.
140    fn require_plugin_fields(plugin: &str, required: &[&str]) -> crate::AuthResult<()> {
141        require_plugin_fields(plugin, "session", Self::PLUGIN_FIELDS, required)
142    }
143
144    fn id(&self) -> SchemaValue<Cow<'_, str>>;
145    fn expires_at(&self) -> crate::FieldDate;
146    fn token(&self) -> &str;
147    fn created_at(&self) -> crate::FieldDate;
148    fn updated_at(&self) -> crate::FieldDate;
149    fn ip_address(&self) -> Option<&str>;
150    fn user_agent(&self) -> Option<&str>;
151    fn user_id(&self) -> SchemaValue<Cow<'_, str>>;
152    fn impersonated_by(&self) -> Option<&str>;
153    fn active_organization_id(&self) -> Option<&str>;
154    /// Active team selected for this session.
155    fn active_team_id(&self) -> Option<&str> {
156        None
157    }
158    fn active(&self) -> bool;
159}
160
161fn require_plugin_fields(
162    plugin: &str,
163    entity: &str,
164    available: &[&str],
165    required: &[&str],
166) -> crate::AuthResult<()> {
167    for field in required {
168        if !available.contains(field) {
169            return Err(crate::AuthError::config(format!(
170                "Plugin `{plugin}` requires {entity} field `{field}`; add the field and migrate the database"
171            )));
172        }
173    }
174    Ok(())
175}
176
177/// Trait representing an account entity (OAuth provider linking).
178pub trait AuthAccount:
179    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
180{
181    /// Optional field presence for runtime records; database models expose every mapped column.
182    fn field_presence(&self) -> Option<&std::collections::BTreeSet<String>> {
183        None
184    }
185    fn id(&self) -> Cow<'_, str>;
186    fn account_id(&self) -> &str;
187    fn provider_id(&self) -> &str;
188    fn user_id(&self) -> Cow<'_, str>;
189    fn access_token(&self) -> Option<&str>;
190    fn refresh_token(&self) -> Option<&str>;
191    fn id_token(&self) -> Option<&str>;
192    fn access_token_expires_at(&self) -> Option<crate::FieldDate>;
193    fn refresh_token_expires_at(&self) -> Option<crate::FieldDate>;
194    fn scope(&self) -> Option<&str>;
195    fn password(&self) -> Option<&str>;
196    fn created_at(&self) -> crate::FieldDate;
197    fn updated_at(&self) -> crate::FieldDate;
198}
199
200/// Trait representing an organization entity.
201pub trait AuthOrganization:
202    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
203{
204    /// Application fields already projected by the adapter.
205    fn projected_fields(&self) -> Option<&crate::FieldMap> {
206        None
207    }
208
209    fn id(&self) -> SchemaValue<Cow<'_, str>>;
210    fn name(&self) -> &SchemaValue<String>;
211    fn slug(&self) -> &SchemaValue<String>;
212    fn logo(&self) -> &SchemaValue<Option<String>>;
213    fn metadata(&self) -> &SchemaValue<Option<crate::FieldValue>>;
214    fn created_at(&self) -> &SchemaValue<crate::FieldDate>;
215}
216
217/// Trait representing an organization member entity.
218pub trait AuthMember:
219    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
220{
221    /// Application fields already projected by the adapter.
222    fn projected_fields(&self) -> Option<&crate::FieldMap> {
223        None
224    }
225
226    fn id(&self) -> SchemaValue<Cow<'_, str>>;
227    fn organization_id(&self) -> &SchemaValue<String>;
228    fn user_id(&self) -> &SchemaValue<String>;
229    fn role(&self) -> &SchemaValue<String>;
230    fn created_at(&self) -> &SchemaValue<crate::FieldDate>;
231}
232
233/// Trait representing an invitation entity.
234pub trait AuthInvitation:
235    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
236{
237    /// Application fields already projected by the adapter.
238    fn projected_fields(&self) -> Option<&crate::FieldMap> {
239        None
240    }
241
242    fn id(&self) -> SchemaValue<Cow<'_, str>>;
243    fn organization_id(&self) -> &SchemaValue<String>;
244    fn email(&self) -> &SchemaValue<String>;
245    fn role(&self) -> &SchemaValue<String>;
246    fn status(&self) -> &SchemaValue<InvitationStatus>;
247    fn inviter_id(&self) -> &SchemaValue<String>;
248    fn expires_at(&self) -> &SchemaValue<crate::FieldDate>;
249    fn created_at(&self) -> &SchemaValue<crate::FieldDate>;
250
251    /// Check if the invitation is still pending.
252    fn is_pending(&self) -> bool {
253        self.status() == &SchemaValue::Typed(InvitationStatus::Pending)
254    }
255
256    /// Check if the invitation has expired.
257    fn is_expired(&self) -> crate::AuthResult<bool> {
258        self.expires_at().is_before(Utc::now())
259    }
260    /// Comma-separated invited team identifiers.
261    fn team_id(&self) -> &SchemaValue<Option<String>>;
262}
263
264/// Trait representing a verification token entity.
265pub trait AuthVerification:
266    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
267{
268    fn id(&self) -> Cow<'_, str>;
269    fn identifier(&self) -> &str;
270    fn value(&self) -> &str;
271    fn expires_at(&self) -> crate::FieldDate;
272    fn created_at(&self) -> crate::FieldDate;
273    fn updated_at(&self) -> crate::FieldDate;
274}
275
276/// Trait representing a two-factor authentication entity.
277pub trait AuthTwoFactor:
278    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
279{
280    /// Return application fields when the entity already contains an adapter projection.
281    fn additional_fields(&self) -> Option<&crate::FieldMap> {
282        None
283    }
284    fn id(&self) -> SchemaValue<Cow<'_, str>>;
285    fn secret(&self) -> &str;
286    fn backup_codes(&self) -> &str;
287    fn user_id(&self) -> SchemaValue<Cow<'_, str>>;
288    fn verified(&self) -> Option<bool>;
289    fn failed_verification_count(&self) -> Option<i64>;
290    fn locked_until(&self) -> Option<crate::FieldDate>;
291    fn created_at(&self) -> &SchemaValue<crate::FieldDate>;
292    fn updated_at(&self) -> &SchemaValue<crate::FieldDate>;
293}
294
295/// Trait representing an API key entity.
296pub trait AuthApiKey:
297    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
298{
299    /// Return application fields when the entity already contains an adapter projection.
300    fn additional_fields(&self) -> Option<&crate::FieldMap> {
301        None
302    }
303    fn id(&self) -> SchemaValue<Cow<'_, str>>;
304    fn name(&self) -> &SchemaValue<Option<String>>;
305    fn start(&self) -> Option<Cow<'_, crate::ApiKeyStart>>;
306    fn prefix(&self) -> Option<&str>;
307    fn key_hash(&self) -> &str;
308    /// Owner of the key — a user id, or an organization id when the key's
309    /// configuration references organizations.
310    fn reference_id(&self) -> Cow<'_, str>;
311    /// Name of the API-key configuration this key belongs to (`"default"`
312    /// unless the application registers named configurations).
313    fn config_id(&self) -> Cow<'_, str>;
314    fn refill_interval(&self) -> Option<f64>;
315    fn refill_amount(&self) -> Option<f64>;
316    fn last_refill_at(&self) -> Option<crate::FieldDate>;
317    fn enabled(&self) -> &SchemaValue<bool>;
318    fn rate_limit_enabled(&self) -> &SchemaValue<bool>;
319    fn rate_limit_time_window(&self) -> Option<f64>;
320    fn rate_limit_max(&self) -> Option<f64>;
321    fn request_count(&self) -> Option<f64>;
322    fn remaining(&self) -> Option<f64>;
323    fn last_request(&self) -> Option<crate::FieldDate>;
324    fn expires_at(&self) -> Option<crate::FieldDate>;
325    fn created_at(&self) -> crate::FieldDate;
326    fn updated_at(&self) -> crate::FieldDate;
327    fn permissions(&self) -> Option<&str>;
328    fn metadata(&self) -> Option<&str>;
329}
330
331/// Trait representing a passkey entity.
332pub trait AuthPasskey:
333    AuthRecordFields + Clone + Send + Sync + Serialize + std::fmt::Debug + 'static
334{
335    /// Return application fields when the entity already contains an adapter projection.
336    fn additional_fields(&self) -> Option<&crate::FieldMap> {
337        None
338    }
339    fn id(&self) -> SchemaValue<Cow<'_, str>>;
340    fn name(&self) -> &SchemaValue<Option<String>>;
341    fn public_key(&self) -> &str;
342    fn user_id(&self) -> SchemaValue<Cow<'_, str>>;
343    fn credential_id(&self) -> &str;
344    fn counter(&self) -> u64;
345    fn device_type(&self) -> &str;
346    fn backed_up(&self) -> bool;
347    fn transports(&self) -> Option<&str>;
348    fn created_at(&self) -> &SchemaValue<Option<crate::FieldDate>>;
349    fn updated_at(&self) -> &SchemaValue<crate::FieldDate>;
350    fn aaguid(&self) -> &SchemaValue<Option<String>>;
351    fn credential(&self) -> &SchemaValue<String>;
352}
353
354/// Minimal user info for member-related API responses.
355///
356/// This is a concrete framework type (not generic) used to project
357/// user fields into member responses.
358#[derive(Debug, Clone, Default)]
359pub struct MemberUserView {
360    pub id: SchemaValue<String>,
361    pub email: SchemaValue<Option<String>>,
362    pub name: SchemaValue<Option<String>>,
363    pub image: SchemaValue<Option<String>>,
364}
365
366mod member_user_view;
367mod record_fields;
368
369impl MemberUserView {
370    /// Retain the runtime identity fields used in organization member responses.
371    pub fn from_user(user: &crate::UserView) -> Self {
372        Self {
373            id: SchemaValue::from_field(user.native_field_value("id").unwrap_or_default()),
374            email: SchemaValue::from_field(user.native_field_value("email").unwrap_or_default()),
375            name: SchemaValue::from_field(user.native_field_value("name").unwrap_or_default()),
376            image: SchemaValue::from_field(user.native_field_value("image").unwrap_or_default()),
377        }
378    }
379}