M Cargo.lock => Cargo.lock +28 -4
@@ 98,7 98,7 @@ dependencies = [
"actix-service",
"actix-utils",
"ahash",
- "base64",
+ "base64 0.21.5",
"bitflags 2.4.1",
"brotli",
"bytes",
@@ 514,6 514,12 @@ dependencies = [
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9"
@@ 2293,6 2299,21 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 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",
M starkingdoms-api/Cargo.toml => starkingdoms-api/Cargo.toml +4 -1
@@ 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
M starkingdoms-api/src/config.rs => starkingdoms-api/src/config.rs +1 -0
@@ 28,6 28,7 @@ pub struct ConfigServer {
pub workers: Option<usize>,
pub machine_id: i32,
pub node_id: i32,
+ pub application_key: String,
}
#[derive(Deserialize, Clone)]
M starkingdoms-api/src/main.rs => starkingdoms-api/src/main.rs +9 -0
@@ 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<AsyncDieselConnectionManager<AsyncPgConnection>>,
pub idgen: Arc<Mutex<SnowflakeIdGenerator>>,
+ pub key: Hmac<Sha256>,
}
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())
A starkingdoms-api/src/routes/login.rs => starkingdoms-api/src/routes/login.rs +93 -0
@@ 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 <https://www.gnu.org/licenses/>.
+
+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<LoginRequest>,
+ state: Data<AppState>,
+) -> JsonAPIResponse<LoginResponse> {
+ 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
+ })
+}
M starkingdoms-api/src/routes/mod.rs => starkingdoms-api/src/routes/mod.rs +1 -0
@@ 13,4 13,5 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
+pub mod login;
pub mod signup;
M starkingdoms-api/src/routes/signup.rs => starkingdoms-api/src/routes/signup.rs +15 -0
@@ 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 <https://www.gnu.org/licenses/>.
use crate::models::User;
use crate::response::JsonAPIResponse;
use crate::AppState;
A starkingdoms-api/src/tokens.rs => starkingdoms-api/src/tokens.rs +26 -0
@@ 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 <https://www.gnu.org/licenses/>.
+
+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,
+}