feat(user): add support for resend invite (#3523)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Apoorv Dixit
2024-02-02 15:04:49 +05:30
committed by GitHub
parent 892b04f805
commit cf0e0b330e
7 changed files with 120 additions and 33 deletions

View File

@ -12,8 +12,8 @@ use crate::user::{
}, },
AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest, DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignInResponse, InviteUserResponse, ReInviteUserRequest, ResetPasswordRequest, SendVerifyEmailRequest,
SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
UpdateUserAccountDetailsRequest, UserMerchantCreate, VerifyEmailRequest, UpdateUserAccountDetailsRequest, UserMerchantCreate, VerifyEmailRequest,
}; };
@ -54,6 +54,7 @@ common_utils::impl_misc_api_event_type!(
ResetPasswordRequest, ResetPasswordRequest,
InviteUserRequest, InviteUserRequest,
InviteUserResponse, InviteUserResponse,
ReInviteUserRequest,
VerifyEmailRequest, VerifyEmailRequest,
SendVerifyEmailRequest, SendVerifyEmailRequest,
SignInResponse, SignInResponse,

View File

@ -113,6 +113,11 @@ pub struct InviteMultipleUserResponse {
pub error: Option<String>, pub error: Option<String>,
} }
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
pub struct ReInviteUserRequest {
pub email: pii::Email,
}
#[derive(Debug, serde::Deserialize, serde::Serialize)] #[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct SwitchMerchantIdRequest { pub struct SwitchMerchantIdRequest {
pub merchant_id: String, pub merchant_id: String,

View File

@ -706,6 +706,63 @@ async fn handle_new_user_invitation(
}) })
} }
#[cfg(feature = "email")]
pub async fn resend_invite(
state: AppState,
user_from_token: auth::UserFromToken,
request: user_api::ReInviteUserRequest,
) -> UserResponse<()> {
let invitee_email = domain::UserEmail::from_pii_email(request.email)?;
let user: domain::UserFromStorage = state
.store
.find_user_by_email(invitee_email.clone().get_secret().expose().as_str())
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::InvalidRoleOperation)
.attach_printable("User not found in the records")
} else {
e.change_context(UserErrors::InternalServerError)
}
})?
.into();
let user_role = state
.store
.find_user_role_by_user_id_merchant_id(user.get_user_id(), &user_from_token.merchant_id)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::InvalidRoleOperation)
.attach_printable("User role with given UserId MerchantId not found")
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;
if !matches!(user_role.status, UserStatus::InvitationSent) {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("Invalid Status for Reinvitation");
}
let email_contents = email_types::InviteUser {
recipient_email: invitee_email,
user_name: domain::UserName::new(user.get_name())?,
settings: state.conf.clone(),
subject: "You have been invited to join Hyperswitch Community!",
merchant_id: user_from_token.merchant_id,
};
state
.email_client
.compose_and_send_email(
Box::new(email_contents),
state.conf.proxy.https_url.as_ref(),
)
.await
.change_context(UserErrors::InternalServerError)?;
Ok(ApplicationResponse::StatusOk)
}
pub async fn create_internal_user( pub async fn create_internal_user(
state: AppState, state: AppState,
request: user_api::CreateInternalUserRequest, request: user_api::CreateInternalUserRequest,

View File

@ -976,41 +976,12 @@ impl User {
.service(web::resource("/switch/list").route(web::get().to(list_merchant_ids_for_user))) .service(web::resource("/switch/list").route(web::get().to(list_merchant_ids_for_user)))
.service(web::resource("/permission_info").route(web::get().to(get_authorization_info))) .service(web::resource("/permission_info").route(web::get().to(get_authorization_info)))
.service(web::resource("/update").route(web::post().to(update_user_account_details))) .service(web::resource("/update").route(web::post().to(update_user_account_details)))
.service(
web::resource("/user/invite_multiple").route(web::post().to(invite_multiple_user)),
)
.service( .service(
web::resource("/data") web::resource("/data")
.route(web::get().to(get_multiple_dashboard_metadata)) .route(web::get().to(get_multiple_dashboard_metadata))
.route(web::post().to(set_dashboard_metadata)), .route(web::post().to(set_dashboard_metadata)),
)
.service(web::resource("/user/delete").route(web::delete().to(delete_user_role)));
// User management
route = route.service(
web::scope("/user")
.service(web::resource("/list").route(web::get().to(get_user_details)))
.service(web::resource("/invite").route(web::post().to(invite_user)))
.service(web::resource("/invite/accept").route(web::post().to(accept_invitation)))
.service(web::resource("/update_role").route(web::post().to(update_user_role))),
); );
// Role information
route = route.service(
web::scope("/role")
.service(web::resource("").route(web::get().to(get_role_from_token)))
.service(web::resource("/list").route(web::get().to(list_all_roles)))
.service(web::resource("/{role_id}").route(web::get().to(get_role))),
);
#[cfg(feature = "dummy_connector")]
{
route = route.service(
web::resource("/sample_data")
.route(web::post().to(generate_sample_data))
.route(web::delete().to(delete_sample_data)),
)
}
#[cfg(feature = "email")] #[cfg(feature = "email")]
{ {
route = route route = route
@ -1031,12 +1002,43 @@ impl User {
.service( .service(
web::resource("/verify_email_request") web::resource("/verify_email_request")
.route(web::post().to(verify_email_request)), .route(web::post().to(verify_email_request)),
); )
.service(web::resource("/user/resend_invite").route(web::post().to(resend_invite)));
} }
#[cfg(not(feature = "email"))] #[cfg(not(feature = "email"))]
{ {
route = route.service(web::resource("/signup").route(web::post().to(user_signup))) route = route.service(web::resource("/signup").route(web::post().to(user_signup)))
} }
// User management
route = route.service(
web::scope("/user")
.service(web::resource("/list").route(web::get().to(get_user_details)))
.service(web::resource("/invite").route(web::post().to(invite_user)))
.service(
web::resource("/invite_multiple").route(web::post().to(invite_multiple_user)),
)
.service(web::resource("/invite/accept").route(web::post().to(accept_invitation)))
.service(web::resource("/update_role").route(web::post().to(update_user_role)))
.service(web::resource("/delete").route(web::delete().to(delete_user_role))),
);
// Role information
route = route.service(
web::scope("/role")
.service(web::resource("").route(web::get().to(get_role_from_token)))
.service(web::resource("/list").route(web::get().to(list_all_roles)))
.service(web::resource("/{role_id}").route(web::get().to(get_role))),
);
#[cfg(feature = "dummy_connector")]
{
route = route.service(
web::resource("/sample_data")
.route(web::post().to(generate_sample_data))
.route(web::delete().to(delete_sample_data)),
)
}
route route
} }
} }

View File

@ -182,6 +182,7 @@ impl From<Flow> for ApiIdentifier {
| Flow::ResetPassword | Flow::ResetPassword
| Flow::InviteUser | Flow::InviteUser
| Flow::InviteMultipleUser | Flow::InviteMultipleUser
| Flow::ReInviteUser
| Flow::UserSignUpWithMerchantId | Flow::UserSignUpWithMerchantId
| Flow::VerifyEmailWithoutInviteChecks | Flow::VerifyEmailWithoutInviteChecks
| Flow::VerifyEmail | Flow::VerifyEmail

View File

@ -401,6 +401,25 @@ pub async fn invite_multiple_user(
.await .await
} }
#[cfg(feature = "email")]
pub async fn resend_invite(
state: web::Data<AppState>,
req: HttpRequest,
payload: web::Json<user_api::ReInviteUserRequest>,
) -> HttpResponse {
let flow = Flow::ReInviteUser;
Box::pin(api::server_wrap(
flow,
state.clone(),
&req,
payload.into_inner(),
user_core::resend_invite,
&auth::JWTAuth(Permission::UsersWrite),
api_locking::LockAction::NotApplicable,
))
.await
}
#[cfg(feature = "email")] #[cfg(feature = "email")]
pub async fn verify_email_without_invite_checks( pub async fn verify_email_without_invite_checks(
state: web::Data<AppState>, state: web::Data<AppState>,

View File

@ -331,6 +331,8 @@ pub enum Flow {
InviteUser, InviteUser,
/// Invite multiple users /// Invite multiple users
InviteMultipleUser, InviteMultipleUser,
/// Reinvite user
ReInviteUser,
/// Delete user role /// Delete user role
DeleteUserRole, DeleteUserRole,
/// Incremental Authorization flow /// Incremental Authorization flow