mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(user): implement change password for user (#2959)
This commit is contained in:
@ -1,6 +1,6 @@
|
|||||||
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||||
|
|
||||||
use crate::user::{ConnectAccountRequest, ConnectAccountResponse};
|
use crate::user::{ChangePasswordRequest, ConnectAccountRequest, ConnectAccountResponse};
|
||||||
|
|
||||||
impl ApiEventMetric for ConnectAccountResponse {
|
impl ApiEventMetric for ConnectAccountResponse {
|
||||||
fn get_api_event_type(&self) -> Option<ApiEventsType> {
|
fn get_api_event_type(&self) -> Option<ApiEventsType> {
|
||||||
@ -12,3 +12,5 @@ impl ApiEventMetric for ConnectAccountResponse {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ApiEventMetric for ConnectAccountRequest {}
|
impl ApiEventMetric for ConnectAccountRequest {}
|
||||||
|
|
||||||
|
common_utils::impl_misc_api_event_type!(ChangePasswordRequest);
|
||||||
|
|||||||
@ -19,3 +19,9 @@ pub struct ConnectAccountResponse {
|
|||||||
#[serde(skip_serializing)]
|
#[serde(skip_serializing)]
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Deserialize, Debug, serde::Serialize)]
|
||||||
|
pub struct ChangePasswordRequest {
|
||||||
|
pub new_password: Secret<String>,
|
||||||
|
pub old_password: Secret<String>,
|
||||||
|
}
|
||||||
|
|||||||
@ -13,6 +13,8 @@ pub enum UserErrors {
|
|||||||
InvalidCredentials,
|
InvalidCredentials,
|
||||||
#[error("UserExists")]
|
#[error("UserExists")]
|
||||||
UserExists,
|
UserExists,
|
||||||
|
#[error("InvalidOldPassword")]
|
||||||
|
InvalidOldPassword,
|
||||||
#[error("EmailParsingError")]
|
#[error("EmailParsingError")]
|
||||||
EmailParsingError,
|
EmailParsingError,
|
||||||
#[error("NameParsingError")]
|
#[error("NameParsingError")]
|
||||||
@ -27,6 +29,8 @@ pub enum UserErrors {
|
|||||||
InvalidEmailError,
|
InvalidEmailError,
|
||||||
#[error("DuplicateOrganizationId")]
|
#[error("DuplicateOrganizationId")]
|
||||||
DuplicateOrganizationId,
|
DuplicateOrganizationId,
|
||||||
|
#[error("MerchantIdNotFound")]
|
||||||
|
MerchantIdNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||||
@ -49,6 +53,12 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
|||||||
"An account already exists with this email",
|
"An account already exists with this email",
|
||||||
None,
|
None,
|
||||||
)),
|
)),
|
||||||
|
Self::InvalidOldPassword => AER::BadRequest(ApiError::new(
|
||||||
|
sub_code,
|
||||||
|
6,
|
||||||
|
"Old password incorrect. Please enter the correct password",
|
||||||
|
None,
|
||||||
|
)),
|
||||||
Self::EmailParsingError => {
|
Self::EmailParsingError => {
|
||||||
AER::BadRequest(ApiError::new(sub_code, 7, "Invalid Email", None))
|
AER::BadRequest(ApiError::new(sub_code, 7, "Invalid Email", None))
|
||||||
}
|
}
|
||||||
@ -73,6 +83,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
|||||||
"An Organization with the id already exists",
|
"An Organization with the id already exists",
|
||||||
None,
|
None,
|
||||||
)),
|
)),
|
||||||
|
Self::MerchantIdNotFound => {
|
||||||
|
AER::BadRequest(ApiError::new(sub_code, 18, "Invalid Merchant ID", None))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,17 @@
|
|||||||
use api_models::user as api;
|
use api_models::user as api;
|
||||||
use diesel_models::enums::UserStatus;
|
use diesel_models::enums::UserStatus;
|
||||||
use error_stack::IntoReport;
|
use error_stack::{IntoReport, ResultExt};
|
||||||
use masking::{ExposeInterface, Secret};
|
use masking::{ExposeInterface, Secret};
|
||||||
use router_env::env;
|
use router_env::env;
|
||||||
|
|
||||||
use super::errors::{UserErrors, UserResponse};
|
use super::errors::{UserErrors, UserResponse};
|
||||||
use crate::{consts, routes::AppState, services::ApplicationResponse, types::domain};
|
use crate::{
|
||||||
|
consts,
|
||||||
|
db::user::UserInterface,
|
||||||
|
routes::AppState,
|
||||||
|
services::{authentication::UserFromToken, ApplicationResponse},
|
||||||
|
types::domain,
|
||||||
|
};
|
||||||
|
|
||||||
pub async fn connect_account(
|
pub async fn connect_account(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
@ -77,3 +83,35 @@ pub async fn connect_account(
|
|||||||
Err(UserErrors::InternalServerError.into())
|
Err(UserErrors::InternalServerError.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn change_password(
|
||||||
|
state: AppState,
|
||||||
|
request: api::ChangePasswordRequest,
|
||||||
|
user_from_token: UserFromToken,
|
||||||
|
) -> UserResponse<()> {
|
||||||
|
let user: domain::UserFromStorage =
|
||||||
|
UserInterface::find_user_by_id(&*state.store, &user_from_token.user_id)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
user.compare_password(request.old_password)
|
||||||
|
.change_context(UserErrors::InvalidOldPassword)?;
|
||||||
|
|
||||||
|
let new_password_hash =
|
||||||
|
crate::utils::user::password::generate_password_hash(request.new_password)?;
|
||||||
|
|
||||||
|
let _ = UserInterface::update_user_by_user_id(
|
||||||
|
&*state.store,
|
||||||
|
user.get_user_id(),
|
||||||
|
diesel_models::user::UserUpdate::AccountUpdate {
|
||||||
|
name: None,
|
||||||
|
password: Some(new_password_hash),
|
||||||
|
is_verified: None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
Ok(ApplicationResponse::StatusOk)
|
||||||
|
}
|
||||||
|
|||||||
@ -759,6 +759,7 @@ impl User {
|
|||||||
.service(web::resource("/signup").route(web::post().to(user_connect_account)))
|
.service(web::resource("/signup").route(web::post().to(user_connect_account)))
|
||||||
.service(web::resource("/v2/signin").route(web::post().to(user_connect_account)))
|
.service(web::resource("/v2/signin").route(web::post().to(user_connect_account)))
|
||||||
.service(web::resource("/v2/signup").route(web::post().to(user_connect_account)))
|
.service(web::resource("/v2/signup").route(web::post().to(user_connect_account)))
|
||||||
|
.service(web::resource("/change_password").route(web::post().to(change_password)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -144,7 +144,7 @@ impl From<Flow> for ApiIdentifier {
|
|||||||
| Flow::GsmRuleUpdate
|
| Flow::GsmRuleUpdate
|
||||||
| Flow::GsmRuleDelete => Self::Gsm,
|
| Flow::GsmRuleDelete => Self::Gsm,
|
||||||
|
|
||||||
Flow::UserConnectAccount => Self::User,
|
Flow::UserConnectAccount | Flow::ChangePassword => Self::User,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,3 +29,21 @@ pub async fn user_connect_account(
|
|||||||
))
|
))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn change_password(
|
||||||
|
state: web::Data<AppState>,
|
||||||
|
http_req: HttpRequest,
|
||||||
|
json_payload: web::Json<user_api::ChangePasswordRequest>,
|
||||||
|
) -> HttpResponse {
|
||||||
|
let flow = Flow::ChangePassword;
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state.clone(),
|
||||||
|
&http_req,
|
||||||
|
json_payload.into_inner(),
|
||||||
|
|state, user, req| user::change_password(state, req, user),
|
||||||
|
&auth::DashboardNoPermissionAuth,
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|||||||
@ -14,6 +14,8 @@ use super::authorization::{self, permissions::Permission};
|
|||||||
use super::jwt;
|
use super::jwt;
|
||||||
#[cfg(feature = "olap")]
|
#[cfg(feature = "olap")]
|
||||||
use crate::consts;
|
use crate::consts;
|
||||||
|
#[cfg(feature = "olap")]
|
||||||
|
use crate::core::errors::UserResult;
|
||||||
use crate::{
|
use crate::{
|
||||||
configs::settings,
|
configs::settings,
|
||||||
core::{
|
core::{
|
||||||
@ -97,7 +99,7 @@ impl AuthToken {
|
|||||||
role_id: String,
|
role_id: String,
|
||||||
settings: &settings::Settings,
|
settings: &settings::Settings,
|
||||||
org_id: String,
|
org_id: String,
|
||||||
) -> errors::UserResult<String> {
|
) -> UserResult<String> {
|
||||||
let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS);
|
let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS);
|
||||||
let exp = jwt::generate_exp(exp_duration)?.as_secs();
|
let exp = jwt::generate_exp(exp_duration)?.as_secs();
|
||||||
let token_payload = Self {
|
let token_payload = Self {
|
||||||
@ -111,6 +113,14 @@ impl AuthToken {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct UserFromToken {
|
||||||
|
pub user_id: String,
|
||||||
|
pub merchant_id: String,
|
||||||
|
pub role_id: String,
|
||||||
|
pub org_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
pub trait AuthInfo {
|
pub trait AuthInfo {
|
||||||
fn get_merchant_id(&self) -> Option<&str>;
|
fn get_merchant_id(&self) -> Option<&str>;
|
||||||
}
|
}
|
||||||
@ -421,6 +431,34 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "olap")]
|
||||||
|
#[async_trait]
|
||||||
|
impl<A> AuthenticateAndFetch<UserFromToken, A> for JWTAuth
|
||||||
|
where
|
||||||
|
A: AppStateInfo + Sync,
|
||||||
|
{
|
||||||
|
async fn authenticate_and_fetch(
|
||||||
|
&self,
|
||||||
|
request_headers: &HeaderMap,
|
||||||
|
state: &A,
|
||||||
|
) -> RouterResult<(UserFromToken, AuthenticationType)> {
|
||||||
|
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
UserFromToken {
|
||||||
|
user_id: payload.user_id.clone(),
|
||||||
|
merchant_id: payload.merchant_id.clone(),
|
||||||
|
org_id: payload.org_id,
|
||||||
|
role_id: payload.role_id,
|
||||||
|
},
|
||||||
|
AuthenticationType::MerchantJWT {
|
||||||
|
merchant_id: payload.merchant_id,
|
||||||
|
user_id: Some(payload.user_id),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct JWTAuthMerchantFromRoute {
|
pub struct JWTAuthMerchantFromRoute {
|
||||||
pub merchant_id: String,
|
pub merchant_id: String,
|
||||||
pub required_permission: Permission,
|
pub required_permission: Permission,
|
||||||
@ -519,6 +557,53 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct DashboardNoPermissionAuth;
|
||||||
|
|
||||||
|
#[cfg(feature = "olap")]
|
||||||
|
#[async_trait]
|
||||||
|
impl<A> AuthenticateAndFetch<UserFromToken, A> for DashboardNoPermissionAuth
|
||||||
|
where
|
||||||
|
A: AppStateInfo + Sync,
|
||||||
|
{
|
||||||
|
async fn authenticate_and_fetch(
|
||||||
|
&self,
|
||||||
|
request_headers: &HeaderMap,
|
||||||
|
state: &A,
|
||||||
|
) -> RouterResult<(UserFromToken, AuthenticationType)> {
|
||||||
|
let payload = parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
UserFromToken {
|
||||||
|
user_id: payload.user_id.clone(),
|
||||||
|
merchant_id: payload.merchant_id.clone(),
|
||||||
|
org_id: payload.org_id,
|
||||||
|
role_id: payload.role_id,
|
||||||
|
},
|
||||||
|
AuthenticationType::MerchantJWT {
|
||||||
|
merchant_id: payload.merchant_id,
|
||||||
|
user_id: Some(payload.user_id),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "olap")]
|
||||||
|
#[async_trait]
|
||||||
|
impl<A> AuthenticateAndFetch<(), A> for DashboardNoPermissionAuth
|
||||||
|
where
|
||||||
|
A: AppStateInfo + Sync,
|
||||||
|
{
|
||||||
|
async fn authenticate_and_fetch(
|
||||||
|
&self,
|
||||||
|
request_headers: &HeaderMap,
|
||||||
|
state: &A,
|
||||||
|
) -> RouterResult<((), AuthenticationType)> {
|
||||||
|
parse_jwt_payload::<A, AuthToken>(request_headers, state).await?;
|
||||||
|
|
||||||
|
Ok(((), AuthenticationType::NoAuth))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ClientSecretFetch {
|
pub trait ClientSecretFetch {
|
||||||
fn get_client_secret(&self) -> Option<&String>;
|
fn get_client_secret(&self) -> Option<&String>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -255,6 +255,8 @@ pub enum Flow {
|
|||||||
DecisionManagerDeleteConfig,
|
DecisionManagerDeleteConfig,
|
||||||
/// Retrieve Decision Manager Config
|
/// Retrieve Decision Manager Config
|
||||||
DecisionManagerRetrieveConfig,
|
DecisionManagerRetrieveConfig,
|
||||||
|
/// Change password flow
|
||||||
|
ChangePassword,
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
|
|||||||
Reference in New Issue
Block a user