diff --git a/docs/docs/apis/proto/instance.md b/docs/docs/apis/proto/instance.md
index a20b1f0802..43152e5cea 100644
--- a/docs/docs/apis/proto/instance.md
+++ b/docs/docs/apis/proto/instance.md
@@ -91,6 +91,23 @@ IdQuery is always equals
| details | zitadel.v1.ObjectDetails | - | |
| state | State | - | |
| name | string | - | |
+| version | string | - | |
+
+
+
+
+### InstanceDetail
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| id | string | - | |
+| details | zitadel.v1.ObjectDetails | - | |
+| state | State | - | |
+| name | string | - | |
+| version | string | - | |
+| domains | repeated Domain | - | |
diff --git a/docs/docs/apis/proto/system.md b/docs/docs/apis/proto/system.md
index caf4c73ecc..83207df658 100644
--- a/docs/docs/apis/proto/system.md
+++ b/docs/docs/apis/proto/system.md
@@ -25,7 +25,7 @@ It respondes as soon as ZITADEL started
> **rpc** ListInstances([ListInstancesRequest](#listinstancesrequest))
[ListInstancesResponse](#listinstancesresponse)
-Returns a list of ZITADEL instances/tenants
+Returns a list of ZITADEL instances
@@ -41,7 +41,7 @@ Returns the detail of an instance
- GET: /instances/{id}
+ GET: /instances/{instance_id}
### AddInstance
@@ -67,7 +67,19 @@ This might take some time
- DELETE: /instances/{id}
+ DELETE: /instances/{instance_id}
+
+
+### ExistsDomain
+
+> **rpc** ExistsDomain([ExistsDomainRequest](#existsdomainrequest))
+[ExistsDomainResponse](#existsdomainresponse)
+
+Checks if a domain exists
+
+
+
+ POST: /domains/{domain}/_exists
### ListDomains
@@ -79,7 +91,7 @@ Returns the custom domains of an instance
- POST: /instances/{id}/domains/_search
+ POST: /instances/{instance_id}/domains/_search
### AddDomain
@@ -91,7 +103,7 @@ Returns the domain of an instance
- POST: /instances/{id}/domains
+ POST: /instances/{instance_id}/domains
### RemoveDomain
@@ -103,7 +115,7 @@ Returns the domain of an instance
- DELETE: /instances/{id}/domains/{domain}
+ DELETE: /instances/{instance_id}/domains/{domain}
### SetPrimaryDomain
@@ -115,7 +127,7 @@ Returns the domain of an instance
- POST: /instances/{id}/domains/_set_primary
+ POST: /instances/{instance_id}/domains/_set_primary
### ListViews
@@ -191,7 +203,7 @@ failed event. You can find out if it worked on the `failure_count`
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
+| instance_id | string | - | string.min_len: 1
string.max_len: 200
|
| domain | string | - | string.min_len: 1
string.max_len: 200
|
@@ -217,12 +229,47 @@ failed event. You can find out if it worked on the `failure_count`
| instance_name | string | - | string.min_len: 1
string.max_len: 200
|
| first_org_name | string | - | string.max_len: 200
|
| custom_domain | string | - | string.max_len: 200
|
-| owner_first_name | string | - | string.max_len: 200
|
-| owner_last_name | string | - | string.max_len: 200
|
-| owner_email | string | - | string.min_len: 1
string.max_len: 200
|
-| owner_username | string | - | string.max_len: 200
|
-| request_limit | uint64 | - | |
-| action_mins_limit | uint64 | - | |
+| owner_user_name | string | - | string.max_len: 200
|
+| owner_email | AddInstanceRequest.Email | - | message.required: true
|
+| owner_profile | AddInstanceRequest.Profile | - | message.required: false
|
+| owner_password | AddInstanceRequest.Password | - | message.required: false
|
+
+
+
+
+### AddInstanceRequest.Email
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| email | string | - | string.min_len: 1
string.max_len: 200
|
+| is_email_verified | bool | - | |
+
+
+
+
+### AddInstanceRequest.Password
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| password | string | - | string.max_len: 200
|
+| password_change_required | bool | - | |
+
+
+
+
+### AddInstanceRequest.Profile
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| first_name | string | - | string.max_len: 200
|
+| last_name | string | - | string.max_len: 200
|
+| preferred_language | string | - | string.max_len: 10
|
@@ -233,7 +280,7 @@ failed event. You can find out if it worked on the `failure_count`
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | |
+| instance_id | string | - | |
| details | zitadel.v1.ObjectDetails | - | |
@@ -282,6 +329,28 @@ This is an empty response
+### ExistsDomainRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| domain | string | - | string.min_len: 1
string.max_len: 200
|
+
+
+
+
+### ExistsDomainResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| exists | bool | - | |
+
+
+
+
### FailedEvent
@@ -303,7 +372,7 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
+| instance_id | string | - | string.min_len: 1
string.max_len: 200
|
@@ -314,7 +383,7 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| instance | zitadel.instance.v1.Instance | - | |
+| instance | zitadel.instance.v1.InstanceDetail | - | |
@@ -325,7 +394,7 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
+| instance_id | string | - | string.min_len: 1
string.max_len: 200
|
@@ -361,7 +430,7 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | list limitations and ordering | string.min_len: 1
string.max_len: 200
|
+| instance_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 | |
@@ -448,7 +517,7 @@ This is an empty request
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
+| instance_id | string | - | string.min_len: 1
string.max_len: 200
|
| domain | string | - | string.min_len: 1
string.max_len: 200
|
@@ -490,7 +559,7 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
+| instance_id | string | - | string.min_len: 1
string.max_len: 200
|
@@ -512,7 +581,7 @@ This is an empty response
| Field | Type | Description | Validation |
| ----- | ---- | ----------- | ----------- |
-| id | string | - | string.min_len: 1
string.max_len: 200
|
+| instance_id | string | - | string.min_len: 1
string.max_len: 200
|
| domain | string | - | string.min_len: 1
string.max_len: 200
|
diff --git a/internal/api/grpc/instance/converter.go b/internal/api/grpc/instance/converter.go
index d8283416e2..bb410316aa 100644
--- a/internal/api/grpc/instance/converter.go
+++ b/internal/api/grpc/instance/converter.go
@@ -23,7 +23,22 @@ func InstanceToPb(instance *query.Instance) *instance_pb.Instance {
instance.ChangeDate,
instance.InstanceID(),
),
- Id: instance.InstanceID(),
+ Id: instance.InstanceID(),
+ Name: instance.Name,
+ }
+}
+
+func InstanceDetailToPb(instance *query.Instance) *instance_pb.InstanceDetail {
+ return &instance_pb.InstanceDetail{
+ Details: object.ToViewDetailsPb(
+ instance.Sequence,
+ instance.CreationDate,
+ instance.ChangeDate,
+ instance.InstanceID(),
+ ),
+ Id: instance.InstanceID(),
+ Name: instance.Name,
+ Domains: DomainsToPb(instance.Domains),
}
}
diff --git a/internal/api/grpc/management/user_converter.go b/internal/api/grpc/management/user_converter.go
index 11202db217..77f1d7c09b 100644
--- a/internal/api/grpc/management/user_converter.go
+++ b/internal/api/grpc/management/user_converter.go
@@ -5,6 +5,7 @@ import (
"time"
"github.com/zitadel/logging"
+ "github.com/zitadel/zitadel/pkg/grpc/user"
"golang.org/x/text/language"
"github.com/zitadel/zitadel/internal/api/authz"
@@ -27,14 +28,38 @@ func ListUsersRequestToModel(req *mgmt_pb.ListUsersRequest) (*query.UserSearchQu
}
return &query.UserSearchQueries{
SearchRequest: query.SearchRequest{
- Offset: offset,
- Limit: limit,
- Asc: asc,
+ Offset: offset,
+ Limit: limit,
+ Asc: asc,
+ SortingColumn: UserFieldNameToSortingColumn(req.SortingColumn),
},
Queries: queries,
}, nil
}
+func UserFieldNameToSortingColumn(field user.UserFieldName) query.Column {
+ switch field {
+ case user.UserFieldName_USER_FIELD_NAME_EMAIL:
+ return query.HumanEmailCol
+ case user.UserFieldName_USER_FIELD_NAME_FIRST_NAME:
+ return query.HumanFirstNameCol
+ case user.UserFieldName_USER_FIELD_NAME_LAST_NAME:
+ return query.HumanLastNameCol
+ case user.UserFieldName_USER_FIELD_NAME_DISPLAY_NAME:
+ return query.HumanDisplayNameCol
+ case user.UserFieldName_USER_FIELD_NAME_USER_NAME:
+ return query.UserUsernameCol
+ case user.UserFieldName_USER_FIELD_NAME_STATE:
+ return query.UserStateCol
+ case user.UserFieldName_USER_FIELD_NAME_TYPE:
+ return query.UserTypeCol
+ case user.UserFieldName_USER_FIELD_NAME_NICK_NAME:
+ return query.HumanNickNameCol
+ default:
+ return query.UserIDCol
+ }
+}
+
func BulkSetMetadataToDomain(req *mgmt_pb.BulkSetUserMetadataRequest) []*domain.Metadata {
metadata := make([]*domain.Metadata, len(req.Metadata))
for i, data := range req.Metadata {
diff --git a/internal/api/grpc/system/instance.go b/internal/api/grpc/system/instance.go
index 3650392192..2fe8187524 100644
--- a/internal/api/grpc/system/instance.go
+++ b/internal/api/grpc/system/instance.go
@@ -6,6 +6,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
instance_grpc "github.com/zitadel/zitadel/internal/api/grpc/instance"
"github.com/zitadel/zitadel/internal/api/grpc/object"
+ "github.com/zitadel/zitadel/internal/query"
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
)
@@ -29,13 +30,13 @@ func (s *Server) ListInstances(ctx context.Context, req *system_pb.ListInstances
}
func (s *Server) GetInstance(ctx context.Context, req *system_pb.GetInstanceRequest) (*system_pb.GetInstanceResponse, error) {
- ctx = authz.WithInstanceID(ctx, req.Id)
+ ctx = authz.WithInstanceID(ctx, req.InstanceId)
instance, err := s.query.Instance(ctx)
if err != nil {
return nil, err
}
return &system_pb.GetInstanceResponse{
- Instance: instance_grpc.InstanceToPb(instance),
+ Instance: instance_grpc.InstanceDetailToPb(instance),
}, nil
}
@@ -45,7 +46,7 @@ func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequ
return nil, err
}
return &system_pb.AddInstanceResponse{
- Id: id,
+ InstanceId: id,
Details: object.AddToDetailsPb(
details.Sequence,
details.EventDate,
@@ -55,8 +56,30 @@ func (s *Server) AddInstance(ctx context.Context, req *system_pb.AddInstanceRequ
return nil, nil
}
+func (s *Server) ExistsDomain(ctx context.Context, req *system_pb.ExistsDomainRequest) (*system_pb.ExistsDomainResponse, error) {
+ domainQuery, err := query.NewInstanceDomainDomainSearchQuery(query.TextEqualsIgnoreCase, req.Domain)
+
+ query := &query.InstanceDomainSearchQueries{
+ SearchRequest: query.SearchRequest{
+ Offset: 0,
+ Limit: 1,
+ Asc: true,
+ },
+ Queries: []query.SearchQuery{
+ domainQuery,
+ },
+ }
+ domains, err := s.query.SearchInstanceDomains(ctx, query)
+ if err != nil {
+ return nil, err
+ }
+ return &system_pb.ExistsDomainResponse{
+ Exists: domains.Count > 0,
+ }, nil
+}
+
func (s *Server) ListDomains(ctx context.Context, req *system_pb.ListDomainsRequest) (*system_pb.ListDomainsResponse, error) {
- ctx = authz.WithInstanceID(ctx, req.Id)
+ ctx = authz.WithInstanceID(ctx, req.InstanceId)
queries, err := ListInstanceDomainsRequestToModel(req)
if err != nil {
return nil, err
@@ -77,7 +100,7 @@ func (s *Server) ListDomains(ctx context.Context, req *system_pb.ListDomainsRequ
}
func (s *Server) AddDomain(ctx context.Context, req *system_pb.AddDomainRequest) (*system_pb.AddDomainResponse, error) {
- ctx = authz.WithInstanceID(ctx, req.Id)
+ ctx = authz.WithInstanceID(ctx, req.InstanceId)
instance, err := s.query.Instance(ctx)
if err != nil {
return nil, err
@@ -97,7 +120,7 @@ func (s *Server) AddDomain(ctx context.Context, req *system_pb.AddDomainRequest)
}
func (s *Server) RemoveDomain(ctx context.Context, req *system_pb.RemoveDomainRequest) (*system_pb.RemoveDomainResponse, error) {
- ctx = authz.WithInstanceID(ctx, req.Id)
+ ctx = authz.WithInstanceID(ctx, req.InstanceId)
details, err := s.command.RemoveInstanceDomain(ctx, req.Domain)
if err != nil {
return nil, err
@@ -112,7 +135,7 @@ func (s *Server) RemoveDomain(ctx context.Context, req *system_pb.RemoveDomainRe
}
func (s *Server) SetPrimaryDomain(ctx context.Context, req *system_pb.SetPrimaryDomainRequest) (*system_pb.SetPrimaryDomainResponse, error) {
- ctx = authz.WithInstanceID(ctx, req.Id)
+ ctx = authz.WithInstanceID(ctx, req.InstanceId)
details, err := s.command.SetPrimaryInstanceDomain(ctx, req.Domain)
if err != nil {
return nil, err
diff --git a/internal/api/grpc/system/instance_converter.go b/internal/api/grpc/system/instance_converter.go
index 761e5cd3fe..35ef672b2c 100644
--- a/internal/api/grpc/system/instance_converter.go
+++ b/internal/api/grpc/system/instance_converter.go
@@ -7,6 +7,7 @@ import (
"github.com/zitadel/zitadel/internal/query"
instance_pb "github.com/zitadel/zitadel/pkg/grpc/instance"
system_pb "github.com/zitadel/zitadel/pkg/grpc/system"
+ "golang.org/x/text/language"
)
func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInstance command.InstanceSetup) *command.InstanceSetup {
@@ -20,18 +21,32 @@ func AddInstancePbToSetupInstance(req *system_pb.AddInstanceRequest, defaultInst
if req.FirstOrgName != "" {
defaultInstance.Org.Name = req.FirstOrgName
}
- if req.OwnerEmail != "" {
- defaultInstance.Org.Human.Email.Address = req.OwnerEmail
+ if req.OwnerEmail.Email != "" {
+ defaultInstance.Org.Human.Email.Address = req.OwnerEmail.Email
+ defaultInstance.Org.Human.Email.Verified = req.OwnerEmail.IsEmailVerified
}
- if req.OwnerUsername != "" {
- defaultInstance.Org.Human.Username = req.OwnerUsername
+ if req.OwnerProfile != nil {
+ if req.OwnerProfile.FirstName != "" {
+ defaultInstance.Org.Human.FirstName = req.OwnerProfile.FirstName
+ }
+ if req.OwnerProfile.LastName != "" {
+ defaultInstance.Org.Human.LastName = req.OwnerProfile.LastName
+ }
+ if req.OwnerProfile.PreferredLanguage != "" {
+ lang, err := language.Parse(req.OwnerProfile.PreferredLanguage)
+ if err == nil {
+ defaultInstance.Org.Human.PreferredLang = lang
+ }
+ }
}
- if req.OwnerFirstName != "" {
- defaultInstance.Org.Human.FirstName = req.OwnerFirstName
+ if req.OwnerUserName != "" {
+ defaultInstance.Org.Human.Username = req.OwnerUserName
}
- if req.OwnerLastName != "" {
- defaultInstance.Org.Human.LastName = req.OwnerLastName
+ if req.OwnerPassword != nil {
+ defaultInstance.Org.Human.Password = req.OwnerPassword.Password
+ defaultInstance.Org.Human.PasswordChangeRequired = req.OwnerPassword.PasswordChangeRequired
}
+
return &defaultInstance
}
func ListInstancesRequestToModel(req *system_pb.ListInstancesRequest) (*query.InstanceSearchQueries, error) {
diff --git a/internal/query/instance.go b/internal/query/instance.go
index e4f5a83f7d..dbb032101b 100644
--- a/internal/query/instance.go
+++ b/internal/query/instance.go
@@ -75,6 +75,7 @@ type Instance struct {
ChangeDate time.Time
CreationDate time.Time
Sequence uint64
+ Name string
GlobalOrgID string
IAMProjectID string
@@ -83,6 +84,7 @@ type Instance struct {
DefaultLang language.Tag
SetupStarted domain.Step
SetupDone domain.Step
+ Domains []*InstanceDomain
host string
}
@@ -159,7 +161,7 @@ func (q *Queries) SearchInstances(ctx context.Context, queries *InstanceSearchQu
}
func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
- stmt, scan := prepareInstanceQuery(authz.GetInstance(ctx).RequestedDomain())
+ stmt, scan := prepareInstanceDomainQuery(authz.GetInstance(ctx).RequestedDomain())
query, args, err := stmt.Where(sq.Eq{
InstanceColumnID.identifier(): authz.GetInstance(ctx).InstanceID(),
}).ToSql()
@@ -167,7 +169,10 @@ func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
return nil, errors.ThrowInternal(err, "QUERY-d9ngs", "Errors.Query.SQLStatement")
}
- row := q.client.QueryRowContext(ctx, query, args...)
+ row, err := q.client.QueryContext(ctx, query, args...)
+ if err != nil {
+ return nil, err
+ }
return scan(row)
}
@@ -181,7 +186,10 @@ func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instan
return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement")
}
- row := q.client.QueryRowContext(ctx, query, args...)
+ row, err := q.client.QueryContext(ctx, query, args...)
+ if err != nil {
+ return nil, err
+ }
return scan(row)
}
@@ -226,9 +234,9 @@ func prepareInstanceQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Insta
)
if err != nil {
if errs.Is(err, sql.ErrNoRows) {
- return nil, errors.ThrowNotFound(err, "QUERY-n0wng", "Errors.IAM.NotFound")
+ return nil, errors.ThrowNotFound(err, "QUERY-5m09s", "Errors.IAM.NotFound")
}
- return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
+ return nil, errors.ThrowInternal(err, "QUERY-3j9sf", "Errors.Internal")
}
instance.DefaultLang = language.Make(lang)
return instance, nil
@@ -241,6 +249,7 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
InstanceColumnCreationDate.identifier(),
InstanceColumnChangeDate.identifier(),
InstanceColumnSequence.identifier(),
+ InstanceColumnName.identifier(),
InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(),
@@ -262,6 +271,7 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
&instance.CreationDate,
&instance.ChangeDate,
&instance.Sequence,
+ &instance.Name,
&instance.GlobalOrgID,
&instance.IAMProjectID,
&instance.ConsoleID,
@@ -290,12 +300,13 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
}
}
-func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
+func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Rows) (*Instance, error)) {
return sq.Select(
InstanceColumnID.identifier(),
InstanceColumnCreationDate.identifier(),
InstanceColumnChangeDate.identifier(),
InstanceColumnSequence.identifier(),
+ InstanceColumnName.identifier(),
InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(),
@@ -303,33 +314,73 @@ func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Row) (
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(),
+ InstanceDomainDomainCol.identifier(),
+ InstanceDomainIsPrimaryCol.identifier(),
+ InstanceDomainIsGeneratedCol.identifier(),
+ InstanceDomainCreationDateCol.identifier(),
+ InstanceDomainChangeDateCol.identifier(),
+ InstanceDomainSequenceCol.identifier(),
).
From(instanceTable.identifier()).
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)).
PlaceholderFormat(sq.Dollar),
- func(row *sql.Row) (*Instance, error) {
- instance := &Instance{host: host}
- lang := ""
- err := row.Scan(
- &instance.ID,
- &instance.CreationDate,
- &instance.ChangeDate,
- &instance.Sequence,
- &instance.GlobalOrgID,
- &instance.IAMProjectID,
- &instance.ConsoleID,
- &instance.ConsoleAppID,
- &instance.SetupStarted,
- &instance.SetupDone,
- &lang,
- )
- if err != nil {
- if errs.Is(err, sql.ErrNoRows) {
- return nil, errors.ThrowNotFound(err, "QUERY-n0wng", "Errors.IAM.NotFound")
- }
- return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
+ func(rows *sql.Rows) (*Instance, error) {
+ instance := &Instance{
+ host: host,
+ Domains: make([]*InstanceDomain, 0),
+ }
+ lang := ""
+ for rows.Next() {
+ var (
+ domain sql.NullString
+ isPrimary sql.NullBool
+ isGenerated sql.NullBool
+ changeDate sql.NullTime
+ creationDate sql.NullTime
+ sequecne sql.NullInt64
+ )
+ err := rows.Scan(
+ &instance.ID,
+ &instance.CreationDate,
+ &instance.ChangeDate,
+ &instance.Sequence,
+ &instance.Name,
+ &instance.GlobalOrgID,
+ &instance.IAMProjectID,
+ &instance.ConsoleID,
+ &instance.ConsoleAppID,
+ &instance.SetupStarted,
+ &instance.SetupDone,
+ &lang,
+ &domain,
+ &isPrimary,
+ &isGenerated,
+ &changeDate,
+ &creationDate,
+ &sequecne,
+ )
+ if err != nil {
+ if errs.Is(err, sql.ErrNoRows) {
+ return nil, errors.ThrowNotFound(err, "QUERY-n0wng", "Errors.IAM.NotFound")
+ }
+ return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
+ }
+ if !domain.Valid {
+ continue
+ }
+ instance.Domains = append(instance.Domains, &InstanceDomain{
+ CreationDate: creationDate.Time,
+ ChangeDate: changeDate.Time,
+ Sequence: uint64(sequecne.Int64),
+ Domain: domain.String,
+ IsPrimary: isPrimary.Bool,
+ IsGenerated: isGenerated.Bool,
+ InstanceID: instance.ID,
+ })
+ }
+ if err := rows.Close(); err != nil {
+ return nil, errors.ThrowInternal(err, "QUERY-Dfbe2", "Errors.Query.CloseRows")
}
- instance.DefaultLang = language.Make(lang)
return instance, nil
}
}
diff --git a/internal/query/search_query.go b/internal/query/search_query.go
index 169e2c8211..2f71a906ce 100644
--- a/internal/query/search_query.go
+++ b/internal/query/search_query.go
@@ -31,18 +31,16 @@ func (req *SearchRequest) toQuery(query sq.SelectBuilder) sq.SelectBuilder {
}
if !req.SortingColumn.isZero() {
- clause := "LOWER(" + sqlPlaceholder + ")"
+ clause := req.SortingColumn.orderBy()
if !req.Asc {
clause += " DESC"
}
- query = query.OrderByClause(clause, req.SortingColumn.identifier())
+ query = query.OrderByClause(clause)
}
return query
}
-const sqlPlaceholder = "?"
-
type SearchQuery interface {
toQuery(sq.SelectBuilder) sq.SelectBuilder
comp() sq.Sqlizer
@@ -367,8 +365,9 @@ func (t table) isZero() bool {
}
type Column struct {
- name string
- table table
+ name string
+ table table
+ isOrderByLower bool
}
func (c Column) identifier() string {
@@ -381,6 +380,13 @@ func (c Column) identifier() string {
return c.name
}
+func (c Column) orderBy() string {
+ if !c.isOrderByLower {
+ return c.identifier()
+ }
+ return "LOWER(" + c.identifier() + ")"
+}
+
func (c Column) setTable(t table) Column {
c.table = t
return c
diff --git a/internal/query/search_query_test.go b/internal/query/search_query_test.go
index 7af1c3af77..47ae03c9cf 100644
--- a/internal/query/search_query_test.go
+++ b/internal/query/search_query_test.go
@@ -19,6 +19,11 @@ var (
name: "test_col",
table: testTable,
}
+ testLowerCol = Column{
+ name: "test_lower_col",
+ table: testTable,
+ isOrderByLower: true,
+ }
testNoCol = Column{
name: "",
table: testTable,
@@ -34,7 +39,6 @@ func TestSearchRequest_ToQuery(t *testing.T) {
}
type want struct {
stmtAddition string
- args []interface{}
}
tests := []struct {
name string
@@ -46,7 +50,6 @@ func TestSearchRequest_ToQuery(t *testing.T) {
fields: fields{},
want: want{
stmtAddition: "",
- args: nil,
},
},
{
@@ -56,7 +59,6 @@ func TestSearchRequest_ToQuery(t *testing.T) {
},
want: want{
stmtAddition: "OFFSET 5",
- args: nil,
},
},
{
@@ -66,7 +68,6 @@ func TestSearchRequest_ToQuery(t *testing.T) {
},
want: want{
stmtAddition: "LIMIT 5",
- args: nil,
},
},
{
@@ -76,8 +77,7 @@ func TestSearchRequest_ToQuery(t *testing.T) {
Asc: true,
},
want: want{
- stmtAddition: "ORDER BY LOWER(?)",
- args: []interface{}{"test_table.test_col"},
+ stmtAddition: "ORDER BY test_table.test_col",
},
},
{
@@ -86,8 +86,17 @@ func TestSearchRequest_ToQuery(t *testing.T) {
SortingColumn: testCol,
},
want: want{
- stmtAddition: "ORDER BY LOWER(?) DESC",
- args: []interface{}{"test_table.test_col"},
+ stmtAddition: "ORDER BY test_table.test_col DESC",
+ },
+ },
+ {
+ name: "sort lower asc",
+ fields: fields{
+ SortingColumn: testLowerCol,
+ Asc: true,
+ },
+ want: want{
+ stmtAddition: "ORDER BY LOWER(test_table.test_lower_col)",
},
},
{
@@ -99,8 +108,7 @@ func TestSearchRequest_ToQuery(t *testing.T) {
Asc: true,
},
want: want{
- stmtAddition: "ORDER BY LOWER(?) LIMIT 10 OFFSET 5",
- args: []interface{}{"test_table.test_col"},
+ stmtAddition: "ORDER BY test_table.test_col LIMIT 10 OFFSET 5",
},
},
}
@@ -116,7 +124,7 @@ func TestSearchRequest_ToQuery(t *testing.T) {
query := sq.Select((testCol).identifier()).From(testTable.identifier())
expectedQuery, _, _ := query.ToSql()
- stmt, args, err := req.toQuery(query).ToSql()
+ stmt, _, err := req.toQuery(query).ToSql()
if len(tt.want.stmtAddition) > 0 {
expectedQuery += " " + tt.want.stmtAddition
}
@@ -124,10 +132,6 @@ func TestSearchRequest_ToQuery(t *testing.T) {
t.Errorf("stmt = %q, want %q", stmt, expectedQuery)
}
- if !reflect.DeepEqual(args, tt.want.args) {
- t.Errorf("args = %v, want %v", args, tt.want.stmtAddition)
- }
-
if err != nil {
t.Errorf("no error expected but got %v", err)
}
diff --git a/internal/query/user.go b/internal/query/user.go
index 9ae41aa740..65f9a29bb3 100644
--- a/internal/query/user.go
+++ b/internal/query/user.go
@@ -130,8 +130,9 @@ var (
table: userTable,
}
UserUsernameCol = Column{
- name: projection.UserUsernameCol,
- table: userTable,
+ name: projection.UserUsernameCol,
+ table: userTable,
+ isOrderByLower: true,
}
UserTypeCol = Column{
name: projection.UserTypeCol,
@@ -163,20 +164,24 @@ var (
table: humanTable,
}
HumanFirstNameCol = Column{
- name: projection.HumanFirstNameCol,
- table: humanTable,
+ name: projection.HumanFirstNameCol,
+ table: humanTable,
+ isOrderByLower: true,
}
HumanLastNameCol = Column{
- name: projection.HumanLastNameCol,
- table: humanTable,
+ name: projection.HumanLastNameCol,
+ table: humanTable,
+ isOrderByLower: true,
}
HumanNickNameCol = Column{
- name: projection.HumanNickNameCol,
- table: humanTable,
+ name: projection.HumanNickNameCol,
+ table: humanTable,
+ isOrderByLower: true,
}
HumanDisplayNameCol = Column{
- name: projection.HumanDisplayNameCol,
- table: humanTable,
+ name: projection.HumanDisplayNameCol,
+ table: humanTable,
+ isOrderByLower: true,
}
HumanPreferredLanguageCol = Column{
name: projection.HumanPreferredLanguageCol,
@@ -193,8 +198,9 @@ var (
// email
HumanEmailCol = Column{
- name: projection.HumanEmailCol,
- table: humanTable,
+ name: projection.HumanEmailCol,
+ table: humanTable,
+ isOrderByLower: true,
}
HumanIsEmailVerifiedCol = Column{
name: projection.HumanIsEmailVerifiedCol,
@@ -221,8 +227,9 @@ var (
table: machineTable,
}
MachineNameCol = Column{
- name: projection.MachineNameCol,
- table: machineTable,
+ name: projection.MachineNameCol,
+ table: machineTable,
+ isOrderByLower: true,
}
MachineDescriptionCol = Column{
name: projection.MachineDescriptionCol,
@@ -397,27 +404,27 @@ func NewUserResourceOwnerSearchQuery(value string, comparison TextComparison) (S
}
func NewUserUsernameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
- return NewTextQuery(UserUsernameCol, value, comparison)
+ return NewTextQuery(Column(UserUsernameCol), value, comparison)
}
func NewUserFirstNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
- return NewTextQuery(HumanFirstNameCol, value, comparison)
+ return NewTextQuery(Column(HumanFirstNameCol), value, comparison)
}
func NewUserLastNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
- return NewTextQuery(HumanLastNameCol, value, comparison)
+ return NewTextQuery(Column(HumanLastNameCol), value, comparison)
}
func NewUserNickNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
- return NewTextQuery(HumanNickNameCol, value, comparison)
+ return NewTextQuery(Column(HumanNickNameCol), value, comparison)
}
func NewUserDisplayNameSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
- return NewTextQuery(HumanDisplayNameCol, value, comparison)
+ return NewTextQuery(Column(HumanDisplayNameCol), value, comparison)
}
func NewUserEmailSearchQuery(value string, comparison TextComparison) (SearchQuery, error) {
- return NewTextQuery(HumanEmailCol, value, comparison)
+ return NewTextQuery(Column(HumanEmailCol), value, comparison)
}
func NewUserStateSearchQuery(value int32) (SearchQuery, error) {
diff --git a/proto/zitadel/instance.proto b/proto/zitadel/instance.proto
index e27c0c84b2..e75dd89bf6 100644
--- a/proto/zitadel/instance.proto
+++ b/proto/zitadel/instance.proto
@@ -25,6 +25,36 @@ message Instance {
example: "\"ZITADEL\"";
}
];
+ string version = 5 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"1.0.0\"";
+ }
+ ];
+}
+
+message InstanceDetail {
+ string id = 1 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"69629023906488334\""
+ }
+ ];
+ zitadel.v1.ObjectDetails details = 2;
+ State state = 3 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ description: "current state of the instance";
+ }
+ ];
+ string name = 4 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"ZITADEL\"";
+ }
+ ];
+ string version = 5 [
+ (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
+ example: "\"1.0.0\"";
+ }
+ ];
+ repeated Domain domains = 6;
}
enum State {
diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto
index 229d81e8d0..baeefec7e4 100644
--- a/proto/zitadel/system.proto
+++ b/proto/zitadel/system.proto
@@ -102,7 +102,7 @@ service SystemService {
};
}
- // Returns a list of ZITADEL instances/tenants
+ // Returns a list of ZITADEL instances
rpc ListInstances(ListInstancesRequest) returns (ListInstancesResponse) {
option (google.api.http) = {
post: "/instances/_search"
@@ -113,7 +113,7 @@ service SystemService {
// Returns the detail of an instance
rpc GetInstance(GetInstanceRequest) returns (GetInstanceResponse) {
option (google.api.http) = {
- get: "/instances/{id}";
+ get: "/instances/{instance_id}";
};
}
@@ -130,14 +130,22 @@ service SystemService {
// This might take some time
rpc RemoveInstance(RemoveInstanceRequest) returns (RemoveInstanceResponse) {
option (google.api.http) = {
- delete: "/instances/{id}"
+ delete: "/instances/{instance_id}"
+ };
+ }
+
+ // Checks if a domain exists
+ rpc ExistsDomain(ExistsDomainRequest) returns (ExistsDomainResponse) {
+ option (google.api.http) = {
+ post: "/domains/{domain}/_exists";
+ body: "*"
};
}
// Returns the custom domains of an instance
rpc ListDomains(ListDomainsRequest) returns (ListDomainsResponse) {
option (google.api.http) = {
- post: "/instances/{id}/domains/_search";
+ post: "/instances/{instance_id}/domains/_search";
body: "*"
};
}
@@ -145,7 +153,7 @@ service SystemService {
// Returns the domain of an instance
rpc AddDomain(AddDomainRequest) returns (AddDomainResponse) {
option (google.api.http) = {
- post: "/instances/{id}/domains";
+ post: "/instances/{instance_id}/domains";
body: "*"
};
}
@@ -153,14 +161,14 @@ service SystemService {
// Returns the domain of an instance
rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) {
option (google.api.http) = {
- delete: "/instances/{id}/domains/{domain}";
+ delete: "/instances/{instance_id}/domains/{domain}";
};
}
// Returns the domain of an instance
rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) {
option (google.api.http) = {
- post: "/instances/{id}/domains/_set_primary";
+ post: "/instances/{instance_id}/domains/_set_primary";
body: "*"
};
}
@@ -309,32 +317,44 @@ message ListInstancesResponse {
}
message GetInstanceRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetInstanceResponse {
- zitadel.instance.v1.Instance instance = 1;
+ zitadel.instance.v1.InstanceDetail instance = 1;
}
message AddInstanceRequest {
+ message Profile {
+ string first_name = 1 [(validate.rules).string = {max_len: 200}];
+ string last_name = 2 [(validate.rules).string = {max_len: 200}];
+ string preferred_language = 5 [(validate.rules).string = {max_len: 10}];
+ }
+ message Email {
+ string email = 1[(validate.rules).string = {min_len: 1, max_len: 200}];
+ bool is_email_verified = 2;
+ }
+ message Password {
+ string password = 1 [(validate.rules).string = {max_len: 200}];
+ bool password_change_required = 2;
+ }
+
string instance_name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string first_org_name = 2 [(validate.rules).string = {max_len: 200}];
string custom_domain = 3 [(validate.rules).string = {max_len: 200}];
- string owner_first_name = 4 [(validate.rules).string = {max_len: 200}];
- string owner_last_name = 5 [(validate.rules).string = {max_len: 200}];
- string owner_email = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
- string owner_username = 7 [(validate.rules).string = {max_len: 200}];
- uint64 request_limit = 8;
- uint64 action_mins_limit = 9;
+ string owner_user_name = 4 [(validate.rules).string = {max_len: 200}];
+ Email owner_email = 5 [(validate.rules).message.required = true];
+ Profile owner_profile = 6 [(validate.rules).message.required = false];
+ Password owner_password = 7 [(validate.rules).message.required = false];
}
message AddInstanceResponse {
- string id = 1;
+ string instance_id = 1;
zitadel.v1.ObjectDetails details = 2;
}
message RemoveInstanceRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message RemoveInstanceResponse {
@@ -342,7 +362,7 @@ message RemoveInstanceResponse {
}
message GetUsageRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetUsageResponse {
@@ -351,8 +371,16 @@ message GetUsageResponse {
uint64 executed_action_mins = 3;
}
+message ExistsDomainRequest {
+ string domain = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+}
+
+message ExistsDomainResponse {
+ bool exists = 1;
+}
+
message ListDomainsRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];//list limitations and ordering
+ string instance_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;
@@ -367,7 +395,7 @@ message ListDomainsResponse {
}
message AddDomainRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
@@ -376,7 +404,7 @@ message AddDomainResponse {
}
message RemoveDomainRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
@@ -385,7 +413,7 @@ message RemoveDomainResponse {
}
message SetPrimaryDomainRequest {
- string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string instance_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string domain = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
}