Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(users): setup user authentication methods schema and apis #4999

Merged
merged 25 commits into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
397368a
feat(users): setup table for org authentication methods
apoorvdixit88 Jun 12, 2024
a21f2e9
feat(users): add api to get org auth methods
apoorvdixit88 Jun 12, 2024
556f416
feat(users): implement core functions for create and update
apoorvdixit88 Jun 12, 2024
b58bfab
fix: change error for create and update
apoorvdixit88 Jun 13, 2024
8b4c7bc
fix: remove warnings
apoorvdixit88 Jun 13, 2024
92dfc93
fix: line formatting
apoorvdixit88 Jun 13, 2024
015d7e7
chore: run formatter
hyperswitch-bot[bot] Jun 13, 2024
3dc3295
fix: update schema for db
apoorvdixit88 Jun 14, 2024
aa5fbc0
fix: update config request
apoorvdixit88 Jun 18, 2024
ae64997
update core function
apoorvdixit88 Jun 19, 2024
bfa2a47
fix: rename errors and interface
apoorvdixit88 Jun 19, 2024
4c40b6c
fix: serde to snake case for auth config
apoorvdixit88 Jun 19, 2024
e5b743d
fix: mock db for insert auth method
apoorvdixit88 Jun 19, 2024
3180b9e
fix: formattion
apoorvdixit88 Jun 19, 2024
9ce92a8
feat: add DEK for user-auth-table
ThisIsMani Jun 19, 2024
25f54b2
Merge branch 'main' of https://github.com/juspay/hyperswitch into set…
ThisIsMani Jun 20, 2024
7ef506d
refactor: add user_auth env in remaining toml files
ThisIsMani Jun 20, 2024
d250d78
fix: hack
ThisIsMani Jun 20, 2024
60905a6
resolve review comments
apoorvdixit88 Jun 20, 2024
e4d66c5
refactor: add name in list api response
ThisIsMani Jun 20, 2024
bf4ef76
add enum for name
apoorvdixit88 Jun 20, 2024
257f816
refactor: remove key from list api
ThisIsMani Jun 20, 2024
91cd8d2
refactor: remove comments
ThisIsMani Jun 20, 2024
bbd1c0b
refactor: add sample keys in `config/development.toml` and `config/do…
ThisIsMani Jun 20, 2024
e63a327
refactor: separate out public and private configs
ThisIsMani Jun 20, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion config/config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 4 additions & 1 deletion config/deployments/env_specific.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
5 changes: 4 additions & 1 deletion config/docker_compose.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
22 changes: 13 additions & 9 deletions crates/api_models/src/events/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -77,7 +78,10 @@ common_utils::impl_misc_api_event_type!(
BeginTotpResponse,
VerifyRecoveryCodeRequest,
VerifyTotpRequest,
RecoveryCodes
RecoveryCodes,
GetUserAuthenticationMethodsRequest,
CreateUserAuthenticationMethodRequest,
UpdateUserAuthenticationMethodRequest
);

#[cfg(feature = "dummy_connector")]
Expand Down
57 changes: 57 additions & 0 deletions crates/api_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,60 @@ pub struct VerifyRecoveryCodeRequest {
pub struct RecoveryCodes {
pub recovery_codes: Vec<Secret<String>>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[serde(rename_all = "snake_case")]
pub enum AuthConfig {
OpenIdConnect(OpenIdConnect),
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
#[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: common_enums::AuthMethod,
pub config: Option<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 config: AuthConfig,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct GetUserAuthenticationMethodsRequest {
pub auth_id: String,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct UserAuthenticationMethodResponse {
inventvenkat marked this conversation as resolved.
Show resolved Hide resolved
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::AuthMethod,
pub name: Option<OpenIdProvider>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be forced to take OpenIdProvider type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be changed to String when we will add other cases, where name doesn't come from OpenIdProvider

}
42 changes: 42 additions & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 AuthMethod {
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What would be the owner_id, in case on Owner::Internal.

Copy link
Contributor Author

@apoorvdixit88 apoorvdixit88 Jun 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove Internal, or assign some id (hyperswitch) if we think we encounter relevant use cases for this.

}
1 change: 1 addition & 0 deletions crates/diesel_models/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
1 change: 1 addition & 0 deletions crates/diesel_models/src/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 60 additions & 0 deletions crates/diesel_models/src/query/user_authentication_method.rs
Original file line number Diff line number Diff line change
@@ -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()),
inventvenkat marked this conversation as resolved.
Show resolved Hide resolved
)
.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
}
}
23 changes: 23 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,28 @@ 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_method -> Varchar,
config -> Nullable<Bytea>,
allow_signup -> Bool,
created_at -> Timestamp,
last_modified_at -> Timestamp,
}
}

diesel::table! {
use diesel::sql_types::*;
use crate::enums::diesel_exports::*;
Expand Down Expand Up @@ -1270,6 +1292,7 @@ diesel::allow_tables_to_appear_in_same_query!(
reverse_lookup,
roles,
routing_algorithm,
user_authentication_methods,
user_key_store,
user_roles,
users,
Expand Down
55 changes: 55 additions & 0 deletions crates/diesel_models/src/user_authentication_method.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
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_method: enums::AuthMethod,
pub config: Option<Encryption>,
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_method: enums::AuthMethod,
pub config: Option<Encryption>,
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 config: Option<Encryption>,
pub last_modified_at: PrimitiveDateTime,
}

pub enum UserAuthenticationMethodUpdate {
UpdateConfig { config: Option<Encryption> },
}

impl From<UserAuthenticationMethodUpdate> for OrgAuthenticationMethodUpdateInternal {
fn from(value: UserAuthenticationMethodUpdate) -> Self {
let last_modified_at = common_utils::date_time::now();
match value {
UserAuthenticationMethodUpdate::UpdateConfig { config } => Self {
config,
last_modified_at,
},
}
}
}
25 changes: 25 additions & 0 deletions crates/router/src/configs/secrets_transformers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}
}
Loading
Loading