Bug: refactor: deprecate V1 signin, verify email and invite APIs



Currently the following endpoints are not being used by the dashboard
`/user/signin`
`/user/verify_email`
`/user/invite`

New APIs are created for the above APIs which are
`/user/v2/signin`
`/user/v2/verify_email`
`/user/invite_multiple`

The old APIs are kept for backwards compatibility, as the movement is completed entirely from the FE side as well, old APIs can be removed.
diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs
index 4ef04981572..9884dbc4f41 100644
--- a/crates/api_models/src/events/user.rs
+++ b/crates/api_models/src/events/user.rs
@@ -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,
 };
 
@@ -54,7 +54,6 @@ common_utils::impl_misc_api_event_type!(
     ForgotPasswordRequest,
     ResetPasswordRequest,
     InviteUserRequest,
-    InviteUserResponse,
     ReInviteUserRequest,
     VerifyEmailRequest,
     SendVerifyEmailRequest,
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index afe55afb276..9b3d7a2e654 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -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,
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 54db6d11e7f..71d18774cb8 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -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,
@@ -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,
@@ -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,
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index ff660dbf7b7..10021b6c285 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -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)))
@@ -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")
@@ -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)),
                 )
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index aec3a5f2c1f..cc76ae0b3b4 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -183,7 +183,6 @@ impl From<Flow> for ApiIdentifier {
 
             Flow::UserConnectAccount
             | Flow::UserSignUp
-            | Flow::UserSignInWithoutInviteChecks
             | Flow::UserSignIn
             | Flow::Signout
             | Flow::ChangePassword
@@ -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
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index eb34c8ae321..bf3ddbc4491 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -58,25 +58,6 @@ pub async fn user_signup(
     .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(
     state: web::Data<AppState>,
     http_req: HttpRequest,
@@ -383,24 +364,6 @@ pub async fn reset_password(
     ))
     .await
 }
-
-pub async fn invite_user(
-    state: web::Data<AppState>,
-    req: HttpRequest,
-    payload: web::Json<user_api::InviteUserRequest>,
-) -> HttpResponse {
-    let flow = Flow::InviteUser;
-    Box::pin(api::server_wrap(
-        flow,
-        state.clone(),
-        &req,
-        payload.into_inner(),
-        |state, user, payload, req_state| user_core::invite_user(state, payload, user, req_state),
-        &auth::JWTAuth(Permission::UsersWrite),
-        api_locking::LockAction::NotApplicable,
-    ))
-    .await
-}
 pub async fn invite_multiple_user(
     state: web::Data<AppState>,
     req: HttpRequest,
@@ -457,27 +420,6 @@ pub async fn accept_invite_from_email(
     .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")]
 pub async fn verify_email(
     state: web::Data<AppState>,
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 0ec6894debe..0cb00c1a854 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -302,8 +302,6 @@ pub enum Flow {
     UserSignUp,
     /// User Sign Up
     UserSignUpWithMerchantId,
-    /// User Sign In without invite checks
-    UserSignInWithoutInviteChecks,
     /// User Sign In
     UserSignIn,
     /// User connect account
@@ -362,8 +360,6 @@ pub enum Flow {
     ForgotPassword,
     /// Reset password using link
     ResetPassword,
-    /// Invite users
-    InviteUser,
     /// Invite multiple users
     InviteMultipleUser,
     /// Reinvite user
@@ -380,8 +376,6 @@ pub enum Flow {
     SyncOnboardingStatus,
     /// Reset tracking id
     ResetTrackingId,
-    /// Verify email token without invite checks
-    VerifyEmailWithoutInviteChecks,
     /// Verify email Token
     VerifyEmail,
     /// Send verify email

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [x] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This PR will remove the following endpoints and corresponding APIs
`/user/signin`
`/user/verify_email`
`/user/invite`

### Additional Changes

- [x] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Closes #4464 

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->

```
curl --location 'http://localhost:8080/user/signin' \
--header 'Content-Type: application/json' \
--data '{
    "email": "user email",
    "password": "password"
}'
```

```
curl --location 'http://localhost:8080/user/verify_email' \
--header 'Content-Type: application/json' \
--data '{
    "token": "email token"
}'
```

```
curl --location 'http://localhost:8080/user/user/invite' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "email": "email",
    "name": "name",
    "role_id": "merchant_view_only"
}'
```

When these curls are hit, BE will respond with 404 Not Found with the following body
```
{
    "error": {
        "type": "invalid_request",
        "message": "Unrecognized request URL",
        "code": "IR_02"
    }
}
```

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
