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:
Livio Spring
2023-08-15 14:47:05 +02:00
committed by GitHub
parent faa9ed4de9
commit 7c494fd219
76 changed files with 3203 additions and 88 deletions

View File

@@ -89,6 +89,8 @@ type HumanView struct {
Region string `json:"region" gorm:"column:region"`
StreetAddress string `json:"streetAddress" gorm:"column:street_address"`
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"`
MFAMaxSetUp int32 `json:"-" gorm:"column:mfa_max_set_up"`
MFAInitSkipped time.Time `json:"-" gorm:"column:mfa_init_skipped"`
@@ -178,6 +180,8 @@ func UserToModel(user *UserView) *model.UserView {
Region: user.Region,
StreetAddress: user.StreetAddress,
OTPState: model.MFAState(user.OTPState),
OTPSMSAdded: user.OTPSMSAdded,
OTPEmailAdded: user.OTPEmailAdded,
MFAMaxSetUp: domain.MFALevel(user.MFAMaxSetUp),
MFAInitSkipped: user.MFAInitSkipped,
InitRequired: user.InitRequired,
@@ -301,6 +305,8 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
user.HumanPhoneRemovedType:
u.Phone = ""
u.IsPhoneVerified = false
u.OTPSMSAdded = false
u.MFAInitSkipped = time.Time{}
case user.UserDeactivatedType:
u.State = int32(model.UserStateInactive)
case user.UserReactivatedType,
@@ -326,6 +332,16 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
case user.UserV1MFAOTPRemovedType,
user.HumanMFAOTPRemovedType:
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:
err = u.addU2FToken(event)
case user.HumanU2FTokenVerifiedType:
@@ -520,7 +536,8 @@ func (u *UserView) ComputeMFAMaxSetUp() {
return
}
}
if u.OTPState == int32(model.MFAStateReady) {
if u.OTPState == int32(model.MFAStateReady) ||
u.OTPSMSAdded || u.OTPEmailAdded {
u.MFAMaxSetUp = int32(domain.MFALevelSecondFactor)
return
}
@@ -575,6 +592,10 @@ func (u *UserView) EventTypes() []models.EventType {
models.EventType(user.HumanMFAOTPVerifiedType),
models.EventType(user.UserV1MFAOTPRemovedType),
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.HumanU2FTokenVerifiedType),
models.EventType(user.HumanU2FTokenRemovedType),

View File

@@ -139,12 +139,32 @@ func (v *UserSessionView) AppendEvent(event *models.Event) error {
case user.UserV1MFAOTPCheckSucceededType,
user.HumanMFAOTPCheckSucceededType:
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,
user.UserV1MFAOTPRemovedType,
user.HumanMFAOTPCheckFailedType,
user.HumanMFAOTPRemovedType,
user.HumanU2FTokenCheckFailedType,
user.HumanU2FTokenRemovedType:
user.HumanU2FTokenRemovedType,
user.HumanOTPSMSCheckFailedType,
user.HumanOTPEmailCheckFailedType:
v.SecondFactorVerification = time.Time{}
case user.HumanU2FTokenVerifiedType:
data := new(es_model.WebAuthNVerify)
@@ -218,6 +238,10 @@ func (v *UserSessionView) EventTypes() []models.EventType {
models.EventType(user.UserV1MFAOTPRemovedType),
models.EventType(user.HumanMFAOTPCheckFailedType),
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.HumanU2FTokenRemovedType),
models.EventType(user.HumanU2FTokenVerifiedType),