mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-20 23:07:33 +00:00
7c494fd219
* 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
325 lines
7.8 KiB
Go
325 lines
7.8 KiB
Go
package model
|
|
|
|
import (
|
|
"time"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/errors"
|
|
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
|
iam_model "github.com/zitadel/zitadel/internal/iam/model"
|
|
)
|
|
|
|
type UserView struct {
|
|
ID string
|
|
UserName string
|
|
CreationDate time.Time
|
|
ChangeDate time.Time
|
|
State UserState
|
|
Sequence uint64
|
|
ResourceOwner string
|
|
LastLogin time.Time
|
|
PreferredLoginName string
|
|
LoginNames []string
|
|
*MachineView
|
|
*HumanView
|
|
}
|
|
|
|
type HumanView struct {
|
|
PasswordSet bool
|
|
PasswordInitRequired bool
|
|
PasswordChangeRequired bool
|
|
UsernameChangeRequired bool
|
|
PasswordChanged time.Time
|
|
FirstName string
|
|
LastName string
|
|
NickName string
|
|
DisplayName string
|
|
AvatarKey string
|
|
PreferredLanguage string
|
|
Gender Gender
|
|
Email string
|
|
IsEmailVerified bool
|
|
Phone string
|
|
IsPhoneVerified bool
|
|
Country string
|
|
Locality string
|
|
PostalCode string
|
|
Region string
|
|
StreetAddress string
|
|
OTPState MFAState
|
|
OTPSMSAdded bool
|
|
OTPEmailAdded bool
|
|
U2FTokens []*WebAuthNView
|
|
PasswordlessTokens []*WebAuthNView
|
|
MFAMaxSetUp domain.MFALevel
|
|
MFAInitSkipped time.Time
|
|
InitRequired bool
|
|
PasswordlessInitRequired bool
|
|
}
|
|
|
|
type WebAuthNView struct {
|
|
TokenID string
|
|
Name string
|
|
State MFAState
|
|
}
|
|
|
|
type MachineView struct {
|
|
LastKeyAdded time.Time
|
|
Name string
|
|
Description string
|
|
}
|
|
|
|
type UserSearchRequest struct {
|
|
Offset uint64
|
|
Limit uint64
|
|
SortingColumn UserSearchKey
|
|
Asc bool
|
|
Queries []*UserSearchQuery
|
|
}
|
|
|
|
type UserSearchKey int32
|
|
|
|
const (
|
|
UserSearchKeyUnspecified UserSearchKey = iota
|
|
UserSearchKeyUserID
|
|
UserSearchKeyUserName
|
|
UserSearchKeyFirstName
|
|
UserSearchKeyLastName
|
|
UserSearchKeyNickName
|
|
UserSearchKeyDisplayName
|
|
UserSearchKeyEmail
|
|
UserSearchKeyState
|
|
UserSearchKeyResourceOwner
|
|
UserSearchKeyLoginNames
|
|
UserSearchKeyType
|
|
UserSearchKeyPreferredLoginName
|
|
UserSearchKeyInstanceID
|
|
UserSearchOwnerRemoved
|
|
)
|
|
|
|
type UserSearchQuery struct {
|
|
Key UserSearchKey
|
|
Method domain.SearchMethod
|
|
Value interface{}
|
|
}
|
|
|
|
type UserSearchResponse struct {
|
|
Offset uint64
|
|
Limit uint64
|
|
TotalResult uint64
|
|
Result []*UserView
|
|
Sequence uint64
|
|
Timestamp time.Time
|
|
}
|
|
|
|
type UserState int32
|
|
|
|
const (
|
|
UserStateUnspecified UserState = iota
|
|
UserStateActive
|
|
UserStateInactive
|
|
UserStateDeleted
|
|
UserStateLocked
|
|
UserStateSuspend
|
|
UserStateInitial
|
|
)
|
|
|
|
type Gender int32
|
|
|
|
const (
|
|
GenderUnspecified Gender = iota
|
|
GenderFemale
|
|
GenderMale
|
|
GenderDiverse
|
|
)
|
|
|
|
func (r *UserSearchRequest) EnsureLimit(limit uint64) error {
|
|
if r.Limit > limit {
|
|
return errors.ThrowInvalidArgument(nil, "SEARCH-zz62F", "Errors.Limit.ExceedsDefault")
|
|
}
|
|
if r.Limit == 0 {
|
|
r.Limit = limit
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *UserSearchRequest) AppendMyOrgQuery(orgID string) {
|
|
r.Queries = append(r.Queries, &UserSearchQuery{Key: UserSearchKeyResourceOwner, Method: domain.SearchMethodEquals, Value: orgID})
|
|
}
|
|
|
|
func (u *UserView) MFATypesSetupPossible(level domain.MFALevel, policy *domain.LoginPolicy) []domain.MFAType {
|
|
types := make([]domain.MFAType, 0)
|
|
switch level {
|
|
default:
|
|
fallthrough
|
|
case domain.MFALevelSecondFactor:
|
|
if policy.HasSecondFactors() {
|
|
for _, mfaType := range policy.SecondFactors {
|
|
switch mfaType {
|
|
case domain.SecondFactorTypeTOTP:
|
|
if u.OTPState != MFAStateReady {
|
|
types = append(types, domain.MFATypeTOTP)
|
|
}
|
|
case domain.SecondFactorTypeU2F:
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return types
|
|
}
|
|
|
|
func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPolicy, isInternalAuthentication bool) ([]domain.MFAType, bool) {
|
|
types := make([]domain.MFAType, 0)
|
|
required := true
|
|
switch level {
|
|
default:
|
|
required = domain.RequiresMFA(policy.ForceMFA, policy.ForceMFALocalOnly, isInternalAuthentication)
|
|
fallthrough
|
|
case domain.MFALevelSecondFactor:
|
|
if policy.HasSecondFactors() {
|
|
for _, mfaType := range policy.SecondFactors {
|
|
switch mfaType {
|
|
case domain.SecondFactorTypeTOTP:
|
|
if u.OTPState == MFAStateReady {
|
|
types = append(types, domain.MFATypeTOTP)
|
|
}
|
|
case domain.SecondFactorTypeU2F:
|
|
if u.IsU2FReady() {
|
|
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return types, required
|
|
}
|
|
|
|
func (u *UserView) IsU2FReady() bool {
|
|
for _, token := range u.U2FTokens {
|
|
if token.State == MFAStateReady {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (u *UserView) IsPasswordlessReady() bool {
|
|
for _, token := range u.PasswordlessTokens {
|
|
if token.State == MFAStateReady {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (u *UserView) HasRequiredOrgMFALevel(policy *iam_model.LoginPolicyView) bool {
|
|
if !policy.ForceMFA {
|
|
return true
|
|
}
|
|
switch u.MFAMaxSetUp {
|
|
case domain.MFALevelSecondFactor:
|
|
return policy.HasSecondFactors()
|
|
case domain.MFALevelMultiFactor:
|
|
return policy.HasMultiFactors()
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (u *UserView) GetProfile() (*Profile, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-WLTce", "Errors.User.NotHuman")
|
|
}
|
|
return &Profile{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
FirstName: u.FirstName,
|
|
LastName: u.LastName,
|
|
NickName: u.NickName,
|
|
DisplayName: u.DisplayName,
|
|
PreferredLanguage: language.Make(u.PreferredLanguage),
|
|
Gender: u.Gender,
|
|
PreferredLoginName: u.PreferredLoginName,
|
|
LoginNames: u.LoginNames,
|
|
AvatarKey: u.AvatarKey,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) GetPhone() (*Phone, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-him4a", "Errors.User.NotHuman")
|
|
}
|
|
return &Phone{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
PhoneNumber: u.Phone,
|
|
IsPhoneVerified: u.IsPhoneVerified,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) GetEmail() (*Email, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-PWd6K", "Errors.User.NotHuman")
|
|
}
|
|
return &Email{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
EmailAddress: u.Email,
|
|
IsEmailVerified: u.IsEmailVerified,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) GetAddress() (*Address, error) {
|
|
if u.HumanView == nil {
|
|
return nil, errors.ThrowPreconditionFailed(nil, "MODEL-DN61m", "Errors.User.NotHuman")
|
|
}
|
|
return &Address{
|
|
ObjectRoot: models.ObjectRoot{
|
|
AggregateID: u.ID,
|
|
Sequence: u.Sequence,
|
|
ResourceOwner: u.ResourceOwner,
|
|
CreationDate: u.CreationDate,
|
|
ChangeDate: u.ChangeDate,
|
|
},
|
|
Country: u.Country,
|
|
Locality: u.Locality,
|
|
PostalCode: u.PostalCode,
|
|
Region: u.Region,
|
|
StreetAddress: u.StreetAddress,
|
|
}, nil
|
|
}
|