Skip to content

Commit

Permalink
feat: add create_user to AuthPlugin trait
Browse files Browse the repository at this point in the history
Moving the user table into `torii-core` with a minimum set of fields that all plugins should be safe using.

I also added a new enum (only one variant right now) to aid in the creation of users via different plugins. This is preferred over expanding the user struct.

Password hashing is now down via https://docs.rs/password-auth/latest/password_auth/ which uses argon2 by default.
  • Loading branch information
cmackenzie1 committed Jan 28, 2025
1 parent 505916d commit b2e8432
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 19 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,25 @@

Torii is currently under active development. As we are in the initial phases, many features are experimental and may be significantly modified or removed in future versions.

The goal of Torii is to provide a simple and flexible authentication system for web applications through plugins.

## Development Status

- 🚧 Early Development
- ⚠️ Not Production Ready
- 📝 APIs Subject to Change

## Current Plugins

- [Password Auth](./torii-auth-email/README.md)
- OAuth 2.0 Auth (coming soon)
- OpenID Connect Auth (coming soon)
- WebAuthn Auth (coming soon)

## Disclaimer

This project is not production ready and should not be used in production environments. The maintainers are not responsible for any data loss or other issues that may arise from using this software.

## Contributing

As this project is in its early stages, we welcome discussions and feedback, but please note that major changes may occur.
Expand Down
3 changes: 2 additions & 1 deletion torii-auth-email/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ license.workspace = true
torii-core = { path = "../torii-core" }
async-trait.workspace = true
sqlx.workspace = true
thiserror.workspace = true
thiserror.workspace = true
password-auth = { version = "1", features = ["argon2"] }
40 changes: 35 additions & 5 deletions torii-auth-email/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
mod migrations;

use async_trait::async_trait;
use password_auth::{generate_hash, verify_password};
use sqlx::pool::Pool;
use sqlx::sqlite::Sqlite;
use sqlx::Row;
use torii_core::migration::PluginMigration;
use torii_core::plugin::CreateUserParams;
use torii_core::{AuthPlugin, Credentials, Error, User};

pub struct EmailPasswordPlugin;
Expand All @@ -31,6 +33,30 @@ impl AuthPlugin for EmailPasswordPlugin {
Ok(())
}

async fn create_user(
&self,
pool: &Pool<Sqlite>,
params: &CreateUserParams,
) -> Result<(), Error> {
let (username, password) = match params {
CreateUserParams::Password { username, password } => (username, password),
_ => return Err(Error::Auth("Unsupported create user params".into())),
};

let password_hash = generate_hash(password);

sqlx::query(
r#"
INSERT INTO torii_users (username, password_hash) VALUES (?, ?)
"#,
)
.bind(username)
.bind(password_hash)
.execute(pool)
.await?;
Ok(())
}

async fn authenticate(&self, pool: &Pool<Sqlite>, creds: &Credentials) -> Result<User, Error> {
let password = creds
.password
Expand All @@ -39,23 +65,27 @@ impl AuthPlugin for EmailPasswordPlugin {

let row = sqlx::query(
r#"
SELECT id, username
FROM users
WHERE username = ? AND password_hash = ?
SELECT id, username, password_hash
FROM torii_users
WHERE username = ?
"#,
)
.bind(&creds.username)
.bind(password)
.fetch_one(pool)
.await?;

let stored_hash: String = row.get("password_hash");
if verify_password(password, &stored_hash).is_err() {
return Err(Error::Auth("Invalid credentials".into()));
}

Ok(User {
id: row.get(0),
username: row.get(1),
})
}

fn migrations(&self) -> Vec<Box<dyn PluginMigration>> {
vec![Box::new(migrations::CreateUsersTable)]
vec![Box::new(migrations::AddPasswordColumn)]
}
}
17 changes: 7 additions & 10 deletions torii-auth-email/src/migrations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,22 @@ use torii_core::migration::PluginMigration;
use torii_core::Error;

// Example implementation for EmailPasswordPlugin
pub(crate) struct CreateUsersTable;
pub(crate) struct AddPasswordColumn;

#[async_trait]
impl PluginMigration for CreateUsersTable {
impl PluginMigration for AddPasswordColumn {
fn version(&self) -> i64 {
1
}

fn name(&self) -> &str {
"create_users_table"
"add_password_column"
}

async fn up(&self, pool: &Pool<Sqlite>) -> Result<(), Error> {
sqlx::query(
r#"
CREATE TABLE users (
id INTEGER PRIMARY KEY,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
ALTER TABLE torii_users ADD COLUMN password_hash TEXT NOT NULL;
"#,
)
.execute(pool)
Expand All @@ -33,7 +28,9 @@ impl PluginMigration for CreateUsersTable {
}

async fn down(&self, pool: &Pool<Sqlite>) -> Result<(), Error> {
sqlx::query("DROP TABLE users").execute(pool).await?;
sqlx::query("ALTER TABLE torii_users DROP COLUMN password_hash;")
.execute(pool)
.await?;
Ok(())
}
}
33 changes: 31 additions & 2 deletions torii-core/src/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,22 @@ pub struct Credentials {
pub password: Option<String>,
}

#[non_exhaustive]
#[derive(Debug, Clone)]
pub enum CreateUserParams {
Password { username: String, password: String },
}

#[async_trait]
pub trait AuthPlugin: Send + Sync {
fn name(&self) -> &'static str;
async fn setup(&self, pool: &Pool<Sqlite>) -> Result<(), Error>;
async fn authenticate(&self, pool: &Pool<Sqlite>, creds: &Credentials) -> Result<User, Error>;
async fn create_user(
&self,
pool: &Pool<Sqlite>,
params: &CreateUserParams,
) -> Result<(), Error>;
fn migrations(&self) -> Vec<Box<dyn PluginMigration>>;
}

Expand Down Expand Up @@ -94,6 +105,23 @@ impl PluginManager {
Ok(())
}

async fn init_user_table(&self, pool: &Pool<Sqlite>) -> Result<(), Error> {
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS torii_users (
id INTEGER PRIMARY KEY,
username TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(username)
)
"#,
)
.execute(pool)
.await?;
Ok(())
}

async fn get_applied_migrations(
&self,
pool: &Pool<Sqlite>,
Expand All @@ -113,7 +141,7 @@ impl PluginManager {
&self,
pool: &Pool<Sqlite>,
plugin_name: &str,
migration: &Box<dyn PluginMigration>,
migration: &dyn PluginMigration,
) -> Result<(), Error> {
// Start transaction
let mut tx = pool.begin().await?;
Expand All @@ -136,6 +164,7 @@ impl PluginManager {

pub async fn migrate(&self, pool: &Pool<Sqlite>) -> Result<(), Error> {
self.init_migration_table(pool).await?;
self.init_user_table(pool).await?;

for (plugin_name, plugin) in &self.plugins {
let applied = self.get_applied_migrations(pool, plugin_name).await?;
Expand All @@ -145,7 +174,7 @@ impl PluginManager {
.filter(|m| !applied.contains(&m.version()));

for migration in pending {
self.apply_migration(pool, plugin_name, &migration).await?;
self.apply_migration(pool, plugin_name, &*migration).await?;
}
}
Ok(())
Expand Down
2 changes: 1 addition & 1 deletion torii/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ email-auth = ["torii-auth-email"]
sqlite = ["sqlx/sqlite"]
postgres = ["sqlx/postgres"]
# Runtime
tokio-runtime = ["tokio"]
tokio-runtime = ["tokio"]

0 comments on commit b2e8432

Please sign in to comment.