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 #[serde(rename = "chef")]
40 Chef,
41 #[serde(rename = "twostepverification")]
42 TwoStepVerification,
43 #[serde(rename = "reauthentication")]
45 Reauthentication,
46 #[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 pub async fn queue_challenge(
119 &mut self,
120 challenge: &Challenge,
121 verification_token: &str,
122 ) -> Result<(), Error> {
123 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}