diff --git a/Cargo.lock b/Cargo.lock index 5e77895..6ab46e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "axum-extra" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" +dependencies = [ + "axum", + "axum-core", + "bytes", + "cookie", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -220,6 +243,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -272,6 +306,15 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "deranged" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" +dependencies = [ + "powerfmt", +] + [[package]] name = "derive_arbitrary" version = "1.4.1" @@ -807,6 +850,7 @@ name = "kawari" version = "0.1.0" dependencies = [ "axum", + "axum-extra", "binrw", "bitflags 1.3.2", "md5", @@ -1024,6 +1068,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -1151,6 +1201,12 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1633,6 +1689,37 @@ dependencies = [ "once_cell", ] +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.7.6" @@ -1813,6 +1900,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index cf38549..65001e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ serde_json = { version = "1.0", features = ["std"], default-features = false } [dependencies] # Used for the web servers axum = { version = "0.8", features = ["json", "tokio", "http1", "form", "query"], default-features = false } +axum-extra = { version = "0.10", features = ["cookie"], default-features = false } # Serialization used in almost every server serde = { version = "1.0", features = ["derive"], default-features = false } diff --git a/src/bin/kawari-login.rs b/src/bin/kawari-login.rs index c6d271d..faa36aa 100644 --- a/src/bin/kawari-login.rs +++ b/src/bin/kawari-login.rs @@ -4,10 +4,28 @@ use axum::extract::{Query, State}; use axum::response::{Html, Redirect}; use axum::routing::post; use axum::{Form, Router, routing::get}; +use axum_extra::extract::CookieJar; +use axum_extra::extract::cookie::{Cookie, Expiration}; use kawari::config::get_config; use kawari::login::{LoginDatabase, LoginError}; +use minijinja::{Environment, context}; use serde::Deserialize; +fn setup_default_environment() -> Environment<'static> { + let mut env = Environment::new(); + env.add_template("login.html", include_str!("../../templates/login.html")) + .unwrap(); + env.add_template( + "register.html", + include_str!("../../templates/register.html"), + ) + .unwrap(); + env.add_template("account.html", include_str!("../../templates/account.html")) + .unwrap(); + + env +} + #[derive(Clone)] struct LoginServerState { database: Arc, @@ -109,6 +127,72 @@ async fn check_session( serde_json::to_string(&accounts).unwrap_or(String::new()) } +async fn login() -> Html { + let environment = setup_default_environment(); + let template = environment.get_template("login.html").unwrap(); + Html(template.render(context! {}).unwrap()) +} + +async fn register() -> Html { + let environment = setup_default_environment(); + let template = environment.get_template("register.html").unwrap(); + Html(template.render(context! {}).unwrap()) +} + +#[derive(Deserialize, Debug)] +#[allow(dead_code)] +struct LoginInput { + username: Option, + password: Option, +} + +async fn do_login( + State(state): State, + jar: CookieJar, + Form(input): Form, +) -> (CookieJar, Redirect) { + tracing::info!("{:#?} logging in!", input.username,); + + let Some(username) = input.username else { + panic!("Expected username!"); + }; + let Some(password) = input.password else { + panic!("Expected password!"); + }; + + let sid = state.database.login_user(&username, &password).unwrap(); + + let cookie = Cookie::build(("cis_sessid", sid)) + .path("/") + .secure(false) + .expires(Expiration::Session) + .http_only(true); + + (jar.add(cookie), Redirect::to("/account/app/svc/manage")) +} + +async fn account(State(state): State, jar: CookieJar) -> Html { + if let Some(session_id) = jar.get("cis_sessid") { + let user_id = state.database.get_user_id(session_id.value()); + let username = state.database.get_username(user_id); + + let environment = setup_default_environment(); + let template = environment.get_template("account.html").unwrap(); + Html(template.render(context! { username => username }).unwrap()) + } else { + Html("You need to be logged in!".to_string()) + } +} + +async fn logout(State(state): State, jar: CookieJar) -> (CookieJar, Redirect) { + let config = get_config(); + // TODO: remove session from database + ( + jar.remove("cis_sessid"), + Redirect::to(&format!("http://{}/", config.web.server_name)), + ) +} + #[tokio::main] async fn main() { tracing_subscriber::fmt::init(); @@ -118,11 +202,19 @@ async fn main() { }; let app = Router::new() + // retail API .route("/oauth/ffxivarr/login/top", get(top)) .route("/oauth/ffxivarr/login/login.send", post(login_send)) - .route("/register", post(do_register)) + // private server<->server API // TODO: make these actually private .route("/_private/service_accounts", get(check_session)) + // public website + .route("/oauth/oa/oauthlogin", get(login)) + .route("/oauth/oa/oauthlogin", post(do_login)) + .route("/oauth/oa/registligt", get(register)) + .route("/oauth/oa/registlist", post(do_register)) + .route("/account/app/svc/manage", get(account)) + .route("/account/app/svc/logout", get(logout)) .with_state(state); let config = get_config(); diff --git a/src/bin/kawari-web.rs b/src/bin/kawari-web.rs index 1cc3c85..fc8d5f4 100644 --- a/src/bin/kawari-web.rs +++ b/src/bin/kawari-web.rs @@ -9,18 +9,13 @@ fn setup_default_environment() -> Environment<'static> { let mut env = Environment::new(); env.add_template("web.html", include_str!("../../templates/web.html")) .unwrap(); - env.add_template("login.html", include_str!("../../templates/login.html")) - .unwrap(); - env.add_template( - "register.html", - include_str!("../../templates/register.html"), - ) - .unwrap(); env.add_template( "worldstatus.html", include_str!("../../templates/worldstatus.html"), ) .unwrap(); + env.add_template("account.html", include_str!("../../templates/account.html")) + .unwrap(); env } @@ -31,21 +26,15 @@ struct GateStatus { } async fn root() -> Html { + let config = get_config(); + let environment = setup_default_environment(); let template = environment.get_template("web.html").unwrap(); - Html(template.render(context! {}).unwrap()) -} - -async fn login() -> Html { - let environment = setup_default_environment(); - let template = environment.get_template("login.html").unwrap(); - Html(template.render(context! {}).unwrap()) -} - -async fn register() -> Html { - let environment = setup_default_environment(); - let template = environment.get_template("register.html").unwrap(); - Html(template.render(context! {}).unwrap()) + Html( + template + .render(context! { login_server => config.login.server_name }) + .unwrap(), + ) } async fn world_status() -> Html { @@ -66,8 +55,6 @@ async fn main() { let app = Router::new() .route("/", get(root)) - .route("/login", get(login)) - .route("/register", get(register)) .route("/worldstatus", get(world_status)); let config = get_config(); diff --git a/src/config.rs b/src/config.rs index 9318ab4..0d4bb3a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -92,6 +92,8 @@ impl LobbyConfig { pub struct LoginConfig { pub port: u16, pub listen_address: String, + /// Public-facing domain of the server. + pub server_name: String, } impl Default for LoginConfig { @@ -99,6 +101,7 @@ impl Default for LoginConfig { Self { port: 6700, listen_address: "127.0.0.1".to_string(), + server_name: "ffxiv-login.square.localhost".to_string(), } } } @@ -157,6 +160,8 @@ impl PatchConfig { pub struct WebConfig { pub port: u16, pub listen_address: String, + /// Public-facing domain of the server. + pub server_name: String, } impl Default for WebConfig { @@ -164,6 +169,7 @@ impl Default for WebConfig { Self { port: 5801, listen_address: "127.0.0.1".to_string(), + server_name: "ffxiv.localhost".to_string(), } } } diff --git a/src/login/database.rs b/src/login/database.rs index 06a10a9..426b5ec 100644 --- a/src/login/database.rs +++ b/src/login/database.rs @@ -10,6 +10,7 @@ pub struct LoginDatabase { connection: Mutex, } +#[derive(Debug)] pub enum LoginError { WrongUsername, WrongPassword, @@ -190,4 +191,24 @@ impl LoginDatabase { selected_row.is_ok() } + + pub fn get_user_id(&self, sid: &str) -> u32 { + let connection = self.connection.lock().unwrap(); + + let mut stmt = connection + .prepare("SELECT user_id FROM sessions WHERE sid = ?1") + .ok() + .unwrap(); + stmt.query_row((sid,), |row| Ok(row.get(0)?)).unwrap() + } + + pub fn get_username(&self, user_id: u32) -> String { + let connection = self.connection.lock().unwrap(); + + let mut stmt = connection + .prepare("SELECT username FROM users WHERE id = ?1") + .ok() + .unwrap(); + stmt.query_row((user_id,), |row| Ok(row.get(0)?)).unwrap() + } } diff --git a/templates/account.html b/templates/account.html new file mode 100644 index 0000000..17a5ef5 --- /dev/null +++ b/templates/account.html @@ -0,0 +1,2 @@ +

Managing account {{ username }}

+Logout diff --git a/templates/register.html b/templates/register.html index d102be5..229ad5a 100644 --- a/templates/register.html +++ b/templates/register.html @@ -6,7 +6,7 @@ -
+


diff --git a/templates/web.html b/templates/web.html index 034b845..48d1868 100644 --- a/templates/web.html +++ b/templates/web.html @@ -8,9 +8,9 @@

Welcome to Kawari!

-Login -Signup +Login +Signup World Status - \ No newline at end of file +