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