torii_core/
user.rs

1//! User management and authentication
2//!
3//! This module contains the core user struct and related functionality.
4//!
5//! Users are the core of the authentication system. They are responsible for storing user information and are used to identify users in the system. The core user struct is defined as follows:
6//!
7//! | Field               | Type               | Description                                       |
8//! | ------------------- | ------------------ | ------------------------------------------------- |
9//! | `id`                | `String`           | The unique identifier for the user.               |
10//! | `name`              | `String`           | The name of the user.                             |
11//! | `email`             | `String`           | The email of the user.                            |
12//! | `email_verified_at` | `Option<DateTime>` | The timestamp when the user's email was verified. |
13//! | `created_at`        | `DateTime`         | The timestamp when the user was created.          |
14//! | `updated_at`        | `DateTime`         | The timestamp when the user was last updated.     |
15use crate::{
16    Error,
17    error::ValidationError,
18    id::{generate_prefixed_id, validate_prefixed_id},
19    storage::NewUser,
20};
21use async_trait::async_trait;
22use chrono::{DateTime, Utc};
23use serde::{Deserialize, Serialize};
24
25/// A unique, stable identifier for a specific user
26/// This value should be treated as opaque, and should not be used as a UUID even if it may look like one
27#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
28pub struct UserId(String);
29
30impl UserId {
31    pub fn new(id: &str) -> Self {
32        UserId(id.to_string())
33    }
34
35    pub fn new_random() -> Self {
36        UserId(generate_prefixed_id("usr"))
37    }
38
39    pub fn into_inner(self) -> String {
40        self.0
41    }
42
43    pub fn as_str(&self) -> &str {
44        &self.0
45    }
46
47    /// Validate that this ID has the correct format for a user ID
48    pub fn is_valid(&self) -> bool {
49        validate_prefixed_id(&self.0, "usr")
50    }
51}
52
53impl Default for UserId {
54    fn default() -> Self {
55        Self::new_random()
56    }
57}
58
59impl From<String> for UserId {
60    fn from(s: String) -> Self {
61        Self(s)
62    }
63}
64
65impl From<&str> for UserId {
66    fn from(s: &str) -> Self {
67        Self(s.to_string())
68    }
69}
70
71impl std::fmt::Display for UserId {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        write!(f, "{}", self.0)
74    }
75}
76
77/// The central manager for user operations
78///
79/// This trait defines core functionality for managing users. Implementations
80/// should provide efficient means of creating, retrieving, and updating users.
81#[async_trait]
82pub trait UserManager: Send + Sync + 'static {
83    /// Create a new user
84    async fn create_user(&self, user: &NewUser) -> Result<User, Error>;
85
86    /// Get a user by ID
87    async fn get_user(&self, id: &UserId) -> Result<Option<User>, Error>;
88
89    /// Get a user by email
90    async fn get_user_by_email(&self, email: &str) -> Result<Option<User>, Error>;
91
92    /// Get an existing user or create a new one if not found
93    async fn get_or_create_user_by_email(&self, email: &str) -> Result<User, Error>;
94
95    /// Update a user's information
96    async fn update_user(&self, user: &User) -> Result<User, Error>;
97
98    /// Delete a user by ID
99    async fn delete_user(&self, id: &UserId) -> Result<(), Error>;
100
101    /// Mark a user's email as verified
102    async fn set_user_email_verified(&self, user_id: &UserId) -> Result<(), Error>;
103}
104
105/// Representation of a user in Torii. This is the user object returned by all authentication plugins.
106///
107/// Many of these fields are optional, as they may not be available from the authentication provider,
108/// or may not be known at the time of authentication.
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct User {
111    // The unique identifier for the user.
112    pub id: UserId,
113
114    // The name of the user.
115    pub name: Option<String>,
116
117    // The email of the user.
118    pub email: String,
119
120    // The email verified at timestamp. If the user has not verified their email, this will be None.
121    pub email_verified_at: Option<DateTime<Utc>>,
122
123    // The created at timestamp.
124    pub created_at: DateTime<Utc>,
125
126    // The updated at timestamp.
127    pub updated_at: DateTime<Utc>,
128}
129
130impl User {
131    pub fn builder() -> UserBuilder {
132        UserBuilder::default()
133    }
134
135    /// Check if the user's email has been verified.
136    pub fn is_email_verified(&self) -> bool {
137        self.email_verified_at.is_some()
138    }
139}
140
141#[derive(Default)]
142pub struct UserBuilder {
143    id: Option<UserId>,
144    name: Option<String>,
145    email: Option<String>,
146    email_verified_at: Option<DateTime<Utc>>,
147    created_at: Option<DateTime<Utc>>,
148    updated_at: Option<DateTime<Utc>>,
149}
150
151impl UserBuilder {
152    pub fn id(mut self, id: UserId) -> Self {
153        self.id = Some(id);
154        self
155    }
156
157    pub fn name(mut self, name: Option<String>) -> Self {
158        self.name = name;
159        self
160    }
161
162    pub fn email(mut self, email: String) -> Self {
163        self.email = Some(email);
164        self
165    }
166
167    pub fn email_verified_at(mut self, email_verified_at: Option<DateTime<Utc>>) -> Self {
168        self.email_verified_at = email_verified_at;
169        self
170    }
171
172    pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
173        self.created_at = Some(created_at);
174        self
175    }
176
177    pub fn updated_at(mut self, updated_at: DateTime<Utc>) -> Self {
178        self.updated_at = Some(updated_at);
179        self
180    }
181
182    pub fn build(self) -> Result<User, Error> {
183        let now = Utc::now();
184        Ok(User {
185            id: self.id.unwrap_or_default(),
186            name: self.name,
187            email: self.email.ok_or(ValidationError::InvalidField(
188                "Email is required".to_string(),
189            ))?,
190            email_verified_at: self.email_verified_at,
191            created_at: self.created_at.unwrap_or(now),
192            updated_at: self.updated_at.unwrap_or(now),
193        })
194    }
195}
196
197/// Default implementation of the UserManager trait
198///
199/// This implementation wraps a UserStorage implementation and provides
200/// the core user management functionality needed by authentication systems.
201pub struct DefaultUserManager<S>
202where
203    S: crate::storage::UserStorage,
204{
205    storage: std::sync::Arc<S>,
206}
207
208impl<S> DefaultUserManager<S>
209where
210    S: crate::storage::UserStorage,
211{
212    /// Create a new DefaultUserManager with the specified storage
213    pub fn new(storage: std::sync::Arc<S>) -> Self {
214        Self { storage }
215    }
216}
217
218#[async_trait]
219impl<S> UserManager for DefaultUserManager<S>
220where
221    S: crate::storage::UserStorage,
222{
223    async fn create_user(&self, user: &NewUser) -> Result<User, Error> {
224        self.storage
225            .create_user(user)
226            .await
227            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
228    }
229
230    async fn get_user(&self, id: &UserId) -> Result<Option<User>, Error> {
231        self.storage
232            .get_user(id)
233            .await
234            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
235    }
236
237    async fn get_user_by_email(&self, email: &str) -> Result<Option<User>, Error> {
238        self.storage
239            .get_user_by_email(email)
240            .await
241            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
242    }
243
244    async fn get_or_create_user_by_email(&self, email: &str) -> Result<User, Error> {
245        self.storage
246            .get_or_create_user_by_email(email)
247            .await
248            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
249    }
250
251    async fn update_user(&self, user: &User) -> Result<User, Error> {
252        self.storage
253            .update_user(user)
254            .await
255            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
256    }
257
258    async fn delete_user(&self, id: &UserId) -> Result<(), Error> {
259        self.storage
260            .delete_user(id)
261            .await
262            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
263    }
264
265    async fn set_user_email_verified(&self, user_id: &UserId) -> Result<(), Error> {
266        self.storage
267            .set_user_email_verified(user_id)
268            .await
269            .map_err(|e| Error::Storage(crate::error::StorageError::Database(e.to_string())))
270    }
271}
272
273#[derive(Debug, Clone, Serialize, Deserialize)]
274pub struct OAuthAccount {
275    pub user_id: UserId,
276    pub provider: String,
277    pub subject: String,
278    pub created_at: DateTime<Utc>,
279    pub updated_at: DateTime<Utc>,
280}
281
282impl OAuthAccount {
283    pub fn builder() -> OAuthAccountBuilder {
284        OAuthAccountBuilder::default()
285    }
286}
287
288#[derive(Default)]
289pub struct OAuthAccountBuilder {
290    user_id: Option<UserId>,
291    provider: Option<String>,
292    subject: Option<String>,
293    created_at: Option<DateTime<Utc>>,
294    updated_at: Option<DateTime<Utc>>,
295}
296
297impl OAuthAccountBuilder {
298    pub fn user_id(mut self, user_id: UserId) -> Self {
299        self.user_id = Some(user_id);
300        self
301    }
302
303    pub fn provider(mut self, provider: String) -> Self {
304        self.provider = Some(provider);
305        self
306    }
307
308    pub fn subject(mut self, subject: String) -> Self {
309        self.subject = Some(subject);
310        self
311    }
312
313    pub fn created_at(mut self, created_at: DateTime<Utc>) -> Self {
314        self.created_at = Some(created_at);
315        self
316    }
317
318    pub fn updated_at(mut self, updated_at: DateTime<Utc>) -> Self {
319        self.updated_at = Some(updated_at);
320        self
321    }
322
323    pub fn build(self) -> Result<OAuthAccount, Error> {
324        let now = Utc::now();
325        Ok(OAuthAccount {
326            user_id: self.user_id.ok_or(ValidationError::MissingField(
327                "User ID is required".to_string(),
328            ))?,
329            provider: self.provider.ok_or(ValidationError::MissingField(
330                "Provider is required".to_string(),
331            ))?,
332            subject: self.subject.ok_or(ValidationError::MissingField(
333                "Subject is required".to_string(),
334            ))?,
335            created_at: self.created_at.unwrap_or(now),
336            updated_at: self.updated_at.unwrap_or(now),
337        })
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn test_user_id() {
347        let user_id = UserId::new("test");
348        assert_eq!(user_id.as_str(), "test");
349
350        let user_id_from_str = UserId::from(user_id.as_str());
351        assert_eq!(user_id_from_str, user_id);
352
353        let user_id_random = UserId::new_random();
354        assert_ne!(user_id_random, user_id);
355    }
356
357    #[test]
358    fn test_user_id_prefixed() {
359        let user_id = UserId::new_random();
360        assert!(user_id.as_str().starts_with("usr_"));
361        assert!(user_id.is_valid());
362
363        // Test uniqueness
364        let user_id2 = UserId::new_random();
365        assert_ne!(user_id, user_id2);
366
367        // Test invalid format
368        let invalid_id = UserId::new("invalid");
369        assert!(!invalid_id.is_valid());
370
371        // Test valid manual creation
372        let valid_id = UserId::new("usr_dGVzdA"); // "test" in base64
373        assert!(!valid_id.is_valid()); // Should be false because it's too short (not 96 bits)
374    }
375}