Add the ability to create and list users

This commit is contained in:
Savanni D'Gerinel 2023-10-03 13:31:58 -04:00
parent 6aedff8cda
commit 4a7d741224
1 changed files with 86 additions and 8 deletions

View File

@ -1,9 +1,15 @@
use base64ct::{Base64, Encoding};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sqlx::sqlite::SqlitePool; use sha2::{Digest, Sha256};
use sqlx::{
sqlite::{SqlitePool, SqliteRow},
Executor, Row,
};
use std::collections::HashSet; use std::collections::HashSet;
use std::{ops::Deref, path::PathBuf, sync::Arc}; use std::{ops::Deref, path::PathBuf, sync::Arc};
use thiserror::Error; use thiserror::Error;
use tokio::sync::RwLock; use tokio::sync::RwLock;
use uuid::Uuid;
mod filehandle; mod filehandle;
mod fileinfo; mod fileinfo;
@ -62,7 +68,13 @@ pub enum ReadFileError {
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum AuthError { pub enum AuthError {
#[error("database failed")] #[error("database failed")]
SqlError, SqlError(sqlx::Error),
}
impl From<sqlx::Error> for AuthError {
fn from(err: sqlx::Error) -> AuthError {
AuthError::SqlError(err)
}
} }
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
@ -80,13 +92,13 @@ impl From<&str> for Username {
} }
} }
impl From<Username> for PathBuf { impl From<Username> for String {
fn from(s: Username) -> Self { fn from(s: Username) -> Self {
Self::from(&s) Self::from(&s)
} }
} }
impl From<&Username> for PathBuf { impl From<&Username> for String {
fn from(s: &Username) -> Self { fn from(s: &Username) -> Self {
let Username(s) = s; let Username(s) = s;
Self::from(s) Self::from(s)
@ -100,6 +112,13 @@ impl Deref for Username {
} }
} }
impl sqlx::FromRow<'_, SqliteRow> for Username {
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
let name: String = row.try_get("username")?;
Ok(Username::from(name))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)] #[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
pub struct AuthToken(String); pub struct AuthToken(String);
@ -263,19 +282,47 @@ pub struct AuthDB {
impl AuthDB { impl AuthDB {
pub async fn new(path: PathBuf) -> Result<Self, sqlx::Error> { pub async fn new(path: PathBuf) -> Result<Self, sqlx::Error> {
let migrator = sqlx::migrate!("./migrations");
let pool = SqlitePool::connect(&format!("sqlite://{}", path.to_str().unwrap())).await?; let pool = SqlitePool::connect(&format!("sqlite://{}", path.to_str().unwrap())).await?;
migrator.run(&pool).await?;
Ok(Self { pool }) Ok(Self { pool })
} }
async fn add_user(&self, username: Username) -> Result<AuthToken, AuthError> {
let auth_plaintext = format!("{}:{}", Uuid::new_v4(), username.to_string());
let mut hasher = Sha256::new();
hasher.update(auth_plaintext);
let auth_token = Base64::encode_string(&hasher.finalize());
let _ = sqlx::query("INSERT INTO users (username, token) VALUES ($1, $2)")
.bind(username.to_string())
.bind(auth_token.clone())
.execute(&self.pool)
.await?;
Ok(AuthToken::from(auth_token))
}
async fn list_users(&self) -> Result<Vec<Username>, AuthError> {
let usernames = sqlx::query_as::<_, Username>("SELECT (username) FROM users")
.fetch_all(&self.pool)
.await?;
/*
let usernames = result
.into_iter()
.map(|row| Username::from(row.column(0)))
.collect::<Vec<Username>>();
*/
Ok(usernames)
}
async fn auth_token(&self, _token: AuthToken) -> Result<SessionToken, AuthError> { async fn auth_token(&self, _token: AuthToken) -> Result<SessionToken, AuthError> {
unimplemented!() unimplemented!()
} }
async fn auth_session(&self, _token: SessionToken) -> Result<Username, AuthError> { async fn auth_session(&self, _token: SessionToken) -> Result<Username, AuthError> {
/*
let conn = self.pool.acquire().await.map_err(|_| AuthError::SqlError)?;
conn.transaction(|tr| {})
*/
unimplemented!() unimplemented!()
} }
} }
@ -417,3 +464,34 @@ mod test {
}); });
} }
} }
#[cfg(test)]
mod authdb_test {
use super::*;
use cool_asserts::assert_matches;
#[tokio::test]
async fn can_create_and_list_users() {
let db = AuthDB::new(PathBuf::from(":memory:"))
.await
.expect("a memory-only database will be created");
let _ = db
.add_user(Username::from("savanni"))
.await
.expect("user to be created");
assert_matches!(db.list_users().await, Ok(names) => {
let names = names.into_iter().collect::<HashSet<Username>>();
assert!(names.contains(&Username::from("savanni")));
})
}
#[test]
fn can_authenticate_token() {
unimplemented!()
}
#[test]
fn can_validate_session_token() {
unimplemented!()
}
}