mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-18 13:57:32 +00:00
00220e9532
* begin pw less registration * create pwless one time codes * send pwless link * separate send and add passwordless link * separate send and add passwordless link events * custom message text for passwordless registration * begin custom login texts for passwordless * i18n * i18n message * i18n message * custom message text * custom login text * org design and texts * create link in human import process * fix import human tests * begin passwordless init required step * passwordless init * passwordless init * do not return link in mgmt api * prompt * passwordless init only (no additional prompt) * cleanup * cleanup * add passwordless prompt to custom login text * increase init code complexity * fix grpc * cleanup * fix and add some cases for nextStep tests * fix tests * Update internal/notification/static/i18n/en.yaml * Update internal/notification/static/i18n/de.yaml * Update proto/zitadel/management.proto * Update internal/ui/login/static/i18n/de.yaml * Update internal/ui/login/static/i18n/de.yaml * Update internal/ui/login/static/i18n/de.yaml Co-authored-by: Fabi <38692350+fgerschwiler@users.noreply.github.com>
331 lines
7.9 KiB
Go
331 lines
7.9 KiB
Go
package model
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"time"
|
|
|
|
"github.com/caos/zitadel/internal/domain"
|
|
"github.com/caos/zitadel/internal/static"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
req_model "github.com/caos/zitadel/internal/auth_request/model"
|
|
"github.com/caos/zitadel/internal/errors"
|
|
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
|
iam_model "github.com/caos/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
|
|
AvatarURL string
|
|
PreSignedAvatar *url.URL
|
|
PreferredLanguage string
|
|
Gender Gender
|
|
Email string
|
|
IsEmailVerified bool
|
|
Phone string
|
|
IsPhoneVerified bool
|
|
Country string
|
|
Locality string
|
|
PostalCode string
|
|
Region string
|
|
StreetAddress string
|
|
OTPState MFAState
|
|
U2FTokens []*WebAuthNView
|
|
PasswordlessTokens []*WebAuthNView
|
|
MFAMaxSetUp req_model.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
|
|
)
|
|
|
|
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.SecondFactorTypeOTP:
|
|
if u.OTPState != MFAStateReady {
|
|
types = append(types, domain.MFATypeOTP)
|
|
}
|
|
case domain.SecondFactorTypeU2F:
|
|
types = append(types, domain.MFATypeU2F)
|
|
}
|
|
}
|
|
}
|
|
//PLANNED: add sms
|
|
}
|
|
return types
|
|
}
|
|
|
|
func (u *UserView) MFATypesAllowed(level domain.MFALevel, policy *domain.LoginPolicy) ([]domain.MFAType, bool) {
|
|
types := make([]domain.MFAType, 0)
|
|
required := true
|
|
switch level {
|
|
default:
|
|
required = policy.ForceMFA
|
|
fallthrough
|
|
case domain.MFALevelSecondFactor:
|
|
if policy.HasSecondFactors() {
|
|
for _, mfaType := range policy.SecondFactors {
|
|
switch mfaType {
|
|
case domain.SecondFactorTypeOTP:
|
|
if u.OTPState == MFAStateReady {
|
|
types = append(types, domain.MFATypeOTP)
|
|
}
|
|
case domain.SecondFactorTypeU2F:
|
|
if u.IsU2FReady() {
|
|
types = append(types, domain.MFATypeU2F)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//PLANNED: add sms
|
|
}
|
|
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 req_model.MFALevelSecondFactor:
|
|
return policy.HasSecondFactors()
|
|
case req_model.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,
|
|
AvatarURL: u.AvatarURL,
|
|
}, nil
|
|
}
|
|
|
|
func (u *UserView) FillUserAvatar(ctx context.Context, static static.Storage, expiration time.Duration) error {
|
|
if u.HumanView == nil {
|
|
return errors.ThrowPreconditionFailed(nil, "MODEL-2k8da", "Errors.User.NotHuman")
|
|
}
|
|
if static != nil {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
presignesAvatarURL, err := static.GetObjectPresignedURL(ctx, u.ResourceOwner, u.AvatarKey, expiration)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
u.PreSignedAvatar = presignesAvatarURL
|
|
}
|
|
return 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
|
|
}
|