From a31f8e9f44de8e7c4513996ed548884e13b85251 Mon Sep 17 00:00:00 2001 From: core Date: Thu, 30 Nov 2023 12:50:44 -0500 Subject: [PATCH] api user controls --- Cargo.lock | 32 +++++++-- starkingdoms-api/Cargo.toml | 5 +- starkingdoms-api/src/config.rs | 1 + starkingdoms-api/src/main.rs | 9 +++ starkingdoms-api/src/routes/login.rs | 93 +++++++++++++++++++++++++++ starkingdoms-api/src/routes/mod.rs | 1 + starkingdoms-api/src/routes/signup.rs | 15 +++++ starkingdoms-api/src/tokens.rs | 26 ++++++++ 8 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 starkingdoms-api/src/routes/login.rs create mode 100644 starkingdoms-api/src/tokens.rs diff --git a/Cargo.lock b/Cargo.lock index 9719bda27bd6858cc4d6ef3bac2742d884ac1796..30a33a2ee6bc306d35cc740a71af2ca2c8812234 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -98,7 +98,7 @@ dependencies = [ "actix-service", "actix-utils", "ahash", - "base64", + "base64 0.21.5", "bitflags 2.4.1", "brotli", "bytes", @@ -512,6 +512,12 @@ dependencies = [ "rustc-demangle", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.5" @@ -2292,6 +2298,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwt" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6204285f77fe7d9784db3fdc449ecce1a0114927a51d5a41c4c7a292011c015f" +dependencies = [ + "base64 0.13.1", + "crypto-common", + "digest", + "hmac", + "serde", + "serde_json", + "sha2", +] + [[package]] name = "khronos-egl" version = "4.1.0" @@ -2940,7 +2961,7 @@ version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49b6c5ef183cd3ab4ba005f1ca64c21e8bd97ce4699cfea9e8d9a2c4958ca520" dependencies = [ - "base64", + "base64 0.21.5", "byteorder", "bytes", "fallible-iterator", @@ -3184,7 +3205,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" dependencies = [ - "base64", + "base64 0.21.5", "bitflags 2.4.1", "serde", "serde_derive", @@ -3458,12 +3479,15 @@ dependencies = [ "diesel-async", "diesel_migrations", "env_logger", + "hmac", + "jwt", "log", "password-hash", "rand", "rs-snowflake", "serde", "serde_json", + "sha2", "toml 0.8.8", ] @@ -3844,7 +3868,7 @@ name = "twite" version = "0.1.0" source = "git+https://gitlab.com/ghostlyzsh/twite.git#4998e404fefe46bea91048fb492b89e74b2f9596" dependencies = [ - "base64", + "base64 0.21.5", "rand", "sha1", "url", diff --git a/starkingdoms-api/Cargo.toml b/starkingdoms-api/Cargo.toml index 57c2d05001bc8a79aeb169c0fbaa4a9d8c9c79fc..c3fdbae33b4bf5a4e36653f8d4441565accecfd0 100644 --- a/starkingdoms-api/Cargo.toml +++ b/starkingdoms-api/Cargo.toml @@ -23,4 +23,7 @@ bb8 = "0.8" rand = "0.8" argon2 = "0.5" password-hash = "0.5" -rs-snowflake = "0.6" \ No newline at end of file +rs-snowflake = "0.6" +jwt = "0.16" +sha2 = "0.10" +hmac = "0.12" \ No newline at end of file diff --git a/starkingdoms-api/src/config.rs b/starkingdoms-api/src/config.rs index 6fe0a9169ee89bbdfd67d62140fb232c40e881e1..543f01b1c35246dd51cb5771a62e3f37b600764f 100644 --- a/starkingdoms-api/src/config.rs +++ b/starkingdoms-api/src/config.rs @@ -28,6 +28,7 @@ pub struct ConfigServer { pub workers: Option, pub machine_id: i32, pub node_id: i32, + pub application_key: String, } #[derive(Deserialize, Clone)] diff --git a/starkingdoms-api/src/main.rs b/starkingdoms-api/src/main.rs index d1410264da0ad9c4a261f3a8404a1a293c5e4621..13154527ac3296708cbff70ba2d6bf76cd89dcce 100644 --- a/starkingdoms-api/src/main.rs +++ b/starkingdoms-api/src/main.rs @@ -24,7 +24,10 @@ use diesel_async::pooled_connection::bb8::Pool; use diesel_async::pooled_connection::AsyncDieselConnectionManager; use diesel_async::AsyncPgConnection; use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use hmac::digest::KeyInit; +use hmac::Hmac; use log::{error, info}; +use sha2::Sha256; use snowflake::SnowflakeIdGenerator; use std::fs; use std::path::PathBuf; @@ -40,12 +43,14 @@ pub mod routes; pub mod schema; pub mod auth; +pub mod tokens; #[derive(Clone)] pub struct AppState { pub config: Config, pub pool: bb8::Pool>, pub idgen: Arc>, + pub key: Hmac, } pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); @@ -137,6 +142,8 @@ async fn main() { } } + let key = Hmac::new_from_slice(config.server.application_key.as_bytes()).unwrap(); + let stk_epoch = UNIX_EPOCH + Duration::from_secs(1616260136); let id_generator = SnowflakeIdGenerator::with_epoch( config.server.machine_id, @@ -148,6 +155,7 @@ async fn main() { config, pool, idgen: Arc::new(Mutex::new(id_generator)), + key, }); let server = HttpServer::new(move || { @@ -162,6 +170,7 @@ async fn main() { }) })) .service(routes::signup::signup_req) + .service(routes::login::user_login_req) .wrap(Logger::default()) .wrap(actix_cors::Cors::permissive()) .app_data(app_state.clone()) diff --git a/starkingdoms-api/src/routes/login.rs b/starkingdoms-api/src/routes/login.rs new file mode 100644 index 0000000000000000000000000000000000000000..3cb692438ae5a28adc6cc0ef83ea4f60441d2321 --- /dev/null +++ b/starkingdoms-api/src/routes/login.rs @@ -0,0 +1,93 @@ +// StarKingdoms.IO, a browser game about drifting through space +// Copyright (C) 2023 ghostly_zsh, TerraMaster85, core +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use crate::models::User; +use crate::response::JsonAPIResponse; +use crate::schema::users; +use crate::tokens::UserToken; +use crate::AppState; +use actix_web::{ + http::StatusCode, + post, + web::{Data, Json}, +}; +use argon2::Argon2; +use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper}; +use diesel_async::RunQueryDsl; +use jwt::SignWithKey; +use password_hash::{Encoding, PasswordHash}; +use serde::{Deserialize, Serialize}; +use std::time::{Duration, SystemTime}; + +#[derive(Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Serialize, Debug)] +pub struct LoginResponse { + pub token: String, + pub user: UserToken, +} + +#[post("/login")] +pub async fn user_login_req( + req: Json, + state: Data, +) -> JsonAPIResponse { + let mut conn = handle_error!(state.pool.get().await); + + let user: User = match handle_error!(users::dsl::users + .filter(users::username.eq(&req.username)) + .select(User::as_select()) + .first(&mut conn) + .await + .optional()) + { + Some(t) => t, + None => { + err!( + StatusCode::UNAUTHORIZED, + make_err!("ERR_UNAUTHORIZED", "user does not exist", "username") + ); + } + }; + + let hash = handle_error!(PasswordHash::parse(&user.password_hash, Encoding::B64)); + let pwd_ok = hash + .verify_password(&[&Argon2::default()], req.password.as_bytes()) + .is_ok(); + if !pwd_ok { + err!( + StatusCode::UNAUTHORIZED, + make_err!("ERR_UNAUTHORIZED", "unauthorized") + ) + } + + let token = UserToken { + id: user.id, + username: user.username.clone(), + permission_level: user.permission_level, + expires: SystemTime::now() + Duration::from_secs(86400 * 365), // 1 year + }; + let jwt_str = handle_error!(token.clone().sign_with_key(&state.key)); + + ok!(LoginResponse { + token: jwt_str, + user: token + }) +} diff --git a/starkingdoms-api/src/routes/mod.rs b/starkingdoms-api/src/routes/mod.rs index fcf706d92e16dd686c3af85200f32f9ac6ce9914..a603e00af8cf65ac109a3ac2abf71b36e23b1bce 100644 --- a/starkingdoms-api/src/routes/mod.rs +++ b/starkingdoms-api/src/routes/mod.rs @@ -13,4 +13,5 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +pub mod login; pub mod signup; diff --git a/starkingdoms-api/src/routes/signup.rs b/starkingdoms-api/src/routes/signup.rs index fb4adfe0b045c9fce209b109903c8aedd014b77b..476fc34c03810c15402b8d9aff1c726e3be6cf81 100644 --- a/starkingdoms-api/src/routes/signup.rs +++ b/starkingdoms-api/src/routes/signup.rs @@ -1,3 +1,18 @@ +// StarKingdoms.IO, a browser game about drifting through space +// Copyright (C) 2023 ghostly_zsh, TerraMaster85, core +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . use crate::models::User; use crate::response::JsonAPIResponse; use crate::AppState; diff --git a/starkingdoms-api/src/tokens.rs b/starkingdoms-api/src/tokens.rs new file mode 100644 index 0000000000000000000000000000000000000000..739f18bbcd5231d7505f9b775305fe4a21eac1b7 --- /dev/null +++ b/starkingdoms-api/src/tokens.rs @@ -0,0 +1,26 @@ +// StarKingdoms.IO, a browser game about drifting through space +// Copyright (C) 2023 ghostly_zsh, TerraMaster85, core +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +use serde::{Deserialize, Serialize}; +use std::time::SystemTime; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct UserToken { + pub id: i64, + pub username: String, + pub permission_level: i32, + pub expires: SystemTime, +}