fix: make user creation errors helpful (#5382)

* fix: make user creation errors helpful

* fix linting and unit testing errors

* fix linting

* make zitadel config reusable

* fix human validations

* translate ssr errors

* make zitadel config reusable

* cover more translations for ssr

* handle email validation message centrally

* fix unit tests

* fix linting

* align signatures

* use more precise wording

* handle phone validation message centrally

* fix: return specific profile errors

* docs: edit comments

* fix unit tests

---------

Co-authored-by: Silvan <silvan.reusser@gmail.com>
This commit is contained in:
Elio Bischof 2023-03-14 20:20:38 +01:00 committed by GitHub
parent 9ff810eb92
commit e00cc187fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 610 additions and 485 deletions

View File

@ -91,11 +91,11 @@ func (mig *FirstInstance) Execute(ctx context.Context) error {
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
}
mig.instanceSetup.Org.Human.Email.Address = strings.TrimSpace(mig.instanceSetup.Org.Human.Email.Address)
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
if mig.instanceSetup.Org.Human.Email.Address == "" {
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Username
if !strings.Contains(mig.instanceSetup.Org.Human.Email.Address, "@") {
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain))
}
}

View File

@ -3,7 +3,8 @@ ExternalSecure: false
Database:
cockroach:
Host: db
# This makes the e2e config reusable with an out-of-docker zitadel process and an /etc/hosts entry
Host: host.docker.internal
TLS:
Enabled: false

View File

@ -3,7 +3,8 @@ ExternalSecure: false
Database:
cockroach:
Host: db
# This makes the e2e config reusable with an out-of-docker zitadel process and an /etc/hosts entry
Host: host.docker.internal
TLS:
Enabled: false

View File

@ -156,9 +156,9 @@ type human struct {
AvatarKey string
PreferredLanguage string
Gender domain.Gender
Email string
Email domain.EmailAddress
IsEmailVerified bool
Phone string
Phone domain.PhoneNumber
IsPhoneVerified bool
}

View File

@ -564,13 +564,13 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
}
if user.Human.Email != "" {
dataUser.User.Email = &management_pb.ImportHumanUserRequest_Email{
Email: user.Human.Email,
Email: string(user.Human.Email),
IsEmailVerified: user.Human.IsEmailVerified,
}
}
if user.Human.Phone != "" {
dataUser.User.Phone = &management_pb.ImportHumanUserRequest_Phone{
Phone: user.Human.Phone,
Phone: string(user.Human.Phone),
IsPhoneVerified: user.Human.IsPhoneVerified,
}
}

View File

@ -6,6 +6,7 @@ import (
user_grpc "github.com/zitadel/zitadel/internal/api/grpc/user"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
admin_grpc "github.com/zitadel/zitadel/pkg/grpc/admin"
)
@ -29,7 +30,7 @@ func setUpOrgHumanToCommand(human *admin_grpc.SetUpOrgRequest_Human) *command.Ad
func setUpOrgHumanEmailToDomain(email *admin_grpc.SetUpOrgRequest_Human_Email) command.Email {
return command.Email{
Address: email.Email,
Address: domain.EmailAddress(email.Email),
Verified: email.IsEmailVerified,
}
}
@ -39,7 +40,7 @@ func setUpOrgHumanPhoneToDomain(phone *admin_grpc.SetUpOrgRequest_Human_Phone) c
return command.Phone{}
}
return command.Phone{
Number: phone.Phone,
Number: domain.PhoneNumber(phone.Phone),
Verified: phone.IsPhoneVerified,
}
}

View File

@ -10,6 +10,6 @@ import (
func UpdateMyEmailToDomain(ctx context.Context, email *auth.SetMyEmailRequest) *domain.Email {
return &domain.Email{
ObjectRoot: ctxToObjectRoot(ctx),
EmailAddress: email.Email,
EmailAddress: domain.EmailAddress(email.Email),
}
}

View File

@ -10,6 +10,6 @@ import (
func UpdateMyPhoneToDomain(ctx context.Context, phone *auth.SetMyPhoneRequest) *domain.Phone {
return &domain.Phone{
ObjectRoot: ctxToObjectRoot(ctx),
PhoneNumber: phone.Phone,
PhoneNumber: domain.PhoneNumber(phone.Phone),
}
}

View File

@ -208,7 +208,7 @@ func AddHumanUserRequestToAddHuman(req *mgmt_pb.AddHumanUserRequest) *command.Ad
NickName: req.Profile.NickName,
DisplayName: req.Profile.DisplayName,
Email: command.Email{
Address: req.Email.Email,
Address: domain.EmailAddress(req.Email.Email),
Verified: req.Email.IsEmailVerified,
},
PreferredLanguage: lang,
@ -221,7 +221,7 @@ func AddHumanUserRequestToAddHuman(req *mgmt_pb.AddHumanUserRequest) *command.Ad
}
if req.Phone != nil {
human.Phone = command.Phone{
Number: req.Phone.Phone,
Number: domain.PhoneNumber(req.Phone.Phone),
Verified: req.Phone.IsPhoneVerified,
}
}
@ -446,7 +446,7 @@ func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.Res
if err != nil {
return nil, err
}
details, err := s.command.ResendInitialMail(ctx, req.UserId, req.Email, authz.GetCtxData(ctx).OrgID, initCodeGenerator)
details, err := s.command.ResendInitialMail(ctx, req.UserId, domain.EmailAddress(req.Email), authz.GetCtxData(ctx).OrgID, initCodeGenerator)
if err != nil {
return nil, err
}

View File

@ -91,37 +91,6 @@ func ListUserMetadataToDomain(req *mgmt_pb.ListUserMetadataRequest) (*query.User
}, nil
}
func AddHumanUserRequestToDomain(req *mgmt_pb.AddHumanUserRequest) *domain.Human {
h := &domain.Human{
Username: req.UserName,
}
preferredLanguage, err := language.Parse(req.Profile.PreferredLanguage)
logging.Log("MANAG-M029f").OnError(err).Debug("language malformed")
h.Profile = &domain.Profile{
FirstName: req.Profile.FirstName,
LastName: req.Profile.LastName,
NickName: req.Profile.NickName,
DisplayName: req.Profile.DisplayName,
PreferredLanguage: preferredLanguage,
Gender: user_grpc.GenderToDomain(req.Profile.Gender),
}
h.Email = &domain.Email{
EmailAddress: req.Email.Email,
IsEmailVerified: req.Email.IsEmailVerified,
}
if req.Phone != nil {
h.Phone = &domain.Phone{
PhoneNumber: req.Phone.Phone,
IsPhoneVerified: req.Phone.IsPhoneVerified,
}
}
if req.InitialPassword != "" {
h.Password = &domain.Password{SecretString: req.InitialPassword, ChangeRequired: true}
}
return h
}
func ImportHumanUserRequestToDomain(req *mgmt_pb.ImportHumanUserRequest) (human *domain.Human, passwordless bool, links []*domain.UserIDPLink) {
human = &domain.Human{
Username: req.UserName,
@ -137,12 +106,12 @@ func ImportHumanUserRequestToDomain(req *mgmt_pb.ImportHumanUserRequest) (human
Gender: user_grpc.GenderToDomain(req.Profile.Gender),
}
human.Email = &domain.Email{
EmailAddress: req.Email.Email,
EmailAddress: domain.EmailAddress(req.Email.Email),
IsEmailVerified: req.Email.IsEmailVerified,
}
if req.Phone != nil {
human.Phone = &domain.Phone{
PhoneNumber: req.Phone.Phone,
PhoneNumber: domain.PhoneNumber(req.Phone.Phone),
IsPhoneVerified: req.Phone.IsPhoneVerified,
}
}
@ -199,7 +168,7 @@ func UpdateHumanEmailRequestToDomain(ctx context.Context, req *mgmt_pb.UpdateHum
AggregateID: req.UserId,
ResourceOwner: authz.GetCtxData(ctx).OrgID,
},
EmailAddress: req.Email,
EmailAddress: domain.EmailAddress(req.Email),
IsEmailVerified: req.IsEmailVerified,
}
}
@ -207,7 +176,7 @@ func UpdateHumanEmailRequestToDomain(ctx context.Context, req *mgmt_pb.UpdateHum
func UpdateHumanPhoneRequestToDomain(req *mgmt_pb.UpdateHumanPhoneRequest) *domain.Phone {
return &domain.Phone{
ObjectRoot: models.ObjectRoot{AggregateID: req.UserId},
PhoneNumber: req.Phone,
PhoneNumber: domain.PhoneNumber(req.Phone),
IsPhoneVerified: req.IsPhoneVerified,
}
}

View File

@ -59,7 +59,7 @@ func CreateInstancePbToSetupInstance(req *system_pb.CreateInstanceRequest, defau
func createInstancePbToAddHuman(req *system_pb.CreateInstanceRequest_Human, defaultHuman command.AddHuman, userLoginMustBeDomain bool, org, externalDomain string) *command.AddHuman {
user := defaultHuman
if req.Email != nil {
user.Email.Address = req.Email.Email
user.Email.Address = domain.EmailAddress(req.Email.Email)
user.Email.Verified = req.Email.IsEmailVerified
}
if req.Profile != nil {
@ -164,7 +164,7 @@ func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInst
instance.Org.Human = new(command.AddHuman)
}
if req.OwnerEmail.Email != "" {
instance.Org.Human.Email.Address = req.OwnerEmail.Email
instance.Org.Human.Email.Address = domain.EmailAddress(req.OwnerEmail.Email)
instance.Org.Human.Email.Verified = req.OwnerEmail.IsEmailVerified
}
if req.OwnerProfile != nil {

View File

@ -58,11 +58,11 @@ func HumanToPb(view *query.Human, assetPrefix, owner string) *user_pb.Human {
AvatarUrl: domain.AvatarURL(assetPrefix, owner, view.AvatarKey),
},
Email: &user_pb.Email{
Email: view.Email,
Email: string(view.Email),
IsEmailVerified: view.IsEmailVerified,
},
Phone: &user_pb.Phone{
Phone: view.Phone,
Phone: string(view.Phone),
IsPhoneVerified: view.IsPhoneVerified,
},
}
@ -91,7 +91,7 @@ func ProfileToPb(profile *query.Profile, assetPrefix string) *user_pb.Profile {
func EmailToPb(email *query.Email) *user_pb.Email {
return &user_pb.Email{
Email: email.Email,
Email: string(email.Email),
IsEmailVerified: email.IsVerified,
}
}
@ -105,7 +105,7 @@ func PhoneToPb(phone *query.Phone) *user_pb.Phone {
func ModelEmailToPb(email *query.Email) *user_pb.Email {
return &user_pb.Email{
Email: email.Email,
Email: string(email.Email),
IsEmailVerified: email.IsVerified,
}
}

View File

@ -268,7 +268,7 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
if user.Human == nil {
continue
}
userInfo.SetEmail(user.Human.Email, user.Human.IsEmailVerified)
userInfo.SetEmail(string(user.Human.Email), user.Human.IsEmailVerified)
case oidc.ScopeProfile:
userInfo.SetPreferredUsername(user.PreferredLoginName)
userInfo.SetUpdatedAt(user.ChangeDate)
@ -287,7 +287,7 @@ func (o *OPStorage) setUserinfo(ctx context.Context, userInfo oidc.UserInfoSette
if user.Human == nil {
continue
}
userInfo.SetPhone(user.Human.Phone, user.Human.IsPhoneVerified)
userInfo.SetPhone(string(user.Human.Phone), user.Human.IsPhoneVerified)
case oidc.ScopeAddress:
//TODO: handle address for human users as soon as implemented
case ScopeUserMetaData:

View File

@ -154,7 +154,7 @@ func setUserinfo(user *query.User, userinfo models.AttributeSetter, attributes [
if user.Human == nil {
return
}
userinfo.SetEmail(user.Human.Email)
userinfo.SetEmail(string(user.Human.Email))
userinfo.SetSurname(user.Human.LastName)
userinfo.SetGivenName(user.Human.FirstName)
userinfo.SetFullName(user.Human.DisplayName)
@ -164,7 +164,7 @@ func setUserinfo(user *query.User, userinfo models.AttributeSetter, attributes [
switch attribute {
case provider.AttributeEmail:
if user.Human != nil {
userinfo.SetEmail(user.Human.Email)
userinfo.SetEmail(string(user.Human.Email))
}
case provider.AttributeSurname:
if user.Human != nil {

View File

@ -55,13 +55,13 @@ func (l *Login) runPostExternalAuthenticationActions(
actions.SetFields("setPreferredUsername", func(username string) {
user.PreferredUsername = username
}),
actions.SetFields("setEmail", func(email string) {
actions.SetFields("setEmail", func(email domain.EmailAddress) {
user.Email = email
}),
actions.SetFields("setEmailVerified", func(verified bool) {
user.IsEmailVerified = verified
}),
actions.SetFields("setPhone", func(phone string) {
actions.SetFields("setPhone", func(phone domain.PhoneNumber) {
user.Phone = phone
}),
actions.SetFields("setPhoneVerified", func(verified bool) {
@ -222,7 +222,7 @@ func (l *Login) runPreCreationActions(
actions.SetFields("setUsername", func(username string) {
user.Username = username
}),
actions.SetFields("setEmail", func(email string) {
actions.SetFields("setEmail", func(email domain.EmailAddress) {
if user.Email == nil {
user.Email = &domain.Email{}
}
@ -234,11 +234,11 @@ func (l *Login) runPreCreationActions(
}
user.Email.IsEmailVerified = verified
}),
actions.SetFields("setPhone", func(email string) {
actions.SetFields("setPhone", func(phone domain.PhoneNumber) {
if user.Phone == nil {
user.Phone = &domain.Phone{}
}
user.Phone.PhoneNumber = email
user.Phone.PhoneNumber = phone
}),
actions.SetFields("setPhoneVerified", func(verified bool) {
if user.Phone == nil {

View File

@ -60,28 +60,28 @@ type externalNotFoundOptionData struct {
ShowUsername bool
ShowUsernameSuffix bool
OrgRegister bool
ExternalEmail string
ExternalEmail domain.EmailAddress
ExternalEmailVerified bool
ExternalPhone string
ExternalPhone domain.PhoneNumber
ExternalPhoneVerified bool
}
type externalRegisterFormData struct {
ExternalIDPConfigID string `schema:"external-idp-config-id"`
ExternalIDPExtUserID string `schema:"external-idp-ext-user-id"`
ExternalIDPDisplayName string `schema:"external-idp-display-name"`
ExternalEmail string `schema:"external-email"`
ExternalEmailVerified bool `schema:"external-email-verified"`
Email string `schema:"email"`
Username string `schema:"username"`
Firstname string `schema:"firstname"`
Lastname string `schema:"lastname"`
Nickname string `schema:"nickname"`
ExternalPhone string `schema:"external-phone"`
ExternalPhoneVerified bool `schema:"external-phone-verified"`
Phone string `schema:"phone"`
Language string `schema:"language"`
TermsConfirm bool `schema:"terms-confirm"`
ExternalIDPConfigID string `schema:"external-idp-config-id"`
ExternalIDPExtUserID string `schema:"external-idp-ext-user-id"`
ExternalIDPDisplayName string `schema:"external-idp-display-name"`
ExternalEmail domain.EmailAddress `schema:"external-email"`
ExternalEmailVerified bool `schema:"external-email-verified"`
Email domain.EmailAddress `schema:"email"`
Username string `schema:"username"`
Firstname string `schema:"firstname"`
Lastname string `schema:"lastname"`
Nickname string `schema:"nickname"`
ExternalPhone domain.PhoneNumber `schema:"external-phone"`
ExternalPhoneVerified bool `schema:"external-phone-verified"`
Phone domain.PhoneNumber `schema:"phone"`
Language string `schema:"language"`
TermsConfirm bool `schema:"terms-confirm"`
}
// handleExternalLoginStep is called as nextStep
@ -815,7 +815,7 @@ func mapExternalNotFoundOptionFormDataToLoginUser(formData *externalNotFoundOpti
IDPConfigID: formData.ExternalIDPConfigID,
ExternalUserID: formData.ExternalIDPExtUserID,
PreferredUsername: formData.Username,
DisplayName: formData.Email,
DisplayName: string(formData.Email),
FirstName: formData.Firstname,
LastName: formData.Lastname,
NickName: formData.Nickname,

View File

@ -16,14 +16,14 @@ const (
)
type registerFormData struct {
Email string `schema:"email"`
Username string `schema:"username"`
Firstname string `schema:"firstname"`
Lastname string `schema:"lastname"`
Language string `schema:"language"`
Password string `schema:"register-password"`
Password2 string `schema:"register-password-confirmation"`
TermsConfirm bool `schema:"terms-confirm"`
Email domain.EmailAddress `schema:"email"`
Username string `schema:"username"`
Firstname string `schema:"firstname"`
Lastname string `schema:"lastname"`
Language string `schema:"language"`
Password string `schema:"register-password"`
Password2 string `schema:"register-password-confirmation"`
TermsConfirm bool `schema:"terms-confirm"`
}
type registerData struct {

View File

@ -94,7 +94,7 @@ func (l *Login) passLoginHintToRegistration(r *http.Request, authReq *domain.Aut
if authReq == nil {
return data
}
data.Email = authReq.LoginHint
data.Email = domain.EmailAddress(authReq.LoginHint)
domainPolicy, err := l.getOrgDomainPolicy(r, authReq.RequestedOrgID)
if err != nil {
logging.WithFields("authRequest", authReq.ID, "org", authReq.RequestedOrgID).Error("unable to load domain policy for registration loginHint")

View File

@ -14,14 +14,14 @@ const (
)
type registerOrgFormData struct {
RegisterOrgName string `schema:"orgname"`
Email string `schema:"email"`
Username string `schema:"username"`
Firstname string `schema:"firstname"`
Lastname string `schema:"lastname"`
Password string `schema:"register-password"`
Password2 string `schema:"register-password-confirmation"`
TermsConfirm bool `schema:"terms-confirm"`
RegisterOrgName string `schema:"orgname"`
Email domain.EmailAddress `schema:"email"`
Username string `schema:"username"`
Firstname string `schema:"firstname"`
Lastname string `schema:"lastname"`
Password string `schema:"register-password"`
Password2 string `schema:"register-password-confirmation"`
TermsConfirm bool `schema:"terms-confirm"`
}
type registerOrgData struct {
@ -121,7 +121,7 @@ func (l *Login) renderRegisterOrg(w http.ResponseWriter, r *http.Request, authRe
func (d registerOrgFormData) toUserDomain() *domain.Human {
if d.Username == "" {
d.Username = d.Email
d.Username = string(d.Email)
}
return &domain.Human{
Username: d.Username,
@ -140,7 +140,7 @@ func (d registerOrgFormData) toUserDomain() *domain.Human {
func (d registerOrgFormData) toCommandOrg() *command.OrgSetup {
if d.Username == "" {
d.Username = d.Email
d.Username = string(d.Email)
}
return &command.OrgSetup{
Name: d.RegisterOrgName,

View File

@ -328,6 +328,33 @@ Errors:
Invalid: Userdaten sind ungültig
DomainNotAllowedAsUsername: Domäne ist bereits reserviert und kann nicht verwendet werden
NotAllowedToLink: Der Benutzer darf nicht mit einem externen Login Provider verlinkt werden
Profile:
NotFound: Profil nicht gefunden
NotChanged: Profil nicht verändert
Empty: Profil ist leer
FirstNameEmpty: Vorname im Profil ist leer
LastNameEmpty: Nachname im Profil ist leer
IDMissing: Profil ID fehlt
Email:
NotFound: Email nicht gefunden
Invalid: Email ist ungültig
AlreadyVerified: Email ist bereits verifiziert
NotChanged: Email wurde nicht geändert
Empty: Email ist leer
IDMissing: Email ID fehlt
Phone:
NotFound: Telefonnummer nicht gefunden
Invalid: Telefonnummer ist ungültig
AlreadyVerified: Telefonnummer bereits verifiziert
Empty: Telefonnummer ist leer
NotChanged: Telefonnummer wurde nicht geändert
Address:
NotFound: Adresse nicht gefunden
NotChanged: Adresse wurde nicht geändert
Username:
AlreadyExists: Benutzername ist bereits vergeben
Reserved: Benutzername ist bereits vergeben
Empty: Benutzername ist leer
Password:
ConfirmationWrong: Passwort Bestätigung stimmt nicht überein
Empty: Passwort ist leer

View File

@ -328,6 +328,33 @@ Errors:
Invalid: Invalid userdata
DomainNotAllowedAsUsername: Domain is already reserved and cannot be used
NotAllowedToLink: User is not allowed to link with external login provider
Profile:
NotFound: Profile not found
NotChanged: Profile not changed
Empty: Profile is empty
FirstNameEmpty: First name in profile is empty
LastNameEmpty: Last name in profile is empty
IDMissing: Profile ID is missing
Email:
NotFound: Email not found
Invalid: Email is invalid
AlreadyVerified: Email is already verified
NotChanged: Email not changed
Empty: Email is empty
IDMissing: Email ID is missing
Phone:
NotFound: Phone not found
Invalid: Phone is invalid
AlreadyVerified: Phone already verified
Empty: Phone is empty
NotChanged: Phone not changed
Address:
NotFound: Address not found
NotChanged: Address not changed
Username:
AlreadyExists: Username already taken
Reserved: Username is already taken
Empty: Username is empty
Password:
ConfirmationWrong: Passwordconfirmation is wrong
Empty: Password is empty

View File

@ -328,6 +328,33 @@ Errors:
Invalid: Données utilisateur non valides
DomainNotAllowedAsUsername: Le domaine est déjà réservé et ne peut pas être utilisé.
NotAllowedToLink: L'utilisateur n'est pas autorisé à établir un lien avec un fournisseur de connexion externe
Profile:
NotFound: Profil non trouvé
NotChanged: Le profil n'a pas changé
Empty: Profil est vide
FirstNameEmpty: Le prénom dans le profil est vide
LastNameEmpty: Le nom de famille dans le profil est vide
IDMissing: Profil ID manquant
Email:
NotFound: Email non trouvé
Invalid: L'email n'est pas valide
AlreadyVerified: L'adresse électronique est déjà vérifiée
NotChanged: L'adresse électronique n'a pas changé
Empty: Email est vide
IDMissing: Email ID manquant
Phone:
Notfound: Téléphone non trouvé
Invalid: Le téléphone n'est pas valide
AlreadyVerified: Téléphone déjà vérifié
Empty: Téléphone est vide
NotChanged: Téléphone n'a pas changé
Address:
NotFound: Adresse non trouvée
NotChanged: L'adresse n'a pas changé
Username:
AlreadyExists: Nom d'utilisateur déjà pris
Reserved: Le nom d'utilisateur est déjà pris
Empty: Le nom d'utilisateur est vide
Password:
ConfirmationWrong: La confirmation du mot de passe est erronée
Empty: Le mot de passe est vide

View File

@ -328,6 +328,33 @@ Errors:
Invalid: I dati del utente non sono validi
DomainNotAllowedAsUsername: Il dominio è già riservato e non può essere utilizzato
NotAllowedToLink: L'utente non è autorizzato a collegarsi con un provider di accesso esterno
Profile:
NotFound: Profilo non trovato
NotChanged: Profilo non cambiato
Empty: Profilo è vuoto
FirstNameEmpty: Il nome nel profilo è vuoto
LastNameEmpty: Il cognome nel profilo è vuoto
IDMissing: Profilo ID mancante
Email:
NotFound: Email non trovata
Invalid: L'e-mail non è valida
AlreadyVerified: L'e-mail è già verificata
NotChanged: Email non cambiata
Empty: Email è vuota
IDMissing: Email ID mancante
Phone:
NotFound: Telefono non trovato
Invalid: Il telefono non è valido
AlreadyVerified: Telefono già verificato
Empty: Il telefono è vuoto
NotChanged: Telefono non cambiato
Address:
NotFound: Indirizzo non trovato
NotChanged: Indirizzo non cambiato
Username:
AlreadyExists: Nome utente già preso
Reserved: Il nome utente è già preso
Empty: Il nome utente è vuoto
Password:
ConfirmationWrong: La conferma della password è sbagliata
Empty: La password è vuota

View File

@ -328,6 +328,33 @@ Errors:
Invalid: Nieprawidłowe dane użytkownika
DomainNotAllowedAsUsername: Domena jest już zarezerwowana i nie może być użyta
NotAllowedToLink: Użytkownik nie jest upoważniony do łączenia z zewnętrznym dostawcą logowania
Profile:
NotFound: Profil nie znaleziony
NotChanged: Profil nie zmieniony
Empty: Profil jest pusty
FirstNameEmpty: Imię w profilu jest puste
LastNameEmpty: Nazwisko w profilu jest puste
IDMissing: Profil ID brakuje
Email:
NotFound: Adres e-mail nie znaleziony
Invalid: Adres e-mail jest nieprawidłowy
AlreadyVerified: Adres e-mail jest już zweryfikowany
NotChanged: Adres e-mail nie zmieniony
Empty: Adres e-mail jest pusty
IDMissing: Adres e-mail ID brakuje
Phone:
NotFound: Numer telefonu nie znaleziony
Invalid: Numer telefonu jest nieprawidłowy
AlreadyVerified: Numer telefonu już zweryfikowany
Empty: Numer telefonu jest pusty
NotChanged: Numer telefonu nie zmieniony
Address:
NotFound: Adres nie znaleziony
NotChanged: Adres nie zmieniony
Username:
AlreadyExists: Nazwa użytkownika jest już zajęta
Reserved: Nazwa użytkownika jest już zajęta
Empty: Nazwa użytkownika jest pusty
Password:
ConfirmationWrong: Potwierdzenie hasła jest niepoprawne
Empty: Hasło jest puste

View File

@ -328,6 +328,33 @@ Errors:
Invalid: 无效的用户数据
DomainNotAllowedAsUsername: 域名已存在,但无法使用
NotAllowedToLink: 不允许用户使用外部身份提供者注册
Profile:
NotFound: 未找到个人资料
NotChanged: 个人资料未更改
Empty: 简介是空的
FirstNameEmpty: 简介中的名字是空的
LastNameEmpty: 简介中的姓氏是空的
IDMissing: 简介ID丢失
Email:
NotFound: 电子邮件没有找到
Invalid: 电子邮件无效
AlreadyVerified: 电子邮件已经过验证
NotChanged: 电子邮件未更改
Empty: 电子邮件是空的
IDMissing: 电子邮件ID丢失
Phone:
NotFound: 手机号码未找到
Invalid: 手机号码无效
AlreadyVerified: 手机号码已经验证
Empty: 电话号码是空的
NotChanged: 电话号码没有改变
Address:
NotFound: 找不到地址
NotChanged: 地址没有改变
Username:
AlreadyExists: 用户名已被使用
Reserved: 用户名已被使用
Empty: 用户名是空的
Password:
ConfirmationWrong: 密码不一致
Empty: 密码为空

View File

@ -10,12 +10,12 @@ import (
)
type Email struct {
Address string
Address domain.EmailAddress
Verified bool
}
func (e *Email) Valid() bool {
return e.Address != "" && domain.EmailRegex.MatchString(e.Address)
func (e *Email) Validate() error {
return e.Address.Validate()
}
func newEmailCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {

View File

@ -4,30 +4,16 @@ import (
"context"
"time"
"github.com/ttacon/libphonenumber"
"github.com/zitadel/zitadel/internal/command/preparation"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
)
type Phone struct {
Number string
Number domain.PhoneNumber
Verified bool
}
func FormatPhoneNumber(number string) (string, error) {
if number == "" {
return "", nil
}
phoneNr, err := libphonenumber.Parse(number, libphonenumber.UNKNOWN_REGION)
if err != nil {
return "", errors.ThrowInvalidArgument(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
}
number = libphonenumber.Format(phoneNr, libphonenumber.E164)
return number, nil
}
func newPhoneCode(ctx context.Context, filter preparation.FilterToQueryReducer, alg crypto.EncryptionAlgorithm) (value *crypto.CryptoValue, expiry time.Duration, err error) {
return newCryptoCodeWithExpiry(ctx, filter, domain.SecretGeneratorTypeVerifyPhoneCode, alg)
}

View File

@ -3,12 +3,13 @@ package command
import (
"testing"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
)
func TestFormatPhoneNumber(t *testing.T) {
type args struct {
number string
number domain.PhoneNumber
}
tests := []struct {
name string
@ -44,10 +45,9 @@ func TestFormatPhoneNumber(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
formatted, err := FormatPhoneNumber(tt.args.number)
if tt.errFunc == nil && tt.result.Number != formatted {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.args.number, formatted)
normalized, err := tt.args.number.Normalize()
if tt.errFunc == nil && tt.result.Number != normalized {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.Number, normalized)
}
if tt.errFunc != nil && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)

View File

@ -104,29 +104,31 @@ func (c *Commands) AddHuman(ctx context.Context, resourceOwner string, human *Ad
type humanCreationCommand interface {
eventstore.Command
AddPhoneData(phoneNumber string)
AddPhoneData(phoneNumber domain.PhoneNumber)
AddPasswordData(secret *crypto.CryptoValue, changeRequired bool)
}
func AddHumanCommand(a *user.Aggregate, human *AddHuman, passwordAlg crypto.HashAlgorithm, codeAlg crypto.EncryptionAlgorithm) preparation.Validation {
return func() (_ preparation.CreateCommands, err error) {
if !human.Email.Valid() {
return nil, errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument")
if err := human.Email.Validate(); err != nil {
return nil, err
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
return nil, errors.ThrowInvalidArgument(nil, "V2-zzad3", "Errors.Invalid.Argument")
}
if human.FirstName = strings.TrimSpace(human.FirstName); human.FirstName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument")
return nil, errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.User.Profile.FirstNameEmpty")
}
if human.LastName = strings.TrimSpace(human.LastName); human.LastName == "" {
return nil, errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument")
return nil, errors.ThrowInvalidArgument(nil, "USER-4hB7d", "Errors.User.Profile.LastNameEmpty")
}
human.ensureDisplayName()
if human.Phone.Number, err = FormatPhoneNumber(human.Phone.Number); err != nil {
return nil, errors.ThrowInvalidArgument(nil, "USER-tD6ax", "Errors.Invalid.Argument")
if human.Phone.Number != "" {
if human.Phone.Number, err = human.Phone.Number.Normalize(); err != nil {
return nil, err
}
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
@ -387,19 +389,12 @@ func (c *Commands) RegisterHuman(ctx context.Context, orgID string, human *domai
return writeModelToHuman(registeredHuman), nil
}
func (c *Commands) addHuman(ctx context.Context, orgID string, human *domain.Human, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator crypto.Generator) ([]eventstore.Command, *HumanWriteModel, error) {
if orgID == "" || !human.IsValid() {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-67Ms8", "Errors.User.Invalid")
}
if human.Password != nil && human.Password.SecretString != "" {
human.Password.ChangeRequired = true
}
return c.createHuman(ctx, orgID, human, nil, false, false, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
}
func (c *Commands) importHuman(ctx context.Context, orgID string, human *domain.Human, passwordless bool, links []*domain.UserIDPLink, domainPolicy *domain.DomainPolicy, pwPolicy *domain.PasswordComplexityPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessCodeGenerator crypto.Generator) (events []eventstore.Command, humanWriteModel *HumanWriteModel, passwordlessCodeWriteModel *HumanPasswordlessInitCodeWriteModel, code string, err error) {
if orgID == "" || !human.IsValid() {
return nil, nil, nil, "", errors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Invalid")
if orgID == "" {
return nil, nil, nil, "", errors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.Org.Empty")
}
if err := human.Normalize(); err != nil {
return nil, nil, nil, "", err
}
events, humanWriteModel, err = c.createHuman(ctx, orgID, human, links, false, passwordless, domainPolicy, pwPolicy, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator)
if err != nil {
@ -421,10 +416,16 @@ func (c *Commands) registerHuman(ctx context.Context, orgID string, human *domai
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-JKefw", "Errors.User.Invalid")
}
if human.Username = strings.TrimSpace(human.Username); human.Username == "" {
human.Username = human.EmailAddress
human.Username = string(human.EmailAddress)
}
if orgID == "" || !human.IsValid() || link == nil && (human.Password == nil || human.Password.SecretString == "") {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-9dk45", "Errors.User.Invalid")
if orgID == "" {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-hYsVH", "Errors.Org.Empty")
}
if err := human.Normalize(); err != nil {
return nil, nil, err
}
if link == nil && (human.Password == nil || human.Password.SecretString == "") {
return nil, nil, errors.ThrowInvalidArgument(nil, "COMMAND-X23na", "Errors.User.Password.Empty")
}
if human.Password != nil && human.Password.SecretString != "" {
human.Password.ChangeRequired = false
@ -441,7 +442,7 @@ func (c *Commands) createHuman(ctx context.Context, orgID string, human *domain.
return nil, nil, err
}
human.Username = strings.TrimSpace(human.Username)
human.EmailAddress = strings.TrimSpace(human.EmailAddress)
human.EmailAddress = human.EmailAddress.Normalize()
if !domainPolicy.UserLoginMustBeDomain {
index := strings.LastIndex(human.Username, "@")
if index > 1 {

View File

@ -13,8 +13,11 @@ import (
)
func (c *Commands) ChangeHumanEmail(ctx context.Context, email *domain.Email, emailCodeGenerator crypto.Generator) (*domain.Email, error) {
if !email.IsValid() || email.AggregateID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-4M9sf", "Errors.Email.Invalid")
if email.AggregateID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-0Gzs3", "Errors.User.Email.IDMissing")
}
if err := email.Validate(); err != nil {
return nil, err
}
existingEmail, err := c.emailWriteModel(ctx, email.AggregateID, email.ResourceOwner)

View File

@ -14,7 +14,7 @@ import (
type HumanEmailWriteModel struct {
eventstore.WriteModel
Email string
Email domain.EmailAddress
IsEmailVerified bool
Code *crypto.CryptoValue
@ -95,7 +95,7 @@ func (wm *HumanEmailWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *HumanEmailWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
email string,
email domain.EmailAddress,
) (*user.HumanEmailChangedEvent, bool) {
if wm.Email == email {
return nil, false

View File

@ -11,8 +11,8 @@ import (
"github.com/zitadel/zitadel/internal/repository/user"
)
//ResendInitialMail resend inital mail and changes email if provided
func (c *Commands) ResendInitialMail(ctx context.Context, userID, email, resourceOwner string, initCodeGenerator crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
// ResendInitialMail resend initial mail and changes email if provided
func (c *Commands) ResendInitialMail(ctx context.Context, userID string, email domain.EmailAddress, resourceOwner string, initCodeGenerator crypto.Generator) (objectDetails *domain.ObjectDetails, err error) {
if userID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing")
}

View File

@ -14,7 +14,7 @@ import (
type HumanInitCodeWriteModel struct {
eventstore.WriteModel
Email string
Email domain.EmailAddress
IsEmailVerified bool
Code *crypto.CryptoValue
@ -92,7 +92,7 @@ func (wm *HumanInitCodeWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *HumanInitCodeWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
email string,
email domain.EmailAddress,
) (*user.HumanEmailChangedEvent, bool) {
changedEvent := user.NewHumanEmailChangedEvent(ctx, aggregate, email)
return changedEvent, wm.Email != email

View File

@ -289,7 +289,7 @@ func TestCommandSide_ResendInitialMail(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, tt.args.email, tt.args.resourceOwner, tt.args.secretGenerator)
got, err := r.ResendInitialMail(tt.args.ctx, tt.args.userID, domain.EmailAddress(tt.args.email), tt.args.resourceOwner, tt.args.secretGenerator)
if tt.res.err == nil {
assert.NoError(t, err)
}

View File

@ -22,10 +22,10 @@ type HumanWriteModel struct {
Gender domain.Gender
Avatar string
Email string
Email domain.EmailAddress
IsEmailVerified bool
Phone string
Phone domain.PhoneNumber
IsPhoneVerified bool
Country string

View File

@ -69,7 +69,7 @@ func (c *Commands) AddHumanOTP(ctx context.Context, userID, resourceowner string
accountName := domain.GenerateLoginName(human.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = human.EmailAddress
accountName = string(human.EmailAddress)
}
key, secret, err := domain.NewOTPKey(c.multifactors.OTP.Issuer, accountName, c.multifactors.OTP.CryptoMFA)
if err != nil {

View File

@ -14,10 +14,9 @@ import (
)
func (c *Commands) ChangeHumanPhone(ctx context.Context, phone *domain.Phone, resourceOwner string, phoneCodeGenerator crypto.Generator) (*domain.Phone, error) {
if !phone.IsValid() {
return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-6M0ds", "Errors.Phone.Invalid")
if err := phone.Normalize(); err != nil {
return nil, err
}
existingPhone, err := c.phoneWriteModelByID(ctx, phone.AggregateID, resourceOwner)
if err != nil {
return nil, err

View File

@ -14,7 +14,7 @@ import (
type HumanPhoneWriteModel struct {
eventstore.WriteModel
Phone string
Phone domain.PhoneNumber
IsPhoneVerified bool
Code *crypto.CryptoValue
@ -107,7 +107,7 @@ func (wm *HumanPhoneWriteModel) Query() *eventstore.SearchQueryBuilder {
func (wm *HumanPhoneWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
phone string,
phone domain.PhoneNumber,
) (*user.HumanPhoneChangedEvent, bool) {
changedEvent := user.NewHumanPhoneChangedEvent(ctx, aggregate, phone)
return changedEvent, phone != wm.Phone

View File

@ -9,10 +9,12 @@ import (
)
func (c *Commands) ChangeHumanProfile(ctx context.Context, profile *domain.Profile) (*domain.Profile, error) {
if !profile.IsValid() && profile.AggregateID != "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-8io0d", "Errors.User.Profile.Invalid")
if profile.AggregateID == "" {
return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-AwbEB", "Errors.User.Profile.IDMissing")
}
if err := profile.Validate(); err != nil {
return nil, err
}
existingProfile, err := c.profileWriteModelByID(ctx, profile.AggregateID, profile.ResourceOwner)
if err != nil {
return nil, err

View File

@ -3445,7 +3445,7 @@ func newAddHumanEvent(password string, changeRequired bool, phone string) *user.
changeRequired)
}
if phone != "" {
event.AddPhoneData(phone)
event.AddPhoneData(domain.PhoneNumber(phone))
}
return event
}
@ -3473,7 +3473,7 @@ func newRegisterHumanEvent(username, password string, changeRequired bool, phone
changeRequired)
}
if phone != "" {
event.AddPhoneData(phone)
event.AddPhoneData(domain.PhoneNumber(phone))
}
return event
}
@ -3503,7 +3503,7 @@ func TestAddHumanCommand(t *testing.T) {
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-Ec7dM", "Errors.Invalid.Argument"),
ValidationErr: errors.ThrowInvalidArgument(nil, "EMAIL-599BI", "Errors.User.Email.Invalid"),
},
},
{
@ -3519,7 +3519,7 @@ func TestAddHumanCommand(t *testing.T) {
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.Invalid.Argument"),
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-UCej2", "Errors.User.Profile.FirstNameEmpty"),
},
},
{
@ -3534,7 +3534,7 @@ func TestAddHumanCommand(t *testing.T) {
},
},
want: Want{
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-DiAq8", "Errors.Invalid.Argument"),
ValidationErr: errors.ThrowInvalidArgument(nil, "USER-4hB7d", "Errors.User.Profile.LastNameEmpty"),
},
},
{

View File

@ -155,7 +155,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner s
}
accountName := domain.GenerateLoginName(user.GetUsername(), org.PrimaryDomain, orgPolicy.UserLoginMustBeDomain)
if accountName == "" {
accountName = user.EmailAddress
accountName = string(user.EmailAddress)
}
webAuthN, err := c.webauthnConfig.BeginRegistration(ctx, user, accountName, authenticatorPlatform, userVerification, isLoginUI, tokens...)
if err != nil {

View File

@ -65,10 +65,10 @@ type ExternalUser struct {
FirstName string
LastName string
NickName string
Email string
Email EmailAddress
IsEmailVerified bool
PreferredLanguage language.Tag
Phone string
Phone PhoneNumber
IsPhoneVerified bool
Metadatas []*Metadata
}

View File

@ -4,6 +4,7 @@ import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/errors"
caos_errors "github.com/zitadel/zitadel/internal/errors"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
@ -60,8 +61,22 @@ func (f Gender) Specified() bool {
return f > GenderUnspecified && f < genderCount
}
func (u *Human) IsValid() bool {
return u.Username != "" && u.Profile != nil && u.Profile.IsValid() && u.Email != nil && u.Email.IsValid() && u.Phone == nil || (u.Phone != nil && u.Phone.PhoneNumber != "" && u.Phone.IsValid())
func (u *Human) Normalize() error {
if u.Username == "" {
return errors.ThrowInvalidArgument(nil, "COMMAND-00p2b", "Errors.User.Username.Empty")
}
if err := u.Profile.Validate(); err != nil {
return err
}
if err := u.Email.Validate(); err != nil {
return err
}
if u.Phone != nil && u.Phone.PhoneNumber != "" {
if err := u.Phone.Normalize(); err != nil {
return err
}
}
return nil
}
func (u *Human) CheckDomainPolicy(policy *DomainPolicy) error {
@ -69,7 +84,7 @@ func (u *Human) CheckDomainPolicy(policy *DomainPolicy) error {
return caos_errors.ThrowPreconditionFailed(nil, "DOMAIN-zSH7j", "Errors.Users.DomainPolicyNil")
}
if !policy.UserLoginMustBeDomain && u.Profile != nil && u.Username == "" && u.Email != nil {
u.Username = u.EmailAddress
u.Username = string(u.EmailAddress)
}
return nil
}

View File

@ -2,20 +2,38 @@ package domain
import (
"regexp"
"strings"
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/errors"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
var (
EmailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
)
type EmailAddress string
func (e EmailAddress) Validate() error {
if e == "" {
return errors.ThrowInvalidArgument(nil, "EMAIL-spblu", "Errors.User.Email.Empty")
}
if !emailRegex.MatchString(string(e)) {
return errors.ThrowInvalidArgument(nil, "EMAIL-599BI", "Errors.User.Email.Invalid")
}
return nil
}
func (e EmailAddress) Normalize() EmailAddress {
return EmailAddress(strings.TrimSpace(string(e)))
}
type Email struct {
es_models.ObjectRoot
EmailAddress string
EmailAddress EmailAddress
IsEmailVerified bool
}
@ -26,8 +44,11 @@ type EmailCode struct {
Expiry time.Duration
}
func (e *Email) IsValid() bool {
return e.EmailAddress != "" && EmailRegex.MatchString(e.EmailAddress)
func (e *Email) Validate() error {
if e == nil {
return errors.ThrowInvalidArgument(nil, "EMAIL-spblu", "Errors.User.Email.Empty")
}
return e.EmailAddress.Validate()
}
func NewEmailCode(emailGenerator crypto.Generator) (*EmailCode, error) {

View File

@ -65,7 +65,7 @@ func TestEmailValid(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := tt.args.email.IsValid()
result := tt.args.email.Validate() == nil
if result != tt.result {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result, result)
}

View File

@ -4,19 +4,31 @@ import (
"time"
"github.com/ttacon/libphonenumber"
"github.com/zitadel/zitadel/internal/crypto"
caos_errs "github.com/zitadel/zitadel/internal/errors"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
const (
defaultRegion = "CH"
)
const defaultRegion = "CH"
type PhoneNumber string
func (p PhoneNumber) Normalize() (PhoneNumber, error) {
if p == "" {
return p, caos_errs.ThrowInvalidArgument(nil, "PHONE-Zt0NV", "Errors.User.Phone.Empty")
}
phoneNr, err := libphonenumber.Parse(string(p), defaultRegion)
if err != nil {
return p, caos_errs.ThrowInvalidArgument(err, "PHONE-so0wa", "Errors.User.Phone.Invalid")
}
return PhoneNumber(libphonenumber.Format(phoneNr, libphonenumber.E164)), nil
}
type Phone struct {
es_models.ObjectRoot
PhoneNumber string
PhoneNumber PhoneNumber
IsPhoneVerified bool
}
@ -27,17 +39,16 @@ type PhoneCode struct {
Expiry time.Duration
}
func (p *Phone) IsValid() bool {
err := p.formatPhone()
return p.PhoneNumber != "" && err == nil
}
func (p *Phone) formatPhone() error {
phoneNr, err := libphonenumber.Parse(p.PhoneNumber, defaultRegion)
if err != nil {
return caos_errs.ThrowInvalidArgument(nil, "EVENT-so0wa", "Errors.User.Phone.Invalid")
func (p *Phone) Normalize() error {
if p == nil {
return caos_errs.ThrowInvalidArgument(nil, "PHONE-YlbwO", "Errors.User.Phone.Empty")
}
p.PhoneNumber = libphonenumber.Format(phoneNr, libphonenumber.E164)
normalizedNumber, err := p.PhoneNumber.Normalize()
if err != nil {
return err
}
// Issue for avoiding mutating state: https://github.com/zitadel/zitadel/issues/5412
p.PhoneNumber = normalizedNumber
return nil
}

View File

@ -94,10 +94,9 @@ func TestFormatPhoneNumber(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.phone.formatPhone()
if tt.errFunc == nil && tt.result.PhoneNumber != tt.args.phone.PhoneNumber {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.args.phone.PhoneNumber, tt.result.PhoneNumber)
normalized, err := tt.args.phone.PhoneNumber.Normalize()
if tt.errFunc == nil && tt.result.PhoneNumber != normalized {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.result.PhoneNumber, normalized)
}
if tt.errFunc != nil && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)

View File

@ -3,6 +3,7 @@ package domain
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/errors"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
@ -19,8 +20,17 @@ type Profile struct {
LoginNames []string
}
func (p *Profile) IsValid() bool {
return p.FirstName != "" && p.LastName != ""
func (p *Profile) Validate() error {
if p == nil {
return errors.ThrowInvalidArgument(nil, "PROFILE-GPY3p", "Errors.User.Profile.Empty")
}
if p.FirstName == "" {
return errors.ThrowInvalidArgument(nil, "PROFILE-RF5z2", "Errors.User.Profile.FirstNameEmpty")
}
if p.LastName == "" {
return errors.ThrowInvalidArgument(nil, "PROFILE-DSUkN", "Errors.User.Profile.LastNameEmpty")
}
return nil
}
func AvatarURL(prefix, resourceOwner, key string) string {

View File

@ -4,6 +4,8 @@ import (
"context"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
)
// Provider is the minimal implementation for a 3rd party authentication provider
@ -24,9 +26,9 @@ type User interface {
GetDisplayName() string
GetNickname() string
GetPreferredUsername() string
GetEmail() string
GetEmail() domain.EmailAddress
IsEmailVerified() bool
GetPhone() string
GetPhone() domain.PhoneNumber
IsPhoneVerified() bool
GetPreferredLanguage() language.Tag
GetAvatarURL() string

View File

@ -7,6 +7,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
)
@ -120,13 +121,13 @@ func newConfig(tenant TenantType, clientID, secret, callbackURL string, scopes [
// AzureAD does not return an `email_verified` claim.
// The verification can be automatically activated on the provider ([WithEmailVerified])
type User struct {
Sub string `json:"sub"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Email string `json:"email"`
Picture string `json:"picture"`
Sub string `json:"sub"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Email domain.EmailAddress `json:"email"`
Picture string `json:"picture"`
isEmailVerified bool
}
@ -162,7 +163,7 @@ func (u *User) GetPreferredUsername() string {
}
// GetEmail is an implementation of the [idp.User] interface.
func (u *User) GetEmail() string {
func (u *User) GetEmail() domain.EmailAddress {
return u.Email
}
@ -176,7 +177,7 @@ func (u *User) IsEmailVerified() bool {
// GetPhone is an implementation of the [idp.User] interface.
// It returns an empty string because AzureAD does not provide the user's phone.
func (u *User) GetPhone() string {
func (u *User) GetPhone() domain.PhoneNumber {
return ""
}

View File

@ -14,6 +14,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
)
@ -259,9 +260,9 @@ func TestSession_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -4,6 +4,8 @@ import (
"strconv"
"time"
"github.com/zitadel/zitadel/internal/domain"
"golang.org/x/oauth2"
"golang.org/x/text/language"
@ -68,44 +70,44 @@ func newConfig(clientID, secret, callbackURL, authURL, tokenURL string, scopes [
// User is a representation of the authenticated GitHub user and implements the [idp.User] interface
// https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-the-authenticated-user
type User struct {
Login string `json:"login"`
ID int `json:"id"`
NodeId string `json:"node_id"`
AvatarUrl string `json:"avatar_url"`
GravatarId string `json:"gravatar_id"`
Url string `json:"url"`
HtmlUrl string `json:"html_url"`
FollowersUrl string `json:"followers_url"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
OrganizationsUrl string `json:"organizations_url"`
ReposUrl string `json:"repos_url"`
EventsUrl string `json:"events_url"`
ReceivedEventsUrl string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Name string `json:"name"`
Company string `json:"company"`
Blog string `json:"blog"`
Location string `json:"location"`
Email string `json:"email"`
Hireable bool `json:"hireable"`
Bio string `json:"bio"`
TwitterUsername string `json:"twitter_username"`
PublicRepos int `json:"public_repos"`
PublicGists int `json:"public_gists"`
Followers int `json:"followers"`
Following int `json:"following"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrivateGists int `json:"private_gists"`
TotalPrivateRepos int `json:"total_private_repos"`
OwnedPrivateRepos int `json:"owned_private_repos"`
DiskUsage int `json:"disk_usage"`
Collaborators int `json:"collaborators"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Login string `json:"login"`
ID int `json:"id"`
NodeId string `json:"node_id"`
AvatarUrl string `json:"avatar_url"`
GravatarId string `json:"gravatar_id"`
Url string `json:"url"`
HtmlUrl string `json:"html_url"`
FollowersUrl string `json:"followers_url"`
FollowingUrl string `json:"following_url"`
GistsUrl string `json:"gists_url"`
StarredUrl string `json:"starred_url"`
SubscriptionsUrl string `json:"subscriptions_url"`
OrganizationsUrl string `json:"organizations_url"`
ReposUrl string `json:"repos_url"`
EventsUrl string `json:"events_url"`
ReceivedEventsUrl string `json:"received_events_url"`
Type string `json:"type"`
SiteAdmin bool `json:"site_admin"`
Name string `json:"name"`
Company string `json:"company"`
Blog string `json:"blog"`
Location string `json:"location"`
Email domain.EmailAddress `json:"email"`
Hireable bool `json:"hireable"`
Bio string `json:"bio"`
TwitterUsername string `json:"twitter_username"`
PublicRepos int `json:"public_repos"`
PublicGists int `json:"public_gists"`
Followers int `json:"followers"`
Following int `json:"following"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
PrivateGists int `json:"private_gists"`
TotalPrivateRepos int `json:"total_private_repos"`
OwnedPrivateRepos int `json:"owned_private_repos"`
DiskUsage int `json:"disk_usage"`
Collaborators int `json:"collaborators"`
TwoFactorAuthentication bool `json:"two_factor_authentication"`
Plan struct {
Name string `json:"name"`
Space int `json:"space"`
@ -150,7 +152,7 @@ func (u *User) GetPreferredUsername() string {
}
// GetEmail is an implementation of the [idp.User] interface.
func (u *User) GetEmail() string {
func (u *User) GetEmail() domain.EmailAddress {
return u.Email
}
@ -162,7 +164,7 @@ func (u *User) IsEmailVerified() bool {
// GetPhone is an implementation of the [idp.User] interface.
// It returns an empty string because GitHub does not provide the user's phone.
func (u *User) GetPhone() string {
func (u *User) GetPhone() domain.PhoneNumber {
return ""
}

View File

@ -14,6 +14,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
)
@ -188,9 +189,9 @@ func TestSession_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -14,6 +14,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
)
@ -187,9 +188,9 @@ func TestProvider_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -43,5 +43,5 @@ type User struct {
// GetPreferredUsername implements the [idp.User] interface.
// It returns the email, because Google does not return a username.
func (u *User) GetPreferredUsername() string {
return u.GetEmail()
return string(u.GetEmail())
}

View File

@ -14,6 +14,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp/providers/oidc"
)
@ -188,9 +189,9 @@ func TestSession_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -12,6 +12,7 @@ import (
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
@ -106,8 +107,8 @@ func (u *User) GetNickname() string {
return u.IDTokenClaims.GetNickname()
}
func (u *User) GetPhone() string {
return u.IDTokenClaims.GetPhoneNumber()
func (u *User) GetPhone() domain.PhoneNumber {
return domain.PhoneNumber(u.IDTokenClaims.GetPhoneNumber())
}
func (u *User) IsPhoneVerified() bool {
@ -121,3 +122,7 @@ func (u *User) GetPreferredLanguage() language.Tag {
func (u *User) GetAvatarURL() string {
return u.IDTokenClaims.GetPicture()
}
func (u *User) GetEmail() domain.EmailAddress {
return domain.EmailAddress(u.IDTokenClaims.GetEmail())
}

View File

@ -17,6 +17,7 @@ import (
"gopkg.in/square/go-jose.v2"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
)
func TestSession_FetchUser(t *testing.T) {
@ -193,9 +194,9 @@ func TestSession_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -10,6 +10,7 @@ import (
"github.com/go-ldap/ldap/v3"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
@ -87,9 +88,9 @@ func (s *Session) FetchUser(_ context.Context) (idp.User, error) {
user.GetAttributeValue(s.Provider.displayNameAttribute),
user.GetAttributeValue(s.Provider.nickNameAttribute),
user.GetAttributeValue(s.Provider.preferredUsernameAttribute),
user.GetAttributeValue(s.Provider.emailAttribute),
domain.EmailAddress(user.GetAttributeValue(s.Provider.emailAttribute)),
emailVerified,
user.GetAttributeValue(s.Provider.phoneAttribute),
domain.PhoneNumber(user.GetAttributeValue(s.Provider.phoneAttribute)),
phoneVerified,
language.Make(user.GetAttributeValue(s.Provider.preferredLanguageAttribute)),
user.GetAttributeValue(s.Provider.avatarURLAttribute),

View File

@ -1,6 +1,10 @@
package ldap
import "golang.org/x/text/language"
import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
)
type User struct {
id string
@ -9,9 +13,9 @@ type User struct {
displayName string
nickName string
preferredUsername string
email string
email domain.EmailAddress
emailVerified bool
phone string
phone domain.PhoneNumber
phoneVerified bool
preferredLanguage language.Tag
avatarURL string
@ -25,9 +29,9 @@ func NewUser(
displayName string,
nickName string,
preferredUsername string,
email string,
email domain.EmailAddress,
emailVerified bool,
phone string,
phone domain.PhoneNumber,
phoneVerified bool,
preferredLanguage language.Tag,
avatarURL string,
@ -68,13 +72,13 @@ func (u *User) GetNickname() string {
func (u *User) GetPreferredUsername() string {
return u.preferredUsername
}
func (u *User) GetEmail() string {
func (u *User) GetEmail() domain.EmailAddress {
return u.email
}
func (u *User) IsEmailVerified() bool {
return u.emailVerified
}
func (u *User) GetPhone() string {
func (u *User) GetPhone() domain.PhoneNumber {
return u.phone
}
func (u *User) IsPhoneVerified() bool {

View File

@ -7,6 +7,7 @@ import (
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
@ -74,7 +75,7 @@ func (u *UserMapper) GetPreferredUsername() string {
}
// GetEmail is an implementation of the [idp.User] interface.
func (u *UserMapper) GetEmail() string {
func (u *UserMapper) GetEmail() domain.EmailAddress {
return ""
}
@ -84,7 +85,7 @@ func (u *UserMapper) IsEmailVerified() bool {
}
// GetPhone is an implementation of the [idp.User] interface.
func (u *UserMapper) GetPhone() string {
func (u *UserMapper) GetPhone() domain.PhoneNumber {
return ""
}

View File

@ -13,6 +13,7 @@ import (
"golang.org/x/oauth2"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
@ -260,9 +261,9 @@ func TestProvider_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -8,6 +8,7 @@ import (
"github.com/zitadel/oidc/v2/pkg/oidc"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
@ -82,8 +83,8 @@ func (u *User) GetDisplayName() string {
return u.GetName()
}
func (u *User) GetPhone() string {
return u.GetPhoneNumber()
func (u *User) GetPhone() domain.PhoneNumber {
return domain.PhoneNumber(u.GetPhoneNumber())
}
func (u *User) IsPhoneVerified() bool {
@ -97,3 +98,7 @@ func (u *User) GetPreferredLanguage() language.Tag {
func (u *User) GetAvatarURL() string {
return u.GetPicture()
}
func (u *User) GetEmail() domain.EmailAddress {
return domain.EmailAddress(u.UserInfo.GetEmail())
}

View File

@ -17,6 +17,7 @@ import (
"gopkg.in/square/go-jose.v2"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp"
)
@ -281,9 +282,9 @@ func TestSession_FetchUser(t *testing.T) {
a.Equal(tt.want.displayName, user.GetDisplayName())
a.Equal(tt.want.nickName, user.GetNickname())
a.Equal(tt.want.preferredUsername, user.GetPreferredUsername())
a.Equal(tt.want.email, user.GetEmail())
a.Equal(domain.EmailAddress(tt.want.email), user.GetEmail())
a.Equal(tt.want.isEmailVerified, user.IsEmailVerified())
a.Equal(tt.want.phone, user.GetPhone())
a.Equal(domain.PhoneNumber(tt.want.phone), user.GetPhone())
a.Equal(tt.want.isPhoneVerified, user.IsPhoneVerified())
a.Equal(tt.want.preferredLanguage, user.GetPreferredLanguage())
a.Equal(tt.want.avatarURL, user.GetAvatarURL())

View File

@ -342,7 +342,7 @@ func (p *userProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat
handler.NewCol(HumanPreferredLanguageCol, &sql.NullString{String: e.PreferredLanguage.String(), Valid: !e.PreferredLanguage.IsRoot()}),
handler.NewCol(HumanGenderCol, &sql.NullInt16{Int16: int16(e.Gender), Valid: e.Gender.Specified()}),
handler.NewCol(HumanEmailCol, e.EmailAddress),
handler.NewCol(HumanPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
handler.NewCol(HumanPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
},
crdb.WithTableSuffix(UserHumanSuffix),
),
@ -351,7 +351,7 @@ func (p *userProjection) reduceHumanAdded(event eventstore.Event) (*handler.Stat
handler.NewCol(NotifyUserIDCol, e.Aggregate().ID),
handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCol(NotifyLastEmailCol, e.EmailAddress),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyPasswordSetCol, e.Secret != nil),
},
crdb.WithTableSuffix(UserNotifySuffix),
@ -390,7 +390,7 @@ func (p *userProjection) reduceHumanRegistered(event eventstore.Event) (*handler
handler.NewCol(HumanPreferredLanguageCol, &sql.NullString{String: e.PreferredLanguage.String(), Valid: !e.PreferredLanguage.IsRoot()}),
handler.NewCol(HumanGenderCol, &sql.NullInt16{Int16: int16(e.Gender), Valid: e.Gender.Specified()}),
handler.NewCol(HumanEmailCol, e.EmailAddress),
handler.NewCol(HumanPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
handler.NewCol(HumanPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
},
crdb.WithTableSuffix(UserHumanSuffix),
),
@ -399,7 +399,7 @@ func (p *userProjection) reduceHumanRegistered(event eventstore.Event) (*handler
handler.NewCol(NotifyUserIDCol, e.Aggregate().ID),
handler.NewCol(NotifyInstanceIDCol, e.Aggregate().InstanceID),
handler.NewCol(NotifyLastEmailCol, e.EmailAddress),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyPasswordSetCol, e.Secret != nil),
},
crdb.WithTableSuffix(UserNotifySuffix),
@ -660,7 +660,7 @@ func (p *userProjection) reduceHumanPhoneChanged(event eventstore.Event) (*handl
),
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: e.PhoneNumber, Valid: e.PhoneNumber != ""}),
handler.NewCol(NotifyLastPhoneCol, &sql.NullString{String: string(e.PhoneNumber), Valid: e.PhoneNumber != ""}),
},
[]handler.Condition{
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),
@ -786,7 +786,7 @@ func (p *userProjection) reduceHumanEmailChanged(event eventstore.Event) (*handl
),
crdb.AddUpdateStatement(
[]handler.Column{
handler.NewCol(NotifyLastEmailCol, &sql.NullString{String: e.EmailAddress, Valid: e.EmailAddress != ""}),
handler.NewCol(NotifyLastEmailCol, &sql.NullString{String: string(e.EmailAddress), Valid: e.EmailAddress != ""}),
},
[]handler.Condition{
handler.NewCond(NotifyUserIDCol, e.Aggregate().ID),

View File

@ -75,7 +75,7 @@ func TestUserProjection_reduces(t *testing.T) {
&sql.NullString{String: "display-name", Valid: true},
&sql.NullString{String: "ch-DE", Valid: true},
&sql.NullInt16{Int16: int16(domain.GenderFemale), Valid: true},
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
},
},
@ -84,7 +84,7 @@ func TestUserProjection_reduces(t *testing.T) {
expectedArgs: []interface{}{
"agg-id",
"instance-id",
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
false,
},
@ -144,7 +144,7 @@ func TestUserProjection_reduces(t *testing.T) {
&sql.NullString{String: "display-name", Valid: true},
&sql.NullString{String: "ch-DE", Valid: true},
&sql.NullInt16{Int16: int16(domain.GenderFemale), Valid: true},
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
},
},
@ -153,7 +153,7 @@ func TestUserProjection_reduces(t *testing.T) {
expectedArgs: []interface{}{
"agg-id",
"instance-id",
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
false,
},
@ -208,7 +208,7 @@ func TestUserProjection_reduces(t *testing.T) {
&sql.NullString{},
&sql.NullString{String: "und", Valid: false},
&sql.NullInt16{},
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{},
},
},
@ -217,7 +217,7 @@ func TestUserProjection_reduces(t *testing.T) {
expectedArgs: []interface{}{
"agg-id",
"instance-id",
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "", Valid: false},
false,
},
@ -277,7 +277,7 @@ func TestUserProjection_reduces(t *testing.T) {
&sql.NullString{String: "display-name", Valid: true},
&sql.NullString{String: "ch-DE", Valid: true},
&sql.NullInt16{Int16: int16(domain.GenderFemale), Valid: true},
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
},
},
@ -286,7 +286,7 @@ func TestUserProjection_reduces(t *testing.T) {
expectedArgs: []interface{}{
"agg-id",
"instance-id",
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
false,
},
@ -346,7 +346,7 @@ func TestUserProjection_reduces(t *testing.T) {
&sql.NullString{String: "display-name", Valid: true},
&sql.NullString{String: "ch-DE", Valid: true},
&sql.NullInt16{Int16: int16(domain.GenderFemale), Valid: true},
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
},
},
@ -355,7 +355,7 @@ func TestUserProjection_reduces(t *testing.T) {
expectedArgs: []interface{}{
"agg-id",
"instance-id",
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "+41 00 000 00 00", Valid: true},
false,
},
@ -410,7 +410,7 @@ func TestUserProjection_reduces(t *testing.T) {
&sql.NullString{},
&sql.NullString{String: "und", Valid: false},
&sql.NullInt16{},
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{},
},
},
@ -419,7 +419,7 @@ func TestUserProjection_reduces(t *testing.T) {
expectedArgs: []interface{}{
"agg-id",
"instance-id",
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
&sql.NullString{String: "", Valid: false},
false,
},
@ -879,7 +879,7 @@ func TestUserProjection_reduces(t *testing.T) {
{
expectedStmt: "UPDATE projections.users8_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"+41 00 000 00 00",
domain.PhoneNumber("+41 00 000 00 00"),
false,
"agg-id",
"instance-id",
@ -927,7 +927,7 @@ func TestUserProjection_reduces(t *testing.T) {
{
expectedStmt: "UPDATE projections.users8_humans SET (phone, is_phone_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"+41 00 000 00 00",
domain.PhoneNumber("+41 00 000 00 00"),
false,
"agg-id",
"instance-id",
@ -1157,7 +1157,7 @@ func TestUserProjection_reduces(t *testing.T) {
{
expectedStmt: "UPDATE projections.users8_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
false,
"agg-id",
"instance-id",
@ -1205,7 +1205,7 @@ func TestUserProjection_reduces(t *testing.T) {
{
expectedStmt: "UPDATE projections.users8_humans SET (email, is_email_verified) = ($1, $2) WHERE (user_id = $3) AND (instance_id = $4)",
expectedArgs: []interface{}{
"email@zitadel.com",
domain.EmailAddress("email@zitadel.com"),
false,
"agg-id",
"instance-id",

View File

@ -47,9 +47,9 @@ type Human struct {
AvatarKey string
PreferredLanguage language.Tag
Gender domain.Gender
Email string
Email domain.EmailAddress
IsEmailVerified bool
Phone string
Phone domain.PhoneNumber
IsPhoneVerified bool
}
@ -74,7 +74,7 @@ type Email struct {
ChangeDate time.Time
ResourceOwner string
Sequence uint64
Email string
Email domain.EmailAddress
IsVerified bool
}
@ -847,9 +847,9 @@ func prepareUserQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilder
AvatarKey: avatarKey.String,
PreferredLanguage: language.Make(preferredLanguage.String),
Gender: domain.Gender(gender.Int32),
Email: email.String,
Email: domain.EmailAddress(email.String),
IsEmailVerified: isEmailVerified.Bool,
Phone: phone.String,
Phone: domain.PhoneNumber(phone.String),
IsPhoneVerified: isPhoneVerified.Bool,
}
} else if machineID.Valid {
@ -970,7 +970,7 @@ func prepareEmailQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde
return nil, errors.ThrowPreconditionFailed(nil, "QUERY-pt7HY", "Errors.User.NotHuman")
}
e.Email = email.String
e.Email = domain.EmailAddress(email.String)
e.IsVerified = isEmailVerified.Bool
return e, nil
@ -1318,9 +1318,9 @@ func prepareUsersQuery(ctx context.Context, db prepareDatabase) (sq.SelectBuilde
AvatarKey: avatarKey.String,
PreferredLanguage: language.Make(preferredLanguage.String),
Gender: domain.Gender(gender.Int32),
Email: email.String,
Email: domain.EmailAddress(email.String),
IsEmailVerified: isEmailVerified.Bool,
Phone: phone.String,
Phone: domain.PhoneNumber(phone.String),
IsPhoneVerified: isPhoneVerified.Bool,
}
} else if machineID.Valid {

View File

@ -38,9 +38,9 @@ type HumanAddedEvent struct {
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
Gender domain.Gender `json:"gender,omitempty"`
EmailAddress string `json:"email,omitempty"`
EmailAddress domain.EmailAddress `json:"email,omitempty"`
PhoneNumber string `json:"phone,omitempty"`
PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
Country string `json:"country,omitempty"`
Locality string `json:"locality,omitempty"`
@ -75,7 +75,7 @@ func (e *HumanAddedEvent) AddAddressData(
}
func (e *HumanAddedEvent) AddPhoneData(
phoneNumber string,
phoneNumber domain.PhoneNumber,
) {
e.PhoneNumber = phoneNumber
}
@ -99,7 +99,7 @@ func NewHumanAddedEvent(
displayName string,
preferredLanguage language.Tag,
gender domain.Gender,
emailAddress string,
emailAddress domain.EmailAddress,
userLoginMustBeDomain bool,
) *HumanAddedEvent {
return &HumanAddedEvent{
@ -133,30 +133,24 @@ func HumanAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
}
type HumanRegisteredEvent struct {
eventstore.BaseEvent `json:"-"`
eventstore.BaseEvent `json:"-"`
UserName string `json:"userName"`
userLoginMustBeDomain bool
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
NickName string `json:"nickName,omitempty"`
DisplayName string `json:"displayName,omitempty"`
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
Gender domain.Gender `json:"gender,omitempty"`
EmailAddress string `json:"email,omitempty"`
PhoneNumber string `json:"phone,omitempty"`
Country string `json:"country,omitempty"`
Locality string `json:"locality,omitempty"`
PostalCode string `json:"postalCode,omitempty"`
Region string `json:"region,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"`
Secret *crypto.CryptoValue `json:"secret,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
FirstName string `json:"firstName,omitempty"`
LastName string `json:"lastName,omitempty"`
NickName string `json:"nickName,omitempty"`
DisplayName string `json:"displayName,omitempty"`
PreferredLanguage language.Tag `json:"preferredLanguage,omitempty"`
Gender domain.Gender `json:"gender,omitempty"`
EmailAddress domain.EmailAddress `json:"email,omitempty"`
PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
Country string `json:"country,omitempty"`
Locality string `json:"locality,omitempty"`
PostalCode string `json:"postalCode,omitempty"`
Region string `json:"region,omitempty"`
StreetAddress string `json:"streetAddress,omitempty"`
Secret *crypto.CryptoValue `json:"secret,omitempty"`
ChangeRequired bool `json:"changeRequired,omitempty"`
}
func (e *HumanRegisteredEvent) Data() interface{} {
@ -182,7 +176,7 @@ func (e *HumanRegisteredEvent) AddAddressData(
}
func (e *HumanRegisteredEvent) AddPhoneData(
phoneNumber string,
phoneNumber domain.PhoneNumber,
) {
e.PhoneNumber = phoneNumber
}
@ -206,7 +200,7 @@ func NewHumanRegisteredEvent(
displayName string,
preferredLanguage language.Tag,
gender domain.Gender,
emailAddress string,
emailAddress domain.EmailAddress,
userLoginMustBeDomain bool,
) *HumanRegisteredEvent {
return &HumanRegisteredEvent{

View File

@ -5,10 +5,10 @@ import (
"encoding/json"
"time"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
@ -24,7 +24,7 @@ const (
type HumanEmailChangedEvent struct {
eventstore.BaseEvent `json:"-"`
EmailAddress string `json:"email,omitempty"`
EmailAddress domain.EmailAddress `json:"email,omitempty"`
}
func (e *HumanEmailChangedEvent) Data() interface{} {
@ -35,7 +35,7 @@ func (e *HumanEmailChangedEvent) UniqueConstraints() []*eventstore.EventUniqueCo
return nil
}
func NewHumanEmailChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress string) *HumanEmailChangedEvent {
func NewHumanEmailChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, emailAddress domain.EmailAddress) *HumanEmailChangedEvent {
return &HumanEmailChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
@ -24,7 +25,7 @@ const (
type HumanPhoneChangedEvent struct {
eventstore.BaseEvent `json:"-"`
PhoneNumber string `json:"phone,omitempty"`
PhoneNumber domain.PhoneNumber `json:"phone,omitempty"`
}
func (e *HumanPhoneChangedEvent) Data() interface{} {
@ -35,7 +36,7 @@ func (e *HumanPhoneChangedEvent) UniqueConstraints() []*eventstore.EventUniqueCo
return nil
}
func NewHumanPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone string) *HumanPhoneChangedEvent {
func NewHumanPhoneChangedEvent(ctx context.Context, aggregate *eventstore.Aggregate, phone domain.PhoneNumber) *HumanPhoneChangedEvent {
return &HumanPhoneChangedEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
ctx,

View File

@ -69,17 +69,24 @@ Errors:
UsernameNotChanged: Benutzername wurde nicht verändert
Profile:
NotFound: Profil nicht gefunden
NotChanged: Profile nicht verändert
Invalid: Profildaten sind ungültig
NotChanged: Profil nicht verändert
Empty: Profil ist leer
FirstNameEmpty: Vorname im Profil ist leer
LastNameEmpty: Nachname im Profil ist leer
IDMissing: Profil ID fehlt
Email:
NotFound: Email nicht gefunden
Invalid: Email ist ungültig
AlreadyVerified: Email ist bereits verifiziert
NotChanged: Email wurde nicht geändert
Empty: Email ist leer
IDMissing: Email ID fehlt
Phone:
NotFound: Telefonnummer nicht gefunden
Invalid: Telefonnummer ist ungültig
AlreadyVerified: Telefonnummer bereits verifiziert
Empty: Telefonnummer ist leer
NotChanged: Telefonnummer wurde nicht geändert
Address:
NotFound: Adresse nicht gefunden
NotChanged: Adresse wurde nicht geändert
@ -100,6 +107,7 @@ Errors:
Username:
AlreadyExists: Benutzername ist bereits vergeben
Reserved: Benutzername ist bereits vergeben
Empty: Benutzername ist leer
Code:
Empty: Code ist leer
NotFound: Code konnte nicht gefunden werden

View File

@ -70,16 +70,23 @@ Errors:
Profile:
NotFound: Profile not found
NotChanged: Profile not changed
Invalid: Profile data invalid
Empty: Profile is empty
FirstNameEmpty: First name in profile is empty
LastNameEmpty: Last name in profile is empty
IDMissing: Profile ID is missing
Email:
NotFound: Email not found
Invalid: Email is invalid
AlreadyVerified: Email is already verified
NotChanged: Email not changed
Empty: Email is empty
IDMissing: Email ID is missing
Phone:
NotFound: Phone not found
Invalid: Phone is invalid
AlreadyVerified: Phone already verified
Empty: Phone is empty
NotChanged: Phone not changed
Address:
NotFound: Address not found
NotChanged: Address not changed
@ -100,6 +107,7 @@ Errors:
Username:
AlreadyExists: Username already taken
Reserved: Username is already taken
Empty: Username is empty
Code:
Empty: Code is empty
NotFound: Code not found

View File

@ -70,16 +70,23 @@ Errors:
Profile:
NotFound: Profil non trouvé
NotChanged: Le profil n'a pas changé
Invalid: Données de profil non valides
Empty: Profil est vide
FirstNameEmpty: Le prénom dans le profil est vide
LastNameEmpty: Le nom de famille dans le profil est vide
IDMissing: Profil ID manquant
Email:
NotFound: Email non trouvé
Invalid: L'email n'est pas valide
AlreadyVerified: L'adresse électronique est déjà vérifiée
NotChanged: L'adresse électronique n'a pas changé
Empty: Email est vide
IDMissing: Email ID manquant
Phone:
Notfound: Téléphone non trouvé
Invalid: Le téléphone n'est pas valide
AlreadyVerified: Téléphone déjà vérifié
Empty: Téléphone est vide
NotChanged: Téléphone n'a pas changé
Address:
NotFound: Adresse non trouvée
NotChanged: L'adresse n'a pas changé
@ -100,6 +107,7 @@ Errors:
Username:
AlreadyExists: Nom d'utilisateur déjà pris
Reserved: Le nom d'utilisateur est déjà pris
Empty: Le nom d'utilisateur est vide
Code:
Empty: Le code est vide
NotFound: Code non trouvé

View File

@ -70,16 +70,23 @@ Errors:
Profile:
NotFound: Profilo non trovato
NotChanged: Profilo non cambiato
Invalid: Dati non sono validi
Empty: Profilo è vuoto
FirstNameEmpty: Il nome nel profilo è vuoto
LastNameEmpty: Il cognome nel profilo è vuoto
IDMissing: Profilo ID mancante
Email:
NotFound: Email non trovata
Invalid: L'e-mail non è valida
AlreadyVerified: L'e-mail è già verificata
NotChanged: Email non cambiata
Empty: Email è vuota
IDMissing: Email ID mancante
Phone:
NotFound: Telefono non trovato
Invalid: Il telefono non è valido
AlreadyVerified: Telefono già verificato
Empty: Il telefono è vuoto
NotChanged: Telefono non cambiato
Address:
NotFound: Indirizzo non trovato
NotChanged: Indirizzo non cambiato
@ -100,6 +107,7 @@ Errors:
Username:
AlreadyExists: Nome utente già preso
Reserved: Il nome utente è già preso
Empty: Il nome utente è vuoto
Code:
Empty: Il codice è vuoto
NotFound: Codice non trovato

View File

@ -70,16 +70,23 @@ Errors:
Profile:
NotFound: Profil nie znaleziony
NotChanged: Profil nie zmieniony
Invalid: Nieprawidłowe dane profilu
Empty: Profil jest pusty
FirstNameEmpty: Imię w profilu jest puste
LastNameEmpty: Nazwisko w profilu jest puste
IDMissing: Profil ID brakuje
Email:
NotFound: Adres e-mail nie znaleziony
Invalid: Adres e-mail jest nieprawidłowy
AlreadyVerified: Adres e-mail jest już zweryfikowany
NotChanged: Adres e-mail nie zmieniony
Empty: Adres e-mail jest pusty
IDMissing: Adres e-mail ID brakuje
Phone:
NotFound: Numer telefonu nie znaleziony
Invalid: Numer telefonu jest nieprawidłowy
AlreadyVerified: Numer telefonu już zweryfikowany
Empty: Numer telefonu jest pusty
NotChanged: Numer telefonu nie zmieniony
Address:
NotFound: Adres nie znaleziony
NotChanged: Adres nie zmieniony
@ -100,6 +107,7 @@ Errors:
Username:
AlreadyExists: Nazwa użytkownika jest już zajęta
Reserved: Nazwa użytkownika jest już zajęta
Empty: Nazwa użytkownika jest pusty
Code:
Empty: Kod jest pusty
NotFound: Kod nie znaleziony

View File

@ -70,16 +70,23 @@ Errors:
Profile:
NotFound: 未找到个人资料
NotChanged: 个人资料未更改
Invalid: 个人资料数据无效
Empty: 简介是空的
FirstNameEmpty: 简介中的名字是空的
LastNameEmpty: 简介中的姓氏是空的
IDMissing: 简介ID丢失
Email:
NotFound: 电子邮件没有找到
Invalid: 电子邮件无效
AlreadyVerified: 电子邮件已经过验证
NotChanged: 电子邮件未更改
Empty: 电子邮件是空的
IDMissing: 电子邮件ID丢失
Phone:
NotFound: 手机号码未找到
Invalid: 手机号码无效
AlreadyVerified: 手机号码已经验证
Empty: 电话号码是空的
NotChanged: 电话号码没有改变
Address:
NotFound: 找不到地址
NotChanged: 地址没有改变
@ -100,6 +107,7 @@ Errors:
Username:
AlreadyExists: 用户名已被使用
Reserved: 用户名已被使用
Empty: 用户名是空的
Code:
Empty: 验证码为空
NotFound: 验证码不存在

View File

@ -21,10 +21,6 @@ type EmailCode struct {
Expiry time.Duration
}
func (e *Email) IsValid() bool {
return e.EmailAddress != ""
}
func (e *Email) GenerateEmailCodeIfNeeded(emailGenerator crypto.Generator) (*EmailCode, error) {
var emailCode *EmailCode
if e.IsEmailVerified {

View File

@ -3,9 +3,7 @@ package model
import (
"time"
"github.com/ttacon/libphonenumber"
"github.com/zitadel/zitadel/internal/crypto"
caos_errs "github.com/zitadel/zitadel/internal/errors"
es_models "github.com/zitadel/zitadel/internal/eventstore/v1/models"
)
@ -27,20 +25,6 @@ type PhoneCode struct {
Expiry time.Duration
}
func (p *Phone) IsValid() bool {
err := p.formatPhone()
return p.PhoneNumber != "" && err == nil
}
func (p *Phone) formatPhone() error {
phoneNr, err := libphonenumber.Parse(p.PhoneNumber, defaultRegion)
if err != nil {
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-so0wa", "Phonenumber is invalid")
}
p.PhoneNumber = libphonenumber.Format(phoneNr, libphonenumber.E164)
return nil
}
func (p *Phone) GeneratePhoneCodeIfNeeded(phoneGenerator crypto.Generator) (*PhoneCode, error) {
var phoneCode *PhoneCode
if p.IsPhoneVerified {

View File

@ -1,107 +0,0 @@
package model
import (
"testing"
caos_errs "github.com/zitadel/zitadel/internal/errors"
)
func TestFormatPhoneNumber(t *testing.T) {
type args struct {
phone *Phone
}
tests := []struct {
name string
args args
result *Phone
errFunc func(err error) bool
}{
{
name: "invalid phone number",
args: args{
phone: &Phone{
PhoneNumber: "PhoneNumber",
},
},
errFunc: caos_errs.IsPreconditionFailed,
},
{
name: "format phone 071...",
args: args{
phone: &Phone{
PhoneNumber: "0711234567",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 0041...",
args: args{
phone: &Phone{
PhoneNumber: "0041711234567",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 071 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "071 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone +4171 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "+4171 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format phone 004171 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "004171 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+41711234567",
},
},
{
name: "format non swiss phone 004371 xxx xx xx",
args: args{
phone: &Phone{
PhoneNumber: "004371 123 45 67",
},
},
result: &Phone{
PhoneNumber: "+43711234567",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.args.phone.formatPhone()
if tt.errFunc == nil && tt.result.PhoneNumber != tt.args.phone.PhoneNumber {
t.Errorf("got wrong result: expected: %v, actual: %v ", tt.args.phone.PhoneNumber, tt.result.PhoneNumber)
}
if tt.errFunc != nil && !tt.errFunc(err) {
t.Errorf("got wrong err: %v ", err)
}
})
}
}