mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +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:
		| @ -11,7 +11,7 @@ use router_env::logger; | ||||
|  | ||||
| use super::errors::{UserErrors, UserResponse}; | ||||
| #[cfg(feature = "email")] | ||||
| use crate::services::email::{types as email_types, types::EmailToken}; | ||||
| use crate::services::email::types as email_types; | ||||
| use crate::{ | ||||
|     consts, | ||||
|     db::user::UserInterface, | ||||
| @ -296,9 +296,10 @@ pub async fn reset_password( | ||||
|     state: AppState, | ||||
|     request: user_api::ResetPasswordRequest, | ||||
| ) -> UserResponse<()> { | ||||
|     let token = auth::decode_jwt::<EmailToken>(request.token.expose().as_str(), &state) | ||||
|         .await | ||||
|         .change_context(UserErrors::LinkInvalid)?; | ||||
|     let token = | ||||
|         auth::decode_jwt::<email_types::EmailToken>(request.token.expose().as_str(), &state) | ||||
|             .await | ||||
|             .change_context(UserErrors::LinkInvalid)?; | ||||
|  | ||||
|     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))) | ||||
| } | ||||
|  | ||||
| #[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 | ||||
|             .service(web::resource("/signin").route(web::post().to(user_signin))) | ||||
|             .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("/switch_merchant").route(web::post().to(switch_merchant_id))) | ||||
|             .service( | ||||
| @ -879,7 +874,12 @@ impl User { | ||||
|             .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/{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")] | ||||
|         { | ||||
| @ -901,7 +901,8 @@ impl User { | ||||
|                 .service( | ||||
|                     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))) | ||||
|         } | ||||
|         #[cfg(not(feature = "email"))] | ||||
|         { | ||||
|  | ||||
| @ -170,7 +170,8 @@ impl From<Flow> for ApiIdentifier { | ||||
|             | Flow::ForgotPassword | ||||
|             | Flow::ResetPassword | ||||
|             | Flow::InviteUser | ||||
|             | Flow::UserSignUpWithMerchantId => Self::User, | ||||
|             | Flow::UserSignUpWithMerchantId | ||||
|             | Flow::VerifyEmail => Self::User, | ||||
|  | ||||
|             Flow::ListRoles | Flow::GetRole | Flow::UpdateUserRole | Flow::GetAuthorizationInfo => { | ||||
|                 Self::UserRole | ||||
|  | ||||
| @ -115,7 +115,7 @@ pub async fn change_password( | ||||
|     .await | ||||
| } | ||||
|  | ||||
| pub async fn set_merchant_scoped_dashboard_metadata( | ||||
| pub async fn set_dashboard_metadata( | ||||
|     state: web::Data<AppState>, | ||||
|     req: HttpRequest, | ||||
|     json_payload: web::Json<user_api::dashboard_metadata::SetMetaDataRequest>, | ||||
| @ -351,3 +351,22 @@ pub async fn invite_user( | ||||
|     )) | ||||
|     .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)] | ||||
| pub struct EmailToken { | ||||
|     email: String, | ||||
|     expiration: u64, | ||||
|     exp: u64, | ||||
| } | ||||
|  | ||||
| impl EmailToken { | ||||
| @ -59,10 +59,10 @@ impl EmailToken { | ||||
|         settings: &configs::settings::Settings, | ||||
|     ) -> CustomResult<String, UserErrors> { | ||||
|         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 { | ||||
|             email: email.get_secret().expose(), | ||||
|             expiration, | ||||
|             exp, | ||||
|         }; | ||||
|         jwt::generate_jwt(&token_payload, settings).await | ||||
|     } | ||||
| @ -95,7 +95,7 @@ impl EmailData for VerifyEmail { | ||||
|             .change_context(EmailError::TokenGenerationFailure)?; | ||||
|  | ||||
|         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 { | ||||
|             link: verify_email_link, | ||||
| @ -124,7 +124,7 @@ impl EmailData for ResetPassword { | ||||
|             .change_context(EmailError::TokenGenerationFailure)?; | ||||
|  | ||||
|         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 { | ||||
|             link: reset_password_link, | ||||
| @ -153,7 +153,7 @@ impl EmailData for MagicLink { | ||||
|             .await | ||||
|             .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 { | ||||
|             link: magic_link_login, | ||||
| @ -183,7 +183,7 @@ impl EmailData for InviteUser { | ||||
|             .change_context(EmailError::TokenGenerationFailure)?; | ||||
|  | ||||
|         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 { | ||||
|             link: invite_user_link, | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Mani Chandra
					Mani Chandra