29 files changed, 0 insertions(+), 1179 deletions(-)
D api/Cargo.toml
D api/config.toml
D api/src/config.rs
D api/src/error.rs
D api/src/main.rs
D api/src/routes/beamin.rs
D api/src/routes/beamout.rs
D api/src/routes/callback.rs
D api/src/routes/mod.rs
D api/src/routes/select_realm.rs
D api/src/routes/server_list.rs
D api/starkingdoms_api_entities/Cargo.toml
D api/starkingdoms_api_entities/src/entity/mod.rs
D api/starkingdoms_api_entities/src/entity/prelude.rs
D api/starkingdoms_api_entities/src/entity/user.rs
D api/starkingdoms_api_entities/src/entity/user_auth_realm.rs
D api/starkingdoms_api_entities/src/entity/user_savefile.rs
D api/starkingdoms_api_entities/src/lib.rs
D api/starkingdoms_api_migration/.env
D api/starkingdoms_api_migration/Cargo.toml
D api/starkingdoms_api_migration/README.md
D api/starkingdoms_api_migration/src/lib.rs
D api/starkingdoms_api_migration/src/m20230417_162824_create_table_users.rs
D api/starkingdoms_api_migration/src/m20230417_164240_create_table_user_auth_realms.rs
D api/starkingdoms_api_migration/src/m20230420_144333_create_table_user_data.rs
D api/starkingdoms_api_migration/src/main.rs
D api/static/all.css
D api/templates/base.tera
D api/templates/select_realm.tera
D api/Cargo.toml => api/Cargo.toml +0 -34
@@ 1,34 0,0 @@
-[package]
-name = "starkingdoms-api"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-actix-web = "4" # Web framework
-actix-request-identifier = "4" # Web framework
-actix-files = "0.6" # Web framework
-actix-cors = "0.6.4" # Web framework
-
-serde = { version = "1", features = ["derive"] } # Serialization and deserialization
-
-once_cell = "1" # Config
-toml = "0.7" # Config / Serialization and deserialization
-
-log = "0.4" # Logging
-simple_logger = "4" # Logging
-
-sea-orm = { version = "^0", features = [ "sqlx-postgres", "runtime-actix-rustls", "macros" ]} # Database
-starkingdoms_api_migration = { version = "0.1.0", path = "starkingdoms_api_migration" } # Database
-starkingdoms_api_entities = { version = "0.1.0", path = "starkingdoms_api_entities" } # Database
-
-ulid = "1.0.0" # Identifiers
-
-tera = "1" # Templates
-
-jwt = { version = "0.16", features = ["openssl"] } # Auth
-openssl = "0.10" # Auth
-reqwest = "0.11" # Auth
-hmac = "0.12.1" # Auth
-sha2 = "0.10.6" # Auth
D api/config.toml => api/config.toml +0 -22
@@ 1,22 0,0 @@
-game = "localhost:5173"
-internal_tokens = ["01GY803PVK7YJKXZYWFTK6DS1Y-01GY8040ZQY9SG29DXY4HZ4EPD"]
-jwt_signing_secret = "544adbc8144d375d581a1622a4f0cbcf92f006a156ef8b9d4afac6410f51f73c"
-base = "localhost:8080"
-servers = ["http://localhost:3000"]
-
-[server]
-listen = "0.0.0.0:8080"
-
-[realms.discord]
-authorize_url = "https://api.e3t.cc/auth/discord/authorize.php"
-public_key = "-----BEGIN PUBLIC KEY-----\nMIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHBcZsCM6ebFDCp3dFc+3EOzLw8B\n+fR+9Tx6S/lXOTghk49s7yaxza/zVRPxWaMqyjegfRCEepgV++jbWzBib7bhy91M\n+zlRbeZ9rf++N30Nf4R/XAnUAmhAHt8TzDC08DNQNYAFz37+r4EZlY7APHyND4qU\nd8w3qB95v/wMVB6nAgMBAAE=\n-----END PUBLIC KEY-----"
-issuer = "https://api.e3t.cc"
-
-[database]
-url = "postgres://postgres@localhost/starkingdoms"
-
-[endpoints]
-allowed_return_endpoints = [
- "127.0.0.1:5173",
- "starkingdoms.tk"
-]
D api/src/config.rs => api/src/config.rs +0 -90
@@ 1,90 0,0 @@
-use log::error;
-use once_cell::sync::Lazy;
-use serde::{Deserialize, Serialize};
-use std::collections::HashMap;
-use std::fs;
-use std::net::{Ipv4Addr, SocketAddr, SocketAddrV4};
-
-pub static CONFIG: Lazy<StarkingdomsApiConfig> = Lazy::new(|| {
- let config_str = match fs::read_to_string("/etc/starkingdoms/config.toml") {
- Ok(str) => str,
- Err(e) => {
- error!("Unable to read config file: {}", e);
- std::process::exit(1);
- }
- };
-
- match toml::from_str(&config_str) {
- Ok(cfg) => cfg,
- Err(e) => {
- error!("Unable to parse config file: {}", e);
- std::process::exit(1);
- }
- }
-});
-
-#[derive(Serialize, Debug, Deserialize)]
-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>,
- pub servers: Vec<String>,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct StarkingdomsApiConfigDatabase {
- pub url: String,
- #[serde(default = "max_connections_default")]
- pub max_connections: u32,
- #[serde(default = "min_connections_default")]
- pub min_connections: u32,
- #[serde(default = "time_defaults")]
- pub connect_timeout: u64,
- #[serde(default = "time_defaults")]
- pub acquire_timeout: u64,
- #[serde(default = "time_defaults")]
- pub idle_timeout: u64,
- #[serde(default = "time_defaults")]
- pub max_lifetime: u64,
- #[serde(default = "sqlx_logging_default")]
- pub sqlx_logging: bool,
-}
-
-#[derive(Serialize, Deserialize, Debug)]
-pub struct StarkingdomsApiConfigServer {
- #[serde(default = "socketaddr_8080")]
- pub bind: SocketAddr,
-}
-
-/*
-authorize-url = "https://api.e3t.cc/auth/discord/authorize.php"
-public-key-url = "https://api.e3t.cc/auth/discord/public-key.txt"
-issuer = "https://api.e3t.cc"
- */
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct StarkingdomsApiConfigRealm {
- pub authorize_url: String,
- pub public_key: String,
- pub issuer: String,
-}
-
-fn max_connections_default() -> u32 {
- 100
-}
-fn min_connections_default() -> u32 {
- 5
-}
-fn time_defaults() -> u64 {
- 8
-}
-fn sqlx_logging_default() -> bool {
- true
-}
-fn socketaddr_8080() -> SocketAddr {
- SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from([0, 0, 0, 0]), 8080))
-}
D api/src/error.rs => api/src/error.rs +0 -116
@@ 1,116 0,0 @@
-use actix_web::error::{JsonPayloadError, PayloadError};
-use serde::{Deserialize, Serialize};
-
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct APIErrorsResponse {
- pub errors: Vec<APIError>,
-}
-#[derive(Serialize, Deserialize, Debug, Clone)]
-pub struct APIError {
- pub code: String,
- pub message: String,
- #[serde(skip_serializing_if = "is_none")]
- #[serde(default)]
- pub path: Option<String>,
-}
-
-fn is_none<T>(o: &Option<T>) -> bool {
- o.is_none()
-}
-
-impl From<&JsonPayloadError> for APIError {
- fn from(value: &JsonPayloadError) -> Self {
- match value {
- JsonPayloadError::OverflowKnownLength { length, limit } => {
- APIError {
- code: "ERR_PAYLOAD_OVERFLOW_KNOWN_LENGTH".to_string(),
- message: format!("Payload size is bigger than allowed & content length header set. (length: {}, limit: {})", length, limit),
- path: None
- }
- },
- JsonPayloadError::Overflow { limit } => {
- APIError {
- code: "ERR_PAYLOAD_OVERFLOW".to_string(),
- message: format!("Payload size is bigger than allowed but no content-length header is set. (limit: {})", limit),
- path: None
- }
- },
- JsonPayloadError::ContentType => {
- APIError {
- code: "ERR_NOT_JSON".to_string(),
- message: "Content-Type header not set to expected application/json".to_string(),
- path: None,
- }
- },
- JsonPayloadError::Deserialize(e) => {
- APIError {
- code: "ERR_JSON_DESERIALIZE".to_string(),
- message: format!("Error deserializing JSON: {}", e),
- path: None,
- }
- },
- JsonPayloadError::Serialize(e) => {
- APIError {
- code: "ERR_JSON_SERIALIZE".to_string(),
- message: format!("Error serializing JSON: {}", e),
- path: None,
- }
- },
- JsonPayloadError::Payload(e) => {
- e.into()
- },
- _ => {
- APIError {
- code: "ERR_UNKNOWN_ERROR".to_string(),
- message: "An unknown error has occured".to_string(),
- path: None,
- }
- }
- }
- }
-}
-
-impl From<&PayloadError> for APIError {
- fn from(value: &PayloadError) -> Self {
- match value {
- PayloadError::Incomplete(e) => APIError {
- code: "ERR_UNEXPECTED_EOF".to_string(),
- message: match e {
- None => "Payload reached EOF but was incomplete".to_string(),
- Some(e) => format!("Payload reached EOF but was incomplete: {}", e),
- },
- path: None,
- },
- PayloadError::EncodingCorrupted => APIError {
- code: "ERR_CORRUPTED_PAYLOAD".to_string(),
- message: "Payload content encoding corrupted".to_string(),
- path: None,
- },
- PayloadError::Overflow => APIError {
- code: "ERR_PAYLOAD_OVERFLOW".to_string(),
- message: "Payload reached size limit".to_string(),
- path: None,
- },
- PayloadError::UnknownLength => APIError {
- code: "ERR_PAYLOAD_UNKNOWN_LENGTH".to_string(),
- message: "Unable to determine payload length".to_string(),
- path: None,
- },
- PayloadError::Http2Payload(e) => APIError {
- code: "ERR_HTTP2_ERROR".to_string(),
- message: format!("HTTP/2 error: {}", e),
- path: None,
- },
- PayloadError::Io(e) => APIError {
- code: "ERR_IO_ERROR".to_string(),
- message: format!("I/O error: {}", e),
- path: None,
- },
- _ => APIError {
- code: "ERR_UNKNOWN_ERROR".to_string(),
- message: "An unknown error has occured".to_string(),
- path: None,
- },
- }
- }
-}
D api/src/main.rs => api/src/main.rs +0 -82
@@ 1,82 0,0 @@
-use crate::config::CONFIG;
-use crate::error::{APIError, APIErrorsResponse};
-use actix_cors::Cors;
-use actix_request_identifier::RequestIdentifier;
-use actix_web::http::header::HeaderValue;
-use actix_web::web::{Data, JsonConfig};
-use actix_web::{App, HttpResponse, HttpServer};
-use log::{info, Level};
-use sea_orm::{ConnectOptions, Database, DatabaseConnection};
-use starkingdoms_api_migration::{Migrator, MigratorTrait};
-use std::error::Error;
-use std::time::Duration;
-use tera::Tera;
-
-pub mod config;
-pub mod error;
-pub mod routes;
-
-pub struct AppState {
- pub conn: DatabaseConnection,
- pub templates: Tera,
-}
-
-#[actix_web::main]
-async fn main() -> Result<(), Box<dyn Error>> {
- simple_logger::init_with_level(Level::Debug).unwrap();
-
- info!("Connecting to database at {}...", CONFIG.database.url);
-
- let mut opt = ConnectOptions::new(CONFIG.database.url.clone());
- opt.max_connections(CONFIG.database.max_connections)
- .min_connections(CONFIG.database.min_connections)
- .connect_timeout(Duration::from_secs(CONFIG.database.connect_timeout))
- .acquire_timeout(Duration::from_secs(CONFIG.database.acquire_timeout))
- .idle_timeout(Duration::from_secs(CONFIG.database.idle_timeout))
- .max_lifetime(Duration::from_secs(CONFIG.database.max_lifetime))
- .sqlx_logging(CONFIG.database.sqlx_logging)
- .sqlx_logging_level(log::LevelFilter::Info);
-
- let db = Database::connect(opt).await?;
-
- info!("Performing database migration...");
- Migrator::up(&db, None).await?;
-
- info!("Loading templates...");
- let tera = Tera::new("templates/**/*.tera")?;
-
- let data = Data::new(AppState {
- conn: db,
- templates: tera,
- });
-
- HttpServer::new(move || {
- App::new()
- .wrap(Cors::permissive())
- .app_data(data.clone())
- .app_data(JsonConfig::default().error_handler(|err, _req| {
- let api_error: APIError = (&err).into();
- actix_web::error::InternalError::from_response(
- err,
- HttpResponse::BadRequest().json(APIErrorsResponse {
- errors: vec![api_error],
- }),
- )
- .into()
- }))
- .wrap(RequestIdentifier::with_generator(|| {
- HeaderValue::from_str(&ulid::Ulid::new().to_string()).unwrap()
- }))
- .service(routes::select_realm::select_realm)
- .service(routes::callback::callback)
- .service(routes::beamin::beam_in)
- .service(routes::beamout::beam_out)
- .service(routes::server_list::server_list)
- .service(actix_files::Files::new("/static", "static"))
- })
- .bind(CONFIG.server.bind)?
- .run()
- .await?;
-
- Ok(())
-}
D api/src/routes/beamin.rs => api/src/routes/beamin.rs +0 -107
@@ 1,107 0,0 @@
-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<BeaminRequest>, state: Data<AppState>) -> 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<Sha256> = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap();
- let token: BTreeMap<String, String> = 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<starkingdoms_api_entities::entity::user_savefile::Model> =
- 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,
- })
-}
D api/src/routes/beamout.rs => api/src/routes/beamout.rs +0 -92
@@ 1,92 0,0 @@
-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::{ActiveModelTrait, IntoActiveModel};
-use serde::{Deserialize, Serialize};
-use sha2::Sha256;
-use starkingdoms_protocol::api::APISavedPlayerData;
-use std::collections::BTreeMap;
-use std::time::{SystemTime, UNIX_EPOCH};
-use ulid::Ulid;
-
-#[derive(Serialize, Deserialize)]
-pub struct BeamoutRequest {
- pub api_token: String,
- pub user_auth_realm_id: String,
- pub user_auth_token: String,
- pub data: APISavedPlayerData,
-}
-
-#[derive(Serialize, Deserialize)]
-pub struct BeamoutResponse {}
-
-#[post("/beamout")]
-pub async fn beam_out(data: Json<BeamoutRequest>, state: Data<AppState>) -> 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<Sha256> = Hmac::new_from_slice(CONFIG.jwt_signing_secret.as_bytes()).unwrap();
- let token: BTreeMap<String, String> = 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 saved_data = toml::to_string(&data.data).unwrap();
- let savefile_model = starkingdoms_api_entities::entity::user_savefile::Model {
- id: format!("save-{}", Ulid::new().to_string()),
- user: token.get("user").unwrap().clone(),
- data: saved_data,
- timestamp: SystemTime::now()
- .duration_since(UNIX_EPOCH)
- .unwrap()
- .as_secs() as i64,
- };
- let savefile_active_model = savefile_model.into_active_model();
- match savefile_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 failure".to_string(),
- path: None,
- }],
- });
- }
- }
-
- HttpResponse::Ok().json(BeamoutResponse {})
-}
D api/src/routes/callback.rs => api/src/routes/callback.rs +0 -181
@@ 1,181 0,0 @@
-use crate::config::CONFIG;
-use crate::error::{APIError, APIErrorsResponse};
-use crate::AppState;
-use actix_web::web::{Data, Query};
-use actix_web::{get, HttpResponse};
-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 starkingdoms_api_entities::entity;
-use std::collections::BTreeMap;
-use std::time::{SystemTime, UNIX_EPOCH};
-use ulid::Ulid;
-
-#[derive(Deserialize)]
-pub struct CallbackQueryParams {
- pub token: String,
- pub realm: String,
-}
-
-#[get("/callback")]
-pub async fn callback(query: Query<CallbackQueryParams>, state: Data<AppState>) -> HttpResponse {
- // verify token from auth provider API
-
- // 1. load the public key
- let realm = match CONFIG.realms.get(&query.realm) {
- Some(r) => r,
- None => {
- error!("realm not found");
- return HttpResponse::Unauthorized().json(APIErrorsResponse {
- errors: vec![APIError {
- code: "ERR_UNKNOWN_REALM".to_string(),
- message: "Unknown realm".to_string(),
- path: None,
- }],
- });
- }
- };
-
- let rs256_pub_key = PKeyWithDigest {
- digest: MessageDigest::sha256(),
- key: PKey::public_key_from_pem(realm.public_key.as_bytes()).unwrap(),
- };
-
- let token: BTreeMap<String, String> = match query.token.verify_with_key(&rs256_pub_key) {
- Ok(tkn) => tkn,
- Err(e) => {
- error!("[callback] token verify error: {}", e);
- return generic_unauthorized();
- }
- };
-
- let realm = match token.get("realm").ok_or(generic_unauthorized()) {
- Ok(r) => r,
- Err(e) => return e,
- };
- let realm_local_id = match token.get("realm_native_id").ok_or(generic_unauthorized()) {
- Ok(r) => r,
- Err(e) => return e,
- };
-
- debug!(
- "got authenticated realm native authorization: authenticated as {}:{}",
- realm, realm_local_id
- );
-
- // 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());
- claims.insert("nonce", Ulid::new().to_string());
- 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());
- claims.insert("nonce", Ulid::new().to_string());
- 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 {
- HttpResponse::Unauthorized().json(APIErrorsResponse {
- errors: vec![APIError {
- code: "ERR_INVALID_STATE".to_string(),
- message: "Unknown/invalid login state".to_string(),
- path: None,
- }],
- })
-}
D api/src/routes/mod.rs => api/src/routes/mod.rs +0 -5
@@ 1,5 0,0 @@
-pub mod beamin;
-pub mod beamout;
-pub mod callback;
-pub mod select_realm;
-pub mod server_list;
D api/src/routes/select_realm.rs => api/src/routes/select_realm.rs +0 -50
@@ 1,50 0,0 @@
-use crate::config::{StarkingdomsApiConfigRealm, CONFIG};
-use crate::error::{APIError, APIErrorsResponse};
-use crate::AppState;
-use actix_web::web::Data;
-use actix_web::{get, HttpResponse};
-use log::error;
-use serde::Serialize;
-use std::collections::HashMap;
-use tera::Context;
-
-#[derive(Serialize)]
-pub struct RealmsListTemplateContext {
- pub realms: HashMap<String, StarkingdomsApiConfigRealm>,
- pub back_to: String,
-}
-
-#[get("/select-realm")]
-pub async fn select_realm(state: Data<AppState>) -> HttpResponse {
- let context = match Context::from_serialize(RealmsListTemplateContext {
- back_to: format!("{}/callback", CONFIG.base),
- realms: CONFIG.realms.clone(),
- }) {
- Ok(r) => r,
- Err(e) => {
- error!("[context] error creating render context: {}", e);
- return HttpResponse::InternalServerError().json(APIErrorsResponse {
- errors: vec![APIError {
- code: "ERR_INTERNAL_SERVER_ERROR".to_string(),
- message: "There was an error processing your request. Please try again later."
- .to_string(),
- path: None,
- }],
- });
- }
- };
- match state.templates.render("select_realm.tera", &context) {
- Ok(r) => HttpResponse::Ok().content_type("text/html").body(r),
- Err(e) => {
- error!("[context] error creating render context: {}", e);
- HttpResponse::InternalServerError().json(APIErrorsResponse {
- errors: vec![APIError {
- code: "ERR_INTERNAL_SERVER_ERROR".to_string(),
- message: "There was an error processing your request. Please try again later."
- .to_string(),
- path: None,
- }],
- })
- }
- }
-}
D api/src/routes/server_list.rs => api/src/routes/server_list.rs +0 -16
@@ 1,16 0,0 @@
-use crate::config::CONFIG;
-use actix_web::get;
-use actix_web::web::Json;
-use serde::Serialize;
-
-#[derive(Serialize)]
-pub struct ServerListResponse {
- pub servers: Vec<String>,
-}
-
-#[get("server-list")]
-pub async fn server_list() -> Json<ServerListResponse> {
- Json(ServerListResponse {
- servers: CONFIG.servers.clone(),
- })
-}
D api/starkingdoms_api_entities/Cargo.toml => api/starkingdoms_api_entities/Cargo.toml +0 -9
@@ 1,9 0,0 @@
-[package]
-name = "starkingdoms_api_entities"
-version = "0.1.0"
-edition = "2021"
-
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
-
-[dependencies]
-sea-orm = { version = "^0" }
D api/starkingdoms_api_entities/src/entity/mod.rs => api/starkingdoms_api_entities/src/entity/mod.rs +0 -7
@@ 1,7 0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
-
-pub mod prelude;
-
-pub mod user;
-pub mod user_auth_realm;
-pub mod user_savefile;
D api/starkingdoms_api_entities/src/entity/prelude.rs => api/starkingdoms_api_entities/src/entity/prelude.rs +0 -5
@@ 1,5 0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
-
-pub use super::user::Entity as User;
-pub use super::user_auth_realm::Entity as UserAuthRealm;
-pub use super::user_savefile::Entity as UserSavefile;
D api/starkingdoms_api_entities/src/entity/user.rs => api/starkingdoms_api_entities/src/entity/user.rs +0 -27
@@ 1,27 0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
-
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "user")]
-pub struct Model {
- #[sea_orm(primary_key, auto_increment = false)]
- pub id: String,
- #[sea_orm(unique)]
- pub username: String,
- pub created_on: i64,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(has_many = "super::user_auth_realm::Entity")]
- UserAuthRealm,
-}
-
-impl Related<super::user_auth_realm::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::UserAuthRealm.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
D api/starkingdoms_api_entities/src/entity/user_auth_realm.rs => api/starkingdoms_api_entities/src/entity/user_auth_realm.rs +0 -41
@@ 1,41 0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
-
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "user_auth_realm")]
-pub struct Model {
- #[sea_orm(primary_key, auto_increment = false)]
- pub id: String,
- pub realm: String,
- pub realm_native_id: String,
- pub user: String,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::user::Entity",
- from = "Column::User",
- to = "super::user::Column::Id",
- on_update = "NoAction",
- on_delete = "NoAction"
- )]
- User,
- #[sea_orm(has_many = "super::user_savefile::Entity")]
- UserSavefile,
-}
-
-impl Related<super::user::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::User.def()
- }
-}
-
-impl Related<super::user_savefile::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::UserSavefile.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
D api/starkingdoms_api_entities/src/entity/user_savefile.rs => api/starkingdoms_api_entities/src/entity/user_savefile.rs +0 -34
@@ 1,34 0,0 @@
-//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2
-
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
-#[sea_orm(table_name = "user_savefile")]
-pub struct Model {
- #[sea_orm(primary_key, auto_increment = false)]
- pub id: String,
- pub user: String,
- pub data: String,
- #[sea_orm(unique)]
- pub timestamp: i64,
-}
-
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {
- #[sea_orm(
- belongs_to = "super::user_auth_realm::Entity",
- from = "Column::User",
- to = "super::user_auth_realm::Column::Id",
- on_update = "NoAction",
- on_delete = "NoAction"
- )]
- UserAuthRealm,
-}
-
-impl Related<super::user_auth_realm::Entity> for Entity {
- fn to() -> RelationDef {
- Relation::UserAuthRealm.def()
- }
-}
-
-impl ActiveModelBehavior for ActiveModel {}
D api/starkingdoms_api_entities/src/lib.rs => api/starkingdoms_api_entities/src/lib.rs +0 -1
@@ 1,1 0,0 @@
-pub mod entity;
D api/starkingdoms_api_migration/.env => api/starkingdoms_api_migration/.env +0 -1
@@ 1,1 0,0 @@
-DATABASE_URL=postgres://postgres@localhost/starkingdoms>
\ No newline at end of file
D api/starkingdoms_api_migration/Cargo.toml => api/starkingdoms_api_migration/Cargo.toml +0 -24
@@ 1,24 0,0 @@
-[package]
-name = "starkingdoms_api_migration"
-version = "0.1.0"
-edition = "2021"
-publish = false
-
-[lib]
-name = "starkingdoms_api_migration"
-path = "src/lib.rs"
-
-[dependencies]
-async-std = { version = "1", features = ["attributes", "tokio1"] }
-
-[dependencies.sea-orm-migration]
-version = "0.11.0"
-features = [
- # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI.
- # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime.
- # e.g.
- # "runtime-tokio-rustls", # `ASYNC_RUNTIME` feature
- # "sqlx-postgres", # `DATABASE_DRIVER` feature
- "runtime-tokio-rustls",
- "sqlx-postgres"
-]
D api/starkingdoms_api_migration/README.md => api/starkingdoms_api_migration/README.md +0 -41
@@ 1,41 0,0 @@
-# Running Migrator CLI
-
-- Generate a new migration file
- ```sh
- cargo run -- migrate generate MIGRATION_NAME
- ```
-- Apply all pending migrations
- ```sh
- cargo run
- ```
- ```sh
- cargo run -- up
- ```
-- Apply first 10 pending migrations
- ```sh
- cargo run -- up -n 10
- ```
-- Rollback last applied migrations
- ```sh
- cargo run -- down
- ```
-- Rollback last 10 applied migrations
- ```sh
- cargo run -- down -n 10
- ```
-- Drop all tables from the database, then reapply all migrations
- ```sh
- cargo run -- fresh
- ```
-- Rollback all applied migrations, then reapply all migrations
- ```sh
- cargo run -- refresh
- ```
-- Rollback all applied migrations
- ```sh
- cargo run -- reset
- ```
-- Check the status of all migrations
- ```sh
- cargo run -- status
- ```
D api/starkingdoms_api_migration/src/lib.rs => api/starkingdoms_api_migration/src/lib.rs +0 -18
@@ 1,18 0,0 @@
-pub use sea_orm_migration::prelude::*;
-
-pub mod m20230417_162824_create_table_users;
-pub mod m20230417_164240_create_table_user_auth_realms;
-pub mod m20230420_144333_create_table_user_data;
-
-pub struct Migrator;
-
-#[async_trait::async_trait]
-impl MigratorTrait for Migrator {
- fn migrations() -> Vec<Box<dyn MigrationTrait>> {
- vec![
- Box::new(m20230417_162824_create_table_users::Migration),
- Box::new(m20230417_164240_create_table_user_auth_realms::Migration),
- Box::new(m20230420_144333_create_table_user_data::Migration),
- ]
- }
-}
D api/starkingdoms_api_migration/src/m20230417_162824_create_table_users.rs => api/starkingdoms_api_migration/src/m20230417_162824_create_table_users.rs +0 -40
@@ 1,40 0,0 @@
-use sea_orm_migration::prelude::*;
-
-#[derive(DeriveMigrationName)]
-pub struct Migration;
-
-#[async_trait::async_trait]
-impl MigrationTrait for Migration {
- async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- manager
- .create_table(
- Table::create()
- .table(User::Table)
- .col(ColumnDef::new(User::Id).string().primary_key().not_null())
- .col(
- ColumnDef::new(User::Username)
- .string()
- .unique_key()
- .not_null(),
- )
- .col(ColumnDef::new(User::CreatedOn).big_unsigned().not_null())
- .to_owned(),
- )
- .await
- }
-
- async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- manager
- .drop_table(Table::drop().table(User::Table).to_owned())
- .await
- }
-}
-
-/// Learn more at https://docs.rs/sea-query#iden
-#[derive(Iden)]
-pub enum User {
- Table,
- Id,
- Username,
- CreatedOn,
-}
D api/starkingdoms_api_migration/src/m20230417_164240_create_table_user_auth_realms.rs => api/starkingdoms_api_migration/src/m20230417_164240_create_table_user_auth_realms.rs +0 -52
@@ 1,52 0,0 @@
-use crate::m20230417_162824_create_table_users::User;
-use sea_orm_migration::prelude::*;
-
-#[derive(DeriveMigrationName)]
-pub struct Migration;
-
-#[async_trait::async_trait]
-impl MigrationTrait for Migration {
- async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- manager
- .create_table(
- Table::create()
- .table(UserAuthRealm::Table)
- .col(
- ColumnDef::new(UserAuthRealm::Id)
- .string()
- .not_null()
- .primary_key(),
- )
- .col(ColumnDef::new(UserAuthRealm::Realm).string().not_null())
- .col(
- ColumnDef::new(UserAuthRealm::RealmNativeId)
- .string()
- .not_null(),
- )
- .col(ColumnDef::new(UserAuthRealm::User).string().not_null())
- .foreign_key(
- ForeignKey::create()
- .from(UserAuthRealm::Table, UserAuthRealm::User)
- .to(User::Table, User::Id),
- )
- .to_owned(),
- )
- .await
- }
-
- async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- manager
- .drop_table(Table::drop().table(UserAuthRealm::Table).to_owned())
- .await
- }
-}
-
-/// Learn more at https://docs.rs/sea-query#iden
-#[derive(Iden)]
-pub enum UserAuthRealm {
- Table,
- Id,
- Realm,
- RealmNativeId,
- User,
-}
D api/starkingdoms_api_migration/src/m20230420_144333_create_table_user_data.rs => api/starkingdoms_api_migration/src/m20230420_144333_create_table_user_data.rs +0 -53
@@ 1,53 0,0 @@
-use crate::m20230417_164240_create_table_user_auth_realms::UserAuthRealm;
-use sea_orm_migration::prelude::*;
-
-#[derive(DeriveMigrationName)]
-pub struct Migration;
-
-#[async_trait::async_trait]
-impl MigrationTrait for Migration {
- async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- manager
- .create_table(
- Table::create()
- .table(UserSavefile::Table)
- .col(
- ColumnDef::new(UserSavefile::Id)
- .string()
- .not_null()
- .primary_key(),
- )
- .col(ColumnDef::new(UserSavefile::User).string().not_null())
- .col(ColumnDef::new(UserSavefile::Data).string().not_null())
- .col(
- ColumnDef::new(UserSavefile::Timestamp)
- .big_unsigned()
- .not_null()
- .unique_key(),
- )
- .foreign_key(
- ForeignKey::create()
- .from(UserSavefile::Table, UserSavefile::User)
- .to(UserAuthRealm::Table, UserAuthRealm::Id),
- )
- .to_owned(),
- )
- .await
- }
-
- async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
- manager
- .drop_table(Table::drop().table(UserSavefile::Table).to_owned())
- .await
- }
-}
-
-/// Learn more at https://docs.rs/sea-query#iden
-#[derive(Iden)]
-pub enum UserSavefile {
- Table,
- Id,
- User,
- Data,
- Timestamp,
-}
D api/starkingdoms_api_migration/src/main.rs => api/starkingdoms_api_migration/src/main.rs +0 -6
@@ 1,6 0,0 @@
-use sea_orm_migration::prelude::*;
-
-#[async_std::main]
-async fn main() {
- cli::run_cli(starkingdoms_api_migration::Migrator).await;
-}
D api/static/all.css => api/static/all.css +0 -0
D api/templates/base.tera => api/templates/base.tera +0 -14
@@ 1,14 0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- {% block head %}
- <title>{% block title %}StarKingdoms API{% endblock title %}</title>
- <link rel="stylesheet" href="/static/all.css"/>
- {% endblock head %}
- </head>
- <body>
- {% block body %}
- <p>This page has no content.</p>
- {% endblock body %}
- </body>
-</html>>
\ No newline at end of file
D api/templates/select_realm.tera => api/templates/select_realm.tera +0 -11
@@ 1,11 0,0 @@
-{% extends "base.tera" %}
-
-{% block title %}Select Auth Provider{% endblock title %}
-
-{% block body %}
- <h1>Select Authentication Provider</h1>
- {% 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 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