Skip to main content

better_auth_core/
entity.rs

1//! Entity traits for the Alibi framework.
2//!
3//! These traits define the interface that entity types must implement.
4//! The framework accesses entity fields through these trait methods,
5//! allowing users to define their own entity structs with custom field names
6//! and extra fields.
7//!
8//! Implement these traits manually for any custom types used inside the auth
9//! runtime.
10
11use crate::types::InvitationStatus;
12use chrono::{DateTime, Utc};
13use serde::Deserialize;
14use serde::Serialize;
15use std::borrow::Cow;
16
17/// Conversion between an application timestamp field and the auth UTC clock.
18///
19/// `NaiveDateTime` fields represent UTC wall-clock values, never local time.
20/// The optional implementation preserves typed absence in model mutations.
21pub trait AuthTimestamp: Sized {
22    /// The core timestamp shape (`DateTime<Utc>` or its optional form).
23    type UtcValue;
24    /// Convert a core UTC timestamp into the model representation.
25    fn from_utc(value: Self::UtcValue) -> Self;
26    /// Interpret the model timestamp on the core UTC clock.
27    fn into_utc(self) -> Self::UtcValue;
28}
29
30impl AuthTimestamp for chrono::NaiveDateTime {
31    type UtcValue = DateTime<Utc>;
32    fn from_utc(value: Self::UtcValue) -> Self {
33        value.naive_utc()
34    }
35    fn into_utc(self) -> Self::UtcValue {
36        self.and_utc()
37    }
38}
39
40impl<Tz: chrono::TimeZone> AuthTimestamp for DateTime<Tz>
41where
42    DateTime<Tz>: From<DateTime<Utc>>,
43{
44    type UtcValue = DateTime<Utc>;
45    fn from_utc(value: Self::UtcValue) -> Self {
46        value.into()
47    }
48    fn into_utc(self) -> Self::UtcValue {
49        self.with_timezone(&Utc)
50    }
51}
52
53impl<T: AuthTimestamp> AuthTimestamp for Option<T> {
54    type UtcValue = Option<T::UtcValue>;
55    fn from_utc(value: Self::UtcValue) -> Self {
56        value.map(T::from_utc)
57    }
58    fn into_utc(self) -> Self::UtcValue {
59        self.map(T::into_utc)
60    }
61}
62
63/// Trait representing a user entity.
64///
65/// The framework reads user fields through these getters. Custom types
66/// must provide all framework fields and may have additional fields.
67pub trait AuthUser: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
68    /// Encode the complete typed model for a trusted secondary backend.
69    /// Custom entities can opt in without adding a Deserialize bound to all schemas.
70    fn secondary_snapshot(&self) -> crate::AuthResult<serde_json::Value> {
71        Err(crate::AuthError::NotImplemented(
72            "Secondary model encoding is unsupported".into(),
73        ))
74    }
75    /// Restore a model from a trusted secondary backend, before public projection.
76    fn from_secondary_snapshot(_snapshot: serde_json::Value) -> crate::AuthResult<Self> {
77        Err(crate::AuthError::NotImplemented(
78            "Secondary model decoding is unsupported".into(),
79        ))
80    }
81
82    /// A genuine retained adapter result, including declared undefined values.
83    fn adapter_snapshot(&self) -> Option<&crate::AdapterOutput> {
84        None
85    }
86    /// A chosen authenticated view is already projected and must remain exact.
87    fn retained_user_view(&self) -> Option<&crate::UserView> {
88        None
89    }
90    /// Physical application/plugin values; public policy is applied separately.
91    fn additional_fields(&self) -> crate::field_policy::FieldOutput {
92        crate::field_policy::FieldOutput::new()
93    }
94    fn id(&self) -> Cow<'_, str>;
95    fn email(&self) -> Option<&str>;
96    fn name(&self) -> Option<&str>;
97    fn email_verified(&self) -> bool;
98    fn image(&self) -> Option<&str>;
99    fn created_at(&self) -> DateTime<Utc>;
100    fn updated_at(&self) -> DateTime<Utc>;
101    fn username(&self) -> Option<&str>;
102    fn display_username(&self) -> Option<&str>;
103    fn two_factor_enabled(&self) -> bool;
104    /// Persisted plugin state. `None` preserves an absent or unset field.
105    fn two_factor_enabled_value(&self) -> Option<bool> {
106        Some(self.two_factor_enabled())
107    }
108    fn role(&self) -> Option<&str>;
109    fn banned(&self) -> bool;
110    /// Persisted plugin state. `None` preserves an absent or unset field.
111    fn banned_value(&self) -> Option<bool> {
112        Some(self.banned())
113    }
114    fn ban_reason(&self) -> Option<&str>;
115    fn ban_expires(&self) -> Option<DateTime<Utc>>;
116    fn metadata(&self) -> &serde_json::Value;
117    /// Anonymous-plugin state; `None` means the field is absent or unset.
118    fn is_anonymous(&self) -> Option<bool> {
119        None
120    }
121    fn phone_number(&self) -> Option<&str> {
122        None
123    }
124    fn phone_number_verified(&self) -> Option<bool> {
125        None
126    }
127    fn last_login_method(&self) -> Option<&str> {
128        None
129    }
130}
131
132/// Trait representing a session entity.
133pub trait AuthSession: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
134    /// Encode the complete typed model for a trusted secondary backend.
135    /// Custom entities can opt in without adding a Deserialize bound to all schemas.
136    fn secondary_snapshot(&self) -> crate::AuthResult<serde_json::Value> {
137        Err(crate::AuthError::NotImplemented(
138            "Secondary model encoding is unsupported".into(),
139        ))
140    }
141    /// Restore a model from a trusted secondary backend, before public projection.
142    fn from_secondary_snapshot(_snapshot: serde_json::Value) -> crate::AuthResult<Self> {
143        Err(crate::AuthError::NotImplemented(
144            "Secondary model decoding is unsupported".into(),
145        ))
146    }
147
148    fn adapter_snapshot(&self) -> Option<&crate::AdapterOutput> {
149        None
150    }
151    fn retained_session_view(&self) -> Option<&crate::SessionView> {
152        None
153    }
154    fn additional_fields(&self) -> serde_json::Map<String, serde_json::Value> {
155        serde_json::Map::new()
156    }
157    fn id(&self) -> Cow<'_, str>;
158    fn expires_at(&self) -> DateTime<Utc>;
159    fn token(&self) -> &str;
160    fn created_at(&self) -> DateTime<Utc>;
161    fn updated_at(&self) -> DateTime<Utc>;
162    fn ip_address(&self) -> Option<&str>;
163    fn user_agent(&self) -> Option<&str>;
164    fn user_id(&self) -> Cow<'_, str>;
165    fn impersonated_by(&self) -> Option<&str>;
166    fn active_organization_id(&self) -> Option<&str>;
167    fn active_team_id(&self) -> Option<&str> {
168        None
169    }
170    fn active(&self) -> bool;
171}
172
173/// Trait representing an account entity (OAuth provider linking).
174pub trait AuthAccount: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
175    fn adapter_snapshot(&self) -> Option<&crate::AdapterOutput> {
176        None
177    }
178    fn additional_fields(&self) -> crate::field_policy::FieldOutput {
179        crate::field_policy::FieldOutput::new()
180    }
181    fn id(&self) -> Cow<'_, str>;
182    fn account_id(&self) -> &str;
183    fn provider_id(&self) -> &str;
184    fn user_id(&self) -> Cow<'_, str>;
185    fn access_token(&self) -> Option<&str>;
186    fn refresh_token(&self) -> Option<&str>;
187    fn id_token(&self) -> Option<&str>;
188    fn access_token_expires_at(&self) -> Option<DateTime<Utc>>;
189    fn refresh_token_expires_at(&self) -> Option<DateTime<Utc>>;
190    fn scope(&self) -> Option<&str>;
191    fn password(&self) -> Option<&str>;
192    fn created_at(&self) -> DateTime<Utc>;
193    fn updated_at(&self) -> DateTime<Utc>;
194}
195
196/// Trait representing an organization entity.
197pub trait AuthOrganization: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
198    fn additional_fields(&self) -> std::collections::BTreeMap<String, serde_json::Value> {
199        Default::default()
200    }
201    fn id(&self) -> Cow<'_, str>;
202    fn name(&self) -> &str;
203    fn slug(&self) -> &str;
204    fn logo(&self) -> Option<&str>;
205    fn metadata(&self) -> Option<&serde_json::Value>;
206    fn created_at(&self) -> DateTime<Utc>;
207    fn updated_at(&self) -> DateTime<Utc>;
208}
209
210/// Trait representing an organization member entity.
211pub trait AuthMember: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
212    fn id(&self) -> Cow<'_, str>;
213    fn organization_id(&self) -> Cow<'_, str>;
214    fn user_id(&self) -> Cow<'_, str>;
215    fn role(&self) -> &str;
216    fn created_at(&self) -> DateTime<Utc>;
217}
218
219/// Trait representing an invitation entity.
220pub trait AuthInvitation: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
221    fn id(&self) -> Cow<'_, str>;
222    fn organization_id(&self) -> Cow<'_, str>;
223    fn email(&self) -> &str;
224    fn role(&self) -> &str;
225    fn optional_role(&self) -> Option<&str> {
226        Some(self.role())
227    }
228    fn team_id(&self) -> Option<&str> {
229        None
230    }
231    fn status(&self) -> &InvitationStatus;
232    fn inviter_id(&self) -> Cow<'_, str>;
233    fn expires_at(&self) -> DateTime<Utc>;
234    fn created_at(&self) -> DateTime<Utc>;
235
236    /// Check if the invitation is still pending.
237    fn is_pending(&self) -> bool {
238        *self.status() == InvitationStatus::Pending
239    }
240
241    /// Check if the invitation has expired.
242    fn is_expired(&self) -> bool {
243        self.expires_at() < Utc::now()
244    }
245}
246
247/// Trait representing a verification token entity.
248pub trait AuthVerification: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
249    fn id(&self) -> Cow<'_, str>;
250    fn identifier(&self) -> &str;
251    fn value(&self) -> &str;
252    fn expires_at(&self) -> DateTime<Utc>;
253    fn created_at(&self) -> DateTime<Utc>;
254    fn updated_at(&self) -> DateTime<Utc>;
255}
256
257/// Trait representing a two-factor authentication entity.
258pub trait AuthTwoFactor: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
259    fn id(&self) -> Cow<'_, str>;
260    fn secret(&self) -> &str;
261    fn backup_codes(&self) -> &str;
262    fn user_id(&self) -> Cow<'_, str>;
263    fn created_at(&self) -> DateTime<Utc>;
264    fn updated_at(&self) -> DateTime<Utc>;
265    /// Legacy records without this field are not explicitly unverified.
266    fn verified(&self) -> Option<bool> {
267        None
268    }
269    fn failed_verification_count(&self) -> Option<f64> {
270        None
271    }
272    fn locked_until(&self) -> Option<DateTime<Utc>> {
273        None
274    }
275}
276
277/// Trait representing an API key entity.
278pub trait AuthApiKey: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
279    fn id(&self) -> Cow<'_, str>;
280    fn name(&self) -> Option<&str>;
281    fn start(&self) -> Option<&str>;
282    fn prefix(&self) -> Option<&str>;
283    fn key_hash(&self) -> &str;
284    /// Owner of the key — a user id, or an organization id when the key's
285    /// configuration references organizations.
286    fn reference_id(&self) -> Cow<'_, str>;
287    /// Name of the API-key configuration this key belongs to (`"default"`
288    /// unless the application registers named configurations).
289    fn config_id(&self) -> Cow<'_, str>;
290    fn refill_interval(&self) -> Option<f64>;
291    fn refill_amount(&self) -> Option<f64>;
292    fn last_refill_at(&self) -> Option<&str>;
293    fn enabled(&self) -> bool;
294    fn rate_limit_enabled(&self) -> bool;
295    fn rate_limit_time_window(&self) -> Option<f64>;
296    fn rate_limit_max(&self) -> Option<f64>;
297    fn request_count(&self) -> Option<f64>;
298    fn remaining(&self) -> Option<f64>;
299    fn last_request(&self) -> Option<&str>;
300    fn expires_at(&self) -> Option<&str>;
301    fn created_at(&self) -> &str;
302    fn updated_at(&self) -> &str;
303    fn permissions(&self) -> Option<&str>;
304    fn metadata(&self) -> Option<&str>;
305}
306
307/// Trait representing a passkey entity.
308pub trait AuthPasskey: Clone + Send + Sync + Serialize + std::fmt::Debug + 'static {
309    fn id(&self) -> Cow<'_, str>;
310    fn name(&self) -> Option<&str>;
311    fn public_key(&self) -> &str;
312    fn user_id(&self) -> Cow<'_, str>;
313    fn credential_id(&self) -> &str;
314    fn counter(&self) -> u64;
315    fn device_type(&self) -> &str;
316    fn backed_up(&self) -> bool;
317    fn transports(&self) -> Option<&str>;
318    fn created_at(&self) -> DateTime<Utc>;
319    fn updated_at(&self) -> DateTime<Utc>;
320    fn aaguid(&self) -> Option<&str>;
321    fn credential(&self) -> &str;
322}
323
324/// Minimal user info for member-related API responses.
325///
326/// This is a concrete framework type (not generic) used to project
327/// user fields into member responses.
328#[derive(Debug, Clone, Serialize, Deserialize)]
329pub struct MemberUserView {
330    pub id: String,
331    pub email: Option<String>,
332    pub name: Option<String>,
333    pub image: Option<String>,
334}
335
336impl MemberUserView {
337    /// Construct from any type implementing [`AuthUser`].
338    #[must_use]
339    pub fn from_user(user: &impl AuthUser) -> Self {
340        Self {
341            id: user.id().to_string(),
342            email: user.email().map(ToOwned::to_owned),
343            name: user.name().map(ToOwned::to_owned),
344            image: user.image().map(ToOwned::to_owned),
345        }
346    }
347}