From bd23c04848af80ef1c47538c82ecbe529ee7bd60 Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sun, 16 Mar 2025 20:55:51 -0400 Subject: [PATCH] Add a basic login system This allows you to register in the web frontend now, and the login server now checks this before giving a session ID. Note that this is wildly insecure (it stores the passwords in plaintext!) and is duly noted in the USAGE. --- Cargo.lock | 105 ++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + USAGE.md | 4 +- src/bin/kawari-login.rs | 120 +++++++++++++++++++++++++++++++++++++--- src/bin/kawari-web.rs | 23 +------- templates/register.html | 4 +- 6 files changed, 224 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 156e713..0a35705 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -42,7 +42,7 @@ checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" dependencies = [ "async-trait", "axum-core", - "bitflags", + "bitflags 1.3.2", "bytes", "futures-util", "headers", @@ -134,6 +134,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" + [[package]] name = "block-buffer" version = "0.10.4" @@ -155,6 +161,15 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +[[package]] +name = "cc" +version = "1.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" +dependencies = [ + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -202,12 +217,30 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -287,6 +320,24 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + [[package]] name = "headers" version = "0.3.9" @@ -384,6 +435,7 @@ dependencies = [ "minijinja", "physis", "rand", + "rusqlite", "serde", "serde_json", "tokio", @@ -403,6 +455,17 @@ version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +[[package]] +name = "libsqlite3-sys" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb8270bb4060bd76c6e96f20c52d80620f1d82a3470885694e41e0f81ef6fe7" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + [[package]] name = "libz-rs-sys" version = "0.4.2" @@ -492,7 +555,7 @@ version = "0.4.0" source = "git+https://github.com/redstrate/physis#d3918c13824f5cab6c7c41255437b69c4fb17ee2" dependencies = [ "binrw", - "bitflags", + "bitflags 1.3.2", "half", "libz-rs-sys", "tracing", @@ -530,6 +593,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -587,6 +656,20 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rusqlite" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e34486da88d8e051c7c0e23c3f15fd806ea8546260aa2fec247e97242ec143" +dependencies = [ + "bitflags 2.9.0", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -679,6 +762,18 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + [[package]] name = "socket2" version = "0.5.8" @@ -829,6 +924,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 6cdbd84..92fce40 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,3 +65,6 @@ md5 = "0.7.0" # Used to access game data physis = { git = "https://github.com/redstrate/physis" } + +# Used for data persistence +rusqlite = { version = "0.34.0", features = ["bundled"] } diff --git a/USAGE.md b/USAGE.md index b14a148..46cd0ad 100644 --- a/USAGE.md +++ b/USAGE.md @@ -1,7 +1,9 @@ -# USage +# Usage Kawari is designed to be easy to run, with the goal of being accessible to anyone who wants to run a local server for themselves. +**Note:** Persisted data (logins, characters, etc) is _not_ stable or secure. Treat all data as disposable. + ## Copyright Notice **Kawari requires that you have an active subscription**, and are in possession of a legitimate copy of the game. Kawari is not related or affiliated to SqEx, and by using it you are in explicit violation of the User Agreement (_Limitation 2.4_.) diff --git a/src/bin/kawari-login.rs b/src/bin/kawari-login.rs index 75e219b..52cd823 100644 --- a/src/bin/kawari-login.rs +++ b/src/bin/kawari-login.rs @@ -1,12 +1,56 @@ use std::net::SocketAddr; +use std::sync::{Arc, Mutex}; -use axum::extract::Query; -use axum::response::Html; +use axum::extract::{Query, State}; +use axum::response::{Html, Redirect}; use axum::routing::post; use axum::{Form, Router, routing::get}; use kawari::generate_sid; +use rusqlite::Connection; use serde::Deserialize; +pub enum LoginError { + WrongUsername, + WrongPassword, +} + +#[derive(Clone)] +struct LoginServerState { + connection: Arc>, +} + +impl LoginServerState { + /// Adds a new user to the database. + fn add_user(&self, username: &str, password: &str) { + let connection = self.connection.lock().unwrap(); + + let query = "INSERT INTO users VALUES (?1, ?2);"; + connection + .execute(query, (username, password)) + .expect("Failed to write user to database!"); + } + + /// Login as user, returns a session id. + fn login_user(&self, username: &str, password: &str) -> Result { + let connection = self.connection.lock().unwrap(); + + let mut stmt = connection + .prepare("SELECT username, password FROM users WHERE username = ?1") + .map_err(|_err| LoginError::WrongUsername)?; + let selected_row: Result<(String, String), rusqlite::Error> = + stmt.query_row((username,), |row| Ok((row.get(0)?, row.get(1)?))); + if let Ok((_user, their_password)) = selected_row { + if their_password == password { + return Ok(generate_sid()); + } else { + return Err(LoginError::WrongPassword); + } + } + + Err(LoginError::WrongUsername) + } +} + #[derive(Deserialize)] #[allow(dead_code)] struct Params { @@ -33,20 +77,80 @@ struct Input { otppw: String, } -async fn login_send(Form(_): Form) -> Html { - let sid = generate_sid(); - Html(format!( - "window.external.user(\"login=auth,ok,sid,{sid},terms,1,region,2,etmadd,0,playable,1,ps3pkg,0,maxex,4,product,1\");" - )) +async fn login_send( + State(state): State, + Form(input): Form, +) -> Html { + let user = state.login_user(&input.sqexid, &input.password); + match user { + Ok(session_id) => Html(format!( + "window.external.user(\"login=auth,ok,sid,{session_id},terms,1,region,2,etmadd,0,playable,1,ps3pkg,0,maxex,4,product,1\");" + )), + Err(err) => { + // TODO: see what the official error messages are + match err { + LoginError::WrongUsername => Html(format!( + "window.external.user(\"login=auth,ng,err,Wrong Username\");" + )), + LoginError::WrongPassword => Html(format!( + "window.external.user(\"login=auth,ng,err,Wrong Password\");" + )), + } + } + } +} + +#[derive(Deserialize, Debug)] +#[allow(dead_code)] +struct RegisterInput { + username: Option, + password: Option, +} + +async fn do_register( + State(state): State, + Form(input): Form, +) -> Redirect { + tracing::info!( + "Registering with {:#?} and {:#?}!", + input.username, + input.password + ); + + let Some(username) = input.username else { + panic!("Expected username!"); + }; + let Some(password) = input.password else { + panic!("Expected password!"); + }; + + state.add_user(&username, &password); + + Redirect::to("/") +} + +fn setup_state() -> LoginServerState { + let connection = Connection::open_in_memory().expect("Failed to open database!"); + + let query = "CREATE TABLE users (username TEXT, password TEXT);"; + connection.execute(query, ()).unwrap(); + + LoginServerState { + connection: Arc::new(Mutex::new(connection)), + } } #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); + let state = setup_state(); + let app = Router::new() .route("/oauth/ffxivarr/login/top", get(top)) - .route("/oauth/ffxivarr/login/login.send", post(login_send)); + .route("/oauth/ffxivarr/login/login.send", post(login_send)) + .route("/register", post(do_register)) + .with_state(state); let addr = SocketAddr::from(([127, 0, 0, 1], 6700)); tracing::info!("Login server started on {}", addr); diff --git a/src/bin/kawari-web.rs b/src/bin/kawari-web.rs index 63531c0..505ccb8 100644 --- a/src/bin/kawari-web.rs +++ b/src/bin/kawari-web.rs @@ -43,26 +43,6 @@ async fn world_status() -> Html { ) } -#[derive(Deserialize, Debug)] -#[allow(dead_code)] -struct Input { - login_open: Option, - worlds_open: Option, -} - -async fn apply(Form(input): Form) -> Redirect { - tracing::info!("Apply config changes..."); - - let mut config = get_config(); - config.login_open = input.login_open == Some("1".to_string()); - config.worlds_open = input.worlds_open == Some("1".to_string()); - - serde_json::to_writer(&std::fs::File::create("config.json").unwrap(), &config) - .expect("TODO: panic message"); - - Redirect::to("/") -} - #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); @@ -71,8 +51,7 @@ async fn main() { .route("/", get(root)) .route("/login", get(login)) .route("/register", get(register)) - .route("/worldstatus", get(world_status)) - .route("/apply", post(apply)); + .route("/worldstatus", get(world_status)); let addr = SocketAddr::from(([127, 0, 0, 1], 5801)); tracing::info!("Web server started on {}", addr); diff --git a/templates/register.html b/templates/register.html index 81e8b9e..f628cf8 100644 --- a/templates/register.html +++ b/templates/register.html @@ -6,7 +6,7 @@ -
+


@@ -15,4 +15,4 @@
- \ No newline at end of file +