M Cargo.lock => Cargo.lock +11 -0
@@ 2164,6 2164,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 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",
]
M savefile_decoder/src/main.rs => savefile_decoder/src/main.rs +1 -1
@@ 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);
}
M server/Cargo.toml => server/Cargo.toml +4 -0
@@ 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 = []
M server/src/component.rs => server/src/component.rs +1 -1
@@ 120,5 120,5 @@ impl Default for ModuleTimer {
#[derive(Resource)]
pub struct AppKeys {
- pub app_key: String,
+ pub app_key: Vec<u8>,
}
M server/src/main.rs => server/src/main.rs +72 -3
@@ 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<ManualEventReader<ServerEvent>>,
mut packet_event_send: ResMut<Events<ServerEvent>>,
app_keys: Res<AppKeys>,
-) {
+ ) {
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<Sha256> = 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) {
M server/src/packet.rs => server/src/packet.rs +1 -0
@@ 81,6 81,7 @@ pub enum Packet {
ClientLogin {
username: String,
save: Option<String>,
+ jwt: Option<String>,
},
SendMessage {
target: Option<String>,
M starkingdoms-backplane/Cargo.toml => starkingdoms-backplane/Cargo.toml +2 -1
@@ 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
M starkingdoms-backplane/src/main.rs => starkingdoms-backplane/src/main.rs +1 -1
@@ 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(
M starkingdoms-client/src/hub.ts => starkingdoms-client/src/hub.ts +3 -2
@@ 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--;
}
M starkingdoms-client/src/protocol.ts => starkingdoms-client/src/protocol.ts +1 -0
@@ 36,6 36,7 @@ export interface PartFlags {
export interface ClientLoginPacket {
username: string;
save: string | null;
+ jwt: string | null;
}
export interface SpawnPlayerPacket {
id: number;
M starkingdoms-common/src/lib.rs => starkingdoms-common/src/lib.rs +4 -4
@@ 55,8 55,8 @@ pub struct Savefile {
mac: Vec<u8>,
}
-pub fn pack_savefile(key: &str, save_data: SaveData) -> String {
- let mut mac: Hmac<Sha256> = Hmac::new_from_slice(key.as_bytes()).unwrap();
+pub fn pack_savefile(key: &[u8], save_data: SaveData) -> String {
+ let mut mac: Hmac<Sha256> = 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<SaveData, Box<dyn Error>> {
+pub fn unpack_savefile(key: &[u8], file: String) -> Result<SaveData, Box<dyn Error>> {
// << 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<Sha256> = Hmac::new_from_slice(key.as_bytes()).map_err(|e| format!("error loading hmac-sha256: {e}"))?;
+ let mut mac: Hmac<Sha256> = 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}"))?;