~starkingdoms/starkingdoms

a53b7bca59edbffb01c8b3032adc456a9418fc59 — c0repwn3r 2 years ago 6ddf7ef
client-side authentication
M Cargo.lock => Cargo.lock +2 -0
@@ 3325,6 3325,7 @@ dependencies = [
 "actix-files",
 "actix-request-identifier",
 "actix-web",
 "hmac",
 "jwt",
 "log",
 "once_cell",


@@ 3332,6 3333,7 @@ dependencies = [
 "reqwest",
 "sea-orm",
 "serde",
 "sha2",
 "simple_logger",
 "starkingdoms_api_entities",
 "starkingdoms_api_migration",

M api/Cargo.toml => api/Cargo.toml +3 -1
@@ 28,4 28,6 @@ tera = "1"  # Templates

jwt = { version = "0.16", features = ["openssl"] }  # Auth
openssl = "0.10"                                    # Auth
reqwest = "0.11"                                    # Auth
\ No newline at end of file
reqwest = "0.11"                                    # Auth
hmac = "0.12.1"                                     # Auth
sha2 = "0.10.6"                                     # Auth
\ No newline at end of file

M api/src/config.rs => api/src/config.rs +1 -0
@@ 28,6 28,7 @@ pub struct StarkingdomsApiConfig {
    pub database: StarkingdomsApiConfigDatabase,
    pub server: StarkingdomsApiConfigServer,
    pub internal_tokens: Vec<String>,
    pub jwt_signing_secret: String,
    pub base: String,
    pub game: String,
    pub realms: HashMap<String, StarkingdomsApiConfigRealm>

M api/src/routes/callback.rs => api/src/routes/callback.rs +95 -2
@@ 1,11 1,18 @@
use std::collections::BTreeMap;
use std::time::{SystemTime, UNIX_EPOCH};
use actix_web::{get, HttpResponse};
use actix_web::web::{Data, Query};
use jwt::{PKeyWithDigest, VerifyWithKey};
use hmac::digest::KeyInit;
use hmac::Hmac;
use jwt::{PKeyWithDigest, SignWithKey, VerifyWithKey};
use log::{debug, error};
use openssl::hash::MessageDigest;
use openssl::pkey::PKey;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
use serde::Deserialize;
use sha2::Sha256;
use ulid::Ulid;
use starkingdoms_api_entities::entity;
use crate::AppState;
use crate::config::CONFIG;
use crate::error::{APIError, APIErrorsResponse};


@@ 61,7 68,93 @@ pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) 

    debug!("got authenticated realm native authorization: authenticated as {}:{}", realm, realm_local_id);

    HttpResponse::Ok().finish()
    // see if a user on this realm already exists
    let maybe_user = match entity::user_auth_realm::Entity::find()
        .filter(
            entity::user_auth_realm::Column::RealmNativeId.eq(realm_local_id.clone())
        )
        .filter(
            entity::user_auth_realm::Column::Realm.eq(realm.clone())
        ).one(&state.conn).await {
        Ok(r) => r,
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Database error".to_string(),
                        path: None,
                    }
                ],
            })
        }
    };

    if let Some(user) = maybe_user {
        let key: Hmac<Sha256> = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap();
        let mut claims = BTreeMap::new();
        claims.insert("user", user.id.clone());
        let token_str = claims.sign_with_key(&key).unwrap();
        let auth_url = format!("{}/?token={}&user={}", CONFIG.game, token_str, user.id);
        return HttpResponse::Found().append_header(("Location", auth_url)).finish();
    }

    // create the user
    let new_user = entity::user::Model {
        id: format!("user-{}", Ulid::new().to_string()),
        username: Ulid::new().to_string(),
        created_on: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64,
    };
    let new_user_realm = entity::user_auth_realm::Model {
        id: format!("{}-{}:{}", new_user.id.clone(), realm.clone(), realm_local_id.clone()),
        realm: realm.clone(),
        realm_native_id: realm_local_id.clone(),
        user: new_user.id.clone(),
    };

    let key: Hmac<Sha256> = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap();
    let mut claims = BTreeMap::new();
    claims.insert("user", new_user.id.clone());
    let token_str = claims.sign_with_key(&key).unwrap();
    let auth_url = format!("{}/?token={}&user={}", CONFIG.game, token_str, new_user.id.clone());

    let new_user_active_model = new_user.into_active_model();
    let new_user_realm_active_model = new_user_realm.into_active_model();

    match new_user_active_model.insert(&state.conn).await {
        Ok(_) => (),
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Database error".to_string(),
                        path: None,
                    }
                ],
            })
        }
    }

    match new_user_realm_active_model.insert(&state.conn).await {
        Ok(_) => (),
        Err(e) => {
            error!("database error: {}", e);
            return HttpResponse::InternalServerError().json(APIErrorsResponse {
                errors: vec![
                    APIError {
                        code: "ERR_DB_ERROR".to_string(),
                        message: "Database error".to_string(),
                        path: None,
                    }
                ],
            })
        }
    }

    HttpResponse::Found().append_header(("Location", auth_url)).finish()
}

fn generic_unauthorized() -> HttpResponse {

M api/templates/select_realm.tera => api/templates/select_realm.tera +1 -1
@@ 7,5 7,5 @@
    {% for realm_id, realm in realms %}
        <a href="{{ realm.authorize_url }}?return={{ back_to }}">{{ realm_id }}</a>
    {% endfor %}
    <p>Selecting an authentication provider will redirect you to our trusted partner <a href="https://e3t.cc">e3team</a>'s website for login. You will be returned to StarKingdoms when finished.</p>
    <p>Selecting an authentication provider will redirect you to our parent <a href="https://e3t.cc">e3team</a>'s website for login. You will be returned to StarKingdoms when finished.</p>
{% endblock body %}
\ No newline at end of file

M client/index.html => client/index.html +7 -0
@@ 53,6 53,13 @@
    for (let i = 0; i < servers.length; i++) {
        load_server(servers[i]);
    }

    let query = new URLSearchParams(window.location.search);

    if (query.has("token") && query.has("user")) {
        window.localStorage.setItem("token", query.get("token"));
        window.localStorage.setItem("user", query.get("user"));
    }
</script>
</body>
</html>

M client/src/index.ts => client/src/index.ts +8 -3
@@ 18,7 18,8 @@ export interface GlobalData {
    spritesheet: object | null,
    context: CanvasRenderingContext2D,
    keys: Keys,
    velocity: number
    velocity: number,
    can_beam_out: boolean
}

export interface Keys {


@@ 43,12 44,17 @@ export const global: GlobalData = {
        left: false,
        right: false
    },
    velocity: 0
    velocity: 0,
    can_beam_out: false
}

async function client_main(server: string, username: string, texture_quality: string) {
    logger.info("StarKingdoms client - starting");

    if (window.localStorage.getItem("token") !== null && window.localStorage.getItem("user") !== null) {
        global.can_beam_out = true;
    }

    logger.info("Loading textures");
    let spritesheet_url = `/assets/dist/spritesheet-${texture_quality}.png`;
    let spritesheet_data_url = `/assets/dist/spritesheet-${texture_quality}.json`;


@@ 224,7 230,6 @@ if (!(query.has("server") || query.has("username") || query.has("textures"))) {
    window.location.href = "/index.html";
}


client_main(query.get("server")!, query.get("username")!, query.get("textures")!).then(() => {});

function planet_type_to_tex_id(ty: PlanetType): string {