mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(users): Create Token only support for pre-login user flow APIs (#4558)
This commit is contained in:
@ -14,8 +14,8 @@ use crate::user::{
|
|||||||
CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest,
|
CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest,
|
||||||
GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse,
|
GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse,
|
||||||
InviteUserRequest, ListUsersResponse, ReInviteUserRequest, ResetPasswordRequest,
|
InviteUserRequest, ListUsersResponse, ReInviteUserRequest, ResetPasswordRequest,
|
||||||
SendVerifyEmailRequest, SignInResponse, SignInWithTokenResponse, SignUpRequest,
|
SendVerifyEmailRequest, SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest,
|
||||||
SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, TokenResponse,
|
SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse,
|
||||||
UpdateUserAccountDetailsRequest, UserFromEmailRequest, UserMerchantCreate, VerifyEmailRequest,
|
UpdateUserAccountDetailsRequest, UserFromEmailRequest, UserMerchantCreate, VerifyEmailRequest,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -38,6 +38,12 @@ impl ApiEventMetric for VerifyTokenResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> ApiEventMetric for TokenOrPayloadResponse<T> {
|
||||||
|
fn get_api_event_type(&self) -> Option<ApiEventsType> {
|
||||||
|
Some(ApiEventsType::Miscellaneous)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
common_utils::impl_misc_api_event_type!(
|
common_utils::impl_misc_api_event_type!(
|
||||||
SignUpRequest,
|
SignUpRequest,
|
||||||
SignUpWithMerchantIdRequest,
|
SignUpWithMerchantIdRequest,
|
||||||
@ -62,7 +68,6 @@ common_utils::impl_misc_api_event_type!(
|
|||||||
SignInResponse,
|
SignInResponse,
|
||||||
UpdateUserAccountDetailsRequest,
|
UpdateUserAccountDetailsRequest,
|
||||||
GetUserDetailsResponse,
|
GetUserDetailsResponse,
|
||||||
SignInWithTokenResponse,
|
|
||||||
GetUserRoleDetailsRequest,
|
GetUserRoleDetailsRequest,
|
||||||
GetUserRoleDetailsResponse,
|
GetUserRoleDetailsResponse,
|
||||||
TokenResponse,
|
TokenResponse,
|
||||||
|
|||||||
@ -227,9 +227,9 @@ pub struct TokenResponse {
|
|||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
pub enum SignInWithTokenResponse {
|
pub enum TokenOrPayloadResponse<T> {
|
||||||
Token(TokenResponse),
|
Token(TokenResponse),
|
||||||
SignInResponse(SignInResponse),
|
Payload(T),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
|
|||||||
@ -99,6 +99,7 @@ pub enum UserStatus {
|
|||||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||||
pub struct AcceptInvitationRequest {
|
pub struct AcceptInvitationRequest {
|
||||||
pub merchant_ids: Vec<String>,
|
pub merchant_ids: Vec<String>,
|
||||||
|
// TODO: Remove this once the token only api is being used
|
||||||
pub need_dashboard_entry_response: Option<bool>,
|
pub need_dashboard_entry_response: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -94,7 +94,7 @@ pub async fn get_user_details(
|
|||||||
pub async fn signup(
|
pub async fn signup(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::SignUpRequest,
|
request: user_api::SignUpRequest,
|
||||||
) -> UserResponse<user_api::SignUpResponse> {
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::SignUpResponse>> {
|
||||||
let new_user = domain::NewUser::try_from(request)?;
|
let new_user = domain::NewUser::try_from(request)?;
|
||||||
new_user
|
new_user
|
||||||
.get_new_merchant()
|
.get_new_merchant()
|
||||||
@ -117,13 +117,48 @@ pub async fn signup(
|
|||||||
let response =
|
let response =
|
||||||
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token.clone())?;
|
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token.clone())?;
|
||||||
|
|
||||||
|
auth::cookies::set_cookie_response(user_api::TokenOrPayloadResponse::Payload(response), token)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn signup_token_only_flow(
|
||||||
|
state: AppState,
|
||||||
|
request: user_api::SignUpRequest,
|
||||||
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::SignUpResponse>> {
|
||||||
|
let new_user = domain::NewUser::try_from(request)?;
|
||||||
|
new_user
|
||||||
|
.get_new_merchant()
|
||||||
|
.get_new_organization()
|
||||||
|
.insert_org_in_db(state.clone())
|
||||||
|
.await?;
|
||||||
|
let user_from_db = new_user
|
||||||
|
.insert_user_and_merchant_in_db(state.clone())
|
||||||
|
.await?;
|
||||||
|
let user_role = new_user
|
||||||
|
.insert_user_role_in_db(
|
||||||
|
state.clone(),
|
||||||
|
consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
|
||||||
|
UserStatus::Active,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let next_flow =
|
||||||
|
domain::NextFlow::from_origin(domain::Origin::SignUp, user_from_db.clone(), &state).await?;
|
||||||
|
|
||||||
|
let token = next_flow
|
||||||
|
.get_token_with_user_role(&state, &user_role)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let response = user_api::TokenOrPayloadResponse::Token(user_api::TokenResponse {
|
||||||
|
token: token.clone(),
|
||||||
|
token_type: next_flow.get_flow().into(),
|
||||||
|
});
|
||||||
auth::cookies::set_cookie_response(response, token)
|
auth::cookies::set_cookie_response(response, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn signin(
|
pub async fn signin(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::SignInRequest,
|
request: user_api::SignInRequest,
|
||||||
) -> UserResponse<user_api::SignInWithTokenResponse> {
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::SignInResponse>> {
|
||||||
let user_from_db: domain::UserFromStorage = state
|
let user_from_db: domain::UserFromStorage = state
|
||||||
.store
|
.store
|
||||||
.find_user_by_email(&request.email)
|
.find_user_by_email(&request.email)
|
||||||
@ -161,16 +196,13 @@ pub async fn signin(
|
|||||||
|
|
||||||
let response = signin_strategy.get_signin_response(&state).await?;
|
let response = signin_strategy.get_signin_response(&state).await?;
|
||||||
let token = utils::user::get_token_from_signin_response(&response);
|
let token = utils::user::get_token_from_signin_response(&response);
|
||||||
auth::cookies::set_cookie_response(
|
auth::cookies::set_cookie_response(user_api::TokenOrPayloadResponse::Payload(response), token)
|
||||||
user_api::SignInWithTokenResponse::SignInResponse(response),
|
|
||||||
token,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn signin_token_only_flow(
|
pub async fn signin_token_only_flow(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::SignInRequest,
|
request: user_api::SignInRequest,
|
||||||
) -> UserResponse<user_api::SignInWithTokenResponse> {
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::SignInResponse>> {
|
||||||
let user_from_db: domain::UserFromStorage = state
|
let user_from_db: domain::UserFromStorage = state
|
||||||
.store
|
.store
|
||||||
.find_user_by_email(&request.email)
|
.find_user_by_email(&request.email)
|
||||||
@ -185,7 +217,7 @@ pub async fn signin_token_only_flow(
|
|||||||
|
|
||||||
let token = next_flow.get_token(&state).await?;
|
let token = next_flow.get_token(&state).await?;
|
||||||
|
|
||||||
let response = user_api::SignInWithTokenResponse::Token(user_api::TokenResponse {
|
let response = user_api::TokenOrPayloadResponse::Token(user_api::TokenResponse {
|
||||||
token: token.clone(),
|
token: token.clone(),
|
||||||
token_type: next_flow.get_flow().into(),
|
token_type: next_flow.get_flow().into(),
|
||||||
});
|
});
|
||||||
@ -820,6 +852,73 @@ pub async fn accept_invite_from_email(
|
|||||||
auth::cookies::set_cookie_response(response, token)
|
auth::cookies::set_cookie_response(response, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
pub async fn accept_invite_from_email_token_only_flow(
|
||||||
|
state: AppState,
|
||||||
|
user_token: auth::UserFromSinglePurposeToken,
|
||||||
|
request: user_api::AcceptInviteFromEmailRequest,
|
||||||
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::DashboardEntryResponse>> {
|
||||||
|
let token = request.token.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_from_db: domain::UserFromStorage = state
|
||||||
|
.store
|
||||||
|
.find_user_by_email(
|
||||||
|
&email_token
|
||||||
|
.get_email()
|
||||||
|
.change_context(UserErrors::InternalServerError)?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
if user_from_db.get_user_id() != user_token.user_id {
|
||||||
|
return Err(UserErrors::LinkInvalid.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let merchant_id = email_token
|
||||||
|
.get_merchant_id()
|
||||||
|
.ok_or(UserErrors::LinkInvalid)?;
|
||||||
|
|
||||||
|
let user_role = state
|
||||||
|
.store
|
||||||
|
.update_user_role_by_user_id_merchant_id(
|
||||||
|
user_from_db.get_user_id(),
|
||||||
|
merchant_id,
|
||||||
|
UserRoleUpdate::UpdateStatus {
|
||||||
|
status: UserStatus::Active,
|
||||||
|
modified_by: user_from_db.get_user_id().to_string(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
|
|
||||||
|
let current_flow = domain::CurrentFlow::new(
|
||||||
|
user_token.origin,
|
||||||
|
domain::SPTFlow::AcceptInvitationFromEmail.into(),
|
||||||
|
)?;
|
||||||
|
let next_flow = current_flow.next(user_from_db.clone(), &state).await?;
|
||||||
|
|
||||||
|
let token = next_flow
|
||||||
|
.get_token_with_user_role(&state, &user_role)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let response = user_api::TokenOrPayloadResponse::Token(user_api::TokenResponse {
|
||||||
|
token: token.clone(),
|
||||||
|
token_type: next_flow.get_flow().into(),
|
||||||
|
});
|
||||||
|
auth::cookies::set_cookie_response(response, token)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn create_internal_user(
|
pub async fn create_internal_user(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
request: user_api::CreateInternalUserRequest,
|
request: user_api::CreateInternalUserRequest,
|
||||||
@ -1196,6 +1295,60 @@ pub async fn verify_email(
|
|||||||
auth::cookies::set_cookie_response(response, token)
|
auth::cookies::set_cookie_response(response, token)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
pub async fn verify_email_token_only_flow(
|
||||||
|
state: AppState,
|
||||||
|
user_token: auth::UserFromSinglePurposeToken,
|
||||||
|
req: user_api::VerifyEmailRequest,
|
||||||
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::SignInResponse>> {
|
||||||
|
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_from_email = state
|
||||||
|
.store
|
||||||
|
.find_user_by_email(
|
||||||
|
&email_token
|
||||||
|
.get_email()
|
||||||
|
.change_context(UserErrors::InternalServerError)?,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?;
|
||||||
|
|
||||||
|
if user_from_email.user_id != user_token.user_id {
|
||||||
|
return Err(UserErrors::LinkInvalid.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let user_from_db: domain::UserFromStorage = state
|
||||||
|
.store
|
||||||
|
.update_user_by_user_id(
|
||||||
|
user_from_email.user_id.as_str(),
|
||||||
|
storage_user::UserUpdate::VerifyUser,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let _ = auth::blacklist::insert_email_token_in_blacklist(&state, &token)
|
||||||
|
.await
|
||||||
|
.map_err(|e| logger::error!(?e));
|
||||||
|
|
||||||
|
let current_flow =
|
||||||
|
domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::VerifyEmail.into())?;
|
||||||
|
let next_flow = current_flow.next(user_from_db, &state).await?;
|
||||||
|
let token = next_flow.get_token(&state).await?;
|
||||||
|
|
||||||
|
let response = user_api::TokenOrPayloadResponse::Token(user_api::TokenResponse {
|
||||||
|
token: token.clone(),
|
||||||
|
token_type: next_flow.get_flow().into(),
|
||||||
|
});
|
||||||
|
|
||||||
|
auth::cookies::set_cookie_response(response, token)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
pub async fn send_verification_mail(
|
pub async fn send_verification_mail(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
|
|||||||
@ -172,8 +172,7 @@ pub async fn accept_invitation(
|
|||||||
state: AppState,
|
state: AppState,
|
||||||
user_token: auth::UserFromSinglePurposeToken,
|
user_token: auth::UserFromSinglePurposeToken,
|
||||||
req: user_role_api::AcceptInvitationRequest,
|
req: user_role_api::AcceptInvitationRequest,
|
||||||
_req_state: ReqState,
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::DashboardEntryResponse>> {
|
||||||
) -> UserResponse<user_api::DashboardEntryResponse> {
|
|
||||||
let user_role = futures::future::join_all(req.merchant_ids.iter().map(|merchant_id| async {
|
let user_role = futures::future::join_all(req.merchant_ids.iter().map(|merchant_id| async {
|
||||||
state
|
state
|
||||||
.store
|
.store
|
||||||
@ -215,12 +214,65 @@ pub async fn accept_invitation(
|
|||||||
user_role,
|
user_role,
|
||||||
token.clone(),
|
token.clone(),
|
||||||
)?;
|
)?;
|
||||||
return auth::cookies::set_cookie_response(response, token);
|
return auth::cookies::set_cookie_response(
|
||||||
|
user_api::TokenOrPayloadResponse::Payload(response),
|
||||||
|
token,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ApplicationResponse::StatusOk)
|
Ok(ApplicationResponse::StatusOk)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn accept_invitation_token_only_flow(
|
||||||
|
state: AppState,
|
||||||
|
user_token: auth::UserFromSinglePurposeToken,
|
||||||
|
req: user_role_api::AcceptInvitationRequest,
|
||||||
|
) -> UserResponse<user_api::TokenOrPayloadResponse<user_api::DashboardEntryResponse>> {
|
||||||
|
let user_role = futures::future::join_all(req.merchant_ids.iter().map(|merchant_id| async {
|
||||||
|
state
|
||||||
|
.store
|
||||||
|
.update_user_role_by_user_id_merchant_id(
|
||||||
|
user_token.user_id.as_str(),
|
||||||
|
merchant_id,
|
||||||
|
UserRoleUpdate::UpdateStatus {
|
||||||
|
status: UserStatus::Active,
|
||||||
|
modified_by: user_token.user_id.clone(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
logger::error!("Error while accepting invitation {}", e);
|
||||||
|
})
|
||||||
|
.ok()
|
||||||
|
}))
|
||||||
|
.await
|
||||||
|
.into_iter()
|
||||||
|
.reduce(Option::or)
|
||||||
|
.flatten()
|
||||||
|
.ok_or(UserErrors::MerchantIdNotFound)?;
|
||||||
|
|
||||||
|
let user_from_db: domain::UserFromStorage = state
|
||||||
|
.store
|
||||||
|
.find_user_by_id(user_token.user_id.as_str())
|
||||||
|
.await
|
||||||
|
.change_context(UserErrors::InternalServerError)?
|
||||||
|
.into();
|
||||||
|
|
||||||
|
let current_flow =
|
||||||
|
domain::CurrentFlow::new(user_token.origin, domain::SPTFlow::MerchantSelect.into())?;
|
||||||
|
let next_flow = current_flow.next(user_from_db.clone(), &state).await?;
|
||||||
|
|
||||||
|
let token = next_flow
|
||||||
|
.get_token_with_user_role(&state, &user_role)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let response = user_api::TokenOrPayloadResponse::Token(user_api::TokenResponse {
|
||||||
|
token: token.clone(),
|
||||||
|
token_type: next_flow.get_flow().into(),
|
||||||
|
});
|
||||||
|
auth::cookies::set_cookie_response(response, token)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn delete_user_role(
|
pub async fn delete_user_role(
|
||||||
state: AppState,
|
state: AppState,
|
||||||
user_from_token: auth::UserFromToken,
|
user_from_token: auth::UserFromToken,
|
||||||
|
|||||||
@ -57,15 +57,23 @@ pub async fn user_signup(
|
|||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
http_req: HttpRequest,
|
http_req: HttpRequest,
|
||||||
json_payload: web::Json<user_api::SignUpRequest>,
|
json_payload: web::Json<user_api::SignUpRequest>,
|
||||||
|
query: web::Query<user_api::TokenOnlyQueryParam>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let flow = Flow::UserSignUp;
|
let flow = Flow::UserSignUp;
|
||||||
let req_payload = json_payload.into_inner();
|
let req_payload = json_payload.into_inner();
|
||||||
|
let is_token_only = query.into_inner().token_only;
|
||||||
Box::pin(api::server_wrap(
|
Box::pin(api::server_wrap(
|
||||||
flow.clone(),
|
flow.clone(),
|
||||||
state,
|
state,
|
||||||
&http_req,
|
&http_req,
|
||||||
req_payload.clone(),
|
req_payload.clone(),
|
||||||
|state, _, req_body, _| user_core::signup(state, req_body),
|
|state, _, req_body, _| async move {
|
||||||
|
if let Some(true) = is_token_only {
|
||||||
|
user_core::signup_token_only_flow(state, req_body).await
|
||||||
|
} else {
|
||||||
|
user_core::signup(state, req_body).await
|
||||||
|
}
|
||||||
|
},
|
||||||
&auth::NoAuth,
|
&auth::NoAuth,
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
@ -428,18 +436,37 @@ pub async fn accept_invite_from_email(
|
|||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
payload: web::Json<user_api::AcceptInviteFromEmailRequest>,
|
payload: web::Json<user_api::AcceptInviteFromEmailRequest>,
|
||||||
|
query: web::Query<user_api::TokenOnlyQueryParam>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let flow = Flow::AcceptInviteFromEmail;
|
let flow = Flow::AcceptInviteFromEmail;
|
||||||
Box::pin(api::server_wrap(
|
let is_token_only = query.into_inner().token_only;
|
||||||
flow,
|
if let Some(true) = is_token_only {
|
||||||
state.clone(),
|
Box::pin(api::server_wrap(
|
||||||
&req,
|
flow.clone(),
|
||||||
payload.into_inner(),
|
state,
|
||||||
|state, _, request_payload, _| user_core::accept_invite_from_email(state, request_payload),
|
&req,
|
||||||
&auth::NoAuth,
|
payload.into_inner(),
|
||||||
api_locking::LockAction::NotApplicable,
|
|state, user, req_payload, _| {
|
||||||
))
|
user_core::accept_invite_from_email_token_only_flow(state, user, req_payload)
|
||||||
.await
|
},
|
||||||
|
&auth::SinglePurposeJWTAuth(common_enums::TokenPurpose::AcceptInvitationFromEmail),
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
Box::pin(api::server_wrap(
|
||||||
|
flow,
|
||||||
|
state.clone(),
|
||||||
|
&req,
|
||||||
|
payload.into_inner(),
|
||||||
|
|state, _, request_payload, _| {
|
||||||
|
user_core::accept_invite_from_email(state, request_payload)
|
||||||
|
},
|
||||||
|
&auth::NoAuth,
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
@ -447,18 +474,35 @@ pub async fn verify_email(
|
|||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
http_req: HttpRequest,
|
http_req: HttpRequest,
|
||||||
json_payload: web::Json<user_api::VerifyEmailRequest>,
|
json_payload: web::Json<user_api::VerifyEmailRequest>,
|
||||||
|
query: web::Query<user_api::TokenOnlyQueryParam>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let flow = Flow::VerifyEmail;
|
let flow = Flow::VerifyEmail;
|
||||||
Box::pin(api::server_wrap(
|
let is_token_only = query.into_inner().token_only;
|
||||||
flow.clone(),
|
if let Some(true) = is_token_only {
|
||||||
state,
|
Box::pin(api::server_wrap(
|
||||||
&http_req,
|
flow.clone(),
|
||||||
json_payload.into_inner(),
|
state,
|
||||||
|state, _, req_payload, _| user_core::verify_email(state, req_payload),
|
&http_req,
|
||||||
&auth::NoAuth,
|
json_payload.into_inner(),
|
||||||
api_locking::LockAction::NotApplicable,
|
|state, user, req_payload, _| {
|
||||||
))
|
user_core::verify_email_token_only_flow(state, user, req_payload)
|
||||||
.await
|
},
|
||||||
|
&auth::SinglePurposeJWTAuth(common_enums::TokenPurpose::VerifyEmail),
|
||||||
|
api_locking::LockAction::NotApplicable,
|
||||||
|
))
|
||||||
|
.await
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "email")]
|
#[cfg(feature = "email")]
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
use actix_web::{web, HttpRequest, HttpResponse};
|
use actix_web::{web, HttpRequest, HttpResponse};
|
||||||
use api_models::user_role::{self as user_role_api, role as role_api};
|
use api_models::{
|
||||||
|
user as user_api,
|
||||||
|
user_role::{self as user_role_api, role as role_api},
|
||||||
|
};
|
||||||
use common_enums::TokenPurpose;
|
use common_enums::TokenPurpose;
|
||||||
use router_env::Flow;
|
use router_env::Flow;
|
||||||
|
|
||||||
@ -206,15 +209,23 @@ pub async fn accept_invitation(
|
|||||||
state: web::Data<AppState>,
|
state: web::Data<AppState>,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
json_payload: web::Json<user_role_api::AcceptInvitationRequest>,
|
json_payload: web::Json<user_role_api::AcceptInvitationRequest>,
|
||||||
|
query: web::Query<user_api::TokenOnlyQueryParam>,
|
||||||
) -> HttpResponse {
|
) -> HttpResponse {
|
||||||
let flow = Flow::AcceptInvitation;
|
let flow = Flow::AcceptInvitation;
|
||||||
let payload = json_payload.into_inner();
|
let payload = json_payload.into_inner();
|
||||||
|
let is_token_only = query.into_inner().token_only;
|
||||||
Box::pin(api::server_wrap(
|
Box::pin(api::server_wrap(
|
||||||
flow,
|
flow,
|
||||||
state.clone(),
|
state.clone(),
|
||||||
&req,
|
&req,
|
||||||
payload,
|
payload,
|
||||||
user_role_core::accept_invitation,
|
|state, user, req_body, _| async move {
|
||||||
|
if let Some(true) = is_token_only {
|
||||||
|
user_role_core::accept_invitation_token_only_flow(state, user, req_body).await
|
||||||
|
} else {
|
||||||
|
user_role_core::accept_invitation(state, user, req_body).await
|
||||||
|
}
|
||||||
|
},
|
||||||
&auth::SinglePurposeJWTAuth(TokenPurpose::AcceptInvite),
|
&auth::SinglePurposeJWTAuth(TokenPurpose::AcceptInvite),
|
||||||
api_locking::LockAction::NotApplicable,
|
api_locking::LockAction::NotApplicable,
|
||||||
))
|
))
|
||||||
|
|||||||
@ -7,6 +7,7 @@ use crate::{
|
|||||||
core::errors::{StorageErrorExt, UserErrors, UserResult},
|
core::errors::{StorageErrorExt, UserErrors, UserResult},
|
||||||
routes::AppState,
|
routes::AppState,
|
||||||
services::authentication as auth,
|
services::authentication as auth,
|
||||||
|
utils,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Eq, PartialEq, Clone, Copy)]
|
#[derive(Eq, PartialEq, Clone, Copy)]
|
||||||
@ -150,8 +151,9 @@ const VERIFY_EMAIL_FLOW: [UserFlow; 5] = [
|
|||||||
UserFlow::JWTFlow(JWTFlow::UserInfo),
|
UserFlow::JWTFlow(JWTFlow::UserInfo),
|
||||||
];
|
];
|
||||||
|
|
||||||
const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 4] = [
|
const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 5] = [
|
||||||
UserFlow::SPTFlow(SPTFlow::TOTP),
|
UserFlow::SPTFlow(SPTFlow::TOTP),
|
||||||
|
UserFlow::SPTFlow(SPTFlow::VerifyEmail),
|
||||||
UserFlow::SPTFlow(SPTFlow::AcceptInvitationFromEmail),
|
UserFlow::SPTFlow(SPTFlow::AcceptInvitationFromEmail),
|
||||||
UserFlow::SPTFlow(SPTFlow::ForceSetPassword),
|
UserFlow::SPTFlow(SPTFlow::ForceSetPassword),
|
||||||
UserFlow::JWTFlow(JWTFlow::UserInfo),
|
UserFlow::JWTFlow(JWTFlow::UserInfo),
|
||||||
@ -234,16 +236,38 @@ impl NextFlow {
|
|||||||
{
|
{
|
||||||
self.user.get_verification_days_left(state)?;
|
self.user.get_verification_days_left(state)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_role = self
|
let user_role = self
|
||||||
.user
|
.user
|
||||||
.get_preferred_or_active_user_role_from_db(state)
|
.get_preferred_or_active_user_role_from_db(state)
|
||||||
.await
|
.await
|
||||||
.to_not_found_response(UserErrors::InternalServerError)?;
|
.to_not_found_response(UserErrors::InternalServerError)?;
|
||||||
|
utils::user_role::set_role_permissions_in_cache_by_user_role(state, &user_role)
|
||||||
|
.await;
|
||||||
|
|
||||||
jwt_flow.generate_jwt(state, self, &user_role).await
|
jwt_flow.generate_jwt(state, self, &user_role).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_token_with_user_role(
|
||||||
|
&self,
|
||||||
|
state: &AppState,
|
||||||
|
user_role: &UserRole,
|
||||||
|
) -> UserResult<Secret<String>> {
|
||||||
|
match self.next_flow {
|
||||||
|
UserFlow::SPTFlow(spt_flow) => spt_flow.generate_spt(state, self).await,
|
||||||
|
UserFlow::JWTFlow(jwt_flow) => {
|
||||||
|
#[cfg(feature = "email")]
|
||||||
|
{
|
||||||
|
self.user.get_verification_days_left(state)?;
|
||||||
|
}
|
||||||
|
utils::user_role::set_role_permissions_in_cache_by_user_role(state, user_role)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
jwt_flow.generate_jwt(state, self, user_role).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<UserFlow> for TokenPurpose {
|
impl From<UserFlow> for TokenPurpose {
|
||||||
@ -274,3 +298,15 @@ impl From<JWTFlow> for TokenPurpose {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SPTFlow> for UserFlow {
|
||||||
|
fn from(value: SPTFlow) -> Self {
|
||||||
|
Self::SPTFlow(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JWTFlow> for UserFlow {
|
||||||
|
fn from(value: JWTFlow) -> Self {
|
||||||
|
Self::JWTFlow(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user