Merge branch 'main' into next

This commit is contained in:
Livio Spring 2024-03-05 16:46:02 +01:00
commit 7b8be37fd6
No known key found for this signature in database
GPG Key ID: 26BB1C2FA5952CF0
18 changed files with 117 additions and 16 deletions

View File

@ -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: |

View File

@ -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

View File

@ -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

View File

@ -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).
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).

View File

@ -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.

View File

@ -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.

View File

@ -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).
:::
<details>
<summary>defaults.yaml</summary>
<CodeBlock language="yaml">{DefaultsYamlSource}</CodeBlock>
@ -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.
:::
<details>
<summary>steps.yaml</summary>
<CodeBlock language="yaml">{StepsYamlSource}</CodeBlock>

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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,

View File

@ -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
}

View File

@ -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 "
}

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -13,7 +13,7 @@ import (
)
const (
InstanceFeatureTable = "projections.instance_features"
InstanceFeatureTable = "projections.instance_features2"
InstanceFeatureInstanceIDCol = "instance_id"
InstanceFeatureKeyCol = "key"

View File

@ -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",
},

View File

@ -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
}