diff options
| author | gennyble <gen@nyble.dev> | 2026-09-25 03:56:11 -0500 |
|---|---|---|
| committer | gennyble <gen@nyble.dev> | 2026-09-25 03:56:11 -0500 |
| commit | ba592de54cd3dce52eb2096dabbe748107f5db2b (patch) | |
| tree | 4a23377e9788630e09c669e9ed18aecd27673ee7 /knowing/src/db.rs | |
| parent | f4d8e8d03c1b80d5c59ee85631d5a78048892a32 (diff) | |
splorm auth
Diffstat (limited to 'knowing/src/db.rs')
| -rw-r--r-- | knowing/src/db.rs | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/knowing/src/db.rs b/knowing/src/db.rs new file mode 100644 index 0000000..593baad --- /dev/null +++ b/knowing/src/db.rs @@ -0,0 +1,200 @@ +#![allow(async_fn_in_trait)] +#![allow(unused)] + +use crate::Error; +use somethings::{ + DatabaseError, DatabaseHandle, + auth::{AnonymousUser, HashedPassword, Session, SessionId, User, UserAuth, UserId}, + params, +}; + +pub trait AuthDatabase { + async fn auth_create_tables(&self) -> Result<(), DatabaseError>; + + async fn insert_user(&self, id: &UserId, name: &str, hash: HashedPassword) + -> Result<(), Error>; + async fn insert_anonymous_user(&self, id: &UserId) -> Result<(), Error>; + + async fn user_exists(&self, id: &UserId) -> Result<bool, Error>; + async fn get_user_by_id(&self, id: &UserId) -> Result<User, Error>; + async fn get_anonymous_user_by_id(&self, id: &UserId) -> Result<AnonymousUser, Error>; + + async fn get_userauth_by_name(&self, name: &str) -> Result<UserAuth, Error>; + + async fn insert_session(&self, id: &SessionId, uid: &UserId) -> Result<(), Error>; + + async fn get_session_by_id(&self, id: &SessionId) -> Result<Session, Error>; + + async fn delete_session_by_id(&self, id: &SessionId) -> Result<(), Error>; +} + +impl AuthDatabase for DatabaseHandle { + async fn auth_create_tables(&self) -> Result<(), DatabaseError> { + let lock = self.lock().await; + lock.create_table("users", CREATE_TABLE_USERS)?; + lock.create_table("sessions", CREATE_TABLE_SESSIONS) + } + + async fn insert_user( + &self, + id: &UserId, + name: &str, + hash: HashedPassword, + ) -> Result<(), Error> { + self.lock().await.insert( + "INSERT INTO users(id, name, hash) VALUES(?1, ?2, ?3)", + params![id, name, hash.to_string()], + )?; + + Ok(()) + } + + async fn insert_anonymous_user(&self, id: &UserId) -> Result<(), Error> { + self.lock() + .await + .insert("INSERT INTO users(id) VALUES(?1)", params![id])?; + + Ok(()) + } + + async fn user_exists(&self, id: &UserId) -> Result<bool, Error> { + match get_dbuser_by_id(self, id).await { + Ok(_) => Ok(true), + Err(Error::DatabaseError(DatabaseError::NoRows)) => Ok(false), + Err(e) => Err(e), + } + } + + async fn get_user_by_id(&self, id: &UserId) -> Result<User, Error> { + get_dbuser_by_id(&self, id).await?.try_into() + } + + async fn get_anonymous_user_by_id(&self, id: &UserId) -> Result<AnonymousUser, Error> { + let dbuser = get_dbuser_by_id(&self, id).await?; + match dbuser.name { + Some(_) => Err(Error::UserIsRegistered), + None => Ok(AnonymousUser::new(dbuser.id)), + } + } + + async fn get_userauth_by_name(&self, name: &str) -> Result<UserAuth, Error> { + get_dbuser_by_name(&self, name).await?.try_into() + } + + async fn insert_session(&self, id: &SessionId, uid: &UserId) -> Result<(), Error> { + self.lock().await.insert( + "INSERT INTO sessions(id, user_id) VALUES(?1, ?2)", + params![id, uid], + )?; + + Ok(()) + } + + async fn get_session_by_id(&self, id: &SessionId) -> Result<Session, Error> { + let sql = "SELECT sessions.id, users.id, users.name \ + FROM sessions \ + INNER JOIN users ON sessions.user_id = users.id \ + WHERE sessions.id = ?1"; + + self.lock() + .await + .query_one(sql, params![id], |row| { + Ok(Session::new( + row.get(0)?, + User::new(row.get(1)?, row.get(2)?), + )) + }) + .map_err(|dbe| Error::DatabaseError(dbe)) + } + + async fn delete_session_by_id(&self, id: &SessionId) -> Result<(), Error> { + self.lock() + .await + .execute("DELETE FROM session WHERE id = ?1", params![id])?; + Ok(()) + } +} + +async fn get_dbuser_by_id(db: &DatabaseHandle, id: &UserId) -> Result<DbUser, Error> { + db.lock() + .await + .query_one( + "SELECT id, name, hash FROM users WHERE id = ?1", + params![id], + |row| { + Ok(DbUser { + id: row.get(0)?, + name: row.get(1)?, + hash: row.get(2)?, + }) + }, + ) + .map_err(|e| Error::DatabaseError(e)) +} + +async fn get_dbuser_by_name(db: &DatabaseHandle, name: &str) -> Result<DbUser, Error> { + db.lock() + .await + .query_one( + "SELECT id, name, hash FROM users WHERE name = ?1", + params![name], + |row| { + Ok(DbUser { + id: row.get(0)?, + name: row.get(1)?, + hash: row.get(2)?, + }) + }, + ) + .map_err(|e| Error::DatabaseError(e)) +} + +pub const CREATE_TABLE_USERS: &'static str = "\ + CREATE TABLE IF NOT EXISTS users( + id TEXT NOT NULL PRIMARY KEY, + name TEXT, + hash TEXT, + created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP + );"; + +pub const CREATE_TABLE_SESSIONS: &'static str = "\ + CREATE TABLE IF NOT EXISTS sessions( + id TEXT NOT NULL PRIMARY KEY, + user_id INTEGER NOT NULL, + created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) + REFERENCES users(id) + );"; + +struct DbUser { + id: UserId, + name: Option<String>, + hash: Option<String>, +} + +impl TryInto<User> for DbUser { + type Error = Error; + + fn try_into(self) -> Result<User, Self::Error> { + let DbUser { id, name, hash: _ } = self; + match name { + Some(name) => Ok(User::new(id, name)), + None => Err(Error::UserIsAnonymous), + } + } +} + +impl TryInto<UserAuth> for DbUser { + type Error = Error; + + fn try_into(self) -> Result<UserAuth, Self::Error> { + let DbUser { id, name, hash } = self; + match (name, hash) { + (Some(name), Some(hash)) => Ok(UserAuth::new( + User::new(id, name), + HashedPassword::new(hash), + )), + _ => Err(Error::UserIsAnonymous), + } + } +} |
