mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
fix: claim verified domain from usernames (#603)
* fix: return orgDomain validationType * added missing translations for orgDomain activity * claim org domain * show message if domain token was requested * fix tests * fix tests Co-authored-by: Max Peintner <max@caos.ch>
This commit is contained in:
@@ -3,13 +3,15 @@ package eventsourcing
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
org_model "github.com/caos/zitadel/internal/org/model"
|
||||
policy_model "github.com/caos/zitadel/internal/policy/model"
|
||||
"github.com/golang/protobuf/ptypes"
|
||||
|
||||
"github.com/pquerna/otp/totp"
|
||||
|
||||
@@ -30,6 +32,7 @@ type UserEventstore struct {
|
||||
es_int.Eventstore
|
||||
userCache *UserCache
|
||||
idGenerator id.Generator
|
||||
defaultDomain string
|
||||
PasswordAlg crypto.HashAlgorithm
|
||||
InitializeUserCode crypto.Generator
|
||||
EmailVerificationCode crypto.Generator
|
||||
@@ -65,6 +68,7 @@ func StartUser(conf UserConfig, systemDefaults sd.SystemDefaults) (*UserEventsto
|
||||
Eventstore: conf.Eventstore,
|
||||
userCache: userCache,
|
||||
idGenerator: id.SonyFlakeGenerator,
|
||||
defaultDomain: systemDefaults.DefaultDomain,
|
||||
InitializeUserCode: initCodeGen,
|
||||
EmailVerificationCode: emailVerificationCode,
|
||||
PhoneVerificationCode: phoneVerificationCode,
|
||||
@@ -1084,3 +1088,32 @@ func (es *UserEventstore) SignOut(ctx context.Context, agentID string, userIDs [
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) PrepareDomainClaimed(ctx context.Context, userIDs []string) ([]*es_models.Aggregate, error) {
|
||||
aggregates := make([]*es_models.Aggregate, 0)
|
||||
for _, userID := range userIDs {
|
||||
user, err := es.UserByID(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
repoUser := model.UserFromModel(user)
|
||||
name, err := es.generateTemporaryLoginName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAgg, err := DomainClaimedAggregate(ctx, es.AggregateCreator(), repoUser, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates = append(aggregates, userAgg...)
|
||||
}
|
||||
return aggregates, nil
|
||||
}
|
||||
|
||||
func (es *UserEventstore) generateTemporaryLoginName() (string, error) {
|
||||
id, err := es.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s@temporary.%s", id, es.defaultDomain), nil
|
||||
}
|
||||
|
@@ -55,4 +55,6 @@ const (
|
||||
MfaInitSkipped models.EventType = "user.mfa.init.skipped"
|
||||
|
||||
SignedOut models.EventType = "user.signed.out"
|
||||
|
||||
DomainClaimed models.EventType = "user.domain.claimed"
|
||||
)
|
||||
|
@@ -133,7 +133,8 @@ func (u *User) AppendEvent(event *es_models.Event) (err error) {
|
||||
switch event.Type {
|
||||
case UserAdded,
|
||||
UserRegistered,
|
||||
UserProfileChanged:
|
||||
UserProfileChanged,
|
||||
DomainClaimed:
|
||||
u.setData(event)
|
||||
case UserDeactivated:
|
||||
u.appendDeactivatedEvent()
|
||||
|
@@ -190,6 +190,22 @@ func reservedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.
|
||||
return aggregate.SetPrecondition(UserUserNameUniqueQuery(uniqueUserName), isEventValidation(aggregate, model.UserUserNameReserved)), nil
|
||||
}
|
||||
|
||||
func releasedUniqueUserNameAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, username string) (aggregate *es_models.Aggregate, err error) {
|
||||
aggregate, err = aggCreator.NewAggregate(ctx, username, model.UserUserNameAggregate, model.UserVersion, 0)
|
||||
if resourceOwner != "" {
|
||||
aggregate, err = aggCreator.NewAggregate(ctx, username, model.UserUserNameAggregate, model.UserVersion, 0, es_models.OverwriteResourceOwner(resourceOwner))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregate, err = aggregate.AppendEvent(model.UserUserNameReleased, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aggregate.SetPrecondition(UserUserNameUniqueQuery(username), isEventValidation(aggregate, model.UserUserNameReleased)), nil
|
||||
}
|
||||
|
||||
func reservedUniqueEmailAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, resourceOwner, email string) (aggregate *es_models.Aggregate, err error) {
|
||||
aggregate, err = aggCreator.NewAggregate(ctx, email, model.UserEmailAggregate, model.UserVersion, 0)
|
||||
if resourceOwner != "" {
|
||||
@@ -656,6 +672,30 @@ func SignOutAggregates(aggCreator *es_models.AggregateCreator, existingUsers []*
|
||||
}
|
||||
}
|
||||
|
||||
func DomainClaimedAggregate(ctx context.Context, aggCreator *es_models.AggregateCreator, existingUser *model.User, tempName string) ([]*es_models.Aggregate, error) {
|
||||
aggregates := make([]*es_models.Aggregate, 3)
|
||||
userAggregate, err := UserAggregateOverwriteContext(ctx, aggCreator, existingUser, existingUser.ResourceOwner, existingUser.AggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userAggregate, err = userAggregate.AppendEvent(model.DomainClaimed, map[string]interface{}{"userName": tempName})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates[0] = userAggregate
|
||||
releasedUniqueAggregate, err := releasedUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, existingUser.UserName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates[1] = releasedUniqueAggregate
|
||||
reservedUniqueAggregate, err := reservedUniqueUserNameAggregate(ctx, aggCreator, existingUser.ResourceOwner, tempName, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aggregates[2] = reservedUniqueAggregate
|
||||
return aggregates, nil
|
||||
}
|
||||
|
||||
func isEventValidation(aggregate *es_models.Aggregate, eventType es_models.EventType) func(...*es_models.Event) error {
|
||||
return func(events ...*es_models.Event) error {
|
||||
if len(events) == 0 {
|
||||
@@ -678,6 +718,7 @@ func addUserNameValidation(userName string) func(...*es_models.Event) error {
|
||||
case org_es_model.OrgDomainAdded:
|
||||
domain := new(org_es_model.OrgDomain)
|
||||
domain.SetData(event)
|
||||
domains = append(domains, domain)
|
||||
case org_es_model.OrgDomainVerified:
|
||||
domain := new(org_es_model.OrgDomain)
|
||||
domain.SetData(event)
|
||||
@@ -694,6 +735,7 @@ func addUserNameValidation(userName string) func(...*es_models.Event) error {
|
||||
domains[i] = domains[len(domains)-1]
|
||||
domains[len(domains)-1] = nil
|
||||
domains = domains[:len(domains)-1]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -181,7 +181,8 @@ func (u *UserView) AppendEvent(event *models.Event) (err error) {
|
||||
case es_model.UserPasswordChanged:
|
||||
err = u.setPasswordData(event)
|
||||
case es_model.UserProfileChanged,
|
||||
es_model.UserAddressChanged:
|
||||
es_model.UserAddressChanged,
|
||||
es_model.DomainClaimed:
|
||||
err = u.setData(event)
|
||||
case es_model.UserEmailChanged:
|
||||
u.IsEmailVerified = false
|
||||
|
@@ -2,6 +2,7 @@ package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
@@ -59,6 +60,20 @@ func UsersByOrgID(db *gorm.DB, table, orgID string) ([]*model.UserView, error) {
|
||||
return users, err
|
||||
}
|
||||
|
||||
func UserIDsByDomain(db *gorm.DB, table, domain string) ([]string, error) {
|
||||
users := make([]string, 0)
|
||||
orgIDQuery := &usr_model.UserSearchQuery{
|
||||
Key: usr_model.UserSearchKeyUserName,
|
||||
Method: global_model.SearchMethodEndsWithIgnoreCase,
|
||||
Value: "%" + domain,
|
||||
}
|
||||
query := repository.PrepareSearchQuery(table, model.UserSearchRequest{
|
||||
Queries: []*usr_model.UserSearchQuery{orgIDQuery},
|
||||
})
|
||||
_, err := query(db, &users)
|
||||
return users, err
|
||||
}
|
||||
|
||||
func SearchUsers(db *gorm.DB, table string, req *usr_model.UserSearchRequest) ([]*model.UserView, uint64, error) {
|
||||
users := make([]*model.UserView, 0)
|
||||
query := repository.PrepareSearchQuery(table, model.UserSearchRequest{Limit: req.Limit, Offset: req.Offset, Queries: req.Queries})
|
||||
|
Reference in New Issue
Block a user