Merge pull request #9 from polyphony-chat/feature/register-login

Bring main up-to-date
This commit is contained in:
Flori 2023-04-23 14:05:05 +02:00 committed by GitHub
commit 3d2e1120d4
5 changed files with 278 additions and 96 deletions

View File

@ -1 +1,13 @@
pub mod login {} pub mod login {
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,
) -> Result<LoginResult, InstanceServerError> {
}
} */
}

View File

@ -1,6 +1,6 @@
pub mod register { pub mod register {
use reqwest::Client; use reqwest::Client;
use serde_json::{from_str, json, Value}; use serde_json::json;
use crate::{ use crate::{
api::{ api::{
@ -19,7 +19,7 @@ pub mod register {
# Errors # Errors
* [`InstanceServerError`] - If the server does not respond. * [`InstanceServerError`] - If the server does not respond.
*/ */
pub async fn register( pub async fn register_account(
&mut self, &mut self,
register_schema: &RegisterSchema, register_schema: &RegisterSchema,
) -> Result<Token, InstanceServerError> { ) -> Result<Token, InstanceServerError> {
@ -31,7 +31,7 @@ pub mod register {
let response = limited_requester let response = limited_requester
.send_request(request_builder, LimitType::AuthRegister) .send_request(request_builder, LimitType::AuthRegister)
.await; .await;
if response.is_none() { if !response.is_ok() {
return Err(InstanceServerError::NoResponse); return Err(InstanceServerError::NoResponse);
} }
@ -58,7 +58,7 @@ pub mod register {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use crate::api::schemas::schemas::RegisterSchema; use crate::api::schemas::schemas::{AuthEmail, AuthPassword, AuthUsername, RegisterSchema};
use crate::errors::InstanceServerError; use crate::errors::InstanceServerError;
use crate::instance::Instance; use crate::instance::Instance;
use crate::limit::LimitedRequester; use crate::limit::LimitedRequester;
@ -75,10 +75,10 @@ mod test {
.await .await
.unwrap(); .unwrap();
let reg = RegisterSchema::new( let reg = RegisterSchema::new(
"aaa".to_string(), AuthUsername::new("hiiii".to_string()).unwrap(),
None, None,
true, true,
Some("me@mail.xy".to_string()), Some(AuthEmail::new("me@mail.xy".to_string()).unwrap()),
None, None,
None, None,
None, None,
@ -92,7 +92,7 @@ mod test {
error_type: "date_of_birth".to_string(), error_type: "date_of_birth".to_string(),
error: "This field is required (BASE_TYPE_REQUIRED)".to_string() error: "This field is required (BASE_TYPE_REQUIRED)".to_string()
}, },
test_instance.register(&reg).await.err().unwrap() test_instance.register_account(&reg).await.err().unwrap()
); );
} }
@ -108,10 +108,10 @@ mod test {
.await .await
.unwrap(); .unwrap();
let reg = RegisterSchema::new( let reg = RegisterSchema::new(
"Hiiii".to_string(), AuthUsername::new("Hiiii".to_string()).unwrap(),
Some("mysupersecurepass123!".to_string()), Some(AuthPassword::new("mysupersecurepass123!".to_string()).unwrap()),
true, true,
Some("flori@mail.xyz".to_string()), Some(AuthEmail::new("flori@aaaa.xyz".to_string()).unwrap()),
None, None,
None, None,
Some("2000-01-01".to_string()), Some("2000-01-01".to_string()),
@ -120,7 +120,7 @@ mod test {
None, None,
) )
.unwrap(); .unwrap();
let token = test_instance.register(&reg).await.unwrap().token; let token = test_instance.register_account(&reg).await.unwrap().token;
println!("{}", token); println!("{}", token);
} }
} }

View File

@ -1,10 +1,109 @@
pub mod schemas { pub mod schemas {
use regex::Regex; use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt; use std::{collections::HashMap, fmt};
use crate::errors::FieldFormatError; use crate::errors::FieldFormatError;
/**
A struct that represents a well-formed email address.
*/
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct AuthEmail {
pub email: String,
}
impl AuthEmail {
/**
Returns a new [`Result<AuthEmail, FieldFormatError>`].
## 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<AuthEmail, FieldFormatError> {
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 });
}
}
/**
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, PartialEq, Eq, Debug)]
pub struct AuthUsername {
pub username: String,
}
impl AuthUsername {
/**
Returns a new [`Result<AuthUsername, FieldFormatError>`].
## 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<AuthUsername, FieldFormatError> {
if username.len() < 2 || username.len() > 32 {
return Err(FieldFormatError::UsernameError);
} else {
return Ok(AuthUsername { username });
}
}
}
/**
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, PartialEq, Eq, Debug)]
pub struct AuthPassword {
pub password: String,
}
impl AuthPassword {
/**
Returns a new [`Result<AuthPassword, FieldFormatError>`].
## 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<AuthPassword, FieldFormatError> {
if password.len() < 1 || password.len() > 72 {
return Err(FieldFormatError::PasswordError);
} else {
return Ok(AuthPassword { password });
}
}
}
/**
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)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct RegisterSchema { pub struct RegisterSchema {
@ -34,10 +133,10 @@ pub mod schemas {
These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/) These constraints have been defined [in the Spacebar-API](https://docs.spacebar.chat/routes/)
*/ */
pub fn new( pub fn new(
username: String, username: AuthUsername,
password: Option<String>, password: Option<AuthPassword>,
consent: bool, consent: bool,
email: Option<String>, email: Option<AuthEmail>,
fingerprint: Option<String>, fingerprint: Option<String>,
invite: Option<String>, invite: Option<String>,
date_of_birth: Option<String>, date_of_birth: Option<String>,
@ -45,28 +144,31 @@ pub mod schemas {
captcha_key: Option<String>, captcha_key: Option<String>,
promotional_email_opt_in: Option<bool>, promotional_email_opt_in: Option<bool>,
) -> Result<RegisterSchema, FieldFormatError> { ) -> Result<RegisterSchema, FieldFormatError> {
if username.len() < 2 || username.len() > 32 { let username = username.username;
return Err(FieldFormatError::UsernameError);
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) let has_password;
{ if password.is_some() {
return Err(FieldFormatError::PasswordError); has_password = Some(password.unwrap().password);
} else {
has_password = None;
} }
if !consent { if !consent {
return Err(FieldFormatError::ConsentError); 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 { return Ok(RegisterSchema {
username, username,
password, password: has_password,
consent, consent,
email, email: email_addr,
fingerprint, fingerprint,
invite, invite,
date_of_birth, date_of_birth,
@ -77,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)] #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct LoginSchema { pub struct LoginSchema {
@ -88,6 +199,97 @@ pub mod schemas {
gift_code_sku_id: Option<String>, gift_code_sku_id: Option<String>,
} }
impl LoginSchema {
/**
Returns a new [`Result<LoginSchema, FieldFormatError>`].
## 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<bool>,
captcha_key: Option<String>,
login_source: Option<String>,
gift_code_sku_id: Option<String>,
) -> Result<LoginSchema, FieldFormatError> {
let login = login.username;
return Ok(LoginSchema {
login,
password,
undelete,
captcha_key,
login_source,
gift_code_sku_id,
});
}
}
#[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<String>,
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<i32>,
gateway_connected: bool,
gif_auto_play: bool,
guild_folders: Vec<GuildFolder>,
guild_positions: Vec<i64>,
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<i64>,
show_current_game: bool,
status: String,
stream_notifications_enabled: bool,
theme: String,
timezone_offset: i32,
view_nsfw_guilds: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct FriendSourceFlags {
all: Option<bool>,
mutual_friends: Option<bool>,
mutual_guilds: Option<bool>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct GuildFolder {
id: String,
guild_ids: Vec<i64>,
name: String,
}
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct TotpSchema { pub struct TotpSchema {
@ -97,6 +299,9 @@ pub mod schemas {
login_source: Option<String>, login_source: Option<String>,
} }
/**
Represents the result you get from GET: /api/instance/policies/.
*/
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct InstancePoliciesSchema { pub struct InstancePoliciesSchema {
@ -187,18 +392,7 @@ mod schemas_tests {
#[test] #[test]
fn password_too_short() { fn password_too_short() {
assert_eq!( assert_eq!(
RegisterSchema::new( AuthPassword::new("".to_string()),
"Test".to_string(),
Some("".to_string()),
true,
None,
None,
None,
None,
None,
None,
None,
),
Err(FieldFormatError::PasswordError) Err(FieldFormatError::PasswordError)
); );
} }
@ -210,18 +404,7 @@ mod schemas_tests {
long_pw = long_pw + "a"; long_pw = long_pw + "a";
} }
assert_eq!( assert_eq!(
RegisterSchema::new( AuthPassword::new(long_pw),
"Test".to_string(),
Some(long_pw),
true,
None,
None,
None,
None,
None,
None,
None,
),
Err(FieldFormatError::PasswordError) Err(FieldFormatError::PasswordError)
); );
} }
@ -229,18 +412,7 @@ mod schemas_tests {
#[test] #[test]
fn username_too_short() { fn username_too_short() {
assert_eq!( assert_eq!(
RegisterSchema::new( AuthUsername::new("T".to_string()),
"T".to_string(),
None,
true,
None,
None,
None,
None,
None,
None,
None,
),
Err(FieldFormatError::UsernameError) Err(FieldFormatError::UsernameError)
); );
} }
@ -252,7 +424,7 @@ mod schemas_tests {
long_un = long_un + "a"; long_un = long_un + "a";
} }
assert_eq!( assert_eq!(
RegisterSchema::new(long_un, None, true, None, None, None, None, None, None, None,), AuthUsername::new(long_un),
Err(FieldFormatError::UsernameError) Err(FieldFormatError::UsernameError)
); );
} }
@ -261,7 +433,7 @@ mod schemas_tests {
fn consent_false() { fn consent_false() {
assert_eq!( assert_eq!(
RegisterSchema::new( RegisterSchema::new(
"Test".to_string(), AuthUsername::new("Test".to_string()).unwrap(),
None, None,
false, false,
None, None,
@ -279,18 +451,7 @@ mod schemas_tests {
#[test] #[test]
fn invalid_email() { fn invalid_email() {
assert_eq!( assert_eq!(
RegisterSchema::new( AuthEmail::new("p@p.p".to_string()),
"Test".to_string(),
None,
true,
Some("p@p.p".to_string()),
None,
None,
None,
None,
None,
None,
),
Err(FieldFormatError::EmailError) Err(FieldFormatError::EmailError)
) )
} }
@ -298,10 +459,10 @@ mod schemas_tests {
#[test] #[test]
fn valid_email() { fn valid_email() {
let reg = RegisterSchema::new( let reg = RegisterSchema::new(
"Test".to_string(), AuthUsername::new("Testy".to_string()).unwrap(),
None, None,
true, true,
Some("me@mail.xy".to_string()), Some(AuthEmail::new("me@mail.de".to_string()).unwrap()),
None, None,
None, None,
None, None,

View File

@ -17,4 +17,5 @@ custom_error! {
ReceivedErrorCodeError{error_code:String} = "Received the following error code while requesting from the route: {error_code}", 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}", 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}", InvalidFormBodyError{error_type: String, error:String} = "The server responded with: {error_type}: {error}",
RateLimited = "Ratelimited.",
} }

View File

@ -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 reqwest::{Client, RequestBuilder, Response};
use std::collections::{HashMap, VecDeque}; use std::collections::{HashMap, VecDeque};
@ -67,7 +70,7 @@ impl LimitedRequester {
&mut self, &mut self,
request: RequestBuilder, request: RequestBuilder,
limit_type: LimitType, limit_type: LimitType,
) -> Option<Response> { ) -> Result<Response, InstanceServerError> {
if self.can_send_request(limit_type) { if self.can_send_request(limit_type) {
let built_request = request let built_request = request
.build() .build()
@ -78,13 +81,13 @@ impl LimitedRequester {
Err(e) => panic!("An error occured while processing the response: {}", e), Err(e) => panic!("An error occured while processing the response: {}", e),
}; };
self.update_limits(&response, limit_type); self.update_limits(&response, limit_type);
return Some(response); return Ok(response);
} else { } else {
self.requests.push_back(TypedRequest { self.requests.push_back(TypedRequest {
request: request, request: request,
limit_type: limit_type, limit_type: limit_type,
}); });
return None; return Err(InstanceServerError::RateLimited);
} }
} }
@ -264,20 +267,25 @@ mod rate_limit {
String::from("http://localhost:3001/cdn"), String::from("http://localhost:3001/cdn"),
); );
let mut requester = LimitedRequester::new(urls.api.clone()).await; let mut requester = LimitedRequester::new(urls.api.clone()).await;
let mut request: Option<Response>; let mut request: Option<Result<Response, InstanceServerError>> = None;
request = None;
for _ in 0..50 { for _ in 0..=50 {
let request_path = urls.api.clone() + "/some/random/nonexisting/path"; let request_path = urls.api.clone() + "/some/random/nonexisting/path";
let request_builder = requester.http.get(request_path); let request_builder = requester.http.get(request_path);
request = requester request = Some(
.send_request(request_builder, LimitType::Channel) requester
.await; .send_request(request_builder, LimitType::Channel)
.await,
);
} }
match request { if request.is_some() {
Some(_) => assert!(false), match request.unwrap() {
None => assert!(true), Ok(_) => assert!(false),
Err(_) => assert!(true),
}
} else {
assert!(false)
} }
} }
@ -295,8 +303,8 @@ mod rate_limit {
.send_request(request_builder, LimitType::Channel) .send_request(request_builder, LimitType::Channel)
.await; .await;
let result = match request { let result = match request {
Some(result) => result, Ok(result) => result,
None => panic!("Request failed"), Err(_) => panic!("Request failed"),
}; };
let config: Config = from_str(result.text().await.unwrap().as_str()).unwrap(); let config: Config = from_str(result.text().await.unwrap().as_str()).unwrap();
println!("{:?}", config); println!("{:?}", config);