mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-27 11:24:45 +08:00
feat(users): setup user authentication methods schema and apis (#4999)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Mani Chandra Dulam <mani.dchandra@juspay.in>
This commit is contained in:
@ -644,4 +644,7 @@ enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"} # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table
|
||||
|
||||
@ -259,4 +259,7 @@ enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table
|
||||
|
||||
@ -654,3 +654,6 @@ global_tenant = { schema = "public", redis_key_prefix = "" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
|
||||
|
||||
@ -507,4 +507,7 @@ enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
public = { name = "hyperswitch", base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
|
||||
|
||||
@ -11,14 +11,15 @@ use crate::user::{
|
||||
GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest,
|
||||
},
|
||||
AcceptInviteFromEmailRequest, AuthorizeResponse, BeginTotpResponse, ChangePasswordRequest,
|
||||
ConnectAccountRequest, CreateInternalUserRequest, DashboardEntryResponse,
|
||||
ForgotPasswordRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest,
|
||||
GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse, ReInviteUserRequest,
|
||||
RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest,
|
||||
SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest,
|
||||
TokenOrPayloadResponse, TokenResponse, TwoFactorAuthStatusResponse,
|
||||
UpdateUserAccountDetailsRequest, UserFromEmailRequest, UserMerchantCreate, VerifyEmailRequest,
|
||||
VerifyRecoveryCodeRequest, VerifyTotpRequest,
|
||||
ConnectAccountRequest, CreateInternalUserRequest, CreateUserAuthenticationMethodRequest,
|
||||
DashboardEntryResponse, ForgotPasswordRequest, GetUserAuthenticationMethodsRequest,
|
||||
GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse,
|
||||
InviteUserRequest, ListUsersResponse, ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest,
|
||||
RotatePasswordRequest, SendVerifyEmailRequest, SignInResponse, SignUpRequest,
|
||||
SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse,
|
||||
TwoFactorAuthStatusResponse, UpdateUserAccountDetailsRequest,
|
||||
UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate,
|
||||
VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
|
||||
};
|
||||
|
||||
impl ApiEventMetric for DashboardEntryResponse {
|
||||
@ -77,7 +78,10 @@ common_utils::impl_misc_api_event_type!(
|
||||
BeginTotpResponse,
|
||||
VerifyRecoveryCodeRequest,
|
||||
VerifyTotpRequest,
|
||||
RecoveryCodes
|
||||
RecoveryCodes,
|
||||
GetUserAuthenticationMethodsRequest,
|
||||
CreateUserAuthenticationMethodRequest,
|
||||
UpdateUserAuthenticationMethodRequest
|
||||
);
|
||||
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
|
||||
@ -280,3 +280,78 @@ pub struct VerifyRecoveryCodeRequest {
|
||||
pub struct RecoveryCodes {
|
||||
pub recovery_codes: Vec<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
#[serde(tag = "auth_type")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AuthConfig {
|
||||
OpenIdConnect {
|
||||
private_config: OpenIdConnectPrivateConfig,
|
||||
public_config: OpenIdConnectPublicConfig,
|
||||
},
|
||||
MagicLink,
|
||||
Password,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct OpenIdConnectPrivateConfig {
|
||||
pub base_url: String,
|
||||
pub client_id: Secret<String>,
|
||||
pub client_secret: Secret<String>,
|
||||
pub private_key: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
pub struct OpenIdConnectPublicConfig {
|
||||
pub name: OpenIdProvider,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum OpenIdProvider {
|
||||
Okta,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct OpenIdConnect {
|
||||
pub name: OpenIdProvider,
|
||||
pub base_url: String,
|
||||
pub client_id: String,
|
||||
pub client_secret: Secret<String>,
|
||||
pub private_key: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CreateUserAuthenticationMethodRequest {
|
||||
pub owner_id: String,
|
||||
pub owner_type: common_enums::Owner,
|
||||
pub auth_method: AuthConfig,
|
||||
pub allow_signup: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct UpdateUserAuthenticationMethodRequest {
|
||||
pub id: String,
|
||||
// TODO: When adding more fields make config and new fields option
|
||||
pub auth_method: AuthConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetUserAuthenticationMethodsRequest {
|
||||
pub auth_id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct UserAuthenticationMethodResponse {
|
||||
pub id: String,
|
||||
pub auth_id: String,
|
||||
pub auth_method: AuthMethodDetails,
|
||||
pub allow_signup: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct AuthMethodDetails {
|
||||
#[serde(rename = "type")]
|
||||
pub auth_type: common_enums::UserAuthType,
|
||||
pub name: Option<OpenIdProvider>,
|
||||
}
|
||||
|
||||
@ -2768,3 +2768,45 @@ pub enum TokenPurpose {
|
||||
AcceptInvite,
|
||||
UserInfo,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
)]
|
||||
#[router_derive::diesel_enum(storage_type = "text")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum UserAuthType {
|
||||
OpenIdConnect,
|
||||
MagicLink,
|
||||
#[default]
|
||||
Password,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Eq,
|
||||
PartialEq,
|
||||
serde::Deserialize,
|
||||
serde::Serialize,
|
||||
strum::Display,
|
||||
strum::EnumString,
|
||||
)]
|
||||
#[router_derive::diesel_enum(storage_type = "text")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Owner {
|
||||
Organization,
|
||||
Tenant,
|
||||
Internal,
|
||||
}
|
||||
|
||||
@ -44,6 +44,7 @@ pub mod routing_algorithm;
|
||||
#[allow(unused_qualifications)]
|
||||
pub mod schema;
|
||||
pub mod user;
|
||||
pub mod user_authentication_method;
|
||||
pub mod user_key_store;
|
||||
pub mod user_role;
|
||||
|
||||
|
||||
@ -36,5 +36,6 @@ pub mod reverse_lookup;
|
||||
pub mod role;
|
||||
pub mod routing_algorithm;
|
||||
pub mod user;
|
||||
pub mod user_authentication_method;
|
||||
pub mod user_key_store;
|
||||
pub mod user_role;
|
||||
|
||||
60
crates/diesel_models/src/query/user_authentication_method.rs
Normal file
60
crates/diesel_models/src/query/user_authentication_method.rs
Normal file
@ -0,0 +1,60 @@
|
||||
use diesel::{associations::HasTable, ExpressionMethods};
|
||||
|
||||
use crate::{
|
||||
query::generics, schema::user_authentication_methods::dsl, user_authentication_method::*,
|
||||
PgPooledConn, StorageResult,
|
||||
};
|
||||
|
||||
impl UserAuthenticationMethodNew {
|
||||
pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<UserAuthenticationMethod> {
|
||||
generics::generic_insert(conn, self).await
|
||||
}
|
||||
}
|
||||
|
||||
impl UserAuthenticationMethod {
|
||||
pub async fn list_user_authentication_methods_for_auth_id(
|
||||
conn: &PgPooledConn,
|
||||
auth_id: &str,
|
||||
) -> StorageResult<Vec<Self>> {
|
||||
generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
|
||||
conn,
|
||||
dsl::auth_id.eq(auth_id.to_owned()),
|
||||
None,
|
||||
None,
|
||||
Some(dsl::last_modified_at.asc()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_user_authentication_methods_for_owner_id(
|
||||
conn: &PgPooledConn,
|
||||
owner_id: &str,
|
||||
) -> StorageResult<Vec<Self>> {
|
||||
generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
|
||||
conn,
|
||||
dsl::owner_id.eq(owner_id.to_owned()),
|
||||
None,
|
||||
None,
|
||||
Some(dsl::last_modified_at.asc()),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_user_authentication_method(
|
||||
conn: &PgPooledConn,
|
||||
id: &str,
|
||||
user_authentication_method_update: UserAuthenticationMethodUpdate,
|
||||
) -> StorageResult<Self> {
|
||||
generics::generic_update_with_unique_predicate_get_result::<
|
||||
<Self as HasTable>::Table,
|
||||
_,
|
||||
_,
|
||||
_,
|
||||
>(
|
||||
conn,
|
||||
dsl::id.eq(id.to_owned()),
|
||||
OrgAuthenticationMethodUpdateInternal::from(user_authentication_method_update),
|
||||
)
|
||||
.await
|
||||
}
|
||||
}
|
||||
@ -1172,6 +1172,29 @@ diesel::table! {
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
|
||||
user_authentication_methods (id) {
|
||||
#[max_length = 64]
|
||||
id -> Varchar,
|
||||
#[max_length = 64]
|
||||
auth_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
owner_id -> Varchar,
|
||||
#[max_length = 64]
|
||||
owner_type -> Varchar,
|
||||
#[max_length = 64]
|
||||
auth_type -> Varchar,
|
||||
private_config -> Nullable<Bytea>,
|
||||
public_config -> Nullable<Jsonb>,
|
||||
allow_signup -> Bool,
|
||||
created_at -> Timestamp,
|
||||
last_modified_at -> Timestamp,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
use diesel::sql_types::*;
|
||||
use crate::enums::diesel_exports::*;
|
||||
@ -1270,6 +1293,7 @@ diesel::allow_tables_to_appear_in_same_query!(
|
||||
reverse_lookup,
|
||||
roles,
|
||||
routing_algorithm,
|
||||
user_authentication_methods,
|
||||
user_key_store,
|
||||
user_roles,
|
||||
users,
|
||||
|
||||
65
crates/diesel_models/src/user_authentication_method.rs
Normal file
65
crates/diesel_models/src/user_authentication_method.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use diesel::{AsChangeset, Identifiable, Insertable, Queryable};
|
||||
use time::PrimitiveDateTime;
|
||||
|
||||
use crate::{encryption::Encryption, enums, schema::user_authentication_methods};
|
||||
|
||||
#[derive(Clone, Debug, Identifiable, Queryable)]
|
||||
#[diesel(table_name = user_authentication_methods)]
|
||||
pub struct UserAuthenticationMethod {
|
||||
pub id: String,
|
||||
pub auth_id: String,
|
||||
pub owner_id: String,
|
||||
pub owner_type: enums::Owner,
|
||||
pub auth_type: enums::UserAuthType,
|
||||
pub private_config: Option<Encryption>,
|
||||
pub public_config: Option<serde_json::Value>,
|
||||
pub allow_signup: bool,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
|
||||
#[diesel(table_name = user_authentication_methods)]
|
||||
pub struct UserAuthenticationMethodNew {
|
||||
pub id: String,
|
||||
pub auth_id: String,
|
||||
pub owner_id: String,
|
||||
pub owner_type: enums::Owner,
|
||||
pub auth_type: enums::UserAuthType,
|
||||
pub private_config: Option<Encryption>,
|
||||
pub public_config: Option<serde_json::Value>,
|
||||
pub allow_signup: bool,
|
||||
pub created_at: PrimitiveDateTime,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
|
||||
#[diesel(table_name = user_authentication_methods)]
|
||||
pub struct OrgAuthenticationMethodUpdateInternal {
|
||||
pub private_config: Option<Encryption>,
|
||||
pub public_config: Option<serde_json::Value>,
|
||||
pub last_modified_at: PrimitiveDateTime,
|
||||
}
|
||||
|
||||
pub enum UserAuthenticationMethodUpdate {
|
||||
UpdateConfig {
|
||||
private_config: Option<Encryption>,
|
||||
public_config: Option<serde_json::Value>,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<UserAuthenticationMethodUpdate> for OrgAuthenticationMethodUpdateInternal {
|
||||
fn from(value: UserAuthenticationMethodUpdate) -> Self {
|
||||
let last_modified_at = common_utils::date_time::now();
|
||||
match value {
|
||||
UserAuthenticationMethodUpdate::UpdateConfig {
|
||||
private_config,
|
||||
public_config,
|
||||
} => Self {
|
||||
private_config,
|
||||
public_config,
|
||||
last_modified_at,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -220,6 +220,22 @@ impl SecretsHandler for settings::Secrets {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl SecretsHandler for settings::UserAuthMethodSettings {
|
||||
async fn convert_to_raw_secret(
|
||||
value: SecretStateContainer<Self, SecuredSecret>,
|
||||
secret_management_client: &dyn SecretManagementInterface,
|
||||
) -> CustomResult<SecretStateContainer<Self, RawSecret>, SecretsManagementError> {
|
||||
let user_auth_methods = value.get_inner();
|
||||
|
||||
let encryption_key = secret_management_client
|
||||
.get_secret(user_auth_methods.encryption_key.clone())
|
||||
.await?;
|
||||
|
||||
Ok(value.transition_state(|_| Self { encryption_key }))
|
||||
}
|
||||
}
|
||||
|
||||
/// # Panics
|
||||
///
|
||||
/// Will panic even if kms decryption fails for at least one field
|
||||
@ -302,6 +318,14 @@ pub(crate) async fn fetch_raw_secrets(
|
||||
.await
|
||||
.expect("Failed to decrypt payment method auth configs");
|
||||
|
||||
#[allow(clippy::expect_used)]
|
||||
let user_auth_methods = settings::UserAuthMethodSettings::convert_to_raw_secret(
|
||||
conf.user_auth_methods,
|
||||
secret_management_client,
|
||||
)
|
||||
.await
|
||||
.expect("Failed to decrypt user_auth_methods configs");
|
||||
|
||||
Settings {
|
||||
server: conf.server,
|
||||
master_database,
|
||||
@ -368,5 +392,6 @@ pub(crate) async fn fetch_raw_secrets(
|
||||
unmasked_headers: conf.unmasked_headers,
|
||||
saved_payment_methods: conf.saved_payment_methods,
|
||||
multitenancy: conf.multitenancy,
|
||||
user_auth_methods,
|
||||
}
|
||||
}
|
||||
|
||||
@ -123,6 +123,7 @@ pub struct Settings<S: SecretState> {
|
||||
pub unmasked_headers: UnmaskedHeaders,
|
||||
pub multitenancy: Multitenancy,
|
||||
pub saved_payment_methods: EligiblePaymentMethods,
|
||||
pub user_auth_methods: SecretStateContainer<UserAuthMethodSettings, S>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
@ -615,6 +616,11 @@ pub struct ConnectorRequestReferenceIdConfig {
|
||||
pub merchant_ids_send_payment_id_as_connector_request_id: HashSet<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone, Default)]
|
||||
pub struct UserAuthMethodSettings {
|
||||
pub encryption_key: Secret<String>,
|
||||
}
|
||||
|
||||
impl Settings<SecuredSecret> {
|
||||
pub fn new() -> ApplicationResult<Self> {
|
||||
Self::with_config_path(None)
|
||||
|
||||
@ -80,6 +80,12 @@ pub enum UserErrors {
|
||||
TwoFactorAuthNotSetup,
|
||||
#[error("TOTP secret not found")]
|
||||
TotpSecretNotFound,
|
||||
#[error("User auth method already exists")]
|
||||
UserAuthMethodAlreadyExists,
|
||||
#[error("Invalid user auth method operation")]
|
||||
InvalidUserAuthMethodOperation,
|
||||
#[error("Auth config parsing error")]
|
||||
AuthConfigParsingError,
|
||||
}
|
||||
|
||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||
@ -204,6 +210,15 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::TotpSecretNotFound => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 42, self.get_error_message(), None))
|
||||
}
|
||||
Self::UserAuthMethodAlreadyExists => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 43, self.get_error_message(), None))
|
||||
}
|
||||
Self::InvalidUserAuthMethodOperation => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 44, self.get_error_message(), None))
|
||||
}
|
||||
Self::AuthConfigParsingError => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 45, self.get_error_message(), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,6 +262,9 @@ impl UserErrors {
|
||||
Self::TwoFactorAuthRequired => "Two factor auth required",
|
||||
Self::TwoFactorAuthNotSetup => "Two factor auth not setup",
|
||||
Self::TotpSecretNotFound => "TOTP secret not found",
|
||||
Self::UserAuthMethodAlreadyExists => "User auth method already exists",
|
||||
Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation",
|
||||
Self::AuthConfigParsingError => "Auth config parsing error",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use api_models::user::{self as user_api, InviteMultipleUserResponse};
|
||||
use common_utils::ext_traits::ValueExt;
|
||||
#[cfg(feature = "email")]
|
||||
use diesel_models::user_role::UserRoleUpdate;
|
||||
use diesel_models::{
|
||||
enums::{TotpStatus, UserStatus},
|
||||
user as storage_user,
|
||||
user_authentication_method::{UserAuthenticationMethodNew, UserAuthenticationMethodUpdate},
|
||||
user_role::UserRoleNew,
|
||||
};
|
||||
use error_stack::{report, ResultExt};
|
||||
@ -884,7 +886,7 @@ pub async fn resend_invite(
|
||||
if e.current_context().is_db_not_found() {
|
||||
e.change_context(UserErrors::InvalidRoleOperation)
|
||||
.attach_printable(format!(
|
||||
"User role with user_id = {} and org_id = {} is not found",
|
||||
"User role with user_id = {} and merchant_id = {} is not found",
|
||||
user.get_user_id(),
|
||||
user_from_token.merchant_id
|
||||
))
|
||||
@ -1982,3 +1984,184 @@ pub async fn check_two_factor_auth_status(
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn create_user_authentication_method(
|
||||
state: SessionState,
|
||||
req: user_api::CreateUserAuthenticationMethodRequest,
|
||||
) -> UserResponse<()> {
|
||||
let user_auth_encryption_key = hex::decode(
|
||||
state
|
||||
.conf
|
||||
.user_auth_methods
|
||||
.get_inner()
|
||||
.encryption_key
|
||||
.clone()
|
||||
.expose(),
|
||||
)
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to decode DEK")?;
|
||||
|
||||
let (private_config, public_config) = match req.auth_method {
|
||||
user_api::AuthConfig::OpenIdConnect {
|
||||
ref private_config,
|
||||
ref public_config,
|
||||
} => {
|
||||
let private_config_value = serde_json::to_value(private_config.clone())
|
||||
.change_context(UserErrors::AuthConfigParsingError)
|
||||
.attach_printable("Failed to convert auth config to json")?;
|
||||
|
||||
let encrypted_config = domain::types::encrypt::<serde_json::Value, masking::WithType>(
|
||||
private_config_value.into(),
|
||||
&user_auth_encryption_key,
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to encrypt auth config")?;
|
||||
|
||||
Ok::<_, error_stack::Report<UserErrors>>((
|
||||
Some(encrypted_config.into()),
|
||||
Some(
|
||||
serde_json::to_value(public_config.clone())
|
||||
.change_context(UserErrors::AuthConfigParsingError)
|
||||
.attach_printable("Failed to convert auth config to json")?,
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => Ok((None, None)),
|
||||
}?;
|
||||
|
||||
let auth_methods = state
|
||||
.store
|
||||
.list_user_authentication_methods_for_owner_id(&req.owner_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to get list of auth methods for the owner id")?;
|
||||
|
||||
let auth_id = auth_methods
|
||||
.first()
|
||||
.map(|auth_method| auth_method.auth_id.clone())
|
||||
.unwrap_or(uuid::Uuid::new_v4().to_string());
|
||||
|
||||
let now = common_utils::date_time::now();
|
||||
state
|
||||
.store
|
||||
.insert_user_authentication_method(UserAuthenticationMethodNew {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
auth_id,
|
||||
owner_id: req.owner_id,
|
||||
owner_type: req.owner_type,
|
||||
auth_type: req.auth_method.foreign_into(),
|
||||
private_config,
|
||||
public_config,
|
||||
allow_signup: req.allow_signup,
|
||||
created_at: now,
|
||||
last_modified_at: now,
|
||||
})
|
||||
.await
|
||||
.to_duplicate_response(UserErrors::UserAuthMethodAlreadyExists)?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn update_user_authentication_method(
|
||||
state: SessionState,
|
||||
req: user_api::UpdateUserAuthenticationMethodRequest,
|
||||
) -> UserResponse<()> {
|
||||
let user_auth_encryption_key = hex::decode(
|
||||
state
|
||||
.conf
|
||||
.user_auth_methods
|
||||
.get_inner()
|
||||
.encryption_key
|
||||
.clone()
|
||||
.expose(),
|
||||
)
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to decode DEK")?;
|
||||
|
||||
let (private_config, public_config) = match req.auth_method {
|
||||
user_api::AuthConfig::OpenIdConnect {
|
||||
ref private_config,
|
||||
ref public_config,
|
||||
} => {
|
||||
let private_config_value = serde_json::to_value(private_config.clone())
|
||||
.change_context(UserErrors::AuthConfigParsingError)
|
||||
.attach_printable("Failed to convert auth config to json")?;
|
||||
|
||||
let encrypted_config = domain::types::encrypt::<serde_json::Value, masking::WithType>(
|
||||
private_config_value.into(),
|
||||
&user_auth_encryption_key,
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to encrypt auth config")?;
|
||||
|
||||
Ok::<_, error_stack::Report<UserErrors>>((
|
||||
Some(encrypted_config.into()),
|
||||
Some(
|
||||
serde_json::to_value(public_config.clone())
|
||||
.change_context(UserErrors::AuthConfigParsingError)
|
||||
.attach_printable("Failed to convert auth config to json")?,
|
||||
),
|
||||
))
|
||||
}
|
||||
_ => Ok((None, None)),
|
||||
}?;
|
||||
|
||||
state
|
||||
.store
|
||||
.update_user_authentication_method(
|
||||
&req.id,
|
||||
UserAuthenticationMethodUpdate::UpdateConfig {
|
||||
private_config,
|
||||
public_config,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InvalidUserAuthMethodOperation)?;
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn list_user_authentication_methods(
|
||||
state: SessionState,
|
||||
req: user_api::GetUserAuthenticationMethodsRequest,
|
||||
) -> UserResponse<Vec<user_api::UserAuthenticationMethodResponse>> {
|
||||
let user_authentication_methods = state
|
||||
.store
|
||||
.list_user_authentication_methods_for_auth_id(&req.auth_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::Json(
|
||||
user_authentication_methods
|
||||
.into_iter()
|
||||
.map(|auth_method| {
|
||||
let auth_name = match (auth_method.auth_type, auth_method.public_config) {
|
||||
(common_enums::UserAuthType::OpenIdConnect, Some(config)) => {
|
||||
let open_id_public_config: user_api::OpenIdConnectPublicConfig = config
|
||||
.parse_value("OpenIdConnectPublicConfig")
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("unable to parse generic data value")?;
|
||||
|
||||
Ok(Some(open_id_public_config.name))
|
||||
}
|
||||
(common_enums::UserAuthType::OpenIdConnect, None) => {
|
||||
Err(UserErrors::InternalServerError)
|
||||
.attach_printable("No config found for open_id_connect auth_method")
|
||||
}
|
||||
_ => Ok(None),
|
||||
}?;
|
||||
|
||||
Ok(user_api::UserAuthenticationMethodResponse {
|
||||
id: auth_method.id,
|
||||
auth_id: auth_method.auth_id,
|
||||
auth_method: user_api::AuthMethodDetails {
|
||||
name: auth_name,
|
||||
auth_type: auth_method.auth_type,
|
||||
},
|
||||
allow_signup: auth_method.allow_signup,
|
||||
})
|
||||
})
|
||||
.collect::<UserResult<_>>()?,
|
||||
))
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ pub mod reverse_lookup;
|
||||
pub mod role;
|
||||
pub mod routing_algorithm;
|
||||
pub mod user;
|
||||
pub mod user_authentication_method;
|
||||
pub mod user_key_store;
|
||||
pub mod user_role;
|
||||
|
||||
@ -117,6 +118,7 @@ pub trait StorageInterface:
|
||||
+ user::sample_data::BatchSampleDataInterface
|
||||
+ health_check::HealthCheckDbInterface
|
||||
+ role::RoleInterface
|
||||
+ user_authentication_method::UserAuthenticationMethodInterface
|
||||
+ authentication::AuthenticationInterface
|
||||
+ 'static
|
||||
{
|
||||
|
||||
@ -33,6 +33,7 @@ use super::{
|
||||
dashboard_metadata::DashboardMetadataInterface,
|
||||
role::RoleInterface,
|
||||
user::{sample_data::BatchSampleDataInterface, UserInterface},
|
||||
user_authentication_method::UserAuthenticationMethodInterface,
|
||||
user_key_store::UserKeyStoreInterface,
|
||||
user_role::UserRoleInterface,
|
||||
};
|
||||
@ -2874,3 +2875,43 @@ impl UserKeyStoreInterface for KafkaStore {
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UserAuthenticationMethodInterface for KafkaStore {
|
||||
async fn insert_user_authentication_method(
|
||||
&self,
|
||||
user_authentication_method: storage::UserAuthenticationMethodNew,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.insert_user_authentication_method(user_authentication_method)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_user_authentication_methods_for_auth_id(
|
||||
&self,
|
||||
auth_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.list_user_authentication_methods_for_auth_id(auth_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn list_user_authentication_methods_for_owner_id(
|
||||
&self,
|
||||
owner_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.list_user_authentication_methods_for_owner_id(owner_id)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_user_authentication_method(
|
||||
&self,
|
||||
id: &str,
|
||||
user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.update_user_authentication_method(id, user_authentication_method_update)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
197
crates/router/src/db/user_authentication_method.rs
Normal file
197
crates/router/src/db/user_authentication_method.rs
Normal file
@ -0,0 +1,197 @@
|
||||
use diesel_models::user_authentication_method::{self as storage};
|
||||
use error_stack::report;
|
||||
use router_env::{instrument, tracing};
|
||||
|
||||
use super::MockDb;
|
||||
use crate::{
|
||||
connection,
|
||||
core::errors::{self, CustomResult},
|
||||
services::Store,
|
||||
};
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait UserAuthenticationMethodInterface {
|
||||
async fn insert_user_authentication_method(
|
||||
&self,
|
||||
user_authentication_method: storage::UserAuthenticationMethodNew,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
|
||||
|
||||
async fn list_user_authentication_methods_for_auth_id(
|
||||
&self,
|
||||
auth_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError>;
|
||||
|
||||
async fn list_user_authentication_methods_for_owner_id(
|
||||
&self,
|
||||
owner_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError>;
|
||||
|
||||
async fn update_user_authentication_method(
|
||||
&self,
|
||||
id: &str,
|
||||
user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError>;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UserAuthenticationMethodInterface for Store {
|
||||
#[instrument(skip_all)]
|
||||
async fn insert_user_authentication_method(
|
||||
&self,
|
||||
user_authentication_method: storage::UserAuthenticationMethodNew,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
user_authentication_method
|
||||
.insert(&conn)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn list_user_authentication_methods_for_auth_id(
|
||||
&self,
|
||||
auth_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::UserAuthenticationMethod::list_user_authentication_methods_for_auth_id(
|
||||
&conn, auth_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn list_user_authentication_methods_for_owner_id(
|
||||
&self,
|
||||
owner_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::UserAuthenticationMethod::list_user_authentication_methods_for_owner_id(
|
||||
&conn, owner_id,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
async fn update_user_authentication_method(
|
||||
&self,
|
||||
id: &str,
|
||||
user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::UserAuthenticationMethod::update_user_authentication_method(
|
||||
&conn,
|
||||
id,
|
||||
user_authentication_method_update,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| report!(errors::StorageError::from(error)))
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl UserAuthenticationMethodInterface for MockDb {
|
||||
async fn insert_user_authentication_method(
|
||||
&self,
|
||||
user_authentication_method: storage::UserAuthenticationMethodNew,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||
let mut user_authentication_methods = self.user_authentication_methods.lock().await;
|
||||
let existing_auth_id = user_authentication_methods
|
||||
.iter()
|
||||
.find(|uam| uam.owner_id == user_authentication_method.owner_id)
|
||||
.map(|uam| uam.auth_id.clone());
|
||||
|
||||
let auth_id = existing_auth_id.unwrap_or_else(|| uuid::Uuid::new_v4().to_string());
|
||||
let user_authentication_method = storage::UserAuthenticationMethod {
|
||||
id: uuid::Uuid::new_v4().to_string(),
|
||||
auth_id,
|
||||
owner_id: user_authentication_method.auth_id,
|
||||
owner_type: user_authentication_method.owner_type,
|
||||
auth_type: user_authentication_method.auth_type,
|
||||
public_config: user_authentication_method.public_config,
|
||||
private_config: user_authentication_method.private_config,
|
||||
allow_signup: user_authentication_method.allow_signup,
|
||||
created_at: user_authentication_method.created_at,
|
||||
last_modified_at: user_authentication_method.last_modified_at,
|
||||
};
|
||||
|
||||
user_authentication_methods.push(user_authentication_method.clone());
|
||||
Ok(user_authentication_method)
|
||||
}
|
||||
|
||||
async fn list_user_authentication_methods_for_auth_id(
|
||||
&self,
|
||||
auth_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
|
||||
let user_authentication_methods = self.user_authentication_methods.lock().await;
|
||||
|
||||
let user_authentication_methods_list: Vec<_> = user_authentication_methods
|
||||
.iter()
|
||||
.filter(|auth_method_inner| auth_method_inner.auth_id == auth_id)
|
||||
.cloned()
|
||||
.collect();
|
||||
if user_authentication_methods_list.is_empty() {
|
||||
return Err(errors::StorageError::ValueNotFound(format!(
|
||||
"No user authentication method found for auth_id = {}",
|
||||
auth_id
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(user_authentication_methods_list)
|
||||
}
|
||||
|
||||
async fn list_user_authentication_methods_for_owner_id(
|
||||
&self,
|
||||
owner_id: &str,
|
||||
) -> CustomResult<Vec<storage::UserAuthenticationMethod>, errors::StorageError> {
|
||||
let user_authentication_methods = self.user_authentication_methods.lock().await;
|
||||
|
||||
let user_authentication_methods_list: Vec<_> = user_authentication_methods
|
||||
.iter()
|
||||
.filter(|auth_method_inner| auth_method_inner.owner_id == owner_id)
|
||||
.cloned()
|
||||
.collect();
|
||||
if user_authentication_methods_list.is_empty() {
|
||||
return Err(errors::StorageError::ValueNotFound(format!(
|
||||
"No user authentication method found for owner_id = {}",
|
||||
owner_id
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(user_authentication_methods_list)
|
||||
}
|
||||
|
||||
async fn update_user_authentication_method(
|
||||
&self,
|
||||
id: &str,
|
||||
user_authentication_method_update: storage::UserAuthenticationMethodUpdate,
|
||||
) -> CustomResult<storage::UserAuthenticationMethod, errors::StorageError> {
|
||||
let mut user_authentication_methods = self.user_authentication_methods.lock().await;
|
||||
user_authentication_methods
|
||||
.iter_mut()
|
||||
.find(|auth_method_inner| auth_method_inner.id == id)
|
||||
.map(|auth_method_inner| {
|
||||
*auth_method_inner = match user_authentication_method_update {
|
||||
storage::UserAuthenticationMethodUpdate::UpdateConfig {
|
||||
private_config,
|
||||
public_config,
|
||||
} => storage::UserAuthenticationMethod {
|
||||
private_config,
|
||||
public_config,
|
||||
last_modified_at: common_utils::date_time::now(),
|
||||
..auth_method_inner.to_owned()
|
||||
},
|
||||
};
|
||||
auth_method_inner.to_owned()
|
||||
})
|
||||
.ok_or(
|
||||
errors::StorageError::ValueNotFound(format!(
|
||||
"No authentication method available for the id = {id}"
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1377,6 +1377,18 @@ impl User {
|
||||
),
|
||||
);
|
||||
|
||||
route = route.service(
|
||||
web::scope("/auth")
|
||||
.service(
|
||||
web::resource("")
|
||||
.route(web::post().to(create_user_authentication_method))
|
||||
.route(web::put().to(update_user_authentication_method)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/list").route(web::get().to(list_user_authentication_methods)),
|
||||
),
|
||||
);
|
||||
|
||||
#[cfg(feature = "email")]
|
||||
{
|
||||
route = route
|
||||
|
||||
@ -223,7 +223,10 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::RecoveryCodeVerify
|
||||
| Flow::RecoveryCodesGenerate
|
||||
| Flow::TerminateTwoFactorAuth
|
||||
| Flow::TwoFactorAuthStatus => Self::User,
|
||||
| Flow::TwoFactorAuthStatus
|
||||
| Flow::CreateUserAuthenticationMethod
|
||||
| Flow::UpdateUserAuthenticationMethod
|
||||
| Flow::ListUserAuthenticationMethods => Self::User,
|
||||
|
||||
Flow::ListRoles
|
||||
| Flow::GetRole
|
||||
|
||||
@ -749,3 +749,60 @@ pub async fn check_two_factor_auth_status(
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_user_authentication_method(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<user_api::CreateUserAuthenticationMethodRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::CreateUserAuthenticationMethod;
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, _, req_body, _| user_core::create_user_authentication_method(state, req_body),
|
||||
&auth::AdminApiAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update_user_authentication_method(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<user_api::UpdateUserAuthenticationMethodRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::UpdateUserAuthenticationMethod;
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
|state, _, req_body, _| user_core::update_user_authentication_method(state, req_body),
|
||||
&auth::AdminApiAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_user_authentication_methods(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
query: web::Query<user_api::GetUserAuthenticationMethodsRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::ListUserAuthenticationMethods;
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
query.into_inner(),
|
||||
|state, _, req, _| user_core::list_user_authentication_methods(state, req),
|
||||
&auth::NoAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@ -35,6 +35,7 @@ pub mod reverse_lookup;
|
||||
pub mod role;
|
||||
pub mod routing_algorithm;
|
||||
pub mod user;
|
||||
pub mod user_authentication_method;
|
||||
pub mod user_role;
|
||||
|
||||
use std::collections::HashMap;
|
||||
@ -62,7 +63,7 @@ pub use self::{
|
||||
file::*, fraud_check::*, gsm::*, locker_mock_up::*, mandate::*, merchant_account::*,
|
||||
merchant_connector_account::*, merchant_key_store::*, payment_link::*, payment_method::*,
|
||||
process_tracker::*, refund::*, reverse_lookup::*, role::*, routing_algorithm::*, user::*,
|
||||
user_role::*,
|
||||
user_authentication_method::*, user_role::*,
|
||||
};
|
||||
use crate::types::api::routing;
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
pub use diesel_models::user_authentication_method::*;
|
||||
@ -13,7 +13,10 @@ use crate::{
|
||||
authentication::{AuthToken, UserFromToken},
|
||||
authorization::roles::RoleInfo,
|
||||
},
|
||||
types::domain::{self, MerchantAccount, UserFromStorage},
|
||||
types::{
|
||||
domain::{self, MerchantAccount, UserFromStorage},
|
||||
transformers::ForeignFrom,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod dashboard_metadata;
|
||||
@ -200,3 +203,13 @@ pub fn get_redis_connection(state: &SessionState) -> UserResult<Arc<RedisConnect
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to get redis connection")
|
||||
}
|
||||
|
||||
impl ForeignFrom<user_api::AuthConfig> for common_enums::UserAuthType {
|
||||
fn foreign_from(from: user_api::AuthConfig) -> Self {
|
||||
match from {
|
||||
user_api::AuthConfig::OpenIdConnect { .. } => Self::OpenIdConnect,
|
||||
user_api::AuthConfig::Password => Self::Password,
|
||||
user_api::AuthConfig::MagicLink => Self::MagicLink,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -420,6 +420,12 @@ pub enum Flow {
|
||||
TerminateTwoFactorAuth,
|
||||
// Check 2FA status
|
||||
TwoFactorAuthStatus,
|
||||
// Create user authentication method
|
||||
CreateUserAuthenticationMethod,
|
||||
// Update user authentication method
|
||||
UpdateUserAuthenticationMethod,
|
||||
// List user authentication methods
|
||||
ListUserAuthenticationMethods,
|
||||
/// List initial webhook delivery attempts
|
||||
WebhookEventInitialDeliveryAttemptList,
|
||||
/// List delivery attempts for a webhook event
|
||||
|
||||
@ -58,6 +58,8 @@ pub struct MockDb {
|
||||
pub authentications: Arc<Mutex<Vec<store::authentication::Authentication>>>,
|
||||
pub roles: Arc<Mutex<Vec<store::role::Role>>>,
|
||||
pub user_key_store: Arc<Mutex<Vec<store::user_key_store::UserKeyStore>>>,
|
||||
pub user_authentication_methods:
|
||||
Arc<Mutex<Vec<store::user_authentication_method::UserAuthenticationMethod>>>,
|
||||
}
|
||||
|
||||
impl MockDb {
|
||||
@ -102,6 +104,7 @@ impl MockDb {
|
||||
authentications: Default::default(),
|
||||
roles: Default::default(),
|
||||
user_key_store: Default::default(),
|
||||
user_authentication_methods: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX IF EXISTS auth_id_index;
|
||||
DROP INDEX IF EXISTS owner_id_index;
|
||||
DROP TABLE IF EXISTS user_authentication_methods;
|
||||
@ -0,0 +1,16 @@
|
||||
-- Your SQL goes here
|
||||
CREATE TABLE IF NOT EXISTS user_authentication_methods (
|
||||
id VARCHAR(64) PRIMARY KEY,
|
||||
auth_id VARCHAR(64) NOT NULL,
|
||||
owner_id VARCHAR(64) NOT NULL,
|
||||
owner_type VARCHAR(64) NOT NULL,
|
||||
auth_type VARCHAR(64) NOT NULL,
|
||||
private_config bytea,
|
||||
public_config JSONB,
|
||||
allow_signup BOOLEAN NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||
last_modified_at TIMESTAMP NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS auth_id_index ON user_authentication_methods (auth_id);
|
||||
CREATE INDEX IF NOT EXISTS owner_id_index ON user_authentication_methods (owner_id);
|
||||
Reference in New Issue
Block a user