diff --git a/Cargo.lock b/Cargo.lock index 2fe2470..a13030d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1085,6 +1085,7 @@ dependencies = [ "rkyv_typename", "rsasl", "rumqttc", + "rusqlite", "rust-argon2", "rustls", "rustls-native-certs", @@ -1215,6 +1216,18 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fastrand" version = "1.8.0" @@ -1514,6 +1527,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashlink" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +dependencies = [ + "hashbrown", +] + [[package]] name = "hdrhistogram" version = "7.5.2" @@ -1799,6 +1821,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libsqlite3-sys" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29f835d03d717946d28b1d1ed632eb6f0e24a299388ee623d0c23118d3e8a7fa" +dependencies = [ + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-sys" version = "1.1.8" @@ -2633,6 +2665,20 @@ dependencies = [ "webpki", ] +[[package]] +name = "rusqlite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01e213bc3ecb39ac32e81e51ebe31fd888a940515173e3a18a35f8c6e896422a" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rust-argon2" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 2805841..16f5477 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,6 +112,8 @@ rustls-native-certs = "0.6.1" shadow-rs = "0.11" +rusqlite = "0.28" + [dependencies.rsasl] version = "2.0.0" default_features = false diff --git a/bffhd/users/mod.rs b/bffhd/users/mod.rs index bd7a7f9..f4a93a7 100644 --- a/bffhd/users/mod.rs +++ b/bffhd/users/mod.rs @@ -15,6 +15,7 @@ use std::sync::Arc; use thiserror::Error; pub mod db; +mod sqlite; use crate::users::db::UserData; use crate::UserDB; @@ -211,3 +212,12 @@ impl Users { Ok(0) } } + +pub trait UserDb { + type User; + type Error: std::error::Error + miette::Diagnostic; + + fn lookup(&self, userid: &str) -> Result, Self::Error>; + + fn get_roles(&self, user: &Self::User) -> Result, Self::Error>; +} \ No newline at end of file diff --git a/bffhd/users/sqlite.rs b/bffhd/users/sqlite.rs new file mode 100644 index 0000000..0dd1500 --- /dev/null +++ b/bffhd/users/sqlite.rs @@ -0,0 +1,93 @@ +use std::collections::HashMap; +use std::path::Path; +use rusqlite::{Connection, OptionalExtension}; +use crate::users::db::{User, UserData}; + +pub struct UserSqliteDB { + conn: Connection, +} + +impl UserSqliteDB { + pub fn open(path: impl AsRef) -> rusqlite::Result { + let conn = Connection::open(path)?; + + conn.execute("CREATE TABLE IF NOT EXISTS users (\ + id INTEGER PRIMARY KEY,\ + name TEXT NOT NULL,\ + password TEXT\ + )", ())?; + conn.execute("CREATE TABLE IF NOT EXISTS user_kv (\ + id INTEGER PRIMARY KEY,\ + user INTEGER,\ + key TEXT NOT NULL,\ + value TEXT NOT NULL,\ + FOREIGN KEY(user) REFERENCES users(id)\ + )", ())?; + conn.execute("CREATE TABLE IF NOT EXISTS user_roles (\ + id INTEGER PRIMARY KEY,\ + user INTEGER,\ + role TEXT NOT NULL,\ + FOREIGN KEY(user) REFERENCES users(id)\ + )", ())?; + + Ok(Self { conn }) + } + + pub fn get(&self, uid: &str) -> rusqlite::Result> { + let sqlite_user = self.conn.query_row("SELECT id, name, password FROM users WHERE name = ?1", [uid], |row| { + Ok(SqliteUser { + id: row.get(0)?, + name: row.get(1)?, + password: row.get(2)?, + }) + }).optional()?; + if let Some(SqliteUser { name, password, .. }) = sqlite_user { + let mut kv_stmt = self.conn.prepare("SELECT key, value FROM user_kv WHERE user_id = :id")?; + let kv: HashMap = kv_stmt + .query_map(&[(":id", &name)], |row| Ok((row.get(0)?, row.get(1)?)))? + .filter_map(|fields| fields.ok()) + .collect(); + + let mut roles_stmt = self.conn.prepare("SELECT role FROM user_roles WHERE user_id = :id")?; + let roles: Vec = roles_stmt + .query_map(&[(":id", &name)], |row| row.get(0))? + .filter_map(|fields| fields.ok()) + .collect(); + + let passwd = if password.is_empty() { None } else { Some(password) }; + Ok(Some(User { + id: name, + userdata: UserData { + roles, + passwd, + kv, + } + })) + } else { + Ok(None) + } + } + + pub fn put(&self, uid: &str, user: &SqliteUser) -> rusqlite::Result<()> { + todo!() + } +} + +pub struct SqliteUser { + id: i32, + name: String, + password: String, +} + +struct SqliteUserKv { + id: i32, + user_id: i32, + key: String, + value: String, +} + +struct SqliteRoles { + id: i32, + user_id: i32, + role: String, +} \ No newline at end of file