An unofficial collection of APIs used in FreeJam games and mods
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

164 lines
5.1KB

  1. use serde::{Deserialize, Serialize};
  2. use ureq::{Agent, Error};
  3. use serde_json::{to_string, from_slice};
  4. use crate::robocraft::ITokenProvider;
  5. /// Token provider for an existing Robocraft account.
  6. ///
  7. /// Steam accounts are not supported.
  8. pub struct AuthenticatedTokenProvider {
  9. /// The account's username
  10. pub username: String,
  11. /// The account's password
  12. pub password: String,
  13. /// Ureq HTTP client
  14. client: Agent,
  15. }
  16. impl AuthenticatedTokenProvider {
  17. pub fn with_email(email: &str, password: &str) -> Result<Self, Error> {
  18. let client = Agent::new();
  19. let payload = AuthenticationEmailPayload {
  20. email_address: email.to_string(),
  21. password: password.to_string(),
  22. };
  23. let response = client.post("https://account.freejamgames.com/api/authenticate/email/web")
  24. .set("Content-Type", "application/json")
  25. .send_string(&to_string(&payload).unwrap())?;
  26. let json_res = response.into_json::<AuthenticationResponseInfo>()?;
  27. Ok(Self {
  28. username: json_res.decode_jwt_data().display_name,
  29. password: password.to_string(),
  30. client,
  31. })
  32. }
  33. pub fn with_username(username: &str, password: &str) -> Result<Self, Error> {
  34. let new_obj = Self {
  35. username: username.to_string(),
  36. password: password.to_string(),
  37. client: Agent::new(),
  38. };
  39. new_obj.do_auth()?;
  40. Ok(new_obj)
  41. }
  42. fn do_auth(&self) -> Result<AuthenticationResponseInfo, Error> {
  43. let payload = AuthenticationUsernamePayload {
  44. username: self.username.clone(),
  45. password: self.password.clone(),
  46. };
  47. let response = self.client.post("https://account.freejamgames.com/api/authenticate/displayname/web")
  48. .set("Content-Type", "application/json")
  49. .send_string(&to_string(&payload).unwrap())?;
  50. let json_res = response.into_json::<AuthenticationResponseInfo>()?;
  51. Ok(json_res)
  52. }
  53. pub fn get_account_info(&self) -> Result<AccountInfo, Error> {
  54. let json_res = self.do_auth()?;
  55. Ok(json_res.decode_jwt_data())
  56. }
  57. }
  58. impl ITokenProvider for AuthenticatedTokenProvider {
  59. fn token(&self) -> Result<String, ()> {
  60. let json_res = self.do_auth().map_err(|_|())?;
  61. Ok(json_res.token)
  62. }
  63. }
  64. #[derive(Deserialize, Serialize, Clone)]
  65. pub(crate) struct AuthenticationEmailPayload {
  66. #[serde(rename = "EmailAddress")]
  67. pub email_address: String,
  68. #[serde(rename = "Password")]
  69. pub password: String,
  70. }
  71. #[derive(Deserialize, Serialize, Clone)]
  72. pub(crate) struct AuthenticationUsernamePayload {
  73. #[serde(rename = "DisplayName")]
  74. pub username: String,
  75. #[serde(rename = "Password")]
  76. pub password: String,
  77. }
  78. #[derive(Deserialize, Serialize, Clone, Debug)]
  79. pub(crate) struct AuthenticationResponseInfo {
  80. #[serde(rename = "Token")]
  81. pub token: String,
  82. #[serde(rename = "RefreshToken")]
  83. pub refresh_token: String,
  84. #[serde(rename = "RefreshTokenExpiry")]
  85. pub refresh_token_expiry: String,
  86. }
  87. impl AuthenticationResponseInfo {
  88. pub fn decode_jwt_data(&self) -> AccountInfo {
  89. // Refer to https://jwt.io/
  90. // header is before dot, signature is after dot.
  91. // data is sandwiched in the middle, and it's all we care about
  92. let data = self.token.split(".").collect::<Vec<&str>>()[1];
  93. let data_vec = base64::decode(data).unwrap();
  94. from_slice::<AccountInfo>(&data_vec).unwrap()
  95. }
  96. }
  97. /// Robocraft account information.
  98. #[derive(Deserialize, Serialize, Clone)]
  99. pub struct AccountInfo {
  100. /// User's public ID
  101. #[serde(rename = "PublicId")]
  102. pub public_id: String,
  103. /// Account display name
  104. #[serde(rename = "DisplayName")]
  105. pub display_name: String,
  106. /// Account GUID, or display name for older accounts
  107. #[serde(rename = "RobocraftName")]
  108. pub robocraft_name: String,
  109. /// ??? is confirmed?
  110. #[serde(rename = "Confirmed")]
  111. pub confirmed: bool,
  112. /// Freejam support code
  113. #[serde(rename = "SupportCode")]
  114. pub support_code: String,
  115. /// User's email address
  116. #[serde(rename = "EmailAddress")]
  117. pub email_address: String,
  118. /// Email address is verified?
  119. #[serde(rename = "EmailVerified")]
  120. pub email_verified: bool,
  121. /// Account creation date
  122. #[serde(rename = "CreatedDate")]
  123. pub created_date: String,
  124. /// Owned products (?)
  125. #[serde(rename = "Products")]
  126. pub products: Vec<String>,
  127. /// Account flags
  128. #[serde(rename = "Flags")]
  129. pub flags: Vec<String>,
  130. /// Account has a password?
  131. #[serde(rename = "HasPassword")]
  132. pub has_password: bool,
  133. /// Mailing lists that the account is signed up for
  134. #[serde(rename = "MailingLists")]
  135. pub mailing_lists: Vec<String>,
  136. /// Is Steam account? (always false)
  137. #[serde(rename = "HasSteam")]
  138. pub has_steam: bool,
  139. /// iss (?)
  140. #[serde(rename = "iss")]
  141. pub iss: String,
  142. /// sub (?)
  143. #[serde(rename = "sub")]
  144. pub sub: String,
  145. /// Token created at (unix time) (?)
  146. #[serde(rename = "iat")]
  147. pub iat: u64,
  148. /// Token expiry (unix time) (?)
  149. #[serde(rename = "exp")]
  150. pub exp: u64,
  151. }