~starkingdoms/starkingdoms

a31f8e9f44de8e7c4513996ed548884e13b85251 — core 2 years ago 3dfcf8b
api user controls
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,
}