Merge branch 'pr/xystrive/521-1' into mfa
This commit is contained in:
commit
ac8352e968
|
@ -24,5 +24,5 @@ async fn main() {
|
||||||
.await
|
.await
|
||||||
.expect("An error occurred during the login process");
|
.expect("An error occurred during the login process");
|
||||||
dbg!(user.belongs_to);
|
dbg!(user.belongs_to);
|
||||||
dbg!(&user.object.read().unwrap().username);
|
dbg!(&user.object.unwrap().as_ref().read().unwrap().username);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,9 @@ use crate::errors::ChorusResult;
|
||||||
use crate::gateway::Gateway;
|
use crate::gateway::Gateway;
|
||||||
use crate::instance::{ChorusUser, Instance};
|
use crate::instance::{ChorusUser, Instance};
|
||||||
use crate::ratelimiter::ChorusRequest;
|
use crate::ratelimiter::ChorusRequest;
|
||||||
use crate::types::{GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema, User};
|
use crate::types::{
|
||||||
|
AuthenticatorType, GatewayIdentifyPayload, LimitType, LoginResult, LoginSchema, SendMfaSmsResponse, SendMfaSmsSchema, User, VerifyMFALoginResponse, VerifyMFALoginSchema
|
||||||
|
};
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
/// Logs into an existing account on the spacebar server.
|
/// Logs into an existing account on the spacebar server.
|
||||||
|
@ -32,7 +34,7 @@ impl Instance {
|
||||||
// instances' limits to pass them on as user_rate_limits later.
|
// instances' limits to pass them on as user_rate_limits later.
|
||||||
let mut user =
|
let mut user =
|
||||||
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
||||||
|
|
||||||
let login_result = chorus_request
|
let login_result = chorus_request
|
||||||
.deserialize_response::<LoginResult>(&mut user)
|
.deserialize_response::<LoginResult>(&mut user)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -40,7 +42,7 @@ impl Instance {
|
||||||
user.settings = login_result.settings;
|
user.settings = login_result.settings;
|
||||||
|
|
||||||
let object = User::get(&mut user, None).await?;
|
let object = User::get(&mut user, None).await?;
|
||||||
*user.object.write().unwrap() = object;
|
user.object = Some(Arc::new(RwLock::new(object)));
|
||||||
|
|
||||||
let mut identify = GatewayIdentifyPayload::common();
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
identify.token = user.token();
|
identify.token = user.token();
|
||||||
|
@ -48,4 +50,59 @@ impl Instance {
|
||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verifies a multi-factor authentication login
|
||||||
|
///
|
||||||
|
/// # Reference
|
||||||
|
/// See <https://docs.discord.sex/authentication#verify-mfa-login>
|
||||||
|
pub async fn verify_mfa_login(
|
||||||
|
&mut self,
|
||||||
|
authenticator: AuthenticatorType,
|
||||||
|
schema: VerifyMFALoginSchema,
|
||||||
|
) -> ChorusResult<ChorusUser> {
|
||||||
|
let endpoint_url = self.urls.api.clone() + &authenticator.to_string();
|
||||||
|
|
||||||
|
let chorus_request = ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(endpoint_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&schema),
|
||||||
|
limit_type: LimitType::AuthLogin,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut user =
|
||||||
|
ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
||||||
|
|
||||||
|
match chorus_request.deserialize_response::<VerifyMFALoginResponse>(&mut user).await? {
|
||||||
|
VerifyMFALoginResponse::Success { token, user_settings: _ } => user.set_token(token),
|
||||||
|
VerifyMFALoginResponse::UserSuspended { suspended_user_token } => return Err(crate::errors::ChorusError::SuspendUser { token: suspended_user_token }),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
|
identify.token = user.token();
|
||||||
|
user.gateway.send_identify(identify).await;
|
||||||
|
|
||||||
|
Ok(user)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a multi-factor authentication code to the user's phone number
|
||||||
|
///
|
||||||
|
/// # Reference
|
||||||
|
/// See <https://docs.discord.sex/authentication#send-mfa-sms>
|
||||||
|
pub async fn send_mfa_sms(&mut self, schema: SendMfaSmsSchema) -> ChorusResult<SendMfaSmsResponse> {
|
||||||
|
let endpoint_url = self.urls.api.clone() + "/auth/mfa/sms/send";
|
||||||
|
let chorus_request = ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(endpoint_url)
|
||||||
|
.header("Content-Type", "application/json")
|
||||||
|
.json(&schema),
|
||||||
|
limit_type: LimitType::Ip
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut chorus_user = ChorusUser::shell(Arc::new(RwLock::new(self.clone())), "None".to_string()).await;
|
||||||
|
|
||||||
|
let send_mfa_sms_response = chorus_request.deserialize_response::<SendMfaSmsResponse>(&mut chorus_user).await?;
|
||||||
|
|
||||||
|
Ok(send_mfa_sms_response)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ impl Instance {
|
||||||
let object = User::get(&mut user, None).await?;
|
let object = User::get(&mut user, None).await?;
|
||||||
let settings = User::get_settings(&mut user).await?;
|
let settings = User::get_settings(&mut user).await?;
|
||||||
|
|
||||||
*user.object.write().unwrap() = object;
|
user.object = Some(Arc::new(RwLock::new(object)));
|
||||||
*user.settings.write().unwrap() = settings;
|
*user.settings.write().unwrap() = settings;
|
||||||
|
|
||||||
let mut identify = GatewayIdentifyPayload::common();
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
|
|
|
@ -49,7 +49,7 @@ impl Instance {
|
||||||
let object = User::get(&mut user, None).await?;
|
let object = User::get(&mut user, None).await?;
|
||||||
let settings = User::get_settings(&mut user).await?;
|
let settings = User::get_settings(&mut user).await?;
|
||||||
|
|
||||||
*user.object.write().unwrap() = object;
|
user.object = Some(Arc::new(RwLock::new(object)));
|
||||||
*user.settings.write().unwrap() = settings;
|
*user.settings.write().unwrap() = settings;
|
||||||
|
|
||||||
let mut identify = GatewayIdentifyPayload::common();
|
let mut identify = GatewayIdentifyPayload::common();
|
||||||
|
|
|
@ -30,7 +30,6 @@ impl Channel {
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -61,7 +60,6 @@ impl Channel {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.id),
|
LimitType::Channel(self.id),
|
||||||
);
|
);
|
||||||
|
@ -101,7 +99,6 @@ impl Channel {
|
||||||
&url,
|
&url,
|
||||||
Some(to_string(&modify_data).unwrap()),
|
Some(to_string(&modify_data).unwrap()),
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -134,7 +131,6 @@ impl Channel {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
Default::default(),
|
Default::default(),
|
||||||
);
|
);
|
||||||
|
@ -196,7 +192,6 @@ impl Channel {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.id),
|
LimitType::Channel(self.id),
|
||||||
);
|
);
|
||||||
|
@ -225,7 +220,6 @@ impl Channel {
|
||||||
&url,
|
&url,
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
|
|
@ -151,7 +151,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -182,7 +181,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
audit_log_reason,
|
audit_log_reason,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -209,7 +207,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
audit_log_reason,
|
audit_log_reason,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -258,7 +255,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -292,7 +288,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -321,7 +316,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -348,7 +342,6 @@ impl Message {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -382,7 +375,6 @@ impl Message {
|
||||||
&url,
|
&url,
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -409,7 +401,6 @@ impl Message {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -447,7 +438,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&messages).unwrap()),
|
Some(to_string(&messages).unwrap()),
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
@ -472,7 +462,6 @@ impl Message {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
|
|
@ -83,7 +83,6 @@ impl types::Channel {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(channel_id),
|
LimitType::Channel(channel_id),
|
||||||
);
|
);
|
||||||
|
|
|
@ -36,7 +36,6 @@ impl ReactionMeta {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.channel_id),
|
LimitType::Channel(self.channel_id),
|
||||||
);
|
);
|
||||||
|
@ -65,7 +64,6 @@ impl ReactionMeta {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.channel_id),
|
LimitType::Channel(self.channel_id),
|
||||||
);
|
);
|
||||||
|
@ -96,7 +94,6 @@ impl ReactionMeta {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.channel_id),
|
LimitType::Channel(self.channel_id),
|
||||||
);
|
);
|
||||||
|
@ -130,7 +127,6 @@ impl ReactionMeta {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.channel_id),
|
LimitType::Channel(self.channel_id),
|
||||||
);
|
);
|
||||||
|
@ -159,7 +155,6 @@ impl ReactionMeta {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.channel_id),
|
LimitType::Channel(self.channel_id),
|
||||||
);
|
);
|
||||||
|
@ -196,7 +191,6 @@ impl ReactionMeta {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Channel(self.channel_id),
|
LimitType::Channel(self.channel_id),
|
||||||
);
|
);
|
||||||
|
|
|
@ -220,7 +220,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -246,7 +245,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -279,7 +277,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
None,
|
None,
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -309,7 +306,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -336,7 +332,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -362,7 +357,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -393,7 +387,6 @@ impl Guild {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -426,7 +419,6 @@ impl Guild {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -456,7 +448,6 @@ impl Guild {
|
||||||
.as_str(),
|
.as_str(),
|
||||||
Some(to_string(&schema).unwrap()),
|
Some(to_string(&schema).unwrap()),
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
@ -487,7 +478,6 @@ impl Guild {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
|
|
@ -188,7 +188,6 @@ impl types::RoleObject {
|
||||||
&url,
|
&url,
|
||||||
None,
|
None,
|
||||||
audit_log_reason.as_deref(),
|
audit_log_reason.as_deref(),
|
||||||
None,
|
|
||||||
Some(user),
|
Some(user),
|
||||||
LimitType::Guild(guild_id),
|
LimitType::Guild(guild_id),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
//! Contains all the errors that can be returned by the library.
|
//! Contains all the errors that can be returned by the library.
|
||||||
use custom_error::custom_error;
|
use custom_error::custom_error;
|
||||||
|
|
||||||
use crate::types::WebSocketEvent;
|
use crate::types::{MfaRequiredSchema, WebSocketEvent};
|
||||||
use chorus_macros::WebSocketEvent;
|
use chorus_macros::WebSocketEvent;
|
||||||
|
|
||||||
custom_error! {
|
custom_error! {
|
||||||
|
@ -46,7 +46,11 @@ custom_error! {
|
||||||
/// Malformed or unexpected response.
|
/// Malformed or unexpected response.
|
||||||
InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}",
|
InvalidResponse{error: String} = "The response is malformed and cannot be processed. Error: {error}",
|
||||||
/// Invalid, insufficient or too many arguments provided.
|
/// Invalid, insufficient or too many arguments provided.
|
||||||
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}"
|
InvalidArguments{error: String} = "Invalid arguments were provided. Error: {error}",
|
||||||
|
/// The request requires MFA verification
|
||||||
|
MfaRequired {error: MfaRequiredSchema} = "Mfa verification is required to perform this action",
|
||||||
|
/// The user's account is suspended
|
||||||
|
SuspendUser { token: String } = "Your account has been suspended"
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<reqwest::Error> for ChorusError {
|
impl From<reqwest::Error> for ChorusError {
|
||||||
|
@ -151,4 +155,3 @@ custom_error! {
|
||||||
CannotBind{error: String} = "Cannot bind socket due to a UDP error: {error}",
|
CannotBind{error: String} = "Cannot bind socket due to a UDP error: {error}",
|
||||||
CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}",
|
CannotConnect{error: String} = "Cannot connect due to a UDP error: {error}",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,16 +8,18 @@ use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use reqwest::Client;
|
use reqwest::Client;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use chrono::Utc;
|
||||||
|
|
||||||
use crate::errors::ChorusResult;
|
use crate::errors::ChorusResult;
|
||||||
use crate::gateway::{Gateway, GatewayHandle, GatewayOptions};
|
use crate::gateway::{Gateway, GatewayHandle, GatewayOptions};
|
||||||
use crate::ratelimiter::ChorusRequest;
|
use crate::ratelimiter::ChorusRequest;
|
||||||
use crate::types::types::subconfigs::limits::rates::RateLimits;
|
use crate::types::types::subconfigs::limits::rates::RateLimits;
|
||||||
use crate::types::{
|
use crate::types::{
|
||||||
GeneralConfiguration, Limit, LimitType, LimitsConfiguration, Shared, User, UserSettings,
|
GeneralConfiguration, Limit, LimitType, LimitsConfiguration, MfaTokenSchema, MfaVerifySchema, Shared, User, UserSettings, MfaToken
|
||||||
};
|
};
|
||||||
use crate::UrlBundle;
|
use crate::UrlBundle;
|
||||||
|
|
||||||
|
@ -152,9 +154,10 @@ impl fmt::Display for Token {
|
||||||
pub struct ChorusUser {
|
pub struct ChorusUser {
|
||||||
pub belongs_to: Shared<Instance>,
|
pub belongs_to: Shared<Instance>,
|
||||||
pub token: String,
|
pub token: String,
|
||||||
|
pub mfa_token: Option<MfaToken>,
|
||||||
pub limits: Option<HashMap<LimitType, Limit>>,
|
pub limits: Option<HashMap<LimitType, Limit>>,
|
||||||
pub settings: Shared<UserSettings>,
|
pub settings: Shared<UserSettings>,
|
||||||
pub object: Shared<User>,
|
pub object: Option<Shared<User>>,
|
||||||
pub gateway: GatewayHandle,
|
pub gateway: GatewayHandle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,12 +180,13 @@ impl ChorusUser {
|
||||||
token: String,
|
token: String,
|
||||||
limits: Option<HashMap<LimitType, Limit>>,
|
limits: Option<HashMap<LimitType, Limit>>,
|
||||||
settings: Shared<UserSettings>,
|
settings: Shared<UserSettings>,
|
||||||
object: Shared<User>,
|
object: Option<Shared<User>>,
|
||||||
gateway: GatewayHandle,
|
gateway: GatewayHandle,
|
||||||
) -> ChorusUser {
|
) -> ChorusUser {
|
||||||
ChorusUser {
|
ChorusUser {
|
||||||
belongs_to,
|
belongs_to,
|
||||||
token,
|
token,
|
||||||
|
mfa_token: None,
|
||||||
limits,
|
limits,
|
||||||
settings,
|
settings,
|
||||||
object,
|
object,
|
||||||
|
@ -197,7 +201,6 @@ impl ChorusUser {
|
||||||
/// first.
|
/// first.
|
||||||
pub(crate) async fn shell(instance: Shared<Instance>, token: String) -> ChorusUser {
|
pub(crate) async fn shell(instance: Shared<Instance>, token: String) -> ChorusUser {
|
||||||
let settings = Arc::new(RwLock::new(UserSettings::default()));
|
let settings = Arc::new(RwLock::new(UserSettings::default()));
|
||||||
let object = Arc::new(RwLock::new(User::default()));
|
|
||||||
let wss_url = instance.read().unwrap().urls.wss.clone();
|
let wss_url = instance.read().unwrap().urls.wss.clone();
|
||||||
// Dummy gateway object
|
// Dummy gateway object
|
||||||
let gateway = Gateway::spawn(wss_url, GatewayOptions::default())
|
let gateway = Gateway::spawn(wss_url, GatewayOptions::default())
|
||||||
|
@ -205,6 +208,7 @@ impl ChorusUser {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
ChorusUser {
|
ChorusUser {
|
||||||
token,
|
token,
|
||||||
|
mfa_token: None,
|
||||||
belongs_to: instance.clone(),
|
belongs_to: instance.clone(),
|
||||||
limits: instance
|
limits: instance
|
||||||
.read()
|
.read()
|
||||||
|
@ -213,8 +217,41 @@ impl ChorusUser {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|info| info.ratelimits.clone()),
|
.map(|info| info.ratelimits.clone()),
|
||||||
settings,
|
settings,
|
||||||
object,
|
object: None,
|
||||||
gateway,
|
gateway,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sends a request to complete an MFA challenge.
|
||||||
|
///
|
||||||
|
/// If successful, the MFA verification JWT returned is set on the current [ChorusUser] executing the
|
||||||
|
/// request.
|
||||||
|
///
|
||||||
|
/// The JWT token expires after 5 minutes.
|
||||||
|
///
|
||||||
|
/// # Reference
|
||||||
|
/// See <https://docs.discord.sex/authentication#verify-mfa>
|
||||||
|
pub async fn complete_mfa_challenge(&mut self, mfa_verify_schema: MfaVerifySchema) -> ChorusResult<()> {
|
||||||
|
let endpoint_url = self.belongs_to.read().unwrap().urls.api.clone() + "/mfa/finish";
|
||||||
|
let chorus_request = ChorusRequest {
|
||||||
|
request: Client::new()
|
||||||
|
.post(endpoint_url)
|
||||||
|
.header("Authorization", self.token())
|
||||||
|
.json(&mfa_verify_schema),
|
||||||
|
limit_type: match self.object.is_some() {
|
||||||
|
true => LimitType::Global,
|
||||||
|
false => LimitType::Ip,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let mfa_token_schema = chorus_request
|
||||||
|
.deserialize_response::<MfaTokenSchema>(self).await?;
|
||||||
|
|
||||||
|
self.mfa_token = Some(MfaToken {
|
||||||
|
token: mfa_token_schema.token,
|
||||||
|
expires_at: Utc::now() + Duration::from_secs(60 * 5),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use serde_json::from_str;
|
||||||
use crate::{
|
use crate::{
|
||||||
errors::{ChorusError, ChorusResult},
|
errors::{ChorusError, ChorusResult},
|
||||||
instance::ChorusUser,
|
instance::ChorusUser,
|
||||||
types::{types::subconfigs::limits::rates::RateLimits, Limit, LimitType, LimitsConfiguration},
|
types::{types::subconfigs::limits::rates::RateLimits, Limit, LimitType, LimitsConfiguration, MfaRequiredSchema},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Chorus' request struct. This struct is used to send rate-limited requests to the Spacebar server.
|
/// Chorus' request struct. This struct is used to send rate-limited requests to the Spacebar server.
|
||||||
|
@ -34,13 +34,12 @@ impl ChorusRequest {
|
||||||
/// * [`http::Method::DELETE`]
|
/// * [`http::Method::DELETE`]
|
||||||
/// * [`http::Method::PATCH`]
|
/// * [`http::Method::PATCH`]
|
||||||
/// * [`http::Method::HEAD`]
|
/// * [`http::Method::HEAD`]
|
||||||
#[allow(unused_variables)] // TODO: Add mfa_token to request, once we figure out *how* to do so correctly
|
#[allow(unused_variables)]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
method: http::Method,
|
method: http::Method,
|
||||||
url: &str,
|
url: &str,
|
||||||
body: Option<String>,
|
body: Option<String>,
|
||||||
audit_log_reason: Option<&str>,
|
audit_log_reason: Option<&str>,
|
||||||
mfa_token: Option<&str>,
|
|
||||||
chorus_user: Option<&mut ChorusUser>,
|
chorus_user: Option<&mut ChorusUser>,
|
||||||
limit_type: LimitType,
|
limit_type: LimitType,
|
||||||
) -> ChorusRequest {
|
) -> ChorusRequest {
|
||||||
|
@ -266,7 +265,14 @@ impl ChorusRequest {
|
||||||
|
|
||||||
async fn interpret_error(response: reqwest::Response) -> ChorusError {
|
async fn interpret_error(response: reqwest::Response) -> ChorusError {
|
||||||
match response.status().as_u16() {
|
match response.status().as_u16() {
|
||||||
401..=403 | 407 => ChorusError::NoPermission,
|
401 => {
|
||||||
|
let response = response.text().await.unwrap();
|
||||||
|
match serde_json::from_str::<MfaRequiredSchema>(&response) {
|
||||||
|
Ok(response) => ChorusError::MfaRequired { error: response },
|
||||||
|
Err(_) => ChorusError::NoPermission,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
402..=403 | 407 => ChorusError::NoPermission,
|
||||||
404 => ChorusError::NotFound {
|
404 => ChorusError::NotFound {
|
||||||
error: response.text().await.unwrap(),
|
error: response.text().await.unwrap(),
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MfaToken {
|
||||||
|
pub token: String,
|
||||||
|
pub expires_at: DateTime<Utc>,
|
||||||
|
}
|
|
@ -26,6 +26,7 @@ pub use user::*;
|
||||||
pub use user_settings::*;
|
pub use user_settings::*;
|
||||||
pub use voice_state::*;
|
pub use voice_state::*;
|
||||||
pub use webhook::*;
|
pub use webhook::*;
|
||||||
|
pub use mfa_token::*;
|
||||||
|
|
||||||
use crate::types::Shared;
|
use crate::types::Shared;
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
|
@ -67,6 +68,7 @@ mod user;
|
||||||
mod user_settings;
|
mod user_settings;
|
||||||
mod voice_state;
|
mod voice_state;
|
||||||
mod webhook;
|
mod webhook;
|
||||||
|
mod mfa_token;
|
||||||
|
|
||||||
#[cfg(feature = "client")]
|
#[cfg(feature = "client")]
|
||||||
#[async_trait(?Send)]
|
#[async_trait(?Send)]
|
||||||
|
|
|
@ -36,11 +36,25 @@ pub struct LoginSchema {
|
||||||
pub gift_code_sku_id: Option<String>,
|
pub gift_code_sku_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub struct TotpSchema {
|
pub struct VerifyMFALoginSchema {
|
||||||
code: String,
|
pub ticket: String,
|
||||||
ticket: String,
|
pub code: String,
|
||||||
gift_code_sku_id: Option<String>,
|
pub login_source: Option<String>,
|
||||||
login_source: Option<String>,
|
pub gift_code_sku_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum VerifyMFALoginResponse {
|
||||||
|
Success { token: String, user_settings: LoginSettings },
|
||||||
|
UserSuspended { suspended_user_token: String }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct LoginSettings {
|
||||||
|
pub locale: String,
|
||||||
|
pub theme: String,
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct MfaRequiredSchema {
|
||||||
|
pub message: String,
|
||||||
|
pub code: i32,
|
||||||
|
pub mfa: MfaVerificationSchema,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for MfaRequiredSchema {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_struct("MfaRequired")
|
||||||
|
.field("message", &self.message)
|
||||||
|
.field("code", &self.code)
|
||||||
|
.field("mfa", &self.mfa)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct MfaVerificationSchema {
|
||||||
|
pub ticket: String,
|
||||||
|
pub methods: Vec<MfaMethod>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct MfaMethod {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
pub kind: AuthenticatorType,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub challenge: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub backup_codes_allowed: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum AuthenticatorType {
|
||||||
|
TOTP,
|
||||||
|
SMS,
|
||||||
|
Backup,
|
||||||
|
WebAuthn,
|
||||||
|
Password,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for AuthenticatorType {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}",
|
||||||
|
match self {
|
||||||
|
AuthenticatorType::TOTP => "totp",
|
||||||
|
AuthenticatorType::SMS => "sms",
|
||||||
|
AuthenticatorType::Backup => "backup",
|
||||||
|
AuthenticatorType::WebAuthn => "webauthn",
|
||||||
|
AuthenticatorType::Password => "password",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub struct MfaVerifySchema {
|
||||||
|
pub ticket: String,
|
||||||
|
pub mfa_type: AuthenticatorType,
|
||||||
|
pub data: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct MfaTokenSchema {
|
||||||
|
pub token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SendMfaSmsSchema {
|
||||||
|
pub ticket: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct SendMfaSmsResponse {
|
||||||
|
pub phone: String,
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
pub use apierror::*;
|
pub use apierror::*;
|
||||||
pub use audit_log::*;
|
pub use audit_log::*;
|
||||||
pub use auth::*;
|
pub use auth::*;
|
||||||
|
pub use mfa::*;
|
||||||
pub use channel::*;
|
pub use channel::*;
|
||||||
pub use guild::*;
|
pub use guild::*;
|
||||||
pub use message::*;
|
pub use message::*;
|
||||||
|
@ -17,6 +18,7 @@ pub use voice_state::*;
|
||||||
mod apierror;
|
mod apierror;
|
||||||
mod audit_log;
|
mod audit_log;
|
||||||
mod auth;
|
mod auth;
|
||||||
|
mod mfa;
|
||||||
mod channel;
|
mod channel;
|
||||||
mod guild;
|
mod guild;
|
||||||
mod message;
|
mod message;
|
||||||
|
|
125
tests/auth.rs
125
tests/auth.rs
|
@ -2,9 +2,9 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use std::str::FromStr;
|
use std::{os::unix::fs::chroot, str::FromStr};
|
||||||
|
|
||||||
use chorus::types::{LoginSchema, RegisterSchema};
|
use chorus::{instance::ChorusUser, types::{AuthenticatorType, LoginSchema, MfaVerifySchema, RegisterSchema, SendMfaSmsSchema}};
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
use wasm_bindgen_test::*;
|
use wasm_bindgen_test::*;
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
@ -85,8 +85,8 @@ async fn test_login_with_token() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
bundle.user.object.read().unwrap().id,
|
bundle.user.object.as_ref().unwrap().read().unwrap().id,
|
||||||
other_user.object.read().unwrap().id
|
other_user.object.unwrap().read().unwrap().id
|
||||||
);
|
);
|
||||||
assert_eq!(bundle.user.token, other_user.token);
|
assert_eq!(bundle.user.token, other_user.token);
|
||||||
|
|
||||||
|
@ -105,3 +105,120 @@ async fn test_login_with_invalid_token() {
|
||||||
|
|
||||||
common::teardown(bundle).await;
|
common::teardown(bundle).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||||
|
async fn test_complete_mfa_challenge_totp() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
|
let token = "".to_string();
|
||||||
|
let mut chorus_user = bundle.instance.login_with_token(token).await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = MfaVerifySchema {
|
||||||
|
ticket: "".to_string(),
|
||||||
|
mfa_type: AuthenticatorType::TOTP,
|
||||||
|
data: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = chorus_user.complete_mfa_challenge(schema)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||||
|
async fn test_complete_mfa_challenge_sms() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
|
let token = "".to_string();
|
||||||
|
let mut chorus_user = bundle.instance.login_with_token(token).await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = MfaVerifySchema {
|
||||||
|
ticket: "".to_string(),
|
||||||
|
mfa_type: AuthenticatorType::SMS,
|
||||||
|
data: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = chorus_user.complete_mfa_challenge(schema)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||||
|
async fn test_verify_mfa_login_webauthn() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
|
let token = "".to_string();
|
||||||
|
let mut chorus_user = bundle.instance.login_with_token(token).await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = MfaVerifySchema {
|
||||||
|
ticket: "".to_string(),
|
||||||
|
mfa_type: AuthenticatorType::SMS,
|
||||||
|
data: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = chorus_user.complete_mfa_challenge(schema)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||||
|
async fn test_complete_mfa_challenge_backup() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
|
let token = "".to_string();
|
||||||
|
let mut chorus_user = bundle.instance.login_with_token(token).await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = MfaVerifySchema {
|
||||||
|
ticket: "".to_string(),
|
||||||
|
mfa_type: AuthenticatorType::Backup,
|
||||||
|
data: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = chorus_user.complete_mfa_challenge(schema)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||||
|
async fn test_complete_mfa_challenge_password() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
|
let token = "".to_string();
|
||||||
|
let mut chorus_user = bundle.instance.login_with_token(token).await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let schema = MfaVerifySchema {
|
||||||
|
ticket: "".to_string(),
|
||||||
|
mfa_type: AuthenticatorType::Password,
|
||||||
|
data: "".to_string(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = chorus_user.complete_mfa_challenge(schema)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
assert!(result.is_ok())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
|
||||||
|
#[cfg_attr(not(target_arch = "wasm32"), tokio::test)]
|
||||||
|
async fn test_send_mfa_sms() {
|
||||||
|
let mut bundle = common::setup().await;
|
||||||
|
|
||||||
|
let schema = SendMfaSmsSchema { ticket: "".to_string() };
|
||||||
|
|
||||||
|
let result = bundle.instance.send_mfa_sms(schema).await;
|
||||||
|
|
||||||
|
assert!(result.is_ok())
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,11 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
use chorus::types::{self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags, PermissionOverwrite, PermissionOverwriteType, PrivateChannelCreateSchema, RelationshipType, Snowflake};
|
use chorus::types::{
|
||||||
|
self, Channel, GetChannelMessagesSchema, MessageSendSchema, PermissionFlags,
|
||||||
|
PermissionOverwrite, PermissionOverwriteType, PrivateChannelCreateSchema, RelationshipType,
|
||||||
|
Snowflake,
|
||||||
|
};
|
||||||
|
|
||||||
mod common;
|
mod common;
|
||||||
|
|
||||||
|
@ -67,7 +71,7 @@ async fn modify_channel() {
|
||||||
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
|
assert_eq!(modified_channel.name, Some(CHANNEL_NAME.to_string()));
|
||||||
|
|
||||||
let permission_override = PermissionFlags::MANAGE_CHANNELS | PermissionFlags::MANAGE_MESSAGES;
|
let permission_override = PermissionFlags::MANAGE_CHANNELS | PermissionFlags::MANAGE_MESSAGES;
|
||||||
let user_id: types::Snowflake = bundle.user.object.read().unwrap().id;
|
let user_id: types::Snowflake = bundle.user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
let permission_override = PermissionOverwrite {
|
let permission_override = PermissionOverwrite {
|
||||||
id: user_id,
|
id: user_id,
|
||||||
overwrite_type: PermissionOverwriteType::Member,
|
overwrite_type: PermissionOverwriteType::Member,
|
||||||
|
@ -155,7 +159,13 @@ async fn create_dm() {
|
||||||
let other_user = bundle.create_user("integrationtestuser2").await;
|
let other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let private_channel_create_schema = PrivateChannelCreateSchema {
|
let private_channel_create_schema = PrivateChannelCreateSchema {
|
||||||
recipients: Some(Vec::from([other_user.object.read().unwrap().id])),
|
recipients: Some(Vec::from([other_user
|
||||||
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.id])),
|
||||||
access_tokens: None,
|
access_tokens: None,
|
||||||
nicks: None,
|
nicks: None,
|
||||||
};
|
};
|
||||||
|
@ -174,7 +184,7 @@ async fn create_dm() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id
|
.id
|
||||||
.clone(),
|
.clone(),
|
||||||
other_user.object.read().unwrap().id
|
other_user.object.unwrap().read().unwrap().id
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
dm_channel
|
dm_channel
|
||||||
|
@ -187,7 +197,7 @@ async fn create_dm() {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.id
|
.id
|
||||||
.clone(),
|
.clone(),
|
||||||
user.object.read().unwrap().id.clone()
|
user.object.as_ref().unwrap().read().unwrap().id.clone()
|
||||||
);
|
);
|
||||||
common::teardown(bundle).await;
|
common::teardown(bundle).await;
|
||||||
}
|
}
|
||||||
|
@ -199,9 +209,9 @@ async fn remove_add_person_from_to_dm() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let mut third_user = bundle.create_user("integrationtestuser3").await;
|
let mut third_user = bundle.create_user("integrationtestuser3").await;
|
||||||
let third_user_id = third_user.object.read().unwrap().id;
|
let third_user_id = third_user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
let other_user_id = other_user.object.read().unwrap().id;
|
let other_user_id = other_user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
let user_id = bundle.user.object.read().unwrap().id;
|
let user_id = bundle.user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let private_channel_create_schema = PrivateChannelCreateSchema {
|
let private_channel_create_schema = PrivateChannelCreateSchema {
|
||||||
recipients: Some(Vec::from([other_user_id, third_user_id])),
|
recipients: Some(Vec::from([other_user_id, third_user_id])),
|
||||||
|
|
|
@ -47,6 +47,7 @@ impl TestBundle {
|
||||||
ChorusUser {
|
ChorusUser {
|
||||||
belongs_to: self.user.belongs_to.clone(),
|
belongs_to: self.user.belongs_to.clone(),
|
||||||
token: self.user.token.clone(),
|
token: self.user.token.clone(),
|
||||||
|
mfa_token: None,
|
||||||
limits: self.user.limits.clone(),
|
limits: self.user.limits.clone(),
|
||||||
settings: self.user.settings.clone(),
|
settings: self.user.settings.clone(),
|
||||||
object: self.user.object.clone(),
|
object: self.user.object.clone(),
|
||||||
|
|
|
@ -60,7 +60,8 @@ async fn guild_create_ban() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
other_user.accept_invite(&invite.code, None).await.unwrap();
|
other_user.accept_invite(&invite.code, None).await.unwrap();
|
||||||
let other_user_id = other_user.object.read().unwrap().id;
|
let other_user_id = other_user.object.as_ref().unwrap()
|
||||||
|
.read().unwrap().id;
|
||||||
Guild::create_ban(
|
Guild::create_ban(
|
||||||
guild.id,
|
guild.id,
|
||||||
other_user_id,
|
other_user_id,
|
||||||
|
@ -103,7 +104,9 @@ async fn guild_remove_member() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
other_user.accept_invite(&invite.code, None).await.unwrap();
|
other_user.accept_invite(&invite.code, None).await.unwrap();
|
||||||
let other_user_id = other_user.object.read().unwrap().id;
|
let other_user_id = other_user.object
|
||||||
|
.as_ref().unwrap()
|
||||||
|
.read().unwrap().id;
|
||||||
Guild::remove_member(guild.id, other_user_id, None, &mut bundle.user)
|
Guild::remove_member(guild.id, other_user_id, None, &mut bundle.user)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
|
@ -16,7 +16,8 @@ async fn add_remove_role() -> ChorusResult<()> {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let guild = bundle.guild.read().unwrap().id;
|
let guild = bundle.guild.read().unwrap().id;
|
||||||
let role = bundle.role.read().unwrap().id;
|
let role = bundle.role.read().unwrap().id;
|
||||||
let member_id = bundle.user.object.read().unwrap().id;
|
let member_id = bundle.user.object.as_ref().unwrap()
|
||||||
|
.read().unwrap().id;
|
||||||
GuildMember::add_role(&mut bundle.user, guild, member_id, role).await?;
|
GuildMember::add_role(&mut bundle.user, guild, member_id, role).await?;
|
||||||
let member = GuildMember::get(&mut bundle.user, guild, member_id)
|
let member = GuildMember::get(&mut bundle.user, guild, member_id)
|
||||||
.await
|
.await
|
||||||
|
|
|
@ -106,7 +106,7 @@ async fn search_messages() {
|
||||||
let _arg = Some(&vec_attach);
|
let _arg = Some(&vec_attach);
|
||||||
let message = bundle.user.send_message(message, channel.id).await.unwrap();
|
let message = bundle.user.send_message(message, channel.id).await.unwrap();
|
||||||
let query = MessageSearchQuery {
|
let query = MessageSearchQuery {
|
||||||
author_id: Some(Vec::from([bundle.user.object.read().unwrap().id])),
|
author_id: Some(Vec::from([bundle.user.object.as_ref().unwrap().read().unwrap().id])),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let guild_id = bundle.guild.read().unwrap().id;
|
let guild_id = bundle.guild.read().unwrap().id;
|
||||||
|
|
|
@ -16,9 +16,24 @@ async fn test_get_mutual_relationships() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let username = user.object.read().unwrap().username.clone();
|
|
||||||
let discriminator = user.object.read().unwrap().discriminator.clone();
|
let username = user
|
||||||
let other_user_id: types::Snowflake = other_user.object.read().unwrap().id;
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.username
|
||||||
|
.clone();
|
||||||
|
let discriminator = user
|
||||||
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.discriminator
|
||||||
|
.clone();
|
||||||
|
let other_user_id: types::Snowflake = other_user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
let friend_request_schema = types::FriendRequestSendSchema {
|
let friend_request_schema = types::FriendRequestSendSchema {
|
||||||
username,
|
username,
|
||||||
discriminator: Some(discriminator),
|
discriminator: Some(discriminator),
|
||||||
|
@ -38,8 +53,22 @@ async fn test_get_relationships() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let username = user.object.read().unwrap().username.clone();
|
let username = user
|
||||||
let discriminator = user.object.read().unwrap().discriminator.clone();
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.username
|
||||||
|
.clone();
|
||||||
|
let discriminator = user
|
||||||
|
.object
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.read()
|
||||||
|
.unwrap()
|
||||||
|
.discriminator
|
||||||
|
.clone();
|
||||||
let friend_request_schema = types::FriendRequestSendSchema {
|
let friend_request_schema = types::FriendRequestSendSchema {
|
||||||
username,
|
username,
|
||||||
discriminator: Some(discriminator),
|
discriminator: Some(discriminator),
|
||||||
|
@ -51,7 +80,7 @@ async fn test_get_relationships() {
|
||||||
let relationships = user.get_relationships().await.unwrap();
|
let relationships = user.get_relationships().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().id,
|
relationships.first().unwrap().id,
|
||||||
other_user.object.read().unwrap().id
|
other_user.object.unwrap().read().unwrap().id
|
||||||
);
|
);
|
||||||
common::teardown(bundle).await
|
common::teardown(bundle).await
|
||||||
}
|
}
|
||||||
|
@ -62,8 +91,8 @@ async fn test_modify_relationship_friends() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let user_id: types::Snowflake = user.object.read().unwrap().id;
|
let user_id: types::Snowflake = user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
let other_user_id: types::Snowflake = other_user.object.read().unwrap().id;
|
let other_user_id: types::Snowflake = other_user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
|
|
||||||
other_user
|
other_user
|
||||||
.modify_user_relationship(user_id, types::RelationshipType::Friends)
|
.modify_user_relationship(user_id, types::RelationshipType::Friends)
|
||||||
|
@ -72,7 +101,7 @@ async fn test_modify_relationship_friends() {
|
||||||
let relationships = user.get_relationships().await.unwrap();
|
let relationships = user.get_relationships().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().id,
|
relationships.first().unwrap().id,
|
||||||
other_user.object.read().unwrap().id
|
other_user.object.as_ref().unwrap().read().unwrap().id
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().relationship_type,
|
relationships.first().unwrap().relationship_type,
|
||||||
|
@ -81,7 +110,7 @@ async fn test_modify_relationship_friends() {
|
||||||
let relationships = other_user.get_relationships().await.unwrap();
|
let relationships = other_user.get_relationships().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().id,
|
relationships.first().unwrap().id,
|
||||||
user.object.read().unwrap().id
|
user.object.as_ref().unwrap().read().unwrap().id
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().relationship_type,
|
relationships.first().unwrap().relationship_type,
|
||||||
|
@ -114,7 +143,7 @@ async fn test_modify_relationship_block() {
|
||||||
let mut bundle = common::setup().await;
|
let mut bundle = common::setup().await;
|
||||||
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
let mut other_user = bundle.create_user("integrationtestuser2").await;
|
||||||
let user = &mut bundle.user;
|
let user = &mut bundle.user;
|
||||||
let user_id: types::Snowflake = user.object.read().unwrap().id;
|
let user_id: types::Snowflake = user.object.as_ref().unwrap().read().unwrap().id;
|
||||||
|
|
||||||
other_user
|
other_user
|
||||||
.modify_user_relationship(user_id, types::RelationshipType::Blocked)
|
.modify_user_relationship(user_id, types::RelationshipType::Blocked)
|
||||||
|
@ -125,7 +154,7 @@ async fn test_modify_relationship_block() {
|
||||||
let relationships = other_user.get_relationships().await.unwrap();
|
let relationships = other_user.get_relationships().await.unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().id,
|
relationships.first().unwrap().id,
|
||||||
user.object.read().unwrap().id
|
user.object.as_ref().unwrap().read().unwrap().id
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
relationships.first().unwrap().relationship_type,
|
relationships.first().unwrap().relationship_type,
|
||||||
|
|
Loading…
Reference in New Issue