mirror of
https://git.ngram.ca/OpenJam/rc-servers
synced 2026-08-23 23:08:52 +00:00
Add basic RC steam and user/pass authentication
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
|
||||||
|
steam_appid.txt
|
||||||
|
|||||||
79
Cargo.lock
generated
79
Cargo.lock
generated
@@ -160,6 +160,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"hex",
|
"hex",
|
||||||
|
"jsonwebtoken",
|
||||||
"libfj",
|
"libfj",
|
||||||
"log",
|
"log",
|
||||||
"rocket",
|
"rocket",
|
||||||
@@ -188,6 +189,12 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64"
|
name = "base64"
|
||||||
version = "0.22.1"
|
version = "0.22.1"
|
||||||
@@ -1033,6 +1040,21 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"js-sys",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lazy_static"
|
name = "lazy_static"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@@ -1050,7 +1072,7 @@ name = "libfj"
|
|||||||
version = "0.7.5"
|
version = "0.7.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"cgmath 0.18.0",
|
"cgmath 0.18.0",
|
||||||
"chrono",
|
"chrono",
|
||||||
"genmesh",
|
"genmesh",
|
||||||
@@ -1195,12 +1217,31 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-bigint"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
|
||||||
|
dependencies = [
|
||||||
|
"num-integer",
|
||||||
|
"num-traits 0.2.19",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-conv"
|
name = "num-conv"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-integer"
|
||||||
|
version = "0.1.46"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits 0.2.19",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.1.43"
|
version = "0.1.43"
|
||||||
@@ -1323,6 +1364,16 @@ dependencies = [
|
|||||||
"syn 2.0.96",
|
"syn 2.0.96",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@@ -1507,6 +1558,16 @@ dependencies = [
|
|||||||
"getrandom",
|
"getrandom",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rc_static_data"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"env_logger",
|
||||||
|
"libfj",
|
||||||
|
"log",
|
||||||
|
"rocket",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rdrand"
|
name = "rdrand"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
@@ -1595,7 +1656,7 @@ version = "0.12.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
|
checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@@ -1903,6 +1964,18 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits 0.2.19",
|
||||||
|
"thiserror 2.0.11",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -2388,7 +2461,7 @@ version = "2.12.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.22.1",
|
||||||
"flate2",
|
"flate2",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ edition = "2021"
|
|||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"auth",
|
"auth", "rc_static_data",
|
||||||
]
|
]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -6,11 +6,20 @@ A collection of open source servers for FreeJam games
|
|||||||
|
|
||||||
### CardLife
|
### CardLife
|
||||||
|
|
||||||
To use get CardLife to use these servers, replace the ServerConfig.json file in the game files with [this ServerConfig.json](assets/cardlife/ServerConfig.json).
|
To get CardLife to use these servers, replace the ServerConfig.json file in the game files with [this ServerConfig.json](assets/cardlife/ServerConfig.json).
|
||||||
|
|
||||||
### Robocraft
|
### Robocraft
|
||||||
|
|
||||||
??? (This might require modifying `/etc/hosts`... remember to mention the Windows equivalent)
|
To get Robocraft to use these servers, please add the following to your OS's `hosts` file:
|
||||||
|
|
||||||
|
```
|
||||||
|
127.0.0.1 robocraftstaticdata.s3.amazonaws.com
|
||||||
|
127.0.0.1 services-1.servers.robocraftgame.com
|
||||||
|
```
|
||||||
|
|
||||||
|
The `hosts` file can be found at `/etc/hosts` on Linux and `C:\Windows\system32\drivers\etc\hosts` on Windows. Usually this requires elevated permissions (root/admin) to edit.
|
||||||
|
|
||||||
|
// TODO Don't expect people to run these servers on their own computer
|
||||||
|
|
||||||
## Privacy
|
## Privacy
|
||||||
|
|
||||||
|
|||||||
51
assets/robocraft/auth_notes.md
Normal file
51
assets/robocraft/auth_notes.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Login
|
||||||
|
|
||||||
|
Seems to use `Login.RoboAuthService`.
|
||||||
|
|
||||||
|
These are mostly assumptions and untested.
|
||||||
|
|
||||||
|
## Standard login
|
||||||
|
|
||||||
|
Username/password login calls `AuthWithIdEnumerator(string identifier, string password, Dictionary<string, object> dataToReturn)`, which instantiates the class `#=zTk$8WYeWP0gxjV8edvqGncg=` (nested class in `RoboAuthService`). It sets the following variables:
|
||||||
|
|
||||||
|
```
|
||||||
|
#=zTk$8WYeWP0gxjV8edvqGncg=.#=zOuYEgQM= = identifier;
|
||||||
|
#=zTk$8WYeWP0gxjV8edvqGncg=.#=zBLK$yEM= = password;
|
||||||
|
#=zTk$8WYeWP0gxjV8edvqGncg=.#=zmTDf_G1vkOnQ = dataToReturn;
|
||||||
|
```
|
||||||
|
|
||||||
|
`identifier` is presumably user display name. No idea what `dataToReturn` contains.
|
||||||
|
|
||||||
|
## Steam Login
|
||||||
|
|
||||||
|
The first time, steam login calls `RegisterAndAuthenticateSteam(string validDisplayName, string ticket, Action<Dictionary<string, object>> onAuthSuccess, Action<Exception> onError)`, which calls `#=zeCm7CtCIMhaXGczndJSgBvo=(...)`, which instantiates `#=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==` (nested class in `RoboAuthService`) and schedules it in the TaskRunner (it's an enumerator).
|
||||||
|
|
||||||
|
```
|
||||||
|
// in RegisterAndAuthenticateSteam
|
||||||
|
RoboAuthService.#=zeCm7CtCIMhaXGczndJSgBvo=(validDisplayName, ticket, onAuthSuccess, onError);
|
||||||
|
|
||||||
|
// which calls
|
||||||
|
[DebuggerHidden]
|
||||||
|
private static IEnumerator #=zeCm7CtCIMhaXGczndJSgBvo=(string #=zgoyh9IIuSHQ$, string #=zI$MKIcRAkgM$, Action<Dictionary<string, object>> #=zmoN68pYsK05$, Action<Exception> #=zN4ekGCs=)
|
||||||
|
{
|
||||||
|
#=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg== #=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg== = new #=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==();
|
||||||
|
#=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==.#=zI$MKIcRAkgM$ = #=zI$MKIcRAkgM$;
|
||||||
|
#=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==.#=zgoyh9IIuSHQ$ = #=zgoyh9IIuSHQ$;
|
||||||
|
#=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==.#=zmoN68pYsK05$ = #=zmoN68pYsK05$;
|
||||||
|
#=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==.#=zN4ekGCs= = #=zN4ekGCs=;
|
||||||
|
return #=zudtivLXmdm5aL7OqfX$JZA2ozi4nV_Rakg==;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If it's not the first time, steam login calls `AuthenticateSteamUser(Action<Dictionary<string, object>> onSuccess, Action onFailure, Action<Exception> onError)`, which calls `AuthenticateSteamUserInternal(...)` and then schedules the task (IEnumerator is returned).
|
||||||
|
|
||||||
|
```
|
||||||
|
[DebuggerHidden]
|
||||||
|
public static IEnumerator AuthenticateSteamUserInternal(Action<Dictionary<string, object>> onSuccess, Action onFailure, Action<Exception> onError)
|
||||||
|
{
|
||||||
|
#=zxQRony8oFWvBWu4O0RraT0fsXcjv #=zxQRony8oFWvBWu4O0RraT0fsXcjv = new #=zxQRony8oFWvBWu4O0RraT0fsXcjv();
|
||||||
|
#=zxQRony8oFWvBWu4O0RraT0fsXcjv.#=zmoN68pYsK05$ = onSuccess;
|
||||||
|
#=zxQRony8oFWvBWu4O0RraT0fsXcjv.#=zN4ekGCs= = onError;
|
||||||
|
return #=zxQRony8oFWvBWu4O0RraT0fsXcjv;
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -5,7 +5,9 @@ edition = "2021"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
steam = ["steamworks"]
|
steam = ["steamworks"]
|
||||||
default = []
|
robocraft = []
|
||||||
|
cardlife = []
|
||||||
|
default = ["robocraft", "cardlife"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket.workspace = true
|
rocket.workspace = true
|
||||||
@@ -15,3 +17,4 @@ env_logger.workspace = true
|
|||||||
uuid = { version = "1.12", features = [ "v4", "fast-rng" ] }
|
uuid = { version = "1.12", features = [ "v4", "fast-rng" ] }
|
||||||
steamworks = { version = "0.11", optional = true }
|
steamworks = { version = "0.11", optional = true }
|
||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
|
jsonwebtoken = "9"
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ mod temporary_get_user_id;
|
|||||||
mod token;
|
mod token;
|
||||||
|
|
||||||
pub fn stage() -> rocket::fairing::AdHoc {
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
rocket::fairing::AdHoc::on_ignite("JSON", |rocket| async {
|
rocket::fairing::AdHoc::on_ignite("cardlife", |rocket| async {
|
||||||
rocket.attach(email::stage())
|
rocket.attach(email::stage())
|
||||||
.attach(steam::stage())
|
.attach(steam::stage())
|
||||||
.attach(temporary_get_user_id::stage())
|
.attach(temporary_get_user_id::stage())
|
||||||
|
|||||||
@@ -1,86 +1,34 @@
|
|||||||
use rocket::{post, routes, serde::json::Json, http::Status};
|
use rocket::{post, routes, serde::json::Json, http::Status};
|
||||||
|
|
||||||
#[cfg(feature = "steam")]
|
|
||||||
static STEAM_SERVER: std::sync::OnceLock<steamworks::Server> = OnceLock::new();
|
|
||||||
|
|
||||||
#[cfg(feature = "steam")]
|
|
||||||
fn init_steam() -> steamworks::Server {
|
|
||||||
let (server, single_client) = steamworks::Server::init(core::net::Ipv4Addr::new(127, 0, 0, 1), 9000, 9001, steamworks::ServerMode::NoAuthentication, "")
|
|
||||||
.expect("Steam is unavailable");
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
single_client.run_callbacks();
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
server.set_product("920690");
|
|
||||||
server.log_on_anonymous();
|
|
||||||
server
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_u64_with_offset(arr: &[u8], start: usize) -> u64 {
|
|
||||||
let arr_actual: [u8; 8] = [
|
|
||||||
arr[start],
|
|
||||||
arr[start+1],
|
|
||||||
arr[start+2],
|
|
||||||
arr[start+3],
|
|
||||||
arr[start+4],
|
|
||||||
arr[start+5],
|
|
||||||
arr[start+6],
|
|
||||||
arr[start+7],
|
|
||||||
];
|
|
||||||
u64::from_le_bytes(arr_actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[post("/api/auth/steamauthenticate", data = "<body>")]
|
#[post("/api/auth/steamauthenticate", data = "<body>")]
|
||||||
pub fn steam_auth(body: Json<libfj::cardlife::SteamAuthenticationPayload>) -> Result<Json<libfj::cardlife::AuthenticationInfo>, Status> {
|
pub fn steam_auth(body: Json<libfj::cardlife::SteamAuthenticationPayload>) -> Result<Json<libfj::cardlife::AuthenticationInfo>, Status> {
|
||||||
log::debug!("steam ticket: {}", body.steam_ticket);
|
log::debug!("steam ticket: {}", body.steam_ticket);
|
||||||
match hex::decode(&body.steam_ticket) {
|
let steam_id = crate::common::steam_utils::authenticate_steam_ticket(&body.steam_ticket)
|
||||||
Ok(ticket) => {
|
.map_err(|_| Status { code: 400 })?;
|
||||||
if ticket.len() < 72 {
|
log::debug!("Found steamId {}", steam_id);
|
||||||
return Err(Status { code: 400 })
|
Ok(Json(libfj::cardlife::AuthenticationInfo {
|
||||||
}
|
public_id: uuid::Uuid::from_u64_pair(steam_id, steam_id).to_string(),
|
||||||
let steam_id = get_u64_with_offset(&ticket, 12 /* also at 64 ??? */); // should be 76600000000000000 > number > 76500000000000000
|
email_address: "nobody@openjamgames.com".to_string(),
|
||||||
log::debug!("Found steamId {}", steam_id);
|
display_name: steam_id.to_string(),
|
||||||
#[cfg(feature = "steam")]
|
purchases: vec![1, 2, 3],
|
||||||
{
|
flags: vec![],
|
||||||
let steam = STEAM_SERVER.get_or_init(init_steam);
|
/*flags: //vec![
|
||||||
if let Err(e) = steam.begin_authentication_session(steamworks::SteamId::from_raw(steam_id), &ticket) {
|
"Dev".to_string(),
|
||||||
log::error!("steam server auth session error: {}", e);
|
"GiveInv".to_string(),
|
||||||
return Err(Status { code: 400 })
|
"NoDrop".to_string(),
|
||||||
}
|
"DelStruct".to_string(),
|
||||||
}
|
"BucketA".to_string(),
|
||||||
Ok(Json(libfj::cardlife::AuthenticationInfo {
|
"BucketB".to_string(),
|
||||||
public_id: uuid::Uuid::from_u64_pair(steam_id, steam_id).to_string(),
|
"userlogged".to_string(),
|
||||||
email_address: "nobody@openjamgames.com".to_string(),
|
],*/
|
||||||
display_name: steam_id.to_string(),
|
confirmed: true,
|
||||||
purchases: vec![1, 2, 3],
|
token: uuid::Uuid::from_u64_pair(steam_id, steam_id).to_string(),
|
||||||
flags: vec![],
|
steam_id: Some(steam_id.to_string()),
|
||||||
/*flags: //vec![
|
id: (steam_id & (i32::MAX as u64)) as i32,
|
||||||
"Dev".to_string(),
|
}))
|
||||||
"GiveInv".to_string(),
|
|
||||||
"NoDrop".to_string(),
|
|
||||||
"DekStruct".to_string(),
|
|
||||||
"BucketA".to_string(),
|
|
||||||
"BucketB".to_string(),
|
|
||||||
"userlogged".to_string(),
|
|
||||||
],*/
|
|
||||||
confirmed: true,
|
|
||||||
token: uuid::Uuid::from_u64_pair(steam_id, steam_id).to_string(),
|
|
||||||
steam_id: Some(steam_id.to_string()),
|
|
||||||
id: (steam_id & (i32::MAX as u64)) as i32,
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
log::error!("request with bad steam ticket: {}", e);
|
|
||||||
Err(Status { code: 400 })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stage() -> rocket::fairing::AdHoc {
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
#[cfg(feature = "steam")]
|
|
||||||
STEAM_SERVER.get_or_init(init_steam);
|
|
||||||
rocket::fairing::AdHoc::on_ignite("CardLife Steam", |rocket| async {
|
rocket::fairing::AdHoc::on_ignite("CardLife Steam", |rocket| async {
|
||||||
rocket.mount("/", routes![steam_auth])
|
rocket.mount("/", routes![steam_auth])
|
||||||
})
|
})
|
||||||
|
|||||||
1
auth/src/common/mod.rs
Normal file
1
auth/src/common/mod.rs
Normal file
@@ -0,0 +1 @@
|
|||||||
|
pub(crate) mod steam_utils;
|
||||||
90
auth/src/common/steam_utils.rs
Normal file
90
auth/src/common/steam_utils.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
fn get_u64_with_offset(arr: &[u8], start: usize) -> u64 {
|
||||||
|
let arr_actual: [u8; 8] = [
|
||||||
|
arr[start],
|
||||||
|
arr[start+1],
|
||||||
|
arr[start+2],
|
||||||
|
arr[start+3],
|
||||||
|
arr[start+4],
|
||||||
|
arr[start+5],
|
||||||
|
arr[start+6],
|
||||||
|
arr[start+7],
|
||||||
|
];
|
||||||
|
u64::from_le_bytes(arr_actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_steam_id_from_ticket_hex(hex_ticket: &str) -> Result<u64, hex::FromHexError> {
|
||||||
|
let decoded_ticket = hex::decode(hex_ticket)?;
|
||||||
|
if decoded_ticket.len() < 72 {
|
||||||
|
Err(hex::FromHexError::InvalidStringLength)
|
||||||
|
} else {
|
||||||
|
Ok(get_steam_id_from_ticket(&decoded_ticket))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn get_steam_id_from_ticket(ticket: &[u8]) -> u64 {
|
||||||
|
get_u64_with_offset(&ticket, 12 /* also at 64 ??? */) // should be 76600000000000000 > number > 76500000000000000
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "steam", feature = "cardlife"))]
|
||||||
|
const STEAM_ID: &str = "920690"; // Cardlife steam app id
|
||||||
|
|
||||||
|
#[cfg(all(feature = "steam", feature = "robocraft"))]
|
||||||
|
const STEAM_ID: &str = "301520"; // Robocraft steam app id
|
||||||
|
|
||||||
|
#[cfg(feature = "steam")]
|
||||||
|
static STEAM_SERVER: std::sync::OnceLock<steamworks::Server> = std::sync::OnceLock::new();
|
||||||
|
|
||||||
|
#[cfg(feature = "steam")]
|
||||||
|
fn init_steam() -> steamworks::Server {
|
||||||
|
if let Err(e) = std::fs::write::<&str, &[u8]>("./steam_appid.txt", STEAM_ID.as_ref()) {
|
||||||
|
log::error!("Failed to write appId to steam_appid.txt: {}", e);
|
||||||
|
}
|
||||||
|
let (server, single_client) = steamworks::Server::init(core::net::Ipv4Addr::new(127, 0, 0, 1), 9000, 9001, steamworks::ServerMode::NoAuthentication, "")
|
||||||
|
.expect("Steam is unavailable");
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
single_client.run_callbacks();
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(10));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.set_product(STEAM_ID);
|
||||||
|
server.log_on_anonymous();
|
||||||
|
server
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "steam")]
|
||||||
|
fn get_steam() -> &'static steamworks::Server {
|
||||||
|
STEAM_SERVER.get_or_init(init_steam)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "steam"))]
|
||||||
|
pub fn authenticate_steam_ticket(hex_ticket: &str) -> Result<u64, ()> {
|
||||||
|
get_steam_id_from_ticket_hex(hex_ticket)
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to parse steamId: {}", e);
|
||||||
|
()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "steam")]
|
||||||
|
pub fn authenticate_steam_ticket(hex_ticket: &str) -> Result<u64, ()> {
|
||||||
|
let decoded_ticket = hex::decode(hex_ticket)
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("Failed to decode hexadecimal steam ticket: {}", e);
|
||||||
|
()
|
||||||
|
})?;
|
||||||
|
if decoded_ticket.len() < 72 {
|
||||||
|
log::error!("Failed to parse steamId: ticket too short");
|
||||||
|
return Err(())
|
||||||
|
}
|
||||||
|
let steam_id = get_steam_id_from_ticket(&decoded_ticket);
|
||||||
|
let steam = get_steam();
|
||||||
|
steam.begin_authentication_session(steamworks::SteamId::from_raw(steam_id), &decoded_ticket)
|
||||||
|
.map_err(|e| {
|
||||||
|
log::error!("steam server auth session error: {}", e);
|
||||||
|
()
|
||||||
|
})?;
|
||||||
|
Ok(steam_id)
|
||||||
|
}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
|
mod common;
|
||||||
|
|
||||||
|
#[cfg(feature = "cardlife")]
|
||||||
mod cardlife;
|
mod cardlife;
|
||||||
|
#[cfg(feature = "robocraft")]
|
||||||
|
mod robocraft;
|
||||||
|
|
||||||
#[rocket::get("/")]
|
#[rocket::get("/")]
|
||||||
fn index() -> &'static str {
|
fn index() -> &'static str {
|
||||||
@@ -8,7 +13,17 @@ fn index() -> &'static str {
|
|||||||
#[rocket::launch]
|
#[rocket::launch]
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
rocket::build().mount("/", rocket::routes![index])
|
#[allow(unused_mut)]
|
||||||
.attach(cardlife::stage())
|
let mut builder = rocket::build().mount("/", rocket::routes![index]);
|
||||||
|
|
||||||
|
#[cfg(feature = "cardlife")]
|
||||||
|
{builder = builder.attach(cardlife::stage());}
|
||||||
|
#[cfg(feature = "robocraft")]
|
||||||
|
{builder = builder.attach(robocraft::stage());}
|
||||||
|
|
||||||
|
builder
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "steam", feature = "robocraft", feature = "cardlife"))]
|
||||||
|
compile_error!("Feature \"steam\" cannot work with features \"cardlife\" and \"robocraft\" at the same time");
|
||||||
|
|
||||||
|
|||||||
13
auth/src/robocraft/debug.rs
Normal file
13
auth/src/robocraft/debug.rs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
use rocket::{post, routes};
|
||||||
|
|
||||||
|
#[post("/", data = "<body>")]
|
||||||
|
pub fn debug_endpoint(body: String) -> String {
|
||||||
|
log::info!("got body: `{}`", body);
|
||||||
|
body.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("Robocraft debug", |rocket| async {
|
||||||
|
rocket.mount("/", routes![debug_endpoint])
|
||||||
|
})
|
||||||
|
}
|
||||||
39
auth/src/robocraft/email.rs
Normal file
39
auth/src/robocraft/email.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
use rocket::{post, routes, serde::json::Json, http::Status};
|
||||||
|
|
||||||
|
fn generate_token(user_auth: &libfj::robocraft::EmailUserAuthenticationPayload) -> String {
|
||||||
|
let header = jsonwebtoken::Header {
|
||||||
|
typ: Some("JWT".to_string()),
|
||||||
|
alg: jsonwebtoken::Algorithm::HS256,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let payload = libfj::robocraft::TokenPayload {
|
||||||
|
public_id: user_auth.display_name.to_owned(),
|
||||||
|
display_name: user_auth.display_name.to_owned(),
|
||||||
|
robocraft_name: user_auth.display_name.to_owned(),
|
||||||
|
email_address: user_auth.email_address.to_owned(),
|
||||||
|
email_verified: true,
|
||||||
|
flags: Vec::new(),
|
||||||
|
};
|
||||||
|
let secret = jsonwebtoken::EncodingKey::from_secret(user_auth.password.as_bytes()); // FIXME use an actually secret secret
|
||||||
|
jsonwebtoken::encode(&header, &payload, &secret)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to encode JWT: {}", e);
|
||||||
|
libfj::robocraft::DEFAULT_TOKEN.to_owned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/authenticate/robocraft/game", data = "<body>")]
|
||||||
|
pub fn email_password_auth(body: Json<libfj::robocraft::EmailUserAuthenticationPayload>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||||
|
log::info!("Authenticating {} user {}", body.target, body.display_name);
|
||||||
|
Ok(Json(libfj::robocraft::AuthenticationResponseInfo {
|
||||||
|
token: generate_token(&body),
|
||||||
|
refresh_token: "qwertyuiop".to_string(), // TODO
|
||||||
|
refresh_token_expiry: "0".to_string(), // TODO (seems like this isn't actually considered by the client)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("Robocraft Email/Password", |rocket| async {
|
||||||
|
rocket.mount("/", routes![email_password_auth])
|
||||||
|
})
|
||||||
|
}
|
||||||
11
auth/src/robocraft/mod.rs
Normal file
11
auth/src/robocraft/mod.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
mod debug;
|
||||||
|
mod email;
|
||||||
|
mod steam;
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("robocraft", |rocket| async {
|
||||||
|
rocket.attach(email::stage())
|
||||||
|
.attach(steam::stage())
|
||||||
|
.attach(debug::stage())
|
||||||
|
})
|
||||||
|
}
|
||||||
41
auth/src/robocraft/steam.rs
Normal file
41
auth/src/robocraft/steam.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use rocket::{post, routes, serde::json::Json, http::Status};
|
||||||
|
|
||||||
|
fn generate_token(user_auth: &libfj::robocraft::SteamAuthenticationPayload, steam_id: u64) -> String {
|
||||||
|
let header = jsonwebtoken::Header {
|
||||||
|
typ: Some("JWT".to_string()),
|
||||||
|
alg: jsonwebtoken::Algorithm::HS256,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let payload = libfj::robocraft::TokenPayload {
|
||||||
|
public_id: steam_id.to_string(),
|
||||||
|
display_name: steam_id.to_string(),
|
||||||
|
robocraft_name: steam_id.to_string(),
|
||||||
|
email_address: format!("{}.rc.steam@ngni.us", steam_id),
|
||||||
|
email_verified: true,
|
||||||
|
flags: Vec::new(),
|
||||||
|
};
|
||||||
|
let secret = jsonwebtoken::EncodingKey::from_secret(user_auth.steam_ticket.as_ref()); // FIXME use an actually secret secret
|
||||||
|
jsonwebtoken::encode(&header, &payload, &secret)
|
||||||
|
.unwrap_or_else(|e| {
|
||||||
|
log::error!("Failed to encode JWT: {}", e);
|
||||||
|
libfj::robocraft::DEFAULT_TOKEN.to_owned()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/authenticate/steam/game", data = "<body>")]
|
||||||
|
pub fn steam_auth(body: Json<libfj::robocraft::SteamAuthenticationPayload>) -> Result<Json<libfj::robocraft::AuthenticationResponseInfo>, Status> {
|
||||||
|
let steam_id = crate::common::steam_utils::authenticate_steam_ticket(&body.steam_ticket)
|
||||||
|
.map_err(|_| Status { code: 401 })?;
|
||||||
|
log::info!("Authenticating {} steam user {}", body.target, steam_id);
|
||||||
|
Ok(Json(libfj::robocraft::AuthenticationResponseInfo {
|
||||||
|
token: generate_token(&body, steam_id),
|
||||||
|
refresh_token: "qwertyuiop".to_string(), // TODO
|
||||||
|
refresh_token_expiry: "0".to_string(), // TODO (seems like this isn't actually considered by the client)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("Robocraft Steam", |rocket| async {
|
||||||
|
rocket.mount("/", routes![steam_auth])
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
920690
|
|
||||||
10
rc_static_data/Cargo.toml
Normal file
10
rc_static_data/Cargo.toml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
[package]
|
||||||
|
name = "rc_static_data"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rocket.workspace = true
|
||||||
|
libfj.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
12
rc_static_data/Rocket.toml
Normal file
12
rc_static_data/Rocket.toml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
## defaults for _all_ profiles
|
||||||
|
[default]
|
||||||
|
address = "127.0.0.1"
|
||||||
|
port = 8010
|
||||||
|
|
||||||
|
## set only when compiled in debug mode, i.e, `cargo build`
|
||||||
|
[debug]
|
||||||
|
port = 80
|
||||||
|
|
||||||
|
## set only when compiled in release mode, i.e, `cargo build --release`
|
||||||
|
[release]
|
||||||
|
address = "0.0.0.0"
|
||||||
3
rc_static_data/build_arm64.sh
Executable file
3
rc_static_data/build_arm64.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
cargo build --release --target aarch64-unknown-linux-musl
|
||||||
3
rc_static_data/run_debug.sh
Executable file
3
rc_static_data/run_debug.sh
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cargo build
|
||||||
|
RUST_LOG=debug sudo -HE ../target/debug/rc_static_data
|
||||||
14
rc_static_data/src/main.rs
Normal file
14
rc_static_data/src/main.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
mod robocraft;
|
||||||
|
|
||||||
|
#[rocket::get("/")]
|
||||||
|
fn index() -> &'static str {
|
||||||
|
"Hello, world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
#[rocket::launch]
|
||||||
|
fn rocket() -> _ {
|
||||||
|
env_logger::init();
|
||||||
|
rocket::build().mount("/", rocket::routes![index])
|
||||||
|
.attach(robocraft::stage())
|
||||||
|
}
|
||||||
|
|
||||||
58
rc_static_data/src/robocraft/live_data.rs
Normal file
58
rc_static_data/src/robocraft/live_data.rs
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
use rocket::{get, routes, serde::json::Json};
|
||||||
|
|
||||||
|
#[get("/live/data.json")]
|
||||||
|
pub fn static_live_data() -> Json<libfj::robocraft::StaticDataRaw> {
|
||||||
|
Json(libfj::robocraft::StaticDataRaw {
|
||||||
|
MaintenanceMode: "false".into(),
|
||||||
|
MaintenanceRegex: "".into(),
|
||||||
|
EacEnabled: "true".into(),
|
||||||
|
MinimumVersion: "2855".into(),
|
||||||
|
PhotonSocialServer: "rc-backend.servers.robocraftgame.com:4534".into(),
|
||||||
|
PhotonServicesServer: "rc-backend.servers.robocraftgame.com:4532".into(),
|
||||||
|
PhotonChatServer: "rc-backend.servers.robocraftgame.com:4530".into(),
|
||||||
|
PhotonSinglePlayerServer: "rc-backend.servers.robocraftgame.com:4536".into(),
|
||||||
|
GameplayServerServiceAddress: "rc-backend.servers.robocraftgame.com:4538".into(),
|
||||||
|
PhotonLobbyServer: "rc-backend.servers.robocraftgame.com:4540".into(),
|
||||||
|
ErrorLogAddress: "logs.freejamgames.com:4561".into(),
|
||||||
|
ServerErrorLogAddress: "logs.freejamgames.com:4562".into(),
|
||||||
|
authUrl: "http://127.0.0.1:8001/".into(), // originally "https://auth-backend.freejamgames.com/"
|
||||||
|
paymentUrl: "https://pay.robocraftgame.com/".into(),
|
||||||
|
enterBattleLogGenerationTimeout: "60".into(),
|
||||||
|
GameServerConnectionTestTimeout: 10,
|
||||||
|
AvatarCdnUrl: "https://rc-cdn-images.robocraftgame.com/customavatar/Live/".into(),
|
||||||
|
ClanAvatarCdnUrl: "https://rc-cdn-images.robocraftgame.com/clanavatar/Live/".into(),
|
||||||
|
FeatureThrottlerOnPercent: "100".into(),
|
||||||
|
EmailCaptureEnabled: "true".into(),
|
||||||
|
UnreliableMessages: "true".into(),
|
||||||
|
MessageQueueEnabled: "true".into(),
|
||||||
|
BrawlDataUrl: "https://rc-cdn-images.robocraftgame.com/brawldata/Live/".into(),
|
||||||
|
CampaignDataUrl: "https://rc-cdn-images.robocraftgame.com/campaigndata/Live/".into(),
|
||||||
|
LeaderboardsUrl: "https://leaderboards.robocraftgame.com".into(),
|
||||||
|
NetworkChannelTypes: "3113".into(),
|
||||||
|
MaxSentMessageQueueSize: 64,
|
||||||
|
IsAcksLong: 1,
|
||||||
|
NetworkDropThreshold: 80,
|
||||||
|
PacketSize: 1200,
|
||||||
|
MaxPacketSize: 5888,
|
||||||
|
MaxCombinedReliableMessageCount: 20,
|
||||||
|
MaxCombinedReliableMessageSize: 200,
|
||||||
|
MaxDelay: 1,
|
||||||
|
OverflowThreshold: 10,
|
||||||
|
MinUpdateTimeout: 1,
|
||||||
|
DevMessageRefresh: 60,
|
||||||
|
MaintenanceRefresh: 30,
|
||||||
|
SaveRequestOnPhoton: "false".into(),
|
||||||
|
UseS3System: "true".into(),
|
||||||
|
authMigrationUrl: "http://88.150.159.132:3000/auth-migration".into(),
|
||||||
|
xsollaEnabled: "true".into(),
|
||||||
|
MaintenanceMessage: "Robocraft is currently undergoing server maintenance. ".into(),
|
||||||
|
DevMessage: "NGnius says hello".into(),
|
||||||
|
DevMessageDisplayTime: "10".into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("Robocraft live data.json", |rocket| async {
|
||||||
|
rocket.mount("/", routes![static_live_data])
|
||||||
|
})
|
||||||
|
}
|
||||||
7
rc_static_data/src/robocraft/mod.rs
Normal file
7
rc_static_data/src/robocraft/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod live_data;
|
||||||
|
|
||||||
|
pub fn stage() -> rocket::fairing::AdHoc {
|
||||||
|
rocket::fairing::AdHoc::on_ignite("JSON", |rocket| async {
|
||||||
|
rocket.attach(live_data::stage())
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user