mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(users): Signin and Verify Email changes for User Invitation changes (#3420)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -12,9 +12,9 @@ use crate::user::{
|
|||||||
},
|
},
|
||||||
AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
|
AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
|
||||||
DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
|
DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
|
||||||
InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignUpRequest,
|
InviteUserResponse, ResetPasswordRequest, SendVerifyEmailRequest, SignInResponse,
|
||||||
SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UpdateUserAccountDetailsRequest,
|
SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
|
||||||
UserMerchantCreate, VerifyEmailRequest,
|
UpdateUserAccountDetailsRequest, UserMerchantCreate, VerifyEmailRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl ApiEventMetric for DashboardEntryResponse {
|
impl ApiEventMetric for DashboardEntryResponse {
|
||||||
@ -56,6 +56,7 @@ common_utils::impl_misc_api_event_type!(
|
|||||||
InviteUserResponse,
|
InviteUserResponse,
|
||||||
VerifyEmailRequest,
|
VerifyEmailRequest,
|
||||||
SendVerifyEmailRequest,
|
SendVerifyEmailRequest,
|
||||||
|
SignInResponse,
|
||||||
UpdateUserAccountDetailsRequest
|
UpdateUserAccountDetailsRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,21 @@ pub struct DashboardEntryResponse {
|
|||||||
|
|
||||||
pub type SignInRequest = SignUpRequest;
|
pub type SignInRequest = SignUpRequest;
|
||||||
|
|
||||||
pub type SignInResponse = DashboardEntryResponse;
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
#[serde(tag = "flow_type", rename_all = "snake_case")]
|
||||||
|
pub enum SignInResponse {
|
||||||
|
MerchantSelect(MerchantSelectResponse),
|
||||||
|
DashboardEntry(DashboardEntryResponse),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Serialize)]
|
||||||
|
pub struct MerchantSelectResponse {
|
||||||
|
pub token: Secret<String>,
|
||||||
|
pub name: Secret<String>,
|
||||||
|
pub email: pii::Email,
|
||||||
|
pub verification_days_left: Option<i64>,
|
||||||
|
pub merchants: Vec<UserMerchantAccount>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
|
#[derive(serde::Deserialize, Debug, Clone, serde::Serialize)]
|
||||||
pub struct ConnectAccountRequest {
|
pub struct ConnectAccountRequest {
|
||||||
@ -138,7 +152,7 @@ pub struct VerifyEmailRequest {
|
|||||||
pub token: Secret<String>,
|
pub token: Secret<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type VerifyEmailResponse = DashboardEntryResponse;
|
pub type VerifyEmailResponse = SignInResponse;
|
||||||
|
|
||||||
#[derive(serde::Deserialize, Debug, serde::Serialize)]
|
#[derive(serde::Deserialize, Debug, serde::Serialize)]
|
||||||
pub struct SendVerifyEmailRequest {
|
pub struct SendVerifyEmailRequest {
|
||||||
@ -149,6 +163,7 @@ pub struct SendVerifyEmailRequest {
|
|||||||
pub struct UserMerchantAccount {
|
pub struct UserMerchantAccount {
|
||||||
pub merchant_id: String,
|
pub merchant_id: String,
|
||||||
pub merchant_name: OptionalEncryptableName,
|
pub merchant_name: OptionalEncryptableName,
|
||||||
|
pub is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "recon")]
|
#[cfg(feature = "recon")]
|
||||||
|
|||||||
@ -58,6 +58,8 @@ pub enum UserErrors {
|
|||||||
InvalidDeleteOperation,
|
InvalidDeleteOperation,
|
||||||
#[error("MaxInvitationsError")]
|
#[error("MaxInvitationsError")]
|
||||||
MaxInvitationsError,
|
MaxInvitationsError,
|
||||||
|
#[error("RoleNotFound")]
|
||||||
|
RoleNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||||
@ -146,6 +148,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
|||||||
Self::MaxInvitationsError => {
|
Self::MaxInvitationsError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 31, self.get_error_message(), None))
|
AER::BadRequest(ApiError::new(sub_code, 31, self.get_error_message(), None))
|
||||||
}
|
}
|
||||||
|
Self::RoleNotFound => {
|
||||||
|
AER::BadRequest(ApiError::new(sub_code, 32, self.get_error_message(), None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,6 +183,7 @@ impl UserErrors {
|
|||||||
Self::ChangePasswordError => "Old and new password cannot be the same",
|
Self::ChangePasswordError => "Old and new password cannot be the same",
|
||||||
Self::InvalidDeleteOperation => "Delete Operation Not Supported",
|
Self::InvalidDeleteOperation => "Delete Operation Not Supported",
|
||||||
Self::MaxInvitationsError => "Maximum invite count per request exceeded",
|
Self::MaxInvitationsError => "Maximum invite count per request exceeded",
|
||||||
|
Self::RoleNotFound => "Role Not Found",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,10 +97,10 @@ pub async fn signup(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn signin(
|
pub async fn signin_without_invite_checks(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::SignInRequest,
|
request: user_api::SignInRequest,
|
||||||
) -> UserResponse<user_api::SignInResponse> {
|
) -> UserResponse<user_api::DashboardEntryResponse> {
|
||||||
let user_from_db: domain::UserFromStorage = state
|
let user_from_db: domain::UserFromStorage = state
|
||||||
.store
|
.store
|
||||||
.find_user_by_email(request.email.clone().expose().expose().as_str())
|
.find_user_by_email(request.email.clone().expose().expose().as_str())
|
||||||
@ -124,6 +124,50 @@ pub async fn signin(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn signin(
|
||||||
|
state: AppState,
|
||||||
|
request: user_api::SignInRequest,
|
||||||
|
) -> UserResponse<user_api::SignInResponse> {
|
||||||
|
let user_from_db: domain::UserFromStorage = state
|
||||||
|
.store
|
||||||
|
.find_user_by_email(request.email.clone().expose().expose().as_str())
|
||||||
|
.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 signin_strategy =
|
||||||
|
if let Some(preferred_merchant_id) = user_from_db.get_preferred_merchant_id() {
|
||||||
|
let preferred_role = user_from_db
|
||||||
|
.get_role_from_db_by_merchant_id(&state, preferred_merchant_id.as_str())
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("User role with preferred_merchant_id not found")?;
|
||||||
|
domain::SignInWithRoleStrategyType::SingleRole(domain::SignInWithSingleRoleStrategy {
|
||||||
|
user: user_from_db,
|
||||||
|
user_role: preferred_role,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let user_roles = user_from_db.get_roles_from_db(&state).await?;
|
||||||
|
domain::SignInWithRoleStrategyType::decide_signin_strategy_by_user_roles(
|
||||||
|
user_from_db,
|
||||||
|
user_roles,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::Json(
|
||||||
|
signin_strategy.get_signin_response(&state).await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
pub async fn connect_account(
|
pub async fn connect_account(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
@ -832,22 +876,22 @@ pub async fn list_merchant_ids_for_user(
|
|||||||
state: AppState,
|
state: AppState,
|
||||||
user: auth::UserFromToken,
|
user: auth::UserFromToken,
|
||||||
) -> UserResponse<Vec<user_api::UserMerchantAccount>> {
|
) -> UserResponse<Vec<user_api::UserMerchantAccount>> {
|
||||||
let merchant_ids = utils::user_role::get_merchant_ids_for_user(&state, &user.user_id).await?;
|
let user_roles =
|
||||||
|
utils::user_role::get_active_user_roles_for_user(&state, &user.user_id).await?;
|
||||||
|
|
||||||
let merchant_accounts = state
|
let merchant_accounts = state
|
||||||
.store
|
.store
|
||||||
.list_multiple_merchant_accounts(merchant_ids)
|
.list_multiple_merchant_accounts(
|
||||||
|
user_roles
|
||||||
|
.iter()
|
||||||
|
.map(|role| role.merchant_id.clone())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
Ok(ApplicationResponse::Json(
|
Ok(ApplicationResponse::Json(
|
||||||
merchant_accounts
|
utils::user::get_multiple_merchant_details_with_status(user_roles, merchant_accounts)?,
|
||||||
.into_iter()
|
|
||||||
.map(|acc| user_api::UserMerchantAccount {
|
|
||||||
merchant_id: acc.merchant_id,
|
|
||||||
merchant_name: acc.merchant_name,
|
|
||||||
})
|
|
||||||
.collect(),
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -868,11 +912,38 @@ pub async fn get_users_for_merchant_account(
|
|||||||
Ok(ApplicationResponse::Json(user_api::GetUsersResponse(users)))
|
Ok(ApplicationResponse::Json(user_api::GetUsersResponse(users)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
pub async fn verify_email_without_invite_checks(
|
||||||
|
state: AppState,
|
||||||
|
req: user_api::VerifyEmailRequest,
|
||||||
|
) -> UserResponse<user_api::DashboardEntryResponse> {
|
||||||
|
let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::LinkInvalid)?;
|
||||||
|
let user = state
|
||||||
|
.store
|
||||||
|
.find_user_by_email(token.get_email())
|
||||||
|
.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 token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::Json(
|
||||||
|
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
pub async fn verify_email(
|
pub async fn verify_email(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
req: user_api::VerifyEmailRequest,
|
req: user_api::VerifyEmailRequest,
|
||||||
) -> UserResponse<user_api::VerifyEmailResponse> {
|
) -> UserResponse<user_api::SignInResponse> {
|
||||||
let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
|
let token = auth::decode_jwt::<email_types::EmailToken>(&req.token.clone().expose(), &state)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::LinkInvalid)?;
|
.change_context(UserErrors::LinkInvalid)?;
|
||||||
@ -890,11 +961,29 @@ pub async fn verify_email(
|
|||||||
.change_context(UserErrors::InternalServerError)?;
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
let user_from_db: domain::UserFromStorage = user.into();
|
let user_from_db: domain::UserFromStorage = user.into();
|
||||||
let user_role = user_from_db.get_role_from_db(state.clone()).await?;
|
|
||||||
let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;
|
let signin_strategy =
|
||||||
|
if let Some(preferred_merchant_id) = user_from_db.get_preferred_merchant_id() {
|
||||||
|
let preferred_role = user_from_db
|
||||||
|
.get_role_from_db_by_merchant_id(&state, preferred_merchant_id.as_str())
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
.attach_printable("User role with preferred_merchant_id not found")?;
|
||||||
|
domain::SignInWithRoleStrategyType::SingleRole(domain::SignInWithSingleRoleStrategy {
|
||||||
|
user: user_from_db,
|
||||||
|
user_role: preferred_role,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
let user_roles = user_from_db.get_roles_from_db(&state).await?;
|
||||||
|
domain::SignInWithRoleStrategyType::decide_signin_strategy_by_user_roles(
|
||||||
|
user_from_db,
|
||||||
|
user_roles,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
};
|
||||||
|
|
||||||
Ok(ApplicationResponse::Json(
|
Ok(ApplicationResponse::Json(
|
||||||
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
|
signin_strategy.get_signin_response(&state).await?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -952,7 +952,10 @@ impl User {
|
|||||||
let mut route = web::scope("/user").app_data(web::Data::new(state));
|
let mut route = web::scope("/user").app_data(web::Data::new(state));
|
||||||
|
|
||||||
route = route
|
route = route
|
||||||
.service(web::resource("/signin").route(web::post().to(user_signin)))
|
.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("/change_password").route(web::post().to(change_password)))
|
.service(web::resource("/change_password").route(web::post().to(change_password)))
|
||||||
.service(web::resource("/internal_signup").route(web::post().to(internal_user_signup)))
|
.service(web::resource("/internal_signup").route(web::post().to(internal_user_signup)))
|
||||||
.service(web::resource("/switch_merchant").route(web::post().to(switch_merchant_id)))
|
.service(web::resource("/switch_merchant").route(web::post().to(switch_merchant_id)))
|
||||||
@ -961,14 +964,7 @@ impl User {
|
|||||||
.route(web::post().to(user_merchant_account_create)),
|
.route(web::post().to(user_merchant_account_create)),
|
||||||
)
|
)
|
||||||
.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("/user/list").route(web::get().to(get_user_details)))
|
|
||||||
.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("/user/update_role").route(web::post().to(update_user_role)))
|
|
||||||
.service(web::resource("/role/list").route(web::get().to(list_roles)))
|
|
||||||
.service(web::resource("/role").route(web::get().to(get_role_from_token)))
|
|
||||||
.service(web::resource("/role/{role_id}").route(web::get().to(get_role)))
|
|
||||||
.service(web::resource("/user/invite").route(web::post().to(invite_user)))
|
|
||||||
.service(web::resource("/user/invite/accept").route(web::post().to(accept_invitation)))
|
|
||||||
.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(
|
.service(
|
||||||
web::resource("/user/invite_multiple").route(web::post().to(invite_multiple_user)),
|
web::resource("/user/invite_multiple").route(web::post().to(invite_multiple_user)),
|
||||||
@ -980,6 +976,23 @@ impl User {
|
|||||||
)
|
)
|
||||||
.service(web::resource("/user/delete").route(web::delete().to(delete_user_role)));
|
.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")]
|
#[cfg(feature = "dummy_connector")]
|
||||||
{
|
{
|
||||||
route = route.service(
|
route = route.service(
|
||||||
@ -1000,7 +1013,11 @@ impl User {
|
|||||||
web::resource("/signup_with_merchant_id")
|
web::resource("/signup_with_merchant_id")
|
||||||
.route(web::post().to(user_signup_with_merchant_id)),
|
.route(web::post().to(user_signup_with_merchant_id)),
|
||||||
)
|
)
|
||||||
.service(web::resource("/verify_email").route(web::post().to(verify_email)))
|
.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(
|
.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)),
|
||||||
|
|||||||
@ -161,6 +161,7 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
|
|
||||||
Flow::UserConnectAccount
|
Flow::UserConnectAccount
|
||||||
| Flow::UserSignUp
|
| Flow::UserSignUp
|
||||||
|
| Flow::UserSignInWithoutInviteChecks
|
||||||
| Flow::UserSignIn
|
| Flow::UserSignIn
|
||||||
| Flow::ChangePassword
|
| Flow::ChangePassword
|
||||||
| Flow::SetDashboardMetadata
|
| Flow::SetDashboardMetadata
|
||||||
@ -179,6 +180,7 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::InviteMultipleUser
|
| Flow::InviteMultipleUser
|
||||||
| Flow::DeleteUser
|
| Flow::DeleteUser
|
||||||
| Flow::UserSignUpWithMerchantId
|
| Flow::UserSignUpWithMerchantId
|
||||||
|
| Flow::VerifyEmailWithoutInviteChecks
|
||||||
| Flow::VerifyEmail
|
| Flow::VerifyEmail
|
||||||
| Flow::VerifyEmailRequest
|
| Flow::VerifyEmailRequest
|
||||||
| Flow::UpdateUserAccountDetails => Self::User,
|
| Flow::UpdateUserAccountDetails => Self::User,
|
||||||
|
|||||||
@ -58,6 +58,25 @@ pub async fn user_signup(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn user_signin_without_invite_checks(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
http_req: HttpRequest,
|
||||||
|
json_payload: web::Json<user_api::SignInRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::UserSignInWithoutInviteChecks;
|
||||||
|
let req_payload = json_payload.into_inner();
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow.clone(),
|
||||||
|
state,
|
||||||
|
&http_req,
|
||||||
|
req_payload.clone(),
|
||||||
|
|state, _, req_body| user_core::signin_without_invite_checks(state, req_body),
|
||||||
|
&auth::NoAuth,
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn user_signin(
|
pub async fn user_signin(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
http_req: HttpRequest,
|
http_req: HttpRequest,
|
||||||
@ -368,6 +387,25 @@ pub async fn invite_multiple_user(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
pub async fn verify_email_without_invite_checks(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
http_req: HttpRequest,
|
||||||
|
json_payload: web::Json<user_api::VerifyEmailRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::VerifyEmailWithoutInviteChecks;
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow.clone(),
|
||||||
|
state,
|
||||||
|
&http_req,
|
||||||
|
json_payload.into_inner(),
|
||||||
|
|state, _, req_payload| user_core::verify_email_without_invite_checks(state, req_payload),
|
||||||
|
&auth::NoAuth,
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
pub async fn verify_email(
|
pub async fn verify_email(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
|
|||||||
@ -29,7 +29,7 @@ pub async fn get_authorization_info(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_roles(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
||||||
let flow = Flow::ListRoles;
|
let flow = Flow::ListRoles;
|
||||||
Box::pin(api::server_wrap(
|
Box::pin(api::server_wrap(
|
||||||
flow,
|
flow,
|
||||||
|
|||||||
@ -26,11 +26,12 @@ use crate::{
|
|||||||
db::StorageInterface,
|
db::StorageInterface,
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
services::{
|
services::{
|
||||||
|
authentication as auth,
|
||||||
authentication::UserFromToken,
|
authentication::UserFromToken,
|
||||||
authorization::{info, predefined_permissions},
|
authorization::{info, predefined_permissions},
|
||||||
},
|
},
|
||||||
types::transformers::ForeignFrom,
|
types::transformers::ForeignFrom,
|
||||||
utils::user::password,
|
utils::{self, user::password},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod dashboard_metadata;
|
pub mod dashboard_metadata;
|
||||||
@ -733,7 +734,15 @@ impl UserFromStorage {
|
|||||||
pub async fn get_role_from_db(&self, state: AppState) -> UserResult<UserRole> {
|
pub async fn get_role_from_db(&self, state: AppState) -> UserResult<UserRole> {
|
||||||
state
|
state
|
||||||
.store
|
.store
|
||||||
.find_user_role_by_user_id(self.get_user_id())
|
.find_user_role_by_user_id(&self.0.user_id)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_roles_from_db(&self, state: &AppState) -> UserResult<Vec<UserRole>> {
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.list_user_roles_by_user_id(&self.0.user_id)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)
|
.change_context(UserErrors::InternalServerError)
|
||||||
}
|
}
|
||||||
@ -760,6 +769,29 @@ impl UserFromStorage {
|
|||||||
let days_left_for_verification = last_date_for_verification - today;
|
let days_left_for_verification = last_date_for_verification - today;
|
||||||
Ok(Some(days_left_for_verification.whole_days()))
|
Ok(Some(days_left_for_verification.whole_days()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_preferred_merchant_id(&self) -> Option<String> {
|
||||||
|
self.0.preferred_merchant_id.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_role_from_db_by_merchant_id(
|
||||||
|
&self,
|
||||||
|
state: &AppState,
|
||||||
|
merchant_id: &str,
|
||||||
|
) -> UserResult<UserRole> {
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.find_user_role_by_user_id_merchant_id(self.get_user_id(), merchant_id)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
if e.current_context().is_db_not_found() {
|
||||||
|
UserErrors::RoleNotFound
|
||||||
|
} else {
|
||||||
|
UserErrors::InternalServerError
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.into_report()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<info::ModuleInfo> for user_role_api::ModuleInfo {
|
impl From<info::ModuleInfo> for user_role_api::ModuleInfo {
|
||||||
@ -828,3 +860,101 @@ impl TryFrom<UserAndRoleJoined> for user_api::UserDetails {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum SignInWithRoleStrategyType {
|
||||||
|
SingleRole(SignInWithSingleRoleStrategy),
|
||||||
|
MultipleRoles(SignInWithMultipleRolesStrategy),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignInWithRoleStrategyType {
|
||||||
|
pub async fn decide_signin_strategy_by_user_roles(
|
||||||
|
user: UserFromStorage,
|
||||||
|
user_roles: Vec<UserRole>,
|
||||||
|
) -> UserResult<Self> {
|
||||||
|
if user_roles.is_empty() {
|
||||||
|
return Err(UserErrors::InternalServerError.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(user_role) = user_roles
|
||||||
|
.iter()
|
||||||
|
.find(|role| role.status == UserStatus::Active)
|
||||||
|
{
|
||||||
|
Ok(Self::SingleRole(SignInWithSingleRoleStrategy {
|
||||||
|
user,
|
||||||
|
user_role: user_role.clone(),
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
Ok(Self::MultipleRoles(SignInWithMultipleRolesStrategy {
|
||||||
|
user,
|
||||||
|
user_roles,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_signin_response(
|
||||||
|
self,
|
||||||
|
state: &AppState,
|
||||||
|
) -> UserResult<user_api::SignInResponse> {
|
||||||
|
match self {
|
||||||
|
Self::SingleRole(strategy) => strategy.get_signin_response(state).await,
|
||||||
|
Self::MultipleRoles(strategy) => strategy.get_signin_response(state).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SignInWithSingleRoleStrategy {
|
||||||
|
pub user: UserFromStorage,
|
||||||
|
pub user_role: UserRole,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignInWithSingleRoleStrategy {
|
||||||
|
async fn get_signin_response(self, state: &AppState) -> UserResult<user_api::SignInResponse> {
|
||||||
|
let token =
|
||||||
|
utils::user::generate_jwt_auth_token(state, &self.user, &self.user_role).await?;
|
||||||
|
let dashboard_entry_response =
|
||||||
|
utils::user::get_dashboard_entry_response(state, self.user, self.user_role, token)?;
|
||||||
|
Ok(user_api::SignInResponse::DashboardEntry(
|
||||||
|
dashboard_entry_response,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct SignInWithMultipleRolesStrategy {
|
||||||
|
pub user: UserFromStorage,
|
||||||
|
pub user_roles: Vec<UserRole>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignInWithMultipleRolesStrategy {
|
||||||
|
async fn get_signin_response(self, state: &AppState) -> UserResult<user_api::SignInResponse> {
|
||||||
|
let merchant_accounts = state
|
||||||
|
.store
|
||||||
|
.list_multiple_merchant_accounts(
|
||||||
|
self.user_roles
|
||||||
|
.iter()
|
||||||
|
.map(|role| role.merchant_id.clone())
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
let merchant_details = utils::user::get_multiple_merchant_details_with_status(
|
||||||
|
self.user_roles,
|
||||||
|
merchant_accounts,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(user_api::SignInResponse::MerchantSelect(
|
||||||
|
user_api::MerchantSelectResponse {
|
||||||
|
name: self.user.get_name(),
|
||||||
|
email: self.user.get_email(),
|
||||||
|
token: auth::UserAuthToken::new_token(
|
||||||
|
self.user.get_user_id().to_string(),
|
||||||
|
&state.conf,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.into(),
|
||||||
|
merchants: merchant_details,
|
||||||
|
verification_days_left: utils::user::get_verification_days_left(state, &self.user)?,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use api_models::user as user_api;
|
use api_models::user as user_api;
|
||||||
use diesel_models::user_role::UserRole;
|
use diesel_models::{enums::UserStatus, user_role::UserRole};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
use masking::Secret;
|
use masking::Secret;
|
||||||
|
|
||||||
@ -118,3 +120,29 @@ pub fn get_verification_days_left(
|
|||||||
#[cfg(not(feature = "email"))]
|
#[cfg(not(feature = "email"))]
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_multiple_merchant_details_with_status(
|
||||||
|
user_roles: Vec<UserRole>,
|
||||||
|
merchant_accounts: Vec<MerchantAccount>,
|
||||||
|
) -> UserResult<Vec<user_api::UserMerchantAccount>> {
|
||||||
|
let roles: HashMap<_, _> = user_roles
|
||||||
|
.into_iter()
|
||||||
|
.map(|user_role| (user_role.merchant_id.clone(), user_role))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
merchant_accounts
|
||||||
|
.into_iter()
|
||||||
|
.map(|merchant| {
|
||||||
|
let role = roles
|
||||||
|
.get(merchant.merchant_id.as_str())
|
||||||
|
.ok_or(UserErrors::InternalServerError.into())
|
||||||
|
.attach_printable("Merchant exists but user role doesn't")?;
|
||||||
|
|
||||||
|
Ok(user_api::UserMerchantAccount {
|
||||||
|
merchant_id: merchant.merchant_id.clone(),
|
||||||
|
merchant_name: merchant.merchant_name.clone(),
|
||||||
|
is_active: role.status == UserStatus::Active,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
use api_models::user_role as user_role_api;
|
use api_models::user_role as user_role_api;
|
||||||
use diesel_models::enums::UserStatus;
|
use diesel_models::{enums::UserStatus, user_role::UserRole};
|
||||||
use error_stack::ResultExt;
|
use error_stack::ResultExt;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -17,19 +17,17 @@ pub fn is_internal_role(role_id: &str) -> bool {
|
|||||||
|| role_id == consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER
|
|| role_id == consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_merchant_ids_for_user(state: &AppState, user_id: &str) -> UserResult<Vec<String>> {
|
pub async fn get_active_user_roles_for_user(
|
||||||
|
state: &AppState,
|
||||||
|
user_id: &str,
|
||||||
|
) -> UserResult<Vec<UserRole>> {
|
||||||
Ok(state
|
Ok(state
|
||||||
.store
|
.store
|
||||||
.list_user_roles_by_user_id(user_id)
|
.list_user_roles_by_user_id(user_id)
|
||||||
.await
|
.await
|
||||||
.change_context(UserErrors::InternalServerError)?
|
.change_context(UserErrors::InternalServerError)?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|ele| {
|
.filter(|ele| ele.status == UserStatus::Active)
|
||||||
if ele.status == UserStatus::Active {
|
|
||||||
return Some(ele.merchant_id);
|
|
||||||
}
|
|
||||||
None
|
|
||||||
})
|
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -267,6 +267,8 @@ pub enum Flow {
|
|||||||
UserSignUp,
|
UserSignUp,
|
||||||
/// User Sign Up
|
/// User Sign Up
|
||||||
UserSignUpWithMerchantId,
|
UserSignUpWithMerchantId,
|
||||||
|
/// User Sign In without invite checks
|
||||||
|
UserSignInWithoutInviteChecks,
|
||||||
/// User Sign In
|
/// User Sign In
|
||||||
UserSignIn,
|
UserSignIn,
|
||||||
/// User connect account
|
/// User connect account
|
||||||
@ -333,6 +335,8 @@ pub enum Flow {
|
|||||||
SyncOnboardingStatus,
|
SyncOnboardingStatus,
|
||||||
/// Reset tracking id
|
/// Reset tracking id
|
||||||
ResetTrackingId,
|
ResetTrackingId,
|
||||||
|
/// Verify email token without invite checks
|
||||||
|
VerifyEmailWithoutInviteChecks,
|
||||||
/// Verify email Token
|
/// Verify email Token
|
||||||
VerifyEmail,
|
VerifyEmail,
|
||||||
/// Send verify email
|
/// Send verify email
|
||||||
|
|||||||
Reference in New Issue
Block a user