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

@@ -1,6 +1,8 @@
package command
import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -60,6 +62,18 @@ func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
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 {
eventstore.WriteModel
@@ -67,6 +81,14 @@ type HumanOTPSMSWriteModel struct {
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 {
return &HumanOTPSMSWriteModel{
WriteModel: eventstore.WriteModel{
@@ -107,8 +129,66 @@ func (wm *HumanOTPSMSWriteModel) Query() *eventstore.SearchQueryBuilder {
).
Builder()
if wm.ResourceOwner != "" {
query.ResourceOwner(wm.ResourceOwner)
if wm.WriteModel.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
}
@@ -120,6 +200,14 @@ type HumanOTPEmailWriteModel struct {
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 {
return &HumanOTPEmailWriteModel{
WriteModel: eventstore.WriteModel{
@@ -151,15 +239,73 @@ func (wm *HumanOTPEmailWriteModel) Query() *eventstore.SearchQueryBuilder {
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(user.HumanEmailVerifiedType,
EventTypes(
user.HumanEmailVerifiedType,
user.HumanOTPEmailAddedType,
user.HumanOTPEmailRemovedType,
user.UserRemovedType,
).
Builder()
if wm.ResourceOwner != "" {
query.ResourceOwner(wm.ResourceOwner)
if wm.WriteModel.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
}