diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md
index 6b0640a027..89d07061b4 100644
--- a/docs/docs/apis/proto/admin.md
+++ b/docs/docs/apis/proto/admin.md
@@ -56,6 +56,18 @@ Set the default language
GET: /languages/default
+### ListInstanceDomains
+
+> **rpc** ListInstanceDomains([ListInstanceDomainsRequest](#listinstancedomainsrequest))
+[ListInstanceDomainsResponse](#listinstancedomainsresponse)
+
+Returns the domains of an instance
+
+
+
+ GET: /domains
+
+
### ListSecretGenerators
> **rpc** ListSecretGenerators([ListSecretGeneratorsRequest](#listsecretgeneratorsrequest))
@@ -2629,6 +2641,32 @@ This is an empty request
+### ListInstanceDomainsRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| query | zitadel.v1.ListQuery | - | |
+| sorting_column | zitadel.instance.v1.DomainFieldName | the field the result is sorted | |
+| queries | repeated zitadel.instance.v1.DomainSearchQuery | criterias the client is looking for | |
+
+
+
+
+### ListInstanceDomainsResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ListDetails | - | |
+| sorting_column | zitadel.instance.v1.DomainFieldName | - | |
+| result | repeated zitadel.instance.v1.Domain | - | |
+
+
+
+
### ListLoginPolicyIDPsRequest
diff --git a/docs/docs/apis/proto/instance.md b/docs/docs/apis/proto/instance.md
index 69d7cda721..dacfab4921 100644
--- a/docs/docs/apis/proto/instance.md
+++ b/docs/docs/apis/proto/instance.md
@@ -9,14 +9,63 @@ title: zitadel/instance.proto
## Messages
-### DomainsQuery
+### Domain
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| domains | repeated string | - | string.max_len: 200
|
-| method | zitadel.v1.ListQueryMethod | - | enum.defined_only: true
|
+| details | zitadel.v1.ObjectDetails | - | |
+| domain | string | - | |
+| primary | bool | - | |
+| generated | bool | - | |
+
+
+
+
+### DomainGeneratedQuery
+DomainGeneratedQuery is always equals
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| generated | bool | - | |
+
+
+
+
+### DomainPrimaryQuery
+DomainPrimaryQuery is always equals
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| primary | bool | - | |
+
+
+
+
+### DomainQuery
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| domain | string | - | string.max_len: 200
|
+| method | zitadel.v1.TextQueryMethod | - | enum.defined_only: true
|
+
+
+
+
+### DomainSearchQuery
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.domain_query | DomainQuery | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.generated_query | DomainGeneratedQuery | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.primary_query | DomainPrimaryQuery | - | |
@@ -41,8 +90,6 @@ IdQuery is always equals
| id | string | - | |
| details | zitadel.v1.ObjectDetails | - | |
| state | State | - | |
-| generated_domain | string | - | |
-| custom_domains | repeated string | - | |
| name | string | - | |
| version | string | - | |
@@ -56,7 +103,6 @@ IdQuery is always equals
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.id_query | IdQuery | - | |
-| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.domains_query | DomainsQuery | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) query.state_query | StateQuery | - | |
@@ -78,6 +124,20 @@ StateQuery is always equals
## Enums
+### DomainFieldName {#domainfieldname}
+
+
+| Name | Number | Description |
+| ---- | ------ | ----------- |
+| DOMAIN_FIELD_NAME_UNSPECIFIED | 0 | - |
+| DOMAIN_FIELD_NAME_DOMAIN | 1 | - |
+| DOMAIN_FIELD_NAME_PRIMARY | 2 | - |
+| DOMAIN_FIELD_NAME_GENERATED | 3 | - |
+| DOMAIN_FIELD_NAME_CREATION_DATE | 4 | - |
+
+
+
+
### FieldName {#fieldname}
@@ -85,9 +145,8 @@ StateQuery is always equals
| ---- | ------ | ----------- |
| FIELD_NAME_UNSPECIFIED | 0 | - |
| FIELD_NAME_ID | 1 | - |
-| FIELD_NAME_GENERATED_DOMAIN | 2 | - |
-| FIELD_NAME_NAME | 3 | - |
-| FIELD_NAME_CREATION_DATE | 4 | - |
+| FIELD_NAME_NAME | 2 | - |
+| FIELD_NAME_CREATION_DATE | 3 | - |
diff --git a/docs/docs/apis/proto/system.md b/docs/docs/apis/proto/system.md
index 1b601750ef..aae6b40457 100644
--- a/docs/docs/apis/proto/system.md
+++ b/docs/docs/apis/proto/system.md
@@ -82,40 +82,52 @@ Returns the usage metrics of an instance
GET: /instances/{id}/usage
-### GetGeneratedDomain
+### ListDomains
-> **rpc** GetGeneratedDomain([GetGeneratedDomainRequest](#getgenerateddomainrequest))
-[GetGeneratedDomainResponse](#getgenerateddomainresponse)
-
-Returns the domain of an instance
-
-
-
- GET: /instances/{id}/domains/generated
-
-
-### GetCustomDomains
-
-> **rpc** GetCustomDomains([GetCustomDomainsRequest](#getcustomdomainsrequest))
-[GetCustomDomainsResponse](#getcustomdomainsresponse)
+> **rpc** ListDomains([ListDomainsRequest](#listdomainsrequest))
+[ListDomainsResponse](#listdomainsresponse)
Returns the custom domains of an instance
- GET: /instances/{id}/domains/custom
+ GET: /instances/{id}/domains
-### AddCustomDomain
+### AddDomain
-> **rpc** AddCustomDomain([AddCustomDomainRequest](#addcustomdomainrequest))
-[AddCustomDomainResponse](#addcustomdomainresponse)
+> **rpc** AddDomain([AddDomainRequest](#adddomainrequest))
+[AddDomainResponse](#adddomainresponse)
Returns the domain of an instance
- POST: /instances/{id}/domains/custom
+ POST: /instances/{id}/domains
+
+
+### RemoveDomain
+
+> **rpc** RemoveDomain([RemoveDomainRequest](#removedomainrequest))
+[RemoveDomainResponse](#removedomainresponse)
+
+Returns the domain of an instance
+
+
+
+ DELETE: /instances/{id}/domains/{domain}
+
+
+### SetPrimaryDomain
+
+> **rpc** SetPrimaryDomain([SetPrimaryDomainRequest](#setprimarydomainrequest))
+[SetPrimaryDomainResponse](#setprimarydomainresponse)
+
+Returns the domain of an instance
+
+
+
+ POST: /instances/{id}/domains/_set_primary
### ListViews
@@ -185,19 +197,19 @@ failed event. You can find out if it worked on the `failure_count`
## Messages
-### AddCustomDomainRequest
+### AddDomainRequest
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
| id | string | - | string.min_len: 1
string.max_len: 200
|
-| custom_domain | string | - | string.min_len: 1
string.max_len: 200
|
+| domain | string | - | string.min_len: 1
string.max_len: 200
|
-### AddCustomDomainResponse
+### AddDomainResponse
@@ -297,52 +309,6 @@ This is an empty response
-### GetCustomDomainsRequest
-
-
-
-| Field | Type | Description | Validation |
-| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
-
-
-
-
-### GetCustomDomainsResponse
-
-
-
-| Field | Type | Description | Validation |
-| ----- | ---- | ----------- | ----------- |
-| details | zitadel.v1.ObjectDetails | - | |
-| domains | repeated string | - | |
-
-
-
-
-### GetGeneratedDomainRequest
-
-
-
-| Field | Type | Description | Validation |
-| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
-
-
-
-
-### GetGeneratedDomainResponse
-
-
-
-| Field | Type | Description | Validation |
-| ----- | ---- | ----------- | ----------- |
-| details | zitadel.v1.ObjectDetails | - | |
-| domain | string | - | |
-
-
-
-
### GetInstanceRequest
@@ -401,6 +367,33 @@ This is an empty response
+### ListDomainsRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| id | string | list limitations and ordering | string.min_len: 1
string.max_len: 200
|
+| query | zitadel.v1.ListQuery | - | |
+| sorting_column | zitadel.instance.v1.DomainFieldName | the field the result is sorted | |
+| queries | repeated zitadel.instance.v1.DomainSearchQuery | criterias the client is looking for | |
+
+
+
+
+### ListDomainsResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ListDetails | - | |
+| sorting_column | zitadel.instance.v1.DomainFieldName | - | |
+| result | repeated zitadel.instance.v1.Domain | - | |
+
+
+
+
### ListFailedEventsRequest
This is an empty request
@@ -461,6 +454,29 @@ This is an empty request
+### RemoveDomainRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| id | string | - | string.min_len: 1
string.max_len: 200
|
+| domain | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### RemoveDomainResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### RemoveFailedEventRequest
@@ -502,6 +518,29 @@ This is an empty response
+### SetPrimaryDomainRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| id | string | - | string.min_len: 1
string.max_len: 200
|
+| domain | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### SetPrimaryDomainResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### View
diff --git a/internal/api/authz/instance.go b/internal/api/authz/instance.go
index 6501793bb6..6f55c20438 100644
--- a/internal/api/authz/instance.go
+++ b/internal/api/authz/instance.go
@@ -12,6 +12,7 @@ type Instance interface {
InstanceID() string
ProjectID() string
ConsoleClientID() string
+ ConsoleApplicationID() string
RequestedDomain() string
}
@@ -36,6 +37,10 @@ func (i *instance) ConsoleClientID() string {
return ""
}
+func (i *instance) ConsoleApplicationID() string {
+ return ""
+}
+
func (i *instance) RequestedDomain() string {
return i.Domain
}
diff --git a/internal/api/authz/instance_test.go b/internal/api/authz/instance_test.go
index c14f5fdf3a..db714cf118 100644
--- a/internal/api/authz/instance_test.go
+++ b/internal/api/authz/instance_test.go
@@ -79,6 +79,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleID"
}
+func (m *mockInstance) ConsoleApplicationID() string {
+ return "appID"
+}
+
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}
diff --git a/internal/api/grpc/admin/instance.go b/internal/api/grpc/admin/instance.go
new file mode 100644
index 0000000000..6778c3f034
--- /dev/null
+++ b/internal/api/grpc/admin/instance.go
@@ -0,0 +1,29 @@
+package admin
+
+import (
+ "context"
+
+ instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
+)
+
+func (s *Server) GetInstanceDomains(ctx context.Context, req *admin_pb.ListInstanceDomainsRequest) (*admin_pb.ListInstanceDomainsResponse, error) {
+ queries, err := ListInstanceDomainsRequestToModel(req)
+ if err != nil {
+ return nil, err
+ }
+
+ domains, err := s.query.SearchInstanceDomains(ctx, queries)
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.ListInstanceDomainsResponse{
+ Result: instance_grpc.DomainsToPb(domains.Domains),
+ Details: object.ToListDetails(
+ domains.Count,
+ domains.Sequence,
+ domains.Timestamp,
+ ),
+ }, nil
+}
diff --git a/internal/api/grpc/admin/instance_converter.go b/internal/api/grpc/admin/instance_converter.go
new file mode 100644
index 0000000000..b83cbb90be
--- /dev/null
+++ b/internal/api/grpc/admin/instance_converter.go
@@ -0,0 +1,25 @@
+package admin
+
+import (
+ instance_grpc "github.com/caos/zitadel/internal/api/grpc/instance"
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ "github.com/caos/zitadel/internal/query"
+ admin_pb "github.com/caos/zitadel/pkg/grpc/admin"
+)
+
+func ListInstanceDomainsRequestToModel(req *admin_pb.ListInstanceDomainsRequest) (*query.InstanceDomainSearchQueries, error) {
+ offset, limit, asc := object.ListQueryToModel(req.Query)
+ queries, err := instance_grpc.DomainQueriesToModel(req.Queries)
+ if err != nil {
+ return nil, err
+ }
+ return &query.InstanceDomainSearchQueries{
+ SearchRequest: query.SearchRequest{
+ Offset: offset,
+ Limit: limit,
+ Asc: asc,
+ },
+ //SortingColumn: //TODO: sorting
+ Queries: queries,
+ }, nil
+}
diff --git a/internal/api/grpc/instance/converter.go b/internal/api/grpc/instance/converter.go
new file mode 100644
index 0000000000..f944a1f18e
--- /dev/null
+++ b/internal/api/grpc/instance/converter.go
@@ -0,0 +1,54 @@
+package org
+
+import (
+ "github.com/caos/zitadel/internal/api/grpc/object"
+ "github.com/caos/zitadel/internal/errors"
+ "github.com/caos/zitadel/internal/query"
+ instance_pb "github.com/caos/zitadel/pkg/grpc/instance"
+)
+
+func DomainQueriesToModel(queries []*instance_pb.DomainSearchQuery) (_ []query.SearchQuery, err error) {
+ q := make([]query.SearchQuery, len(queries))
+ for i, query := range queries {
+ q[i], err = DomainQueryToModel(query)
+ if err != nil {
+ return nil, err
+ }
+ }
+ return q, nil
+}
+
+func DomainQueryToModel(searchQuery *instance_pb.DomainSearchQuery) (query.SearchQuery, error) {
+ switch q := searchQuery.Query.(type) {
+ case *instance_pb.DomainSearchQuery_DomainQuery:
+ return query.NewInstanceDomainDomainSearchQuery(object.TextMethodToQuery(q.DomainQuery.Method), q.DomainQuery.Domain)
+ case *instance_pb.DomainSearchQuery_GeneratedQuery:
+ return query.NewInstanceDomainGeneratedSearchQuery(q.GeneratedQuery.Generated)
+ case *instance_pb.DomainSearchQuery_PrimaryQuery:
+ return query.NewInstanceDomainPrimarySearchQuery(q.PrimaryQuery.Primary)
+ default:
+ return nil, errors.ThrowInvalidArgument(nil, "ORG-Ags42", "List.Query.Invalid")
+ }
+}
+
+func DomainsToPb(domains []*query.InstanceDomain) []*instance_pb.Domain {
+ d := make([]*instance_pb.Domain, len(domains))
+ for i, domain := range domains {
+ d[i] = DomainToPb(domain)
+ }
+ return d
+}
+
+func DomainToPb(d *query.InstanceDomain) *instance_pb.Domain {
+ return &instance_pb.Domain{
+ Domain: d.Domain,
+ Primary: d.IsPrimary,
+ Generated: d.IsGenerated,
+ Details: object.ToViewDetailsPb(
+ d.Sequence,
+ d.CreationDate,
+ d.ChangeDate,
+ d.InstanceID,
+ ),
+ }
+}
diff --git a/internal/api/grpc/server/middleware/instance_interceptor_test.go b/internal/api/grpc/server/middleware/instance_interceptor_test.go
index dab78be056..8bfc274cc6 100644
--- a/internal/api/grpc/server/middleware/instance_interceptor_test.go
+++ b/internal/api/grpc/server/middleware/instance_interceptor_test.go
@@ -173,6 +173,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}
+func (m *mockInstance) ConsoleApplicationID() string {
+ return "consoleApplicationID"
+}
+
func (m *mockInstance) RequestedDomain() string {
return "localhost"
}
diff --git a/internal/api/http/middleware/instance_interceptor_test.go b/internal/api/http/middleware/instance_interceptor_test.go
index 625d3c748c..10052fd2c7 100644
--- a/internal/api/http/middleware/instance_interceptor_test.go
+++ b/internal/api/http/middleware/instance_interceptor_test.go
@@ -257,6 +257,10 @@ func (m *mockInstance) ConsoleClientID() string {
return "consoleClientID"
}
+func (m *mockInstance) ConsoleApplicationID() string {
+ return "consoleApplicationID"
+}
+
func (m *mockInstance) RequestedDomain() string {
return "zitadel.cloud"
}
diff --git a/internal/command/instance.go b/internal/command/instance.go
index 1dfc721180..e0c68b39b3 100644
--- a/internal/command/instance.go
+++ b/internal/command/instance.go
@@ -356,7 +356,7 @@ func (c *commandNew) SetUpInstance(ctx context.Context, setup *InstanceSetup) (*
),
AddOIDCAppCommand(console, nil),
- SetIAMConsoleID(instanceAgg, &console.ClientID),
+ SetIAMConsoleID(instanceAgg, &console.ClientID, &setup.Zitadel.consoleAppID),
)
cmds, err := preparation.PrepareCommands(ctx, c.es.Filter, validations...)
@@ -387,11 +387,11 @@ func SetIAMProject(a *instance.Aggregate, projectID string) preparation.Validati
}
//SetIAMConsoleID defines the command to set the clientID of the Console App onto the instance
-func SetIAMConsoleID(a *instance.Aggregate, clientID *string) preparation.Validation {
+func SetIAMConsoleID(a *instance.Aggregate, clientID, appID *string) preparation.Validation {
return func() (preparation.CreateCommands, error) {
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
return []eventstore.Command{
- instance.NewIAMConsoleSetEvent(ctx, &a.Aggregate, clientID),
+ instance.NewIAMConsoleSetEvent(ctx, &a.Aggregate, clientID, appID),
}, nil
}, nil
}
diff --git a/internal/command/instance_domain.go b/internal/command/instance_domain.go
index c0db780047..757df7abb7 100644
--- a/internal/command/instance_domain.go
+++ b/internal/command/instance_domain.go
@@ -10,6 +10,7 @@ import (
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/repository/instance"
+ "github.com/caos/zitadel/internal/repository/project"
)
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
@@ -30,6 +31,24 @@ func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string)
}, nil
}
+func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
+ instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
+ validation := c.setPrimaryInstanceDomain(instanceAgg, instanceDomain)
+ cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
+ if err != nil {
+ return nil, err
+ }
+ events, err := c.eventstore.Push(ctx, cmds...)
+ if err != nil {
+ return nil, err
+ }
+ return &domain.ObjectDetails{
+ Sequence: events[len(events)-1].Sequence(),
+ EventDate: events[len(events)-1].CreationDate(),
+ ResourceOwner: events[len(events)-1].Aggregate().InstanceID,
+ }, nil
+}
+
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.removeInstanceDomain(instanceAgg, instanceDomain)
@@ -61,7 +80,46 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
if domainWriteModel.State == domain.InstanceDomainStateActive {
return nil, errors.ThrowAlreadyExists(nil, "INST-i2nl", "Errors.Instance.Domain.AlreadyExists")
}
- return []eventstore.Command{instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated)}, nil
+ appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
+ if err != nil {
+ return nil, err
+ }
+ redirectUrls := append(appWriteModel.RedirectUris, instanceDomain+consoleRedirectPath)
+ logoutUrls := append(appWriteModel.PostLogoutRedirectUris, instanceDomain+consolePostLogoutPath)
+ consoleChangeEvent, err := project.NewOIDCConfigChangedEvent(
+ ctx,
+ ProjectAggregateFromWriteModel(&appWriteModel.WriteModel),
+ appWriteModel.AppID,
+ []project.OIDCConfigChanges{
+ project.ChangeRedirectURIs(redirectUrls),
+ project.ChangePostLogoutRedirectURIs(logoutUrls),
+ },
+ )
+ if err != nil {
+ return nil, err
+ }
+ return []eventstore.Command{
+ instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
+ consoleChangeEvent,
+ }, nil
+ }, nil
+ }
+}
+
+func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
+ return func() (preparation.CreateCommands, error) {
+ if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
+ return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
+ }
+ return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
+ domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
+ if err != nil {
+ return nil, err
+ }
+ if !domainWriteModel.State.Exists() {
+ return nil, errors.ThrowNotFound(nil, "INSTANCE-9nkWf", "Errors.Instance.Domain.NotFound")
+ }
+ return []eventstore.Command{instance.NewDomainPrimarySetEvent(ctx, &a.Aggregate, instanceDomain)}, nil
}, nil
}
}
diff --git a/internal/command/instance_domain_test.go b/internal/command/instance_domain_test.go
index 075e0dcc85..19aff8ebda 100644
--- a/internal/command/instance_domain_test.go
+++ b/internal/command/instance_domain_test.go
@@ -3,8 +3,11 @@ package command
import (
"context"
"testing"
+ "time"
"github.com/caos/zitadel/internal/api/authz"
+ "github.com/caos/zitadel/internal/crypto"
+ "github.com/caos/zitadel/internal/repository/project"
"github.com/stretchr/testify/assert"
"github.com/caos/zitadel/internal/domain"
@@ -77,6 +80,43 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
eventstore: eventstoreExpect(
t,
expectFilter(),
+ expectFilter(
+ eventFromEventPusherWithInstanceID(
+ "INSTANCE",
+ project.NewApplicationAddedEvent(context.Background(),
+ &project.NewAggregate("project1", "org1").Aggregate,
+ "consoleApplicationID",
+ "app",
+ ),
+ ),
+ eventFromEventPusherWithInstanceID(
+ "INSTANCE",
+ project.NewOIDCConfigAddedEvent(context.Background(),
+ &project.NewAggregate("projectID", "org1").Aggregate,
+ domain.OIDCVersionV1,
+ "consoleApplicationID",
+ "client1@project",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("a"),
+ },
+ []string{"https://test.ch"},
+ []domain.OIDCResponseType{domain.OIDCResponseTypeCode},
+ []domain.OIDCGrantType{domain.OIDCGrantTypeAuthorizationCode},
+ domain.OIDCApplicationTypeWeb,
+ domain.OIDCAuthMethodTypePost,
+ []string{"https://test.ch/logout"},
+ true,
+ domain.OIDCTokenTypeBearer,
+ true,
+ true,
+ true,
+ time.Second*1,
+ []string{"https://sub.test.ch"}),
+ ),
+ ),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
@@ -86,11 +126,121 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
"domain.ch",
false,
)),
+ eventFromEventPusherWithInstanceID(
+ "INSTANCE",
+ newOIDCAppChangedEventInstanceDomain(context.Background(), "consoleApplicationID", "projectID", "org1"),
+ ),
},
uniqueConstraintsFromEventConstraintWithInstanceID("INSTANCE", instance.NewAddInstanceDomainUniqueConstraint("domain.ch")),
),
),
},
+ args: args{
+ ctx: authz.WithInstance(context.Background(), new(mockInstance)),
+ domain: "domain.ch",
+ },
+ res: res{
+ want: &domain.ObjectDetails{
+ ResourceOwner: "INSTANCE",
+ },
+ },
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ r := &Commands{
+ eventstore: tt.fields.eventstore,
+ }
+ got, err := r.AddInstanceDomain(tt.args.ctx, tt.args.domain)
+ if tt.res.err == nil {
+ assert.NoError(t, err)
+ }
+ if tt.res.err != nil && !tt.res.err(err) {
+ t.Errorf("got wrong err: %v ", err)
+ }
+ if tt.res.err == nil {
+ assert.Equal(t, tt.res.want, got)
+ }
+ })
+ }
+}
+
+func TestCommandSide_SetPrimaryInstanceDomain(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ }
+ type args struct {
+ ctx context.Context
+ domain string
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ name: "invalid domain, error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ domain: "",
+ },
+ res: res{
+ err: caos_errs.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "domain not exists, precondition error",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ domain: "domain.ch",
+ },
+ res: res{
+ err: caos_errs.IsNotFound,
+ },
+ },
+ {
+ name: "set primary domain, ok",
+ fields: fields{
+ eventstore: eventstoreExpect(
+ t,
+ expectFilter(
+ eventFromEventPusherWithInstanceID(
+ "INSTANCE",
+ instance.NewDomainAddedEvent(context.Background(),
+ &instance.NewAggregate("INSTANCE").Aggregate,
+ "domain.ch",
+ false,
+ ),
+ ),
+ ),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusherWithInstanceID(
+ "INSTANCE",
+ instance.NewDomainPrimarySetEvent(context.Background(),
+ &instance.NewAggregate("INSTANCE").Aggregate,
+ "domain.ch",
+ )),
+ },
+ ),
+ ),
+ },
args: args{
ctx: authz.WithInstanceID(context.Background(), "INSTANCE"),
domain: "domain.ch",
@@ -107,7 +257,7 @@ func TestCommandSide_AddInstanceDomain(t *testing.T) {
r := &Commands{
eventstore: tt.fields.eventstore,
}
- got, err := r.AddInstanceDomain(tt.args.ctx, tt.args.domain)
+ got, err := r.SetPrimaryInstanceDomain(tt.args.ctx, tt.args.domain)
if tt.res.err == nil {
assert.NoError(t, err)
}
@@ -251,3 +401,16 @@ func TestCommandSide_RemoveInstanceDomain(t *testing.T) {
})
}
}
+
+func newOIDCAppChangedEventInstanceDomain(ctx context.Context, appID, projectID, resourceOwner string) *project.OIDCConfigChangedEvent {
+ changes := []project.OIDCConfigChanges{
+ project.ChangeRedirectURIs([]string{"https://test.ch", "domain.ch/ui/console/auth/callback"}),
+ project.ChangePostLogoutRedirectURIs([]string{"https://test.ch/logout", "domain.ch/ui/console/signedout"}),
+ }
+ event, _ := project.NewOIDCConfigChangedEvent(ctx,
+ &project.NewAggregate(projectID, resourceOwner).Aggregate,
+ appID,
+ changes,
+ )
+ return event
+}
diff --git a/internal/command/main_test.go b/internal/command/main_test.go
index 4cae45781b..17f0fc9950 100644
--- a/internal/command/main_test.go
+++ b/internal/command/main_test.go
@@ -256,3 +256,25 @@ func (v *testVerifier) CheckOrgFeatures(ctx context.Context, orgID string, requi
}
return nil
}
+
+type mockInstance struct{}
+
+func (m *mockInstance) InstanceID() string {
+ return "INSTANCE"
+}
+
+func (m *mockInstance) ProjectID() string {
+ return "projectID"
+}
+
+func (m *mockInstance) ConsoleClientID() string {
+ return "consoleID"
+}
+
+func (m *mockInstance) ConsoleApplicationID() string {
+ return "consoleApplicationID"
+}
+
+func (m *mockInstance) RequestedDomain() string {
+ return "zitadel.cloud"
+}
diff --git a/internal/command/project_application_oidc.go b/internal/command/project_application_oidc.go
index 2a826ece66..6d987fb6c3 100644
--- a/internal/command/project_application_oidc.go
+++ b/internal/command/project_application_oidc.go
@@ -309,7 +309,7 @@ func (c *Commands) VerifyOIDCClientSecret(ctx context.Context, projectID, appID,
return err
}
_, err = c.eventstore.Push(ctx, project_repo.NewOIDCConfigSecretCheckFailedEvent(ctx, projectAgg, app.AppID))
- logging.Log("COMMAND-ADfhz").OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
+ logging.New().OnError(err).Error("could not push event OIDCClientSecretCheckFailed")
return caos_errs.ThrowInvalidArgument(nil, "COMMAND-Bz542", "Errors.Project.App.ClientSecretInvalid")
}
diff --git a/internal/domain/instance_domain.go b/internal/domain/instance_domain.go
index 5e3a7c1dde..be1284ed6a 100644
--- a/internal/domain/instance_domain.go
+++ b/internal/domain/instance_domain.go
@@ -18,6 +18,10 @@ func (f InstanceDomainState) Valid() bool {
return f >= 0 && f < instanceDomainStateCount
}
+func (f InstanceDomainState) Exists() bool {
+ return f == InstanceDomainStateActive
+}
+
func NewGeneratedInstanceDomain(instanceName, iamDomain string) string {
return strings.ToLower(strings.ReplaceAll(instanceName, " ", "-") + "." + iamDomain)
}
diff --git a/internal/query/instance.go b/internal/query/instance.go
index 7e911da8e6..c0356c2170 100644
--- a/internal/query/instance.go
+++ b/internal/query/instance.go
@@ -43,6 +43,10 @@ var (
name: projection.InstanceColumnConsoleID,
table: instanceTable,
}
+ InstanceColumnConsoleAppID = Column{
+ name: projection.InstanceColumnConsoleAppID,
+ table: instanceTable,
+ }
InstanceColumnSetupStarted = Column{
name: projection.InstanceColumnSetUpStarted,
table: instanceTable,
@@ -65,6 +69,7 @@ type Instance struct {
GlobalOrgID string
IAMProjectID string
ConsoleID string
+ ConsoleAppID string
DefaultLanguage language.Tag
SetupStarted domain.Step
SetupDone domain.Step
@@ -83,6 +88,10 @@ func (i *Instance) ConsoleClientID() string {
return i.ConsoleID
}
+func (i *Instance) ConsoleApplicationID() string {
+ return i.ConsoleAppID
+}
+
func (i *Instance) RequestedDomain() string {
return i.Host
}
@@ -142,6 +151,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(),
+ InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(),
@@ -157,6 +167,7 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
&instance.GlobalOrgID,
&instance.IAMProjectID,
&instance.ConsoleID,
+ &instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang,
diff --git a/internal/query/instance_domain.go b/internal/query/instance_domain.go
index 39c3d0dd6b..f7e92b166b 100644
--- a/internal/query/instance_domain.go
+++ b/internal/query/instance_domain.go
@@ -19,6 +19,7 @@ type InstanceDomain struct {
Domain string
InstanceID string
IsGenerated bool
+ IsPrimary bool
}
type InstanceDomains struct {
@@ -47,8 +48,12 @@ func NewInstanceDomainInstanceIDSearchQuery(value string) (SearchQuery, error) {
return NewTextQuery(InstanceDomainInstanceIDCol, value, TextEquals)
}
-func NewInstanceDomainGeneratedSearchQuery(verified bool) (SearchQuery, error) {
- return NewBoolQuery(InstanceDomainIsGeneratedCol, verified)
+func NewInstanceDomainGeneratedSearchQuery(generated bool) (SearchQuery, error) {
+ return NewBoolQuery(InstanceDomainIsGeneratedCol, generated)
+}
+
+func NewInstanceDomainPrimarySearchQuery(primary bool) (SearchQuery, error) {
+ return NewBoolQuery(InstanceDomainIsPrimaryCol, primary)
}
func (q *Queries) SearchInstanceDomains(ctx context.Context, queries *InstanceDomainSearchQueries) (domains *InstanceDomains, err error) {
@@ -81,6 +86,7 @@ func prepareInstanceDomainsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance
InstanceDomainDomainCol.identifier(),
InstanceDomainInstanceIDCol.identifier(),
InstanceDomainIsGeneratedCol.identifier(),
+ InstanceDomainIsPrimaryCol.identifier(),
countColumn.identifier(),
).From(instanceDomainsTable.identifier()).PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*InstanceDomains, error) {
@@ -95,6 +101,7 @@ func prepareInstanceDomainsQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instance
&domain.Domain,
&domain.InstanceID,
&domain.IsGenerated,
+ &domain.IsPrimary,
&count,
)
if err != nil {
@@ -145,4 +152,8 @@ var (
name: projection.InstanceDomainIsGeneratedCol,
table: instanceDomainsTable,
}
+ InstanceDomainIsPrimaryCol = Column{
+ name: projection.InstanceDomainIsPrimaryCol,
+ table: instanceDomainsTable,
+ }
)
diff --git a/internal/query/instance_domain_test.go b/internal/query/instance_domain_test.go
index b8445701bc..fd2acd6f10 100644
--- a/internal/query/instance_domain_test.go
+++ b/internal/query/instance_domain_test.go
@@ -31,6 +31,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
+ ` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
nil,
@@ -50,6 +51,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
+ ` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
[]string{
@@ -59,6 +61,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"domain",
"instance_id",
"is_generated",
+ "is_primary",
"count",
},
[][]driver.Value{
@@ -69,6 +72,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.ch",
"inst-id",
true,
+ true,
},
},
),
@@ -85,6 +89,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.ch",
InstanceID: "inst-id",
IsGenerated: true,
+ IsPrimary: true,
},
},
},
@@ -100,6 +105,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
+ ` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
[]string{
@@ -109,6 +115,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"domain",
"instance_id",
"is_generated",
+ "is_primary",
"count",
},
[][]driver.Value{
@@ -119,6 +126,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.ch",
"inst-id",
true,
+ true,
},
{
testNow,
@@ -127,6 +135,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
"zitadel.com",
"inst-id",
false,
+ false,
},
},
),
@@ -143,6 +152,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.ch",
InstanceID: "inst-id",
IsGenerated: true,
+ IsPrimary: true,
},
{
CreationDate: testNow,
@@ -151,6 +161,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
Domain: "zitadel.com",
InstanceID: "inst-id",
IsGenerated: false,
+ IsPrimary: false,
},
},
},
@@ -166,6 +177,7 @@ func Test_InstanceDomainPrepares(t *testing.T) {
` projections.instance_domains.domain,`+
` projections.instance_domains.instance_id,`+
` projections.instance_domains.is_generated,`+
+ ` projections.instance_domains.is_primary,`+
` COUNT(*) OVER ()`+
` FROM projections.instance_domains`),
sql.ErrConnDone,
diff --git a/internal/query/instance_test.go b/internal/query/instance_test.go
index b7e653a74c..90561d88cd 100644
--- a/internal/query/instance_test.go
+++ b/internal/query/instance_test.go
@@ -39,6 +39,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+
+ ` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+
@@ -68,6 +69,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+
+ ` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+
@@ -79,6 +81,7 @@ func Test_InstancePrepares(t *testing.T) {
"global_org_id",
"iam_project_id",
"console_client_id",
+ "console_app_id",
"setup_started",
"setup_done",
"default_language",
@@ -90,6 +93,7 @@ func Test_InstancePrepares(t *testing.T) {
"global-org-id",
"project-id",
"client-id",
+ "app-id",
domain.Step2,
domain.Step1,
"en",
@@ -103,6 +107,7 @@ func Test_InstancePrepares(t *testing.T) {
GlobalOrgID: "global-org-id",
IAMProjectID: "project-id",
ConsoleID: "client-id",
+ ConsoleAppID: "app-id",
SetupStarted: domain.Step2,
SetupDone: domain.Step1,
DefaultLanguage: language.English,
@@ -121,6 +126,7 @@ func Test_InstancePrepares(t *testing.T) {
` projections.instances.global_org_id,`+
` projections.instances.iam_project_id,`+
` projections.instances.console_client_id,`+
+ ` projections.instances.console_app_id,`+
` projections.instances.setup_started,`+
` projections.instances.setup_done,`+
` projections.instances.default_language`+
diff --git a/internal/query/projection/instance.go b/internal/query/projection/instance.go
index ededbd3ae7..ea00f7ba1f 100644
--- a/internal/query/projection/instance.go
+++ b/internal/query/projection/instance.go
@@ -18,6 +18,7 @@ const (
InstanceColumnGlobalOrgID = "global_org_id"
InstanceColumnProjectID = "iam_project_id"
InstanceColumnConsoleID = "console_client_id"
+ InstanceColumnConsoleAppID = "console_app_id"
InstanceColumnSequence = "sequence"
InstanceColumnSetUpStarted = "setup_started"
InstanceColumnSetUpDone = "setup_done"
@@ -129,6 +130,7 @@ func (p *InstanceProjection) reduceConsoleSet(event eventstore.Event) (*handler.
handler.NewCol(InstanceColumnChangeDate, e.CreationDate()),
handler.NewCol(InstanceColumnSequence, e.Sequence()),
handler.NewCol(InstanceColumnConsoleID, e.ClientID),
+ handler.NewCol(InstanceColumnConsoleAppID, e.AppID),
},
), nil
}
diff --git a/internal/query/projection/instance_domain.go b/internal/query/projection/instance_domain.go
index 9610d45d27..6e4b7f1e19 100644
--- a/internal/query/projection/instance_domain.go
+++ b/internal/query/projection/instance_domain.go
@@ -19,6 +19,7 @@ const (
InstanceDomainSequenceCol = "sequence"
InstanceDomainDomainCol = "domain"
InstanceDomainIsGeneratedCol = "is_generated"
+ InstanceDomainIsPrimaryCol = "is_primary"
)
type InstanceDomainProjection struct {
@@ -37,6 +38,7 @@ func NewInstanceDomainProjection(ctx context.Context, config crdb.StatementHandl
crdb.NewColumn(InstanceDomainSequenceCol, crdb.ColumnTypeInt64),
crdb.NewColumn(InstanceDomainDomainCol, crdb.ColumnTypeText),
crdb.NewColumn(InstanceDomainIsGeneratedCol, crdb.ColumnTypeBool),
+ crdb.NewColumn(InstanceDomainIsPrimaryCol, crdb.ColumnTypeBool),
},
crdb.NewPrimaryKey(InstanceDomainInstanceIDCol, InstanceDomainDomainCol),
),
@@ -54,6 +56,10 @@ func (p *InstanceDomainProjection) reducers() []handler.AggregateReducer {
Event: instance.InstanceDomainAddedEventType,
Reduce: p.reduceDomainAdded,
},
+ {
+ Event: instance.InstanceDomainAddedEventType,
+ Reduce: p.reduceDomainPrimarySet,
+ },
{
Event: instance.InstanceDomainRemovedEventType,
Reduce: p.reduceDomainRemoved,
@@ -77,10 +83,43 @@ func (p *InstanceDomainProjection) reduceDomainAdded(event eventstore.Event) (*h
handler.NewCol(InstanceDomainDomainCol, e.Domain),
handler.NewCol(InstanceDomainInstanceIDCol, e.Aggregate().ID),
handler.NewCol(InstanceDomainIsGeneratedCol, e.Generated),
+ handler.NewCol(InstanceDomainIsPrimaryCol, false),
},
), nil
}
+func (p *InstanceDomainProjection) reduceDomainPrimarySet(event eventstore.Event) (*handler.Statement, error) {
+ e, ok := event.(*instance.DomainPrimarySetEvent)
+ if !ok {
+ return nil, errors.ThrowInvalidArgumentf(nil, "PROJE-f8nlw", "reduce.wrong.event.type %s", instance.InstanceDomainPrimarySetEventType)
+ }
+ return crdb.NewMultiStatement(
+ e,
+ crdb.AddUpdateStatement(
+ []handler.Column{
+ handler.NewCol(InstanceDomainChangeDateCol, e.CreationDate()),
+ handler.NewCol(InstanceDomainSequenceCol, e.Sequence()),
+ handler.NewCol(InstanceDomainIsPrimaryCol, false),
+ },
+ []handler.Condition{
+ handler.NewCond(InstanceDomainInstanceIDCol, e.Aggregate().InstanceID),
+ handler.NewCond(InstanceDomainIsPrimaryCol, true),
+ },
+ ),
+ crdb.AddUpdateStatement(
+ []handler.Column{
+ handler.NewCol(InstanceDomainChangeDateCol, e.CreationDate()),
+ handler.NewCol(InstanceDomainSequenceCol, e.Sequence()),
+ handler.NewCol(InstanceDomainIsPrimaryCol, true),
+ },
+ []handler.Condition{
+ handler.NewCond(InstanceDomainDomainCol, e.Domain),
+ handler.NewCond(InstanceDomainInstanceIDCol, e.Aggregate().ID),
+ },
+ ),
+ ), nil
+}
+
func (p *InstanceDomainProjection) reduceDomainRemoved(event eventstore.Event) (*handler.Statement, error) {
e, ok := event.(*instance.DomainRemovedEvent)
if !ok {
diff --git a/internal/query/projection/instance_domain_test.go b/internal/query/projection/instance_domain_test.go
index 772a18327a..5ada417e14 100644
--- a/internal/query/projection/instance_domain_test.go
+++ b/internal/query/projection/instance_domain_test.go
@@ -38,7 +38,7 @@ func TestInstanceDomainProjection_reduces(t *testing.T) {
executer: &testExecuter{
executions: []execution{
{
- expectedStmt: "INSERT INTO projections.instance_domains (creation_date, change_date, sequence, domain, instance_id, is_generated) VALUES ($1, $2, $3, $4, $5, $6)",
+ expectedStmt: "INSERT INTO projections.instance_domains (creation_date, change_date, sequence, domain, instance_id, is_generated, is_primary) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
anyArg{},
anyArg{},
@@ -46,6 +46,7 @@ func TestInstanceDomainProjection_reduces(t *testing.T) {
"domain.new",
"agg-id",
true,
+ false,
},
},
},
diff --git a/internal/repository/instance/domain.go b/internal/repository/instance/domain.go
index 8d8a463dfe..c1e8a8784a 100644
--- a/internal/repository/instance/domain.go
+++ b/internal/repository/instance/domain.go
@@ -11,10 +11,11 @@ import (
)
const (
- UniqueInstanceDomain = "instance_domain"
- domainEventPrefix = instanceEventTypePrefix + "domain."
- InstanceDomainAddedEventType = domainEventPrefix + "added"
- InstanceDomainRemovedEventType = domainEventPrefix + "removed"
+ UniqueInstanceDomain = "instance_domain"
+ domainEventPrefix = instanceEventTypePrefix + "domain."
+ InstanceDomainAddedEventType = domainEventPrefix + "added"
+ InstanceDomainPrimarySetEventType = domainEventPrefix + "primary.set"
+ InstanceDomainRemovedEventType = domainEventPrefix + "removed"
)
func NewAddInstanceDomainUniqueConstraint(orgDomain string) *eventstore.EventUniqueConstraint {
@@ -69,6 +70,43 @@ func DomainAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
return orgDomainAdded, nil
}
+type DomainPrimarySetEvent struct {
+ eventstore.BaseEvent `json:"-"`
+
+ Domain string `json:"domain,omitempty"`
+}
+
+func (e *DomainPrimarySetEvent) Data() interface{} {
+ return e
+}
+
+func (e *DomainPrimarySetEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
+ return nil
+}
+
+func NewDomainPrimarySetEvent(ctx context.Context, aggregate *eventstore.Aggregate, domain string) *DomainPrimarySetEvent {
+ return &DomainPrimarySetEvent{
+ BaseEvent: *eventstore.NewBaseEventForPush(
+ ctx,
+ aggregate,
+ InstanceDomainPrimarySetEventType,
+ ),
+ Domain: domain,
+ }
+}
+
+func DomainPrimarySetEventMapper(event *repository.Event) (eventstore.Event, error) {
+ orgDomainAdded := &DomainAddedEvent{
+ BaseEvent: *eventstore.BaseEventFromRepo(event),
+ }
+ err := json.Unmarshal(event.Data, orgDomainAdded)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "INSTANCE-29jöF", "unable to unmarshal instance domain added")
+ }
+
+ return orgDomainAdded, nil
+}
+
type DomainRemovedEvent struct {
eventstore.BaseEvent `json:"-"`
diff --git a/internal/repository/instance/event_iam_project_set.go b/internal/repository/instance/event_iam_project_set.go
index 1322ce6cf5..75bce840f0 100644
--- a/internal/repository/instance/event_iam_project_set.go
+++ b/internal/repository/instance/event_iam_project_set.go
@@ -60,6 +60,7 @@ type ConsoleSetEvent struct {
eventstore.BaseEvent `json:"-"`
ClientID string `json:"clientId"`
+ AppID string `json:"appId"`
}
func (e *ConsoleSetEvent) Data() interface{} {
@@ -73,7 +74,8 @@ func (e *ConsoleSetEvent) UniqueConstraints() []*eventstore.EventUniqueConstrain
func NewIAMConsoleSetEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
- clientID *string,
+ clientID,
+ appID *string,
) *ConsoleSetEvent {
return &ConsoleSetEvent{
BaseEvent: *eventstore.NewBaseEventForPush(
@@ -82,6 +84,7 @@ func NewIAMConsoleSetEvent(
ConsoleSetEventType,
),
ClientID: *clientID,
+ AppID: *appID,
}
}
diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go
index bb0113f515..a6b235367a 100644
--- a/internal/repository/instance/eventstore.go
+++ b/internal/repository/instance/eventstore.go
@@ -87,6 +87,7 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(CustomTextTemplateRemovedEventType, CustomTextTemplateRemovedEventMapper).
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
RegisterFilterEventMapper(InstanceDomainAddedEventType, DomainAddedEventMapper).
+ RegisterFilterEventMapper(InstanceDomainPrimarySetEventType, DomainPrimarySetEventMapper).
RegisterFilterEventMapper(InstanceDomainRemovedEventType, DomainRemovedEventMapper).
RegisterFilterEventMapper(InstanceAddedEventType, InstanceAddedEventMapper).
RegisterFilterEventMapper(InstanceChangedEventType, InstanceChangedEventMapper).
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index 622b1522d7..539ff0131d 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -1,6 +1,7 @@
syntax = "proto3";
import "zitadel/idp.proto";
+import "zitadel/instance.proto";
import "zitadel/user.proto";
import "zitadel/object.proto";
import "zitadel/options.proto";
@@ -184,6 +185,13 @@ service AdminService {
};
}
+ // Returns the domains of an instance
+ rpc ListInstanceDomains(ListInstanceDomainsRequest) returns (ListInstanceDomainsResponse) {
+ option (google.api.http) = {
+ get: "/domains";
+ };
+ }
+
// Set the default language
rpc ListSecretGenerators(ListSecretGeneratorsRequest) returns (ListSecretGeneratorsResponse) {
option (google.api.http) = {
@@ -2642,6 +2650,20 @@ message GetDefaultLanguageResponse {
string language = 1;
}
+message ListInstanceDomainsRequest {
+ zitadel.v1.ListQuery query = 1;
+ // the field the result is sorted
+ zitadel.instance.v1.DomainFieldName sorting_column = 2;
+ //criterias the client is looking for
+ repeated zitadel.instance.v1.DomainSearchQuery queries = 3;
+}
+
+message ListInstanceDomainsResponse {
+ zitadel.v1.ListDetails details = 1;
+ zitadel.instance.v1.DomainFieldName sorting_column = 2;
+ repeated zitadel.instance.v1.Domain result = 3;
+}
+
message ListSecretGeneratorsRequest {
//list limitations and ordering
zitadel.v1.ListQuery query = 1;
diff --git a/proto/zitadel/instance.proto b/proto/zitadel/instance.proto
index f33a62841a..8e138788cc 100644
--- a/proto/zitadel/instance.proto
+++ b/proto/zitadel/instance.proto
@@ -20,22 +20,12 @@ message Instance {
description: "current state of the instance";
}
];
- string generated_domain = 4 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"organization.zitadel.com\"";
- }
- ];
- repeated string custom_domains = 5 [
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "[\"zitadel.com\", \"zitadel.cloud\", \"zitadel.ch\"]";
- }
- ];
- string name = 6 [
+ string name = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ZITADEL\"";
}
];
- string version = 7 [
+ string version = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"v1.0.0\"";
}
@@ -55,8 +45,7 @@ message Query {
option (validate.required) = true;
IdQuery id_query = 1;
- DomainsQuery domains_query = 2;
- StateQuery state_query = 3;
+ StateQuery state_query = 2;
}
}
@@ -70,21 +59,6 @@ message IdQuery {
];
}
-message DomainsQuery {
- repeated string domains = 1 [
- (validate.rules).string = {max_len: 200},
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- example: "\"caos.ch\"";
- }
- ];
- zitadel.v1.ListQueryMethod method = 2 [
- (validate.rules).enum.defined_only = true,
- (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
- description: "defines which list equality method is used";
- }
- ];
-}
-
//StateQuery is always equals
message StateQuery {
State state = 1 [
@@ -98,7 +72,68 @@ message StateQuery {
enum FieldName {
FIELD_NAME_UNSPECIFIED = 0;
FIELD_NAME_ID = 1;
- FIELD_NAME_GENERATED_DOMAIN = 2;
- FIELD_NAME_NAME = 3;
- FIELD_NAME_CREATION_DATE = 4;
+ FIELD_NAME_NAME = 2;
+ FIELD_NAME_CREATION_DATE = 3;
+}
+
+message Domain {
+ zitadel.v1.ObjectDetails details = 1;
+ string domain = 2 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"zitadel.com\""
+ }
+ ];
+ bool primary = 3;
+ bool generated = 4;
+}
+
+message DomainSearchQuery {
+ oneof query {
+ option (validate.required) = true;
+
+ DomainQuery domain_query = 1;
+ DomainGeneratedQuery generated_query = 2;
+ DomainPrimaryQuery primary_query = 3;
+ }
+}
+
+message DomainQuery {
+ string domain = 1 [
+ (validate.rules).string = {max_len: 200},
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "zitadel.com";
+ }
+ ];
+ zitadel.v1.TextQueryMethod method = 2 [
+ (validate.rules).enum.defined_only = true,
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "defines which text equality method is used";
+ }
+ ];
+}
+
+//DomainGeneratedQuery is always equals
+message DomainGeneratedQuery {
+ bool generated = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "generated domains";
+ }
+ ];
+}
+
+//DomainPrimaryQuery is always equals
+message DomainPrimaryQuery {
+ bool primary = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "primary domains";
+ }
+ ];
+}
+
+enum DomainFieldName {
+ DOMAIN_FIELD_NAME_UNSPECIFIED = 0;
+ DOMAIN_FIELD_NAME_DOMAIN = 1;
+ DOMAIN_FIELD_NAME_PRIMARY = 2;
+ DOMAIN_FIELD_NAME_GENERATED = 3;
+ DOMAIN_FIELD_NAME_CREATION_DATE = 4;
}
diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto
index e10a3ea0ba..0618d105cb 100644
--- a/proto/zitadel/system.proto
+++ b/proto/zitadel/system.proto
@@ -141,28 +141,37 @@ service SystemService {
};
}
- // Returns the domain of an instance
- rpc GetGeneratedDomain(GetGeneratedDomainRequest) returns (GetGeneratedDomainResponse) {
- option (google.api.http) = {
- get: "/instances/{id}/domains/generated";
- };
- }
-
// Returns the custom domains of an instance
- rpc GetCustomDomains(GetCustomDomainsRequest) returns (GetCustomDomainsResponse) {
+ rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
option (google.api.http) = {
- get: "/instances/{id}/domains/custom";
+ get: "/instances/{id}/domains";
};
}
// Returns the domain of an instance
- rpc AddCustomDomain(AddCustomDomainRequest) returns (AddCustomDomainResponse) {
+ rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
option (google.api.http) = {
- post: "/instances/{id}/domains/custom";
+ post: "/instances/{id}/domains";
body: "*"
};
}
+ // Returns the domain of an instance
+ rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
+ option (google.api.http) = {
+ delete: "/instances/{id}/domains/{domain}";
+ };
+ }
+
+ // Returns the domain of an instance
+ rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
+ option (google.api.http) = {
+ post: "/instances/{id}/domains/_set_primary";
+ body: "*"
+ };
+ }
+
+
//Returns all stored read models of ZITADEL
// views are used for search optimisation and optimise request latencies
// they represent the delta of the event happend on the objects
@@ -346,30 +355,45 @@ message GetUsageResponse {
uint64 executed_action_mins = 3;
}
-message GetGeneratedDomainRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+message ListDomainsRequest {
+ string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];//list limitations and ordering
+ zitadel.v1.ListQuery query = 2;
+ // the field the result is sorted
+ zitadel.instance.v1.DomainFieldName sorting_column = 3;
+ //criterias the client is looking for
+ repeated zitadel.instance.v1.DomainSearchQuery queries = 4;
}
-message GetGeneratedDomainResponse {
+message ListDomainsResponse {
+ zitadel.v1.ListDetails details = 1;
+ zitadel.instance.v1.DomainFieldName sorting_column = 2;
+ repeated zitadel.instance.v1.Domain result = 3;
+}
+
+message AddDomainRequest {
+ string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message AddDomainResponse {
zitadel.v1.ObjectDetails details = 1;
- string domain = 2;
}
-message GetCustomDomainsRequest {
+message RemoveDomainRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
-message GetCustomDomainsResponse {
+message RemoveDomainResponse {
zitadel.v1.ObjectDetails details = 1;
- repeated string domains = 2;
}
-message AddCustomDomainRequest {
+message SetPrimaryDomainRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string custom_domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
-message AddCustomDomainResponse {
+message SetPrimaryDomainResponse {
zitadel.v1.ObjectDetails details = 1;
}