mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-13 03:24:26 +00:00
a07b2f4677
# Which Problems Are Solved As an administrator I want to be able to invite users to my application with the API V2, some user data I will already prefil, the user should add the authentication method themself (password, passkey, sso). # How the Problems Are Solved - A user can now be created with a email explicitly set to false. - If a user has no verified email and no authentication method, an `InviteCode` can be created through the User V2 API. - the code can be returned or sent through email - additionally `URLTemplate` and an `ApplicatioName` can provided for the email - The code can be resent and verified through the User V2 API - The V1 login allows users to verify and resend the code and set a password (analog user initialization) - The message text for the user invitation can be customized # Additional Changes - `verifyUserPasskeyCode` directly uses `crypto.VerifyCode` (instead of `verifyEncryptedCode`) - `verifyEncryptedCode` is removed (unnecessarily queried for the code generator) # Additional Context - closes #8310 - TODO: login V2 will have to implement invite flow: https://github.com/zitadel/typescript/issues/166
1208 lines
29 KiB
Go
1208 lines
29 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/muhlemmer/gu"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"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"
|
|
"github.com/zitadel/zitadel/internal/repository/org"
|
|
"github.com/zitadel/zitadel/internal/repository/user"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
func TestCommands_CreateInviteCode(t *testing.T) {
|
|
type fields struct {
|
|
checkPermission domain.PermissionCheck
|
|
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
|
eventstore func(*testing.T) *eventstore.Eventstore
|
|
defaultSecretGenerators *SecretGenerators
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
invite *CreateUserInvite
|
|
}
|
|
type want struct {
|
|
details *domain.ObjectDetails
|
|
returnCode *string
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
"user id missing",
|
|
fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
invite: &CreateUserInvite{
|
|
UserID: "",
|
|
},
|
|
},
|
|
want{
|
|
err: zerrors.ThrowInvalidArgument(nil, "COMMAND-4jio3", "Errors.User.UserIDMissing"),
|
|
},
|
|
},
|
|
{
|
|
"missing permission",
|
|
fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
invite: &CreateUserInvite{
|
|
UserID: "userID",
|
|
},
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
|
},
|
|
},
|
|
{
|
|
"user does not exist",
|
|
fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
invite: &CreateUserInvite{
|
|
UserID: "unknown",
|
|
},
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wgvn4", "Errors.User.NotFound"),
|
|
},
|
|
},
|
|
{
|
|
"create ok",
|
|
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(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
time.Hour,
|
|
"",
|
|
false,
|
|
"",
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
|
defaultSecretGenerators: &SecretGenerators{},
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
invite: &CreateUserInvite{
|
|
UserID: "userID",
|
|
},
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
returnCode: nil,
|
|
},
|
|
},
|
|
{
|
|
"return ok",
|
|
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(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
time.Hour,
|
|
"",
|
|
true,
|
|
"",
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", 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("code"),
|
|
},
|
|
},
|
|
{
|
|
"with template and application name ok",
|
|
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(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
time.Hour,
|
|
"https://example.com/invite?userID={{.UserID}}",
|
|
false,
|
|
"applicationName",
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
|
defaultSecretGenerators: &SecretGenerators{},
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
invite: &CreateUserInvite{
|
|
UserID: "userID",
|
|
URLTemplate: "https://example.com/invite?userID={{.UserID}}",
|
|
ReturnCode: false,
|
|
ApplicationName: "applicationName",
|
|
},
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
returnCode: nil,
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
checkPermission: tt.fields.checkPermission,
|
|
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
|
|
eventstore: tt.fields.eventstore(t),
|
|
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
|
}
|
|
gotDetails, gotReturnCode, err := c.CreateInviteCode(tt.args.ctx, tt.args.invite)
|
|
|
|
require.ErrorIs(t, err, tt.want.err)
|
|
assert.Equal(t, tt.want.details, gotDetails)
|
|
assert.Equal(t, tt.want.returnCode, gotReturnCode)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_ResendInviteCode(t *testing.T) {
|
|
type fields struct {
|
|
checkPermission domain.PermissionCheck
|
|
newEncryptedCodeWithDefault encryptedCodeWithDefaultFunc
|
|
eventstore func(*testing.T) *eventstore.Eventstore
|
|
defaultSecretGenerators *SecretGenerators
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
orgID string
|
|
authRequestID string
|
|
}
|
|
type want struct {
|
|
details *domain.ObjectDetails
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
"missing user id",
|
|
fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowInvalidArgument(nil, "COMMAND-2n8vs", "Errors.User.UserIDMissing"),
|
|
},
|
|
},
|
|
{
|
|
"missing permission",
|
|
fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPermissionDenied(nil, "AUTHZ-HKJD33", "Errors.PermissionDenied"),
|
|
},
|
|
},
|
|
{
|
|
"user does not exist",
|
|
fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "unknown",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-H3b2a", "Errors.User.NotFound"),
|
|
},
|
|
},
|
|
{
|
|
"no previous 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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound"),
|
|
},
|
|
},
|
|
{
|
|
"previous code returned",
|
|
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,
|
|
"",
|
|
true,
|
|
"",
|
|
"",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound"),
|
|
},
|
|
},
|
|
{
|
|
"resend ok",
|
|
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(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
time.Hour,
|
|
"",
|
|
false,
|
|
"",
|
|
"authRequestID",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
|
defaultSecretGenerators: &SecretGenerators{},
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"resend with new auth requestID ok",
|
|
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(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
time.Hour,
|
|
"",
|
|
false,
|
|
"",
|
|
"authRequestID2",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckAllowed(),
|
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
|
defaultSecretGenerators: &SecretGenerators{},
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
authRequestID: "authRequestID2",
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"resend with own user ok",
|
|
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.NewMockContext("instanceID", "org1", "userID"),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
&crypto.CryptoValue{
|
|
CryptoType: crypto.TypeEncryption,
|
|
Algorithm: "enc",
|
|
KeyID: "id",
|
|
Crypted: []byte("code"),
|
|
},
|
|
time.Hour,
|
|
"",
|
|
false,
|
|
"",
|
|
"authRequestID2",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
checkPermission: newMockPermissionCheckNotAllowed(), // user does not have permission, is allowed in the own context
|
|
newEncryptedCodeWithDefault: mockEncryptedCodeWithDefault("code", time.Hour),
|
|
defaultSecretGenerators: &SecretGenerators{},
|
|
},
|
|
args{
|
|
ctx: authz.NewMockContext("instanceID", "org1", "userID"),
|
|
userID: "userID",
|
|
authRequestID: "authRequestID2",
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
checkPermission: tt.fields.checkPermission,
|
|
newEncryptedCodeWithDefault: tt.fields.newEncryptedCodeWithDefault,
|
|
eventstore: tt.fields.eventstore(t),
|
|
defaultSecretGenerators: tt.fields.defaultSecretGenerators,
|
|
}
|
|
details, err := c.ResendInviteCode(tt.args.ctx, tt.args.userID, tt.args.orgID, tt.args.authRequestID)
|
|
assert.ErrorIs(t, err, tt.want.err)
|
|
assert.Equal(t, tt.want.details, details)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_InviteCodeSent(t *testing.T) {
|
|
type fields struct {
|
|
eventstore func(*testing.T) *eventstore.Eventstore
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
orgID string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
wantErr error
|
|
}{
|
|
{
|
|
"missing user id",
|
|
fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
zerrors.ThrowInvalidArgument(nil, "COMMAND-Sgf31", "Errors.User.UserIDMissing"),
|
|
},
|
|
{
|
|
"user does not exist",
|
|
fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "unknown",
|
|
},
|
|
zerrors.ThrowPreconditionFailed(nil, "COMMAND-HN34a", "Errors.User.NotFound"),
|
|
},
|
|
{
|
|
"code does not exist",
|
|
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,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "unknown",
|
|
},
|
|
zerrors.ThrowPreconditionFailed(nil, "COMMAND-Wr3gq", "Errors.User.Code.NotFound"),
|
|
},
|
|
{
|
|
"sent ok",
|
|
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.NewHumanInviteCodeSentEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
},
|
|
nil,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
eventstore: tt.fields.eventstore(t),
|
|
}
|
|
err := c.InviteCodeSent(tt.args.ctx, tt.args.userID, tt.args.orgID)
|
|
assert.ErrorIs(t, err, tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_VerifyInviteCode(t *testing.T) {
|
|
type fields struct {
|
|
eventstore func(*testing.T) *eventstore.Eventstore
|
|
userEncryption crypto.EncryptionAlgorithm
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
code string
|
|
}
|
|
type want struct {
|
|
details *domain.ObjectDetails
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
"code ok",
|
|
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,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
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.NewHumanInviteCheckSucceededEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanEmailVerifiedEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
code: "code",
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
},
|
|
},
|
|
// all other cases are tested in TestCommands_VerifyInviteCodeSetPassword
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
eventstore: tt.fields.eventstore(t),
|
|
userEncryption: tt.fields.userEncryption,
|
|
}
|
|
gotDetails, err := c.VerifyInviteCode(tt.args.ctx, tt.args.userID, tt.args.code)
|
|
assert.ErrorIs(t, err, tt.want.err)
|
|
assert.Equal(t, tt.want.details, gotDetails)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCommands_VerifyInviteCodeSetPassword(t *testing.T) {
|
|
type fields struct {
|
|
eventstore func(*testing.T) *eventstore.Eventstore
|
|
userEncryption crypto.EncryptionAlgorithm
|
|
userPasswordHasher *crypto.Hasher
|
|
}
|
|
type args struct {
|
|
ctx context.Context
|
|
userID string
|
|
code string
|
|
password string
|
|
userAgentID string
|
|
}
|
|
type want struct {
|
|
details *domain.ObjectDetails
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
fields fields
|
|
args args
|
|
want want
|
|
}{
|
|
{
|
|
"missing user id",
|
|
fields{
|
|
eventstore: expectEventstore(),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowInvalidArgument(nil, "COMMAND-Gk3f2", "Errors.User.UserIDMissing"),
|
|
},
|
|
},
|
|
{
|
|
"user does not exist",
|
|
fields{
|
|
eventstore: expectEventstore(
|
|
expectFilter(),
|
|
),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "unknown",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowPreconditionFailed(nil, "COMMAND-F5g2h", "Errors.User.NotFound"),
|
|
},
|
|
},
|
|
{
|
|
"invalid 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,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
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.NewHumanInviteCheckFailedEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
code: "invalid",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowInvalidArgument(nil, "COMMAND-Wgn4q", "Errors.User.Code.Invalid"),
|
|
},
|
|
},
|
|
{
|
|
"code ok",
|
|
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,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
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.NewHumanInviteCheckSucceededEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanEmailVerifiedEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
code: "code",
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"code ok, with password and user agent",
|
|
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,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
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",
|
|
),
|
|
),
|
|
),
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
|
&org.NewAggregate("org1").Aggregate,
|
|
6,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
),
|
|
),
|
|
),
|
|
expectPush(
|
|
eventFromEventPusher(
|
|
user.NewHumanInviteCheckSucceededEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanEmailVerifiedEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
),
|
|
),
|
|
eventFromEventPusher(
|
|
user.NewHumanPasswordChangedEvent(context.Background(),
|
|
&user.NewAggregate("userID", "org1").Aggregate,
|
|
"$plain$x$Password1!",
|
|
false,
|
|
"userAgentID",
|
|
),
|
|
),
|
|
),
|
|
),
|
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
|
userPasswordHasher: mockPasswordHasher("x"),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
code: "code",
|
|
password: "Password1!",
|
|
userAgentID: "userAgentID",
|
|
},
|
|
want{
|
|
details: &domain.ObjectDetails{
|
|
ResourceOwner: "org1",
|
|
ID: "userID",
|
|
},
|
|
},
|
|
},
|
|
{
|
|
"code ok, with non compliant password",
|
|
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,
|
|
),
|
|
),
|
|
eventFromEventPusherWithCreationDateNow(
|
|
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",
|
|
),
|
|
),
|
|
),
|
|
expectFilter(
|
|
eventFromEventPusher(
|
|
org.NewPasswordComplexityPolicyAddedEvent(context.Background(),
|
|
&org.NewAggregate("org1").Aggregate,
|
|
6,
|
|
true,
|
|
true,
|
|
true,
|
|
true,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
userEncryption: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
|
userPasswordHasher: mockPasswordHasher("x"),
|
|
},
|
|
args{
|
|
ctx: context.Background(),
|
|
userID: "userID",
|
|
code: "code",
|
|
password: "pw",
|
|
userAgentID: "userAgentID",
|
|
},
|
|
want{
|
|
err: zerrors.ThrowInvalidArgument(nil, "DOMAIN-HuJf6", "Errors.User.PasswordComplexityPolicy.MinLength"),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
c := &Commands{
|
|
eventstore: tt.fields.eventstore(t),
|
|
userEncryption: tt.fields.userEncryption,
|
|
userPasswordHasher: tt.fields.userPasswordHasher,
|
|
}
|
|
gotDetails, err := c.VerifyInviteCodeSetPassword(tt.args.ctx, tt.args.userID, tt.args.code, tt.args.password, tt.args.userAgentID)
|
|
assert.ErrorIs(t, err, tt.want.err)
|
|
assert.Equal(t, tt.want.details, gotDetails)
|
|
})
|
|
}
|
|
}
|