mirror of
https://github.com/zitadel/zitadel.git
synced 2025-05-22 15:48:19 +00:00
feat(login): add OTP (email and sms) (#6353)
* feat: login with otp * fix(i18n): japanese translation * add missing files * fix provider change * add event types translations to en * add tests * resourceOwner * remove unused handler * fix: secret generators and add comments * add setup step * rename * linting * fix setup * improve otp handling * fix autocomplete * translations for login and notifications * translations for event types * changes from review * check selected mfa type
This commit is contained in:
parent
faa9ed4de9
commit
7c494fd219
26
cmd/setup/12.go
Normal file
26
cmd/setup/12.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package setup
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//go:embed 12/12_add_otp_columns.sql
|
||||||
|
addOTPColumns string
|
||||||
|
)
|
||||||
|
|
||||||
|
type AddOTPColumns struct {
|
||||||
|
dbClient *database.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *AddOTPColumns) Execute(ctx context.Context) error {
|
||||||
|
_, err := mig.dbClient.ExecContext(ctx, addOTPColumns)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mig *AddOTPColumns) String() string {
|
||||||
|
return "12_auth_users_otp_columns"
|
||||||
|
}
|
2
cmd/setup/12/12_add_otp_columns.sql
Normal file
2
cmd/setup/12/12_add_otp_columns.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE auth.users2 ADD COLUMN otp_sms_added BOOL DEFAULT false;
|
||||||
|
ALTER TABLE auth.users2 ADD COLUMN otp_email_added BOOL DEFAULT false;
|
@ -67,6 +67,7 @@ type Steps struct {
|
|||||||
s9EventstoreIndexes2 *EventstoreIndexesNew
|
s9EventstoreIndexes2 *EventstoreIndexesNew
|
||||||
CorrectCreationDate *CorrectCreationDate
|
CorrectCreationDate *CorrectCreationDate
|
||||||
AddEventCreatedAt *AddEventCreatedAt
|
AddEventCreatedAt *AddEventCreatedAt
|
||||||
|
s12AddOTPColumns *AddOTPColumns
|
||||||
}
|
}
|
||||||
|
|
||||||
type encryptionKeyConfig struct {
|
type encryptionKeyConfig struct {
|
||||||
|
@ -94,6 +94,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
|||||||
steps.CorrectCreationDate.dbClient = dbClient
|
steps.CorrectCreationDate.dbClient = dbClient
|
||||||
steps.AddEventCreatedAt.dbClient = dbClient
|
steps.AddEventCreatedAt.dbClient = dbClient
|
||||||
steps.AddEventCreatedAt.step10 = steps.CorrectCreationDate
|
steps.AddEventCreatedAt.step10 = steps.CorrectCreationDate
|
||||||
|
steps.s12AddOTPColumns = &AddOTPColumns{dbClient: dbClient}
|
||||||
|
|
||||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil)
|
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil)
|
||||||
logging.OnError(err).Fatal("unable to start projections")
|
logging.OnError(err).Fatal("unable to start projections")
|
||||||
@ -134,6 +135,8 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
|||||||
logging.OnError(err).Fatal("unable to migrate step 10")
|
logging.OnError(err).Fatal("unable to migrate step 10")
|
||||||
err = migration.Migrate(ctx, eventstoreClient, steps.AddEventCreatedAt)
|
err = migration.Migrate(ctx, eventstoreClient, steps.AddEventCreatedAt)
|
||||||
logging.OnError(err).Fatal("unable to migrate step 11")
|
logging.OnError(err).Fatal("unable to migrate step 11")
|
||||||
|
err = migration.Migrate(ctx, eventstoreClient, steps.s12AddOTPColumns)
|
||||||
|
logging.OnError(err).Fatal("unable to migrate step 12")
|
||||||
|
|
||||||
for _, repeatableStep := range repeatableSteps {
|
for _, repeatableStep := range repeatableSteps {
|
||||||
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
err = migration.Migrate(ctx, eventstoreClient, repeatableStep)
|
||||||
|
@ -32,8 +32,8 @@ export class DialogAddSecretGeneratorComponent {
|
|||||||
expiry: [exp, [requiredValidator]],
|
expiry: [exp, [requiredValidator]],
|
||||||
length: [data.config?.length ?? 6, [requiredValidator]],
|
length: [data.config?.length ?? 6, [requiredValidator]],
|
||||||
includeDigits: [data.config?.includeDigits ?? true, [requiredValidator]],
|
includeDigits: [data.config?.includeDigits ?? true, [requiredValidator]],
|
||||||
includeLowerLetters: [data.config?.includeSymbols ?? true, [requiredValidator]],
|
includeSymbols: [data.config?.includeSymbols ?? true, [requiredValidator]],
|
||||||
includeSymbols: [data.config?.includeLowerLetters ?? true, [requiredValidator]],
|
includeLowerLetters: [data.config?.includeLowerLetters ?? true, [requiredValidator]],
|
||||||
includeUpperLetters: [data.config?.includeUpperLetters ?? true, [requiredValidator]],
|
includeUpperLetters: [data.config?.includeUpperLetters ?? true, [requiredValidator]],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,7 @@ func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRe
|
|||||||
|
|
||||||
func (s *Server) ResendMyPhoneVerification(ctx context.Context, _ *auth_pb.ResendMyPhoneVerificationRequest) (*auth_pb.ResendMyPhoneVerificationResponse, error) {
|
func (s *Server) ResendMyPhoneVerification(ctx context.Context, _ *auth_pb.ResendMyPhoneVerificationRequest) (*auth_pb.ResendMyPhoneVerificationResponse, error) {
|
||||||
ctxData := authz.GetCtxData(ctx)
|
ctxData := authz.GetCtxData(ctx)
|
||||||
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg)
|
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, ctxData.UserID, ctxData.ResourceOwner, phoneCodeGenerator)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -549,11 +549,7 @@ func (s *Server) RemoveHumanPhone(ctx context.Context, req *mgmt_pb.RemoveHumanP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.ResendHumanPhoneVerificationRequest) (*mgmt_pb.ResendHumanPhoneVerificationResponse, error) {
|
func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.ResendHumanPhoneVerificationRequest) (*mgmt_pb.ResendHumanPhoneVerificationResponse, error) {
|
||||||
phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg)
|
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
objectDetails, err := s.command.CreateHumanPhoneVerificationCode(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, phoneCodeGenerator)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -261,7 +261,9 @@ func CodeChallengeToOIDC(challenge *domain.OIDCCodeChallenge) *oidc.CodeChalleng
|
|||||||
|
|
||||||
func AMRFromMFAType(mfaType domain.MFAType) string {
|
func AMRFromMFAType(mfaType domain.MFAType) string {
|
||||||
switch mfaType {
|
switch mfaType {
|
||||||
case domain.MFATypeTOTP:
|
case domain.MFATypeTOTP,
|
||||||
|
domain.MFATypeOTPSMS,
|
||||||
|
domain.MFATypeOTPEmail:
|
||||||
return OTP
|
return OTP
|
||||||
case domain.MFATypeU2F,
|
case domain.MFATypeU2F,
|
||||||
domain.MFATypeU2FUserVerification:
|
domain.MFATypeU2FUserVerification:
|
||||||
|
@ -149,6 +149,8 @@ type authMethod string
|
|||||||
const (
|
const (
|
||||||
authMethodPassword authMethod = "password"
|
authMethodPassword authMethod = "password"
|
||||||
authMethodOTP authMethod = "OTP"
|
authMethodOTP authMethod = "OTP"
|
||||||
|
authMethodOTPSMS authMethod = "OTP SMS"
|
||||||
|
authMethodOTPEmail authMethod = "OTP Email"
|
||||||
authMethodU2F authMethod = "U2F"
|
authMethodU2F authMethod = "U2F"
|
||||||
authMethodPasswordless authMethod = "passwordless"
|
authMethodPasswordless authMethod = "passwordless"
|
||||||
)
|
)
|
||||||
|
@ -171,6 +171,14 @@ func setContext(ctx context.Context, resourceOwner string) context.Context {
|
|||||||
return authz.SetCtxData(ctx, data)
|
return authz.SetCtxData(ctx, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setUserContext(ctx context.Context, userID, resourceOwner string) context.Context {
|
||||||
|
data := authz.CtxData{
|
||||||
|
UserID: userID,
|
||||||
|
OrgID: resourceOwner,
|
||||||
|
}
|
||||||
|
return authz.SetCtxData(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
func (l *Login) baseURL(ctx context.Context) string {
|
func (l *Login) baseURL(ctx context.Context) string {
|
||||||
return http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure) + HandlerPrefix
|
return http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure) + HandlerPrefix
|
||||||
}
|
}
|
||||||
|
125
internal/api/ui/login/mfa_init_sms.go
Normal file
125
internal/api/ui/login/mfa_init_sms.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tmplMFASMSInit = "mfainitsms"
|
||||||
|
)
|
||||||
|
|
||||||
|
type smsInitData struct {
|
||||||
|
userData
|
||||||
|
Edit bool
|
||||||
|
MFAType domain.MFAType
|
||||||
|
Phone string
|
||||||
|
}
|
||||||
|
|
||||||
|
type smsInitFormData struct {
|
||||||
|
Edit bool `schema:"edit"`
|
||||||
|
Resend bool `schema:"resend"`
|
||||||
|
Phone string `schema:"phone"`
|
||||||
|
NewPhone string `schema:"newPhone"`
|
||||||
|
Code string `schema:"code"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRegisterOTPSMS checks if the user has a verified phone number and will directly add OTP SMS as 2FA.
|
||||||
|
// It will also add a successful OTP SMS check to the auth request.
|
||||||
|
// If there's no verified phone number, the potential last phone number will be used to render the registration page
|
||||||
|
func (l *Login) handleRegisterOTPSMS(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
|
||||||
|
user, err := l.query.GetNotifyUserByID(r.Context(), true, authReq.UserID, false)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if user.VerifiedPhone == "" {
|
||||||
|
data := new(smsInitData)
|
||||||
|
data.Phone = user.LastPhone
|
||||||
|
data.Edit = user.LastPhone == ""
|
||||||
|
l.renderRegisterSMS(w, r, authReq, data, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = l.command.AddHumanOTPSMSWithCheckSucceeded(setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done := &mfaDoneData{
|
||||||
|
MFAType: domain.MFATypeOTPSMS,
|
||||||
|
}
|
||||||
|
l.renderMFAInitDone(w, r, authReq, done)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) renderRegisterSMS(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, data *smsInitData, err error) {
|
||||||
|
var errID, errMessage string
|
||||||
|
if err != nil {
|
||||||
|
errID, errMessage = l.getErrorMessage(r, err)
|
||||||
|
}
|
||||||
|
data.baseData = l.getBaseData(r, authReq, "InitMFAOTP.Title", "InitMFAOTP.Description", errID, errMessage)
|
||||||
|
data.profileData = l.getProfileData(authReq)
|
||||||
|
data.MFAType = domain.MFATypeOTPSMS
|
||||||
|
l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), l.renderer.Templates[tmplMFASMSInit], data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleRegisterSMSCheck handles form submissions of the SMS registration.
|
||||||
|
// The user can be either in edit mode, where a phone number can be entered / changed.
|
||||||
|
// If a phone was set, the user can either switch to edit mode, have a resend of the code or verify the code by entering it.
|
||||||
|
// On successful code verification, the phone will be added to the user as well as his MFA
|
||||||
|
// and a successful OTP SMS check will be added to the auth request.
|
||||||
|
func (l *Login) handleRegisterSMSCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
|
formData := new(smsInitFormData)
|
||||||
|
authReq, err := l.getAuthRequestAndParseData(r, formData)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID)
|
||||||
|
// save the current state
|
||||||
|
data := &smsInitData{Phone: formData.Phone}
|
||||||
|
|
||||||
|
if formData.Edit {
|
||||||
|
data.Edit = true
|
||||||
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if formData.Resend {
|
||||||
|
_, err = l.command.CreateHumanPhoneVerificationCode(ctx, authReq.UserID, authReq.UserOrgID)
|
||||||
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the user is currently in edit mode,
|
||||||
|
// he can either change the phone number
|
||||||
|
// or just return to the code verification again
|
||||||
|
if formData.Code == "" {
|
||||||
|
data.Phone = formData.NewPhone
|
||||||
|
if formData.NewPhone != formData.Phone {
|
||||||
|
_, err = l.command.ChangeUserPhone(ctx, authReq.UserID, authReq.UserOrgID, formData.NewPhone, l.userCodeAlg)
|
||||||
|
if err != nil {
|
||||||
|
// stay in edit more
|
||||||
|
data.Edit = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = l.command.VerifyUserPhone(ctx, authReq.UserID, authReq.UserOrgID, formData.Code, l.userCodeAlg)
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = l.command.AddHumanOTPSMSWithCheckSucceeded(ctx, authReq.UserID, authReq.UserOrgID, authReq)
|
||||||
|
if err != nil {
|
||||||
|
l.renderRegisterSMS(w, r, authReq, data, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done := &mfaDoneData{
|
||||||
|
MFAType: domain.MFATypeOTPSMS,
|
||||||
|
}
|
||||||
|
l.renderMFAInitDone(w, r, authReq, done)
|
||||||
|
}
|
@ -83,6 +83,12 @@ func (l *Login) handleMFACreation(w http.ResponseWriter, r *http.Request, authRe
|
|||||||
case domain.MFATypeTOTP:
|
case domain.MFATypeTOTP:
|
||||||
l.handleTOTPCreation(w, r, authReq, data)
|
l.handleTOTPCreation(w, r, authReq, data)
|
||||||
return
|
return
|
||||||
|
case domain.MFATypeOTPSMS:
|
||||||
|
l.handleRegisterOTPSMS(w, r, authReq)
|
||||||
|
return
|
||||||
|
case domain.MFATypeOTPEmail:
|
||||||
|
l.handleRegisterOTPEmail(w, r, authReq)
|
||||||
|
return
|
||||||
case domain.MFATypeU2F:
|
case domain.MFATypeU2F:
|
||||||
l.renderRegisterU2F(w, r, authReq, nil)
|
l.renderRegisterU2F(w, r, authReq, nil)
|
||||||
return
|
return
|
||||||
@ -103,3 +109,17 @@ func (l *Login) handleTOTPCreation(w http.ResponseWriter, r *http.Request, authR
|
|||||||
}
|
}
|
||||||
l.renderMFAInitVerify(w, r, authReq, data, nil)
|
l.renderMFAInitVerify(w, r, authReq, data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleRegisterOTPEmail will directly add OTP Email as 2FA.
|
||||||
|
// It will also add a successful OTP Email check to the auth request.
|
||||||
|
func (l *Login) handleRegisterOTPEmail(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
|
||||||
|
_, err := l.command.AddHumanOTPEmailWithCheckSucceeded(setUserContext(r.Context(), authReq.UserID, authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
done := &mfaDoneData{
|
||||||
|
MFAType: domain.MFATypeOTPEmail,
|
||||||
|
}
|
||||||
|
l.renderMFAInitDone(w, r, authReq, done)
|
||||||
|
}
|
||||||
|
@ -84,6 +84,12 @@ func (l *Login) renderMFAVerifySelected(w http.ResponseWriter, r *http.Request,
|
|||||||
data.SelectedMFAProvider = domain.MFATypeTOTP
|
data.SelectedMFAProvider = domain.MFATypeTOTP
|
||||||
data.Title = translator.LocalizeWithoutArgs("VerifyMFAOTP.Title")
|
data.Title = translator.LocalizeWithoutArgs("VerifyMFAOTP.Title")
|
||||||
data.Description = translator.LocalizeWithoutArgs("VerifyMFAOTP.Description")
|
data.Description = translator.LocalizeWithoutArgs("VerifyMFAOTP.Description")
|
||||||
|
case domain.MFATypeOTPSMS:
|
||||||
|
l.handleOTPVerification(w, r, authReq, verificationStep.MFAProviders, domain.MFATypeOTPSMS, nil)
|
||||||
|
return
|
||||||
|
case domain.MFATypeOTPEmail:
|
||||||
|
l.handleOTPVerification(w, r, authReq, verificationStep.MFAProviders, domain.MFATypeOTPEmail, nil)
|
||||||
|
return
|
||||||
default:
|
default:
|
||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
|
126
internal/api/ui/login/mfa_verify_otp_handler.go
Normal file
126
internal/api/ui/login/mfa_verify_otp_handler.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
package login
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
http_mw "github.com/zitadel/zitadel/internal/api/http/middleware"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tmplOTPVerification = "otpverification"
|
||||||
|
querySelectedProvider = "selectedProvider"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mfaOTPData struct {
|
||||||
|
userData
|
||||||
|
MFAProviders []domain.MFAType
|
||||||
|
SelectedProvider domain.MFAType
|
||||||
|
}
|
||||||
|
|
||||||
|
type mfaOTPFormData struct {
|
||||||
|
Resend bool `schema:"resend"`
|
||||||
|
Code string `schema:"code"`
|
||||||
|
SelectedProvider domain.MFAType `schema:"selectedProvider"`
|
||||||
|
Provider domain.MFAType `schema:"provider"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func OTPLink(origin, authRequestID, code string, provider domain.MFAType) string {
|
||||||
|
return fmt.Sprintf("%s%s?%s=%s&%s=%s&%s=%d", externalLink(origin), EndpointMFAOTPVerify, QueryAuthRequestID, authRequestID, queryCode, code, querySelectedProvider, provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// renderOTPVerification renders the OTP verification for SMS and Email based on the passed MFAType.
|
||||||
|
// It will send a new code to either phone or email first.
|
||||||
|
func (l *Login) handleOTPVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, selectedProvider domain.MFAType, err error) {
|
||||||
|
if err != nil {
|
||||||
|
l.renderOTPVerification(w, r, authReq, providers, selectedProvider, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
var sendCode func(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) error
|
||||||
|
switch selectedProvider {
|
||||||
|
case domain.MFATypeOTPSMS:
|
||||||
|
sendCode = l.authRepo.SendMFAOTPSMS
|
||||||
|
case domain.MFATypeOTPEmail:
|
||||||
|
sendCode = l.authRepo.SendMFAOTPEmail
|
||||||
|
// another type should never be passed, but just making sure
|
||||||
|
case domain.MFATypeU2F,
|
||||||
|
domain.MFATypeTOTP,
|
||||||
|
domain.MFATypeU2FUserVerification:
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = sendCode(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, authReq.ID, userAgentID)
|
||||||
|
l.renderOTPVerification(w, r, authReq, providers, selectedProvider, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Login) renderOTPVerification(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, providers []domain.MFAType, selectedProvider domain.MFAType, err error) {
|
||||||
|
var errID, errMessage string
|
||||||
|
if err != nil {
|
||||||
|
errID, errMessage = l.getErrorMessage(r, err)
|
||||||
|
}
|
||||||
|
data := &mfaOTPData{
|
||||||
|
userData: l.getUserData(r, authReq, "VerifyMFAU2F.Title", "VerifyMFAU2F.Description", errID, errMessage),
|
||||||
|
MFAProviders: removeSelectedProviderFromList(providers, selectedProvider),
|
||||||
|
SelectedProvider: selectedProvider,
|
||||||
|
}
|
||||||
|
l.renderer.RenderTemplate(w, r, l.getTranslator(r.Context(), authReq), l.renderer.Templates[tmplOTPVerification], data, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleOTPVerificationCheck handles form submissions of the OTP verification.
|
||||||
|
// On successful code verification, the check will be added to the auth request.
|
||||||
|
// A user is also able to request a code resend or choose another provider.
|
||||||
|
func (l *Login) handleOTPVerificationCheck(w http.ResponseWriter, r *http.Request) {
|
||||||
|
formData := new(mfaOTPFormData)
|
||||||
|
authReq, err := l.getAuthRequestAndParseData(r, formData)
|
||||||
|
if err != nil {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
step, ok := authReq.PossibleSteps[0].(*domain.MFAVerificationStep)
|
||||||
|
if !ok {
|
||||||
|
l.renderError(w, r, authReq, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if formData.Resend {
|
||||||
|
l.handleOTPVerification(w, r, authReq, step.MFAProviders, formData.SelectedProvider, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if formData.Code == "" {
|
||||||
|
l.renderMFAVerifySelected(w, r, authReq, step, formData.Provider, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())
|
||||||
|
var actionType authMethod
|
||||||
|
var verifyCode func(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) error
|
||||||
|
switch formData.SelectedProvider {
|
||||||
|
case domain.MFATypeOTPSMS:
|
||||||
|
actionType = authMethodOTPSMS
|
||||||
|
verifyCode = l.authRepo.VerifyMFAOTPSMS
|
||||||
|
case domain.MFATypeOTPEmail:
|
||||||
|
actionType = authMethodOTPEmail
|
||||||
|
verifyCode = l.authRepo.VerifyMFAOTPEmail
|
||||||
|
// another type should never be passed, but just making sure
|
||||||
|
case domain.MFATypeU2F,
|
||||||
|
domain.MFATypeTOTP,
|
||||||
|
domain.MFATypeU2FUserVerification:
|
||||||
|
l.renderOTPVerification(w, r, authReq, step.MFAProviders, formData.SelectedProvider, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = verifyCode(setContext(r.Context(), authReq.UserOrgID), authReq.UserID, authReq.UserOrgID, formData.Code, authReq.ID, userAgentID, domain.BrowserInfoFromRequest(r))
|
||||||
|
|
||||||
|
metadata, actionErr := l.runPostInternalAuthenticationActions(authReq, r, actionType, err)
|
||||||
|
if err == nil && actionErr == nil && len(metadata) > 0 {
|
||||||
|
_, err = l.command.BulkSetUserMetadata(r.Context(), authReq.UserID, authReq.UserOrgID, metadata...)
|
||||||
|
} else if actionErr != nil && err == nil {
|
||||||
|
err = actionErr
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.renderOTPVerification(w, r, authReq, step.MFAProviders, formData.SelectedProvider, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
l.renderNextStep(w, r, authReq)
|
||||||
|
}
|
@ -54,9 +54,11 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
|
|||||||
tmplPasswordlessRegistration: "passwordless_registration.html",
|
tmplPasswordlessRegistration: "passwordless_registration.html",
|
||||||
tmplPasswordlessRegistrationDone: "passwordless_registration_done.html",
|
tmplPasswordlessRegistrationDone: "passwordless_registration_done.html",
|
||||||
tmplPasswordlessPrompt: "passwordless_prompt.html",
|
tmplPasswordlessPrompt: "passwordless_prompt.html",
|
||||||
tmplMFAVerify: "mfa_verify_otp.html",
|
tmplMFAVerify: "mfa_verify_totp.html",
|
||||||
tmplMFAPrompt: "mfa_prompt.html",
|
tmplMFAPrompt: "mfa_prompt.html",
|
||||||
tmplMFAInitVerify: "mfa_init_otp.html",
|
tmplMFAInitVerify: "mfa_init_otp.html",
|
||||||
|
tmplMFASMSInit: "mfa_init_otp_sms.html",
|
||||||
|
tmplOTPVerification: "mfa_verify_otp.html",
|
||||||
tmplMFAU2FInit: "mfa_init_u2f.html",
|
tmplMFAU2FInit: "mfa_init_u2f.html",
|
||||||
tmplU2FVerification: "mfa_verification_u2f.html",
|
tmplU2FVerification: "mfa_verification_u2f.html",
|
||||||
tmplMFAInitDone: "mfa_init_done.html",
|
tmplMFAInitDone: "mfa_init_done.html",
|
||||||
@ -170,6 +172,12 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
|
|||||||
"mfaInitVerifyUrl": func() string {
|
"mfaInitVerifyUrl": func() string {
|
||||||
return path.Join(r.pathPrefix, EndpointMFAInitVerify)
|
return path.Join(r.pathPrefix, EndpointMFAInitVerify)
|
||||||
},
|
},
|
||||||
|
"mfaInitSMSVerifyUrl": func() string {
|
||||||
|
return path.Join(r.pathPrefix, EndpointMFASMSInitVerify)
|
||||||
|
},
|
||||||
|
"mfaOTPVerifyUrl": func() string {
|
||||||
|
return path.Join(r.pathPrefix, EndpointMFAOTPVerify)
|
||||||
|
},
|
||||||
"mfaInitU2FVerifyUrl": func() string {
|
"mfaInitU2FVerifyUrl": func() string {
|
||||||
return path.Join(r.pathPrefix, EndpointMFAInitU2FVerify)
|
return path.Join(r.pathPrefix, EndpointMFAInitU2FVerify)
|
||||||
},
|
},
|
||||||
|
@ -31,6 +31,8 @@ const (
|
|||||||
EndpointMFAVerify = "/mfa/verify"
|
EndpointMFAVerify = "/mfa/verify"
|
||||||
EndpointMFAPrompt = "/mfa/prompt"
|
EndpointMFAPrompt = "/mfa/prompt"
|
||||||
EndpointMFAInitVerify = "/mfa/init/verify"
|
EndpointMFAInitVerify = "/mfa/init/verify"
|
||||||
|
EndpointMFASMSInitVerify = "/mfa/init/sms/verify"
|
||||||
|
EndpointMFAOTPVerify = "/mfa/otp/verify"
|
||||||
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
EndpointMFAInitU2FVerify = "/mfa/init/u2f/verify"
|
||||||
EndpointU2FVerification = "/mfa/u2f/verify"
|
EndpointU2FVerification = "/mfa/u2f/verify"
|
||||||
EndpointMailVerification = "/mail/verification"
|
EndpointMailVerification = "/mail/verification"
|
||||||
@ -89,6 +91,9 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
|||||||
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPromptSelection).Methods(http.MethodGet)
|
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPromptSelection).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPrompt).Methods(http.MethodPost)
|
router.HandleFunc(EndpointMFAPrompt, login.handleMFAPrompt).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointMFAInitVerify, login.handleMFAInitVerify).Methods(http.MethodPost)
|
router.HandleFunc(EndpointMFAInitVerify, login.handleMFAInitVerify).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc(EndpointMFASMSInitVerify, login.handleRegisterSMSCheck).Methods(http.MethodPost)
|
||||||
|
router.HandleFunc(EndpointMFAOTPVerify, login.handleOTPVerificationCheck).Methods(http.MethodGet)
|
||||||
|
router.HandleFunc(EndpointMFAOTPVerify, login.handleOTPVerificationCheck).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointMFAInitU2FVerify, login.handleRegisterU2F).Methods(http.MethodPost)
|
router.HandleFunc(EndpointMFAInitU2FVerify, login.handleRegisterU2F).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointU2FVerification, login.handleU2FVerification).Methods(http.MethodPost)
|
router.HandleFunc(EndpointU2FVerification, login.handleU2FVerification).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)
|
router.HandleFunc(EndpointMailVerification, login.handleMailVerification).Methods(http.MethodGet)
|
||||||
|
@ -85,6 +85,8 @@ InitMFAPrompt:
|
|||||||
потребителски акаунт.
|
потребителски акаунт.
|
||||||
Provider0: 'Приложение за удостоверяване (напр. Google/Microsoft Authenticator, Authy)'
|
Provider0: 'Приложение за удостоверяване (напр. Google/Microsoft Authenticator, Authy)'
|
||||||
Provider1: 'Зависи от устройството (напр. FaceID, Windows Hello, пръстов отпечатък)'
|
Provider1: 'Зависи от устройството (напр. FaceID, Windows Hello, пръстов отпечатък)'
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP имейл
|
||||||
NextButtonText: следващия
|
NextButtonText: следващия
|
||||||
SkipButtonText: пропуснете
|
SkipButtonText: пропуснете
|
||||||
InitMFAOTP:
|
InitMFAOTP:
|
||||||
@ -98,6 +100,15 @@ InitMFAOTP:
|
|||||||
CodeLabel: Код
|
CodeLabel: Код
|
||||||
NextButtonText: следващия
|
NextButtonText: следващия
|
||||||
CancelButtonText: анулиране
|
CancelButtonText: анулиране
|
||||||
|
InitMFAOTPSMS:
|
||||||
|
Title: 2-факторна проверка
|
||||||
|
DescriptionPhone: Създайте своя 2-фактор. Въведете телефонния си номер, за да го потвърдите.
|
||||||
|
DescriptionCode: Създайте своя 2-фактор. Въведете получения код, за да потвърдите своя телефонен номер.
|
||||||
|
PhoneLabel: Тайна
|
||||||
|
CodeLabel: Код
|
||||||
|
EditButtonText: редактиране
|
||||||
|
ResendButtonText: код за препращане
|
||||||
|
NextButtonText: следващия
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Добавете ключ за сигурност
|
Title: Добавете ключ за сигурност
|
||||||
Description: >-
|
Description: >-
|
||||||
@ -118,6 +129,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: 'Приложение за удостоверяване (напр. Google/Microsoft Authenticator, Authy)'
|
Provider0: 'Приложение за удостоверяване (напр. Google/Microsoft Authenticator, Authy)'
|
||||||
Provider1: 'Зависи от устройството (напр. FaceID, Windows Hello, пръстов отпечатък)'
|
Provider1: 'Зависи от устройството (напр. FaceID, Windows Hello, пръстов отпечатък)'
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP имейл
|
||||||
ChooseOther: или изберете друга опция
|
ChooseOther: или изберете друга опция
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
Title: Проверете 2-фактора
|
Title: Проверете 2-фактора
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: 2-Faktor-Authentifizierung gibt dir eine zusätzliche Sicherheit für dein Benutzerkonto. Damit stellst du sicher, dass nur du Zugriff auf deinen Account hast.
|
Description: 2-Faktor-Authentifizierung gibt dir eine zusätzliche Sicherheit für dein Benutzerkonto. Damit stellst du sicher, dass nur du Zugriff auf deinen Account hast.
|
||||||
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
|
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP Email
|
||||||
NextButtonText: weiter
|
NextButtonText: weiter
|
||||||
SkipButtonText: überspringen
|
SkipButtonText: überspringen
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: weiter
|
NextButtonText: weiter
|
||||||
CancelButtonText: abbrechen
|
CancelButtonText: abbrechen
|
||||||
|
|
||||||
|
InitMFAOTPSMS:
|
||||||
|
Title: 2-Faktor hinzufügen
|
||||||
|
DescriptionPhone: Erstelle deinen 2-Faktor. Gib deine Telefonnummer ein, um sie zu verifizieren.
|
||||||
|
DescriptionCode: Erstelle deinen 2-Faktor. Gib den erhaltenen Code ein um deinen Telefonnummer zu verifizieren.
|
||||||
|
PhoneLabel: Telefonnummer
|
||||||
|
CodeLabel: Code
|
||||||
|
EditButtonText: bearbeiten
|
||||||
|
ResendButtonText: Code erneut senden
|
||||||
|
NextButtonText: weiter
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Sicherheitsschlüssel hinzufügen
|
Title: Sicherheitsschlüssel hinzufügen
|
||||||
Description: Ein Sicherheitsschlüssel ist eine Verifizierungsmethode, die in Ihrem Telefon integriert sein kann, Bluetooth verwenden oder direkt an den USB-Anschluss Ihres Computers angeschlossen werden.
|
Description: Ein Sicherheitsschlüssel ist eine Verifizierungsmethode, die in Ihrem Telefon integriert sein kann, Bluetooth verwenden oder direkt an den USB-Anschluss Ihres Computers angeschlossen werden.
|
||||||
@ -118,9 +130,18 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
|
Provider1: Geräte abhängig (e.g FaceID, Windows Hello, Fingerprint)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP Email
|
||||||
ChooseOther: oder wähle eine andere Option aus
|
ChooseOther: oder wähle eine andere Option aus
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
Title: 2-Faktor verifizieren
|
||||||
|
Description: Verifiziere deinen Zweitfaktor
|
||||||
|
CodeLabel: Code
|
||||||
|
ResendButtonText: Code erneut senden
|
||||||
|
NextButtonText: next
|
||||||
|
|
||||||
|
VerifyOTP:
|
||||||
Title: 2-Faktor verifizieren
|
Title: 2-Faktor verifizieren
|
||||||
Description: Verifiziere deinen Zweitfaktor
|
Description: Verifiziere deinen Zweitfaktor
|
||||||
CodeLabel: Code
|
CodeLabel: Code
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: 2-factor authentication gives you an additional security for your user account. This ensures that only you have access to your account.
|
Description: 2-factor authentication gives you an additional security for your user account. This ensures that only you have access to your account.
|
||||||
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
|
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP Email
|
||||||
NextButtonText: next
|
NextButtonText: next
|
||||||
SkipButtonText: skip
|
SkipButtonText: skip
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: next
|
NextButtonText: next
|
||||||
CancelButtonText: cancel
|
CancelButtonText: cancel
|
||||||
|
|
||||||
|
InitMFAOTPSMS:
|
||||||
|
Title: 2-Factor Verification
|
||||||
|
DescriptionPhone: Create your 2-factor. Enter your phone number to verify it.
|
||||||
|
DescriptionCode: Create your 2-factor. Enter the received code to verify your phone number.
|
||||||
|
PhoneLabel: Phone
|
||||||
|
CodeLabel: Code
|
||||||
|
EditButtonText: edit
|
||||||
|
ResendButtonText: resend code
|
||||||
|
NextButtonText: next
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Add security key
|
Title: Add security key
|
||||||
Description: A security key is a verification method that can be built into your phone, use Bluetooth, or plug directly into your computer's USB port.
|
Description: A security key is a verification method that can be built into your phone, use Bluetooth, or plug directly into your computer's USB port.
|
||||||
@ -110,7 +122,7 @@ InitMFAU2F:
|
|||||||
ErrorRetry: Retry, create a new challenge or choose a different method.
|
ErrorRetry: Retry, create a new challenge or choose a different method.
|
||||||
|
|
||||||
InitMFADone:
|
InitMFADone:
|
||||||
Title: Security key verified
|
Title: 2-factor verified
|
||||||
Description: Awesome! You just successfully set up your 2-factor and made your account way more secure. The Factor has to be entered on each login.
|
Description: Awesome! You just successfully set up your 2-factor and made your account way more secure. The Factor has to be entered on each login.
|
||||||
NextButtonText: next
|
NextButtonText: next
|
||||||
CancelButtonText: cancel
|
CancelButtonText: cancel
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
Provider0: Authenticator App (e.g Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
|
Provider1: Device dependent (e.g FaceID, Windows Hello, Fingerprint)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP Email
|
||||||
ChooseOther: or choose another option
|
ChooseOther: or choose another option
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
@ -126,6 +140,13 @@ VerifyMFAOTP:
|
|||||||
CodeLabel: Code
|
CodeLabel: Code
|
||||||
NextButtonText: next
|
NextButtonText: next
|
||||||
|
|
||||||
|
VerifyOTP:
|
||||||
|
Title: Verify 2-Factor
|
||||||
|
Description: Verify your second factor
|
||||||
|
CodeLabel: Code
|
||||||
|
ResendButtonText: resend code
|
||||||
|
NextButtonText: next
|
||||||
|
|
||||||
VerifyMFAU2F:
|
VerifyMFAU2F:
|
||||||
Title: 2-Factor Verification
|
Title: 2-Factor Verification
|
||||||
Description: Verify your 2-Factor with the registered device (e.g FaceID, Windows Hello, Fingerprint)
|
Description: Verify your 2-Factor with the registered device (e.g FaceID, Windows Hello, Fingerprint)
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: La autenticación de doble factor te proporciona seguridad adicional para tu cuenta de usuario. Ésta asegura que solo tú tienes acceso a tu cuenta.
|
Description: La autenticación de doble factor te proporciona seguridad adicional para tu cuenta de usuario. Ésta asegura que solo tú tienes acceso a tu cuenta.
|
||||||
Provider0: App autenticadora (p.e Google/Microsoft Authenticator, Authy)
|
Provider0: App autenticadora (p.e Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dependiente de un dispositivo (p.e FaceID, Windows Hello, Huella dactilar)
|
Provider1: Dependiente de un dispositivo (p.e FaceID, Windows Hello, Huella dactilar)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP email
|
||||||
NextButtonText: siguiente
|
NextButtonText: siguiente
|
||||||
SkipButtonText: saltar
|
SkipButtonText: saltar
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: siguiente
|
NextButtonText: siguiente
|
||||||
CancelButtonText: cancelar
|
CancelButtonText: cancelar
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: Verificación de doble factor
|
||||||
|
DescriptionPhone: Crea tu doble factor de autenticación. Introduce tu número de teléfono para verificarlo.
|
||||||
|
DescriptionCode: Crea tu doble factor de autenticación. Ingrese el código recibido para verificar su número de teléfono.
|
||||||
|
PhoneLabel: Número de teléfono
|
||||||
|
CodeLabel: Código
|
||||||
|
EditButtonText: editar
|
||||||
|
ResendButtonText: reenviar código
|
||||||
|
NextButtonText: siguiente
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Añadir clave de seguridad
|
Title: Añadir clave de seguridad
|
||||||
Description: Una clave de seguridad es un método de verificación que puede integrarse en tu teléfono móvil, con Bluetooth, o conectándolo directamente en el puerto USB de tu ordenador.
|
Description: Una clave de seguridad es un método de verificación que puede integrarse en tu teléfono móvil, con Bluetooth, o conectándolo directamente en el puerto USB de tu ordenador.
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: App autenticadora (p.e Google/Microsoft Authenticator, Authy)
|
Provider0: App autenticadora (p.e Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dependiente de un dispositivo (p.e FaceID, Windows Hello, Huella dactilar)
|
Provider1: Dependiente de un dispositivo (p.e FaceID, Windows Hello, Huella dactilar)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP email
|
||||||
ChooseOther: o elige otra opción
|
ChooseOther: o elige otra opción
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: L'authentification à deux facteurs vous offre une sécurité supplémentaire pour votre compte d'utilisateur. Vous êtes ainsi assuré d'être le seul à avoir accès à votre compte.
|
Description: L'authentification à deux facteurs vous offre une sécurité supplémentaire pour votre compte d'utilisateur. Vous êtes ainsi assuré d'être le seul à avoir accès à votre compte.
|
||||||
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
|
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
|
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
NextButtonText: Suivant
|
NextButtonText: Suivant
|
||||||
SkipButtonText: Passer
|
SkipButtonText: Passer
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: Suivant
|
NextButtonText: Suivant
|
||||||
CancelButtonText: Annuler
|
CancelButtonText: Annuler
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: Vérification à deux facteurs
|
||||||
|
DescriptionPhone: Créez votre 2-facteurs. Entrez votre numéro de téléphone pour le vérifier.
|
||||||
|
DescriptionCode: Créez votre 2-facteurs. Entrez le code reçu pour vérifier votre numéro de téléphone.
|
||||||
|
PhoneLabel: Numéro de téléphone
|
||||||
|
CodeLabel: Code
|
||||||
|
EditButtonText: Modifier
|
||||||
|
ResendButtonText: Renvoyer le code
|
||||||
|
NextButtonText: Suivant
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Ajouter une clé de sécurité
|
Title: Ajouter une clé de sécurité
|
||||||
Description: Une clé de sécurité est une méthode de vérification qui peut être intégrée à votre téléphone, utiliser Bluetooth ou se brancher directement sur le port USB de votre ordinateur.
|
Description: Une clé de sécurité est une méthode de vérification qui peut être intégrée à votre téléphone, utiliser Bluetooth ou se brancher directement sur le port USB de votre ordinateur.
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
|
Provider0: Application d'authentification (par exemple, Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
|
Provider1: Dépend de l'appareil (par ex. FaceID, Windows Hello, empreinte digitale)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
ChooseOther: ou choisissez une autre option
|
ChooseOther: ou choisissez une autre option
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: L'autenticazione a due fattori offre un'ulteriore sicurezza al vostro account utente. Questo garantisce che solo voi possiate accedere al vostro account.
|
Description: L'autenticazione a due fattori offre un'ulteriore sicurezza al vostro account utente. Questo garantisce che solo voi possiate accedere al vostro account.
|
||||||
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
|
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
|
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
NextButtonText: Avanti
|
NextButtonText: Avanti
|
||||||
SkipButtonText: salta
|
SkipButtonText: salta
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: Avanti
|
NextButtonText: Avanti
|
||||||
CancelButtonText: annulla
|
CancelButtonText: annulla
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: Verificazione a 2 fattori
|
||||||
|
DescriptionPhone: Crea il tuo 2 fattori. Inserisci il tuo numero di telefono per verificarlo.
|
||||||
|
DescriptionCode: Crea il tuo 2 fattori. Inserisci il codice ricevuto per verificare il tuo numero di telefono.
|
||||||
|
PhoneLabel: Numero di telefono
|
||||||
|
CodeLabel: Codice
|
||||||
|
EditButtonText: Modifica
|
||||||
|
ResendButtonText: Reinvia codice
|
||||||
|
NextButtonText: Avanti
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Aggiungi chiave di sicurezza
|
Title: Aggiungi chiave di sicurezza
|
||||||
Description: Una chiave di sicurezza è un metodo di verifica che può essere integrato nel telefono, utilizzare il Bluetooth o collegarlo direttamente alla porta USB del computer.
|
Description: Una chiave di sicurezza è un metodo di verifica che può essere integrato nel telefono, utilizzare il Bluetooth o collegarlo direttamente alla porta USB del computer.
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
|
Provider0: App Autenticatore (ad esempio Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
|
Provider1: Dipende dal dispositivo (ad es. FaceID, Windows Hello, impronta digitale)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
ChooseOther: o scegli un'altra opzione
|
ChooseOther: o scegli un'altra opzione
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -82,6 +82,8 @@ InitMFAPrompt:
|
|||||||
Description: 二要素認証でアカウントのセキュリティを強化します。
|
Description: 二要素認証でアカウントのセキュリティを強化します。
|
||||||
Provider0: 認証アプリ(Google/Microsoft Authenticator、Authyなど)
|
Provider0: 認証アプリ(Google/Microsoft Authenticator、Authyなど)
|
||||||
Provider1: デバイス依存(例:FaceID、Windows Hello、指紋など)
|
Provider1: デバイス依存(例:FaceID、Windows Hello、指紋など)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTPメール
|
||||||
NextButtonText: 次へ
|
NextButtonText: 次へ
|
||||||
SkipButtonText: スキップ
|
SkipButtonText: スキップ
|
||||||
|
|
||||||
@ -94,6 +96,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: 次へ
|
NextButtonText: 次へ
|
||||||
CancelButtonText: キャンセル
|
CancelButtonText: キャンセル
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: 二要素認証
|
||||||
|
DescriptionPhone: 二要素認証を作成します。確認するには電話番号を入力してください。
|
||||||
|
DescriptionCode: 二要素認証を作成します。受信したコードを入力して電話番号を確認します。
|
||||||
|
PhoneLabel: 電話番号
|
||||||
|
CodeLabel: コード
|
||||||
|
EditButtonText: 編集
|
||||||
|
ResendButtonText: コードを再送信
|
||||||
|
NextButtonText: 次へ
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: セキュリティキーの追加
|
Title: セキュリティキーの追加
|
||||||
Description: セキュリティキーは、携帯電話への組み込みや、Bluetoothの使用、パソコンのUSBポートに直接差し込むことなどで認証する方法です。
|
Description: セキュリティキーは、携帯電話への組み込みや、Bluetoothの使用、パソコンのUSBポートに直接差し込むことなどで認証する方法です。
|
||||||
@ -111,6 +123,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Authenticatorアプリ(Google/Microsoft Authenticator、Authyなど)
|
Provider0: Authenticatorアプリ(Google/Microsoft Authenticator、Authyなど)
|
||||||
Provider1: デバイス依存(FaceID、Windows Hello、指紋など)
|
Provider1: デバイス依存(FaceID、Windows Hello、指紋など)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTPメール
|
||||||
ChooseOther: または、他のオプションを選択
|
ChooseOther: または、他のオプションを選択
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: 2-факторската автентикација ви дава дополнителна безбедност за вашата корисничка сметка. Ова обезбедува само вие да имате пристап до вашата сметка.
|
Description: 2-факторската автентикација ви дава дополнителна безбедност за вашата корисничка сметка. Ова обезбедува само вие да имате пристап до вашата сметка.
|
||||||
Provider0: Апликација за автентикација (на пример Google/Microsoft Authenticator, Authy)
|
Provider0: Апликација за автентикација (на пример Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Во зависност од вашиот уред (на пример FaceID, Windows Hello, отпечаток од прст)
|
Provider1: Во зависност од вашиот уред (на пример FaceID, Windows Hello, отпечаток од прст)
|
||||||
|
Provider3: ОТП СМС
|
||||||
|
Provider4: ОТП е-пошта
|
||||||
NextButtonText: следно
|
NextButtonText: следно
|
||||||
SkipButtonText: прескокни
|
SkipButtonText: прескокни
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: следно
|
NextButtonText: следно
|
||||||
CancelButtonText: откажи
|
CancelButtonText: откажи
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: Потврда на 2-факторска автентикација
|
||||||
|
DescriptionPhone: Направете двофакторна автентикација. Внесете го вашиот телефонски број за да го потврдите.
|
||||||
|
DescriptionCode: Направете двофакторна автентикација. Внесете го примениот код за да го потврдите вашиот телефонски број.
|
||||||
|
PhoneLabel: Телефонски број
|
||||||
|
CodeLabel: Код
|
||||||
|
EditButtonText: Уредување
|
||||||
|
ResendButtonText: повторно испрати код
|
||||||
|
NextButtonText: следно
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Додајте безбедносен клуч
|
Title: Додајте безбедносен клуч
|
||||||
Description: Безбедносниот клуч е метод на верификација кој може да се интегрира во вашиот телефон, да користи Bluetooth или директно да се поврзе во USB приклучокот на вашиот компјутер.
|
Description: Безбедносниот клуч е метод на верификација кој може да се интегрира во вашиот телефон, да користи Bluetooth или директно да се поврзе во USB приклучокот на вашиот компјутер.
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Апликација за автентикација (на пример Google/Microsoft Authenticator, Authy)
|
Provider0: Апликација за автентикација (на пример Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Во зависност од вашиот уред (на пример FaceID, Windows Hello, отпечаток од прст)
|
Provider1: Во зависност од вашиот уред (на пример FaceID, Windows Hello, отпечаток од прст)
|
||||||
|
Provider3: ОТП СМС
|
||||||
|
Provider4: ОТП е-пошта
|
||||||
ChooseOther: или изберете друга опција
|
ChooseOther: или изберете друга опција
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: 2-etapowe uwierzytelnianie daje Ci dodatkową ochronę dla Twojego konta użytkownika. Dzięki temu masz pewność, że tylko Ty masz dostęp do swojego konta.
|
Description: 2-etapowe uwierzytelnianie daje Ci dodatkową ochronę dla Twojego konta użytkownika. Dzięki temu masz pewność, że tylko Ty masz dostęp do swojego konta.
|
||||||
Provider0: Aplikacja uwierzytelniająca (np. Google/Microsoft Authenticator, Authy)
|
Provider0: Aplikacja uwierzytelniająca (np. Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Zależny od urządzenia (np. FaceID, Windows Hello, Odcisk palca)
|
Provider1: Zależny od urządzenia (np. FaceID, Windows Hello, Odcisk palca)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
NextButtonText: dalej
|
NextButtonText: dalej
|
||||||
SkipButtonText: pomiń
|
SkipButtonText: pomiń
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: dalej
|
NextButtonText: dalej
|
||||||
CancelButtonText: anuluj
|
CancelButtonText: anuluj
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: Weryfikacja 2-etapowa
|
||||||
|
DescriptionPhone: Utwórz uwierzytelnianie dwuskładnikowe. Wprowadź swój numer telefonu, aby go zweryfikować.
|
||||||
|
DescriptionCode: Utwórz uwierzytelnianie dwuskładnikowe. Wprowadź otrzymany kod, aby zweryfikować swój numer telefonu.
|
||||||
|
PhoneLabel: Numer telefonu
|
||||||
|
CodeLabel: Kod
|
||||||
|
EditButtonText: edytować
|
||||||
|
ResendButtonText: wyślij kod ponownie
|
||||||
|
NextButtonText: dalej
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Dodaj klucz zabezpieczeń
|
Title: Dodaj klucz zabezpieczeń
|
||||||
Description: Klucz zabezpieczeń to metoda weryfikacji, która może być zintegrowana z twoim telefonem, używająca Bluetooth lub podłączana bezpośrednio do portu USB komputera.
|
Description: Klucz zabezpieczeń to metoda weryfikacji, która może być zintegrowana z twoim telefonem, używająca Bluetooth lub podłączana bezpośrednio do portu USB komputera.
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Aplikacja uwierzytelniająca (np. Google/Microsoft Authenticator, Authy)
|
Provider0: Aplikacja uwierzytelniająca (np. Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Zależny od urządzenia (np. FaceID, Windows Hello, Odcisk palca)
|
Provider1: Zależny od urządzenia (np. FaceID, Windows Hello, Odcisk palca)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
ChooseOther: lub wybierz inną opcję
|
ChooseOther: lub wybierz inną opcję
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: A autenticação de 2 fatores fornece uma segurança adicional para sua conta de usuário. Isso garante que apenas você tenha acesso à sua conta.
|
Description: A autenticação de 2 fatores fornece uma segurança adicional para sua conta de usuário. Isso garante que apenas você tenha acesso à sua conta.
|
||||||
Provider0: Aplicativo de autenticação (por exemplo, Google/Microsoft Authenticator, Authy)
|
Provider0: Aplicativo de autenticação (por exemplo, Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dependente do dispositivo (por exemplo, FaceID, Windows Hello, Impressão digital)
|
Provider1: Dependente do dispositivo (por exemplo, FaceID, Windows Hello, Impressão digital)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
NextButtonText: próximo
|
NextButtonText: próximo
|
||||||
SkipButtonText: pular
|
SkipButtonText: pular
|
||||||
|
|
||||||
@ -101,6 +103,16 @@ InitMFAOTP:
|
|||||||
NextButtonText: próximo
|
NextButtonText: próximo
|
||||||
CancelButtonText: cancelar
|
CancelButtonText: cancelar
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: Verificação de 2 fatores
|
||||||
|
DescriptionPhone: Crie sua verificação de 2 fatores. Digite seu número de telefone para verificá-lo.
|
||||||
|
DescriptionCode: Crie sua verificação de 2 fatores. Digite o código recebido para verificar seu número de telefone.
|
||||||
|
PhoneLabel: Número de telefone
|
||||||
|
CodeLabel: Código
|
||||||
|
EditButtonText: editar
|
||||||
|
ResendButtonText: reenviar código
|
||||||
|
NextButtonText: próximo
|
||||||
|
|
||||||
InitMFAU2F:
|
InitMFAU2F:
|
||||||
Title: Adicionar chave de segurança
|
Title: Adicionar chave de segurança
|
||||||
Description: Uma chave de segurança é um método de verificação que pode ser incorporado ao seu telefone, usar Bluetooth ou conectar diretamente à porta USB do seu computador.
|
Description: Uma chave de segurança é um método de verificação que pode ser incorporado ao seu telefone, usar Bluetooth ou conectar diretamente à porta USB do seu computador.
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: Aplicativo de autenticação (por exemplo, Google/Microsoft Authenticator, Authy)
|
Provider0: Aplicativo de autenticação (por exemplo, Google/Microsoft Authenticator, Authy)
|
||||||
Provider1: Dependente do dispositivo (por exemplo, FaceID, Windows Hello, Impressão digital)
|
Provider1: Dependente do dispositivo (por exemplo, FaceID, Windows Hello, Impressão digital)
|
||||||
|
Provider3: OTP SMS
|
||||||
|
Provider4: OTP e-mail
|
||||||
ChooseOther: ou escolha outra opção
|
ChooseOther: ou escolha outra opção
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
@ -89,6 +89,8 @@ InitMFAPrompt:
|
|||||||
Description: 两步验证为您的账户提供了额外的安全保障。这确保只有你能访问你的账户。
|
Description: 两步验证为您的账户提供了额外的安全保障。这确保只有你能访问你的账户。
|
||||||
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy)
|
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy)
|
||||||
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹)
|
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹)
|
||||||
|
Provider3: 一次性密码短信
|
||||||
|
Provider4: 一次性密码电子邮件
|
||||||
NextButtonText: 继续
|
NextButtonText: 继续
|
||||||
SkipButtonText: 跳过
|
SkipButtonText: 跳过
|
||||||
|
|
||||||
@ -96,6 +98,16 @@ InitMFAOTP:
|
|||||||
Title: 双因素验证
|
Title: 双因素验证
|
||||||
Description: 创建你的双因素。如果你还没有,请下载一个认证器应用程序。
|
Description: 创建你的双因素。如果你还没有,请下载一个认证器应用程序。
|
||||||
OTPDescription: 使用您的身份验证器应用程序(例如 Google Authenticator)扫描代码或复制密码并在下方插入生成的代码。
|
OTPDescription: 使用您的身份验证器应用程序(例如 Google Authenticator)扫描代码或复制密码并在下方插入生成的代码。
|
||||||
|
PhoneLabel: 电话号码
|
||||||
|
CodeLabel: 验证码
|
||||||
|
EditButtonText: 编辑
|
||||||
|
ResendButtonText: 重发代码
|
||||||
|
NextButtonText: 继续
|
||||||
|
|
||||||
|
InitMFASMS:
|
||||||
|
Title: 双因素验证
|
||||||
|
DescriptionPhone: 创建双因素身份验证。输入您的电话号码进行验证。
|
||||||
|
DescriptionCode: 创建双因素身份验证。输入收到的代码以验证您的电话号码。
|
||||||
SecretLabel: 秘钥
|
SecretLabel: 秘钥
|
||||||
CodeLabel: 验证码
|
CodeLabel: 验证码
|
||||||
NextButtonText: 继续
|
NextButtonText: 继续
|
||||||
@ -118,6 +130,8 @@ InitMFADone:
|
|||||||
MFAProvider:
|
MFAProvider:
|
||||||
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy)
|
Provider0: 软件应用(如 Google/Migrosoft Authenticator、Authy)
|
||||||
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹)
|
Provider1: 硬件设备(如 Face ID、Windows Hello、指纹)
|
||||||
|
Provider3: 一次性密码短信
|
||||||
|
Provider4: 一次性密码电子邮件
|
||||||
ChooseOther: 或选择其他选项
|
ChooseOther: 或选择其他选项
|
||||||
|
|
||||||
VerifyMFAOTP:
|
VerifyMFAOTP:
|
||||||
|
6
internal/api/ui/login/static/resources/scripts/edit.js
Normal file
6
internal/api/ui/login/static/resources/scripts/edit.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
let form = document.getElementsByTagName('form')[0];
|
||||||
|
let editButton = document.getElementById('edit');
|
||||||
|
editButton.addEventListener('click', function () {
|
||||||
|
form.submit();
|
||||||
|
});
|
||||||
|
|
@ -1,15 +1,16 @@
|
|||||||
@mixin lgn-mfa-base {
|
@mixin lgn-mfa-base {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
justify-content: space-evenly;
|
justify-content: space-evenly;
|
||||||
margin: 2rem 0;
|
margin: 1rem 0;
|
||||||
|
|
||||||
.mfa {
|
.mfa {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0 0.5rem;
|
padding: 1rem 0.5rem;
|
||||||
|
|
||||||
label {
|
label {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
63
internal/api/ui/login/static/templates/mfa_init_otp_sms.html
Normal file
63
internal/api/ui/login/static/templates/mfa_init_otp_sms.html
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
{{template "main-top" .}}
|
||||||
|
|
||||||
|
<div class="lgn-head">
|
||||||
|
<h1>{{t "InitMFAOTPSMS.Title"}}</h1>
|
||||||
|
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
|
{{if .Edit}}
|
||||||
|
<p>{{t "InitMFAOTPSMS.DescriptionPhone"}}</p>
|
||||||
|
{{else}}
|
||||||
|
<p>{{t "InitMFAOTPSMS.DescriptionCode"}}</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ mfaInitSMSVerifyUrl }}" method="POST">
|
||||||
|
|
||||||
|
{{ .CSRF }}
|
||||||
|
|
||||||
|
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||||
|
<input type="hidden" name="mfaType" value="{{ .MFAType }}" />
|
||||||
|
<input type="hidden" name="phone" value="{{ .Phone }}" />
|
||||||
|
|
||||||
|
<div class="fields">
|
||||||
|
{{if .Edit}}
|
||||||
|
<div class="field">
|
||||||
|
<label class="lgn-label" for="newPhone">{{t "InitMFAOTPSMS.PhoneLabel"}}</label>
|
||||||
|
<input class="lgn-input" type="tel" id="newPhone" name="newPhone" autocomplete="off" value="{{.Phone}}" autofocus required>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="field lgn-actions">
|
||||||
|
<p>{{.Phone}}</p>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<button type="button" id="edit" name="edit" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.EditButtonText"}}</button>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if and .Phone (not .Edit) }}
|
||||||
|
<div class="field">
|
||||||
|
<label class="lgn-label" for="code">{{t "InitMFAOTPSMS.CodeLabel"}}</label>
|
||||||
|
<input class="lgn-input" type="text" id="code" name="code" autocomplete="one-time-code" autofocus required>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ template "error-message" .}}
|
||||||
|
|
||||||
|
<div class="lgn-actions lgn-reverse-order">
|
||||||
|
<!-- position element in header -->
|
||||||
|
<a class="lgn-icon-button lgn-left-action" href="{{ mfaPromptChangeUrl .AuthReqID .MFAType }}">
|
||||||
|
<i class="lgn-icon-arrow-left-solid"></i>
|
||||||
|
</a>
|
||||||
|
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "InitMFAOTPSMS.NextButtonText"}}</button>
|
||||||
|
{{if and .Phone (not .Edit) }}
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.ResendButtonText"}}</button>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="{{ resourceUrl "scripts/edit.js" }}"></script>
|
||||||
|
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
|
||||||
|
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
|
||||||
|
|
||||||
|
{{template "main-bottom" .}}
|
@ -34,6 +34,16 @@
|
|||||||
<img width="100px" height="100px" alt="OTP" src="{{ resourceUrl
|
<img width="100px" height="100px" alt="OTP" src="{{ resourceUrl
|
||||||
"images/mfa/mfa-u2f.svg" }}" />
|
"images/mfa/mfa-u2f.svg" }}" />
|
||||||
</div>
|
</div>
|
||||||
|
{{ end }} {{ if eq $provider 3 }}
|
||||||
|
<div class="mfa-img">
|
||||||
|
<img width="100px" height="100px" alt="OTP SMS" src="{{ resourceUrl
|
||||||
|
"images/mfa/mfa-u2f.svg" }}" /> // TODO: image
|
||||||
|
</div>
|
||||||
|
{{ end }}{{ if eq $provider 4 }}
|
||||||
|
<div class="mfa-img">
|
||||||
|
<img width="100px" height="100px" alt="OTP Email" src="{{ resourceUrl
|
||||||
|
"images/mfa/mfa-u2f.svg" }}" /> // TODO: image
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<span>{{ $providerName }} </span>
|
<span>{{ $providerName }} </span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,34 +1,37 @@
|
|||||||
{{template "main-top" .}}
|
{{template "main-top" .}}
|
||||||
|
|
||||||
<div class="lgn-head">
|
<div class="lgn-head">
|
||||||
<h1>{{t "VerifyMFAOTP.Title"}}</h1>
|
<h1>{{t "VerifyOTP.Title"}}</h1>
|
||||||
|
|
||||||
{{ template "user-profile" . }}
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
<p>{{t "VerifyMFAOTP.Description"}}</p>
|
<p>{{t "VerifyOTP.Description"}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form action="{{ mfaVerifyUrl }}" method="POST">
|
<form action="{{ mfaOTPVerifyUrl }}" method="POST">
|
||||||
|
|
||||||
{{ .CSRF }}
|
{{ .CSRF }}
|
||||||
|
|
||||||
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||||
<input type="hidden" name="mfaType" value="{{ .SelectedMFAProvider }}" />
|
<input type="hidden" name="selectedProvider" value="{{ .SelectedProvider }}" />
|
||||||
|
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
<label class="lgn-label" for="code">{{t "VerifyMFAOTP.CodeLabel"}}</label>
|
<label class="lgn-label" for="code">{{t "VerifyOTP.CodeLabel"}}</label>
|
||||||
<input class="lgn-input" type="text" id="code" name="code" autocomplete="off" autofocus required>
|
<input class="lgn-input" type="text" id="code" name="code" autocomplete="one-time-code" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ template "error-message" .}}
|
{{ template "error-message" .}}
|
||||||
|
|
||||||
<div class="lgn-actions">
|
<div class="lgn-actions lgn-reverse-order">
|
||||||
<!-- position element in header -->
|
<!-- position element in header -->
|
||||||
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
|
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
|
||||||
<i class="lgn-icon-arrow-left-solid"></i>
|
<i class="lgn-icon-arrow-left-solid"></i>
|
||||||
</a>
|
</a>
|
||||||
|
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyOTP.NextButtonText"}}</button>
|
||||||
|
|
||||||
<span class="fill-space"></span>
|
<span class="fill-space"></span>
|
||||||
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyMFAOTP.NextButtonText"}}</button>
|
|
||||||
|
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "VerifyOTP.ResendButtonText"}}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{ if .MFAProviders }}
|
{{ if .MFAProviders }}
|
||||||
|
48
internal/api/ui/login/static/templates/mfa_verify_totp.html
Normal file
48
internal/api/ui/login/static/templates/mfa_verify_totp.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{{template "main-top" .}}
|
||||||
|
|
||||||
|
<div class="lgn-head">
|
||||||
|
<h1>{{t "VerifyMFAOTP.Title"}}</h1>
|
||||||
|
|
||||||
|
{{ template "user-profile" . }}
|
||||||
|
|
||||||
|
<p>{{t "VerifyMFAOTP.Description"}}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ mfaVerifyUrl }}" method="POST">
|
||||||
|
|
||||||
|
{{ .CSRF }}
|
||||||
|
|
||||||
|
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
|
||||||
|
<input type="hidden" name="mfaType" value="{{ .SelectedMFAProvider }}" />
|
||||||
|
|
||||||
|
<div class="fields">
|
||||||
|
<label class="lgn-label" for="code">{{t "VerifyMFAOTP.CodeLabel"}}</label>
|
||||||
|
<input class="lgn-input" type="text" id="code" name="code" autocomplete="off" autofocus required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ template "error-message" .}}
|
||||||
|
|
||||||
|
<div class="lgn-actions">
|
||||||
|
<!-- position element in header -->
|
||||||
|
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
|
||||||
|
<i class="lgn-icon-arrow-left-solid"></i>
|
||||||
|
</a>
|
||||||
|
<span class="fill-space"></span>
|
||||||
|
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyMFAOTP.NextButtonText"}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ if .MFAProviders }}
|
||||||
|
<div class="lgn-mfa-other">
|
||||||
|
<p>{{t "MFAProvider.ChooseOther"}}</p>
|
||||||
|
{{ range $provider := .MFAProviders}}
|
||||||
|
{{ $providerName := (t (printf "MFAProvider.Provider%v" $provider)) }}
|
||||||
|
<button class="lgn-stroked-button" type="submit" name="provider" value="{{$provider}}"
|
||||||
|
formnovalidate>{{$providerName}}</button>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
|
||||||
|
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
|
||||||
|
{{template "main-bottom" .}}
|
@ -23,6 +23,10 @@ type AuthRequestRepository interface {
|
|||||||
VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) error
|
VerifyPassword(ctx context.Context, id, userID, resourceOwner, password, userAgentID string, info *domain.BrowserInfo) error
|
||||||
|
|
||||||
VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) error
|
VerifyMFAOTP(ctx context.Context, authRequestID, userID, resourceOwner, code, userAgentID string, info *domain.BrowserInfo) error
|
||||||
|
SendMFAOTPSMS(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) error
|
||||||
|
VerifyMFAOTPSMS(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) error
|
||||||
|
SendMFAOTPEmail(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) error
|
||||||
|
VerifyMFAOTPEmail(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) error
|
||||||
BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error)
|
BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (*domain.WebAuthNLogin, error)
|
||||||
VerifyMFAU2F(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
|
VerifyMFAU2F(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string, credentialData []byte, info *domain.BrowserInfo) error
|
||||||
BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error)
|
BeginPasswordlessSetup(ctx context.Context, userID, resourceOwner string, preferredPlatformType domain.AuthenticatorAttachment) (login *domain.WebAuthNToken, err error)
|
||||||
|
@ -376,6 +376,48 @@ func (repo *AuthRequestRepo) VerifyMFAOTP(ctx context.Context, authRequestID, us
|
|||||||
return repo.Command.HumanCheckMFATOTP(ctx, userID, code, resourceOwner, request.WithCurrentInfo(info))
|
return repo.Command.HumanCheckMFATOTP(ctx, userID, code, resourceOwner, request.WithCurrentInfo(info))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) SendMFAOTPSMS(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.Command.HumanSendOTPSMS(ctx, userID, resourceOwner, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) VerifyMFAOTPSMS(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) (err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.Command.HumanCheckOTPSMS(ctx, userID, code, resourceOwner, request.WithCurrentInfo(info))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) SendMFAOTPEmail(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.Command.HumanSendOTPEmail(ctx, userID, resourceOwner, request)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *AuthRequestRepo) VerifyMFAOTPEmail(ctx context.Context, userID, resourceOwner, code, authRequestID, userAgentID string, info *domain.BrowserInfo) (err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
request, err := repo.getAuthRequestEnsureUser(ctx, authRequestID, userAgentID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return repo.Command.HumanCheckOTPEmail(ctx, userID, code, resourceOwner, request.WithCurrentInfo(info))
|
||||||
|
}
|
||||||
|
|
||||||
func (repo *AuthRequestRepo) BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (login *domain.WebAuthNLogin, err error) {
|
func (repo *AuthRequestRepo) BeginMFAU2FLogin(ctx context.Context, userID, resourceOwner, authRequestID, userAgentID string) (login *domain.WebAuthNLogin, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
|
@ -138,6 +138,10 @@ func (u *User) ProcessUser(event *es_models.Event) (err error) {
|
|||||||
user_repo.HumanMFAOTPAddedType,
|
user_repo.HumanMFAOTPAddedType,
|
||||||
user_repo.HumanMFAOTPVerifiedType,
|
user_repo.HumanMFAOTPVerifiedType,
|
||||||
user_repo.HumanMFAOTPRemovedType,
|
user_repo.HumanMFAOTPRemovedType,
|
||||||
|
user_repo.HumanOTPSMSAddedType,
|
||||||
|
user_repo.HumanOTPSMSRemovedType,
|
||||||
|
user_repo.HumanOTPEmailAddedType,
|
||||||
|
user_repo.HumanOTPEmailRemovedType,
|
||||||
user_repo.HumanU2FTokenAddedType,
|
user_repo.HumanU2FTokenAddedType,
|
||||||
user_repo.HumanU2FTokenVerifiedType,
|
user_repo.HumanU2FTokenVerifiedType,
|
||||||
user_repo.HumanU2FTokenRemovedType,
|
user_repo.HumanU2FTokenRemovedType,
|
||||||
|
@ -137,6 +137,9 @@ func writeModelToWebAuthN(wm *HumanWebAuthNWriteModel) *domain.WebAuthNToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func authRequestDomainToAuthRequestInfo(authRequest *domain.AuthRequest) *user.AuthRequestInfo {
|
func authRequestDomainToAuthRequestInfo(authRequest *domain.AuthRequest) *user.AuthRequestInfo {
|
||||||
|
if authRequest == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
info := &user.AuthRequestInfo{
|
info := &user.AuthRequestInfo{
|
||||||
ID: authRequest.ID,
|
ID: authRequest.ID,
|
||||||
UserAgentID: authRequest.AgentID,
|
UserAgentID: authRequest.AgentID,
|
||||||
|
@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/pquerna/otp"
|
"github.com/pquerna/otp"
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
@ -15,16 +16,16 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, resourceowner string, key string) error {
|
func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, resourceOwner string, key string) error {
|
||||||
encryptedSecret, err := crypto.Encrypt([]byte(key), c.multifactors.OTP.CryptoMFA)
|
encryptedSecret, err := crypto.Encrypt([]byte(key), c.multifactors.OTP.CryptoMFA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = c.checkUserExists(ctx, userID, resourceowner); err != nil {
|
if err = c.checkUserExists(ctx, userID, resourceOwner); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceowner)
|
otpWriteModel, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -40,11 +41,11 @@ func (c *Commands) ImportHumanTOTP(ctx context.Context, userID, userAgentID, res
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) AddHumanTOTP(ctx context.Context, userID, resourceowner string) (*domain.TOTP, error) {
|
func (c *Commands) AddHumanTOTP(ctx context.Context, userID, resourceOwner string) (*domain.TOTP, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-5M0sd", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
prep, err := c.createHumanTOTP(ctx, userID, resourceowner)
|
prep, err := c.createHumanTOTP(ctx, userID, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -114,12 +115,12 @@ func (c *Commands) createHumanTOTP(ctx context.Context, userID, resourceOwner st
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, userAgentID, resourceowner string) (*domain.ObjectDetails, error) {
|
func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, userAgentID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
|
|
||||||
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceowner)
|
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -145,11 +146,11 @@ func (c *Commands) HumanCheckMFATOTPSetup(ctx context.Context, userID, code, use
|
|||||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resourceowner string, authRequest *domain.AuthRequest) error {
|
func (c *Commands) HumanCheckMFATOTP(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8N9ds", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceowner)
|
existingOTP, err := c.totpWriteModelByID(ctx, userID, resourceOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -191,7 +192,26 @@ func (c *Commands) HumanRemoveTOTP(ctx context.Context, userID, resourceOwner st
|
|||||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddHumanOTPSMS adds the OTP SMS factor to a user.
|
||||||
|
// It can only be added if it not already is and the phone has to be verified.
|
||||||
func (c *Commands) AddHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
func (c *Commands) AddHumanOTPSMS(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
return c.addHumanOTPSMS(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHumanOTPSMSWithCheckSucceeded adds the OTP SMS factor to a user.
|
||||||
|
// It can only be added if it's not already and the phone has to be verified.
|
||||||
|
// An OTPSMSCheckSucceededEvent will be added to the passed AuthRequest, if not nil.
|
||||||
|
func (c *Commands) AddHumanOTPSMSWithCheckSucceeded(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) (*domain.ObjectDetails, error) {
|
||||||
|
if authRequest == nil {
|
||||||
|
return c.addHumanOTPSMS(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
event := func(ctx context.Context, userAgg *eventstore.Aggregate) eventstore.Command {
|
||||||
|
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))
|
||||||
|
}
|
||||||
|
return c.addHumanOTPSMS(ctx, userID, resourceOwner, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) addHumanOTPSMS(ctx context.Context, userID, resourceOwner string, events ...eventCallback) (*domain.ObjectDetails, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-QSF2s", "Errors.User.UserIDMissing")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-QSF2s", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
@ -209,7 +229,12 @@ func (c *Commands) AddHumanOTPSMS(ctx context.Context, userID, resourceOwner str
|
|||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-Q54j2", "Errors.User.MFA.OTP.NotReady")
|
||||||
}
|
}
|
||||||
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
||||||
if err = c.pushAppendAndReduce(ctx, otpWriteModel, user.NewHumanOTPSMSAddedEvent(ctx, userAgg)); err != nil {
|
cmds := make([]eventstore.Command, len(events)+1)
|
||||||
|
cmds[0] = user.NewHumanOTPSMSAddedEvent(ctx, userAgg)
|
||||||
|
for i, event := range events {
|
||||||
|
cmds[i+1] = event(ctx, userAgg)
|
||||||
|
}
|
||||||
|
if err = c.pushAppendAndReduce(ctx, otpWriteModel, cmds...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
|
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
|
||||||
@ -225,7 +250,7 @@ func (c *Commands) RemoveHumanOTPSMS(ctx context.Context, userID, resourceOwner
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if userID != authz.GetCtxData(ctx).UserID {
|
if userID != authz.GetCtxData(ctx).UserID {
|
||||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil {
|
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,7 +264,77 @@ func (c *Commands) RemoveHumanOTPSMS(ctx context.Context, userID, resourceOwner
|
|||||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) HumanSendOTPSMS(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) error {
|
||||||
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
||||||
|
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
|
||||||
|
return user.NewHumanOTPSMSCodeAddedEvent(ctx, aggregate, code, expiry, info)
|
||||||
|
}
|
||||||
|
return c.sendHumanOTP(
|
||||||
|
ctx,
|
||||||
|
userID,
|
||||||
|
resourceOwner,
|
||||||
|
authRequest,
|
||||||
|
smsWriteModel,
|
||||||
|
domain.SecretGeneratorTypeOTPSMS,
|
||||||
|
codeAddedEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) HumanOTPSMSCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
|
||||||
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
||||||
|
return c.otpSMSWriteModelByID(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
|
||||||
|
return user.NewHumanOTPSMSCodeSentEvent(ctx, aggregate)
|
||||||
|
}
|
||||||
|
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) HumanCheckOTPSMS(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
|
||||||
|
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
|
||||||
|
return c.otpSMSCodeWriteModelByID(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
||||||
|
return user.NewHumanOTPSMSCheckSucceededEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
||||||
|
}
|
||||||
|
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
||||||
|
return user.NewHumanOTPSMSCheckFailedEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
||||||
|
}
|
||||||
|
return c.humanCheckOTP(
|
||||||
|
ctx,
|
||||||
|
userID,
|
||||||
|
code,
|
||||||
|
resourceOwner,
|
||||||
|
authRequest,
|
||||||
|
writeModel,
|
||||||
|
domain.SecretGeneratorTypeOTPSMS,
|
||||||
|
succeededEvent,
|
||||||
|
failedEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHumanOTPEmail adds the OTP Email factor to a user.
|
||||||
|
// It can only be added if it not already is and the phone has to be verified.
|
||||||
func (c *Commands) AddHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
func (c *Commands) AddHumanOTPEmail(ctx context.Context, userID, resourceOwner string) (*domain.ObjectDetails, error) {
|
||||||
|
return c.addHumanOTPEmail(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddHumanOTPEmailWithCheckSucceeded adds the OTP Email factor to a user.
|
||||||
|
// It can only be added if it's not already and the email has to be verified.
|
||||||
|
// An OTPEmailCheckSucceededEvent will be added to the passed AuthRequest, if not nil.
|
||||||
|
func (c *Commands) AddHumanOTPEmailWithCheckSucceeded(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) (*domain.ObjectDetails, error) {
|
||||||
|
if authRequest == nil {
|
||||||
|
return c.addHumanOTPEmail(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
event := func(ctx context.Context, userAgg *eventstore.Aggregate) eventstore.Command {
|
||||||
|
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest))
|
||||||
|
}
|
||||||
|
return c.addHumanOTPEmail(ctx, userID, resourceOwner, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) addHumanOTPEmail(ctx context.Context, userID, resourceOwner string, events ...eventCallback) (*domain.ObjectDetails, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Sg1hz", "Errors.User.UserIDMissing")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-Sg1hz", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
@ -254,7 +349,12 @@ func (c *Commands) AddHumanOTPEmail(ctx context.Context, userID, resourceOwner s
|
|||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-KLJ2d", "Errors.User.MFA.OTP.NotReady")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-KLJ2d", "Errors.User.MFA.OTP.NotReady")
|
||||||
}
|
}
|
||||||
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
userAgg := UserAggregateFromWriteModel(&otpWriteModel.WriteModel)
|
||||||
if err = c.pushAppendAndReduce(ctx, otpWriteModel, user.NewHumanOTPEmailAddedEvent(ctx, userAgg)); err != nil {
|
cmds := make([]eventstore.Command, len(events)+1)
|
||||||
|
cmds[0] = user.NewHumanOTPEmailAddedEvent(ctx, userAgg)
|
||||||
|
for i, event := range events {
|
||||||
|
cmds[i+1] = event(ctx, userAgg)
|
||||||
|
}
|
||||||
|
if err = c.pushAppendAndReduce(ctx, otpWriteModel, cmds...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
|
return writeModelToObjectDetails(&otpWriteModel.WriteModel), nil
|
||||||
@ -270,7 +370,7 @@ func (c *Commands) RemoveHumanOTPEmail(ctx context.Context, userID, resourceOwne
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if userID != authz.GetCtxData(ctx).UserID {
|
if userID != authz.GetCtxData(ctx).UserID {
|
||||||
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.ResourceOwner, userID); err != nil {
|
if err := c.checkPermission(ctx, domain.PermissionUserWrite, existingOTP.WriteModel.ResourceOwner, userID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -284,6 +384,147 @@ func (c *Commands) RemoveHumanOTPEmail(ctx context.Context, userID, resourceOwne
|
|||||||
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
return writeModelToObjectDetails(&existingOTP.WriteModel), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) HumanSendOTPEmail(ctx context.Context, userID, resourceOwner string, authRequest *domain.AuthRequest) error {
|
||||||
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
||||||
|
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
codeAddedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command {
|
||||||
|
return user.NewHumanOTPEmailCodeAddedEvent(ctx, aggregate, code, expiry, info)
|
||||||
|
}
|
||||||
|
return c.sendHumanOTP(
|
||||||
|
ctx,
|
||||||
|
userID,
|
||||||
|
resourceOwner,
|
||||||
|
authRequest,
|
||||||
|
smsWriteModel,
|
||||||
|
domain.SecretGeneratorTypeOTPEmail,
|
||||||
|
codeAddedEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) HumanOTPEmailCodeSent(ctx context.Context, userID, resourceOwner string) (err error) {
|
||||||
|
smsWriteModel := func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error) {
|
||||||
|
return c.otpEmailWriteModelByID(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
codeSentEvent := func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command {
|
||||||
|
return user.NewHumanOTPEmailCodeSentEvent(ctx, aggregate)
|
||||||
|
}
|
||||||
|
return c.humanOTPSent(ctx, userID, resourceOwner, smsWriteModel, codeSentEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) HumanCheckOTPEmail(ctx context.Context, userID, code, resourceOwner string, authRequest *domain.AuthRequest) error {
|
||||||
|
writeModel := func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error) {
|
||||||
|
return c.otpEmailCodeWriteModelByID(ctx, userID, resourceOwner)
|
||||||
|
}
|
||||||
|
succeededEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
||||||
|
return user.NewHumanOTPEmailCheckSucceededEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
||||||
|
}
|
||||||
|
failedEvent := func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command {
|
||||||
|
return user.NewHumanOTPEmailCheckFailedEvent(ctx, aggregate, authRequestDomainToAuthRequestInfo(authRequest))
|
||||||
|
}
|
||||||
|
return c.humanCheckOTP(
|
||||||
|
ctx,
|
||||||
|
userID,
|
||||||
|
code,
|
||||||
|
resourceOwner,
|
||||||
|
authRequest,
|
||||||
|
writeModel,
|
||||||
|
domain.SecretGeneratorTypeOTPEmail,
|
||||||
|
succeededEvent,
|
||||||
|
failedEvent,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendHumanOTP creates a code for a registered mechanism (sms / email), which is used for a check (during login)
|
||||||
|
func (c *Commands) sendHumanOTP(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, resourceOwner string,
|
||||||
|
authRequest *domain.AuthRequest,
|
||||||
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
|
||||||
|
secretGeneratorType domain.SecretGeneratorType,
|
||||||
|
codeAddedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, code *crypto.CryptoValue, expiry time.Duration, info *user.AuthRequestInfo) eventstore.Command,
|
||||||
|
) (err error) {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-S3SF1", "Errors.User.UserIDMissing")
|
||||||
|
}
|
||||||
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !existingOTP.OTPAdded() {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SFD52", "Errors.User.MFA.OTP.NotReady")
|
||||||
|
}
|
||||||
|
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, secretGeneratorType)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gen := crypto.NewEncryptionGenerator(*config, c.userEncryption)
|
||||||
|
value, _, err := crypto.NewCode(gen)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
|
||||||
|
_, err = c.eventstore.Push(ctx, codeAddedEvent(ctx, userAgg, value, gen.Expiry(), authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) humanOTPSent(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, resourceOwner string,
|
||||||
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPWriteModel, error),
|
||||||
|
codeSentEvent func(ctx context.Context, aggregate *eventstore.Aggregate) eventstore.Command,
|
||||||
|
) (err error) {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-AE2h2", "Errors.User.UserIDMissing")
|
||||||
|
}
|
||||||
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !existingOTP.OTPAdded() {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-SD3gh", "Errors.User.MFA.OTP.NotReady")
|
||||||
|
}
|
||||||
|
userAgg := &user.NewAggregate(userID, resourceOwner).Aggregate
|
||||||
|
_, err = c.eventstore.Push(ctx, codeSentEvent(ctx, userAgg))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Commands) humanCheckOTP(
|
||||||
|
ctx context.Context,
|
||||||
|
userID, code, resourceOwner string,
|
||||||
|
authRequest *domain.AuthRequest,
|
||||||
|
writeModelByID func(ctx context.Context, userID string, resourceOwner string) (OTPCodeWriteModel, error),
|
||||||
|
secretGeneratorType domain.SecretGeneratorType,
|
||||||
|
checkSucceededEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
||||||
|
checkFailedEvent func(ctx context.Context, aggregate *eventstore.Aggregate, info *user.AuthRequestInfo) eventstore.Command,
|
||||||
|
) error {
|
||||||
|
if userID == "" {
|
||||||
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-S453v", "Errors.User.UserIDMissing")
|
||||||
|
}
|
||||||
|
if code == "" {
|
||||||
|
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-SJl2g", "Errors.User.Code.Empty")
|
||||||
|
}
|
||||||
|
existingOTP, err := writeModelByID(ctx, userID, resourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !existingOTP.OTPAdded() {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-d2r52", "Errors.User.MFA.OTP.NotReady")
|
||||||
|
}
|
||||||
|
if existingOTP.Code() == nil {
|
||||||
|
return caos_errs.ThrowPreconditionFailed(nil, "COMMAND-S34gh", "Errors.User.Code.NotFound")
|
||||||
|
}
|
||||||
|
userAgg := &user.NewAggregate(userID, existingOTP.ResourceOwner()).Aggregate
|
||||||
|
err = crypto.VerifyCodeWithAlgorithm(existingOTP.CodeCreationDate(), existingOTP.CodeExpiry(), existingOTP.Code(), code, c.userEncryption)
|
||||||
|
if err == nil {
|
||||||
|
_, err = c.eventstore.Push(ctx, checkSucceededEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, pushErr := c.eventstore.Push(ctx, checkFailedEvent(ctx, userAgg, authRequestDomainToAuthRequestInfo(authRequest)))
|
||||||
|
logging.WithFields("userID", userID).OnError(pushErr).Error("otp failure check push failed")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
func (c *Commands) totpWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanTOTPWriteModel, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
@ -308,6 +549,18 @@ func (c *Commands) otpSMSWriteModelByID(ctx context.Context, userID, resourceOwn
|
|||||||
return writeModel, nil
|
return writeModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) otpSMSCodeWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPSMSCodeWriteModel, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
writeModel = NewHumanOTPSMSCodeWriteModel(userID, resourceOwner)
|
||||||
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModel, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Commands) otpEmailWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailWriteModel, err error) {
|
func (c *Commands) otpEmailWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailWriteModel, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
@ -319,3 +572,15 @@ func (c *Commands) otpEmailWriteModelByID(ctx context.Context, userID, resourceO
|
|||||||
}
|
}
|
||||||
return writeModel, nil
|
return writeModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Commands) otpEmailCodeWriteModelByID(ctx context.Context, userID, resourceOwner string) (writeModel *HumanOTPEmailCodeWriteModel, err error) {
|
||||||
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
|
defer func() { span.EndWithError(err) }()
|
||||||
|
|
||||||
|
writeModel = NewHumanOTPEmailCodeWriteModel(userID, resourceOwner)
|
||||||
|
err = c.eventstore.FilterToQueryReducer(ctx, writeModel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return writeModel, nil
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package command
|
package command
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
@ -60,6 +62,18 @@ func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type OTPWriteModel interface {
|
||||||
|
OTPAdded() bool
|
||||||
|
ResourceOwner() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type OTPCodeWriteModel interface {
|
||||||
|
OTPWriteModel
|
||||||
|
CodeCreationDate() time.Time
|
||||||
|
CodeExpiry() time.Duration
|
||||||
|
Code() *crypto.CryptoValue
|
||||||
|
}
|
||||||
|
|
||||||
type HumanOTPSMSWriteModel struct {
|
type HumanOTPSMSWriteModel struct {
|
||||||
eventstore.WriteModel
|
eventstore.WriteModel
|
||||||
|
|
||||||
@ -67,6 +81,14 @@ type HumanOTPSMSWriteModel struct {
|
|||||||
otpAdded bool
|
otpAdded bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSWriteModel) OTPAdded() bool {
|
||||||
|
return wm.otpAdded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSWriteModel) ResourceOwner() string {
|
||||||
|
return wm.WriteModel.ResourceOwner
|
||||||
|
}
|
||||||
|
|
||||||
func NewHumanOTPSMSWriteModel(userID, resourceOwner string) *HumanOTPSMSWriteModel {
|
func NewHumanOTPSMSWriteModel(userID, resourceOwner string) *HumanOTPSMSWriteModel {
|
||||||
return &HumanOTPSMSWriteModel{
|
return &HumanOTPSMSWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
@ -107,8 +129,66 @@ func (wm *HumanOTPSMSWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
).
|
).
|
||||||
Builder()
|
Builder()
|
||||||
|
|
||||||
if wm.ResourceOwner != "" {
|
if wm.WriteModel.ResourceOwner != "" {
|
||||||
query.ResourceOwner(wm.ResourceOwner)
|
query.ResourceOwner(wm.WriteModel.ResourceOwner)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
type HumanOTPSMSCodeWriteModel struct {
|
||||||
|
*HumanOTPSMSWriteModel
|
||||||
|
|
||||||
|
code *crypto.CryptoValue
|
||||||
|
codeCreationDate time.Time
|
||||||
|
codeExpiry time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) CodeCreationDate() time.Time {
|
||||||
|
return wm.codeCreationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) CodeExpiry() time.Duration {
|
||||||
|
return wm.codeExpiry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) Code() *crypto.CryptoValue {
|
||||||
|
return wm.code
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHumanOTPSMSCodeWriteModel(userID, resourceOwner string) *HumanOTPSMSCodeWriteModel {
|
||||||
|
return &HumanOTPSMSCodeWriteModel{
|
||||||
|
HumanOTPSMSWriteModel: NewHumanOTPSMSWriteModel(userID, resourceOwner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
if e, ok := event.(*user.HumanOTPSMSCodeAddedEvent); ok {
|
||||||
|
wm.code = e.Code
|
||||||
|
wm.codeCreationDate = e.CreationDate()
|
||||||
|
wm.codeExpiry = e.Expiry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.HumanOTPSMSWriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPSMSCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(user.AggregateType).
|
||||||
|
AggregateIDs(wm.AggregateID).
|
||||||
|
EventTypes(
|
||||||
|
user.HumanOTPSMSCodeAddedType,
|
||||||
|
user.HumanPhoneVerifiedType,
|
||||||
|
user.HumanOTPSMSAddedType,
|
||||||
|
user.HumanOTPSMSRemovedType,
|
||||||
|
user.HumanPhoneRemovedType,
|
||||||
|
user.UserRemovedType,
|
||||||
|
).
|
||||||
|
Builder()
|
||||||
|
|
||||||
|
if wm.WriteModel.ResourceOwner != "" {
|
||||||
|
query.ResourceOwner(wm.WriteModel.ResourceOwner)
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
@ -120,6 +200,14 @@ type HumanOTPEmailWriteModel struct {
|
|||||||
otpAdded bool
|
otpAdded bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailWriteModel) OTPAdded() bool {
|
||||||
|
return wm.otpAdded
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailWriteModel) ResourceOwner() string {
|
||||||
|
return wm.WriteModel.ResourceOwner
|
||||||
|
}
|
||||||
|
|
||||||
func NewHumanOTPEmailWriteModel(userID, resourceOwner string) *HumanOTPEmailWriteModel {
|
func NewHumanOTPEmailWriteModel(userID, resourceOwner string) *HumanOTPEmailWriteModel {
|
||||||
return &HumanOTPEmailWriteModel{
|
return &HumanOTPEmailWriteModel{
|
||||||
WriteModel: eventstore.WriteModel{
|
WriteModel: eventstore.WriteModel{
|
||||||
@ -151,15 +239,73 @@ func (wm *HumanOTPEmailWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
AddQuery().
|
AddQuery().
|
||||||
AggregateTypes(user.AggregateType).
|
AggregateTypes(user.AggregateType).
|
||||||
AggregateIDs(wm.AggregateID).
|
AggregateIDs(wm.AggregateID).
|
||||||
EventTypes(user.HumanEmailVerifiedType,
|
EventTypes(
|
||||||
|
user.HumanEmailVerifiedType,
|
||||||
user.HumanOTPEmailAddedType,
|
user.HumanOTPEmailAddedType,
|
||||||
user.HumanOTPEmailRemovedType,
|
user.HumanOTPEmailRemovedType,
|
||||||
user.UserRemovedType,
|
user.UserRemovedType,
|
||||||
).
|
).
|
||||||
Builder()
|
Builder()
|
||||||
|
|
||||||
if wm.ResourceOwner != "" {
|
if wm.WriteModel.ResourceOwner != "" {
|
||||||
query.ResourceOwner(wm.ResourceOwner)
|
query.ResourceOwner(wm.WriteModel.ResourceOwner)
|
||||||
|
}
|
||||||
|
return query
|
||||||
|
}
|
||||||
|
|
||||||
|
type HumanOTPEmailCodeWriteModel struct {
|
||||||
|
*HumanOTPEmailWriteModel
|
||||||
|
|
||||||
|
code *crypto.CryptoValue
|
||||||
|
codeCreationDate time.Time
|
||||||
|
codeExpiry time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) CodeCreationDate() time.Time {
|
||||||
|
return wm.codeCreationDate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) CodeExpiry() time.Duration {
|
||||||
|
return wm.codeExpiry
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) Code() *crypto.CryptoValue {
|
||||||
|
return wm.code
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHumanOTPEmailCodeWriteModel(userID, resourceOwner string) *HumanOTPEmailCodeWriteModel {
|
||||||
|
return &HumanOTPEmailCodeWriteModel{
|
||||||
|
HumanOTPEmailWriteModel: NewHumanOTPEmailWriteModel(userID, resourceOwner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) Reduce() error {
|
||||||
|
for _, event := range wm.Events {
|
||||||
|
if e, ok := event.(*user.HumanOTPEmailCodeAddedEvent); ok {
|
||||||
|
wm.code = e.Code
|
||||||
|
wm.codeCreationDate = e.CreationDate()
|
||||||
|
wm.codeExpiry = e.Expiry
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wm.HumanOTPEmailWriteModel.Reduce()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wm *HumanOTPEmailCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||||
|
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
|
AddQuery().
|
||||||
|
AggregateTypes(user.AggregateType).
|
||||||
|
AggregateIDs(wm.AggregateID).
|
||||||
|
EventTypes(
|
||||||
|
user.HumanOTPEmailCodeAddedType,
|
||||||
|
user.HumanEmailVerifiedType,
|
||||||
|
user.HumanOTPEmailAddedType,
|
||||||
|
user.HumanOTPEmailRemovedType,
|
||||||
|
user.UserRemovedType,
|
||||||
|
).
|
||||||
|
Builder()
|
||||||
|
|
||||||
|
if wm.WriteModel.ResourceOwner != "" {
|
||||||
|
query.ResourceOwner(wm.WriteModel.ResourceOwner)
|
||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -97,7 +97,7 @@ func (c *Commands) VerifyHumanPhone(ctx context.Context, userID, code, resourceo
|
|||||||
return nil, caos_errs.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid")
|
return nil, caos_errs.ThrowInvalidArgument(err, "COMMAND-sM0cs", "Errors.User.Code.Invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string, phoneCodeGenerator crypto.Generator) (*domain.ObjectDetails, error) {
|
func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID, resourceowner string) (*domain.ObjectDetails, error) {
|
||||||
if userID == "" {
|
if userID == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
|
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M0ds", "Errors.User.UserIDMissing")
|
||||||
}
|
}
|
||||||
@ -116,19 +116,17 @@ func (c *Commands) CreateHumanPhoneVerificationCode(ctx context.Context, userID,
|
|||||||
if existingPhone.IsPhoneVerified {
|
if existingPhone.IsPhoneVerified {
|
||||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified")
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-2M9sf", "Errors.User.Phone.AlreadyVerified")
|
||||||
}
|
}
|
||||||
|
config, err := secretGeneratorConfig(ctx, c.eventstore.Filter, domain.SecretGeneratorTypeVerifyPhoneCode)
|
||||||
phoneCode, err := domain.NewPhoneCode(phoneCodeGenerator)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
phoneCode, err := domain.NewPhoneCode(crypto.NewEncryptionGenerator(*config, c.userEncryption))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
|
userAgg := UserAggregateFromWriteModel(&existingPhone.WriteModel)
|
||||||
pushedEvents, err := c.eventstore.Push(ctx, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.Code, phoneCode.Expiry))
|
if err = c.pushAppendAndReduce(ctx, existingPhone, user.NewHumanPhoneCodeAddedEvent(ctx, userAgg, phoneCode.Code, phoneCode.Expiry)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
err = AppendAndReduce(existingPhone, pushedEvents...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return writeModelToObjectDetails(&existingPhone.WriteModel), nil
|
return writeModelToObjectDetails(&existingPhone.WriteModel), nil
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
@ -14,6 +15,7 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
"github.com/zitadel/zitadel/internal/eventstore/repository"
|
||||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||||
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
"github.com/zitadel/zitadel/internal/repository/user"
|
"github.com/zitadel/zitadel/internal/repository/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -584,13 +586,13 @@ func TestCommandSide_VerifyHumanPhone(t *testing.T) {
|
|||||||
|
|
||||||
func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
|
func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
|
||||||
type fields struct {
|
type fields struct {
|
||||||
eventstore *eventstore.Eventstore
|
eventstore *eventstore.Eventstore
|
||||||
|
userEncryption crypto.EncryptionAlgorithm
|
||||||
}
|
}
|
||||||
type args struct {
|
type args struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
userID string
|
userID string
|
||||||
resourceOwner string
|
resourceOwner string
|
||||||
secretGenerator crypto.Generator
|
|
||||||
}
|
}
|
||||||
type res struct {
|
type res struct {
|
||||||
want *domain.ObjectDetails
|
want *domain.ObjectDetails
|
||||||
@ -704,6 +706,19 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
expectFilter(
|
||||||
|
eventFromEventPusher(
|
||||||
|
instance.NewSecretGeneratorAddedEvent(context.Background(),
|
||||||
|
&instance.NewAggregate("instanceID").Aggregate,
|
||||||
|
domain.SecretGeneratorTypeVerifyPhoneCode,
|
||||||
|
8,
|
||||||
|
time.Hour,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
)),
|
||||||
|
),
|
||||||
expectPush(
|
expectPush(
|
||||||
[]*repository.Event{
|
[]*repository.Event{
|
||||||
eventFromEventPusher(
|
eventFromEventPusher(
|
||||||
@ -713,7 +728,7 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
|
|||||||
CryptoType: crypto.TypeEncryption,
|
CryptoType: crypto.TypeEncryption,
|
||||||
Algorithm: "enc",
|
Algorithm: "enc",
|
||||||
KeyID: "id",
|
KeyID: "id",
|
||||||
Crypted: []byte("a"),
|
Crypted: []byte("12345678"),
|
||||||
},
|
},
|
||||||
time.Hour*1,
|
time.Hour*1,
|
||||||
),
|
),
|
||||||
@ -721,12 +736,12 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
userEncryption: crypto.CreateMockEncryptionAlgWithCode(gomock.NewController(t), "12345678"),
|
||||||
},
|
},
|
||||||
args: args{
|
args: args{
|
||||||
ctx: context.Background(),
|
ctx: context.Background(),
|
||||||
userID: "user1",
|
userID: "user1",
|
||||||
resourceOwner: "org1",
|
resourceOwner: "org1",
|
||||||
secretGenerator: GetMockSecretGenerator(t),
|
|
||||||
},
|
},
|
||||||
res: res{
|
res: res{
|
||||||
want: &domain.ObjectDetails{
|
want: &domain.ObjectDetails{
|
||||||
@ -738,9 +753,10 @@ func TestCommandSide_CreateVerificationCodeHumanPhone(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
r := &Commands{
|
r := &Commands{
|
||||||
eventstore: tt.fields.eventstore,
|
eventstore: tt.fields.eventstore,
|
||||||
|
userEncryption: tt.fields.userEncryption,
|
||||||
}
|
}
|
||||||
got, err := r.CreateHumanPhoneVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner, tt.args.secretGenerator)
|
got, err := r.CreateHumanPhoneVerificationCode(tt.args.ctx, tt.args.userID, tt.args.resourceOwner)
|
||||||
if tt.res.err == nil {
|
if tt.res.err == nil {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
@ -9,14 +9,37 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CreateMockEncryptionAlg(ctrl *gomock.Controller) EncryptionAlgorithm {
|
func CreateMockEncryptionAlg(ctrl *gomock.Controller) EncryptionAlgorithm {
|
||||||
|
return createMockEncryptionAlgorithm(
|
||||||
|
ctrl,
|
||||||
|
func(code []byte) ([]byte, error) {
|
||||||
|
return code, nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMockEncryptionAlgWithCode compares the length of the value to be encrypted with the length of the provided code.
|
||||||
|
// It will return an error if they do not match.
|
||||||
|
// The provided code will be used to encrypt in favor of the value passed to the encryption.
|
||||||
|
// This function is intended to be used where the passed value is not in control, but where the returned encryption requires a static value.
|
||||||
|
func CreateMockEncryptionAlgWithCode(ctrl *gomock.Controller, code string) EncryptionAlgorithm {
|
||||||
|
return createMockEncryptionAlgorithm(
|
||||||
|
ctrl,
|
||||||
|
func(c []byte) ([]byte, error) {
|
||||||
|
if len(c) != len(code) {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "id", "invalid code length - expected %d, got %d", len(code), len(c))
|
||||||
|
}
|
||||||
|
return []byte(code), nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createMockEncryptionAlgorithm(ctrl *gomock.Controller, encryptFunction func(c []byte) ([]byte, error)) *MockEncryptionAlgorithm {
|
||||||
mCrypto := NewMockEncryptionAlgorithm(ctrl)
|
mCrypto := NewMockEncryptionAlgorithm(ctrl)
|
||||||
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
|
mCrypto.EXPECT().Algorithm().AnyTimes().Return("enc")
|
||||||
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
|
mCrypto.EXPECT().EncryptionKeyID().AnyTimes().Return("id")
|
||||||
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
|
mCrypto.EXPECT().DecryptionKeyIDs().AnyTimes().Return([]string{"id"})
|
||||||
mCrypto.EXPECT().Encrypt(gomock.Any()).AnyTimes().DoAndReturn(
|
mCrypto.EXPECT().Encrypt(gomock.Any()).AnyTimes().DoAndReturn(
|
||||||
func(code []byte) ([]byte, error) {
|
encryptFunction,
|
||||||
return code, nil
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(
|
mCrypto.EXPECT().DecryptString(gomock.Any(), gomock.Any()).AnyTimes().DoAndReturn(
|
||||||
func(code []byte, keyID string) (string, error) {
|
func(code []byte, keyID string) (string, error) {
|
||||||
|
@ -105,6 +105,8 @@ const (
|
|||||||
MFATypeTOTP MFAType = iota
|
MFATypeTOTP MFAType = iota
|
||||||
MFATypeU2F
|
MFATypeU2F
|
||||||
MFATypeU2FUserVerification
|
MFATypeU2FUserVerification
|
||||||
|
MFATypeOTPSMS
|
||||||
|
MFATypeOTPEmail
|
||||||
)
|
)
|
||||||
|
|
||||||
type MFALevel int
|
type MFALevel int
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
"github.com/zitadel/zitadel/internal/crypto"
|
||||||
"github.com/zitadel/zitadel/internal/domain"
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
@ -106,6 +107,14 @@ func (u *userNotifier) reducers() []handler.AggregateReducer {
|
|||||||
Event: user.HumanPasswordChangedType,
|
Event: user.HumanPasswordChangedType,
|
||||||
Reduce: u.reducePasswordChanged,
|
Reduce: u.reducePasswordChanged,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanOTPSMSCodeAddedType,
|
||||||
|
Reduce: u.reduceOTPSMSCodeAdded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanOTPEmailCodeAddedType,
|
||||||
|
Reduce: u.reduceOTPEmailCodeAdded,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -332,6 +341,136 @@ func (u *userNotifier) reducePasswordCodeAdded(event eventstore.Event) (*handler
|
|||||||
return crdb.NewNoOpStatement(e), nil
|
return crdb.NewNoOpStatement(e), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *userNotifier) reduceOTPSMSCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanOTPSMSCodeAddedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ASF3g", "reduce.wrong.event.type %s", user.HumanOTPSMSCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := HandlerContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
|
user.HumanOTPSMSCodeAddedType, user.HumanOTPSMSCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifySMSOTPMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, origin, err := u.queries.Origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
notify := types.SendSMSTwilio(
|
||||||
|
ctx,
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
u.queries.GetTwilioConfig,
|
||||||
|
u.queries.GetFileSystemProvider,
|
||||||
|
u.queries.GetLogProvider,
|
||||||
|
colors,
|
||||||
|
u.assetsPrefix(ctx),
|
||||||
|
e,
|
||||||
|
u.metricSuccessfulDeliveriesSMS,
|
||||||
|
u.metricFailedDeliveriesSMS,
|
||||||
|
)
|
||||||
|
err = notify.SendOTPSMSCode(authz.GetInstance(ctx).RequestedDomain(), origin, code, e.Expiry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = u.commands.HumanOTPSMSCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userNotifier) reduceOTPEmailCodeAdded(event eventstore.Event) (*handler.Statement, error) {
|
||||||
|
e, ok := event.(*user.HumanOTPEmailCodeAddedEvent)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-JL3hw", "reduce.wrong.event.type %s", user.HumanOTPEmailCodeAddedType)
|
||||||
|
}
|
||||||
|
ctx := HandlerContext(event.Aggregate())
|
||||||
|
alreadyHandled, err := u.checkIfCodeAlreadyHandledOrExpired(ctx, event, e.Expiry, nil,
|
||||||
|
user.HumanOTPEmailCodeAddedType, user.HumanOTPEmailCodeSentType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if alreadyHandled {
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
code, err := crypto.DecryptString(e.Code, u.queries.UserDataCrypto)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
colors, err := u.queries.ActiveLabelPolicyByOrg(ctx, e.Aggregate().ResourceOwner, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
template, err := u.queries.MailTemplateByOrg(ctx, e.Aggregate().ResourceOwner, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyUser, err := u.queries.GetNotifyUserByID(ctx, true, e.Aggregate().ID, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
translator, err := u.queries.GetTranslatorWithOrgTexts(ctx, notifyUser.ResourceOwner, domain.VerifyEmailOTPMessageType)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, origin, err := u.queries.Origin(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var authRequestID string
|
||||||
|
if e.AuthRequestInfo != nil {
|
||||||
|
authRequestID = e.AuthRequestInfo.ID
|
||||||
|
}
|
||||||
|
notify := types.SendEmail(
|
||||||
|
ctx,
|
||||||
|
string(template.Template),
|
||||||
|
translator,
|
||||||
|
notifyUser,
|
||||||
|
u.queries.GetSMTPConfig,
|
||||||
|
u.queries.GetFileSystemProvider,
|
||||||
|
u.queries.GetLogProvider,
|
||||||
|
colors,
|
||||||
|
u.assetsPrefix(ctx),
|
||||||
|
e,
|
||||||
|
u.metricSuccessfulDeliveriesEmail,
|
||||||
|
u.metricFailedDeliveriesEmail,
|
||||||
|
)
|
||||||
|
err = notify.SendOTPEmailCode(notifyUser, authz.GetInstance(ctx).RequestedDomain(), origin, code, authRequestID, e.Expiry)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = u.commands.HumanOTPEmailCodeSent(ctx, e.Aggregate().ID, e.Aggregate().ResourceOwner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return crdb.NewNoOpStatement(e), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Statement, error) {
|
func (u *userNotifier) reduceDomainClaimed(event eventstore.Event) (*handler.Statement, error) {
|
||||||
e, ok := event.(*user.DomainClaimedEvent)
|
e, ok := event.(*user.DomainClaimedEvent)
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -583,7 +722,7 @@ func (u *userNotifier) reducePhoneCodeAdded(event eventstore.Event) (*handler.St
|
|||||||
e,
|
e,
|
||||||
u.metricSuccessfulDeliveriesSMS,
|
u.metricSuccessfulDeliveriesSMS,
|
||||||
u.metricFailedDeliveriesSMS,
|
u.metricFailedDeliveriesSMS,
|
||||||
).SendPhoneVerificationCode(notifyUser, origin, code)
|
).SendPhoneVerificationCode(notifyUser, origin, code, authz.GetInstance(ctx).RequestedDomain())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,10 @@ VerifyEmailOTP:
|
|||||||
Text: Моля, използвай бутона 'Удостовери' или копирай временната парола {{.OTP}} и я постави на екрана за удостоверяване, за да се удостовериш в ZITADEL в рамките на следващите пет минути.
|
Text: Моля, използвай бутона 'Удостовери' или копирай временната парола {{.OTP}} и я постави на екрана за удостоверяване, за да се удостовериш в ZITADEL в рамките на следващите пет минути.
|
||||||
ButtonText: Удостовери
|
ButtonText: Удостовери
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Моля, посети {{ .VerifyURL }} или копирай временната парола {{.OTP}} и я постави на екрана за удостоверяване, за да се удостовериш в ZITADEL в рамките на следващите пет минути.
|
Text: >-
|
||||||
|
{{.OTP}} е вашата еднократна парола за {{ .Domain }}. Използвайте го в рамките на следващия {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Domain}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Домейнът е заявен
|
Title: ZITADEL - Домейнът е заявен
|
||||||
PreHeader: Промяна на имейл/потребителско име
|
PreHeader: Промяна на имейл/потребителско име
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Bitte nutze den 'Authentifizieren'-Button oder kopiere das Einmalpasswort {{.OTP}} und füge es in den Authentifizierungsbildschirm ein, um dich innerhalb der nächsten fünf Minuten bei ZITADEL zu authentifizieren.
|
Text: Bitte nutze den 'Authentifizieren'-Button oder kopiere das Einmalpasswort {{.OTP}} und füge es in den Authentifizierungsbildschirm ein, um dich innerhalb der nächsten fünf Minuten bei ZITADEL zu authentifizieren.
|
||||||
ButtonText: Authentifizieren
|
ButtonText: Authentifizieren
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Bitte besuche {{ .VerifyURL }} oder kopiere das Einmalpasswort {{.OTP}} und füge es in den Authentifizierungsbildschirm ein, um dich innerhalb der nächsten fünf Minuten bei ZITADEL zu authentifizieren.
|
Text: >-
|
||||||
|
{{.OTP}} ist Ihr Einmalpasswort für {{ .Domain }}. Verwenden Sie es innerhalb der nächsten {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Domain}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Domain wurde beansprucht
|
Title: ZITADEL - Domain wurde beansprucht
|
||||||
PreHeader: Email / Username ändern
|
PreHeader: Email / Username ändern
|
||||||
|
@ -31,10 +31,13 @@ VerifyEmailOTP:
|
|||||||
PreHeader: Verify One-Time Password
|
PreHeader: Verify One-Time Password
|
||||||
Subject: Verify One-Time Password
|
Subject: Verify One-Time Password
|
||||||
Greeting: Hello {{.DisplayName}},
|
Greeting: Hello {{.DisplayName}},
|
||||||
Text: Please use the "Authenticate" button or copy the one-time password {{.OTP}} and paste it to to the authentication screen in order to authenticate at ZITADEL within the next five minutes.
|
Text: Please use the one-time password {{.OTP}} to authenticate at ZITADEL within the next five minutes or click the "Authenticate" button.
|
||||||
ButtonText: Authenticate
|
ButtonText: Authenticate
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Please visit {{ .VerifyURL }} or copy the one-time password {{.OTP}} and paste it to to the authentication screen in order to authenticate at ZITADEL within the next five minutes.
|
Text: >-
|
||||||
|
{{.OTP}} is your one-time-password for {{ .Domain }}. Use it within the next {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Domain}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Domain has been claimed
|
Title: ZITADEL - Domain has been claimed
|
||||||
PreHeader: Change email / username
|
PreHeader: Change email / username
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Por favor, utiliza el botón 'Autenticar' o copia la contraseña de un solo uso {{.OTP}} y pégala en la pantalla de autenticación para autenticarte en ZITADEL en los próximos cinco minutos.
|
Text: Por favor, utiliza el botón 'Autenticar' o copia la contraseña de un solo uso {{.OTP}} y pégala en la pantalla de autenticación para autenticarte en ZITADEL en los próximos cinco minutos.
|
||||||
ButtonText: Autenticar
|
ButtonText: Autenticar
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Por favor, visita {{ .VerifyURL }} o copia la contraseña de un solo uso {{.OTP}} y pégala en la pantalla de autenticación para autenticarte en ZITADEL en los próximos cinco minutos.
|
Text: >-
|
||||||
|
{{.OTP}} es su contraseña de un solo uso para {{ .Domain }}. Úselo dentro de los próximos {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Dominio}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Se ha reclamado un dominio
|
Title: ZITADEL - Se ha reclamado un dominio
|
||||||
PreHeader: Cambiar dirección de correo electrónico / nombre de usuario
|
PreHeader: Cambiar dirección de correo electrónico / nombre de usuario
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Utilise le bouton 'Authentifier' ou copie le mot de passe à usage unique {{.OTP}} et colle-le à l'écran d'authentification pour t'authentifier sur ZITADEL dans les cinq prochaines minutes.
|
Text: Utilise le bouton 'Authentifier' ou copie le mot de passe à usage unique {{.OTP}} et colle-le à l'écran d'authentification pour t'authentifier sur ZITADEL dans les cinq prochaines minutes.
|
||||||
ButtonText: Authentifier
|
ButtonText: Authentifier
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Visite {{ .VerifyURL }} ou copie le mot de passe à usage unique {{.OTP}} et colle-le à l'écran d'authentification pour t'authentifier sur ZITADEL dans les cinq prochaines minutes.
|
Text: >-
|
||||||
|
{{.OTP}} est votre mot de passe à usage unique pour {{ .Domain }}. Utilisez-le dans le prochain {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Domaine}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Le domaine a été réclamé
|
Title: ZITADEL - Le domaine a été réclamé
|
||||||
PreHeader: Modifier l'email / le nom d'utilisateur
|
PreHeader: Modifier l'email / le nom d'utilisateur
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Per favore, utilizza il pulsante 'Autentica' o copia la password monouso {{.OTP}} e incollala nella schermata di autenticazione per autenticarti a ZITADEL entro i prossimi cinque minuti.
|
Text: Per favore, utilizza il pulsante 'Autentica' o copia la password monouso {{.OTP}} e incollala nella schermata di autenticazione per autenticarti a ZITADEL entro i prossimi cinque minuti.
|
||||||
ButtonText: Autentica
|
ButtonText: Autentica
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Per favore, visita {{ .VerifyURL }} o copia la password monouso {{.OTP}} e incollala nella schermata di autenticazione per autenticarti a ZITADEL entro i prossimi cinque minuti.
|
Text: >-
|
||||||
|
{{.OTP}} è la tua password monouso per {{ .Domain }}. Usalo entro il prossimo {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Dominio}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Il dominio è stato rivendicato
|
Title: ZITADEL - Il dominio è stato rivendicato
|
||||||
PreHeader: Cambiare email / nome utente
|
PreHeader: Cambiare email / nome utente
|
||||||
|
@ -31,10 +31,13 @@ VerifyEmailOTP:
|
|||||||
PreHeader: ワンタイムパスワードを確認する
|
PreHeader: ワンタイムパスワードを確認する
|
||||||
Subject: ワンタイムパスワードを確認する
|
Subject: ワンタイムパスワードを確認する
|
||||||
Greeting: こんにちは、{{.DisplayName}}さん
|
Greeting: こんにちは、{{.DisplayName}}さん
|
||||||
Text: '認証'ボタンを使用するか、ワンタイムパスワード {{.OTP}} をコピーして認証画面に貼り付け、次の5分以内にZITADELで認証してください。
|
Text: 認証ボタンを使用するか、ワンタイムパスワード {{.OTP}} をコピーして認証画面に貼り付け、次の5分以内にZITADELで認証してください。
|
||||||
ButtonText: 認証
|
ButtonText: 認証
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: {{ .VerifyURL }} を訪れるか、ワンタイムパスワード {{.OTP}} をコピーして認証画面に貼り付け、次の5分以内にZITADELで認証してください。
|
Text: >-
|
||||||
|
{{.OTP}} は、{{ .Domain }} のワンタイムパスワードです。次の {{.Expiry}} 以内に使用してください。
|
||||||
|
|
||||||
|
@{{.ドメイン}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - ドメインの登録
|
Title: ZITADEL - ドメインの登録
|
||||||
PreHeader: メールアドレス・ユーザー名の変更
|
PreHeader: メールアドレス・ユーザー名の変更
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Ве молам, користи го копчето 'Автентицирај' или копирај ја еднократната лозинка {{.OTP}} и стави ја на екранот за автентикација за да се автентицираш на ZITADEL во следните пет минути.
|
Text: Ве молам, користи го копчето 'Автентицирај' или копирај ја еднократната лозинка {{.OTP}} и стави ја на екранот за автентикација за да се автентицираш на ZITADEL во следните пет минути.
|
||||||
ButtonText: Автентицирај
|
ButtonText: Автентицирај
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Ве молам, посети го {{ .VerifyURL }} или копирај ја еднократната лозинка {{.OTP}} и стави ја на екранот за автентикација за да се автентицираш на ZITADEL во следните пет минути.
|
Text: >-
|
||||||
|
{{.OTP}} е вашата еднократна лозинка за {{ .Домен }}. Користете го во следниот {{.Истек}}.
|
||||||
|
|
||||||
|
@{{.Домен}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Доменот е преземен
|
Title: ZITADEL - Доменот е преземен
|
||||||
PreHeader: Промена на е-пошта / корисничко име
|
PreHeader: Промена на е-пошта / корисничко име
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Proszę, użyj przycisku 'Uwierzytelnij' lub skopiuj hasło jednorazowe {{.OTP}} i wklej go na ekran uwierzytelniania, aby uwierzytelnić się w ZITADEL w ciągu najbliższych pięciu minut.
|
Text: Proszę, użyj przycisku 'Uwierzytelnij' lub skopiuj hasło jednorazowe {{.OTP}} i wklej go na ekran uwierzytelniania, aby uwierzytelnić się w ZITADEL w ciągu najbliższych pięciu minut.
|
||||||
ButtonText: Uwierzytelnij
|
ButtonText: Uwierzytelnij
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Proszę, odwiedź {{ .VerifyURL }} lub skopiuj hasło jednorazowe {{.OTP}} i wklej go na ekran uwierzytelniania, aby uwierzytelnić się w ZITADEL w ciągu najbliższych pięciu minut.
|
Text: >-
|
||||||
|
{{.OTP}} to Twoje jednorazowe hasło do domeny {{ .Domain }}. Użyj go w ciągu najbliższych {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Domena}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Domena została zarejestrowana
|
Title: ZITADEL - Domena została zarejestrowana
|
||||||
PreHeader: Zmiana adresu e-mail / nazwy użytkownika
|
PreHeader: Zmiana adresu e-mail / nazwy użytkownika
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: Por favor, usa o botão 'Autenticar' ou copia a senha de uso único {{.OTP}} e cola-a na tela de autenticação para te autenticares no ZITADEL nos próximos cinco minutos.
|
Text: Por favor, usa o botão 'Autenticar' ou copia a senha de uso único {{.OTP}} e cola-a na tela de autenticação para te autenticares no ZITADEL nos próximos cinco minutos.
|
||||||
ButtonText: Autenticar
|
ButtonText: Autenticar
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: Por favor, visita {{ .VerifyURL }} ou copia a senha de uso único {{.OTP}} e cola-a na tela de autenticação para te autenticares no ZITADEL nos próximos cinco minutos.
|
Text: >-
|
||||||
|
{{.OTP}} é sua senha única para {{ .Domain }}. Use-o nos próximos {{.Expiry}}.
|
||||||
|
|
||||||
|
@{{.Domain}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - Domínio foi reivindicado
|
Title: ZITADEL - Domínio foi reivindicado
|
||||||
PreHeader: Alterar e-mail / nome de usuário
|
PreHeader: Alterar e-mail / nome de usuário
|
||||||
|
@ -34,7 +34,10 @@ VerifyEmailOTP:
|
|||||||
Text: 请使用 '验证' 按钮,或复制一次性密码 {{.OTP}} 并将其粘贴到验证屏幕中,以在接下来的五分钟内在 ZITADEL 中进行验证。
|
Text: 请使用 '验证' 按钮,或复制一次性密码 {{.OTP}} 并将其粘贴到验证屏幕中,以在接下来的五分钟内在 ZITADEL 中进行验证。
|
||||||
ButtonText: 验证
|
ButtonText: 验证
|
||||||
VerifySMSOTP:
|
VerifySMSOTP:
|
||||||
Text: 请访问 {{ .VerifyURL }} 或复制一次性密码 {{.OTP}} 并将其粘贴到身份验证屏幕,以在接下来的五分钟内在ZITADEL进行身份验证。
|
Text: >-
|
||||||
|
{{.OTP}} 是您的 {{ .Domain }} 的一次性密码。在下一个 {{.Expiry}} 内使用它。
|
||||||
|
|
||||||
|
@{{.Domain}} #{{.OTP}}
|
||||||
DomainClaimed:
|
DomainClaimed:
|
||||||
Title: ZITADEL - 域名所有权验证
|
Title: ZITADEL - 域名所有权验证
|
||||||
PreHeader: 更改电子邮件/用户名
|
PreHeader: 更改电子邮件/用户名
|
||||||
|
29
internal/notification/types/otp.go
Normal file
29
internal/notification/types/otp.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
||||||
|
"github.com/zitadel/zitadel/internal/domain"
|
||||||
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (notify Notify) SendOTPSMSCode(requestedDomain, origin, code string, expiry time.Duration) error {
|
||||||
|
args := otpArgs(code, origin, requestedDomain, expiry)
|
||||||
|
return notify("", args, domain.VerifySMSOTPMessageType, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (notify Notify) SendOTPEmailCode(user *query.NotifyUser, requestedDomain, origin, code, authRequestID string, expiry time.Duration) error {
|
||||||
|
url := login.OTPLink(origin, authRequestID, code, domain.MFATypeOTPEmail)
|
||||||
|
args := otpArgs(code, origin, requestedDomain, expiry)
|
||||||
|
return notify(url, args, domain.VerifyEmailOTPMessageType, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func otpArgs(code, origin, requestedDomain string, expiry time.Duration) map[string]interface{} {
|
||||||
|
args := make(map[string]interface{})
|
||||||
|
args["OTP"] = code
|
||||||
|
args["Origin"] = origin
|
||||||
|
args["Domain"] = requestedDomain
|
||||||
|
args["Expiry"] = expiry
|
||||||
|
return args
|
||||||
|
}
|
@ -5,8 +5,9 @@ import (
|
|||||||
"github.com/zitadel/zitadel/internal/query"
|
"github.com/zitadel/zitadel/internal/query"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (notify Notify) SendPhoneVerificationCode(user *query.NotifyUser, origin, code string) error {
|
func (notify Notify) SendPhoneVerificationCode(user *query.NotifyUser, origin, code, requestedDomain string) error {
|
||||||
args := make(map[string]interface{})
|
args := make(map[string]interface{})
|
||||||
args["Code"] = code
|
args["Code"] = code
|
||||||
|
args["Domain"] = requestedDomain
|
||||||
return notify("", args, domain.VerifyPhoneMessageType, true)
|
return notify("", args, domain.VerifyPhoneMessageType, true)
|
||||||
}
|
}
|
||||||
|
@ -113,6 +113,14 @@ func (p *userAuthMethodProjection) reducers() []handler.AggregateReducer {
|
|||||||
Event: user.HumanOTPSMSRemovedType,
|
Event: user.HumanOTPSMSRemovedType,
|
||||||
Reduce: p.reduceRemoveAuthMethod,
|
Reduce: p.reduceRemoveAuthMethod,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Event: user.HumanPhoneRemovedType,
|
||||||
|
Reduce: p.reduceRemoveAuthMethod,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Event: user.UserV1PhoneRemovedType,
|
||||||
|
Reduce: p.reduceRemoveAuthMethod,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Event: user.HumanOTPEmailRemovedType,
|
Event: user.HumanOTPEmailRemovedType,
|
||||||
Reduce: p.reduceRemoveAuthMethod,
|
Reduce: p.reduceRemoveAuthMethod,
|
||||||
|
@ -168,7 +168,7 @@ func ChangeSecretGeneratorIncludeDigits(includeDigits bool) func(event *SecretGe
|
|||||||
|
|
||||||
func ChangeSecretGeneratorIncludeSymbols(includeSymbols bool) func(event *SecretGeneratorChangedEvent) {
|
func ChangeSecretGeneratorIncludeSymbols(includeSymbols bool) func(event *SecretGeneratorChangedEvent) {
|
||||||
return func(e *SecretGeneratorChangedEvent) {
|
return func(e *SecretGeneratorChangedEvent) {
|
||||||
e.IncludeDigits = &includeSymbols
|
e.IncludeSymbols = &includeSymbols
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,10 +91,14 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
|||||||
RegisterFilterEventMapper(AggregateType, HumanMFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper).
|
RegisterFilterEventMapper(AggregateType, HumanMFAOTPCheckFailedType, HumanOTPCheckFailedEventMapper).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPSMSAddedType, eventstore.GenericEventMapper[HumanOTPSMSAddedEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPSMSAddedType, eventstore.GenericEventMapper[HumanOTPSMSAddedEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPSMSRemovedType, eventstore.GenericEventMapper[HumanOTPSMSRemovedEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPSMSRemovedType, eventstore.GenericEventMapper[HumanOTPSMSRemovedEvent]).
|
||||||
|
RegisterFilterEventMapper(AggregateType, HumanOTPSMSCodeAddedType, eventstore.GenericEventMapper[HumanOTPSMSCodeAddedEvent]).
|
||||||
|
RegisterFilterEventMapper(AggregateType, HumanOTPSMSCodeSentType, eventstore.GenericEventMapper[HumanOTPSMSCodeSentEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPSMSCheckSucceededType, eventstore.GenericEventMapper[HumanOTPSMSCheckSucceededEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPSMSCheckSucceededType, eventstore.GenericEventMapper[HumanOTPSMSCheckSucceededEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPSMSCheckFailedType, eventstore.GenericEventMapper[HumanOTPSMSCheckFailedEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPSMSCheckFailedType, eventstore.GenericEventMapper[HumanOTPSMSCheckFailedEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPEmailAddedType, eventstore.GenericEventMapper[HumanOTPEmailAddedEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPEmailAddedType, eventstore.GenericEventMapper[HumanOTPEmailAddedEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPEmailRemovedType, eventstore.GenericEventMapper[HumanOTPEmailRemovedEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPEmailRemovedType, eventstore.GenericEventMapper[HumanOTPEmailRemovedEvent]).
|
||||||
|
RegisterFilterEventMapper(AggregateType, HumanOTPEmailCodeAddedType, eventstore.GenericEventMapper[HumanOTPEmailCodeAddedEvent]).
|
||||||
|
RegisterFilterEventMapper(AggregateType, HumanOTPEmailCodeSentType, eventstore.GenericEventMapper[HumanOTPEmailCodeSentEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPEmailCheckSucceededType, eventstore.GenericEventMapper[HumanOTPEmailCheckSucceededEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPEmailCheckSucceededType, eventstore.GenericEventMapper[HumanOTPEmailCheckSucceededEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanOTPEmailCheckFailedType, eventstore.GenericEventMapper[HumanOTPEmailCheckFailedEvent]).
|
RegisterFilterEventMapper(AggregateType, HumanOTPEmailCheckFailedType, eventstore.GenericEventMapper[HumanOTPEmailCheckFailedEvent]).
|
||||||
RegisterFilterEventMapper(AggregateType, HumanU2FTokenAddedType, HumanU2FAddedEventMapper).
|
RegisterFilterEventMapper(AggregateType, HumanU2FTokenAddedType, HumanU2FAddedEventMapper).
|
||||||
|
@ -3,6 +3,7 @@ package user
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
|
|
||||||
@ -21,11 +22,15 @@ const (
|
|||||||
otpSMSEventPrefix = otpEventPrefix + "sms."
|
otpSMSEventPrefix = otpEventPrefix + "sms."
|
||||||
HumanOTPSMSAddedType = otpSMSEventPrefix + "added"
|
HumanOTPSMSAddedType = otpSMSEventPrefix + "added"
|
||||||
HumanOTPSMSRemovedType = otpSMSEventPrefix + "removed"
|
HumanOTPSMSRemovedType = otpSMSEventPrefix + "removed"
|
||||||
|
HumanOTPSMSCodeAddedType = otpSMSEventPrefix + "code.added"
|
||||||
|
HumanOTPSMSCodeSentType = otpSMSEventPrefix + "code.sent"
|
||||||
HumanOTPSMSCheckSucceededType = otpSMSEventPrefix + "check.succeeded"
|
HumanOTPSMSCheckSucceededType = otpSMSEventPrefix + "check.succeeded"
|
||||||
HumanOTPSMSCheckFailedType = otpSMSEventPrefix + "check.failed"
|
HumanOTPSMSCheckFailedType = otpSMSEventPrefix + "check.failed"
|
||||||
otpEmailEventPrefix = otpEventPrefix + "email."
|
otpEmailEventPrefix = otpEventPrefix + "email."
|
||||||
HumanOTPEmailAddedType = otpEmailEventPrefix + "added"
|
HumanOTPEmailAddedType = otpEmailEventPrefix + "added"
|
||||||
HumanOTPEmailRemovedType = otpEmailEventPrefix + "removed"
|
HumanOTPEmailRemovedType = otpEmailEventPrefix + "removed"
|
||||||
|
HumanOTPEmailCodeAddedType = otpEmailEventPrefix + "code.added"
|
||||||
|
HumanOTPEmailCodeSentType = otpEmailEventPrefix + "code.sent"
|
||||||
HumanOTPEmailCheckSucceededType = otpEmailEventPrefix + "check.succeeded"
|
HumanOTPEmailCheckSucceededType = otpEmailEventPrefix + "check.succeeded"
|
||||||
HumanOTPEmailCheckFailedType = otpEmailEventPrefix + "check.failed"
|
HumanOTPEmailCheckFailedType = otpEmailEventPrefix + "check.failed"
|
||||||
)
|
)
|
||||||
@ -271,6 +276,78 @@ func NewHumanOTPSMSRemovedEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HumanOTPSMSCodeAddedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
*AuthRequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPSMSCodeAddedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPSMSCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPSMSCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = *event
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHumanOTPSMSCodeAddedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
code *crypto.CryptoValue,
|
||||||
|
expiry time.Duration,
|
||||||
|
info *AuthRequestInfo,
|
||||||
|
) *HumanOTPSMSCodeAddedEvent {
|
||||||
|
return &HumanOTPSMSCodeAddedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
HumanOTPSMSCodeAddedType,
|
||||||
|
),
|
||||||
|
Code: code,
|
||||||
|
Expiry: expiry,
|
||||||
|
AuthRequestInfo: info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HumanOTPSMSCodeSentEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
*AuthRequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPSMSCodeSentEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPSMSCodeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPSMSCodeSentEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = *event
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHumanOTPSMSCodeSentEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
) *HumanOTPSMSCodeSentEvent {
|
||||||
|
return &HumanOTPSMSCodeSentEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
HumanOTPSMSCodeSentType,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type HumanOTPSMSCheckSucceededEvent struct {
|
type HumanOTPSMSCheckSucceededEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
*AuthRequestInfo
|
*AuthRequestInfo
|
||||||
@ -393,6 +470,78 @@ func NewHumanOTPEmailRemovedEvent(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type HumanOTPEmailCodeAddedEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
*AuthRequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPEmailCodeAddedEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPEmailCodeAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPEmailCodeAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = *event
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHumanOTPEmailCodeAddedEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
code *crypto.CryptoValue,
|
||||||
|
expiry time.Duration,
|
||||||
|
info *AuthRequestInfo,
|
||||||
|
) *HumanOTPEmailCodeAddedEvent {
|
||||||
|
return &HumanOTPEmailCodeAddedEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
HumanOTPEmailCodeAddedType,
|
||||||
|
),
|
||||||
|
Code: code,
|
||||||
|
Expiry: expiry,
|
||||||
|
AuthRequestInfo: info,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type HumanOTPEmailCodeSentEvent struct {
|
||||||
|
eventstore.BaseEvent `json:"-"`
|
||||||
|
|
||||||
|
Code *crypto.CryptoValue `json:"code,omitempty"`
|
||||||
|
Expiry time.Duration `json:"expiry,omitempty"`
|
||||||
|
*AuthRequestInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPEmailCodeSentEvent) Data() interface{} {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPEmailCodeSentEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *HumanOTPEmailCodeSentEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||||
|
e.BaseEvent = *event
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHumanOTPEmailCodeSentEvent(
|
||||||
|
ctx context.Context,
|
||||||
|
aggregate *eventstore.Aggregate,
|
||||||
|
) *HumanOTPEmailCodeSentEvent {
|
||||||
|
return &HumanOTPEmailCodeSentEvent{
|
||||||
|
BaseEvent: *eventstore.NewBaseEventForPush(
|
||||||
|
ctx,
|
||||||
|
aggregate,
|
||||||
|
HumanOTPEmailCodeSentType,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type HumanOTPEmailCheckSucceededEvent struct {
|
type HumanOTPEmailCheckSucceededEvent struct {
|
||||||
eventstore.BaseEvent `json:"-"`
|
eventstore.BaseEvent `json:"-"`
|
||||||
*AuthRequestInfo
|
*AuthRequestInfo
|
||||||
|
@ -630,6 +630,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Многофакторната OTP проверка е успешна
|
succeeded: Многофакторната OTP проверка е успешна
|
||||||
failed: Многофакторната OTP проверка е неуспешна
|
failed: Многофакторната OTP проверка е неуспешна
|
||||||
|
sms:
|
||||||
|
added: Добавен Многофакторната OTP SMS
|
||||||
|
removed: Премахнато многофакторно OTP SMS
|
||||||
|
code:
|
||||||
|
added: Добавен многофакторен OTP SMS код
|
||||||
|
sent: Многофакторната OTP SMS код е изпратен
|
||||||
|
check:
|
||||||
|
succeeded: Успешна многофакторна OTP SMS проверка
|
||||||
|
failed: Многофакторната OTP проверка на SMS не бе успешна
|
||||||
|
email:
|
||||||
|
added: Добавен Многофакторната OTP имейл
|
||||||
|
removed: Премахнат многофакторен OTP имейл
|
||||||
|
code:
|
||||||
|
added: Добавен многофакторен OTP имейл код
|
||||||
|
sent: Многофакторният OTP имейл код е изпратен
|
||||||
|
check:
|
||||||
|
succeeded: Многофакторната еднократна имейл потвърждение е успешна
|
||||||
|
failed: Многофакторната OTP проверка на имейл не бе успешна
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Добавен е многофакторен U2F токен
|
added: Добавен е многофакторен U2F токен
|
||||||
|
@ -618,6 +618,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Multifaktor OTP Verifikation erfolgreich
|
succeeded: Multifaktor OTP Verifikation erfolgreich
|
||||||
failed: Multifaktor OTP Verifikation fehlgeschlagen
|
failed: Multifaktor OTP Verifikation fehlgeschlagen
|
||||||
|
sms:
|
||||||
|
added: Multifaktor OTP SMS hinzugefügt
|
||||||
|
removed: Multifaktor OTP SMS entfernt
|
||||||
|
code:
|
||||||
|
added: Multifaktor OTP SMS Code hinzugefügt
|
||||||
|
sent: Multifaktor OTP SMS Code versendet
|
||||||
|
check:
|
||||||
|
succeeded: Multifaktor OTP SMS Verifikation erfolgreich
|
||||||
|
failed: Multifaktor OTP SMS Verifikation fehlgeschlagen
|
||||||
|
email:
|
||||||
|
added: Multifaktor OTP Email hinzugefügt
|
||||||
|
removed: Multifaktor OTP Email entfernt
|
||||||
|
code:
|
||||||
|
added: Multifaktor OTP Email Code hinzugefügt
|
||||||
|
sent: Multifaktor OTP Email Code versendet
|
||||||
|
check:
|
||||||
|
succeeded: Multifaktor OTP Email Verifikation erfolgreich
|
||||||
|
failed: Multifaktor OTP Email Verifikation fehlgeschlagen
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Multifaktor U2F Token hinzugefügt
|
added: Multifaktor U2F Token hinzugefügt
|
||||||
|
@ -618,6 +618,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Multifactor OTP check succeeded
|
succeeded: Multifactor OTP check succeeded
|
||||||
failed: Multifactor OTP check failed
|
failed: Multifactor OTP check failed
|
||||||
|
sms:
|
||||||
|
added: Multifactor OTP SMS added
|
||||||
|
removed: Multifactor OTP SMS removed
|
||||||
|
code:
|
||||||
|
added: Multifactor OTP SMS code added
|
||||||
|
sent: Multifactor OTP SMS code sent
|
||||||
|
check:
|
||||||
|
succeeded: Multifactor OTP SMS check succeeded
|
||||||
|
failed: Multifactor OTP SMS check failed
|
||||||
|
email:
|
||||||
|
added: Multifactor OTP Email added
|
||||||
|
removed: Multifactor OTP Email removed
|
||||||
|
code:
|
||||||
|
added: Multifactor OTP Email code added
|
||||||
|
sent: Multifactor OTP Email code sent
|
||||||
|
check:
|
||||||
|
succeeded: Multifactor OTP Email check succeeded
|
||||||
|
failed: Multifactor OTP Email check failed
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Multifactor U2F Token added
|
added: Multifactor U2F Token added
|
||||||
|
@ -618,6 +618,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Comprobación exitosa de Multifactor OTP
|
succeeded: Comprobación exitosa de Multifactor OTP
|
||||||
failed: Comprobación fallida de Multifactor OTP
|
failed: Comprobación fallida de Multifactor OTP
|
||||||
|
sms:
|
||||||
|
added: Multifactor OTP SMS añadido
|
||||||
|
removed: Multifactor OTP SMS elimonado
|
||||||
|
code:
|
||||||
|
added: Código Multifactor OTP SMS añadido
|
||||||
|
sent: Código Multifactor OTP SMS enviado
|
||||||
|
check:
|
||||||
|
succeeded: Comprobación Multifactor OTP SMS exitosa
|
||||||
|
failed: Comprobación Multifactor OTP SMS fallida
|
||||||
|
email:
|
||||||
|
added: Multifactor OTP email añadido
|
||||||
|
removed: Multifactor OTP email elimonado
|
||||||
|
code:
|
||||||
|
added: Código Multifactor OTP email añadido
|
||||||
|
sent: Código Multifactor OTP email enviado
|
||||||
|
check:
|
||||||
|
succeeded: Comprobación Multifactor OTP email exitosa
|
||||||
|
failed: Comprobación Multifactor OTP email fallida
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Multifactor U2F Token añadido
|
added: Multifactor U2F Token añadido
|
||||||
|
@ -616,6 +616,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Vérification de l'OTP multifactorielle réussie
|
succeeded: Vérification de l'OTP multifactorielle réussie
|
||||||
failed: La vérification de l'OTP multifactorielle a échoué
|
failed: La vérification de l'OTP multifactorielle a échoué
|
||||||
|
sms:
|
||||||
|
added: Ajout de SMS OTP multifactoriels
|
||||||
|
removed: Suppression des SMS OTP multifactoriels
|
||||||
|
code:
|
||||||
|
added: Ajout du code SMS OTP multifactoriel
|
||||||
|
sent: Code SMS OTP multifacteur envoyé
|
||||||
|
check:
|
||||||
|
succeeded: Vérification par SMS OTP multifacteur réussie
|
||||||
|
failed: Échec de la vérification par SMS OTP multifacteur
|
||||||
|
email:
|
||||||
|
added: Ajout d'un e-mail OTP multifactoriel
|
||||||
|
removed: Suppression de l'e-mail OTP multifacteur
|
||||||
|
code:
|
||||||
|
added: Ajout d'un code de messagerie OTP multifactoriel
|
||||||
|
sent: Code de messagerie OTP multifacteur envoyé
|
||||||
|
check:
|
||||||
|
succeeded: Vérification de l'e-mail OTP multifacteur réussie
|
||||||
|
failed: Échec de la vérification de l'e-mail OTP multifacteur
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Ajout d'un jeton U2F multifacteur
|
added: Ajout d'un jeton U2F multifacteur
|
||||||
|
@ -616,6 +616,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Controllo OTP riuscito
|
succeeded: Controllo OTP riuscito
|
||||||
failed: Controllo OTP fallito
|
failed: Controllo OTP fallito
|
||||||
|
sms:
|
||||||
|
added: Aggiunto SMS OTP
|
||||||
|
removed: OTP SMS rimosso
|
||||||
|
code:
|
||||||
|
added: Aggiunto codice OTP SMS
|
||||||
|
sent: Codice OTP SMS inviato
|
||||||
|
check:
|
||||||
|
succeeded: Controllo OTP SMS riuscito
|
||||||
|
failed: Controllo OTP SMS fallito
|
||||||
|
email:
|
||||||
|
added: Aggiunto OTP e-mail
|
||||||
|
removed: OTP e-mail rimosso
|
||||||
|
code:
|
||||||
|
added: Aggiunto codice OTP e-mail
|
||||||
|
sent: Codice OTP e-mail inviato
|
||||||
|
check:
|
||||||
|
succeeded: OTP Controllo e-mail riuscito
|
||||||
|
failed: OTP Controllo e-mail fallito
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Aggiunto il U2F Token
|
added: Aggiunto il U2F Token
|
||||||
|
@ -603,6 +603,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: MFA OTPチェックの成功
|
succeeded: MFA OTPチェックの成功
|
||||||
failed: MFA OTPチェックの失敗
|
failed: MFA OTPチェックの失敗
|
||||||
|
sms:
|
||||||
|
added: 多要素 OTP SMS を追加しました
|
||||||
|
removed: 多要素 OTP SMS を削除しました
|
||||||
|
code:
|
||||||
|
added: 多要素 OTP SMS コードを追加しました
|
||||||
|
sent: 多要素 OTP SMS コードが送信されました
|
||||||
|
check:
|
||||||
|
succeeded: 多要素 OTP SMS 検証が成功しました
|
||||||
|
failed: 多要素 OTP SMS 検証が失敗しました
|
||||||
|
email:
|
||||||
|
added: 多要素 OTP 電子メールを追加しました
|
||||||
|
removed: 多要素 OTP 電子メールを削除しました
|
||||||
|
code:
|
||||||
|
added: 多要素 OTP 電子メール コードを追加しました
|
||||||
|
sent: 多要素 OTP 電子メール コードが送信されました
|
||||||
|
check:
|
||||||
|
succeeded: 多要素 OTP 電子メール検証が成功しました
|
||||||
|
failed: 多要素 OTP 電子メール検証が失敗しました
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: MFA U2Fトークンの追加
|
added: MFA U2Fトークンの追加
|
||||||
|
@ -614,6 +614,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Проверката на мултифактор OTP е успешна
|
succeeded: Проверката на мултифактор OTP е успешна
|
||||||
failed: Проверката на мултифактор OTP е неуспешна
|
failed: Проверката на мултифактор OTP е неуспешна
|
||||||
|
sms:
|
||||||
|
added: Додадена е мултифакторна OTP SMS
|
||||||
|
removed: Отстранета мултифакторна OTP SMS
|
||||||
|
code:
|
||||||
|
added: Додаден мултифакторски OTP SMS код
|
||||||
|
sent: Мултифакторен OTP СМС-код е испратен
|
||||||
|
check:
|
||||||
|
succeeded: Мултифакторна OTP СМС-верификација е успешна
|
||||||
|
failed: Мултифакторна OTP СМС-верификација не успеа
|
||||||
|
email:
|
||||||
|
added: Додадена е повеќефакторна OTP е-пошта
|
||||||
|
removed: Отстранета мултифакторна OTP е-пошта
|
||||||
|
code:
|
||||||
|
added: Додаден мултифакторски OTP код за е-пошта
|
||||||
|
sent: Испратен е-пошта OTP-код со повеќе фактори
|
||||||
|
check:
|
||||||
|
succeeded: Успешна е-пошта OTP-верификација на мултифактор
|
||||||
|
failed: Неуспешна потврда на е-пошта OTP со повеќе фактори
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Додаден мултифактор U2F токен
|
added: Додаден мултифактор U2F токен
|
||||||
|
@ -618,6 +618,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Sprawdzenie wielofaktorowego OTP zakończone powodzeniem
|
succeeded: Sprawdzenie wielofaktorowego OTP zakończone powodzeniem
|
||||||
failed: Sprawdzenie wielofaktorowego OTP nie powiodło się
|
failed: Sprawdzenie wielofaktorowego OTP nie powiodło się
|
||||||
|
sms:
|
||||||
|
added: Dodano wieloskładnikowy SMS OTP
|
||||||
|
removed: Usunięto wieloskładnikowy SMS OTP
|
||||||
|
code:
|
||||||
|
added: Dodano wieloczynnikowy kod SMS OTP
|
||||||
|
sent: Wysłano kod SMS Multifactor OTP
|
||||||
|
check:
|
||||||
|
succeeded: Pomyślna weryfikacja wieloczynnikowego SMS-a OTP
|
||||||
|
failed: Wieloskładnikowa weryfikacja wiadomości SMS OTP nie powiodła się
|
||||||
|
email:
|
||||||
|
added: Dodano wieloskładnikowy e-mail OTP
|
||||||
|
removed: Usunięto wieloskładnikowy e-mail OTP
|
||||||
|
code:
|
||||||
|
added: Dodano wieloskładnikowy kod e-mail OTP
|
||||||
|
sent: Wysłano kod e-mail Multifactor OTP
|
||||||
|
check:
|
||||||
|
succeeded: Pomyślna wieloczynnikowa weryfikacja adresu e-mail OTP
|
||||||
|
failed: Wieloczynnikowa weryfikacja adresu e-mail OTP nie powiodła się
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Dodano token wielofaktorowego U2F
|
added: Dodano token wielofaktorowego U2F
|
||||||
|
@ -609,6 +609,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: Verificação de OTP de autenticação multifator bem-sucedida
|
succeeded: Verificação de OTP de autenticação multifator bem-sucedida
|
||||||
failed: Verificação de OTP de autenticação multifator falhou
|
failed: Verificação de OTP de autenticação multifator falhou
|
||||||
|
sms:
|
||||||
|
added: Adicionado SMS OTP multifator
|
||||||
|
removed: SMS OTP multifator removido
|
||||||
|
code:
|
||||||
|
added: Adicionado código SMS OTP multifator
|
||||||
|
sent: Código SMS OTP multifator enviado
|
||||||
|
check:
|
||||||
|
succeeded: Verificação multifator OTP SMS bem-sucedida
|
||||||
|
failed: Falha na verificação multifator OTP SMS
|
||||||
|
email:
|
||||||
|
added: Adicionado e-mail OTP multifator
|
||||||
|
removed: E-mail OTP multifator removido
|
||||||
|
code:
|
||||||
|
added: Adicionado código de e-mail OTP multifator
|
||||||
|
sent: Código de e-mail OTP multifator enviado
|
||||||
|
check:
|
||||||
|
succeeded: Verificação de e-mail OTP multifator bem-sucedida
|
||||||
|
failed: Falha na verificação de e-mail OTP multifator
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: Token U2F de autenticação multifator adicionado
|
added: Token U2F de autenticação multifator adicionado
|
||||||
|
@ -612,6 +612,24 @@ EventTypes:
|
|||||||
check:
|
check:
|
||||||
succeeded: 验证 MFA OTP 成功
|
succeeded: 验证 MFA OTP 成功
|
||||||
failed: 验证 MFA OTP 失败
|
failed: 验证 MFA OTP 失败
|
||||||
|
sms:
|
||||||
|
added: 添加了多因素 OTP 短信
|
||||||
|
removed: 删除了多因素 OTP 短信
|
||||||
|
code:
|
||||||
|
added: 添加了多因素 OTP 短信代码
|
||||||
|
sent: 已发送多因素 OTP 短信代码
|
||||||
|
check:
|
||||||
|
succeeded: 多因素 OTP 短信验证成功
|
||||||
|
failed: 多因素 OTP 短信验证失败
|
||||||
|
email:
|
||||||
|
added: 添加了多因素 OTP 电子邮件
|
||||||
|
removed: 删除了多因素 OTP 电子邮件
|
||||||
|
code:
|
||||||
|
added: 添加了多因素 OTP 电子邮件代码
|
||||||
|
sent: 已发送多因素 OTP 电子邮件代码
|
||||||
|
check:
|
||||||
|
succeeded: 多因素 OTP 电子邮件验证成功
|
||||||
|
failed: 多因素 OTP 电子邮件验证失败
|
||||||
u2f:
|
u2f:
|
||||||
token:
|
token:
|
||||||
added: 添加 MFA U2F 令牌
|
added: 添加 MFA U2F 令牌
|
||||||
|
@ -49,6 +49,8 @@ type HumanView struct {
|
|||||||
Region string
|
Region string
|
||||||
StreetAddress string
|
StreetAddress string
|
||||||
OTPState MFAState
|
OTPState MFAState
|
||||||
|
OTPSMSAdded bool
|
||||||
|
OTPEmailAdded bool
|
||||||
U2FTokens []*WebAuthNView
|
U2FTokens []*WebAuthNView
|
||||||
PasswordlessTokens []*WebAuthNView
|
PasswordlessTokens []*WebAuthNView
|
||||||
MFAMaxSetUp domain.MFALevel
|
MFAMaxSetUp domain.MFALevel
|
||||||
@ -162,10 +164,17 @@ func (u *UserView) MFATypesSetupPossible(level domain.MFALevel, policy *domain.L
|
|||||||
}
|
}
|
||||||
case domain.SecondFactorTypeU2F:
|
case domain.SecondFactorTypeU2F:
|
||||||
types = append(types, domain.MFATypeU2F)
|
types = append(types, domain.MFATypeU2F)
|
||||||
|
case domain.SecondFactorTypeOTPSMS:
|
||||||
|
if !u.OTPSMSAdded {
|
||||||
|
types = append(types, domain.MFATypeOTPSMS)
|
||||||
|
}
|
||||||
|
case domain.SecondFactorTypeOTPEmail:
|
||||||
|
if !u.OTPEmailAdded {
|
||||||
|
types = append(types, domain.MFATypeOTPEmail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//PLANNED: add sms
|
|
||||||
}
|
}
|
||||||
return types
|
return types
|
||||||
}
|
}
|
||||||
@ -189,10 +198,17 @@ func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPo
|
|||||||
if u.IsU2FReady() {
|
if u.IsU2FReady() {
|
||||||
types = append(types, domain.MFATypeU2F)
|
types = append(types, domain.MFATypeU2F)
|
||||||
}
|
}
|
||||||
|
case domain.SecondFactorTypeOTPSMS:
|
||||||
|
if u.OTPSMSAdded {
|
||||||
|
types = append(types, domain.MFATypeOTPSMS)
|
||||||
|
}
|
||||||
|
case domain.SecondFactorTypeOTPEmail:
|
||||||
|
if u.OTPEmailAdded {
|
||||||
|
types = append(types, domain.MFATypeOTPEmail)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//PLANNED: add sms
|
|
||||||
}
|
}
|
||||||
return types, required
|
return types, required
|
||||||
}
|
}
|
||||||
|
@ -89,6 +89,8 @@ type HumanView struct {
|
|||||||
Region string `json:"region" gorm:"column:region"`
|
Region string `json:"region" gorm:"column:region"`
|
||||||
StreetAddress string `json:"streetAddress" gorm:"column:street_address"`
|
StreetAddress string `json:"streetAddress" gorm:"column:street_address"`
|
||||||
OTPState int32 `json:"-" gorm:"column:otp_state"`
|
OTPState int32 `json:"-" gorm:"column:otp_state"`
|
||||||
|
OTPSMSAdded bool `json:"-" gorm:"column:otp_sms_added"`
|
||||||
|
OTPEmailAdded bool `json:"-" gorm:"column:otp_email_added"`
|
||||||
U2FTokens WebAuthNTokens `json:"-" gorm:"column:u2f_tokens"`
|
U2FTokens WebAuthNTokens `json:"-" gorm:"column:u2f_tokens"`
|
||||||
MFAMaxSetUp int32 `json:"-" gorm:"column:mfa_max_set_up"`
|
MFAMaxSetUp int32 `json:"-" gorm:"column:mfa_max_set_up"`
|
||||||
MFAInitSkipped time.Time `json:"-" gorm:"column:mfa_init_skipped"`
|
MFAInitSkipped time.Time `json:"-" gorm:"column:mfa_init_skipped"`
|
||||||
@ -178,6 +180,8 @@ func UserToModel(user *UserView) *model.UserView {
|
|||||||
Region: user.Region,
|
Region: user.Region,
|
||||||
StreetAddress: user.StreetAddress,
|
StreetAddress: user.StreetAddress,
|
||||||
OTPState: model.MFAState(user.OTPState),
|
OTPState: model.MFAState(user.OTPState),
|
||||||
|
OTPSMSAdded: user.OTPSMSAdded,
|
||||||
|
OTPEmailAdded: user.OTPEmailAdded,
|
||||||
MFAMaxSetUp: domain.MFALevel(user.MFAMaxSetUp),
|
MFAMaxSetUp: domain.MFALevel(user.MFAMaxSetUp),
|
||||||
MFAInitSkipped: user.MFAInitSkipped,
|
MFAInitSkipped: user.MFAInitSkipped,
|
||||||
InitRequired: user.InitRequired,
|
InitRequired: user.InitRequired,
|
||||||
@ -301,6 +305,8 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
|
|||||||
user.HumanPhoneRemovedType:
|
user.HumanPhoneRemovedType:
|
||||||
u.Phone = ""
|
u.Phone = ""
|
||||||
u.IsPhoneVerified = false
|
u.IsPhoneVerified = false
|
||||||
|
u.OTPSMSAdded = false
|
||||||
|
u.MFAInitSkipped = time.Time{}
|
||||||
case user.UserDeactivatedType:
|
case user.UserDeactivatedType:
|
||||||
u.State = int32(model.UserStateInactive)
|
u.State = int32(model.UserStateInactive)
|
||||||
case user.UserReactivatedType,
|
case user.UserReactivatedType,
|
||||||
@ -326,6 +332,16 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
|
|||||||
case user.UserV1MFAOTPRemovedType,
|
case user.UserV1MFAOTPRemovedType,
|
||||||
user.HumanMFAOTPRemovedType:
|
user.HumanMFAOTPRemovedType:
|
||||||
u.OTPState = int32(model.MFAStateUnspecified)
|
u.OTPState = int32(model.MFAStateUnspecified)
|
||||||
|
case user.HumanOTPSMSAddedType:
|
||||||
|
u.OTPSMSAdded = true
|
||||||
|
case user.HumanOTPSMSRemovedType:
|
||||||
|
u.OTPSMSAdded = false
|
||||||
|
u.MFAInitSkipped = time.Time{}
|
||||||
|
case user.HumanOTPEmailAddedType:
|
||||||
|
u.OTPEmailAdded = true
|
||||||
|
case user.HumanOTPEmailRemovedType:
|
||||||
|
u.OTPEmailAdded = false
|
||||||
|
u.MFAInitSkipped = time.Time{}
|
||||||
case user.HumanU2FTokenAddedType:
|
case user.HumanU2FTokenAddedType:
|
||||||
err = u.addU2FToken(event)
|
err = u.addU2FToken(event)
|
||||||
case user.HumanU2FTokenVerifiedType:
|
case user.HumanU2FTokenVerifiedType:
|
||||||
@ -520,7 +536,8 @@ func (u *UserView) ComputeMFAMaxSetUp() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if u.OTPState == int32(model.MFAStateReady) {
|
if u.OTPState == int32(model.MFAStateReady) ||
|
||||||
|
u.OTPSMSAdded || u.OTPEmailAdded {
|
||||||
u.MFAMaxSetUp = int32(domain.MFALevelSecondFactor)
|
u.MFAMaxSetUp = int32(domain.MFALevelSecondFactor)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -575,6 +592,10 @@ func (u *UserView) EventTypes() []models.EventType {
|
|||||||
models.EventType(user.HumanMFAOTPVerifiedType),
|
models.EventType(user.HumanMFAOTPVerifiedType),
|
||||||
models.EventType(user.UserV1MFAOTPRemovedType),
|
models.EventType(user.UserV1MFAOTPRemovedType),
|
||||||
models.EventType(user.HumanMFAOTPRemovedType),
|
models.EventType(user.HumanMFAOTPRemovedType),
|
||||||
|
models.EventType(user.HumanOTPSMSAddedType),
|
||||||
|
models.EventType(user.HumanOTPSMSRemovedType),
|
||||||
|
models.EventType(user.HumanOTPEmailAddedType),
|
||||||
|
models.EventType(user.HumanOTPEmailRemovedType),
|
||||||
models.EventType(user.HumanU2FTokenAddedType),
|
models.EventType(user.HumanU2FTokenAddedType),
|
||||||
models.EventType(user.HumanU2FTokenVerifiedType),
|
models.EventType(user.HumanU2FTokenVerifiedType),
|
||||||
models.EventType(user.HumanU2FTokenRemovedType),
|
models.EventType(user.HumanU2FTokenRemovedType),
|
||||||
|
@ -139,12 +139,32 @@ func (v *UserSessionView) AppendEvent(event *models.Event) error {
|
|||||||
case user.UserV1MFAOTPCheckSucceededType,
|
case user.UserV1MFAOTPCheckSucceededType,
|
||||||
user.HumanMFAOTPCheckSucceededType:
|
user.HumanMFAOTPCheckSucceededType:
|
||||||
v.setSecondFactorVerification(event.CreationDate, domain.MFATypeTOTP)
|
v.setSecondFactorVerification(event.CreationDate, domain.MFATypeTOTP)
|
||||||
|
case user.HumanOTPSMSCheckSucceededType:
|
||||||
|
data := new(es_model.OTPVerified)
|
||||||
|
err := data.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v.UserAgentID == data.UserAgentID {
|
||||||
|
v.setSecondFactorVerification(event.CreationDate, domain.MFATypeOTPSMS)
|
||||||
|
}
|
||||||
|
case user.HumanOTPEmailCheckSucceededType:
|
||||||
|
data := new(es_model.OTPVerified)
|
||||||
|
err := data.SetData(event)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if v.UserAgentID == data.UserAgentID {
|
||||||
|
v.setSecondFactorVerification(event.CreationDate, domain.MFATypeOTPEmail)
|
||||||
|
}
|
||||||
case user.UserV1MFAOTPCheckFailedType,
|
case user.UserV1MFAOTPCheckFailedType,
|
||||||
user.UserV1MFAOTPRemovedType,
|
user.UserV1MFAOTPRemovedType,
|
||||||
user.HumanMFAOTPCheckFailedType,
|
user.HumanMFAOTPCheckFailedType,
|
||||||
user.HumanMFAOTPRemovedType,
|
user.HumanMFAOTPRemovedType,
|
||||||
user.HumanU2FTokenCheckFailedType,
|
user.HumanU2FTokenCheckFailedType,
|
||||||
user.HumanU2FTokenRemovedType:
|
user.HumanU2FTokenRemovedType,
|
||||||
|
user.HumanOTPSMSCheckFailedType,
|
||||||
|
user.HumanOTPEmailCheckFailedType:
|
||||||
v.SecondFactorVerification = time.Time{}
|
v.SecondFactorVerification = time.Time{}
|
||||||
case user.HumanU2FTokenVerifiedType:
|
case user.HumanU2FTokenVerifiedType:
|
||||||
data := new(es_model.WebAuthNVerify)
|
data := new(es_model.WebAuthNVerify)
|
||||||
@ -218,6 +238,10 @@ func (v *UserSessionView) EventTypes() []models.EventType {
|
|||||||
models.EventType(user.UserV1MFAOTPRemovedType),
|
models.EventType(user.UserV1MFAOTPRemovedType),
|
||||||
models.EventType(user.HumanMFAOTPCheckFailedType),
|
models.EventType(user.HumanMFAOTPCheckFailedType),
|
||||||
models.EventType(user.HumanMFAOTPRemovedType),
|
models.EventType(user.HumanMFAOTPRemovedType),
|
||||||
|
models.EventType(user.HumanOTPSMSCheckSucceededType),
|
||||||
|
models.EventType(user.HumanOTPSMSCheckFailedType),
|
||||||
|
models.EventType(user.HumanOTPEmailCheckSucceededType),
|
||||||
|
models.EventType(user.HumanOTPEmailCheckFailedType),
|
||||||
models.EventType(user.HumanU2FTokenCheckFailedType),
|
models.EventType(user.HumanU2FTokenCheckFailedType),
|
||||||
models.EventType(user.HumanU2FTokenRemovedType),
|
models.EventType(user.HumanU2FTokenRemovedType),
|
||||||
models.EventType(user.HumanU2FTokenVerifiedType),
|
models.EventType(user.HumanU2FTokenVerifiedType),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user