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)]
32#[serde(rename_all = "lowercase")]
33pub enum ChallengeType {
34 #[default]
35 Generic,
36 Captcha,
37 Chef,
39 #[serde(rename = "twostepverification")]
40 TwoStepVerification,
41 Reauthentication,
43 #[serde(rename = "security-questions")]
45 SecurityQuestions,
46}
47
48impl From<&str> for ChallengeType {
49 fn from(value: &str) -> Self {
50 match value {
51 "generic" => Self::Generic,
52 "captcha" => Self::Captcha,
53 "chef" => Self::Chef,
54 "twostepverification" => Self::TwoStepVerification,
55 "reauthentication" => Self::Reauthentication,
56 "security-questions" => Self::SecurityQuestions,
57
58 _ => Self::Generic,
59 }
60 }
61}
62
63impl std::fmt::Display for ChallengeType {
64 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
65 write!(f, "{:?}", self)
66 }
67}
68
69#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
70#[serde(rename_all = "camelCase")]
71pub struct ChallengeMetadata {
72 pub(crate) user_id: String,
73 #[serde(rename = "challengeId")]
74 pub server_challenge_id: String,
75 pub action_type: ActionType,
76 pub(crate) remember_device: bool,
77}
78
79#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
80#[serde(rename_all = "camelCase")]
81pub struct ChefChallengeMetadata {
82 pub(crate) user_id: String,
83 #[serde(rename = "challengeId")]
84 pub server_challenge_id: String,
85 pub expected_symbols: Vec<String>,
86 pub script_identifiers: Vec<String>,
87}
88
89#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
90pub struct Challenge {
91 pub id: String,
92 pub kind: ChallengeType,
93 pub metadata: ChallengeMetadata,
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
97#[serde(rename_all = "camelCase")]
98pub(crate) struct ChallengeMetadataRequest {
99 pub(crate) verification_token: String,
100 pub(crate) challenge_id: String,
101 pub(crate) action_type: ActionType,
102 pub(crate) remember_device: bool,
103}
104
105impl Client {
106 pub async fn queue_challenge(
109 &mut self,
110 challenge: &Challenge,
111 verification_token: &str,
112 ) -> Result<(), Error> {
113 challenge::v1::continue_challenge(self, challenge, verification_token).await?;
115
116 self.requestor.default_headers.insert(
117 CHALLENGE_ID_HEADER,
118 HeaderValue::from_str(&challenge.id).unwrap(),
119 );
120
121 self.requestor.default_headers.insert(
122 CHALLENGE_TYPE_HEADER,
123 HeaderValue::from_str(&challenge.kind.to_string()).unwrap(),
124 );
125
126 let metadata_b64 = BASE64_STANDARD.encode(
127 serde_json::to_vec(
128 &(ChallengeMetadataRequest {
129 verification_token: verification_token.to_string(),
130 challenge_id: challenge.metadata.server_challenge_id.clone(),
131 action_type: challenge.metadata.action_type,
132 remember_device: challenge.metadata.remember_device,
133 }),
134 )
135 .unwrap(),
136 );
137
138 self.requestor.default_headers.insert(
139 CHALLENGE_METADATA_HEADER,
140 HeaderValue::from_str(&metadata_b64).unwrap(),
141 );
142
143 Ok(())
144 }
145
146 pub(crate) fn remove_challenge(&mut self) {
147 self.requestor.default_headers.remove(CHALLENGE_ID_HEADER);
148 self.requestor.default_headers.remove(CHALLENGE_TYPE_HEADER);
149 self.requestor
150 .default_headers
151 .remove(CHALLENGE_METADATA_HEADER);
152 }
153}