From 23d6d24bc8f10515d6852bf34159d537002c6092 Mon Sep 17 00:00:00 2001 From: Iraq <66622793+kkrime@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:19:50 +0200 Subject: [PATCH] fix(login): changed permission check for sending invite code on log in (#10197) # Which Problems Are Solved Fixes issue when users would get an error message when attempting to resend invitation code when logging in # How the Problems Are Solved Changing the permission check for looking for `org.write` to `ommand.checkPermissionUpdateUser()` # Additional Context - Closes https://github.com/zitadel/zitadel/issues/10100 - backport to 3.x --- internal/command/user_v2_invite.go | 7 +- internal/command/user_v2_invite_test.go | 130 ++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/internal/command/user_v2_invite.go b/internal/command/user_v2_invite.go index 430ba8c7d1..10948164e9 100644 --- a/internal/command/user_v2_invite.go +++ b/internal/command/user_v2_invite.go @@ -6,6 +6,7 @@ import ( "github.com/zitadel/logging" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -50,8 +51,10 @@ func (c *Commands) sendInviteCode(ctx context.Context, invite *CreateUserInvite, if err != nil { return nil, nil, err } - if err := c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil { - return nil, nil, err + if wm.AggregateID != authz.GetCtxData(ctx).UserID { + if err := c.checkPermission(ctx, domain.PermissionUserWrite, wm.ResourceOwner, wm.AggregateID); err != nil { + return nil, nil, err + } } if !wm.UserState.Exists() { return nil, nil, zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wgvn4", "Errors.User.NotFound") diff --git a/internal/command/user_v2_invite_test.go b/internal/command/user_v2_invite_test.go index 75bd3157db..53ad1bd944 100644 --- a/internal/command/user_v2_invite_test.go +++ b/internal/command/user_v2_invite_test.go @@ -11,6 +11,7 @@ import ( "go.uber.org/mock/gomock" "golang.org/x/text/language" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/eventstore" @@ -205,6 +206,64 @@ func TestCommands_CreateInviteCode(t *testing.T) { returnCode: gu.Ptr("code"), }, }, + { + "return ok, with same user requests code", + 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, + ), + ), + ), + expectPush( + eventFromEventPusher( + user.NewHumanInviteCodeAddedEvent(authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}), + &user.NewAggregate("userID", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + "", + true, + "", + "", + ), + ), + ), + ), + // we do not run checkPermission() because the same user is requesting the code as the user to which the code is intended for + checkPermission: nil, + newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour), + defaultSecretGenerators: &SecretGenerators{}, + }, + args{ + ctx: authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}), + invite: &CreateUserInvite{ + UserID: "userID", + ReturnCode: true, + }, + }, + want{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + ID: "userID", + }, + returnCode: gu.Ptr("code"), + }, + }, { "with template and application name ok", fields{ @@ -510,6 +569,77 @@ func TestCommands_ResendInviteCode(t *testing.T) { }, }, }, + { + "return ok, with same user requests code", + 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, + ), + ), + eventFromEventPusher( + user.NewHumanInviteCodeAddedEvent(context.Background(), + &user.NewAggregate("userID", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + "", + false, + "", + "authRequestID", + ), + ), + ), + expectPush( + eventFromEventPusher( + user.NewHumanInviteCodeAddedEvent(authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}), + &user.NewAggregate("userID", "org1").Aggregate, + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("code"), + }, + time.Hour, + "", + false, + "", + "authRequestID", + ), + ), + ), + ), + // we do not run checkPermission() because the same user is requesting the code as the user to which the code is intended for + checkPermission: nil, + newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour), + defaultSecretGenerators: &SecretGenerators{}, + }, + args{ + // ctx: context.Background(), + ctx: authz.SetCtxData(context.Background(), authz.CtxData{UserID: "userID"}), + userID: "userID", + }, + want{ + details: &domain.ObjectDetails{ + ResourceOwner: "org1", + ID: "userID", + }, + }, + }, { "resend with new auth requestID ok", fields{