Test user authentication

This commit is contained in:
Savanni D'Gerinel 2024-12-31 12:56:05 -05:00
parent a33b94e5b3
commit a0f1a0b81c
7 changed files with 110 additions and 19 deletions

View File

@ -35,3 +35,4 @@ CREATE TABLE roles(
FOREIGN KEY(game_id) REFERENCES games(uuid)
);
INSERT INTO users VALUES ('admin', 'admin', '', true, true);

View File

@ -11,7 +11,7 @@ use uuid::Uuid;
use crate::{
asset_db::{self, AssetId, Assets},
database::{CharacterId, Database, SessionId, UserId},
types::{AppError, FatalError, Game, Message, Tabletop, User, Rgb},
types::{AppError, FatalError, Game, Message, Rgb, Tabletop, User},
};
const DEFAULT_BACKGROUND_COLOR: Rgb = Rgb {
@ -154,9 +154,7 @@ impl Core {
asset_db::Error::Inaccessible => {
AppError::Inaccessible(format!("{}", asset_id))
}
asset_db::Error::Unexpected(err) => {
AppError::Inaccessible(format!("{}", err))
}
asset_db::Error::Unexpected(err) => AppError::Inaccessible(format!("{}", err)),
}),
)
}
@ -212,6 +210,25 @@ impl Core {
});
}
pub async fn save_user(
&self,
uuid: Option<UserId>,
username: &str,
password: &str,
admin: bool,
enabled: bool,
) -> ResultExt<UserId, AppError, FatalError> {
let state = self.0.read().await;
match state
.db
.save_user(uuid, username, password, admin, enabled)
.await
{
Ok(uuid) => ok(uuid),
Err(err) => fatal(err),
}
}
pub async fn set_password(
&self,
uuid: UserId,
@ -290,7 +307,7 @@ mod test {
]);
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
conn.save_user(None, "admin", "aoeu", true, true)
conn.save_user(Some(UserId::from("admin")), "admin", "aoeu", true, true)
.await
.unwrap();
conn.save_user(None, "gm_1", "aoeu", false, true)

View File

@ -100,7 +100,7 @@ impl DiskDb {
) -> Result<UserId, FatalError> {
match user_id {
None => {
let user_id = UserId::new();
let user_id = UserId::default();
let mut stmt = self
.conn
.prepare("INSERT INTO users VALUES (?, ?, ?, ?, ?)")

View File

@ -173,7 +173,7 @@ mod test {
let no_path: Option<PathBuf> = None;
let db = DiskDb::new(no_path).unwrap();
db.save_user(None, "admin", "abcdefg", true, true).unwrap();
db.save_user(Some(UserId::from("admin")), "admin", "abcdefg", true, true).unwrap();
let game_id = db.save_game(None, "Candela").unwrap();
(db, game_id)
}

View File

@ -6,15 +6,17 @@ use uuid::Uuid;
pub struct UserId(String);
impl UserId {
pub fn new() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for UserId {
fn default() -> Self {
Self(format!("{}", Uuid::new_v4().hyphenated()))
}
}
impl From<&str> for UserId {
fn from(s: &str) -> Self {
Self(s.to_owned())

View File

@ -32,8 +32,8 @@ pub async fn healthcheck(core: Core) -> Vec<u8> {
#[derive(Clone, Debug, Deserialize, Serialize)]
#[typeshare]
pub struct AuthRequest {
username: String,
password: String,
pub username: String,
pub password: String,
}
pub async fn check_password(core: Core, req: Json<AuthRequest>) -> (StatusCode, Json<Option<SessionId>>) {

View File

@ -1,4 +1,7 @@
use axum::{routing::{get, post}, Json, Router};
use axum::{
routing::{get, post},
Json, Router,
};
use crate::{
core::Core,
@ -27,12 +30,19 @@ pub fn routes(core: Core) -> Router {
mod test {
use std::path::PathBuf;
use axum::http::StatusCode;
use axum_test::TestServer;
use cool_asserts::assert_matches;
use result_extended::ResultExt;
use crate::{asset_db::FsAssets, core::Core, database::DbConn};
use super::*;
use crate::{
asset_db::FsAssets,
core::Core,
database::{Database, DbConn, SessionId, UserId},
};
fn setup() -> (Core, TestServer) {
fn setup_without_admin() -> (Core, TestServer) {
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
let core = Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
@ -41,14 +51,75 @@ mod test {
(core, server)
}
async fn setup_admin_enabled() -> (Core, TestServer) {
let memory_db: Option<PathBuf> = None;
let conn = DbConn::new(memory_db);
conn.save_user(Some(UserId::from("admin")), "admin", "aoeu", true, true)
.await
.unwrap();
let core = Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
let app = routes(core.clone());
let server = TestServer::new(app).unwrap();
(core, server)
}
#[tokio::test]
async fn it_returns_a_healthcheck() {
let (_core, server) = setup();
let (core, server) = setup_without_admin();
let response = server.get("/api/v1/health").await;
response.assert_status_ok();
let b: crate::handlers::HealthCheck = response.json();
assert_eq!(b, crate::handlers::HealthCheck { ok: false });
assert_matches!(
core.save_user(Some(UserId::from("admin")), "admin", "aoeu", true, true)
.await,
ResultExt::Ok(_)
);
let response = server.get("/api/v1/health").await;
response.assert_status_ok();
let b: crate::handlers::HealthCheck = response.json();
assert_eq!(b, crate::handlers::HealthCheck { ok: true });
}
#[tokio::test]
async fn it_authenticates_a_user() {
let (_core, server) = setup_admin_enabled().await;
let response = server
.post("/api/v1/auth")
.json(&AuthRequest {
username: "admin".to_owned(),
password: "wrong".to_owned(),
})
.await;
response.assert_status(StatusCode::UNAUTHORIZED);
let response = server
.post("/api/v1/auth")
.json(&AuthRequest {
username: "unknown".to_owned(),
password: "wrong".to_owned(),
})
.await;
response.assert_status(StatusCode::UNAUTHORIZED);
let response = server
.post("/api/v1/auth")
.json(&AuthRequest {
username: "admin".to_owned(),
password: "aoeu".to_owned(),
})
.await;
response.assert_status_ok();
let session_id: Option<SessionId> = response.json();
assert!(session_id.is_some());
}
#[tokio::test]
async fn it_returns_user_profile() {
unimplemented!();
}
}