Check username and password
This commit is contained in:
parent
2a616ef6c9
commit
e62ff9aa7a
59
Cargo.lock
generated
59
Cargo.lock
generated
@ -932,6 +932,19 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||
dependencies = [
|
||||
"humantime",
|
||||
"is-terminal",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
@ -1882,6 +1895,12 @@ version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.30"
|
||||
@ -2049,6 +2068,17 @@ version = "2.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
|
||||
|
||||
[[package]]
|
||||
name = "is-terminal"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b"
|
||||
dependencies = [
|
||||
"hermit-abi 0.4.0",
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
@ -2824,6 +2854,16 @@ dependencies = [
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
@ -3813,6 +3853,15 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.64"
|
||||
@ -4294,6 +4343,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"mime",
|
||||
"mime_guess",
|
||||
"pretty_env_logger",
|
||||
"result-extended",
|
||||
"rusqlite",
|
||||
"rusqlite_migration",
|
||||
@ -4476,6 +4526,15 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-util"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
|
@ -15,6 +15,7 @@ include_dir = { version = "0.7.4" }
|
||||
lazy_static = { version = "1.5.0" }
|
||||
mime = { version = "0.3.17" }
|
||||
mime_guess = { version = "2.0.5" }
|
||||
pretty_env_logger = "0.5.0"
|
||||
result-extended = { path = "../../result-extended" }
|
||||
rusqlite = { version = "0.32.1" }
|
||||
rusqlite_migration = { version = "1.3.1", features = ["from-directory"] }
|
||||
|
@ -220,6 +220,15 @@ impl Core {
|
||||
Err(err) => fatal(err),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn auth(&self, username: String, password: String) -> ResultExt<UserId, 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),
|
||||
Ok(_) => error(AppError::AuthFailed),
|
||||
Err(err) => fatal(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -26,6 +26,7 @@ enum Request {
|
||||
Charsheet(CharacterId),
|
||||
Games,
|
||||
User(UserId),
|
||||
UserByUsername(String),
|
||||
Users,
|
||||
SaveUser(Option<UserId>, String, String, bool, bool),
|
||||
}
|
||||
@ -180,6 +181,8 @@ pub struct CharsheetRow {
|
||||
pub trait Database: Send + Sync {
|
||||
async fn user(&mut self, _: UserId) -> Result<Option<UserRow>, FatalError>;
|
||||
|
||||
async fn user_by_username(&self, _: String) -> Result<Option<UserRow>, FatalError>;
|
||||
|
||||
async fn save_user(
|
||||
&mut self,
|
||||
user_id: Option<UserId>,
|
||||
@ -290,6 +293,31 @@ impl DiskDb {
|
||||
}
|
||||
}
|
||||
|
||||
fn user_by_username(&self, username: String) -> 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| {
|
||||
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(username.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
fn users(&self) -> Result<Vec<UserRow>, FatalError> {
|
||||
let mut stmt = self
|
||||
.conn
|
||||
@ -448,6 +476,13 @@ async fn db_handler(db: DiskDb, requestor: Receiver<DatabaseRequest>) {
|
||||
err => panic!("{:?}", err),
|
||||
}
|
||||
}
|
||||
Request::UserByUsername(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);
|
||||
match user_id {
|
||||
@ -514,6 +549,26 @@ impl Database for DbConn {
|
||||
}
|
||||
}
|
||||
|
||||
async fn user_by_username(&self, username: String) -> Result<Option<UserRow>, FatalError> {
|
||||
let (tx, rx) = bounded::<DatabaseResponse>(1);
|
||||
|
||||
let request = DatabaseRequest {
|
||||
tx,
|
||||
req: Request::UserByUsername(username),
|
||||
};
|
||||
|
||||
match self.conn.send(request).await {
|
||||
Ok(()) => (),
|
||||
Err(_) => return Err(FatalError::DatabaseConnectionLost),
|
||||
};
|
||||
|
||||
match rx.recv().await {
|
||||
Ok(DatabaseResponse::User(user)) => Ok(user),
|
||||
Ok(_) => Err(FatalError::MessageMismatch),
|
||||
Err(_) => Err(FatalError::DatabaseConnectionLost),
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_user(
|
||||
&mut self,
|
||||
user_id: Option<UserId>,
|
||||
|
@ -3,6 +3,7 @@ use std::future::Future;
|
||||
use futures::{SinkExt, StreamExt};
|
||||
use result_extended::{error, ok, return_error, ResultExt};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typeshare::typeshare;
|
||||
use warp::{http::Response, http::StatusCode, reply::Reply, ws::Message};
|
||||
|
||||
use crate::{
|
||||
@ -255,3 +256,24 @@ pub async fn handle_set_admin_password(core: Core, password: String) -> impl Rep
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[typeshare]
|
||||
pub struct AuthRequest {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
pub async fn handle_auth(core: Core, auth_request: AuthRequest) -> impl Reply {
|
||||
handler(async move {
|
||||
let userid = return_error!(core.auth(auth_request.username, auth_request.password).await);
|
||||
|
||||
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())
|
||||
.unwrap())
|
||||
}).await
|
||||
}
|
||||
|
@ -8,10 +8,14 @@ use asset_db::{AssetId, FsAssets};
|
||||
use authdb::AuthError;
|
||||
use database::DbConn;
|
||||
use handlers::{
|
||||
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
|
||||
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 warp::{
|
||||
// header,
|
||||
filters::{method, path},
|
||||
http::{Response, StatusCode},
|
||||
reply::Reply,
|
||||
Filter,
|
||||
@ -99,17 +103,16 @@ async fn handle_rejection(err: warp::Rejection) -> Result<impl Reply, Infallible
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let conn = DbConn::new(Some("/home/savanni/game.db"));
|
||||
|
||||
let core = core::Core::new(FsAssets::new(PathBuf::from("/home/savanni/Pictures")), conn);
|
||||
let log = warp::log("visions::api");
|
||||
|
||||
let route_server_status = warp::path!("api" / "v1" / "status")
|
||||
.and(warp::get())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move || handle_server_status(core.clone())
|
||||
});
|
||||
let route_server_status = warp::path!("api" / "v1" / "status").and(warp::get()).then({
|
||||
let core = core.clone();
|
||||
move || handle_server_status(core.clone())
|
||||
});
|
||||
|
||||
let route_image = warp::path!("api" / "v1" / "image" / String)
|
||||
.and(warp::get())
|
||||
@ -164,16 +167,13 @@ pub async fn main() {
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |body| handle_set_background_image(core.clone(), body)
|
||||
})
|
||||
.with(log);
|
||||
|
||||
let route_get_users = warp::path!("api" / "v1" / "users")
|
||||
.and(warp::get())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move || handle_get_users(core.clone())
|
||||
});
|
||||
|
||||
let route_get_users = warp::path!("api" / "v1" / "users").and(warp::get()).then({
|
||||
let core = core.clone();
|
||||
move || handle_get_users(core.clone())
|
||||
});
|
||||
|
||||
let route_get_charsheet = warp::path!("api" / "v1" / "charsheet" / String)
|
||||
.and(warp::get())
|
||||
.then({
|
||||
@ -202,6 +202,26 @@ pub async fn main() {
|
||||
move |body| handle_set_admin_password(core.clone(), body)
|
||||
});
|
||||
|
||||
let route_check_password_options = warp::path!("api" / "v1" / "auth")
|
||||
.and(warp::options())
|
||||
.map({
|
||||
move || {
|
||||
Response::builder()
|
||||
.header("Access-Control-Allow-Origin", "*")
|
||||
.header("Access-Control-Allow-Methods", "PUT")
|
||||
.header("Access-Control-Allow-Headers", "content-type")
|
||||
.body("")
|
||||
.unwrap()
|
||||
}
|
||||
});
|
||||
let route_check_password = warp::path!("api" / "v1" / "auth")
|
||||
.and(warp::put())
|
||||
.and(warp::body::json())
|
||||
.then({
|
||||
let core = core.clone();
|
||||
move |body| handle_auth(core.clone(), body)
|
||||
});
|
||||
|
||||
let filter = route_server_status
|
||||
.or(route_register_client)
|
||||
.or(route_unregister_client)
|
||||
@ -214,6 +234,9 @@ pub async fn main() {
|
||||
.or(route_get_charsheet)
|
||||
.or(route_set_admin_password_options)
|
||||
.or(route_set_admin_password)
|
||||
.or(route_check_password_options)
|
||||
.or(route_check_password)
|
||||
.with(warp::log("visions"))
|
||||
.recover(handle_rejection);
|
||||
|
||||
let server = warp::serve(filter);
|
||||
|
@ -36,6 +36,9 @@ pub enum AppError {
|
||||
#[error("the requested operation is not allowed")]
|
||||
PermissionDenied,
|
||||
|
||||
#[error("the requested username/password combination was not found")]
|
||||
AuthFailed,
|
||||
|
||||
#[error("invalid json {0}")]
|
||||
JsonError(serde_json::Error),
|
||||
|
||||
|
@ -36,7 +36,7 @@ const AuthedView = ({ client, children }: PropsWithChildren<AuthedViewProps>) =>
|
||||
return (
|
||||
<Authentication onAdminPassword={(password) => {
|
||||
manager.setAdminPassword(password);
|
||||
}} onAuth={(username, password) => console.log(username, password)}>
|
||||
}} onAuth={(username, password) => manager.auth(username, password)}>
|
||||
{children}
|
||||
</Authentication>
|
||||
);
|
||||
|
@ -64,6 +64,12 @@ export class Client {
|
||||
return fetch(url, { method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify(password) });
|
||||
}
|
||||
|
||||
async auth(username: string, password: string) {
|
||||
const url = new URL(this.base);
|
||||
url.pathname = `api/v1/auth`
|
||||
return fetch(url, { method: 'PUT', headers: [['Content-Type', 'application/json']], body: JSON.stringify({ 'username': username, 'password': password }) });
|
||||
}
|
||||
|
||||
async status() {
|
||||
const url = new URL(this.base);
|
||||
url.pathname = `/api/v1/status`;
|
||||
|
@ -49,6 +49,15 @@ class StateManager {
|
||||
await this.client.setAdminPassword(password);
|
||||
await this.status();
|
||||
}
|
||||
|
||||
async auth(username: string, password: string) {
|
||||
if (!this.client || !this.dispatch) return;
|
||||
|
||||
let resp = await this.client.auth(username, password);
|
||||
let userid = await resp.json();
|
||||
console.log("userid retrieved", userid);
|
||||
this.dispatch({ type: "SetAuthState", content: { type: "Authed", userid } });
|
||||
}
|
||||
}
|
||||
|
||||
export const StateContext = createContext<[AppState, StateManager]>([initialState(), new StateManager(undefined, undefined)]);
|
||||
|
Loading…
Reference in New Issue
Block a user