roblox_api/
challenge.rs

1use base64::{Engine, prelude::BASE64_STANDARD};
2use reqwest::header::HeaderValue;
3use serde::{Deserialize, Serialize};
4
5use crate::{Error, api::challenge, client::Client};
6
7pub(crate) const CHALLENGE_ID_HEADER: &str = "rblx-challenge-id";
8pub(crate) const CHALLENGE_TYPE_HEADER: &str = "rblx-challenge-type";
9pub(crate) const CHALLENGE_METADATA_HEADER: &str = "rblx-challenge-metadata";
10
11#[derive(Clone, Copy, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
12pub enum ActionType {
13    #[default]
14    Unknown = 0,
15    Login,
16    RobuxSpend,
17    ItemTrade,
18    Resale,
19    PasswordReset,
20    RevertAccount,
21    Generic,
22    GenericWithRecoveryCodes,
23}
24
25impl std::fmt::Display for ActionType {
26    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
27        write!(f, "{:?}", self)
28    }
29}
30
31#[derive(Clone, Copy, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
32pub enum ChallengeType {
33    #[default]
34    #[serde(rename = "generic")]
35    Generic,
36    #[serde(rename = "captcha")]
37    Captcha,
38    // This requires doing javascript challenges, however completing them is upto the user of this api
39    #[serde(rename = "chef")]
40    Chef,
41    #[serde(rename = "twostepverification")]
42    TwoStepVerification,
43    // I'm not sure what this is, however I say it being referenced in some places
44    #[serde(rename = "reauthentication")]
45    Reauthentication,
46    // I'm not sure what this is, however I say it being referenced in some places
47    #[serde(rename = "security-questions")]
48    SecurityQuestions,
49}
50
51impl From<&str> for ChallengeType {
52    fn from(value: &str) -> Self {
53        match value {
54            "generic" => Self::Generic,
55            "captcha" => Self::Captcha,
56            "chef" => Self::Chef,
57            "twostepverification" => Self::TwoStepVerification,
58            "reauthentication" => Self::Reauthentication,
59            "security-questions" => Self::SecurityQuestions,
60
61            _ => Self::Generic,
62        }
63    }
64}
65
66impl std::fmt::Display for ChallengeType {
67    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68        write!(f, "{:?}", self)
69    }
70}
71
72#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
73pub struct ChallengeMetadata {
74    #[serde(rename = "userId")]
75    pub(crate) user_id: String,
76    #[serde(rename = "challengeId")]
77    pub server_challenge_id: String,
78    #[serde(rename = "actionType")]
79    pub action_type: ActionType,
80    #[serde(rename = "rememberDevice")]
81    pub(crate) remember_device: bool,
82}
83
84#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
85pub struct ChefChallengeMetadata {
86    #[serde(rename = "userId")]
87    pub(crate) user_id: String,
88    #[serde(rename = "challengeId")]
89    pub server_challenge_id: String,
90    #[serde(rename = "expectedSymbols")]
91    pub expected_symbols: Vec<String>,
92    #[serde(rename = "scriptIdentifiers")]
93    pub script_identifiers: Vec<String>,
94}
95
96#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
97pub struct Challenge {
98    pub id: String,
99    pub kind: ChallengeType,
100    pub metadata: ChallengeMetadata,
101}
102
103#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
104pub(crate) struct ChallengeMetadataRequest {
105    #[serde(rename = "verificationToken")]
106    pub(crate) verification_token: String,
107    #[serde(rename = "challengeId")]
108    pub(crate) challenge_id: String,
109    #[serde(rename = "actionType")]
110    pub(crate) action_type: ActionType,
111    #[serde(rename = "rememberDevice")]
112    pub(crate) remember_device: bool,
113}
114
115impl Client {
116    // the name is misleading, there's no queue, also this function is kinda ugly to use,
117    // perhaps it should be reworked
118    pub async fn queue_challenge(
119        &mut self,
120        challenge: &Challenge,
121        verification_token: &str,
122    ) -> Result<(), Error> {
123        // the challenge requires this api call, otherwise it fails
124        challenge::v1::continue_challenge(self, &challenge, verification_token).await?;
125
126        self.requestor.default_headers.insert(
127            CHALLENGE_ID_HEADER,
128            HeaderValue::from_str(&challenge.id).unwrap(),
129        );
130
131        self.requestor.default_headers.insert(
132            CHALLENGE_TYPE_HEADER,
133            HeaderValue::from_str(&challenge.kind.to_string()).unwrap(),
134        );
135
136        let metadata_b64 = BASE64_STANDARD.encode(
137            serde_json::to_vec(
138                &(ChallengeMetadataRequest {
139                    verification_token: verification_token.to_string(),
140                    challenge_id: challenge.metadata.server_challenge_id.clone(),
141                    action_type: challenge.metadata.action_type,
142                    remember_device: challenge.metadata.remember_device,
143                }),
144            )
145            .unwrap(),
146        );
147
148        self.requestor.default_headers.insert(
149            CHALLENGE_METADATA_HEADER,
150            HeaderValue::from_str(&metadata_b64).unwrap(),
151        );
152
153        Ok(())
154    }
155
156    pub(crate) fn remove_challenge(&mut self) {
157        self.requestor.default_headers.remove(CHALLENGE_ID_HEADER);
158        self.requestor.default_headers.remove(CHALLENGE_TYPE_HEADER);
159        self.requestor
160            .default_headers
161            .remove(CHALLENGE_METADATA_HEADER);
162    }
163}