Add session creation and lookup
This commit is contained in:
parent
1d400ce38b
commit
d5f4b7cfa5
@ -1,11 +1,18 @@
|
||||
CREATE TABLE users(
|
||||
uuid TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
name TEXT UNIQUE,
|
||||
password TEXT,
|
||||
admin BOOLEAN,
|
||||
enabled BOOLEAN
|
||||
);
|
||||
|
||||
CREATE TABLE sessions(
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT,
|
||||
|
||||
FOREIGN KEY(user_id) REFERENCES users(uuid)
|
||||
);
|
||||
|
||||
CREATE TABLE games(
|
||||
uuid TEXT PRIMARY KEY,
|
||||
name TEXT
|
||||
@ -28,5 +35,3 @@ CREATE TABLE roles(
|
||||
FOREIGN KEY(game_id) REFERENCES games(uuid)
|
||||
);
|
||||
|
||||
INSERT INTO users VALUES ("admin", "admin", "", true, true);
|
||||
|
||||
|
@ -10,7 +10,7 @@ use uuid::Uuid;
|
||||
|
||||
use crate::{
|
||||
asset_db::{self, AssetId, Assets},
|
||||
database::{CharacterId, Database, UserId},
|
||||
database::{CharacterId, Database, SessionId, UserId},
|
||||
types::{AppError, FatalError, Game, Message, Tabletop, User, RGB},
|
||||
};
|
||||
|
||||
@ -61,7 +61,7 @@ impl Core {
|
||||
|
||||
pub async fn status(&self) -> ResultExt<Status, AppError, FatalError> {
|
||||
let mut state = self.0.write().await;
|
||||
let admin_user = return_error!(match state.db.user(UserId::from("admin")).await {
|
||||
let admin_user = return_error!(match state.db.user(&UserId::from("admin")).await {
|
||||
Ok(Some(admin_user)) => ok(admin_user),
|
||||
Ok(None) => {
|
||||
return ok(Status {
|
||||
@ -106,6 +106,15 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_by_username(&self, username: &str) -> ResultExt<Option<User>, AppError, FatalError> {
|
||||
let state = self.0.read().await;
|
||||
match state.db.user_by_username(username).await {
|
||||
Ok(Some(user_row)) => ok(Some(User::from(user_row))),
|
||||
Ok(None) => ok(None),
|
||||
Err(err) => fatal(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn list_users(&self) -> ResultExt<Vec<User>, AppError, FatalError> {
|
||||
let users = self.0.write().await.db.users().await;
|
||||
match users {
|
||||
@ -206,7 +215,7 @@ impl Core {
|
||||
password: String,
|
||||
) -> ResultExt<(), AppError, FatalError> {
|
||||
let mut state = self.0.write().await;
|
||||
let user = match state.db.user(uuid.clone()).await {
|
||||
let user = match state.db.user(&uuid).await {
|
||||
Ok(Some(row)) => row,
|
||||
Ok(None) => return error(AppError::NotFound(uuid.as_str().to_owned())),
|
||||
Err(err) => return fatal(err),
|
||||
@ -221,10 +230,13 @@ impl Core {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn auth(&self, username: String, password: String) -> ResultExt<UserId, AppError, FatalError> {
|
||||
pub async fn auth(&self, username: &str, password: &str) -> ResultExt<SessionId, AppError, FatalError> {
|
||||
let state = self.0.write().await;
|
||||
match state.db.user_by_username(username).await {
|
||||
Ok(Some(row)) if (row.password == password) => ok(row.id),
|
||||
match state.db.user_by_username(&username).await {
|
||||
Ok(Some(row)) if (row.password == password) => {
|
||||
let session_id = state.db.create_session(row.id).await.unwrap();
|
||||
ok(session_id)
|
||||
}
|
||||
Ok(_) => error(AppError::AuthFailed),
|
||||
Err(err) => fatal(err),
|
||||
}
|
||||
@ -244,7 +256,7 @@ mod test {
|
||||
database::{DbConn, DiskDb},
|
||||
};
|
||||
|
||||
fn test_core() -> Core {
|
||||
async fn test_core() -> Core {
|
||||
let assets = MemoryAssets::new(vec![
|
||||
(
|
||||
AssetId::from("asset_1"),
|
||||
@ -274,19 +286,21 @@ mod test {
|
||||
]);
|
||||
let memory_db: Option<PathBuf> = None;
|
||||
let conn = DbConn::new(memory_db);
|
||||
conn.save_user(None, "admin", "aoeu", true, true).await.unwrap();
|
||||
conn.save_user(None, "gm_1", "aoeu", false, true).await.unwrap();
|
||||
Core::new(assets, conn)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_lists_available_images() {
|
||||
let core = test_core();
|
||||
let core = test_core().await;
|
||||
let image_paths = core.available_images().await;
|
||||
assert_eq!(image_paths.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_retrieves_an_asset() {
|
||||
let core = test_core();
|
||||
let core = test_core().await;
|
||||
assert_matches!(core.get_asset(AssetId::from("asset_1")).await, ResultExt::Ok((mime, data)) => {
|
||||
assert_eq!(mime.type_(), mime::IMAGE);
|
||||
assert_eq!(data, "abcdefg".as_bytes());
|
||||
@ -295,7 +309,7 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_can_retrieve_the_default_tabletop() {
|
||||
let core = test_core();
|
||||
let core = test_core().await;
|
||||
assert_matches!(core.tabletop().await, Tabletop{ background_color, background_image } => {
|
||||
assert_eq!(background_color, DEFAULT_BACKGROUND_COLOR);
|
||||
assert_eq!(background_image, None);
|
||||
@ -304,7 +318,7 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_can_change_the_tabletop_background() {
|
||||
let core = test_core();
|
||||
let core = test_core().await;
|
||||
assert_matches!(
|
||||
core.set_background_image(AssetId::from("asset_1")).await,
|
||||
ResultExt::Ok(())
|
||||
@ -317,7 +331,7 @@ mod test {
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_sends_notices_to_clients_on_tabletop_change() {
|
||||
let core = test_core();
|
||||
let core = test_core().await;
|
||||
let client_id = core.register_client().await;
|
||||
let mut receiver = core.connect_client(client_id).await;
|
||||
|
||||
@ -336,4 +350,21 @@ mod test {
|
||||
None => panic!("receiver did not get a message"),
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_creates_a_sessionid_on_successful_auth() {
|
||||
let core = test_core().await;
|
||||
match core.auth("admin", "aoeu").await {
|
||||
ResultExt::Ok(session_id) => {
|
||||
let st = core.0.read().await;
|
||||
match st.db.session(session_id).await {
|
||||
Ok(Some(user_row)) => assert_eq!(user_row.name, "admin"),
|
||||
Ok(None) => panic!("no matching user row for the session id"),
|
||||
Err(err) => panic!("{}", err),
|
||||
}
|
||||
},
|
||||
ResultExt::Err(err) => panic!("{}", err),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,11 +24,13 @@ lazy_static! {
|
||||
#[derive(Debug)]
|
||||
enum Request {
|
||||
Charsheet(CharacterId),
|
||||
CreateSession(UserId),
|
||||
Games,
|
||||
SaveUser(Option<UserId>, String, String, bool, bool),
|
||||
Session(SessionId),
|
||||
User(UserId),
|
||||
UserByUsername(String),
|
||||
Users,
|
||||
SaveUser(Option<UserId>, String, String, bool, bool),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -40,10 +42,12 @@ struct DatabaseRequest {
|
||||
#[derive(Debug)]
|
||||
enum DatabaseResponse {
|
||||
Charsheet(Option<CharsheetRow>),
|
||||
CreateSession(SessionId),
|
||||
Games(Vec<GameRow>),
|
||||
SaveUser(UserId),
|
||||
Session(Option<UserRow>),
|
||||
User(Option<UserRow>),
|
||||
Users(Vec<UserRow>),
|
||||
SaveUser(UserId),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
@ -80,6 +84,40 @@ impl FromSql for UserId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct SessionId(String);
|
||||
|
||||
impl SessionId {
|
||||
pub fn new() -> Self {
|
||||
Self(format!("{}", Uuid::new_v4().hyphenated()))
|
||||
}
|
||||
|
||||
pub fn as_str<'a>(&'a self) -> &'a str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for SessionId {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SessionId {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromSql for SessionId {
|
||||
fn column_result(value: ValueRef<'_>) -> FromSqlResult<Self> {
|
||||
match value {
|
||||
ValueRef::Text(text) => Ok(Self::from(String::from_utf8(text.to_vec()).unwrap())),
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct GameId(String);
|
||||
|
||||
@ -177,14 +215,20 @@ pub struct CharsheetRow {
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SessionRow {
|
||||
id: SessionId,
|
||||
user_id: SessionId,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait Database: Send + Sync {
|
||||
async fn user(&mut self, _: UserId) -> Result<Option<UserRow>, FatalError>;
|
||||
async fn user(&mut self, _: &UserId) -> Result<Option<UserRow>, FatalError>;
|
||||
|
||||
async fn user_by_username(&self, _: String) -> Result<Option<UserRow>, FatalError>;
|
||||
async fn user_by_username(&self, _: &str) -> Result<Option<UserRow>, FatalError>;
|
||||
|
||||
async fn save_user(
|
||||
&mut self,
|
||||
&self,
|
||||
user_id: Option<UserId>,
|
||||
name: &str,
|
||||
password: &str,
|
||||
@ -197,6 +241,10 @@ pub trait Database: Send + Sync {
|
||||
async fn games(&mut self) -> Result<Vec<GameRow>, FatalError>;
|
||||
|
||||
async fn character(&mut self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError>;
|
||||
|
||||
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError>;
|
||||
|
||||
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError>;
|
||||
}
|
||||
|
||||
pub struct DiskDb {
|
||||
@ -268,7 +316,7 @@ impl DiskDb {
|
||||
Ok(DiskDb { conn })
|
||||
}
|
||||
|
||||
fn user(&self, id: UserId) -> Result<Option<UserRow>, FatalError> {
|
||||
fn user(&self, id: &UserId) -> Result<Option<UserRow>, FatalError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE uuid=?")
|
||||
@ -293,13 +341,13 @@ impl DiskDb {
|
||||
}
|
||||
}
|
||||
|
||||
fn user_by_username(&self, username: String) -> Result<Option<UserRow>, FatalError> {
|
||||
fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("SELECT uuid, name, password, admin, enabled FROM users WHERE name=?")
|
||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||
let items: Vec<UserRow> = stmt
|
||||
.query_map([username.as_str()], |row| {
|
||||
.query_map([username], |row| {
|
||||
Ok(UserRow {
|
||||
id: row.get(0).unwrap(),
|
||||
name: row.get(1).unwrap(),
|
||||
@ -361,9 +409,7 @@ impl DiskDb {
|
||||
Some(user_id) => {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare(
|
||||
"UPDATE users SET name=?, password=?, admin=?, enabled=? WHERE uuid=?",
|
||||
)
|
||||
.prepare("UPDATE users SET name=?, password=?, admin=?, enabled=? WHERE uuid=?")
|
||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||
stmt.execute((name, password, admin, enabled, user_id.as_str()))
|
||||
.unwrap();
|
||||
@ -394,6 +440,51 @@ impl DiskDb {
|
||||
}
|
||||
}
|
||||
|
||||
fn session(&self, session_id: &SessionId) -> Result<Option<UserRow>, FatalError> {
|
||||
let mut stmt = self.conn
|
||||
.prepare("SELECT u.uuid, u.name, u.password, u.admin, u.enabled FROM sessions s INNER JOIN users u ON u.uuid = s.user_id WHERE s.id = ?")
|
||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||
|
||||
let items: Vec<UserRow> = stmt
|
||||
.query_map([session_id.as_str()], |row| {
|
||||
Ok(UserRow {
|
||||
id: row.get(0).unwrap(),
|
||||
name: row.get(1).unwrap(),
|
||||
password: row.get(2).unwrap(),
|
||||
admin: row.get(3).unwrap(),
|
||||
enabled: row.get(4).unwrap(),
|
||||
})
|
||||
})
|
||||
.unwrap()
|
||||
.collect::<Result<Vec<UserRow>, rusqlite::Error>>()
|
||||
.unwrap();
|
||||
match &items[..] {
|
||||
[] => Ok(None),
|
||||
[item] => Ok(Some(item.clone())),
|
||||
_ => Err(FatalError::NonUniqueDatabaseKey(
|
||||
session_id.as_str().to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
fn create_session(&self, user_id: &UserId) -> Result<SessionId, FatalError> {
|
||||
match self.user(user_id) {
|
||||
Ok(Some(_)) => {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
.prepare("INSERT INTO sessions VALUES (?, ?)")
|
||||
.map_err(|err| FatalError::ConstructQueryFailure(format!("{}", err)))?;
|
||||
|
||||
let session_id = SessionId::new();
|
||||
stmt.execute((session_id.as_str(), user_id.as_str()))
|
||||
.unwrap();
|
||||
Ok(session_id)
|
||||
}
|
||||
Ok(None) => Err(FatalError::DatabaseKeyMissing),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn character(&self, id: CharacterId) -> Result<Option<CharsheetRow>, FatalError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
@ -456,7 +547,6 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
||||
match req {
|
||||
Request::Charsheet(id) => {
|
||||
let sheet = db.character(id);
|
||||
println!("sheet retrieved: {:?}", sheet);
|
||||
match sheet {
|
||||
Ok(sheet) => {
|
||||
tx.send(DatabaseResponse::Charsheet(sheet)).await.unwrap();
|
||||
@ -464,11 +554,17 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
Request::CreateSession(id) => {
|
||||
let session_id = db.create_session(&id).unwrap();
|
||||
tx.send(DatabaseResponse::CreateSession(session_id))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
Request::Games => {
|
||||
unimplemented!();
|
||||
}
|
||||
Request::User(uid) => {
|
||||
let user = db.user(uid);
|
||||
let user = db.user(&uid);
|
||||
match user {
|
||||
Ok(user) => {
|
||||
tx.send(DatabaseResponse::User(user)).await.unwrap();
|
||||
@ -477,14 +573,20 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
||||
}
|
||||
}
|
||||
Request::UserByUsername(username) => {
|
||||
let user = db.user_by_username(username);
|
||||
let user = db.user_by_username(&username);
|
||||
match user {
|
||||
Ok(user) => tx.send(DatabaseResponse::User(user)).await.unwrap(),
|
||||
err => panic!("{:?}", err),
|
||||
}
|
||||
}
|
||||
Request::SaveUser(user_id, username, password, admin, enabled) => {
|
||||
let user_id = db.save_user(user_id, username.as_ref(), password.as_ref(), admin, enabled);
|
||||
let user_id = db.save_user(
|
||||
user_id,
|
||||
username.as_ref(),
|
||||
password.as_ref(),
|
||||
admin,
|
||||
enabled,
|
||||
);
|
||||
match user_id {
|
||||
Ok(user_id) => {
|
||||
tx.send(DatabaseResponse::SaveUser(user_id)).await.unwrap();
|
||||
@ -492,6 +594,13 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
||||
err => panic!("{:?}", err),
|
||||
}
|
||||
}
|
||||
Request::Session(session_id) => {
|
||||
let user = db.session(&session_id);
|
||||
match user {
|
||||
Ok(user) => tx.send(DatabaseResponse::Session(user)).await.unwrap(),
|
||||
err => panic!("{:?}", err),
|
||||
}
|
||||
}
|
||||
Request::Users => {
|
||||
let users = db.users();
|
||||
match users {
|
||||
@ -529,12 +638,12 @@ impl DbConn {
|
||||
|
||||
#[async_trait]
|
||||
impl Database for DbConn {
|
||||
async fn user(&mut self, uid: UserId) -> Result<Option<UserRow>, FatalError> {
|
||||
async fn user(&mut self, uid: &UserId) -> Result<Option<UserRow>, FatalError> {
|
||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||
|
||||
let request = DatabaseRequest {
|
||||
tx,
|
||||
req: Request::User(uid),
|
||||
req: Request::User(uid.clone()),
|
||||
};
|
||||
|
||||
match self.conn.send(request).await {
|
||||
@ -549,12 +658,12 @@ impl Database for DbConn {
|
||||
}
|
||||
}
|
||||
|
||||
async fn user_by_username(&self, username: String) -> Result<Option<UserRow>, FatalError> {
|
||||
async fn user_by_username(&self, username: &str) -> Result<Option<UserRow>, FatalError> {
|
||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||
|
||||
let request = DatabaseRequest {
|
||||
tx,
|
||||
req: Request::UserByUsername(username),
|
||||
req: Request::UserByUsername(username.to_owned()),
|
||||
};
|
||||
|
||||
match self.conn.send(request).await {
|
||||
@ -570,7 +679,7 @@ impl Database for DbConn {
|
||||
}
|
||||
|
||||
async fn save_user(
|
||||
&mut self,
|
||||
&self,
|
||||
user_id: Option<UserId>,
|
||||
name: &str,
|
||||
password: &str,
|
||||
@ -661,6 +770,46 @@ impl Database for DbConn {
|
||||
Err(_) => Err(FatalError::DatabaseConnectionLost),
|
||||
}
|
||||
}
|
||||
|
||||
async fn session(&self, id: SessionId) -> Result<Option<UserRow>, FatalError> {
|
||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||
|
||||
let request = DatabaseRequest {
|
||||
tx,
|
||||
req: Request::Session(id),
|
||||
};
|
||||
|
||||
match self.conn.send(request).await {
|
||||
Ok(()) => (),
|
||||
Err(_) => return Err(FatalError::DatabaseConnectionLost),
|
||||
};
|
||||
|
||||
match rx.recv().await {
|
||||
Ok(DatabaseResponse::Session(row)) => Ok(row),
|
||||
Ok(_) => Err(FatalError::MessageMismatch),
|
||||
Err(_) => Err(FatalError::DatabaseConnectionLost),
|
||||
}
|
||||
}
|
||||
|
||||
async fn create_session(&self, id: UserId) -> Result<SessionId, FatalError> {
|
||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||
|
||||
let request = DatabaseRequest {
|
||||
tx,
|
||||
req: Request::CreateSession(id),
|
||||
};
|
||||
|
||||
match self.conn.send(request).await {
|
||||
Ok(()) => (),
|
||||
Err(_) => return Err(FatalError::DatabaseConnectionLost),
|
||||
};
|
||||
|
||||
match rx.recv().await {
|
||||
Ok(DatabaseResponse::CreateSession(session_id)) => Ok(session_id),
|
||||
Ok(_) => Err(FatalError::MessageMismatch),
|
||||
Err(_) => Err(FatalError::DatabaseConnectionLost),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -668,7 +817,6 @@ mod test {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use cool_asserts::assert_matches;
|
||||
use result_extended::ResultExt;
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -678,7 +826,7 @@ mod test {
|
||||
let no_path: Option<PathBuf> = None;
|
||||
let db = DiskDb::new(no_path).unwrap();
|
||||
|
||||
db.save_user(None, "admin", "abcdefg", true, true);
|
||||
db.save_user(None, "admin", "abcdefg", true, true).unwrap();
|
||||
let game_id = db.save_game(None, "Candela").unwrap();
|
||||
(db, game_id)
|
||||
}
|
||||
@ -699,9 +847,6 @@ mod test {
|
||||
let memory_db: Option<PathBuf> = None;
|
||||
let mut conn = DbConn::new(memory_db);
|
||||
|
||||
assert_matches!(
|
||||
conn.character(CharacterId::from("1")).await,
|
||||
Ok(None)
|
||||
);
|
||||
assert_matches!(conn.character(CharacterId::from("1")).await, Ok(None));
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,11 @@ use warp::{
|
||||
};
|
||||
|
||||
use crate::{
|
||||
asset_db::AssetId, core::Core, handlers::{handle_auth, handle_available_images, handle_connect_websocket, handle_file, handle_get_charsheet, handle_get_users, handle_register_client, handle_server_status, handle_set_admin_password, handle_set_background_image, handle_unregister_client, RegisterRequest}
|
||||
asset_db::AssetId,
|
||||
core::Core,
|
||||
handlers::{
|
||||
handle_available_images, handle_check_password, handle_connect_websocket, handle_file, handle_get_charsheet, handle_register_client, handle_server_status, handle_set_background_image, handle_unregister_client, RegisterRequest
|
||||
},
|
||||
};
|
||||
|
||||
fn cors<H, M>(methods: Vec<M>, headers: Vec<H>) -> Builder
|
||||
@ -23,6 +27,13 @@ where
|
||||
.allow_headers(headers)
|
||||
}
|
||||
|
||||
pub fn route_healthcheck() -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone
|
||||
{
|
||||
warp::path!("api" / "v1" / "healthcheck")
|
||||
.and(warp::get())
|
||||
.map(|| warp::reply::reply())
|
||||
}
|
||||
|
||||
pub fn route_server_status(
|
||||
core: Core,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
@ -44,7 +55,6 @@ pub fn route_set_bg_image(
|
||||
.with(cors::<HeaderName, Method>(vec![Method::PUT], vec![]))
|
||||
}
|
||||
|
||||
|
||||
pub fn route_image(
|
||||
core: Core,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
@ -76,7 +86,6 @@ pub fn route_register_client(
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
pub fn route_unregister_client(
|
||||
core: Core,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
@ -111,7 +120,7 @@ pub fn route_get_charsheet(
|
||||
})
|
||||
}
|
||||
|
||||
pub fn route_check_password(
|
||||
pub fn route_authenticate(
|
||||
core: Core,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "v1" / "auth")
|
||||
@ -119,7 +128,7 @@ pub fn route_check_password(
|
||||
.and(warp::body::json())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |body| handle_auth(core.clone(), body)
|
||||
move |body| handle_check_password(core.clone(), body)
|
||||
})
|
||||
.with(cors::<HeaderName, Method>(vec![Method::PUT], vec![]))
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
use warp::{
|
||||
http::{header::CONTENT_TYPE, Method},
|
||||
http::{header::CONTENT_TYPE, HeaderName, Method},
|
||||
reply::Reply,
|
||||
Filter,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
core::Core,
|
||||
handlers::{handle_get_users, handle_set_admin_password},
|
||||
handlers::{handle_check_password, handle_get_users, handle_set_admin_password},
|
||||
};
|
||||
|
||||
use super::cors;
|
||||
@ -37,3 +37,71 @@ pub fn routes_user_management(
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
route_get_users(core.clone()).or(route_set_admin_password(core.clone()))
|
||||
}
|
||||
|
||||
pub fn route_check_password(
|
||||
core: Core,
|
||||
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
|
||||
warp::path!("api" / "v1" / "auth")
|
||||
.and(warp::put())
|
||||
.and(warp::body::json())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |body| handle_check_password(core.clone(), body)
|
||||
})
|
||||
.with(cors::<HeaderName, Method>(vec![Method::PUT], vec![]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use result_extended::ResultExt;
|
||||
|
||||
use crate::{asset_db::mocks::MemoryAssets, database::{Database, DbConn, UserId}};
|
||||
|
||||
use super::*;
|
||||
|
||||
async fn setup() -> Core {
|
||||
let asset_store = MemoryAssets::new(vec![]);
|
||||
let memory_file: Option<PathBuf> = None;
|
||||
let db = DbConn::new(memory_file);
|
||||
db.save_user(None, "admin", "", true, true).await.unwrap();
|
||||
Core::new(asset_store, db)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn handle_check_password_should_return_a_valid_token() {
|
||||
let core = setup().await;
|
||||
match core.list_users().await {
|
||||
ResultExt::Ok(users) => println!("{:?}", users),
|
||||
ResultExt::Err(err) => panic!("{}", err),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
}
|
||||
match core.user_by_username("admin").await {
|
||||
ResultExt::Ok(Some(user)) => {
|
||||
let _ = core.set_password(UserId::from(user.id), "aoeu".to_owned()).await;
|
||||
},
|
||||
ResultExt::Ok(None) => panic!("expected user wasn't found"),
|
||||
ResultExt::Err(err) => panic!("{}", err),
|
||||
ResultExt::Fatal(err) => panic!("{}", err),
|
||||
}
|
||||
let filter = route_check_password(core);
|
||||
let params: HashMap<String, String> = vec![
|
||||
("username".to_owned(), "admin".to_owned()),
|
||||
("password".to_owned(), "aoeu".to_owned()),
|
||||
]
|
||||
.into_iter()
|
||||
.collect();
|
||||
let resp = warp::test::request()
|
||||
.method("PUT")
|
||||
.path("/api/v1/auth")
|
||||
.json(¶ms)
|
||||
.reply(&filter)
|
||||
.await;
|
||||
|
||||
println!("response: {}", resp.status());
|
||||
assert!(resp.status().is_success());
|
||||
println!("resp.body(): {}", String::from_utf8(resp.body().to_vec()).unwrap());
|
||||
serde_json::from_slice::<String>(resp.body()).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -47,10 +47,13 @@ where
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(vec![])
|
||||
.unwrap(),
|
||||
ResultExt::Err(_) => Response::builder()
|
||||
ResultExt::Err(err) => {
|
||||
println!("request error: {:?}", err);
|
||||
Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(vec![])
|
||||
.unwrap(),
|
||||
.unwrap()
|
||||
}
|
||||
ResultExt::Fatal(err) => {
|
||||
panic!("Shutting down with fatal error: {:?}", err);
|
||||
}
|
||||
@ -262,35 +265,20 @@ pub async fn handle_set_admin_password(core: Core, password: String) -> impl Rep
|
||||
pub struct AuthRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
|
||||
}
|
||||
|
||||
pub async fn handle_auth(core: Core, auth_request: AuthRequest) -> impl Reply {
|
||||
pub async fn handle_check_password(core: Core, auth_request: AuthRequest) -> impl Reply {
|
||||
handler(async move {
|
||||
let userid = return_error!(core.auth(auth_request.username, auth_request.password).await);
|
||||
let session_id = return_error!(
|
||||
core.auth(&auth_request.username, &auth_request.password)
|
||||
.await
|
||||
);
|
||||
println!("handle_check_password: {:?}", session_id);
|
||||
|
||||
ok(Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Methods", "*")
|
||||
.header("Access-Control-Allow-Headers", "content-type")
|
||||
.header("Content-Type", "application/json")
|
||||
.body(serde_json::to_vec(&userid).unwrap())
|
||||
.body(serde_json::to_vec(&session_id).unwrap())
|
||||
.unwrap())
|
||||
}).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{asset_db::mocks::MemoryAssets, database::DbConn};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn setup() -> Core {
|
||||
let asset_store = MemoryAssets::new(vec![]);
|
||||
let memory_file: Option<PathBuf> = None;
|
||||
let db = DbConn::new(memory_file);
|
||||
Core::new(asset_store, db)
|
||||
}
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
@ -7,13 +7,7 @@ use std::{
|
||||
use asset_db::{AssetId, FsAssets};
|
||||
use authdb::AuthError;
|
||||
use database::DbConn;
|
||||
use filters::{route_available_images, route_check_password, route_get_charsheet, route_image, route_register_client, route_server_status, route_set_bg_image, route_unregister_client, route_websocket, routes_user_management};
|
||||
use handlers::{
|
||||
handle_auth, handle_available_images, handle_connect_websocket, handle_file,
|
||||
handle_get_charsheet, handle_get_users, handle_register_client, handle_server_status,
|
||||
handle_set_admin_password, handle_set_background_image, handle_unregister_client,
|
||||
RegisterRequest,
|
||||
};
|
||||
use filters::{route_authenticate, route_available_images, route_get_charsheet, route_healthcheck, route_image, route_register_client, route_server_status, route_set_bg_image, route_unregister_client, route_websocket, routes_user_management};
|
||||
use warp::{
|
||||
// header,
|
||||
filters::{method, path},
|
||||
@ -111,21 +105,13 @@ pub async fn main() {
|
||||
|
||||
let core = core::Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
|
||||
|
||||
let unauthenticated_endpoints = route_healthcheck().or(route_authenticate(core.clone()));
|
||||
let authenticated_endpoints = route_image(core.clone());
|
||||
|
||||
let filter = route_server_status(core.clone())
|
||||
.or(route_register_client(core.clone()))
|
||||
.or(route_unregister_client(core.clone()))
|
||||
.or(route_websocket(core.clone()))
|
||||
.or(route_image(core.clone()))
|
||||
.or(route_available_images(core.clone()))
|
||||
.or(route_set_bg_image(core.clone()))
|
||||
.or(routes_user_management(core.clone()))
|
||||
.or(route_get_charsheet(core.clone()))
|
||||
.or(route_check_password(core.clone()))
|
||||
let server = warp::serve(unauthenticated_endpoints
|
||||
.or(authenticated_endpoints)
|
||||
.with(warp::log("visions"))
|
||||
.recover(handle_rejection);
|
||||
|
||||
let server = warp::serve(filter);
|
||||
.recover(handle_rejection));
|
||||
server
|
||||
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8001))
|
||||
.await;
|
||||
|
@ -7,20 +7,23 @@ use crate::{asset_db::AssetId, database::UserRow};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum FatalError {
|
||||
#[error("Non-unique database key {0}")]
|
||||
NonUniqueDatabaseKey(String),
|
||||
|
||||
#[error("Database migrations failed {0}")]
|
||||
DatabaseMigrationFailure(String),
|
||||
|
||||
#[error("Failed to construct a query")]
|
||||
ConstructQueryFailure(String),
|
||||
|
||||
#[error("Database connection lost")]
|
||||
DatabaseConnectionLost,
|
||||
|
||||
#[error("Expected database key is missing")]
|
||||
DatabaseKeyMissing,
|
||||
|
||||
#[error("Database migrations failed {0}")]
|
||||
DatabaseMigrationFailure(String),
|
||||
|
||||
#[error("Unexpected response for message")]
|
||||
MessageMismatch,
|
||||
|
||||
#[error("Non-unique database key {0}")]
|
||||
NonUniqueDatabaseKey(String),
|
||||
}
|
||||
|
||||
impl result_extended::FatalError for FatalError {}
|
||||
|
Loading…
Reference in New Issue
Block a user