Browse Source

Add RC account info

master
NGnius (Graham) 2 years ago
parent
commit
77ef47dce4
6 changed files with 205 additions and 4 deletions
  1. +4
    -2
      Cargo.toml
  2. +163
    -0
      src/robocraft/account.rs
  3. +3
    -0
      src/robocraft/mod.rs
  4. +2
    -1
      test.sh
  5. +32
    -0
      tests/robocraft_auth.rs
  6. +1
    -1
      tests/robocraft_factory.rs

+ 4
- 2
Cargo.toml View File

@@ -1,6 +1,6 @@
[package]
name = "libfj"
version = "0.5.1"
version = "0.5.2"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
edition = "2018"
description = "An unofficial collection of APIs used in FreeJam games and mods"
@@ -35,8 +35,10 @@ genmesh = {version = "^0.6", optional = true}
tokio = { version = "1.4.0", features = ["macros"]}

[features]
all = ["simple", "robocraft", "cardlife", "techblox", "convert"]
default = ["all"]
simple = ["ureq"]
robocraft = ["reqwest"]
robocraft = ["reqwest", "ureq"]
cardlife = ["reqwest"]
techblox = ["chrono", "highhash", "half", "libfj_parsable_macro_derive"]
convert = ["obj", "genmesh"]

+ 163
- 0
src/robocraft/account.rs View File

@@ -0,0 +1,163 @@
use serde::{Deserialize, Serialize};
use ureq::{Agent, Error};
use serde_json::{to_string, from_slice};

use crate::robocraft::ITokenProvider;

/// Token provider for an existing Robocraft account.
///
/// Steam accounts are not supported.
pub struct AuthenticatedTokenProvider {
/// The account's username
pub username: String,
/// The account's password
pub password: String,
/// Ureq HTTP client
client: Agent,
}

impl AuthenticatedTokenProvider {
pub fn with_email(email: &str, password: &str) -> Result<Self, Error> {
let client = Agent::new();
let payload = AuthenticationEmailPayload {
email_address: email.to_string(),
password: password.to_string(),
};
let response = client.post("https://account.freejamgames.com/api/authenticate/email/web")
.set("Content-Type", "application/json")
.send_string(&to_string(&payload).unwrap())?;
let json_res = response.into_json::<AuthenticationResponseInfo>()?;
Ok(Self {
username: json_res.decode_jwt_data().display_name,
password: password.to_string(),
client,
})
}

pub fn with_username(username: &str, password: &str) -> Result<Self, Error> {
let new_obj = Self {
username: username.to_string(),
password: password.to_string(),
client: Agent::new(),
};
new_obj.do_auth()?;
Ok(new_obj)
}

fn do_auth(&self) -> Result<AuthenticationResponseInfo, Error> {
let payload = AuthenticationUsernamePayload {
username: self.username.clone(),
password: self.password.clone(),
};
let response = self.client.post("https://account.freejamgames.com/api/authenticate/displayname/web")
.set("Content-Type", "application/json")
.send_string(&to_string(&payload).unwrap())?;
let json_res = response.into_json::<AuthenticationResponseInfo>()?;
Ok(json_res)
}

pub fn get_account_info(&self) -> Result<AccountInfo, Error> {
let json_res = self.do_auth()?;
Ok(json_res.decode_jwt_data())
}
}

impl ITokenProvider for AuthenticatedTokenProvider {
fn token(&self) -> Result<String, ()> {
let json_res = self.do_auth().map_err(|_|())?;
Ok(json_res.token)
}
}

#[derive(Deserialize, Serialize, Clone)]
pub(crate) struct AuthenticationEmailPayload {
#[serde(rename = "EmailAddress")]
pub email_address: String,
#[serde(rename = "Password")]
pub password: String,
}

#[derive(Deserialize, Serialize, Clone)]
pub(crate) struct AuthenticationUsernamePayload {
#[serde(rename = "DisplayName")]
pub username: String,
#[serde(rename = "Password")]
pub password: String,
}

#[derive(Deserialize, Serialize, Clone)]
pub(crate) struct AuthenticationResponseInfo {
#[serde(rename = "Token")]
pub token: String,
#[serde(rename = "RefreshToken")]
pub refresh_token: String,
#[serde(rename = "RefreshTokenExpiry")]
pub refresh_token_expiry: String,
}

impl AuthenticationResponseInfo {
pub fn decode_jwt_data(&self) -> AccountInfo {
// Refer to https://jwt.io/
// header is before dot, signature is after dot.
// data is sandwiched in the middle, and it's all we care about
let data = self.token.split(".").collect::<Vec<&str>>()[1];
let data_vec = base64::decode(data).unwrap();
from_slice::<AccountInfo>(&data_vec).unwrap()
}
}

/// Robocraft account information.
#[derive(Deserialize, Serialize, Clone)]
pub struct AccountInfo {
/// User's public ID
#[serde(rename = "PublicId")]
pub public_id: String,
/// Account display name
#[serde(rename = "DisplayName")]
pub display_name: String,
/// Account GUID, or display name for older accounts
#[serde(rename = "RobocraftName")]
pub robocraft_name: String,
/// ??? is confirmed?
#[serde(rename = "Confirmed")]
pub confirmed: bool,
/// Freejam support code
#[serde(rename = "SupportCode")]
pub support_code: String,
/// User's email address
#[serde(rename = "EmailAddress")]
pub email_address: String,
/// Email address is verified?
#[serde(rename = "EmailVerified")]
pub email_verified: bool,
/// Account creation date
#[serde(rename = "CreatedDate")]
pub created_date: String,
/// Owned products (?)
#[serde(rename = "Products")]
pub products: Vec<String>,
/// Account flags
#[serde(rename = "Flags")]
pub flags: Vec<String>,
/// Account has a password?
#[serde(rename = "HasPassword")]
pub has_password: bool,
/// Mailing lists that the account is signed up for
#[serde(rename = "MailingLists")]
pub mailing_lists: Vec<String>,
/// Is Steam account? (always false)
#[serde(rename = "HasSteam")]
pub has_steam: bool,
/// iss (?)
#[serde(rename = "iss")]
pub iss: String,
/// sub (?)
#[serde(rename = "sub")]
pub sub: String,
/// Token created at (unix time) (?)
#[serde(rename = "iat")]
pub iat: u64,
/// Token expiry (unix time) (?)
#[serde(rename = "exp")]
pub exp: u64,
}

+ 3
- 0
src/robocraft/mod.rs View File

@@ -16,5 +16,8 @@ pub use self::cubes::{Cube, Cubes};
mod auth;
pub use self::auth::{ITokenProvider, DefaultTokenProvider};

mod account;
pub use self::account::{AuthenticatedTokenProvider, AccountInfo};

/// Token defined in a javascript file from Freejam which never expires
pub const DEFAULT_TOKEN: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJQdWJsaWNJZCI6IjEyMyIsIkRpc3BsYXlOYW1lIjoiVGVzdCIsIlJvYm9jcmFmdE5hbWUiOiJGYWtlQ1JGVXNlciIsIkZsYWdzIjpbXSwiaXNzIjoiRnJlZWphbSIsInN1YiI6IldlYiIsImlhdCI6MTU0NTIyMzczMiwiZXhwIjoyNTQ1MjIzNzkyfQ.ralLmxdMK9rVKPZxGng8luRIdbTflJ4YMJcd25dKlqg";

+ 2
- 1
test.sh View File

@@ -1,5 +1,6 @@
#!/bin/bash
RUST_BACKTRACE=1 cargo test --all-features -- --nocapture
# RUST_BACKTRACE=1 cargo test --all-features -- --nocapture
# RUST_BACKTRACE=1 cargo test --release --all-features -- --nocapture
# RUST_BACKTRACE=1 cargo test --features techblox -- --nocapture
RUST_BACKTRACE=1 cargo test --features robocraft -- --nocapture
exit $?

+ 32
- 0
tests/robocraft_auth.rs View File

@@ -0,0 +1,32 @@
#[cfg(feature = "robocraft")]
use libfj::robocraft;
#[cfg(feature = "robocraft")]
use libfj::robocraft::ITokenProvider;

#[cfg(feature = "robocraft")]
#[test]
fn robocraft_auth_login() -> Result<(), ()> {
let token_maybe = robocraft::AuthenticatedTokenProvider::with_email("melon.spoik@gmail.com", "P4$$w0rd");
assert!(token_maybe.is_ok());
let token_maybe = robocraft::AuthenticatedTokenProvider::with_username("FJAPIC00L", "P4$$w0rd");
assert!(token_maybe.is_ok());
let token_p = token_maybe.unwrap();
let raw_token_maybe = token_p.token();
assert!(raw_token_maybe.is_ok());
println!("Token: {}", raw_token_maybe.unwrap());
Ok(())
}

#[cfg(feature = "robocraft")]
#[test]
fn robocraft_account() -> Result<(), ()> {
let token_maybe = robocraft::AuthenticatedTokenProvider::with_username("FJAPIC00L", "P4$$w0rd");
assert!(token_maybe.is_ok());
let token_provider = token_maybe.unwrap();
let account_maybe = token_provider.get_account_info();
assert!(account_maybe.is_ok());
let account = account_maybe.unwrap();
assert_eq!(account.display_name, "FJAPIC00L");
assert_eq!(account.created_date, "2019-01-18T14:48:09");
Ok(())
}

+ 1
- 1
tests/robocraft_factory.rs View File

@@ -78,7 +78,7 @@ async fn robocraft_factory_custom_query() -> Result<(), ()> {
#[tokio::test]
async fn robocraft_factory_player_query() -> Result<(), ()> {
let result = builder()
.text("MilanZhi".to_string()) // there is a featured robot by this user, so this should never fail
.text("Spacecam".to_string()) // there is a featured robot by this user, so this should never fail
.text_search_type(robocraft::FactoryTextSearchType::Player)
.items_per_page(10)
.send().await;


Loading…
Cancel
Save