diff --git a/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts b/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts index 584476d293..916e805d01 100644 --- a/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts +++ b/console/src/app/modules/policies/login-policy/factor-table/factor-table.component.ts @@ -146,7 +146,12 @@ export class FactorTableComponent { this.componentType === LoginMethodComponentType.MultiFactor ? [MultiFactorType.MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION] : this.componentType === LoginMethodComponentType.SecondFactor - ? [SecondFactorType.SECOND_FACTOR_TYPE_U2F, SecondFactorType.SECOND_FACTOR_TYPE_OTP] + ? [ + SecondFactorType.SECOND_FACTOR_TYPE_U2F, + SecondFactorType.SECOND_FACTOR_TYPE_OTP, + SecondFactorType.SECOND_FACTOR_TYPE_OTP_SMS, + SecondFactorType.SECOND_FACTOR_TYPE_OTP_EMAIL, + ] : []; const filtered = (allTypes as Array).filter((type) => !this.list.includes(type)); diff --git a/console/src/assets/i18n/bg.json b/console/src/assets/i18n/bg.json index b5a2abb5a0..b780879a73 100644 --- a/console/src/assets/i18n/bg.json +++ b/console/src/assets/i18n/bg.json @@ -1840,8 +1840,10 @@ }, "SECONDFACTORTYPES": { "0": "неизвестен", - "1": "Еднократна парола (OTP)", - "2": "Пръстов отпечатък, ключове за сигурност, Face ID и други" + "1": "Еднократна парола чрез приложение за удостоверяване на автентичността (TOTP)", + "2": "Пръстов отпечатък, ключове за сигурност, Face ID и други", + "3": "Еднократна парола по имейл (Email OTP)", + "4": "Еднократна парола чрез SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/de.json b/console/src/assets/i18n/de.json index 01c8fb03bd..78da45b99b 100644 --- a/console/src/assets/i18n/de.json +++ b/console/src/assets/i18n/de.json @@ -1849,8 +1849,10 @@ }, "SECONDFACTORTYPES": { "0": "Unknown", - "1": "One Time Password (OTP)", - "2": "Fingerabdruck, Security Keys, Face ID und andere" + "1": "One Time Password per Authenticator App (TOTP)", + "2": "Fingerabdruck, Security Keys, Face ID und andere", + "3": "One Time Password per Email (Email OTP)", + "4": "One Time Password per SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/en.json b/console/src/assets/i18n/en.json index e4c8d5dd2d..61fa997c88 100644 --- a/console/src/assets/i18n/en.json +++ b/console/src/assets/i18n/en.json @@ -1846,8 +1846,10 @@ }, "SECONDFACTORTYPES": { "0": "Unknown", - "1": "One Time Password (OTP)", - "2": "Fingerprint, Security Keys, Face ID and other" + "1": "One Time Password by Authenticator App (TOTP)", + "2": "Fingerprint, Security Keys, Face ID and other", + "3": "One Time Password by Email (Email OTP)", + "4": "One Time Password by SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/es.json b/console/src/assets/i18n/es.json index bc7af48408..5c128eff11 100644 --- a/console/src/assets/i18n/es.json +++ b/console/src/assets/i18n/es.json @@ -1846,8 +1846,10 @@ }, "SECONDFACTORTYPES": { "0": "Desconocido", - "1": "One Time Password (OTP)", - "2": "Huella dactilar, claves de seguridad, Face ID y otros" + "1": "One Time Password por Authenticator App (TOTP)", + "2": "Huella dactilar, claves de seguridad, Face ID y otros", + "3": "One Time Password por email (Email OTP)", + "4": "One Time Password por SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/fr.json b/console/src/assets/i18n/fr.json index 61f2f7f780..676badc0e9 100644 --- a/console/src/assets/i18n/fr.json +++ b/console/src/assets/i18n/fr.json @@ -1850,8 +1850,10 @@ }, "SECONDFACTORTYPES": { "0": "Inconnu", - "1": "Mot de passe à usage unique (OTP)", - "2": "Empreinte digitale, clés de sécurité, Face ID et autres" + "1": "One Time Password par authenticator app (TOTP)", + "2": "Empreinte digitale, clés de sécurité, Face ID et autres", + "3": "One Time Password par email (Email OTP)", + "4": "One Time Password par SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/it.json b/console/src/assets/i18n/it.json index ed6e2fd811..1bec1139e6 100644 --- a/console/src/assets/i18n/it.json +++ b/console/src/assets/i18n/it.json @@ -1850,8 +1850,10 @@ }, "SECONDFACTORTYPES": { "0": "Sconosciuto", - "1": "One Time Password (OTP)", - "2": "Impronta digitale, chiave di sicurezza, Face ID e altri" + "1": "One Time Password per Authenticator App (TOTP)", + "2": "Impronta digitale, chiave di sicurezza, Face ID e altri", + "3": "One Time Password per Email (Email OTP)", + "4": "One Time Password per SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/ja.json b/console/src/assets/i18n/ja.json index bdba41562b..3551fcf4a7 100644 --- a/console/src/assets/i18n/ja.json +++ b/console/src/assets/i18n/ja.json @@ -1841,8 +1841,10 @@ }, "SECONDFACTORTYPES": { "0": "不明", - "1": "ワンタイムパスワード(OTP)", - "2": "指紋、セキュリティキー、フェイスIDなど" + "1": "認証アプリ用ワンタイムパスワード(TOTP)", + "2": "指紋、セキュリティキー、フェイスIDなど", + "3": "Eメール用ワンタイムパスワード(email OTP)", + "4": "SMS用ワンタイムパスワード(SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/mk.json b/console/src/assets/i18n/mk.json index 87d2e8a6ef..590ee2365f 100644 --- a/console/src/assets/i18n/mk.json +++ b/console/src/assets/i18n/mk.json @@ -1846,8 +1846,10 @@ }, "SECONDFACTORTYPES": { "0": "Непознато", - "1": "Еднократна лозинка (OTP)", - "2": "Отисок на прст, безбедносни клучеви, Face ID и другo" + "1": "Еднократна лозинка преку апликација за автентикатор (TOTP)", + "2": "Отпечаток на прст, безбедносни клучеви, Face ID и други", + "3": "Еднократна лозинка по е-пошта (Еmail OTP)", + "4": "Еднократна лозинка преку СМС (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/pl.json b/console/src/assets/i18n/pl.json index cd79105421..2e839ddd62 100644 --- a/console/src/assets/i18n/pl.json +++ b/console/src/assets/i18n/pl.json @@ -1850,8 +1850,10 @@ }, "SECONDFACTORTYPES": { "0": "Nieznany", - "1": "Jednorazowe hasło (OTP)", - "2": "Odcisk palca, klucze bezpieczeństwa, Face ID i inne" + "1": "Hasło jednorazowe dla aplikacji uwierzytelniającej (TOTP)", + "2": "Odcisk palca, Klucze Bezpieczeństwa, Face ID i inne", + "3": "Hasło jednorazowe dla wiadomości e-mail (Email OTP)", + "4": "Hasło jednorazowe dla wiadomości SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/pt.json b/console/src/assets/i18n/pt.json index 21e5d79359..a034c9b0ef 100644 --- a/console/src/assets/i18n/pt.json +++ b/console/src/assets/i18n/pt.json @@ -1844,8 +1844,10 @@ }, "SECONDFACTORTYPES": { "0": "Desconhecido", - "1": "Senha de Uso Único (OTP)", - "2": "Impressão Digital, Chaves de Segurança, Face ID e outros" + "1": "Senha de uso único para o aplicativo autenticador (TOTP)", + "2": "Impressão digital, Chaves de Segurança, Face ID e outros", + "3": "Senha de uso único para e-mail (Email OTP)", + "4": "Senha de uso único para SMS (SMS OTP)" } }, "LOGINPOLICY": { diff --git a/console/src/assets/i18n/zh.json b/console/src/assets/i18n/zh.json index 5247d16913..2efd31d80d 100644 --- a/console/src/assets/i18n/zh.json +++ b/console/src/assets/i18n/zh.json @@ -1849,8 +1849,10 @@ }, "SECONDFACTORTYPES": { "0": "未知", - "1": "一次性密码 (OTP)", - "2": "指纹、安全密钥、Face ID 等" + "1": "身份验证应用程序的一次性密码(TOTP)", + "2": "指纹、安全密钥、Face ID 等", + "3": "电子邮件一次性密码(email OTP)", + "4": "短信一次性密码(SMS OTP)" } }, "LOGINPOLICY": { diff --git a/docs/docs/guides/manage/console/instance-settings.mdx b/docs/docs/guides/manage/console/instance-settings.mdx index 913661c33d..471db69451 100644 --- a/docs/docs/guides/manage/console/instance-settings.mdx +++ b/docs/docs/guides/manage/console/instance-settings.mdx @@ -120,8 +120,10 @@ Multifactors: Secondfactors (2FA): -- OTP (One Time Password), Authenticator Apps like Google/Microsoft Authenticator, Authy, etc. -- U2F (Universal Second Factor), e.g FaceID, WindowsHello, Fingerprint, Hardwaretokens like Yubikey +- Time-based One Time Password (TOTP), Authenticator Apps like Google/Microsoft Authenticator, Authy, etc. +- Universal Second Factor (U2F), e.g FaceID, WindowsHello, Fingerprint, Hardwaretokens like Yubikey +- One Time Password with Email (Email OTP) +- One Time Password with SMS (SMS OTP) Force a user to register and use a multifactor authentication, by checking the option "Force MFA". Ensure that you have added the MFA methods you want to allow. diff --git a/internal/api/grpc/policy/auth_factor.go b/internal/api/grpc/policy/auth_factor.go index 56a155413c..e12eabfd94 100644 --- a/internal/api/grpc/policy/auth_factor.go +++ b/internal/api/grpc/policy/auth_factor.go @@ -16,9 +16,13 @@ func SecondFactorsTypesToDomain(secondFactorTypes []policy_pb.SecondFactorType) func SecondFactorTypeToDomain(secondFactorType policy_pb.SecondFactorType) domain.SecondFactorType { switch secondFactorType { case policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP: - return domain.SecondFactorTypeOTP + return domain.SecondFactorTypeTOTP case policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_U2F: return domain.SecondFactorTypeU2F + case policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP_EMAIL: + return domain.SecondFactorTypeOTPEmail + case policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP_SMS: + return domain.SecondFactorTypeOTPSMS default: return domain.SecondFactorTypeUnspecified } @@ -34,10 +38,14 @@ func ModelSecondFactorTypesToPb(types []domain.SecondFactorType) []policy_pb.Sec func ModelSecondFactorTypeToPb(secondFactorType domain.SecondFactorType) policy_pb.SecondFactorType { switch secondFactorType { - case domain.SecondFactorTypeOTP: + case domain.SecondFactorTypeTOTP: return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP case domain.SecondFactorTypeU2F: return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_U2F + case domain.SecondFactorTypeOTPEmail: + return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP_EMAIL + case domain.SecondFactorTypeOTPSMS: + return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_OTP_SMS default: return policy_pb.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED } diff --git a/internal/api/grpc/settings/v2/settings_converter.go b/internal/api/grpc/settings/v2/settings_converter.go index 4330613d04..f48177e1df 100644 --- a/internal/api/grpc/settings/v2/settings_converter.go +++ b/internal/api/grpc/settings/v2/settings_converter.go @@ -62,10 +62,14 @@ func passkeysTypeToPb(passwordlessType domain.PasswordlessType) settings.Passkey func secondFactorTypeToPb(secondFactorType domain.SecondFactorType) settings.SecondFactorType { switch secondFactorType { - case domain.SecondFactorTypeOTP: + case domain.SecondFactorTypeTOTP: return settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP case domain.SecondFactorTypeU2F: return settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F + case domain.SecondFactorTypeOTPEmail: + return settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP_EMAIL + case domain.SecondFactorTypeOTPSMS: + return settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP_SMS case domain.SecondFactorTypeUnspecified: return settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED default: diff --git a/internal/api/grpc/settings/v2/settings_converter_test.go b/internal/api/grpc/settings/v2/settings_converter_test.go index e81c0a70c9..37ad664f48 100644 --- a/internal/api/grpc/settings/v2/settings_converter_test.go +++ b/internal/api/grpc/settings/v2/settings_converter_test.go @@ -39,8 +39,10 @@ func Test_loginSettingsToPb(t *testing.T) { SecondFactorCheckLifetime: time.Microsecond, MultiFactorCheckLifetime: time.Nanosecond, SecondFactors: []domain.SecondFactorType{ - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, domain.SecondFactorTypeU2F, + domain.SecondFactorTypeOTPEmail, + domain.SecondFactorTypeOTPSMS, }, MultiFactors: []domain.MultiFactorType{ domain.MultiFactorTypeU2FWithPIN, @@ -69,6 +71,8 @@ func Test_loginSettingsToPb(t *testing.T) { SecondFactors: []settings.SecondFactorType{ settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP, settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F, + settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP_EMAIL, + settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP_SMS, }, MultiFactors: []settings.MultiFactorType{ settings.MultiFactorType_MULTI_FACTOR_TYPE_U2F_WITH_VERIFICATION, @@ -146,13 +150,21 @@ func Test_secondFactorTypeToPb(t *testing.T) { want settings.SecondFactorType }{ { - args: args{domain.SecondFactorTypeOTP}, + args: args{domain.SecondFactorTypeTOTP}, want: settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP, }, { args: args{domain.SecondFactorTypeU2F}, want: settings.SecondFactorType_SECOND_FACTOR_TYPE_U2F, }, + { + args: args{domain.SecondFactorTypeOTPSMS}, + want: settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP_SMS, + }, + { + args: args{domain.SecondFactorTypeOTPEmail}, + want: settings.SecondFactorType_SECOND_FACTOR_TYPE_OTP_EMAIL, + }, { args: args{domain.SecondFactorTypeUnspecified}, want: settings.SecondFactorType_SECOND_FACTOR_TYPE_UNSPECIFIED, diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go index 29737757b1..9603387952 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request_test.go @@ -889,7 +889,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { &domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -923,7 +923,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { &domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -959,7 +959,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { UserID: "UserID", SelectedIDPConfigID: "IDPConfigID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, ExternalLoginCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, @@ -996,7 +996,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { &domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1027,7 +1027,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { args{&domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1059,7 +1059,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { args{&domain.AuthRequest{ UserID: "UserID", LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1095,7 +1095,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { UserID: "UserID", Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1132,7 +1132,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1169,7 +1169,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1208,7 +1208,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1248,7 +1248,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1288,7 +1288,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1329,7 +1329,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { Prompt: []domain.Prompt{domain.PromptNone}, Request: &domain.AuthRequestOIDC{}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, PasswordCheckLifetime: 10 * 24 * time.Hour, SecondFactorCheckLifetime: 18 * time.Hour, }, @@ -1399,7 +1399,7 @@ func TestAuthRequestRepo_nextSteps(t *testing.T) { SelectedIDPConfigID: "IDPConfigID", LinkingUsers: []*domain.ExternalUser{{IDPConfigID: "IDPConfigID", ExternalUserID: "UserID", DisplayName: "DisplayName"}}, LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, PasswordCheckLifetime: 10 * 24 * time.Hour, }, @@ -1503,7 +1503,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, MFAInitSkipLifetime: 30 * 24 * time.Hour, }, }, @@ -1528,7 +1528,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ ForceMFA: true, - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, MFAInitSkipLifetime: 30 * 24 * time.Hour, }, }, @@ -1573,7 +1573,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, }, }, @@ -1595,7 +1595,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, }, }, @@ -1620,7 +1620,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, }, }, @@ -1644,7 +1644,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, }, }, @@ -1666,7 +1666,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, ForceMFA: true, }, @@ -1693,7 +1693,7 @@ func TestAuthRequestRepo_mfaChecked(t *testing.T) { args{ request: &domain.AuthRequest{ LoginPolicy: &domain.LoginPolicy{ - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, SecondFactorCheckLifetime: 18 * time.Hour, ForceMFA: true, ForceMFALocalOnly: true, diff --git a/internal/command/instance.go b/internal/command/instance.go index 6ae4776521..8bc43fbd38 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -245,8 +245,12 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str setup.LoginPolicy.SecondFactorCheckLifetime, setup.LoginPolicy.MultiFactorCheckLifetime, ), - prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeOTP), + prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeTOTP), prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeU2F), + /* TODO: incomment when usable + prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeOTPEmail), + prepareAddSecondFactorToDefaultLoginPolicy(instanceAgg, domain.SecondFactorTypeOTPSMS), + */ prepareAddMultiFactorToDefaultLoginPolicy(instanceAgg, domain.MultiFactorTypeU2FWithPIN), prepareAddDefaultPrivacyPolicy(instanceAgg, setup.PrivacyPolicy.TOSLink, setup.PrivacyPolicy.PrivacyLink, setup.PrivacyPolicy.HelpLink, setup.PrivacyPolicy.SupportEmail), diff --git a/internal/command/instance_policy_login_test.go b/internal/command/instance_policy_login_test.go index 881f1c9640..e69c6a5660 100644 --- a/internal/command/instance_policy_login_test.go +++ b/internal/command/instance_policy_login_test.go @@ -884,7 +884,7 @@ func TestCommandSide_AddSecondFactorDefaultLoginPolicy(t *testing.T) { eventFromEventPusher( instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), ), @@ -892,14 +892,14 @@ func TestCommandSide_AddSecondFactorDefaultLoginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, }, res: res{ err: caos_errs.IsErrorAlreadyExists, }, }, { - name: "add factor, ok", + name: "add factor totp, ok", fields: fields{ eventstore: eventstoreExpect( t, @@ -910,7 +910,7 @@ func TestCommandSide_AddSecondFactorDefaultLoginPolicy(t *testing.T) { "INSTANCE", instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - domain.SecondFactorTypeOTP), + domain.SecondFactorTypeTOTP), ), }, ), @@ -918,7 +918,98 @@ func TestCommandSide_AddSecondFactorDefaultLoginPolicy(t *testing.T) { }, args: args{ ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "add factor otp email, ok ", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPEmail), + ), + }, + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + factor: domain.SecondFactorTypeOTPEmail, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "add factor otp sms, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPSMS), + ), + }, + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + factor: domain.SecondFactorTypeOTPSMS, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "add factor totp, add otp sms, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeTOTP, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "INSTANCE", + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPSMS), + ), + }, + ), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + factor: domain.SecondFactorTypeOTPSMS, }, res: res{ want: &domain.ObjectDetails{ @@ -989,14 +1080,14 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, }, res: res{ err: caos_errs.IsNotFound, }, }, { - name: "factor removed, not found error", + name: "factor removed totp, not found error", fields: fields{ eventstore: eventstoreExpect( t, @@ -1004,13 +1095,13 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { eventFromEventPusher( instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), eventFromEventPusher( instance.NewLoginPolicySecondFactorRemovedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), ), @@ -1018,14 +1109,14 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, }, res: res{ err: caos_errs.IsNotFound, }, }, { - name: "add factor, ok", + name: "factor removed otp email, not found error", fields: fields{ eventstore: eventstoreExpect( t, @@ -1033,7 +1124,65 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { eventFromEventPusher( instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeOTPEmail, + ), + ), + eventFromEventPusher( + instance.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPEmail, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPEmail, + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "factor removed otp sms, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPSMS, + ), + ), + eventFromEventPusher( + instance.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPSMS, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "remove factor totp, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeTOTP, ), ), ), @@ -1042,7 +1191,7 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { eventFromEventPusher( instance.NewLoginPolicySecondFactorRemovedEvent(context.Background(), &instance.NewAggregate("INSTANCE").Aggregate, - domain.SecondFactorTypeOTP), + domain.SecondFactorTypeTOTP), ), }, ), @@ -1050,7 +1199,7 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, }, res: res{ want: &domain.ObjectDetails{ @@ -1058,6 +1207,97 @@ func TestCommandSide_RemoveSecondFactorDefaultLoginPolicy(t *testing.T) { }, }, }, + { + name: "remove factor email, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPEmail, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + instance.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPEmail), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPEmail, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "remove factor sms, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPSMS, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + instance.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeOTPSMS), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "INSTANCE", + }, + }, + }, + { + name: "factor added totp, removed otp sms, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + instance.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &instance.NewAggregate("INSTANCE").Aggregate, + domain.SecondFactorTypeTOTP, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/command/org_policy_login_test.go b/internal/command/org_policy_login_test.go index f7d1accfa5..ebc96ac419 100644 --- a/internal/command/org_policy_login_test.go +++ b/internal/command/org_policy_login_test.go @@ -231,7 +231,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { eventFromEventPusher( org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), eventFromEventPusher( @@ -265,7 +265,7 @@ func TestCommandSide_AddLoginPolicy(t *testing.T) { MFAInitSkipLifetime: time.Hour * 3, SecondFactorCheckLifetime: time.Hour * 4, MultiFactorCheckLifetime: time.Hour * 5, - SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeOTP}, + SecondFactors: []domain.SecondFactorType{domain.SecondFactorTypeTOTP}, MultiFactors: []domain.MultiFactorType{domain.MultiFactorTypeU2FWithPIN}, }, }, @@ -1504,7 +1504,7 @@ func TestCommandSide_AddSecondFactorLoginPolicy(t *testing.T) { eventFromEventPusher( org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), ), @@ -1512,7 +1512,7 @@ func TestCommandSide_AddSecondFactorLoginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, resourceOwner: "org1", }, res: res{ @@ -1520,7 +1520,7 @@ func TestCommandSide_AddSecondFactorLoginPolicy(t *testing.T) { }, }, { - name: "add factor, ok", + name: "add factor totp, ok", fields: fields{ eventstore: eventstoreExpect( t, @@ -1530,7 +1530,7 @@ func TestCommandSide_AddSecondFactorLoginPolicy(t *testing.T) { eventFromEventPusher( org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP), + domain.SecondFactorTypeTOTP), ), }, ), @@ -1538,11 +1538,96 @@ func TestCommandSide_AddSecondFactorLoginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, resourceOwner: "org1", }, res: res{ - want: domain.SecondFactorTypeOTP, + want: domain.SecondFactorTypeTOTP, + }, + }, + { + name: "add factor otp email, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPEmail), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPEmail, + resourceOwner: "org1", + }, + res: res{ + want: domain.SecondFactorTypeOTPEmail, + }, + }, + { + name: "add factor otp sms, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPSMS), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, + resourceOwner: "org1", + }, + res: res{ + want: domain.SecondFactorTypeOTPSMS, + }, + }, + { + name: "add factor totp, add otp sms, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeTOTP, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPSMS), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, + resourceOwner: "org1", + }, + res: res{ + want: domain.SecondFactorTypeOTPSMS, }, }, } @@ -1593,7 +1678,7 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, }, res: res{ err: caos_errs.IsErrorInvalidArgument, @@ -1624,7 +1709,7 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, resourceOwner: "org1", }, res: res{ @@ -1632,7 +1717,7 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { }, }, { - name: "factor removed, not found error", + name: "factor totp removed, not found error", fields: fields{ eventstore: eventstoreExpect( t, @@ -1640,13 +1725,13 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { eventFromEventPusher( org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), eventFromEventPusher( org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeTOTP, ), ), ), @@ -1654,7 +1739,7 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, resourceOwner: "org1", }, res: res{ @@ -1662,7 +1747,7 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { }, }, { - name: "add factor, ok", + name: "factor otp email removed, not found error", fields: fields{ eventstore: eventstoreExpect( t, @@ -1670,7 +1755,67 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { eventFromEventPusher( org.NewLoginPolicySecondFactorAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP, + domain.SecondFactorTypeOTPEmail, + ), + ), + eventFromEventPusher( + org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPEmail, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPEmail, + resourceOwner: "org1", + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "factor otp sms removed, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPSMS, + ), + ), + eventFromEventPusher( + org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPSMS, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, + resourceOwner: "org1", + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "add factor totp, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeTOTP, ), ), ), @@ -1679,7 +1824,7 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { eventFromEventPusher( org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, - domain.SecondFactorTypeOTP), + domain.SecondFactorTypeTOTP), ), }, ), @@ -1687,7 +1832,77 @@ func TestCommandSide_RemoveSecondFactoroginPolicy(t *testing.T) { }, args: args{ ctx: context.Background(), - factor: domain.SecondFactorTypeOTP, + factor: domain.SecondFactorTypeTOTP, + resourceOwner: "org1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "add factor otp email, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPEmail, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPEmail), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPEmail, + resourceOwner: "org1", + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "org1", + }, + }, + }, + { + name: "add factor otp sms, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + org.NewLoginPolicySecondFactorAddedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPSMS, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + org.NewLoginPolicySecondFactorRemovedEvent(context.Background(), + &org.NewAggregate("org1").Aggregate, + domain.SecondFactorTypeOTPSMS), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + factor: domain.SecondFactorTypeOTPSMS, resourceOwner: "org1", }, res: res{ diff --git a/internal/domain/factors.go b/internal/domain/factors.go index 172b20c665..41f1a9f8c4 100644 --- a/internal/domain/factors.go +++ b/internal/domain/factors.go @@ -4,20 +4,14 @@ type SecondFactorType int32 const ( SecondFactorTypeUnspecified SecondFactorType = iota - SecondFactorTypeOTP + SecondFactorTypeTOTP SecondFactorTypeU2F + SecondFactorTypeOTPEmail + SecondFactorTypeOTPSMS secondFactorCount ) -func SecondFactorTypes() []SecondFactorType { - types := make([]SecondFactorType, 0, secondFactorCount-1) - for i := SecondFactorTypeUnspecified + 1; i < secondFactorCount; i++ { - types = append(types, i) - } - return types -} - type MultiFactorType int32 const ( @@ -27,14 +21,6 @@ const ( multiFactorCount ) -func MultiFactorTypes() []MultiFactorType { - types := make([]MultiFactorType, 0, multiFactorCount-1) - for i := MultiFactorTypeUnspecified + 1; i < multiFactorCount; i++ { - types = append(types, i) - } - return types -} - type FactorState int32 const ( diff --git a/internal/iam/model/login_policy_view.go b/internal/iam/model/login_policy_view.go index 7a39818b9c..c4aaafa828 100644 --- a/internal/iam/model/login_policy_view.go +++ b/internal/iam/model/login_policy_view.go @@ -106,8 +106,12 @@ func secondFactorsToDomain(types []domain.SecondFactorType) []domain.SecondFacto switch secondfactorType { case domain.SecondFactorTypeU2F: secondfactors[i] = domain.SecondFactorTypeU2F - case domain.SecondFactorTypeOTP: - secondfactors[i] = domain.SecondFactorTypeOTP + case domain.SecondFactorTypeTOTP: + secondfactors[i] = domain.SecondFactorTypeTOTP + case domain.SecondFactorTypeOTPEmail: + secondfactors[i] = domain.SecondFactorTypeOTPEmail + case domain.SecondFactorTypeOTPSMS: + secondfactors[i] = domain.SecondFactorTypeOTPSMS } } return secondfactors diff --git a/internal/query/login_policy_test.go b/internal/query/login_policy_test.go index 2e1900f218..9aa398d81d 100644 --- a/internal/query/login_policy_test.go +++ b/internal/query/login_policy_test.go @@ -129,7 +129,7 @@ func Test_LoginPolicyPrepares(t *testing.T) { true, true, true, - database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeOTP}, + database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeTOTP}, database.EnumArray[domain.MultiFactorType]{domain.MultiFactorTypeU2FWithPIN}, domain.PasswordlessTypeAllowed, true, @@ -157,7 +157,7 @@ func Test_LoginPolicyPrepares(t *testing.T) { AllowExternalIDPs: true, ForceMFA: true, ForceMFALocalOnly: true, - SecondFactors: database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeOTP}, + SecondFactors: database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeTOTP}, MultiFactors: database.EnumArray[domain.MultiFactorType]{domain.MultiFactorTypeU2FWithPIN}, PasswordlessType: domain.PasswordlessTypeAllowed, IsDefault: true, @@ -217,7 +217,7 @@ func Test_LoginPolicyPrepares(t *testing.T) { regexp.QuoteMeta(prepareLoginPolicy2FAsStmt), prepareLoginPolicy2FAsCols, []driver.Value{ - database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeOTP}, + database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeTOTP}, }, ), }, @@ -225,7 +225,7 @@ func Test_LoginPolicyPrepares(t *testing.T) { SearchResponse: SearchResponse{ Count: 1, }, - Factors: database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeOTP}, + Factors: database.EnumArray[domain.SecondFactorType]{domain.SecondFactorTypeTOTP}, }, }, { diff --git a/internal/query/projection/login_policy.go b/internal/query/projection/login_policy.go index 77fadc2ba8..856d02a464 100644 --- a/internal/query/projection/login_policy.go +++ b/internal/query/projection/login_policy.go @@ -115,11 +115,11 @@ func (p *loginPolicyProjection) reducers() []handler.AggregateReducer { }, { Event: org.LoginPolicySecondFactorAddedEventType, - Reduce: p.reduce2FAAdded, + Reduce: p.reduceSecondFactorAdded, }, { Event: org.LoginPolicySecondFactorRemovedEventType, - Reduce: p.reduce2FARemoved, + Reduce: p.reduceSecondFactorRemoved, }, { Event: org.OrgRemovedEventType, @@ -148,11 +148,11 @@ func (p *loginPolicyProjection) reducers() []handler.AggregateReducer { }, { Event: instance.LoginPolicySecondFactorAddedEventType, - Reduce: p.reduce2FAAdded, + Reduce: p.reduceSecondFactorAdded, }, { Event: instance.LoginPolicySecondFactorRemovedEventType, - Reduce: p.reduce2FARemoved, + Reduce: p.reduceSecondFactorRemoved, }, { Event: instance.InstanceRemovedEventType, @@ -345,7 +345,7 @@ func (p *loginPolicyProjection) reduceLoginPolicyRemoved(event eventstore.Event) ), nil } -func (p *loginPolicyProjection) reduce2FAAdded(event eventstore.Event) (*handler.Statement, error) { +func (p *loginPolicyProjection) reduceSecondFactorAdded(event eventstore.Event) (*handler.Statement, error) { var policyEvent policy.SecondFactorAddedEvent switch e := event.(type) { case *instance.LoginPolicySecondFactorAddedEvent: @@ -370,7 +370,7 @@ func (p *loginPolicyProjection) reduce2FAAdded(event eventstore.Event) (*handler ), nil } -func (p *loginPolicyProjection) reduce2FARemoved(event eventstore.Event) (*handler.Statement, error) { +func (p *loginPolicyProjection) reduceSecondFactorRemoved(event eventstore.Event) (*handler.Statement, error) { var policyEvent policy.SecondFactorRemovedEvent switch e := event.(type) { case *instance.LoginPolicySecondFactorRemovedEvent: diff --git a/internal/query/projection/login_policy_test.go b/internal/query/projection/login_policy_test.go index bd87c5c4b0..b475fa2a28 100644 --- a/internal/query/projection/login_policy_test.go +++ b/internal/query/projection/login_policy_test.go @@ -310,8 +310,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { }, }, { - name: "org reduce2FAAdded", - reduce: (&loginPolicyProjection{}).reduce2FAAdded, + name: "org reduceSecondFactorAdded", + reduce: (&loginPolicyProjection{}).reduceSecondFactorAdded, args: args{ event: getEvent(testEvent( repository.EventType(org.LoginPolicySecondFactorAddedEventType), @@ -342,8 +342,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { }, }, { - name: "org reduce2FARemoved", - reduce: (&loginPolicyProjection{}).reduce2FARemoved, + name: "org reduceSecondFactorRemoved", + reduce: (&loginPolicyProjection{}).reduceSecondFactorRemoved, args: args{ event: getEvent(testEvent( repository.EventType(org.LoginPolicySecondFactorRemovedEventType), @@ -558,8 +558,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { }, }, { - name: "instance reduce2FAAdded", - reduce: (&loginPolicyProjection{}).reduce2FAAdded, + name: "instance reduceSecondFactorAdded u2f", + reduce: (&loginPolicyProjection{}).reduceSecondFactorAdded, args: args{ event: getEvent(testEvent( repository.EventType(instance.LoginPolicySecondFactorAddedEventType), @@ -590,8 +590,8 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { }, }, { - name: "instance reduce2FARemoved", - reduce: (&loginPolicyProjection{}).reduce2FARemoved, + name: "instance reduceSecondFactorRemoved u2f", + reduce: (&loginPolicyProjection{}).reduceSecondFactorRemoved, args: args{ event: getEvent(testEvent( repository.EventType(instance.LoginPolicySecondFactorRemovedEventType), @@ -621,6 +621,70 @@ func TestLoginPolicyProjection_reduces(t *testing.T) { }, }, }, + { + name: "instance reduceSecondFactorAdded otp email", + reduce: (&loginPolicyProjection{}).reduceSecondFactorAdded, + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.LoginPolicySecondFactorAddedEventType), + instance.AggregateType, + []byte(`{ + "mfaType": 3 + }`), + ), instance.SecondFactorAddedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, second_factors) = ($1, $2, array_append(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SecondFactorTypeOTPEmail, + "agg-id", + "instance-id", + }, + }, + }, + }, + }, + }, + { + name: "instance reduceSecondFactorRemoved otp email", + reduce: (&loginPolicyProjection{}).reduceSecondFactorRemoved, + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.LoginPolicySecondFactorRemovedEventType), + instance.AggregateType, + []byte(`{ + "mfaType": 3 + }`), + ), instance.SecondFactorRemovedEventMapper), + }, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.login_policies5 SET (change_date, sequence, second_factors) = ($1, $2, array_remove(second_factors, $3)) WHERE (aggregate_id = $4) AND (instance_id = $5)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + domain.SecondFactorTypeOTPEmail, + "agg-id", + "instance-id", + }, + }, + }, + }, + }, + }, { name: "org.reduceOwnerRemoved", reduce: (&loginPolicyProjection{}).reduceOwnerRemoved, diff --git a/internal/user/model/user_view.go b/internal/user/model/user_view.go index 8cb0d4346b..e2378a44ed 100644 --- a/internal/user/model/user_view.go +++ b/internal/user/model/user_view.go @@ -156,7 +156,7 @@ func (u *UserView) MFATypesSetupPossible(level domain.MFALevel, policy *domain.L if policy.HasSecondFactors() { for _, mfaType := range policy.SecondFactors { switch mfaType { - case domain.SecondFactorTypeOTP: + case domain.SecondFactorTypeTOTP: if u.OTPState != MFAStateReady { types = append(types, domain.MFATypeOTP) } @@ -181,7 +181,7 @@ func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPo if policy.HasSecondFactors() { for _, mfaType := range policy.SecondFactors { switch mfaType { - case domain.SecondFactorTypeOTP: + case domain.SecondFactorTypeTOTP: if u.OTPState == MFAStateReady { types = append(types, domain.MFATypeOTP) } diff --git a/proto/zitadel/policy.proto b/proto/zitadel/policy.proto index 49dbe34dc3..4b3aa9035e 100644 --- a/proto/zitadel/policy.proto +++ b/proto/zitadel/policy.proto @@ -248,8 +248,11 @@ message LoginPolicy { enum SecondFactorType { SECOND_FACTOR_TYPE_UNSPECIFIED = 0; + // SECOND_FACTOR_TYPE_OTP is the type for TOTP SECOND_FACTOR_TYPE_OTP = 1; SECOND_FACTOR_TYPE_U2F = 2; + SECOND_FACTOR_TYPE_OTP_EMAIL = 3; + SECOND_FACTOR_TYPE_OTP_SMS = 4; } enum MultiFactorType { diff --git a/proto/zitadel/settings/v2alpha/login_settings.proto b/proto/zitadel/settings/v2alpha/login_settings.proto index 9d4b37237c..057d076ce0 100644 --- a/proto/zitadel/settings/v2alpha/login_settings.proto +++ b/proto/zitadel/settings/v2alpha/login_settings.proto @@ -113,8 +113,11 @@ message LoginSettings { enum SecondFactorType { SECOND_FACTOR_TYPE_UNSPECIFIED = 0; + // This is the type for TOTP SECOND_FACTOR_TYPE_OTP = 1; SECOND_FACTOR_TYPE_U2F = 2; + SECOND_FACTOR_TYPE_OTP_EMAIL = 3; + SECOND_FACTOR_TYPE_OTP_SMS = 4; } enum MultiFactorType {