From 42b361c7bdd8a33312624b53f0233f4907cc1f74 Mon Sep 17 00:00:00 2001 From: core Date: Sun, 7 Jan 2024 03:03:51 -0500 Subject: [PATCH] client auth, and the alpha whitelist --- Cargo.lock | 11 +++++ savefile_decoder/src/main.rs | 2 +- server/Cargo.toml | 4 ++ server/src/component.rs | 2 +- server/src/main.rs | 75 +++++++++++++++++++++++++++-- server/src/packet.rs | 1 + starkingdoms-backplane/Cargo.toml | 3 +- starkingdoms-backplane/src/main.rs | 2 +- starkingdoms-client/src/hub.ts | 5 +- starkingdoms-client/src/protocol.ts | 1 + starkingdoms-common/src/lib.rs | 8 +-- 11 files changed, 101 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b819a1fdb5c8c52023238fc95419f63b6f9018b..434724268d03cd2cd96daba2621611ce061cadb1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2163,6 +2163,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hexasphere" version = "9.1.0" @@ -3562,6 +3568,7 @@ dependencies = [ "diesel-async", "diesel_migrations", "env_logger", + "hex", "hmac", "jwt", "log", @@ -3592,9 +3599,13 @@ dependencies = [ "bevy", "bevy_rapier2d", "bevy_twite", + "hex", + "hmac", + "jwt", "rand", "serde", "serde_json", + "sha2", "starkingdoms-common", "tracing-subscriber", ] diff --git a/savefile_decoder/src/main.rs b/savefile_decoder/src/main.rs index 19a9851110a9b9ef31c5340388e00b8f0b33bc01..ba4a6f22f06e2fd5021cc77e536c78297ae9a72e 100644 --- a/savefile_decoder/src/main.rs +++ b/savefile_decoder/src/main.rs @@ -4,7 +4,7 @@ use starkingdoms_common::unpack_savefile; fn main() { let save = std::env::args().nth(1).unwrap(); - let key = fs::read_to_string("/etc/starkingdoms/app_key").unwrap(); + let key = fs::read("/etc/starkingdoms/app_key").unwrap(); let save_data = unpack_savefile(&key, save).unwrap(); println!("{:#?}", save_data); } diff --git a/server/Cargo.toml b/server/Cargo.toml index b75335f06ad83355b14e41a471d641409a18eb0d..6788ba374de1fb8fa1547d1705f44fb5a0b2bbbb 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,6 +13,10 @@ bevy_rapier2d = "0.23.0" rand = "0.8.5" tracing-subscriber = "0.3" starkingdoms-common = { version = "0.1", path = "../starkingdoms-common" } +jwt = "0.16" +sha2 = "0.10" +hmac = "0.12" +hex = "0.4" [features] default = [] diff --git a/server/src/component.rs b/server/src/component.rs index 755da78d8e3504555bc0acf8b71482329313bce6..de4a5223c630857b036666a51f9961dca47173d6 100644 --- a/server/src/component.rs +++ b/server/src/component.rs @@ -120,5 +120,5 @@ impl Default for ModuleTimer { #[derive(Resource)] pub struct AppKeys { - pub app_key: String, + pub app_key: Vec, } diff --git a/server/src/main.rs b/server/src/main.rs index 2dbc4e371c606a08bf66e345b5ad56c652392ebe..744c2dabb5551ba32a5676295b34f56c543b195d 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -29,6 +29,11 @@ use rand::Rng; use starkingdoms_common::SaveModule; use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData}; use std::f32::consts::PI; +use serde::{Serialize, Deserialize}; +use std::time::SystemTime; +use hmac::{Hmac, Mac}; +use jwt::VerifyWithKey; +use sha2::Sha256; pub mod component; pub mod macros; @@ -56,7 +61,7 @@ const FREE_MODULE_CAP: usize = 30; fn main() { // read the key in - let key = std::fs::read_to_string("/etc/starkingdoms/app_key").unwrap(); + let key = std::fs::read("/etc/starkingdoms/app_key").unwrap(); App::new() .insert_resource(AppKeys { app_key: key }) @@ -212,6 +217,22 @@ fn module_spawn( } } +// permissions: +// 0 - regular user (unauthenticated is 0) +// 10 - private alpha +// 20 - supervisor +// 30 - dev + +const REQUIRED_PERMISSION_LEVEL: i32 = 10; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UserToken { + pub id: i64, + pub username: String, + pub permission_level: i32, + pub expires: SystemTime, +} + fn on_message( mut commands: Commands, planet_query: Query<(Entity, &PlanetType, &Transform)>, @@ -246,7 +267,7 @@ fn on_message( mut packet_recv: Local>, mut packet_event_send: ResMut>, app_keys: Res, -) { + ) { let mut event_queue = Vec::new(); for ev in packet_recv.read(&packet_event_send) { if let ServerEvent::Recv(addr, MessageType::Text, data) = ev { @@ -254,7 +275,55 @@ fn on_message( let packet: Packet = err_or_cont!(serde_json::from_str(&data)); match packet { - Packet::ClientLogin { username, save } => { + Packet::ClientLogin { username, save, jwt } => { + + if jwt.is_none() && REQUIRED_PERMISSION_LEVEL != 0 { + // d/c + let packet = Packet::Message { + message_type: packet::MessageType::Error, + actor: "SERVER".to_string(), + content: format!("Unauthorized - permission level of at least {REQUIRED_PERMISSION_LEVEL} required, but you are not logged in"), + }; + + let buf = serde_json::to_vec(&packet).unwrap(); + event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf)); + event_queue.push(ServerEvent::Close(*addr)); + continue; + } + + if let Some(jwt) = jwt { + let key: Hmac = Hmac::new_from_slice(&app_keys.app_key).unwrap(); + let claims: UserToken = match jwt.verify_with_key(&key) { + Ok(claims) => claims, + Err(e) => { + // d/c + let packet = Packet::Message { + message_type: packet::MessageType::Error, + actor: "SERVER".to_string(), + content: format!("Unauthorized - invalid token - {e}"), + }; + + let buf = serde_json::to_vec(&packet).unwrap(); + event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf)); + event_queue.push(ServerEvent::Close(*addr)); + continue; + } + }; + if claims.permission_level < REQUIRED_PERMISSION_LEVEL { + // d/c + let packet = Packet::Message { + message_type: packet::MessageType::Error, + actor: "SERVER".to_string(), + content: format!("Unauthorized - permission level of at least {REQUIRED_PERMISSION_LEVEL} required, but you only have {}. If your permissions were recently updated, you'll need to log out and log back in again for the new permissions to take effect.", claims.permission_level), + }; + + let buf = serde_json::to_vec(&packet).unwrap(); + event_queue.push(ServerEvent::Send(*addr, MessageType::Text, buf)); + event_queue.push(ServerEvent::Close(*addr)); + continue; + } + } + if let Some(save) = save { // attempt to decode the savefile if let Ok(savefile) = unpack_savefile(&app_keys.app_key, save) { diff --git a/server/src/packet.rs b/server/src/packet.rs index 5611c03b1134c8ba43c6cbde0f3100871d4edba0..478c29bcc922003462e4ece1210bc07615ef93ec 100644 --- a/server/src/packet.rs +++ b/server/src/packet.rs @@ -81,6 +81,7 @@ pub enum Packet { ClientLogin { username: String, save: Option, + jwt: Option, }, SendMessage { target: Option, diff --git a/starkingdoms-backplane/Cargo.toml b/starkingdoms-backplane/Cargo.toml index 3b1135ec81db615fe5bda7c29bf41ab22f622e73..3087c624b47140a246c1b138f9b7bb15718715ac 100644 --- a/starkingdoms-backplane/Cargo.toml +++ b/starkingdoms-backplane/Cargo.toml @@ -26,4 +26,5 @@ password-hash = "0.5" rs-snowflake = "0.6" jwt = "0.16" sha2 = "0.10" -hmac = "0.12" \ No newline at end of file +hmac = "0.12" +hex = "0.4" \ No newline at end of file diff --git a/starkingdoms-backplane/src/main.rs b/starkingdoms-backplane/src/main.rs index 84bfbdbd42ef34fe05efe80e4431804e84becc1a..55be6dd292a1b3767f5d43c7bb91e1eaf4345f4b 100644 --- a/starkingdoms-backplane/src/main.rs +++ b/starkingdoms-backplane/src/main.rs @@ -142,7 +142,7 @@ async fn main() { } } - let key = Hmac::new_from_slice(config.server.application_key.as_bytes()).unwrap(); + let key = Hmac::new_from_slice(hex::decode(config.server.application_key).unwrap()).unwrap(); let stk_epoch = UNIX_EPOCH + Duration::from_secs(1616260136); let id_generator = SnowflakeIdGenerator::with_epoch( diff --git a/starkingdoms-client/src/hub.ts b/starkingdoms-client/src/hub.ts index 8ca5025ca8942be1f3f4dde883c16a4d995244fa..67c9408101c816eff723ef3eb1075ad066087a07 100644 --- a/starkingdoms-client/src/hub.ts +++ b/starkingdoms-client/src/hub.ts @@ -67,6 +67,7 @@ export async function hub_connect( c: { username, save: window.localStorage.getItem("save"), + jwt: window.localStorage.getItem("stk-token") }, }; sendPacket(client, packet); @@ -234,8 +235,8 @@ export async function hub_connect( hud.x = new_part.transform.x; hud.y = new_part.transform.y; - x_pos!.innerText = Math.round(new_part.transform.x).toString(); - y_pos!.innerText = Math.round(new_part.transform.y).toString(); + //x_pos!.innerText = Math.round(new_part.transform.x).toString(); + //y_pos!.innerText = Math.round(new_part.transform.y).toString(); } hud.next_poll--; } diff --git a/starkingdoms-client/src/protocol.ts b/starkingdoms-client/src/protocol.ts index 16768e735c455666306705db1e780f29f0570621..fc16c8c7edcd8de36ee30ef304936c41c54b89f9 100644 --- a/starkingdoms-client/src/protocol.ts +++ b/starkingdoms-client/src/protocol.ts @@ -36,6 +36,7 @@ export interface PartFlags { export interface ClientLoginPacket { username: string; save: string | null; + jwt: string | null; } export interface SpawnPlayerPacket { id: number; diff --git a/starkingdoms-common/src/lib.rs b/starkingdoms-common/src/lib.rs index 3145e511cf3fbeaa8ccaf8a24eeacb8f481b214e..a0d1934b8565b737e3409964b840fb360910be3f 100644 --- a/starkingdoms-common/src/lib.rs +++ b/starkingdoms-common/src/lib.rs @@ -55,8 +55,8 @@ pub struct Savefile { mac: Vec, } -pub fn pack_savefile(key: &str, save_data: SaveData) -> String { - let mut mac: Hmac = Hmac::new_from_slice(key.as_bytes()).unwrap(); +pub fn pack_savefile(key: &[u8], save_data: SaveData) -> String { + let mut mac: Hmac = Hmac::new_from_slice(key).unwrap(); let save_data_bytes = rmp_serde::to_vec(&save_data).unwrap(); mac.update(&save_data_bytes); @@ -71,13 +71,13 @@ pub fn pack_savefile(key: &str, save_data: SaveData) -> String { base64::engine::general_purpose::STANDARD.encode(final_bytes) } -pub fn unpack_savefile(key: &str, file: String) -> Result> { +pub fn unpack_savefile(key: &[u8], file: String) -> Result> { // << reverse! << let savefile_bytes = base64::engine::general_purpose::STANDARD.decode(file).map_err(|e| format!("error decoding b64: {e}"))?; let save_file: Savefile = rmp_serde::from_slice(&savefile_bytes).map_err(|e| format!("error decoding savefile wrapper: {e}"))?; - let mut mac: Hmac = Hmac::new_from_slice(key.as_bytes()).map_err(|e| format!("error loading hmac-sha256: {e}"))?; + let mut mac: Hmac = Hmac::new_from_slice(key).map_err(|e| format!("error loading hmac-sha256: {e}"))?; mac.update(&save_file.data_msgpack); mac.verify_slice(&save_file.mac).map_err(|e| format!("error verifying signature: {e}"))?;