mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:37:32 +00:00
feat: user v3 contact email and phone (#8644)
# Which Problems Are Solved Endpoints to maintain email and phone contact on user v3 are not implemented. # How the Problems Are Solved Add 3 endpoints with SetContactEmail, VerifyContactEmail and ResendContactEmailCode. Add 3 endpoints with SetContactPhone, VerifyContactPhone and ResendContactPhoneCode. Refactor the logic how contact is managed in the user creation and update. # Additional Changes None # Additional Context - part of https://github.com/zitadel/zitadel/issues/6433 --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
83
internal/api/grpc/resources/user/v3alpha/email.go
Normal file
83
internal/api/grpc/resources/user/v3alpha/email.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SetContactEmail(ctx context.Context, req *user.SetContactEmailRequest) (_ *user.SetContactEmailResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := setContactEmailRequestToChangeSchemaUserEmail(req)
|
||||
details, err := s.command.ChangeSchemaUserEmail(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SetContactEmailResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.ReturnCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContactEmailRequestToChangeSchemaUserEmail(req *user.SetContactEmailRequest) *command.ChangeSchemaUserEmail {
|
||||
return &command.ChangeSchemaUserEmail{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
Email: setEmailToEmail(req.Email),
|
||||
}
|
||||
}
|
||||
|
||||
func setEmailToEmail(setEmail *user.SetEmail) *command.Email {
|
||||
if setEmail == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Email{
|
||||
Address: domain.EmailAddress(setEmail.Address),
|
||||
ReturnCode: setEmail.GetReturnCode() != nil,
|
||||
Verified: setEmail.GetIsVerified(),
|
||||
URLTemplate: setEmail.GetSendCode().GetUrlTemplate(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) VerifyContactEmail(ctx context.Context, req *user.VerifyContactEmailRequest) (_ *user.VerifyContactEmailResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.VerifySchemaUserEmail(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId(), req.GetVerificationCode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyContactEmailResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendContactEmailCode(ctx context.Context, req *user.ResendContactEmailCodeRequest) (_ *user.ResendContactEmailCodeResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := resendContactEmailCodeRequestToResendSchemaUserEmailCode(req)
|
||||
details, err := s.command.ResendSchemaUserEmailCode(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ResendContactEmailCodeResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.PlainCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resendContactEmailCodeRequestToResendSchemaUserEmailCode(req *user.ResendContactEmailCodeRequest) *command.ResendSchemaUserEmailCode {
|
||||
return &command.ResendSchemaUserEmailCode{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
URLTemplate: req.GetSendCode().GetUrlTemplate(),
|
||||
ReturnCode: req.GetReturnCode() != nil,
|
||||
}
|
||||
}
|
@@ -0,0 +1,772 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_SetContactEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.SetContactEmailRequest) error
|
||||
req *user.SetContactEmailRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "email patch, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, empty",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
email := gofakeit.Email()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, email)
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, no change",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
email := gofakeit.Email()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, email)
|
||||
req.Email.Address = email
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_ReturnCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, return, invalid template",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_SendCode{SendCode: &user.SendEmailVerificationCode{UrlTemplate: gu.Ptr("{{")}},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email patch, verified, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_IsVerified{IsVerified: true},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, template, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_SendCode{SendCode: &user.SendEmailVerificationCode{UrlTemplate: gu.Ptr("https://example.com/email/verify?userID={{.UserID}}&code={{.Code}}&orgID={{.OrgID}}")}},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email patch, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Email: &user.SetEmail{
|
||||
Address: gofakeit.Email(),
|
||||
Verification: &user.SetEmail_SendCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.SetContactEmail(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_VerifyContactEmail(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.VerifyContactEmailRequest) error
|
||||
req *user.VerifyContactEmailRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "email verify, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
VerificationCode: "unimportant",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, wrong code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
VerificationCode: "wrong",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email verify, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email verify, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactEmailRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactEmailRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.VerifyContactEmail(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ResendContactEmailCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.ResendContactEmailCodeRequest) error
|
||||
req *user.ResendContactEmailCodeRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "email resend, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, no code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "email resend, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email resend, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactEmailCodeRequest_ReturnCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "email resend, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactEmailCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserEmail(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Email())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactEmailCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactEmailCodeRequest_SendCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.ResendContactEmailCode(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -0,0 +1,701 @@
|
||||
//go:build integration
|
||||
|
||||
package user_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
resource_object "github.com/zitadel/zitadel/pkg/grpc/resources/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func TestServer_SetContactPhone(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.SetContactPhoneRequest) error
|
||||
req *user.SetContactPhoneRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "phone patch, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone patch, no change",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
number := gofakeit.Phone()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, number)
|
||||
req.Phone.Number = number
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
Verification: &user.SetPhone_ReturnCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, verified, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
Verification: &user.SetPhone_IsVerified{IsVerified: true},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone patch, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.SetContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.SetContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Phone: &user.SetPhone{
|
||||
Number: gofakeit.Phone(),
|
||||
Verification: &user.SetPhone_SendCode{},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.SetContactPhone(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_VerifyContactPhone(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCodePhone bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.VerifyContactPhoneRequest) error
|
||||
req *user.VerifyContactPhoneRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "phone verify, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
VerificationCode: "unimportant",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, wrong code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
VerificationCode: "wrong",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone verify, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone verify, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.VerifyContactPhoneRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
verifyResp := instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
req.VerificationCode = verifyResp.GetVerificationCode()
|
||||
return nil
|
||||
},
|
||||
req: &user.VerifyContactPhoneRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCodePhone: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.VerifyContactPhone(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestServer_ResendContactPhoneCode(t *testing.T) {
|
||||
t.Parallel()
|
||||
instance := integration.NewInstance(CTX)
|
||||
ensureFeatureEnabled(t, instance)
|
||||
isolatedIAMOwnerCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
schema := []byte(`{
|
||||
"$schema": "urn:zitadel:schema:v1",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}`)
|
||||
schemaResp := instance.CreateUserSchema(isolatedIAMOwnerCTX, schema)
|
||||
orgResp := instance.CreateOrganization(isolatedIAMOwnerCTX, gofakeit.Name(), gofakeit.Email())
|
||||
|
||||
type res struct {
|
||||
want *resource_object.Details
|
||||
returnCode bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
dep func(req *user.ResendContactPhoneCodeRequest) error
|
||||
req *user.ResendContactPhoneCodeRequest
|
||||
res res
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "phone resend, no context",
|
||||
ctx: context.Background(),
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, no permission",
|
||||
ctx: instance.WithAuthorization(CTX, integration.UserTypeLogin),
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, not found",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Id: "notexisting",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, not found, org",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: "not existing",
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, no code",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
data := "{\"name\": \"user\"}"
|
||||
schemaID := schemaResp.GetDetails().GetId()
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaID, []byte(data))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "phone resend, no org, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone resend, return, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactPhoneCodeRequest_ReturnCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
returnCode: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "phone resend, sent, ok",
|
||||
ctx: isolatedIAMOwnerCTX,
|
||||
dep: func(req *user.ResendContactPhoneCodeRequest) error {
|
||||
userResp := instance.CreateSchemaUser(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), schemaResp.GetDetails().GetId(), []byte("{\"name\": \"user\"}"))
|
||||
req.Id = userResp.GetDetails().GetId()
|
||||
instance.UpdateSchemaUserPhone(isolatedIAMOwnerCTX, orgResp.GetOrganizationId(), req.Id, gofakeit.Phone())
|
||||
return nil
|
||||
},
|
||||
req: &user.ResendContactPhoneCodeRequest{
|
||||
Organization: &object.Organization{
|
||||
Property: &object.Organization_OrgId{
|
||||
OrgId: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
Verification: &user.ResendContactPhoneCodeRequest_SendCode{},
|
||||
},
|
||||
res: res{
|
||||
want: &resource_object.Details{
|
||||
Changed: timestamppb.Now(),
|
||||
Owner: &object.Owner{
|
||||
Type: object.OwnerType_OWNER_TYPE_ORG,
|
||||
Id: orgResp.GetOrganizationId(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.dep != nil {
|
||||
err := tt.dep(tt.req)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.ResendContactPhoneCode(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCode {
|
||||
assert.NotNil(t, got.VerificationCode)
|
||||
} else {
|
||||
assert.Nil(t, got.VerificationCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -51,7 +52,7 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
assert.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
@@ -59,4 +60,13 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
_, err := instance.Client.UserV3Alpha.SearchUsers(ctx, &user.SearchUsersRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature call")
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/logging"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -628,16 +629,20 @@ func TestServer_PatchUser(t *testing.T) {
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.PatchUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.res.want, got.Details)
|
||||
if tt.res.returnCodeEmail {
|
||||
require.NotNil(t, got.EmailCode)
|
||||
assert.NotNil(t, got.EmailCode)
|
||||
} else {
|
||||
assert.Nil(t, got.EmailCode)
|
||||
}
|
||||
if tt.res.returnCodePhone {
|
||||
require.NotNil(t, got.PhoneCode)
|
||||
assert.NotNil(t, got.PhoneCode)
|
||||
} else {
|
||||
assert.Nil(t, got.PhoneCode)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -843,10 +848,10 @@ func TestServer_DeleteUser(t *testing.T) {
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.DeleteUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
@@ -1054,10 +1059,10 @@ func TestServer_LockUser(t *testing.T) {
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.LockUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
@@ -1237,10 +1242,10 @@ func TestServer_UnlockUser(t *testing.T) {
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.UnlockUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
@@ -1439,10 +1444,10 @@ func TestServer_DeactivateUser(t *testing.T) {
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.DeactivateUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
@@ -1622,10 +1627,10 @@ func TestServer_ActivateUser(t *testing.T) {
|
||||
}
|
||||
got, err := instance.Client.UserV3Alpha.ActivateUser(tt.ctx, tt.req)
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NoError(t, err)
|
||||
integration.AssertResourceDetails(t, tt.want, got.Details)
|
||||
})
|
||||
}
|
||||
|
81
internal/api/grpc/resources/user/v3alpha/phone.go
Normal file
81
internal/api/grpc/resources/user/v3alpha/phone.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SetContactPhone(ctx context.Context, req *user.SetContactPhoneRequest) (_ *user.SetContactPhoneResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := setContactPhoneRequestToChangeSchemaUserPhone(req)
|
||||
details, err := s.command.ChangeSchemaUserPhone(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SetContactPhoneResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.ReturnCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContactPhoneRequestToChangeSchemaUserPhone(req *user.SetContactPhoneRequest) *command.ChangeSchemaUserPhone {
|
||||
return &command.ChangeSchemaUserPhone{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
Phone: setPhoneToPhone(req.Phone),
|
||||
}
|
||||
}
|
||||
|
||||
func setPhoneToPhone(setPhone *user.SetPhone) *command.Phone {
|
||||
if setPhone == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.Phone{
|
||||
Number: domain.PhoneNumber(setPhone.Number),
|
||||
ReturnCode: setPhone.GetReturnCode() != nil,
|
||||
Verified: setPhone.GetIsVerified(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) VerifyContactPhone(ctx context.Context, req *user.VerifyContactPhoneRequest) (_ *user.VerifyContactPhoneResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.VerifySchemaUserPhone(ctx, organizationToUpdateResourceOwner(req.Organization), req.GetId(), req.GetVerificationCode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.VerifyContactPhoneResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) ResendContactPhoneCode(ctx context.Context, req *user.ResendContactPhoneCodeRequest) (_ *user.ResendContactPhoneCodeResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
schemauser := resendContactPhoneCodeRequestToResendSchemaUserPhoneCode(req)
|
||||
details, err := s.command.ResendSchemaUserPhoneCode(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.ResendContactPhoneCodeResponse{
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
VerificationCode: schemauser.PlainCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resendContactPhoneCodeRequestToResendSchemaUserPhoneCode(req *user.ResendContactPhoneCodeRequest) *command.ResendSchemaUserPhoneCode {
|
||||
return &command.ResendSchemaUserPhoneCode{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
ReturnCode: req.GetReturnCode() != nil,
|
||||
}
|
||||
}
|
14
internal/api/grpc/resources/user/v3alpha/query.go
Normal file
14
internal/api/grpc/resources/user/v3alpha/query.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
func (s *Server) SearchUsers(ctx context.Context, _ *user.SearchUsersRequest) (_ *user.SearchUsersResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.SearchUsersResponse{}, nil
|
||||
}
|
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/server"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
user "github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
)
|
||||
|
||||
@@ -14,19 +13,16 @@ var _ user.ZITADELUsersServer = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
user.UnimplementedZITADELUsersServer
|
||||
command *command.Commands
|
||||
userCodeAlg crypto.EncryptionAlgorithm
|
||||
command *command.Commands
|
||||
}
|
||||
|
||||
type Config struct{}
|
||||
|
||||
func CreateServer(
|
||||
command *command.Commands,
|
||||
userCodeAlg crypto.EncryptionAlgorithm,
|
||||
) *Server {
|
||||
return &Server{
|
||||
command: command,
|
||||
userCodeAlg: userCodeAlg,
|
||||
command: command,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -3,12 +3,9 @@ package user
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
resource_object "github.com/zitadel/zitadel/internal/api/grpc/resources/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v3alpha"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/resources/user/v3alpha"
|
||||
@@ -22,14 +19,14 @@ func (s *Server) CreateUser(ctx context.Context, req *user.CreateUserRequest) (_
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.command.CreateSchemaUser(ctx, schemauser, s.userCodeAlg); err != nil {
|
||||
details, err := s.command.CreateSchemaUser(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.CreateUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.Details.ResourceOwner),
|
||||
EmailCode: gu.Ptr(schemauser.ReturnCodeEmail),
|
||||
PhoneCode: gu.Ptr(schemauser.ReturnCodePhone),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
EmailCode: schemauser.ReturnCodeEmail,
|
||||
PhoneCode: schemauser.ReturnCodePhone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -44,6 +41,8 @@ func createUserRequestToCreateSchemaUser(ctx context.Context, req *user.CreateUs
|
||||
SchemaID: req.GetUser().GetSchemaId(),
|
||||
ID: req.GetUser().GetUserId(),
|
||||
Data: data,
|
||||
Email: setEmailToEmail(req.GetUser().GetContact().GetEmail()),
|
||||
Phone: setPhoneToPhone(req.GetUser().GetContact().GetPhone()),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -91,17 +90,36 @@ func (s *Server) PatchUser(ctx context.Context, req *user.PatchUserRequest) (_ *
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.command.ChangeSchemaUser(ctx, schemauser, s.userCodeAlg); err != nil {
|
||||
details, err := s.command.ChangeSchemaUser(ctx, schemauser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user.PatchUserResponse{
|
||||
Details: resource_object.DomainToDetailsPb(schemauser.Details, object.OwnerType_OWNER_TYPE_ORG, schemauser.Details.ResourceOwner),
|
||||
EmailCode: gu.Ptr(schemauser.ReturnCodeEmail),
|
||||
PhoneCode: gu.Ptr(schemauser.ReturnCodePhone),
|
||||
Details: resource_object.DomainToDetailsPb(details, object.OwnerType_OWNER_TYPE_ORG, details.ResourceOwner),
|
||||
EmailCode: schemauser.ReturnCodeEmail,
|
||||
PhoneCode: schemauser.ReturnCodePhone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func patchUserRequestToChangeSchemaUser(req *user.PatchUserRequest) (_ *command.ChangeSchemaUser, err error) {
|
||||
schemaUser, err := setSchemaUserToSchemaUser(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
email, phone := setContactToContact(req.GetUser().GetContact())
|
||||
return &command.ChangeSchemaUser{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
SchemaUser: schemaUser,
|
||||
Email: email,
|
||||
Phone: phone,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setSchemaUserToSchemaUser(req *user.PatchUserRequest) (_ *command.SchemaUser, err error) {
|
||||
if req.GetUser() == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var data []byte
|
||||
if req.GetUser().Data != nil {
|
||||
data, err = req.GetUser().GetData().MarshalJSON()
|
||||
@@ -110,45 +128,19 @@ func patchUserRequestToChangeSchemaUser(req *user.PatchUserRequest) (_ *command.
|
||||
}
|
||||
}
|
||||
|
||||
var email *command.Email
|
||||
var phone *command.Phone
|
||||
if req.GetUser().GetContact() != nil {
|
||||
if req.GetUser().GetContact().GetEmail() != nil {
|
||||
email = &command.Email{
|
||||
Address: domain.EmailAddress(req.GetUser().GetContact().Email.Address),
|
||||
}
|
||||
if req.GetUser().GetContact().Email.GetIsVerified() {
|
||||
email.Verified = true
|
||||
}
|
||||
if req.GetUser().GetContact().Email.GetReturnCode() != nil {
|
||||
email.ReturnCode = true
|
||||
}
|
||||
if req.GetUser().GetContact().Email.GetSendCode() != nil {
|
||||
email.URLTemplate = req.GetUser().GetContact().Email.GetSendCode().GetUrlTemplate()
|
||||
}
|
||||
}
|
||||
if req.GetUser().GetContact().Phone != nil {
|
||||
phone = &command.Phone{
|
||||
Number: domain.PhoneNumber(req.GetUser().GetContact().Phone.Number),
|
||||
}
|
||||
if req.GetUser().GetContact().Phone.GetIsVerified() {
|
||||
phone.Verified = true
|
||||
}
|
||||
if req.GetUser().GetContact().Phone.GetReturnCode() != nil {
|
||||
phone.ReturnCode = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return &command.ChangeSchemaUser{
|
||||
ResourceOwner: organizationToUpdateResourceOwner(req.Organization),
|
||||
ID: req.GetId(),
|
||||
SchemaID: req.GetUser().SchemaId,
|
||||
Data: data,
|
||||
Email: email,
|
||||
Phone: phone,
|
||||
return &command.SchemaUser{
|
||||
SchemaID: req.GetUser().GetSchemaId(),
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setContactToContact(contact *user.SetContact) (*command.Email, *command.Phone) {
|
||||
if contact == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return setEmailToEmail(contact.GetEmail()), setPhoneToPhone(contact.GetPhone())
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateUser(ctx context.Context, req *user.DeactivateUserRequest) (_ *user.DeactivateUserResponse, err error) {
|
||||
if err := checkUserSchemaEnabled(ctx); err != nil {
|
||||
return nil, err
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
schema "github.com/zitadel/zitadel/pkg/grpc/resources/userschema/v3alpha"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -51,7 +52,7 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
f, err := instance.Client.FeatureV2.GetInstanceFeatures(ctx, &feature.GetInstanceFeaturesRequest{
|
||||
Inheritance: true,
|
||||
})
|
||||
require.NoError(ttt, err)
|
||||
assert.NoError(ttt, err)
|
||||
if f.UserSchema.GetEnabled() {
|
||||
return
|
||||
}
|
||||
@@ -59,4 +60,13 @@ func ensureFeatureEnabled(t *testing.T, instance *integration.Instance) {
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature")
|
||||
|
||||
require.EventuallyWithT(t,
|
||||
func(ttt *assert.CollectT) {
|
||||
_, err := instance.Client.UserSchemaV3.SearchUserSchemas(ctx, &schema.SearchUserSchemasRequest{})
|
||||
assert.NoError(ttt, err)
|
||||
},
|
||||
retryDuration,
|
||||
time.Second,
|
||||
"timed out waiting for ensuring instance feature call")
|
||||
}
|
||||
|
Reference in New Issue
Block a user