mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 09:07:09 +08:00
feat(user): Add verify_email API (#3076)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -9,7 +9,7 @@ use crate::user::{
|
|||||||
AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
|
AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest,
|
||||||
DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
|
DashboardEntryResponse, ForgotPasswordRequest, GetUsersResponse, InviteUserRequest,
|
||||||
InviteUserResponse, ResetPasswordRequest, SignUpRequest, SignUpWithMerchantIdRequest,
|
InviteUserResponse, ResetPasswordRequest, SignUpRequest, SignUpWithMerchantIdRequest,
|
||||||
SwitchMerchantIdRequest, UserMerchantCreate,
|
SwitchMerchantIdRequest, UserMerchantCreate, VerifyEmailRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl ApiEventMetric for DashboardEntryResponse {
|
impl ApiEventMetric for DashboardEntryResponse {
|
||||||
@ -38,7 +38,8 @@ common_utils::impl_misc_api_event_type!(
|
|||||||
ForgotPasswordRequest,
|
ForgotPasswordRequest,
|
||||||
ResetPasswordRequest,
|
ResetPasswordRequest,
|
||||||
InviteUserRequest,
|
InviteUserRequest,
|
||||||
InviteUserResponse
|
InviteUserResponse,
|
||||||
|
VerifyEmailRequest
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(feature = "dummy_connector")]
|
#[cfg(feature = "dummy_connector")]
|
||||||
|
|||||||
@ -121,3 +121,10 @@ pub struct UserDetails {
|
|||||||
#[serde(with = "common_utils::custom_serde::iso8601")]
|
#[serde(with = "common_utils::custom_serde::iso8601")]
|
||||||
pub last_modified_at: time::PrimitiveDateTime,
|
pub last_modified_at: time::PrimitiveDateTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
pub struct VerifyEmailRequest {
|
||||||
|
pub token: Secret<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type VerifyEmailResponse = DashboardEntryResponse;
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use router_env::logger;
|
|||||||
|
|
||||||
use super::errors::{UserErrors, UserResponse};
|
use super::errors::{UserErrors, UserResponse};
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
use crate::services::email::{types as email_types, types::EmailToken};
|
use crate::services::email::types as email_types;
|
||||||
use crate::{
|
use crate::{
|
||||||
consts,
|
consts,
|
||||||
db::user::UserInterface,
|
db::user::UserInterface,
|
||||||
@ -296,9 +296,10 @@ pub async fn reset_password(
|
|||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::ResetPasswordRequest,
|
request: user_api::ResetPasswordRequest,
|
||||||
) -> UserResponse<()> {
|
) -> UserResponse<()> {
|
||||||
let token = auth::decode_jwt::<EmailToken>(request.token.expose().as_str(), &state)
|
let token =
|
||||||
.await
|
auth::decode_jwt::<email_types::EmailToken>(request.token.expose().as_str(), &state)
|
||||||
.change_context(UserErrors::LinkInvalid)?;
|
.await
|
||||||
|
.change_context(UserErrors::LinkInvalid)?;
|
||||||
|
|
||||||
let password = domain::UserPassword::new(request.password)?;
|
let password = domain::UserPassword::new(request.password)?;
|
||||||
|
|
||||||
@ -630,3 +631,33 @@ 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(
|
||||||
|
state: AppState,
|
||||||
|
req: user_api::VerifyEmailRequest,
|
||||||
|
) -> UserResponse<user_api::VerifyEmailResponse> {
|
||||||
|
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 jwt_token = utils::user::generate_jwt_auth_token(state, &user_from_db, &user_role).await?;
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::Json(
|
||||||
|
utils::user::get_dashboard_entry_response(user_from_db, user_role, jwt_token),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|||||||
@ -863,11 +863,6 @@ impl User {
|
|||||||
route = route
|
route = route
|
||||||
.service(web::resource("/signin").route(web::post().to(user_signin)))
|
.service(web::resource("/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("/data/merchant")
|
|
||||||
.route(web::post().to(set_merchant_scoped_dashboard_metadata)),
|
|
||||||
)
|
|
||||||
.service(web::resource("/data").route(web::get().to(get_multiple_dashboard_metadata)))
|
|
||||||
.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)))
|
||||||
.service(
|
.service(
|
||||||
@ -879,7 +874,12 @@ impl 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("/user/update_role").route(web::post().to(update_user_role)))
|
.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/list").route(web::get().to(list_roles)))
|
||||||
.service(web::resource("/role/{role_id}").route(web::get().to(get_role)));
|
.service(web::resource("/role/{role_id}").route(web::get().to(get_role)))
|
||||||
|
.service(
|
||||||
|
web::resource("/data")
|
||||||
|
.route(web::get().to(get_multiple_dashboard_metadata))
|
||||||
|
.route(web::post().to(set_dashboard_metadata)),
|
||||||
|
);
|
||||||
|
|
||||||
#[cfg(feature = "dummy_connector")]
|
#[cfg(feature = "dummy_connector")]
|
||||||
{
|
{
|
||||||
@ -901,7 +901,8 @@ impl User {
|
|||||||
.service(
|
.service(
|
||||||
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)))
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "email"))]
|
#[cfg(not(feature = "email"))]
|
||||||
{
|
{
|
||||||
|
|||||||
@ -170,7 +170,8 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::ForgotPassword
|
| Flow::ForgotPassword
|
||||||
| Flow::ResetPassword
|
| Flow::ResetPassword
|
||||||
| Flow::InviteUser
|
| Flow::InviteUser
|
||||||
| Flow::UserSignUpWithMerchantId => Self::User,
|
| Flow::UserSignUpWithMerchantId
|
||||||
|
| Flow::VerifyEmail => Self::User,
|
||||||
|
|
||||||
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {
|
Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => {
|
||||||
Self::UserRole
|
Self::UserRole
|
||||||
|
|||||||
@ -115,7 +115,7 @@ pub async fn change_password(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_merchant_scoped_dashboard_metadata(
|
pub async fn set_dashboard_metadata(
|
||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
json_payload: web::Json<user_api::dashboard_metadata::SetMetaDataRequest>,
|
json_payload: web::Json<user_api::dashboard_metadata::SetMetaDataRequest>,
|
||||||
@ -351,3 +351,22 @@ pub async fn invite_user(
|
|||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
pub async fn verify_email(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
http_req: HttpRequest,
|
||||||
|
json_payload: web::Json<user_api::VerifyEmailRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::VerifyEmail;
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow.clone(),
|
||||||
|
state,
|
||||||
|
&http_req,
|
||||||
|
json_payload.into_inner(),
|
||||||
|
|state, _, req_payload| user_core::verify_email(state, req_payload),
|
||||||
|
&auth::NoAuth,
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|||||||
@ -50,7 +50,7 @@ pub mod html {
|
|||||||
#[derive(serde::Serialize, serde::Deserialize)]
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||||||
pub struct EmailToken {
|
pub struct EmailToken {
|
||||||
email: String,
|
email: String,
|
||||||
expiration: u64,
|
exp: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EmailToken {
|
impl EmailToken {
|
||||||
@ -59,10 +59,10 @@ impl EmailToken {
|
|||||||
settings: &configs::settings::Settings,
|
settings: &configs::settings::Settings,
|
||||||
) -> CustomResult<String, UserErrors> {
|
) -> CustomResult<String, UserErrors> {
|
||||||
let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS);
|
let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS);
|
||||||
let expiration = jwt::generate_exp(expiration_duration)?.as_secs();
|
let exp = jwt::generate_exp(expiration_duration)?.as_secs();
|
||||||
let token_payload = Self {
|
let token_payload = Self {
|
||||||
email: email.get_secret().expose(),
|
email: email.get_secret().expose(),
|
||||||
expiration,
|
exp,
|
||||||
};
|
};
|
||||||
jwt::generate_jwt(&token_payload, settings).await
|
jwt::generate_jwt(&token_payload, settings).await
|
||||||
}
|
}
|
||||||
@ -95,7 +95,7 @@ impl EmailData for VerifyEmail {
|
|||||||
.change_context(EmailError::TokenGenerationFailure)?;
|
.change_context(EmailError::TokenGenerationFailure)?;
|
||||||
|
|
||||||
let verify_email_link =
|
let verify_email_link =
|
||||||
get_link_with_token(&self.settings.server.base_url, token, "verify_email");
|
get_link_with_token(&self.settings.email.base_url, token, "verify_email");
|
||||||
|
|
||||||
let body = html::get_html_body(EmailBody::Verify {
|
let body = html::get_html_body(EmailBody::Verify {
|
||||||
link: verify_email_link,
|
link: verify_email_link,
|
||||||
@ -124,7 +124,7 @@ impl EmailData for ResetPassword {
|
|||||||
.change_context(EmailError::TokenGenerationFailure)?;
|
.change_context(EmailError::TokenGenerationFailure)?;
|
||||||
|
|
||||||
let reset_password_link =
|
let reset_password_link =
|
||||||
get_link_with_token(&self.settings.server.base_url, token, "set_password");
|
get_link_with_token(&self.settings.email.base_url, token, "set_password");
|
||||||
|
|
||||||
let body = html::get_html_body(EmailBody::Reset {
|
let body = html::get_html_body(EmailBody::Reset {
|
||||||
link: reset_password_link,
|
link: reset_password_link,
|
||||||
@ -153,7 +153,7 @@ impl EmailData for MagicLink {
|
|||||||
.await
|
.await
|
||||||
.change_context(EmailError::TokenGenerationFailure)?;
|
.change_context(EmailError::TokenGenerationFailure)?;
|
||||||
|
|
||||||
let magic_link_login = get_link_with_token(&self.settings.server.base_url, token, "login");
|
let magic_link_login = get_link_with_token(&self.settings.email.base_url, token, "login");
|
||||||
|
|
||||||
let body = html::get_html_body(EmailBody::MagicLink {
|
let body = html::get_html_body(EmailBody::MagicLink {
|
||||||
link: magic_link_login,
|
link: magic_link_login,
|
||||||
@ -183,7 +183,7 @@ impl EmailData for InviteUser {
|
|||||||
.change_context(EmailError::TokenGenerationFailure)?;
|
.change_context(EmailError::TokenGenerationFailure)?;
|
||||||
|
|
||||||
let invite_user_link =
|
let invite_user_link =
|
||||||
get_link_with_token(&self.settings.server.base_url, token, "set_password");
|
get_link_with_token(&self.settings.email.base_url, token, "set_password");
|
||||||
|
|
||||||
let body = html::get_html_body(EmailBody::MagicLink {
|
let body = html::get_html_body(EmailBody::MagicLink {
|
||||||
link: invite_user_link,
|
link: invite_user_link,
|
||||||
|
|||||||
@ -311,6 +311,8 @@ pub enum Flow {
|
|||||||
GetActionUrl,
|
GetActionUrl,
|
||||||
/// Sync connector onboarding status
|
/// Sync connector onboarding status
|
||||||
SyncOnboardingStatus,
|
SyncOnboardingStatus,
|
||||||
|
/// Verify email Token
|
||||||
|
VerifyEmail,
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user