Skip to content

Commit

Permalink
feat(users): setup user authentication methods schema and apis (#4999)
Browse files Browse the repository at this point in the history
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Mani Chandra Dulam <[email protected]>
  • Loading branch information
3 people committed Jun 21, 2024
1 parent 5cde7ee commit 2005d3d
Show file tree
Hide file tree
Showing 29 changed files with 888 additions and 16 deletions.
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:https://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:https://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:https://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
public = { name = "hyperswitch", base_url = "http:https://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:https://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}

[user_auth_methods]
encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
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:https://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
public = { name = "hyperswitch", base_url = "http:https://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}

[user_auth_methods]
encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
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
75 changes: 75 additions & 0 deletions crates/api_models/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}
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 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,
}
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()),
)
.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
}
}
24 changes: 24 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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,
Expand Down
65 changes: 65 additions & 0 deletions crates/diesel_models/src/user_authentication_method.rs
Original file line number Diff line number Diff line change
@@ -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,
},
}
}
}
Loading

0 comments on commit 2005d3d

Please sign in to comment.