use crate::config::CONFIG; use crate::error::{APIError, APIErrorsResponse}; use crate::AppState; use actix_web::web::{Data, Json}; use actix_web::{post, HttpResponse}; use hmac::digest::KeyInit; use hmac::Hmac; use jwt::VerifyWithKey; use log::error; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, QueryOrder}; use serde::{Deserialize, Serialize}; use sha2::Sha256; use starkingdoms_protocol::api::APISavedPlayerData; use std::collections::BTreeMap; #[derive(Serialize, Deserialize)] pub struct BeaminRequest { pub api_token: String, pub user_auth_realm_id: String, pub user_auth_token: String, } #[derive(Serialize, Deserialize)] pub struct BeaminResponse { pub save_id: String, pub save: APISavedPlayerData, } #[post("/beamin")] pub async fn beam_in(data: Json, state: Data) -> HttpResponse { if !CONFIG.internal_tokens.contains(&data.api_token) { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![APIError { code: "ERR_BAD_TOKEN".to_string(), message: "Missing or invalid api token".to_string(), path: None, }], }); } let key: Hmac = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap(); let token: BTreeMap = match data.user_auth_token.verify_with_key(&key) { Ok(t) => t, Err(e) => { error!("verifying error: {}", e); return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![APIError { code: "ERR_BAD_TOKEN".to_string(), message: "Missing or invalid user token".to_string(), path: None, }], }); } }; if !token.contains_key("user") || !token.contains_key("nonce") { return HttpResponse::Unauthorized().json(APIErrorsResponse { errors: vec![APIError { code: "ERR_BAD_TOKEN".to_string(), message: "Missing or invalid user token (missing scopes)".to_string(), path: None, }], }); } let user_id = token.get("user").unwrap(); let user_savefile: Vec = match starkingdoms_api_entities::entity::user_savefile::Entity::find() .filter(starkingdoms_api_entities::entity::user_savefile::Column::User.eq(user_id)) .order_by_desc(starkingdoms_api_entities::entity::user_savefile::Column::Timestamp) .all(&state.conn) .await { Ok(sf) => sf, Err(e) => { error!("database error: {}", e); return HttpResponse::InternalServerError().json(APIErrorsResponse { errors: vec![APIError { code: "ERR_DB_ERROR".to_string(), message: "Unable to fetch user savefiles".to_string(), path: None, }], }); } }; if user_savefile.is_empty() { return HttpResponse::NoContent().json(APIErrorsResponse { errors: vec![APIError { code: "ERR_NO_SAVES".to_string(), message: "This user has no savefiles".to_string(), path: None, }], }); } let save = &user_savefile[0]; let save_id = &save.id; let save_data_str = &save.data; let save_data: APISavedPlayerData = toml::from_str(save_data_str).expect("database contained corrupted player save data"); HttpResponse::Ok().json(BeaminResponse { save_id: save_id.clone(), save: save_data, }) }