From 437c834a40e34b29392d1aab432d913d3deb66f1 Mon Sep 17 00:00:00 2001 From: Fabi Date: Fri, 1 Mar 2024 08:10:14 +0100 Subject: [PATCH 1/6] chore: update github action with correct dependabot actor (#7485) --- .github/workflows/issues.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml index 418465409d..7069700791 100644 --- a/.github/workflows/issues.yml +++ b/.github/workflows/issues.yml @@ -23,19 +23,20 @@ jobs: github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} - uses: tspascoal/get-user-teams-membership@v3 id: checkUserMember + if: github.actor != 'dependabot[bot]' with: username: ${{ github.actor }} GITHUB_TOKEN: ${{ secrets.ADD_TO_PROJECT_PAT }} - name: add pr uses: actions/add-to-project@v0.5.0 - if: ${{ github.event_name == 'pull_request_target' && !contains(steps.checkUserMember.outputs.teams, 'engineers') && github.actor != 'app/dependabot'}} + if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'engineers')}} with: # You can target a repository in a different organization # to the issue project-url: https://github.com/orgs/zitadel/projects/2 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} - uses: actions-ecosystem/action-add-labels@v1.1.0 - if: ${{ github.event_name == 'pull_request_target' && !contains(steps.checkUserMember.outputs.teams, 'staff') && github.actor != 'app/dependabot'}} + if: ${{ github.event_name == 'pull_request_target' && github.actor != 'dependabot[bot]' && !contains(steps.checkUserMember.outputs.teams, 'staff')}} with: github_token: ${{ secrets.ADD_TO_PROJECT_PAT }} labels: | From 38777b478ebd20b4d95a366e5a128ef6ea07792b Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Mon, 4 Mar 2024 07:56:48 +0100 Subject: [PATCH 2/6] perf: filter events by instance ids (#7489) fix: filter events by instance ids --- internal/eventstore/repository/sql/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/eventstore/repository/sql/query.go b/internal/eventstore/repository/sql/query.go index 6cd0672e46..145f168d52 100644 --- a/internal/eventstore/repository/sql/query.go +++ b/internal/eventstore/repository/sql/query.go @@ -221,7 +221,7 @@ func eventsScanner(useV1 bool) func(scanner scan, dest interface{}) (err error) } func prepareConditions(criteria querier, query *repository.SearchQuery, useV1 bool) (string, []any) { - clauses, args := prepareQuery(criteria, useV1, query.InstanceID, query.ExcludedInstances) + clauses, args := prepareQuery(criteria, useV1, query.InstanceID, query.InstanceIDs, query.ExcludedInstances) if clauses != "" && len(query.SubQueries) > 0 { clauses += " AND " } From 3ae4e9e6cf42450af7b4011aad81964049f12616 Mon Sep 17 00:00:00 2001 From: Elio Bischof Date: Tue, 5 Mar 2024 08:37:12 +0100 Subject: [PATCH 3/6] docs: describe DefaultInstance vs FirstInstance (#7487) * docs: describe DefaultInstance vs FirstInstance * link to docs * add better searchable tip to the docs * add better searchable tip to the docs * add link --- cmd/defaults.yaml | 5 +++++ cmd/setup/steps.yaml | 1 + docs/docs/self-hosting/manage/configure/configure.mdx | 9 +++++++++ 3 files changed, 15 insertions(+) diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 42b5fa4024..781f13d501 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -543,6 +543,11 @@ Eventstore: # Maximum amount of push retries in case of primary key violation on the sequence MaxRetries: 5 #ZITADEL_EVENTSTORE_MAXRETRIES +# The DefaultInstance section defines the default values for each new virtual instance that is created. +# Check out https://zitadel.com/docs/concepts/structure/instance#multiple-virtual-instances for more information about virtual instances. +# For the initial setup, the default values are used to create the first instance. +# However, you might want to have your first instance created by the setup job to have a different configuration. +# To overwrite the default values for the initial setup, configure the FirstInstance yaml section and pass it using the --steps flag. DefaultInstance: InstanceName: ZITADEL # ZITADEL_DEFAULTINSTANCE_INSTANCENAME DefaultLanguage: en # ZITADEL_DEFAULTINSTANCE_DEFAULTLANGUAGE diff --git a/cmd/setup/steps.yaml b/cmd/setup/steps.yaml index c585c535b2..5e7805f24d 100644 --- a/cmd/setup/steps.yaml +++ b/cmd/setup/steps.yaml @@ -1,3 +1,4 @@ +# By using the FirstInstance section, you can overwrite the DefaultInstance configuration for the first instance created by zitadel setup. FirstInstance: # The machine key from the section FirstInstance.Org.Machine.MachineKey is written to the MachineKeyPath. MachineKeyPath: # ZITADEL_FIRSTINSTANCE_MACHINEKEYPATH diff --git a/docs/docs/self-hosting/manage/configure/configure.mdx b/docs/docs/self-hosting/manage/configure/configure.mdx index 0288014ca5..aaf221dfda 100644 --- a/docs/docs/self-hosting/manage/configure/configure.mdx +++ b/docs/docs/self-hosting/manage/configure/configure.mdx @@ -21,6 +21,11 @@ This guide assumes you are familiar with [running ZITADEL using the least amount You can configure the runtime using the `--config` flag of the `zitadel` binary. Also, you can use the environment variables listed in the defaults.yaml. +:::tip +For overwriting the default configuration for the first instance created by zitadel setup, use the FirstInstance section in the [database initialization file](#database-initialization-file). +::: + +
defaults.yaml {DefaultsYamlSource} @@ -32,6 +37,10 @@ ZITADEL uses a [different configuration file](https://github.com/zitadel/zitadel Use the `--steps` flag of the `zitadel` binary to provide this configuration file. Also, you can use the environment variables listed in the steps.yaml. +:::tip +By using the FirstInstance section, you can overwrite the [DefaultInstance configuration](#runtime-configuration-file) for the first instance created by zitadel setup. +::: +
steps.yaml {StepsYamlSource} From 8f898775c96d4978b7b606e68d850015e726b8ed Mon Sep 17 00:00:00 2001 From: Silvan Date: Tue, 5 Mar 2024 10:46:42 +0100 Subject: [PATCH 4/6] docs: remove localhost from links (#7503) --- docs/docs/concepts/features/custom-domain.md | 2 +- docs/docs/guides/integrate/login/login-users.mdx | 2 +- docs/docs/guides/start/quickstart.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/concepts/features/custom-domain.md b/docs/docs/concepts/features/custom-domain.md index 820ce3a81b..f07dd5a412 100644 --- a/docs/docs/concepts/features/custom-domain.md +++ b/docs/docs/concepts/features/custom-domain.md @@ -10,4 +10,4 @@ By configuring a custom domain within ZITADEL, organizations can replace the def This not only enhances the overall user experience but also reinforces the organization's brand presence. Additionally, custom domains can contribute to trust and credibility, as users are more likely to recognize and trust URLs associated with the organization rather than generic domains. Overall, ZITADEL's custom domain feature empowers organizations to tailor the authentication process to align with their brand identity and user expectations. -Learn how to [configure a custom domain in ZITADEL Cloud](http://localhost:3000/docs/guides/manage/cloud/instances#add-custom-domain) or how to configure [custom domain when self-hosting](http://localhost:3000/docs/self-hosting/manage/custom-domain). \ No newline at end of file +Learn how to [configure a custom domain in ZITADEL Cloud](/guides/manage/cloud/instances#add-custom-domain) or how to configure [custom domain when self-hosting](/self-hosting/manage/custom-domain). \ No newline at end of file diff --git a/docs/docs/guides/integrate/login/login-users.mdx b/docs/docs/guides/integrate/login/login-users.mdx index 32c4bdba69..3438d8772d 100644 --- a/docs/docs/guides/integrate/login/login-users.mdx +++ b/docs/docs/guides/integrate/login/login-users.mdx @@ -210,7 +210,7 @@ The user experience depends mainly on the operating system and browser. ## Build a custom Login UI to authenticate users In certain cases, you want to build your own login UI to optimize your user experience. -We have dedicated guides on [how to build your custom login UI](http://localhost:3000/docs/guides/integrate/login-ui) with ZITADEL. +We have dedicated guides on [how to build your custom login UI](../login-ui) with ZITADEL. When building your own login UI, you will leverage the [Session API](#zitadels-session-api) to authenticate users and manage user sessions. diff --git a/docs/docs/guides/start/quickstart.mdx b/docs/docs/guides/start/quickstart.mdx index ccdf315e02..0995afe2e0 100644 --- a/docs/docs/guides/start/quickstart.mdx +++ b/docs/docs/guides/start/quickstart.mdx @@ -352,7 +352,7 @@ The provided config extends the `UserManagerSettings` of the `oidc-client-ts` li - post_logout_redirect_uri (the URL to redirect to after the user logs out) - scope (the permissions requested from the user) - project_resource_id (To add a ZITADEL project scope. `urn:zitadel:iam:org:project:id:[projectId]:aud` and `urn:zitadel:iam:org:projects:roles` [scopes](https://zitadel.com/docs/apis/openidoauth/scopes#reserved-scopes).) -- prompt ([the OIDC prompt parameter](http://localhost:3000/docs/apis/openidoauth/endpoints#additional-parameters)) +- prompt ([the OIDC prompt parameter](/apis/openidoauth/endpoints#additional-parameters)) 2. Create a folder named components in the src directory. Create two files named Login.js and Callback.js. From dfcc26de1e7402f6d5a1515fd1ffa6195f22dd0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20M=C3=B6hlmann?= Date: Tue, 5 Mar 2024 17:12:49 +0200 Subject: [PATCH 5/6] fix: assign instance ID to aggregate ID when converting from v1 to v2 feature (#7505) * fix: assign instance ID to aggregate ID when converting from v1 to v2 feature This change fixes a mismatch between v1 and v2 aggregate IDs for instance feature events. The old v1 used a random aggregate ID, while v2 uses the instance ID as aggregate ID. The adapter was not correctly mapping, which resulted in the projections.instance_features table being filled with wrong instance IDs. Closes #7501 * fix unit test --- internal/command/instance_features_model.go | 1 - internal/query/instance_by_domain.sql | 2 +- internal/query/instance_by_id.sql | 2 +- internal/query/instance_features_model.go | 1 - internal/query/projection/instance_features.go | 2 +- internal/query/projection/instance_features_test.go | 10 +++++----- internal/repository/feature/feature.go | 4 ++++ 7 files changed, 12 insertions(+), 10 deletions(-) diff --git a/internal/command/instance_features_model.go b/internal/command/instance_features_model.go index ff5ae99ba9..7f618099d9 100644 --- a/internal/command/instance_features_model.go +++ b/internal/command/instance_features_model.go @@ -48,7 +48,6 @@ func (m *InstanceFeaturesWriteModel) Query() *eventstore.SearchQueryBuilder { AwaitOpenTransactions(). AddQuery(). AggregateTypes(feature_v2.AggregateType). - AggregateIDs(m.AggregateID). EventTypes( feature_v1.DefaultLoginInstanceEventType, feature_v2.InstanceResetEventType, diff --git a/internal/query/instance_by_domain.sql b/internal/query/instance_by_domain.sql index ae6cb24249..ffcf8a2f79 100644 --- a/internal/query/instance_by_domain.sql +++ b/internal/query/instance_by_domain.sql @@ -8,7 +8,7 @@ with domain as ( ) features from domain d cross join projections.system_features s - full outer join projections.instance_features i using (key, instance_id) + full outer join projections.instance_features2 i using (key, instance_id) group by instance_id ) select diff --git a/internal/query/instance_by_id.sql b/internal/query/instance_by_id.sql index 54d2b7a949..08398846cc 100644 --- a/internal/query/instance_by_id.sql +++ b/internal/query/instance_by_id.sql @@ -5,7 +5,7 @@ with features as ( ) features from (select $1::text instance_id) x cross join projections.system_features s - full outer join projections.instance_features i using (key, instance_id) + full outer join projections.instance_features2 i using (key, instance_id) group by instance_id ) select diff --git a/internal/query/instance_features_model.go b/internal/query/instance_features_model.go index ff23b82afe..4d6a043d8c 100644 --- a/internal/query/instance_features_model.go +++ b/internal/query/instance_features_model.go @@ -54,7 +54,6 @@ func (m *InstanceFeaturesReadModel) Query() *eventstore.SearchQueryBuilder { AwaitOpenTransactions(). AddQuery(). AggregateTypes(feature_v2.AggregateType). - AggregateIDs(m.AggregateID). EventTypes( feature_v1.DefaultLoginInstanceEventType, feature_v2.InstanceResetEventType, diff --git a/internal/query/projection/instance_features.go b/internal/query/projection/instance_features.go index bf7f0d0413..fae8824429 100644 --- a/internal/query/projection/instance_features.go +++ b/internal/query/projection/instance_features.go @@ -13,7 +13,7 @@ import ( ) const ( - InstanceFeatureTable = "projections.instance_features" + InstanceFeatureTable = "projections.instance_features2" InstanceFeatureInstanceIDCol = "instance_id" InstanceFeatureKeyCol = "key" diff --git a/internal/query/projection/instance_features_test.go b/internal/query/projection/instance_features_test.go index f43233ae67..4a4a46727f 100644 --- a/internal/query/projection/instance_features_test.go +++ b/internal/query/projection/instance_features_test.go @@ -38,7 +38,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.instance_features (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", + expectedStmt: "INSERT INTO projections.instance_features2 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features2.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", expectedArgs: []interface{}{ "agg-id", "legacy_introspection", @@ -69,9 +69,9 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "INSERT INTO projections.instance_features (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", + expectedStmt: "INSERT INTO projections.instance_features2 (instance_id, key, creation_date, change_date, sequence, value) VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT (instance_id, key) DO UPDATE SET (creation_date, change_date, sequence, value) = (projections.instance_features2.creation_date, EXCLUDED.change_date, EXCLUDED.sequence, EXCLUDED.value)", expectedArgs: []interface{}{ - "agg-id", + "instance-id", "login_default_org", anyArg{}, anyArg{}, @@ -100,7 +100,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.instance_features WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.instance_features2 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, @@ -126,7 +126,7 @@ func TestInstanceFeaturesProjection_reduces(t *testing.T) { executer: &testExecuter{ executions: []execution{ { - expectedStmt: "DELETE FROM projections.instance_features WHERE (instance_id = $1)", + expectedStmt: "DELETE FROM projections.instance_features2 WHERE (instance_id = $1)", expectedArgs: []interface{}{ "agg-id", }, diff --git a/internal/repository/feature/feature.go b/internal/repository/feature/feature.go index 25ed66050a..2ec52c464b 100644 --- a/internal/repository/feature/feature.go +++ b/internal/repository/feature/feature.go @@ -22,6 +22,10 @@ func DefaultLoginInstanceEventToV2(e *SetEvent[Boolean]) *feature_v2.SetEvent[bo BaseEvent: e.BaseEvent, Value: e.Value.Boolean, } + + // v1 used a random aggregate ID. + // v2 uses the instance ID as aggregate ID. + v2e.BaseEvent.Agg.ID = e.Agg.InstanceID v2e.BaseEvent.EventType = feature_v2.InstanceLoginDefaultOrgEventType return v2e } From ec3076c3c86e5b87c2118e39e2e63e1ef0663037 Mon Sep 17 00:00:00 2001 From: Silvan Date: Tue, 5 Mar 2024 16:44:51 +0100 Subject: [PATCH 6/6] fix(ListEvents): add aggregate types to filter if not set (#7490) --- internal/api/grpc/admin/event.go | 18 +++++++++ internal/api/grpc/admin/event_test.go | 58 +++++++++++++++++++++++++++ internal/eventstore/eventstore.go | 7 ++++ 3 files changed, 83 insertions(+) create mode 100644 internal/api/grpc/admin/event_test.go diff --git a/internal/api/grpc/admin/event.go b/internal/api/grpc/admin/event.go index 576fce8af1..79a5aae487 100644 --- a/internal/api/grpc/admin/event.go +++ b/internal/api/grpc/admin/event.go @@ -2,6 +2,7 @@ package admin import ( "context" + "slices" "time" "github.com/zitadel/zitadel/internal/api/authz" @@ -60,14 +61,21 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest) for i, eventType := range req.EventTypes { eventTypes[i] = eventstore.EventType(eventType) } + aggregateIDs := make([]string, 0, 1) if req.AggregateId != "" { aggregateIDs = append(aggregateIDs, req.AggregateId) } + aggregateTypes := make([]eventstore.AggregateType, len(req.AggregateTypes)) for i, aggregateType := range req.AggregateTypes { aggregateTypes[i] = eventstore.AggregateType(aggregateType) } + if len(aggregateTypes) == 0 { + aggregateTypes = aggregateTypesFromEventTypes(eventTypes) + } + aggregateTypes = slices.Compact(aggregateTypes) + limit := uint64(req.Limit) if limit == 0 || limit > maxLimit { limit = maxLimit @@ -100,3 +108,13 @@ func eventRequestToFilter(ctx context.Context, req *admin_pb.ListEventsRequest) } return builder, nil } + +func aggregateTypesFromEventTypes(eventTypes []eventstore.EventType) []eventstore.AggregateType { + aggregateTypes := make([]eventstore.AggregateType, 0, len(eventTypes)) + + for _, eventType := range eventTypes { + aggregateTypes = append(aggregateTypes, eventstore.AggregateTypeFromEventType(eventType)) + } + + return aggregateTypes +} diff --git a/internal/api/grpc/admin/event_test.go b/internal/api/grpc/admin/event_test.go new file mode 100644 index 0000000000..926d48ffe8 --- /dev/null +++ b/internal/api/grpc/admin/event_test.go @@ -0,0 +1,58 @@ +package admin + +import ( + "reflect" + "testing" + + "github.com/zitadel/zitadel/internal/eventstore" + "github.com/zitadel/zitadel/internal/repository/deviceauth" + "github.com/zitadel/zitadel/internal/repository/org" + "github.com/zitadel/zitadel/internal/repository/user" +) + +func Test_aggregateTypesFromEventTypes(t *testing.T) { + type args struct { + eventTypes []eventstore.EventType + } + tests := []struct { + name string + args args + want []eventstore.AggregateType + }{ + { + name: "no event types", + args: args{ + eventTypes: []eventstore.EventType{}, + }, + want: []eventstore.AggregateType{}, + }, + { + name: "only by prefix", + args: args{ + eventTypes: []eventstore.EventType{user.MachineAddedEventType, org.OrgAddedEventType}, + }, + want: []eventstore.AggregateType{user.AggregateType, org.AggregateType}, + }, + { + name: "with special", + args: args{ + eventTypes: []eventstore.EventType{deviceauth.ApprovedEventType, org.OrgAddedEventType}, + }, + want: []eventstore.AggregateType{deviceauth.AggregateType, org.AggregateType}, + }, + { + name: "duplicates", + args: args{ + eventTypes: []eventstore.EventType{org.OrgAddedEventType, org.OrgChangedEventType}, + }, + want: []eventstore.AggregateType{org.AggregateType, org.AggregateType}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := aggregateTypesFromEventTypes(tt.args.eventTypes); !reflect.DeepEqual(got, tt.want) { + t.Errorf("aggregateTypesFromEventTypes() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/internal/eventstore/eventstore.go b/internal/eventstore/eventstore.go index 17b570ab64..2602683cca 100644 --- a/internal/eventstore/eventstore.go +++ b/internal/eventstore/eventstore.go @@ -31,6 +31,7 @@ var ( eventInterceptors map[EventType]eventTypeInterceptors eventTypes []string aggregateTypes []string + eventTypeMapping = map[EventType]AggregateType{} ) // RegisterFilterEventMapper registers a function for mapping an eventstore event to an event @@ -45,9 +46,11 @@ func RegisterFilterEventMapper(aggregateType AggregateType, eventType EventType, if eventInterceptors == nil { eventInterceptors = make(map[EventType]eventTypeInterceptors) } + interceptor := eventInterceptors[eventType] interceptor.eventMapper = mapper eventInterceptors[eventType] = interceptor + eventTypeMapping[eventType] = aggregateType } type eventTypeInterceptors struct { @@ -112,6 +115,10 @@ retry: return mappedEvents, nil } +func AggregateTypeFromEventType(typ EventType) AggregateType { + return eventTypeMapping[typ] +} + func (es *Eventstore) EventTypes() []string { return eventTypes }