Skip to main content

torii_storage_seaorm/
lib.rs

1//! SeaORM storage backend for Torii
2//!
3//! This crate provides a SeaORM-based storage implementation for the Torii authentication framework.
4//! SeaORM is a modern async ORM for Rust that provides type-safe database operations and supports
5//! multiple database backends including PostgreSQL, MySQL, and SQLite.
6//!
7//! # Features
8//!
9//! - **Multi-Database Support**: Works with PostgreSQL, MySQL, and SQLite through SeaORM
10//! - **Type-Safe Operations**: Leverages SeaORM's compile-time query validation
11//! - **Async/Await**: Fully async database operations with tokio
12//! - **Automatic Migrations**: Built-in schema migration management
13//! - **User Management**: Store and retrieve user accounts with email verification support
14//! - **Session Management**: Handle user sessions with configurable expiration
15//! - **Password Authentication**: Secure password hashing and verification
16//! - **OAuth Integration**: Store OAuth account connections and tokens
17//! - **Passkey Support**: WebAuthn/FIDO2 passkey storage and challenge management
18//!
19//! # Usage
20//!
21//! ```rust,no_run
22//! use torii_storage_seaorm::SeaORMStorage;
23//! use torii_core::UserId;
24//!
25//! #[tokio::main]
26//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
27//!     // Connect to database (supports PostgreSQL, MySQL, SQLite)
28//!     let storage = SeaORMStorage::connect("sqlite://todos.db?mode=rwc").await?;
29//!
30//!     // Run migrations to set up the schema
31//!     storage.migrate().await?;
32//!
33//!     // Convert to repository provider and use with Torii
34//!     let repositories = std::sync::Arc::new(storage.into_repository_provider());
35//!     let torii = torii::Torii::new(repositories);
36//!
37//!     Ok(())
38//! }
39//! ```
40//!
41//! # Repository Provider
42//!
43//! The crate provides [`SeaORMRepositoryProvider`] which implements the [`RepositoryProvider`] trait
44//! from `torii-core`, allowing it to be used directly with the main Torii authentication coordinator.
45//!
46//! # Database Support
47//!
48//! This crate can be used with any database backend supported by SeaORM:
49//! - **PostgreSQL**: Production-ready with full feature support
50//! - **MySQL**: Production-ready with full feature support
51//! - **SQLite**: Great for development and smaller deployments
52//!
53//! # Storage Implementations
54//!
55//! This crate implements repository patterns for:
56//! - User account management and profile storage
57//! - Session management with automatic expiration
58//! - Password credential storage with secure hashing
59//! - OAuth account connections and token management
60//! - WebAuthn passkey credentials and challenge handling
61//!
62//! # Entity Models
63//!
64//! The crate defines SeaORM entity models for all authentication data:
65//! - `User` - User accounts and profile information
66//! - `Session` - Active user sessions
67//! - `Password` - Hashed password credentials
68//! - `OAuthAccount` - Connected OAuth accounts
69//! - `Passkey` - WebAuthn passkey credentials
70//! - `PasskeyChallenge` - Temporary passkey challenges
71//!
72//! All entities include appropriate relationships and indexes for optimal performance.
73
74mod entities;
75mod migrations;
76mod oauth;
77mod passkey;
78mod password;
79mod session;
80mod token;
81mod user;
82
83pub mod repositories;
84pub use repositories::SeaORMRepositoryProvider;
85
86use migrations::Migrator;
87use sea_orm::{Database, DatabaseConnection};
88use sea_orm_migration::prelude::*;
89
90#[derive(Debug, thiserror::Error)]
91pub enum SeaORMStorageError {
92    #[error(transparent)]
93    Database(#[from] sea_orm::DbErr),
94    #[error("User not found")]
95    UserNotFound,
96}
97
98/// SeaORM storage backend
99///
100/// This storage backend uses SeaORM to manage database connections and migrations.
101/// It provides a `connect` method to create a new storage instance from a database URL.
102/// It also provides a `migrate` method to apply pending migrations.
103///
104/// # Example
105///
106/// ```rust,no_run
107/// use torii_storage_seaorm::SeaORMStorage;
108///
109/// #[tokio::main]
110/// async fn main() {
111///     let storage = SeaORMStorage::connect("sqlite://todos.db?mode=rwc").await.unwrap();
112///     let _ = storage.migrate().await.unwrap();
113/// }
114/// ```
115#[derive(Clone)]
116pub struct SeaORMStorage {
117    pool: DatabaseConnection,
118}
119
120impl SeaORMStorage {
121    pub fn new(pool: DatabaseConnection) -> Self {
122        Self { pool }
123    }
124
125    pub async fn connect(url: &str) -> Result<Self, SeaORMStorageError> {
126        let pool = Database::connect(url).await?;
127        pool.ping().await?;
128
129        Ok(Self::new(pool))
130    }
131
132    pub async fn migrate(&self) -> Result<(), SeaORMStorageError> {
133        Migrator::up(&self.pool, None).await.unwrap();
134
135        Ok(())
136    }
137
138    /// Create a repository provider from this storage instance
139    pub fn into_repository_provider(self) -> SeaORMRepositoryProvider {
140        SeaORMRepositoryProvider::new(self.pool)
141    }
142}
143#[cfg(test)]
144mod tests {
145    use sea_orm::Database;
146
147    use crate::migrations::Migrator;
148
149    use super::*;
150
151    #[tokio::test]
152    async fn test_migrations_up() {
153        let pool = Database::connect("sqlite::memory:").await.unwrap();
154        let migrations = Migrator::get_pending_migrations(&pool).await.unwrap();
155        migrations.iter().for_each(|m| {
156            println!("{}: {}", m.name(), m.status());
157        });
158        Migrator::up(&pool, None).await.unwrap();
159        let migrations = Migrator::get_pending_migrations(&pool).await.unwrap();
160        migrations.iter().for_each(|m| {
161            println!("{}: {}", m.name(), m.status());
162        });
163    }
164}