mirror of
https://github.com/redstrate/Kawari.git
synced 2025-04-20 14:47:45 +00:00
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.
This commit is contained in:
parent
3140dad378
commit
bd23c04848
6 changed files with 224 additions and 35 deletions
105
Cargo.lock
generated
105
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -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"] }
|
||||
|
|
4
USAGE.md
4
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_.)
|
||||
|
|
|
@ -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<Mutex<Connection>>,
|
||||
}
|
||||
|
||||
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<String, LoginError> {
|
||||
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<Input>) -> Html<String> {
|
||||
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<LoginServerState>,
|
||||
Form(input): Form<Input>,
|
||||
) -> Html<String> {
|
||||
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<String>,
|
||||
password: Option<String>,
|
||||
}
|
||||
|
||||
async fn do_register(
|
||||
State(state): State<LoginServerState>,
|
||||
Form(input): Form<RegisterInput>,
|
||||
) -> 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);
|
||||
|
|
|
@ -43,26 +43,6 @@ async fn world_status() -> Html<String> {
|
|||
)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[allow(dead_code)]
|
||||
struct Input {
|
||||
login_open: Option<String>,
|
||||
worlds_open: Option<String>,
|
||||
}
|
||||
|
||||
async fn apply(Form(input): Form<Input>) -> 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);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</head>
|
||||
<body>
|
||||
|
||||
<form method='post'>
|
||||
<form action='http://ffxiv-login.square.local/register' method='post'>
|
||||
<label for="username">Username:</label><br>
|
||||
<input type='text' id='username' name='username'/><br>
|
||||
<label for="password">Password:</label><br>
|
||||
|
|
Loading…
Add table
Reference in a new issue