From 83ebd482e5e745cf4f24fc8d4a4630b4b8efb8fd Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 23 Apr 2023 11:58:45 +0200 Subject: [PATCH 1/6] Made AuthEmail, Password and Username reusable --- src/api/auth/login.rs | 9 +++- src/api/auth/register.rs | 8 ++-- src/api/schemas.rs | 92 +++++++++++++++++++++++++++++++++------- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index ee8788e..a61ed8b 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -1 +1,8 @@ -pub mod login {} +pub mod login { + use crate::api::schemas::schemas::LoginSchema; + use crate::instance::Instance; + + impl Instance { + pub async fn login_account(&mut self, login_schema: &LoginSchema) {} + } +} diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 191480f..de78b88 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -1,6 +1,6 @@ pub mod register { use reqwest::Client; - use serde_json::{from_str, json, Value}; + use serde_json::json; use crate::{ api::{ @@ -19,7 +19,7 @@ pub mod register { # Errors * [`InstanceServerError`] - If the server does not respond. */ - pub async fn register( + pub async fn register_account( &mut self, register_schema: &RegisterSchema, ) -> Result { @@ -92,7 +92,7 @@ mod test { error_type: "date_of_birth".to_string(), error: "This field is required (BASE_TYPE_REQUIRED)".to_string() }, - test_instance.register(®).await.err().unwrap() + test_instance.register_account(®).await.err().unwrap() ); } @@ -120,7 +120,7 @@ mod test { None, ) .unwrap(); - let token = test_instance.register(®).await.unwrap().token; + let token = test_instance.register_account(®).await.unwrap().token; println!("{}", token); } } diff --git a/src/api/schemas.rs b/src/api/schemas.rs index e0a770d..1fdc9ab 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -5,6 +5,63 @@ pub mod schemas { use crate::errors::FieldFormatError; + /** + A struct that represents a well-formed email address. + */ + #[derive(Clone)] + pub struct AuthEmail { + pub email: String, + } + + impl AuthEmail { + /** + Returns a new [`Result`]. + ## Arguments + The email address you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The email address is not in a valid format. + + */ + pub fn new(email: String) -> Result { + let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); + if !regex.is_match(email.clone().as_str()) { + return Err(FieldFormatError::EmailError); + } + return Ok(AuthEmail { email }); + } + } + + #[derive(Clone)] + pub struct AuthUsername { + pub username: String, + } + + impl AuthUsername { + pub fn new(username: String) -> Result { + if username.len() < 2 || username.len() > 32 { + return Err(FieldFormatError::UsernameError); + } else { + return Ok(AuthUsername { username }); + } + } + } + + #[derive(Clone)] + pub struct AuthPassword { + pub password: String, + } + + impl AuthPassword { + pub fn new(password: String) -> Result { + if password.len() < 1 || password.len() > 72 { + return Err(FieldFormatError::PasswordError); + } else { + return Ok(AuthPassword { password }); + } + } + } + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct RegisterSchema { @@ -34,10 +91,10 @@ pub mod schemas { These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) */ pub fn new( - username: String, - password: Option, + username: AuthUsername, + password: Option, consent: bool, - email: Option, + email: Option, fingerprint: Option, invite: Option, date_of_birth: Option, @@ -45,28 +102,31 @@ pub mod schemas { captcha_key: Option, promotional_email_opt_in: Option, ) -> Result { - if username.len() < 2 || username.len() > 32 { - return Err(FieldFormatError::UsernameError); + let username = username.username; + + let email_addr; + if email.is_some() { + email_addr = Some(email.unwrap().email); + } else { + email_addr = None; } - if password.is_some() - && (password.as_ref().unwrap().len() < 1 || password.as_ref().unwrap().len() > 72) - { - return Err(FieldFormatError::PasswordError); + + let has_password; + if password.is_some() { + has_password = Some(password.unwrap().password); + } else { + has_password = None; } + if !consent { return Err(FieldFormatError::ConsentError); } - let regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap(); - if email.clone().is_some() && !regex.is_match(email.clone().unwrap().as_str()) { - return Err(FieldFormatError::EmailError); - } - return Ok(RegisterSchema { username, - password, + password: has_password, consent, - email, + email: email_addr, fingerprint, invite, date_of_birth, From b374fdee7d278f418673c7190efd55dc5b224ca8 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 23 Apr 2023 12:06:51 +0200 Subject: [PATCH 2/6] Add documentation --- src/api/schemas.rs | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/src/api/schemas.rs b/src/api/schemas.rs index 1fdc9ab..b188e2c 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -32,12 +32,28 @@ pub mod schemas { } } + /** + A struct that represents a well-formed username. + ## Arguments + Please use new() to create a new instance of this struct. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is not between 2 and 32 characters. + */ #[derive(Clone)] pub struct AuthUsername { pub username: String, } impl AuthUsername { + /** + Returns a new [`Result`]. + ## Arguments + The username you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is not between 2 and 32 characters. + */ pub fn new(username: String) -> Result { if username.len() < 2 || username.len() > 32 { return Err(FieldFormatError::UsernameError); @@ -47,12 +63,28 @@ pub mod schemas { } } + /** + A struct that represents a well-formed password. + ## Arguments + Please use new() to create a new instance of this struct. + ## Errors + You will receive a [`FieldFormatError`], if: + - The password is not between 1 and 72 characters. + */ #[derive(Clone)] pub struct AuthPassword { pub password: String, } impl AuthPassword { + /** + Returns a new [`Result`]. + ## Arguments + The password you want to validate. + ## Errors + You will receive a [`FieldFormatError`], if: + - The password is not between 1 and 72 characters. + */ pub fn new(password: String) -> Result { if password.len() < 1 || password.len() > 72 { return Err(FieldFormatError::PasswordError); @@ -62,6 +94,16 @@ pub mod schemas { } } + /** + A struct that represents a well-formed register request. + ## Arguments + Please use new() to create a new instance of this struct. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is not between 2 and 32 characters. + - The password is not between 1 and 72 characters. + */ + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct RegisterSchema { @@ -137,6 +179,15 @@ pub mod schemas { } } + /** + A struct that represents a well-formed login request. + ## Arguments + Please use new() to create a new instance of this struct. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is not between 2 and 32 characters. + - The password is not between 1 and 72 characters. + */ #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub struct LoginSchema { @@ -148,6 +199,40 @@ pub mod schemas { gift_code_sku_id: Option, } + impl LoginSchema { + /** + Returns a new [`Result`]. + ## Arguments + login: The username you want to login with. + password: The password you want to login with. + undelete: Honestly no idea what this is for. + captcha_key: The captcha key you want to login with. + login_source: The login source. + gift_code_sku_id: The gift code sku id. + ## Errors + You will receive a [`FieldFormatError`], if: + - The username is less than 2 or more than 32 characters in length + */ + pub fn new( + login: AuthUsername, + password: String, + undelete: Option, + captcha_key: Option, + login_source: Option, + gift_code_sku_id: Option, + ) -> Result { + let login = login.username; + return Ok(LoginSchema { + login, + password, + undelete, + captcha_key, + login_source, + gift_code_sku_id, + }); + } + } + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct TotpSchema { @@ -157,6 +242,9 @@ pub mod schemas { login_source: Option, } + /** + Represents the result you get from GET: /api/instance/policies/. + */ #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct InstancePoliciesSchema { From 8b40b4c3b2991497f40f09e44546ff2250690a26 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 23 Apr 2023 13:45:52 +0200 Subject: [PATCH 3/6] Made test pass --- src/api/auth/register.rs | 12 ++-- src/api/schemas.rs | 125 +++++++++++++++++++++------------------ 2 files changed, 75 insertions(+), 62 deletions(-) diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index de78b88..63c89b9 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -58,7 +58,7 @@ pub mod register { #[cfg(test)] mod test { - use crate::api::schemas::schemas::RegisterSchema; + use crate::api::schemas::schemas::{AuthEmail, AuthPassword, AuthUsername, RegisterSchema}; use crate::errors::InstanceServerError; use crate::instance::Instance; use crate::limit::LimitedRequester; @@ -75,10 +75,10 @@ mod test { .await .unwrap(); let reg = RegisterSchema::new( - "aaa".to_string(), + AuthUsername::new("hiiii".to_string()).unwrap(), None, true, - Some("me@mail.xy".to_string()), + Some(AuthEmail::new("me@mail.xy".to_string()).unwrap()), None, None, None, @@ -108,10 +108,10 @@ mod test { .await .unwrap(); let reg = RegisterSchema::new( - "Hiiii".to_string(), - Some("mysupersecurepass123!".to_string()), + AuthUsername::new("Hiiii".to_string()).unwrap(), + Some(AuthPassword::new("mysupersecurepass123!".to_string()).unwrap()), true, - Some("flori@mail.xyz".to_string()), + Some(AuthEmail::new("flori@aaaa.xyz".to_string()).unwrap()), None, None, Some("2000-01-01".to_string()), diff --git a/src/api/schemas.rs b/src/api/schemas.rs index b188e2c..4a68488 100644 --- a/src/api/schemas.rs +++ b/src/api/schemas.rs @@ -1,14 +1,14 @@ pub mod schemas { use regex::Regex; use serde::{Deserialize, Serialize}; - use std::fmt; + use std::{collections::HashMap, fmt}; use crate::errors::FieldFormatError; /** A struct that represents a well-formed email address. */ - #[derive(Clone)] + #[derive(Clone, PartialEq, Eq, Debug)] pub struct AuthEmail { pub email: String, } @@ -40,7 +40,7 @@ pub mod schemas { You will receive a [`FieldFormatError`], if: - The username is not between 2 and 32 characters. */ - #[derive(Clone)] + #[derive(Clone, PartialEq, Eq, Debug)] pub struct AuthUsername { pub username: String, } @@ -71,7 +71,7 @@ pub mod schemas { You will receive a [`FieldFormatError`], if: - The password is not between 1 and 72 characters. */ - #[derive(Clone)] + #[derive(Clone, PartialEq, Eq, Debug)] pub struct AuthPassword { pub password: String, } @@ -233,6 +233,63 @@ pub mod schemas { } } + #[derive(Debug, Serialize, Deserialize)] + pub struct LoginResult { + token: String, + settings: UserSettings, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct UserSettings { + afk_timeout: i32, + allow_accessibility_detection: bool, + animate_emoji: bool, + animate_stickers: i32, + contact_sync_enabled: bool, + convert_emoticons: bool, + custom_status: Option, + default_guilds_restricted: bool, + detect_platform_accounts: bool, + developer_mode: bool, + disable_games_tab: bool, + enable_tts_command: bool, + explicit_content_filter: i32, + friend_source_flags: FriendSourceFlags, + friend_discovery_flags: Option, + gateway_connected: bool, + gif_auto_play: bool, + guild_folders: Vec, + guild_positions: Vec, + inline_attachment_media: bool, + inline_embed_media: bool, + locale: String, + message_display_compact: bool, + native_phone_integration_enabled: bool, + render_embeds: bool, + render_reactions: bool, + restricted_guilds: Vec, + show_current_game: bool, + status: String, + stream_notifications_enabled: bool, + theme: String, + timezone_offset: i32, + view_nsfw_guilds: Option, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct FriendSourceFlags { + all: Option, + mutual_friends: Option, + mutual_guilds: Option, + } + + #[derive(Debug, Serialize, Deserialize)] + pub struct GuildFolder { + id: String, + guild_ids: Vec, + name: String, + } + #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub struct TotpSchema { @@ -335,18 +392,7 @@ mod schemas_tests { #[test] fn password_too_short() { assert_eq!( - RegisterSchema::new( - "Test".to_string(), - Some("".to_string()), - true, - None, - None, - None, - None, - None, - None, - None, - ), + AuthPassword::new("".to_string()), Err(FieldFormatError::PasswordError) ); } @@ -358,18 +404,7 @@ mod schemas_tests { long_pw = long_pw + "a"; } assert_eq!( - RegisterSchema::new( - "Test".to_string(), - Some(long_pw), - true, - None, - None, - None, - None, - None, - None, - None, - ), + AuthPassword::new(long_pw), Err(FieldFormatError::PasswordError) ); } @@ -377,18 +412,7 @@ mod schemas_tests { #[test] fn username_too_short() { assert_eq!( - RegisterSchema::new( - "T".to_string(), - None, - true, - None, - None, - None, - None, - None, - None, - None, - ), + AuthUsername::new("T".to_string()), Err(FieldFormatError::UsernameError) ); } @@ -400,7 +424,7 @@ mod schemas_tests { long_un = long_un + "a"; } assert_eq!( - RegisterSchema::new(long_un, None, true, None, None, None, None, None, None, None,), + AuthUsername::new(long_un), Err(FieldFormatError::UsernameError) ); } @@ -409,7 +433,7 @@ mod schemas_tests { fn consent_false() { assert_eq!( RegisterSchema::new( - "Test".to_string(), + AuthUsername::new("Test".to_string()).unwrap(), None, false, None, @@ -427,18 +451,7 @@ mod schemas_tests { #[test] fn invalid_email() { assert_eq!( - RegisterSchema::new( - "Test".to_string(), - None, - true, - Some("p@p.p".to_string()), - None, - None, - None, - None, - None, - None, - ), + AuthEmail::new("p@p.p".to_string()), Err(FieldFormatError::EmailError) ) } @@ -446,10 +459,10 @@ mod schemas_tests { #[test] fn valid_email() { let reg = RegisterSchema::new( - "Test".to_string(), + AuthUsername::new("Testy".to_string()).unwrap(), None, true, - Some("me@mail.xy".to_string()), + Some(AuthEmail::new("me@mail.de".to_string()).unwrap()), None, None, None, From 48333cf8549d5cfed10747c05fbfbe130b166101 Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 23 Apr 2023 13:58:17 +0200 Subject: [PATCH 4/6] Make send_request return Result --- src/api/auth/register.rs | 2 +- src/errors.rs | 1 + src/limit.rs | 38 +++++++++++++++++++++++--------------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/api/auth/register.rs b/src/api/auth/register.rs index 63c89b9..d25f7c5 100644 --- a/src/api/auth/register.rs +++ b/src/api/auth/register.rs @@ -31,7 +31,7 @@ pub mod register { let response = limited_requester .send_request(request_builder, LimitType::AuthRegister) .await; - if response.is_none() { + if !response.is_ok() { return Err(InstanceServerError::NoResponse); } diff --git a/src/errors.rs b/src/errors.rs index e57d5c8..58dc856 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -17,4 +17,5 @@ custom_error! { ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}", CantGetInfoError{error:String} = "Something seems to be wrong with the instance. Cannot get information about the instance: {error}", InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}", + RateLimited = "Ratelimited.", } diff --git a/src/limit.rs b/src/limit.rs index b12069f..f5ac4b6 100644 --- a/src/limit.rs +++ b/src/limit.rs @@ -1,4 +1,7 @@ -use crate::api::limits::{Limit, LimitType, Limits}; +use crate::{ + api::limits::{Limit, LimitType, Limits}, + errors::InstanceServerError, +}; use reqwest::{Client, RequestBuilder, Response}; use std::collections::{HashMap, VecDeque}; @@ -67,7 +70,7 @@ impl LimitedRequester { &mut self, request: RequestBuilder, limit_type: LimitType, - ) -> Option { + ) -> Result { if self.can_send_request(limit_type) { let built_request = request .build() @@ -78,13 +81,13 @@ impl LimitedRequester { Err(e) => panic!("An error occured while processing the response: {}", e), }; self.update_limits(&response, limit_type); - return Some(response); + return Ok(response); } else { self.requests.push_back(TypedRequest { request: request, limit_type: limit_type, }); - return None; + return Err(InstanceServerError::RateLimited); } } @@ -264,20 +267,25 @@ mod rate_limit { String::from("http://localhost:3001/cdn"), ); let mut requester = LimitedRequester::new(urls.api.clone()).await; - let mut request: Option; - request = None; + let mut request: Option> = None; - for _ in 0..50 { + for _ in 0..=50 { let request_path = urls.api.clone() + "/some/random/nonexisting/path"; let request_builder = requester.http.get(request_path); - request = requester - .send_request(request_builder, LimitType::Channel) - .await; + request = Some( + requester + .send_request(request_builder, LimitType::Channel) + .await, + ); } - match request { - Some(_) => assert!(false), - None => assert!(true), + if request.is_some() { + match request.unwrap() { + Ok(_) => assert!(false), + Err(_) => assert!(true), + } + } else { + assert!(false) } } @@ -295,8 +303,8 @@ mod rate_limit { .send_request(request_builder, LimitType::Channel) .await; let result = match request { - Some(result) => result, - None => panic!("Request failed"), + Ok(result) => result, + Err(_) => panic!("Request failed"), }; let config: Config = from_str(result.text().await.unwrap().as_str()).unwrap(); println!("{:?}", config); From 22adda7aa65b8097da4541fd46a5ead7949a317f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 23 Apr 2023 14:00:27 +0200 Subject: [PATCH 5/6] start working on login --- src/api/auth/login.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index a61ed8b..c720785 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -1,8 +1,13 @@ pub mod login { - use crate::api::schemas::schemas::LoginSchema; + use crate::api::schemas::schemas::{LoginResult, LoginSchema}; + use crate::errors::InstanceServerError; use crate::instance::Instance; impl Instance { - pub async fn login_account(&mut self, login_schema: &LoginSchema) {} + pub async fn login_account( + &mut self, + login_schema: &LoginSchema, + ) -> Result { + } } } From 242eddd5c8e522d4ea0d38d79f4cdbad76fb954f Mon Sep 17 00:00:00 2001 From: bitfl0wer Date: Sun, 23 Apr 2023 14:01:02 +0200 Subject: [PATCH 6/6] make compiler happy --- src/api/auth/login.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/api/auth/login.rs b/src/api/auth/login.rs index c720785..8813b14 100644 --- a/src/api/auth/login.rs +++ b/src/api/auth/login.rs @@ -3,11 +3,11 @@ pub mod login { use crate::errors::InstanceServerError; use crate::instance::Instance; - impl Instance { + /* impl Instance { pub async fn login_account( &mut self, login_schema: &LoginSchema, ) -> Result { } - } + } */ }