zitadel/internal/command/user_human_otp_model.go
Livio Spring 7c494fd219
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
2023-08-15 12:47:05 +00:00

312 lines
7.5 KiB
Go

package command
import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/repository/user"
)
type HumanTOTPWriteModel struct {
eventstore.WriteModel
State domain.MFAState
Secret *crypto.CryptoValue
}
func NewHumanTOTPWriteModel(userID, resourceOwner string) *HumanTOTPWriteModel {
return &HumanTOTPWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
}
}
func (wm *HumanTOTPWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *user.HumanOTPAddedEvent:
wm.Secret = e.Secret
wm.State = domain.MFAStateNotReady
case *user.HumanOTPVerifiedEvent:
wm.State = domain.MFAStateReady
case *user.HumanOTPRemovedEvent:
wm.State = domain.MFAStateRemoved
case *user.UserRemovedEvent:
wm.State = domain.MFAStateRemoved
}
}
return wm.WriteModel.Reduce()
}
func (wm *HumanTOTPWriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(user.HumanMFAOTPAddedType,
user.HumanMFAOTPVerifiedType,
user.HumanMFAOTPRemovedType,
user.UserRemovedType,
user.UserV1MFAOTPAddedType,
user.UserV1MFAOTPVerifiedType,
user.UserV1MFAOTPRemovedType).
Builder()
if wm.ResourceOwner != "" {
query.ResourceOwner(wm.ResourceOwner)
}
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
phoneVerified 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 {
return &HumanOTPSMSWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
}
}
func (wm *HumanOTPSMSWriteModel) Reduce() error {
for _, event := range wm.Events {
switch event.(type) {
case *user.HumanPhoneVerifiedEvent:
wm.phoneVerified = true
case *user.HumanOTPSMSAddedEvent:
wm.otpAdded = true
case *user.HumanOTPSMSRemovedEvent:
wm.otpAdded = false
case *user.HumanPhoneRemovedEvent,
*user.UserRemovedEvent:
wm.phoneVerified = false
wm.otpAdded = false
}
}
return wm.WriteModel.Reduce()
}
func (wm *HumanOTPSMSWriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(user.HumanPhoneVerifiedType,
user.HumanOTPSMSAddedType,
user.HumanOTPSMSRemovedType,
user.HumanPhoneRemovedType,
user.UserRemovedType,
).
Builder()
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
}
type HumanOTPEmailWriteModel struct {
eventstore.WriteModel
emailVerified 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 {
return &HumanOTPEmailWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: userID,
ResourceOwner: resourceOwner,
},
}
}
func (wm *HumanOTPEmailWriteModel) Reduce() error {
for _, event := range wm.Events {
switch event.(type) {
case *user.HumanEmailVerifiedEvent:
wm.emailVerified = true
case *user.HumanOTPEmailAddedEvent:
wm.otpAdded = true
case *user.HumanOTPEmailRemovedEvent:
wm.otpAdded = false
case *user.UserRemovedEvent:
wm.emailVerified = false
wm.otpAdded = false
}
}
return wm.WriteModel.Reduce()
}
func (wm *HumanOTPEmailWriteModel) Query() *eventstore.SearchQueryBuilder {
query := eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
AddQuery().
AggregateTypes(user.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
user.HumanEmailVerifiedType,
user.HumanOTPEmailAddedType,
user.HumanOTPEmailRemovedType,
user.UserRemovedType,
).
Builder()
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
}