Skip to main content

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