fix: allow creating new invite code before previous is invalid (#11649)

# Which Problems Are Solved

In the refactoring of the logic for the creating and resending of invite
codes (#9962), a bug was introduced where the creating of a new code was
not possible if the request was to return it. It worked when being sent
via mail.

# How the Problems Are Solved

Fixed the check for an existing code.

# Additional Changes

none

# Additional Context

- closes https://github.com/zitadel/zitadel/issues/10718
- requires backport to v4.x
- relates to #9962

Co-authored-by: Marco A. <marco@zitadel.com>
This commit is contained in:
Livio Spring
2026-02-20 12:34:07 +00:00
committed by GitHub
co-authored by Marco A.
parent df8824e16c
commit afeeee87c3
2 changed files with 81 additions and 1 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ func (c *Commands) sendInviteCode(ctx context.Context, invite *CreateUserInvite,
if !wm.CreationAllowed() {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-EF34g", "Errors.User.AlreadyInitialised")
}
if requireExisting && wm.InviteCode == nil || wm.CodeReturned {
if requireExisting && (wm.InviteCode == nil || wm.CodeReturned) {
return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound")
}
code, err := c.newUserInviteCode(ctx, c.eventstore.Filter, c.userEncryption) //nolint
+80
View File
@@ -662,6 +662,86 @@ func TestCommands_CreateInviteCode(t *testing.T) {
returnCode: gu.Ptr("code2"),
},
},
{
"return ok after previous code created",
fields{
eventstore: expectEventstore(
expectFilter(
eventFromEventPusher(
user.NewHumanAddedEvent(context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
"username", "firstName",
"lastName",
"nickName",
"displayName",
language.Afrikaans,
domain.GenderUnspecified,
"email",
false,
),
),
// first invite code generated and returned
eventFromEventPusherWithCreationDateNow(
user.NewHumanInviteCodeAddedEvent(context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code1"),
},
time.Minute, // expired code
"",
true,
"",
"",
),
),
// simulate a failed verification attempt due to expiry
eventFromEventPusher(
user.NewHumanInviteCheckFailedEvent(context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
),
),
),
expectPush(
eventFromEventPusher(
user.NewHumanInviteCodeAddedEvent(context.Background(),
&user.NewAggregate("userID", "org1").Aggregate,
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("code2"),
},
time.Hour,
"",
true,
"",
"",
),
),
),
),
checkPermission: newMockPermissionCheckAllowed(),
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code2", time.Hour),
defaultSecretGenerators: &SecretGenerators{},
},
args{
ctx: context.Background(),
invite: &CreateUserInvite{
UserID: "userID",
ReturnCode: true,
},
},
want{
details: &domain.ObjectDetails{
ResourceOwner: "org1",
ID: "userID",
},
returnCode: gu.Ptr("code2"),
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {