Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(user): Deprecate Signin, Verify email and Invite v1 APIs #4465

Merged
merged 4 commits into from Apr 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 @@ -1153,9 +1153,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 @@ -1188,10 +1185,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 @@ -1215,7 +1208,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 @@ -183,7 +183,6 @@ impl From<Flow> for ApiIdentifier {

Flow::UserConnectAccount
| Flow::UserSignUp
| Flow::UserSignInWithoutInviteChecks
| Flow::UserSignIn
| Flow::Signout
| Flow::ChangePassword
Expand All @@ -200,11 +199,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