Skip to content

Commit

Permalink
refactor(user): Deprecate Signin, Verify email and Invite v1 APIs (#4465
Browse files Browse the repository at this point in the history
)
  • Loading branch information
ThisIsMani committed Apr 30, 2024
1 parent 0c9ba1e commit b0133f3
Show file tree
Hide file tree
Showing 7 changed files with 3 additions and 351 deletions.
7 changes: 3 additions & 4 deletions crates/api_models/src/events/user.rs
Expand Up @@ -12,9 +12,9 @@ use crate::user::{
},
AcceptInviteFromEmailRequest, AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest,
CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest,
GetUserDetailsRequest, GetUserDetailsResponse, InviteUserRequest, InviteUserResponse,
ListUsersResponse, ReInviteUserRequest, ResetPasswordRequest, SendVerifyEmailRequest,
SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
GetUserDetailsRequest, GetUserDetailsResponse, InviteUserRequest, ListUsersResponse,
ReInviteUserRequest, ResetPasswordRequest, SendVerifyEmailRequest, SignInResponse,
SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
UpdateUserAccountDetailsRequest, UserMerchantCreate, VerifyEmailRequest,
};

Expand Down Expand Up @@ -54,7 +54,6 @@ common_utils::impl_misc_api_event_type!(
ForgotPasswordRequest,
ResetPasswordRequest,
InviteUserRequest,
InviteUserResponse,
ReInviteUserRequest,
VerifyEmailRequest,
SendVerifyEmailRequest,
Expand Down
6 changes: 0 additions & 6 deletions crates/api_models/src/user.rs
Expand Up @@ -98,12 +98,6 @@ pub struct InviteUserRequest {
pub role_id: String,
}

#[derive(Debug, serde::Serialize)]
pub struct InviteUserResponse {
pub is_email_sent: bool,
pub password: Option<Secret<String>>,
}

#[derive(Debug, serde::Serialize)]
pub struct InviteMultipleUserResponse {
pub email: pii::Email,
Expand Down
266 changes: 0 additions & 266 deletions crates/router/src/core/user.rs
Expand Up @@ -100,34 +100,6 @@ pub async fn signup(
auth::cookies::set_cookie_response(response, token)
}

pub async fn signin_without_invite_checks(
state: AppState,
request: user_api::SignInRequest,
) -> UserResponse<user_api::DashboardEntryResponse> {
let user_from_db: domain::UserFromStorage = state
.store
.find_user_by_email(&request.email)
.await
.map_err(|e| {
if e.current_context().is_db_not_found() {
e.change_context(UserErrors::InvalidCredentials)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?
.into();

user_from_db.compare_password(request.password)?;

let user_role = user_from_db.get_role_from_db(state.clone()).await?;
utils::user_role::set_role_permissions_in_cache_by_user_role(&state, &user_role).await;

let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
let response =
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token.clone())?;
auth::cookies::set_cookie_response(response, token)
}

pub async fn signin(
state: AppState,
request: user_api::SignInRequest,
Expand Down Expand Up @@ -424,206 +396,6 @@ pub async fn reset_password(
Ok(ApplicationResponse::StatusOk)
}

pub async fn invite_user(
state: AppState,
request: user_api::InviteUserRequest,
user_from_token: auth::UserFromToken,
req_state: ReqState,
) -> UserResponse<user_api::InviteUserResponse> {
let inviter_user = state
.store
.find_user_by_id(user_from_token.user_id.as_str())
.await
.change_context(UserErrors::InternalServerError)?;

if inviter_user.email == request.email {
return Err(UserErrors::InvalidRoleOperationWithMessage(
"User Inviting themselves".to_string(),
)
.into());
}

let role_info = roles::RoleInfo::from_role_id(
&state,
&request.role_id,
&user_from_token.merchant_id,
&user_from_token.org_id,
)
.await
.to_not_found_response(UserErrors::InvalidRoleId)?;

if !role_info.is_invitable() {
return Err(report!(UserErrors::InvalidRoleId))
.attach_printable(format!("role_id = {} is not invitable", request.role_id));
}

let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;

let invitee_user = state
.store
.find_user_by_email(&invitee_email.clone().into_inner())
.await;

if let Ok(invitee_user) = invitee_user {
let invitee_user_from_db = domain::UserFromStorage::from(invitee_user);

let now = common_utils::date_time::now();
state
.store
.insert_user_role(UserRoleNew {
user_id: invitee_user_from_db.get_user_id().to_owned(),
merchant_id: user_from_token.merchant_id.clone(),
role_id: request.role_id,
org_id: user_from_token.org_id,
status: {
if cfg!(feature = "email") {
UserStatus::InvitationSent
} else {
UserStatus::Active
}
},
created_by: user_from_token.user_id.clone(),
last_modified_by: user_from_token.user_id,
created_at: now,
last_modified: now,
})
.await
.map_err(|e| {
if e.current_context().is_db_unique_violation() {
e.change_context(UserErrors::UserExists)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;

let is_email_sent;
#[cfg(feature = "email")]
{
let email_contents = email_types::InviteRegisteredUser {
recipient_email: invitee_email,
user_name: domain::UserName::new(invitee_user_from_db.get_name())?,
settings: state.conf.clone(),
subject: "You have been invited to join Hyperswitch Community!",
merchant_id: user_from_token.merchant_id,
};

is_email_sent = state
.email_client
.compose_and_send_email(
Box::new(email_contents),
state.conf.proxy.https_url.as_ref(),
)
.await
.map(|email_result| logger::info!(?email_result))
.map_err(|email_result| logger::error!(?email_result))
.is_ok();
}
#[cfg(not(feature = "email"))]
{
is_email_sent = false;
}
Ok(ApplicationResponse::Json(user_api::InviteUserResponse {
is_email_sent,
password: None,
}))
} else if invitee_user
.as_ref()
.map_err(|e| e.current_context().is_db_not_found())
.err()
.unwrap_or(false)
{
let new_user = domain::NewUser::try_from((request.clone(), user_from_token.clone()))?;

new_user
.insert_user_in_db(state.store.as_ref())
.await
.change_context(UserErrors::InternalServerError)?;

let invitation_status = if cfg!(feature = "email") {
UserStatus::InvitationSent
} else {
UserStatus::Active
};

let now = common_utils::date_time::now();
state
.store
.insert_user_role(UserRoleNew {
user_id: new_user.get_user_id().to_owned(),
merchant_id: user_from_token.merchant_id.clone(),
role_id: request.role_id.clone(),
org_id: user_from_token.org_id.clone(),
status: invitation_status,
created_by: user_from_token.user_id.clone(),
last_modified_by: user_from_token.user_id,
created_at: now,
last_modified: now,
})
.await
.map_err(|e| {
if e.current_context().is_db_unique_violation() {
e.change_context(UserErrors::UserExists)
} else {
e.change_context(UserErrors::InternalServerError)
}
})?;

let is_email_sent;
#[cfg(feature = "email")]
{
// Doing this to avoid clippy lints
// will add actual usage for this later
let _ = req_state.clone();
let email_contents = email_types::InviteUser {
recipient_email: invitee_email,
user_name: domain::UserName::new(new_user.get_name())?,
settings: state.conf.clone(),
subject: "You have been invited to join Hyperswitch Community!",
merchant_id: user_from_token.merchant_id,
};
let send_email_result = state
.email_client
.compose_and_send_email(
Box::new(email_contents),
state.conf.proxy.https_url.as_ref(),
)
.await;
logger::info!(?send_email_result);
is_email_sent = send_email_result.is_ok();
}
#[cfg(not(feature = "email"))]
{
is_email_sent = false;
let invited_user_token = auth::UserFromToken {
user_id: new_user.get_user_id(),
merchant_id: user_from_token.merchant_id,
org_id: user_from_token.org_id,
role_id: request.role_id,
};

let set_metadata_request = SetMetaDataRequest::IsChangePasswordRequired;
dashboard_metadata::set_metadata(
state.clone(),
invited_user_token,
set_metadata_request,
req_state,
)
.await?;
}

Ok(ApplicationResponse::Json(user_api::InviteUserResponse {
is_email_sent,
password: if cfg!(not(feature = "email")) {
Some(new_user.get_password().get_secret())
} else {
None
},
}))
} else {
Err(report!(UserErrors::InternalServerError))
}
}

pub async fn invite_multiple_user(
state: AppState,
user_from_token: auth::UserFromToken,
Expand Down Expand Up @@ -1317,44 +1089,6 @@ pub async fn list_users_for_merchant_account(
)))
}

#[cfg(feature = "email")]
pub async fn verify_email_without_invite_checks(
state: AppState,
req: user_api::VerifyEmailRequest,
) -> UserResponse<user_api::DashboardEntryResponse> {
let token = req.token.clone().expose();
let email_token = auth::decode_jwt::<email_types::EmailToken>(&token, &state)
.await
.change_context(UserErrors::LinkInvalid)?;
auth::blacklist::check_email_token_in_blacklist(&state, &token).await?;
let user = state
.store
.find_user_by_email(
&email_token
.get_email()
.change_context(UserErrors::InternalServerError)?,
)
.await
.change_context(UserErrors::InternalServerError)?;
let user = state
.store
.update_user_by_user_id(user.user_id.as_str(), storage_user::UserUpdate::VerifyUser)
.await
.change_context(UserErrors::InternalServerError)?;
let user_from_db: domain::UserFromStorage = user.into();
let user_role = user_from_db.get_role_from_db(state.clone()).await?;
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
.await
.map_err(|e| logger::error!(?e));
let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
utils::user_role::set_role_permissions_in_cache_by_user_role(&state, &user_role).await;

let response =
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token.clone())?;

auth::cookies::set_cookie_response(response, token)
}

#[cfg(feature = "email")]
pub async fn verify_email(
state: AppState,
Expand Down
8 changes: 0 additions & 8 deletions crates/router/src/routes/app.rs
Expand Up @@ -1160,9 +1160,6 @@ impl User {
let mut route = web::scope("/user").app_data(web::Data::new(state));

route = route
.service(
web::resource("/signin").route(web::post().to(user_signin_without_invite_checks)),
)
.service(web::resource("/v2/signin").route(web::post().to(user_signin)))
.service(web::resource("/signout").route(web::post().to(signout)))
.service(web::resource("/change_password").route(web::post().to(change_password)))
Expand Down Expand Up @@ -1195,10 +1192,6 @@ impl User {
web::resource("/signup_with_merchant_id")
.route(web::post().to(user_signup_with_merchant_id)),
)
.service(
web::resource("/verify_email")
.route(web::post().to(verify_email_without_invite_checks)),
)
.service(web::resource("/v2/verify_email").route(web::post().to(verify_email)))
.service(
web::resource("/verify_email_request")
Expand All @@ -1222,7 +1215,6 @@ impl User {
.service(
web::resource("/list").route(web::get().to(list_users_for_merchant_account)),
)
.service(web::resource("/invite").route(web::post().to(invite_user)))
.service(
web::resource("/invite_multiple").route(web::post().to(invite_multiple_user)),
)
Expand Down
3 changes: 0 additions & 3 deletions crates/router/src/routes/lock_utils.rs
Expand Up @@ -184,7 +184,6 @@ impl From<Flow> for ApiIdentifier {

Flow::UserConnectAccount
| Flow::UserSignUp
| Flow::UserSignInWithoutInviteChecks
| Flow::UserSignIn
| Flow::Signout
| Flow::ChangePassword
Expand All @@ -201,11 +200,9 @@ impl From<Flow> for ApiIdentifier {
| Flow::ListUsersForMerchantAccount
| Flow::ForgotPassword
| Flow::ResetPassword
| Flow::InviteUser
| Flow::InviteMultipleUser
| Flow::ReInviteUser
| Flow::UserSignUpWithMerchantId
| Flow::VerifyEmailWithoutInviteChecks
| Flow::VerifyEmail
| Flow::AcceptInviteFromEmail
| Flow::VerifyEmailRequest
Expand Down

0 comments on commit b0133f3

Please sign in to comment.