mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-20 03:27:47 +00:00
Merge branch 'main' into mpa_dpf_privavy_policy
This commit is contained in:
@@ -206,6 +206,8 @@ The same applies to messages that are returned by multiple resources.
|
||||
For example, information about the `User` might be different when managing the user resource itself than when it's returned
|
||||
as part of an authorization or a manager role, where only limited information is needed.
|
||||
|
||||
On the other hand, types that always follow the same pattern and are used in multiple resources, such as `IDFilter`, `TimestampFilter` or `InIDsFilter` SHOULD be globalized and reused.
|
||||
|
||||
##### Re-using messages
|
||||
|
||||
Prevent reusing messages for the creation and the retrieval of a resource.
|
||||
@@ -271,7 +273,7 @@ Additionally, state changes, specific actions or operations that do not fit into
|
||||
The API uses OAuth 2 for authorization. There are corresponding middlewares that check the access token for validity and
|
||||
automatically return an error if the token is invalid.
|
||||
|
||||
Permissions grated to the user might be organization specific and can therefore only be checked based on the queried resource.
|
||||
Permissions granted to the user might be organization specific and can therefore only be checked based on the queried resource.
|
||||
In such case, the API does not check the permissions itself but relies on the checks of the functions that are called by the API.
|
||||
If the permission can be checked by the API itself, e.g. if the permission is instance wide, it can be annotated on the endpoint in the proto file (see below).
|
||||
In any case, the required permissions need to be documented in the [API documentation](#documentation).
|
||||
|
@@ -193,7 +193,7 @@ Use [Console](https://zitadel.com/docs/guides/manage/console/overview) or our [A
|
||||
### Login V2
|
||||
|
||||
Check out our new Login V2 version in our [typescript repository](https://github.com/zitadel/typescript) or in our [documentation](https://zitadel.com/docs/guides/integrate/login/hosted-login#hosted-login-version-2-beta)
|
||||
[]
|
||||

|
||||
|
||||
## Security
|
||||
|
||||
|
@@ -3,6 +3,8 @@ package mirror
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/shopspring/decimal"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/v2/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/v2/readmodel"
|
||||
"github.com/zitadel/zitadel/internal/v2/system"
|
||||
@@ -29,7 +31,7 @@ func queryLastSuccessfulMigration(ctx context.Context, destinationES *eventstore
|
||||
return lastSuccess, nil
|
||||
}
|
||||
|
||||
func writeMigrationSucceeded(ctx context.Context, destinationES *eventstore.EventStore, id, source string, position float64) error {
|
||||
func writeMigrationSucceeded(ctx context.Context, destinationES *eventstore.EventStore, id, source string, position decimal.Decimal) error {
|
||||
return destinationES.Push(
|
||||
ctx,
|
||||
eventstore.NewPushIntent(
|
||||
|
@@ -8,7 +8,9 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
@@ -89,7 +91,7 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
|
||||
previousMigration, err := queryLastSuccessfulMigration(ctx, destinationES, source.DatabaseName())
|
||||
logging.OnError(err).Fatal("unable to query latest successful migration")
|
||||
|
||||
var maxPosition float64
|
||||
var maxPosition decimal.Decimal
|
||||
err = source.QueryRowContext(ctx,
|
||||
func(row *sql.Row) error {
|
||||
return row.Scan(&maxPosition)
|
||||
@@ -101,7 +103,7 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
|
||||
logging.WithFields("from", previousMigration.Position, "to", maxPosition).Info("start event migration")
|
||||
|
||||
nextPos := make(chan bool, 1)
|
||||
pos := make(chan float64, 1)
|
||||
pos := make(chan decimal.Decimal, 1)
|
||||
errs := make(chan error, 3)
|
||||
|
||||
go func() {
|
||||
@@ -152,7 +154,7 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
|
||||
go func() {
|
||||
defer close(pos)
|
||||
for range nextPos {
|
||||
var position float64
|
||||
var position decimal.Decimal
|
||||
err := dest.QueryRowContext(
|
||||
ctx,
|
||||
func(row *sql.Row) error {
|
||||
@@ -175,6 +177,10 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
|
||||
tag, err := conn.PgConn().CopyFrom(ctx, reader, "COPY eventstore.events2 FROM STDIN")
|
||||
eventCount = tag.RowsAffected()
|
||||
if err != nil {
|
||||
pgErr := new(pgconn.PgError)
|
||||
errors.As(err, &pgErr)
|
||||
|
||||
logging.WithError(err).WithField("pg_err_details", pgErr.Detail).Error("unable to copy events into destination")
|
||||
return zerrors.ThrowUnknown(err, "MIGRA-DTHi7", "unable to copy events into destination")
|
||||
}
|
||||
|
||||
@@ -187,7 +193,7 @@ func copyEvents(ctx context.Context, source, dest *db.DB, bulkSize uint32) {
|
||||
logging.WithFields("took", time.Since(start), "count", eventCount).Info("events migrated")
|
||||
}
|
||||
|
||||
func writeCopyEventsDone(ctx context.Context, es *eventstore.EventStore, id, source string, position float64, errs <-chan error) {
|
||||
func writeCopyEventsDone(ctx context.Context, es *eventstore.EventStore, id, source string, position decimal.Decimal, errs <-chan error) {
|
||||
joinedErrs := make([]error, 0, len(errs))
|
||||
for err := range errs {
|
||||
joinedErrs = append(joinedErrs, err)
|
||||
|
@@ -296,6 +296,13 @@ func execProjections(ctx context.Context, instances <-chan string, failedInstanc
|
||||
continue
|
||||
}
|
||||
|
||||
err = projection.ProjectInstanceFields(ctx)
|
||||
if err != nil {
|
||||
logging.WithFields("instance", instance).WithError(err).Info("trigger fields failed")
|
||||
failedInstances <- instance
|
||||
continue
|
||||
}
|
||||
|
||||
err = auth_handler.ProjectInstance(ctx)
|
||||
if err != nil {
|
||||
logging.WithFields("instance", instance).WithError(err).Info("trigger auth handler failed")
|
||||
|
27
cmd/setup/57.go
Normal file
27
cmd/setup/57.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 57.sql
|
||||
createResourceCounts string
|
||||
)
|
||||
|
||||
type CreateResourceCounts struct {
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *CreateResourceCounts) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||
_, err := mig.dbClient.ExecContext(ctx, createResourceCounts)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mig *CreateResourceCounts) String() string {
|
||||
return "57_create_resource_counts"
|
||||
}
|
106
cmd/setup/57.sql
Normal file
106
cmd/setup/57.sql
Normal file
@@ -0,0 +1,106 @@
|
||||
CREATE TABLE IF NOT EXISTS projections.resource_counts
|
||||
(
|
||||
id SERIAL PRIMARY KEY, -- allows for easy pagination
|
||||
instance_id TEXT NOT NULL,
|
||||
table_name TEXT NOT NULL, -- needed for trigger matching, not in reports
|
||||
parent_type TEXT NOT NULL,
|
||||
parent_id TEXT NOT NULL,
|
||||
resource_name TEXT NOT NULL, -- friendly name for reporting
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
amount INTEGER NOT NULL DEFAULT 1 CHECK (amount >= 0),
|
||||
|
||||
UNIQUE (instance_id, parent_type, parent_id, table_name)
|
||||
);
|
||||
|
||||
-- count_resource is a trigger function which increases or decreases the count of a resource.
|
||||
-- When creating the trigger the following required arguments (TG_ARGV) can be passed:
|
||||
-- 1. The type of the parent
|
||||
-- 2. The column name of the instance id
|
||||
-- 3. The column name of the owner id
|
||||
-- 4. The name of the resource
|
||||
CREATE OR REPLACE FUNCTION projections.count_resource()
|
||||
RETURNS trigger
|
||||
LANGUAGE 'plpgsql' VOLATILE
|
||||
AS $$
|
||||
DECLARE
|
||||
-- trigger variables
|
||||
tg_table_name TEXT := TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME;
|
||||
tg_parent_type TEXT := TG_ARGV[0];
|
||||
tg_instance_id_column TEXT := TG_ARGV[1];
|
||||
tg_parent_id_column TEXT := TG_ARGV[2];
|
||||
tg_resource_name TEXT := TG_ARGV[3];
|
||||
|
||||
tg_instance_id TEXT;
|
||||
tg_parent_id TEXT;
|
||||
|
||||
select_ids TEXT := format('SELECT ($1).%I, ($1).%I', tg_instance_id_column, tg_parent_id_column);
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') THEN
|
||||
EXECUTE select_ids INTO tg_instance_id, tg_parent_id USING NEW;
|
||||
|
||||
INSERT INTO projections.resource_counts(instance_id, table_name, parent_type, parent_id, resource_name)
|
||||
VALUES (tg_instance_id, tg_table_name, tg_parent_type, tg_parent_id, tg_resource_name)
|
||||
ON CONFLICT (instance_id, table_name, parent_type, parent_id) DO
|
||||
UPDATE SET updated_at = now(), amount = projections.resource_counts.amount + 1;
|
||||
|
||||
RETURN NEW;
|
||||
ELSEIF (TG_OP = 'DELETE') THEN
|
||||
EXECUTE select_ids INTO tg_instance_id, tg_parent_id USING OLD;
|
||||
|
||||
UPDATE projections.resource_counts
|
||||
SET updated_at = now(), amount = amount - 1
|
||||
WHERE instance_id = tg_instance_id
|
||||
AND table_name = tg_table_name
|
||||
AND parent_type = tg_parent_type
|
||||
AND parent_id = tg_parent_id
|
||||
AND resource_name = tg_resource_name
|
||||
AND amount > 0; -- prevent check failure on negative amount.
|
||||
|
||||
RETURN OLD;
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- delete_table_counts removes all resource counts for a TRUNCATED table.
|
||||
CREATE OR REPLACE FUNCTION projections.delete_table_counts()
|
||||
RETURNS trigger
|
||||
LANGUAGE 'plpgsql'
|
||||
AS $$
|
||||
DECLARE
|
||||
-- trigger variables
|
||||
tg_table_name TEXT := TG_TABLE_SCHEMA || '.' || TG_TABLE_NAME;
|
||||
BEGIN
|
||||
DELETE FROM projections.resource_counts
|
||||
WHERE table_name = tg_table_name;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- delete_parent_counts removes all resource counts for a deleted parent.
|
||||
-- 1. The type of the parent
|
||||
-- 2. The column name of the instance id
|
||||
-- 3. The column name of the owner id
|
||||
CREATE OR REPLACE FUNCTION projections.delete_parent_counts()
|
||||
RETURNS trigger
|
||||
LANGUAGE 'plpgsql'
|
||||
AS $$
|
||||
DECLARE
|
||||
-- trigger variables
|
||||
tg_parent_type TEXT := TG_ARGV[0];
|
||||
tg_instance_id_column TEXT := TG_ARGV[1];
|
||||
tg_parent_id_column TEXT := TG_ARGV[2];
|
||||
|
||||
tg_instance_id TEXT;
|
||||
tg_parent_id TEXT;
|
||||
|
||||
select_ids TEXT := format('SELECT ($1).%I, ($1).%I', tg_instance_id_column, tg_parent_id_column);
|
||||
BEGIN
|
||||
EXECUTE select_ids INTO tg_instance_id, tg_parent_id USING OLD;
|
||||
|
||||
DELETE FROM projections.resource_counts
|
||||
WHERE instance_id = tg_instance_id
|
||||
AND parent_type = tg_parent_type
|
||||
AND parent_id = tg_parent_id;
|
||||
|
||||
RETURN OLD;
|
||||
END
|
||||
$$;
|
49
cmd/setup/58.go
Normal file
49
cmd/setup/58.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 58/*.sql
|
||||
replaceLoginNames3View embed.FS
|
||||
)
|
||||
|
||||
type ReplaceLoginNames3View struct {
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *ReplaceLoginNames3View) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||
var exists bool
|
||||
err := mig.dbClient.QueryRowContext(ctx, func(r *sql.Row) error {
|
||||
return r.Scan(&exists)
|
||||
}, "SELECT exists(SELECT 1 from information_schema.views WHERE table_schema = 'projections' AND table_name = 'login_names3')")
|
||||
|
||||
if err != nil || !exists {
|
||||
return err
|
||||
}
|
||||
|
||||
statements, err := readStatements(replaceLoginNames3View, "58")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stmt := range statements {
|
||||
logging.WithFields("file", stmt.file, "migration", mig.String()).Info("execute statement")
|
||||
if _, err := mig.dbClient.ExecContext(ctx, stmt.query); err != nil {
|
||||
return fmt.Errorf("%s %s: %w", mig.String(), stmt.file, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mig *ReplaceLoginNames3View) String() string {
|
||||
return "58_replace_login_names3_view"
|
||||
}
|
36
cmd/setup/58/01_update_login_names3_view.sql
Normal file
36
cmd/setup/58/01_update_login_names3_view.sql
Normal file
@@ -0,0 +1,36 @@
|
||||
CREATE OR REPLACE VIEW projections.login_names3 AS
|
||||
SELECT
|
||||
u.id AS user_id
|
||||
, CASE
|
||||
WHEN p.must_be_domain THEN CONCAT(u.user_name, '@', d.name)
|
||||
ELSE u.user_name
|
||||
END AS login_name
|
||||
, COALESCE(d.is_primary, TRUE) AS is_primary
|
||||
, u.instance_id
|
||||
FROM
|
||||
projections.login_names3_users AS u
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT
|
||||
must_be_domain
|
||||
, is_default
|
||||
FROM
|
||||
projections.login_names3_policies AS p
|
||||
WHERE
|
||||
(
|
||||
p.instance_id = u.instance_id
|
||||
AND NOT p.is_default
|
||||
AND p.resource_owner = u.resource_owner
|
||||
) OR (
|
||||
p.instance_id = u.instance_id
|
||||
AND p.is_default
|
||||
)
|
||||
ORDER BY
|
||||
p.is_default -- custom first
|
||||
LIMIT 1
|
||||
) AS p ON TRUE
|
||||
LEFT JOIN
|
||||
projections.login_names3_domains d
|
||||
ON
|
||||
p.must_be_domain
|
||||
AND u.resource_owner = d.resource_owner
|
||||
AND u.instance_id = d.instance_id
|
1
cmd/setup/58/02_create_index.sql
Normal file
1
cmd/setup/58/02_create_index.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE INDEX CONCURRENTLY IF NOT EXISTS login_names3_policies_is_default_owner_idx ON projections.login_names3_policies (instance_id, is_default, resource_owner) INCLUDE (must_be_domain)
|
@@ -153,6 +153,8 @@ type Steps struct {
|
||||
s54InstancePositionIndex *InstancePositionIndex
|
||||
s55ExecutionHandlerStart *ExecutionHandlerStart
|
||||
s56IDPTemplate6SAMLFederatedLogout *IDPTemplate6SAMLFederatedLogout
|
||||
s57CreateResourceCounts *CreateResourceCounts
|
||||
s58ReplaceLoginNames3View *ReplaceLoginNames3View
|
||||
}
|
||||
|
||||
func MustNewSteps(v *viper.Viper) *Steps {
|
||||
|
@@ -215,6 +215,8 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
steps.s54InstancePositionIndex = &InstancePositionIndex{dbClient: dbClient}
|
||||
steps.s55ExecutionHandlerStart = &ExecutionHandlerStart{dbClient: dbClient}
|
||||
steps.s56IDPTemplate6SAMLFederatedLogout = &IDPTemplate6SAMLFederatedLogout{dbClient: dbClient}
|
||||
steps.s57CreateResourceCounts = &CreateResourceCounts{dbClient: dbClient}
|
||||
steps.s58ReplaceLoginNames3View = &ReplaceLoginNames3View{dbClient: dbClient}
|
||||
|
||||
err = projection.Create(ctx, dbClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
@@ -260,6 +262,8 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
steps.s54InstancePositionIndex,
|
||||
steps.s55ExecutionHandlerStart,
|
||||
steps.s56IDPTemplate6SAMLFederatedLogout,
|
||||
steps.s57CreateResourceCounts,
|
||||
steps.s58ReplaceLoginNames3View,
|
||||
} {
|
||||
setupErr = executeMigration(ctx, eventstoreClient, step, "migration failed")
|
||||
if setupErr != nil {
|
||||
@@ -296,6 +300,7 @@ func Setup(ctx context.Context, config *Config, steps *Steps, masterKey string)
|
||||
client: dbClient,
|
||||
},
|
||||
}
|
||||
repeatableSteps = append(repeatableSteps, triggerSteps(dbClient)...)
|
||||
|
||||
for _, repeatableStep := range repeatableSteps {
|
||||
setupErr = executeMigration(ctx, eventstoreClient, repeatableStep, "unable to migrate repeatable step")
|
||||
|
125
cmd/setup/trigger_steps.go
Normal file
125
cmd/setup/trigger_steps.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/migration"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
)
|
||||
|
||||
// triggerSteps defines the repeatable migrations that set up triggers
|
||||
// for counting resources in the database.
|
||||
func triggerSteps(db *database.DB) []migration.RepeatableMigration {
|
||||
return []migration.RepeatableMigration{
|
||||
// Delete parent count triggers for instances and organizations
|
||||
migration.DeleteParentCountsTrigger(db,
|
||||
projection.InstanceProjectionTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.InstanceColumnID,
|
||||
projection.InstanceColumnID,
|
||||
"instance",
|
||||
),
|
||||
migration.DeleteParentCountsTrigger(db,
|
||||
projection.OrgProjectionTable,
|
||||
domain.CountParentTypeOrganization,
|
||||
projection.OrgColumnInstanceID,
|
||||
projection.OrgColumnID,
|
||||
"organization",
|
||||
),
|
||||
|
||||
// Count triggers for all the resources
|
||||
migration.CountTrigger(db,
|
||||
projection.OrgProjectionTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.OrgColumnInstanceID,
|
||||
projection.OrgColumnInstanceID,
|
||||
"organization",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.ProjectProjectionTable,
|
||||
domain.CountParentTypeOrganization,
|
||||
projection.ProjectColumnInstanceID,
|
||||
projection.ProjectColumnResourceOwner,
|
||||
"project",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.UserTable,
|
||||
domain.CountParentTypeOrganization,
|
||||
projection.UserInstanceIDCol,
|
||||
projection.UserResourceOwnerCol,
|
||||
"user",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.InstanceMemberProjectionTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.MemberInstanceID,
|
||||
projection.MemberResourceOwner,
|
||||
"iam_admin",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.IDPTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.IDPInstanceIDCol,
|
||||
projection.IDPInstanceIDCol,
|
||||
"identity_provider",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.IDPTemplateLDAPTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.LDAPInstanceIDCol,
|
||||
projection.LDAPInstanceIDCol,
|
||||
"identity_provider_ldap",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.ActionTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.ActionInstanceIDCol,
|
||||
projection.ActionInstanceIDCol,
|
||||
"action_v1",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.ExecutionTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.ExecutionInstanceIDCol,
|
||||
projection.ExecutionInstanceIDCol,
|
||||
"execution",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
fmt.Sprintf("%s_%s", projection.ExecutionTable, projection.ExecutionTargetSuffix),
|
||||
domain.CountParentTypeInstance,
|
||||
projection.ExecutionTargetInstanceIDCol,
|
||||
projection.ExecutionTargetInstanceIDCol,
|
||||
"execution_target",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.LoginPolicyTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.LoginPolicyInstanceIDCol,
|
||||
projection.LoginPolicyInstanceIDCol,
|
||||
"login_policy",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.PasswordComplexityTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.ComplexityPolicyInstanceIDCol,
|
||||
projection.ComplexityPolicyInstanceIDCol,
|
||||
"password_complexity_policy",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.PasswordAgeTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.AgePolicyInstanceIDCol,
|
||||
projection.AgePolicyInstanceIDCol,
|
||||
"password_expiry_policy",
|
||||
),
|
||||
migration.CountTrigger(db,
|
||||
projection.LockoutPolicyTable,
|
||||
domain.CountParentTypeInstance,
|
||||
projection.LockoutPolicyInstanceIDCol,
|
||||
projection.LockoutPolicyInstanceIDCol,
|
||||
"lockout_policy",
|
||||
),
|
||||
}
|
||||
}
|
@@ -461,7 +461,7 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, user_v2beta.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(), idp.SAMLRootURL(), assets.AssetAPI(), permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, user_v2.CreateServer(commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(), idp.SAMLRootURL(), assets.AssetAPI(), permissionCheck)); err != nil {
|
||||
if err := apis.RegisterService(ctx, user_v2.CreateServer(config.SystemDefaults, commands, queries, keys.User, keys.IDPConfig, idp.CallbackURL(), idp.SAMLRootURL(), assets.AssetAPI(), permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, session_v2beta.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
@@ -470,7 +470,7 @@ func startAPIs(
|
||||
if err := apis.RegisterService(ctx, settings_v2beta.CreateServer(commands, queries)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, org_v2beta.CreateServer(commands, queries, permissionCheck)); err != nil {
|
||||
if err := apis.RegisterService(ctx, org_v2beta.CreateServer(config.SystemDefaults, commands, queries, permissionCheck)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apis.RegisterService(ctx, feature_v2beta.CreateServer(commands, queries)); err != nil {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package start
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
@@ -29,14 +31,19 @@ Requirements:
|
||||
masterKey, err := key.MasterKey(cmd)
|
||||
logging.OnError(err).Panic("No master key provided")
|
||||
|
||||
initialise.InitAll(cmd.Context(), initialise.MustNewConfig(viper.GetViper()))
|
||||
initCtx, cancel := context.WithCancel(cmd.Context())
|
||||
initialise.InitAll(initCtx, initialise.MustNewConfig(viper.GetViper()))
|
||||
cancel()
|
||||
|
||||
err = setup.BindInitProjections(cmd)
|
||||
logging.OnError(err).Fatal("unable to bind \"init-projections\" flag")
|
||||
|
||||
setupConfig := setup.MustNewConfig(viper.GetViper())
|
||||
setupSteps := setup.MustNewSteps(viper.New())
|
||||
setup.Setup(cmd.Context(), setupConfig, setupSteps, masterKey)
|
||||
|
||||
setupCtx, cancel := context.WithCancel(cmd.Context())
|
||||
setup.Setup(setupCtx, setupConfig, setupSteps, masterKey)
|
||||
cancel()
|
||||
|
||||
startConfig := MustNewConfig(viper.GetViper())
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
package start
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
@@ -34,7 +36,10 @@ Requirements:
|
||||
|
||||
setupConfig := setup.MustNewConfig(viper.GetViper())
|
||||
setupSteps := setup.MustNewSteps(viper.New())
|
||||
setup.Setup(cmd.Context(), setupConfig, setupSteps, masterKey)
|
||||
|
||||
setupCtx, cancel := context.WithCancel(cmd.Context())
|
||||
setup.Setup(setupCtx, setupConfig, setupSteps, masterKey)
|
||||
cancel()
|
||||
|
||||
startConfig := MustNewConfig(viper.GetViper())
|
||||
|
||||
|
@@ -82,6 +82,7 @@
|
||||
"jasmine-spec-reporter": "~7.0.0",
|
||||
"karma": "^6.4.4",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-coverage": "^2.2.1",
|
||||
"karma-coverage-istanbul-reporter": "^3.0.3",
|
||||
"karma-jasmine": "^5.1.0",
|
||||
"karma-jasmine-html-reporter": "^2.1.0",
|
||||
|
@@ -1,16 +1,16 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { QuickstartComponent } from './quickstart.component';
|
||||
import { OIDCConfigurationComponent } from './oidc-configuration.component';
|
||||
|
||||
describe('QuickstartComponent', () => {
|
||||
let component: QuickstartComponent;
|
||||
let fixture: ComponentFixture<QuickstartComponent>;
|
||||
let component: OIDCConfigurationComponent;
|
||||
let fixture: ComponentFixture<OIDCConfigurationComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [QuickstartComponent],
|
||||
declarations: [OIDCConfigurationComponent],
|
||||
});
|
||||
fixture = TestBed.createComponent(QuickstartComponent);
|
||||
fixture = TestBed.createComponent(OIDCConfigurationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { OrgDomainsComponent } from './org-domains.component';
|
||||
import { DomainsComponent } from './domains.component';
|
||||
|
||||
describe('OrgDomainsComponent', () => {
|
||||
let component: OrgDomainsComponent;
|
||||
let fixture: ComponentFixture<OrgDomainsComponent>;
|
||||
let component: DomainsComponent;
|
||||
let fixture: ComponentFixture<DomainsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [OrgDomainsComponent],
|
||||
declarations: [DomainsComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(OrgDomainsComponent);
|
||||
fixture = TestBed.createComponent(DomainsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FilterUserComponent } from './filter-user.component';
|
||||
import { FilterProjectComponent } from './filter-project.component';
|
||||
|
||||
describe('FilterUserComponent', () => {
|
||||
let component: FilterUserComponent;
|
||||
let fixture: ComponentFixture<FilterUserComponent>;
|
||||
let component: FilterProjectComponent;
|
||||
let fixture: ComponentFixture<FilterProjectComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [FilterUserComponent],
|
||||
declarations: [FilterProjectComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(FilterUserComponent);
|
||||
fixture = TestBed.createComponent(FilterProjectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -2,7 +2,7 @@
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'USER.PAGES.STATE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="user && user.state !== undefined"
|
||||
*ngIf="user?.state"
|
||||
class="state"
|
||||
[ngClass]="{
|
||||
active: user.state === UserState.USER_STATE_ACTIVE,
|
||||
@@ -53,7 +53,7 @@
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'IAM.PAGES.STATE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="instance && instance.state !== undefined"
|
||||
*ngIf="instance?.state"
|
||||
class="state"
|
||||
[ngClass]="{
|
||||
active: instance.state === State.INSTANCE_STATE_RUNNING,
|
||||
@@ -66,17 +66,17 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'RESOURCEID' | translate }}</p>
|
||||
<p *ngIf="instance && instance.id" class="info-row-desc">{{ instance.id }}</p>
|
||||
<p *ngIf="instance?.id" class="info-row-desc">{{ instance.id }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'NAME' | translate }}</p>
|
||||
<p *ngIf="instance && instance.name" class="info-row-desc">{{ instance.name }}</p>
|
||||
<p *ngIf="instance?.name" class="info-row-desc">{{ instance.name }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'VERSION' | translate }}</p>
|
||||
<p *ngIf="instance && instance.version" class="info-row-desc">{{ instance.version }}</p>
|
||||
<p *ngIf="instance?.version" class="info-row-desc">{{ instance.version }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper width">
|
||||
@@ -96,15 +96,15 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
|
||||
<p *ngIf="instance && instance.details && instance.details.creationDate" class="info-row-desc">
|
||||
{{ instance.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="instance?.details?.creationDate as creationDate" class="info-row-desc">
|
||||
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
|
||||
<p *ngIf="instance && instance.details && instance.details.changeDate" class="info-row-desc">
|
||||
{{ instance.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="instance?.details?.changeDate as changeDate" class="info-row-desc">
|
||||
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,7 +113,7 @@
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'ORG.PAGES.STATE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="org && org.state !== undefined"
|
||||
*ngIf="org?.state"
|
||||
class="state"
|
||||
[ngClass]="{ active: org.state === OrgState.ORG_STATE_ACTIVE, inactive: org.state === OrgState.ORG_STATE_INACTIVE }"
|
||||
>
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'RESOURCEID' | translate }}</p>
|
||||
<p *ngIf="org && org.id" class="info-row-desc">{{ org.id }}</p>
|
||||
<p *ngIf="org?.id" class="info-row-desc">{{ org.id }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper width">
|
||||
@@ -143,15 +143,15 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'ORG.PAGES.CREATIONDATE' | translate }}</p>
|
||||
<p *ngIf="org && org.details && org.details.creationDate" class="info-row-desc">
|
||||
{{ org.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="org?.details?.creationDate as creationDate" class="info-row-desc">
|
||||
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'ORG.PAGES.DATECHANGED' | translate }}</p>
|
||||
<p *ngIf="org && org.details && org.details.changeDate" class="info-row-desc">
|
||||
{{ org.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="org?.details?.changeDate as changeDate" class="info-row-desc">
|
||||
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -160,7 +160,7 @@
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="project && project.state !== undefined"
|
||||
*ngIf="project?.state"
|
||||
class="state"
|
||||
[ngClass]="{
|
||||
active: project.state === ProjectState.PROJECT_STATE_ACTIVE,
|
||||
@@ -173,20 +173,20 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'RESOURCEID' | translate }}</p>
|
||||
<p *ngIf="project && project.id" class="info-row-desc">{{ project.id }}</p>
|
||||
<p *ngIf="project?.id" class="info-row-desc">{{ project.id }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
|
||||
<p *ngIf="project && project.details && project.details.creationDate" class="info-row-desc">
|
||||
{{ project.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="project?.details?.creationDate as creationDate" class="info-row-desc">
|
||||
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
|
||||
<p *ngIf="project && project.details && project.details.changeDate" class="info-row-desc">
|
||||
{{ project.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="project?.details?.changeDate as changeDate" class="info-row-desc">
|
||||
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -195,7 +195,7 @@
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.STATE.TITLE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="grantedProject && grantedProject.state !== undefined"
|
||||
*ngIf="grantedProject?.state"
|
||||
class="state"
|
||||
[ngClass]="{
|
||||
active: grantedProject.state === ProjectGrantState.PROJECT_GRANT_STATE_ACTIVE,
|
||||
@@ -208,25 +208,25 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'RESOURCEID' | translate }}</p>
|
||||
<p *ngIf="grantedProject && grantedProject.projectId" class="info-row-desc">{{ grantedProject.projectId }}</p>
|
||||
<p *ngIf="grantedProject?.projectId" class="info-row-desc">{{ grantedProject.projectId }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.GRANT.GRANTID' | translate }}</p>
|
||||
<p *ngIf="grantedProject && grantedProject.grantId" class="info-row-desc">{{ grantedProject.grantId }}</p>
|
||||
<p *ngIf="grantedProject?.grantId" class="info-row-desc">{{ grantedProject.grantId }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.PAGES.CREATEDON' | translate }}</p>
|
||||
<p *ngIf="grantedProject && grantedProject.details && grantedProject.details.creationDate" class="info-row-desc">
|
||||
{{ grantedProject.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="grantedProject?.details?.creationDate as creationDate" class="info-row-desc">
|
||||
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'PROJECT.PAGES.LASTMODIFIED' | translate }}</p>
|
||||
<p *ngIf="grantedProject && grantedProject.details && grantedProject.details.changeDate" class="info-row-desc">
|
||||
{{ grantedProject.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="grantedProject?.details?.changeDate as changeDate" class="info-row-desc">
|
||||
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -236,30 +236,43 @@
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'APP.PAGES.STATE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="app && app.state !== undefined"
|
||||
*ngIf="app?.state"
|
||||
class="state"
|
||||
[ngClass]="{ active: app.state === AppState.APP_STATE_ACTIVE, inactive: app.state === AppState.APP_STATE_INACTIVE }"
|
||||
>
|
||||
{{ 'APP.PAGES.DETAIL.STATE.' + app.state | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="info-wrapper" *ngIf="app?.apiConfig?.authMethodType as authMethodType">
|
||||
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
|
||||
<p class="info-row-desc">
|
||||
{{ 'APP.API.AUTHMETHOD.' + authMethodType | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper" *ngIf="app?.oidcConfig?.authMethodType as authMethodType">
|
||||
<p class="info-row-title">{{ 'APP.AUTHMETHOD' | translate }}</p>
|
||||
<p class="info-row-desc">
|
||||
{{ 'APP.OIDC.AUTHMETHOD.' + authMethodType | translate }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'APP.PAGES.ID' | translate }}</p>
|
||||
<p *ngIf="app && app.id" class="info-row-desc">{{ app.id }}</p>
|
||||
<p *ngIf="app?.id" class="info-row-desc">{{ app.id }}</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'APP.PAGES.DATECREATED' | translate }}</p>
|
||||
<p *ngIf="app && app.details && app.details.creationDate" class="info-row-desc">
|
||||
{{ app.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="app?.details?.creationDate as creationDate" class="info-row-desc">
|
||||
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'APP.PAGES.DATECHANGED' | translate }}</p>
|
||||
<p *ngIf="app && app.details && app.details.changeDate" class="info-row-desc">
|
||||
{{ app.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p *ngIf="app?.details?.changeDate as changeDate" class="info-row-desc">
|
||||
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -267,27 +280,27 @@
|
||||
<p class="info-row-title">{{ 'APP.OIDC.INFO.CLIENTID' | translate }}</p>
|
||||
<div class="copy-row" *ngIf="app.oidcConfig?.clientId">
|
||||
<button
|
||||
*ngIf="app.oidcConfig && app.oidcConfig?.clientId"
|
||||
[disabled]="copied === app.oidcConfig.clientId"
|
||||
[matTooltip]="(copied !== app.oidcConfig.clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
|
||||
*ngIf="app.oidcConfig?.clientId as clientId"
|
||||
[disabled]="copied === clientId"
|
||||
[matTooltip]="(copied !== clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
|
||||
cnslCopyToClipboard
|
||||
[valueToCopy]="app.oidcConfig.clientId"
|
||||
[valueToCopy]="clientId"
|
||||
(copiedValue)="copied = $event"
|
||||
>
|
||||
{{ app.oidcConfig.clientId }}
|
||||
{{ clientId }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="copy-row" *ngIf="app.apiConfig?.clientId">
|
||||
<button
|
||||
*ngIf="app && app.apiConfig && app.apiConfig.clientId"
|
||||
[disabled]="copied === app.apiConfig.clientId"
|
||||
[matTooltip]="(copied !== app.apiConfig.clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
|
||||
*ngIf="app.apiConfig?.clientId as clientId"
|
||||
[disabled]="copied === clientId"
|
||||
[matTooltip]="(copied !== clientId ? 'ACTIONS.COPY' : 'ACTIONS.COPIED') | translate"
|
||||
cnslCopyToClipboard
|
||||
[valueToCopy]="app.apiConfig.clientId"
|
||||
[valueToCopy]="clientId"
|
||||
(copiedValue)="copied = $event"
|
||||
>
|
||||
{{ app.apiConfig.clientId }}
|
||||
{{ clientId }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -304,22 +317,22 @@
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'IDP.DETAIL.DATECREATED' | translate }}</p>
|
||||
<p class="info-row-desc" *ngIf="idp && idp.details && idp.details.creationDate">
|
||||
{{ idp.details.creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p class="info-row-desc" *ngIf="idp?.details?.creationDate as creationDate">
|
||||
{{ creationDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'IDP.DETAIL.DATECHANGED' | translate }}</p>
|
||||
<p class="info-row-desc" *ngIf="idp && idp.details && idp.details.changeDate">
|
||||
{{ idp.details.changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
<p class="info-row-desc" *ngIf="idp?.details?.changeDate as changeDate">
|
||||
{{ changeDate | timestampToDate | localizedDate: 'dd. MMMM YYYY, HH:mm' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="info-wrapper">
|
||||
<p class="info-row-title">{{ 'IDP.STATE' | translate }}</p>
|
||||
<p
|
||||
*ngIf="idp && idp.state !== undefined"
|
||||
*ngIf="idp?.state"
|
||||
class="state"
|
||||
[ngClass]="{ active: idp.state === IDPState.IDP_STATE_ACTIVE, inactive: idp.state === IDPState.IDP_STATE_INACTIVE }"
|
||||
>
|
||||
|
@@ -1,8 +1,49 @@
|
||||
import { Component, ElementRef, NgZone } from '@angular/core';
|
||||
import { TestBed, ComponentFixture } from '@angular/core/testing';
|
||||
import { InputDirective } from './input.directive';
|
||||
import { Platform } from '@angular/cdk/platform';
|
||||
import { NgControl, NgForm, FormGroupDirective } from '@angular/forms';
|
||||
import { ErrorStateMatcher } from '@angular/material/core';
|
||||
import { AutofillMonitor } from '@angular/cdk/text-field';
|
||||
import { MatFormField } from '@angular/material/form-field';
|
||||
import { MAT_INPUT_VALUE_ACCESSOR } from '@angular/material/input';
|
||||
import { of } from 'rxjs';
|
||||
import { By } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
template: `<input appInputDirective />`,
|
||||
})
|
||||
class TestHostComponent {}
|
||||
|
||||
describe('InputDirective', () => {
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [InputDirective, TestHostComponent],
|
||||
providers: [
|
||||
{ provide: ElementRef, useValue: new ElementRef(document.createElement('input')) },
|
||||
Platform,
|
||||
{ provide: NgControl, useValue: null },
|
||||
{ provide: NgForm, useValue: null },
|
||||
{ provide: FormGroupDirective, useValue: null },
|
||||
ErrorStateMatcher,
|
||||
{ provide: MAT_INPUT_VALUE_ACCESSOR, useValue: null },
|
||||
{
|
||||
provide: AutofillMonitor,
|
||||
useValue: { monitor: () => of(), stopMonitoring: () => {} },
|
||||
},
|
||||
NgZone,
|
||||
{ provide: MatFormField, useValue: null },
|
||||
],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TestHostComponent);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create an instance', () => {
|
||||
const directive = new InputDirective();
|
||||
expect(directive).toBeTruthy();
|
||||
const directiveEl = fixture.debugElement.query(By.directive(InputDirective));
|
||||
expect(directiveEl).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { AvatarComponent } from './avatar.component';
|
||||
import { LabelComponent } from './label.component';
|
||||
|
||||
describe('AvatarComponent', () => {
|
||||
let component: AvatarComponent;
|
||||
let fixture: ComponentFixture<AvatarComponent>;
|
||||
let component: LabelComponent;
|
||||
let fixture: ComponentFixture<LabelComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AvatarComponent],
|
||||
declarations: [LabelComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvatarComponent);
|
||||
fixture = TestBed.createComponent(LabelComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -4,7 +4,6 @@ import { Timestamp } from 'google-protobuf/google/protobuf/timestamp_pb';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { Metadata as MetadataV2 } from '@zitadel/proto/zitadel/metadata_pb';
|
||||
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
export type MetadataDialogData = {
|
||||
metadata: (Metadata.AsObject | MetadataV2)[];
|
||||
@@ -26,9 +25,10 @@ export class MetadataDialogComponent {
|
||||
public dialogRef: MatDialogRef<MetadataDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: MetadataDialogData,
|
||||
) {
|
||||
const decoder = new TextDecoder();
|
||||
this.metadata = data.metadata.map(({ key, value }) => ({
|
||||
key,
|
||||
value: typeof value === 'string' ? value : Buffer.from(value as unknown as string, 'base64').toString('utf8'),
|
||||
value: typeof value === 'string' ? value : decoder.decode(value),
|
||||
}));
|
||||
}
|
||||
|
||||
|
@@ -5,7 +5,6 @@ import { Observable, ReplaySubject } from 'rxjs';
|
||||
import { Metadata as MetadataV2 } from '@zitadel/proto/zitadel/metadata_pb';
|
||||
import { map, startWith } from 'rxjs/operators';
|
||||
import { Metadata } from 'src/app/proto/generated/zitadel/metadata_pb';
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
type StringMetadata = {
|
||||
key: string;
|
||||
@@ -37,12 +36,13 @@ export class MetadataComponent implements OnInit {
|
||||
|
||||
ngOnInit() {
|
||||
this.dataSource$ = this.metadata$.pipe(
|
||||
map((metadata) =>
|
||||
metadata.map(({ key, value }) => ({
|
||||
map((metadata) => {
|
||||
const decoder = new TextDecoder();
|
||||
return metadata.map(({ key, value }) => ({
|
||||
key,
|
||||
value: Buffer.from(value as any as string, 'base64').toString('utf-8'),
|
||||
})),
|
||||
),
|
||||
value: typeof value === 'string' ? value : decoder.decode(value),
|
||||
}));
|
||||
}),
|
||||
startWith([] as StringMetadata[]),
|
||||
map((metadata) => new MatTableDataSource(metadata)),
|
||||
);
|
||||
|
@@ -2,14 +2,12 @@ import { Component, Injector, Input, OnDestroy, OnInit, Type } from '@angular/co
|
||||
import { AbstractControl, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Duration } from 'google-protobuf/google/protobuf/duration_pb';
|
||||
import { firstValueFrom, forkJoin, from, Observable, of, Subject, take } from 'rxjs';
|
||||
import { forkJoin, from, of, Subject, take } from 'rxjs';
|
||||
import {
|
||||
GetLoginPolicyResponse as AdminGetLoginPolicyResponse,
|
||||
UpdateLoginPolicyRequest,
|
||||
UpdateLoginPolicyResponse,
|
||||
} from 'src/app/proto/generated/zitadel/admin_pb';
|
||||
import {
|
||||
AddCustomLoginPolicyRequest,
|
||||
GetLoginPolicyResponse as MgmtGetLoginPolicyResponse,
|
||||
UpdateCustomLoginPolicyRequest,
|
||||
} from 'src/app/proto/generated/zitadel/management_pb';
|
||||
@@ -24,8 +22,7 @@ import { InfoSectionType } from '../../info-section/info-section.component';
|
||||
import { WarnDialogComponent } from '../../warn-dialog/warn-dialog.component';
|
||||
import { PolicyComponentServiceType } from '../policy-component-types.enum';
|
||||
import { LoginMethodComponentType } from './factor-table/factor-table.component';
|
||||
import { catchError, map, takeUntil } from 'rxjs/operators';
|
||||
import { error } from 'console';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { LoginPolicyService } from '../../../services/login-policy.service';
|
||||
|
||||
const minValueValidator = (minValue: number) => (control: AbstractControl) => {
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { LoginPolicyComponent } from './login-policy.component';
|
||||
import { MessageTextsComponent } from './message-texts.component';
|
||||
|
||||
describe('LoginPolicyComponent', () => {
|
||||
let component: LoginPolicyComponent;
|
||||
let fixture: ComponentFixture<LoginPolicyComponent>;
|
||||
let component: MessageTextsComponent;
|
||||
let fixture: ComponentFixture<MessageTextsComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [LoginPolicyComponent],
|
||||
declarations: [MessageTextsComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginPolicyComponent);
|
||||
fixture = TestBed.createComponent(MessageTextsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { PasswordComplexityPolicyComponent } from './password-complexity-policy.component';
|
||||
import { NotificationPolicyComponent } from './notification-policy.component';
|
||||
|
||||
describe('PasswordComplexityPolicyComponent', () => {
|
||||
let component: PasswordComplexityPolicyComponent;
|
||||
let fixture: ComponentFixture<PasswordComplexityPolicyComponent>;
|
||||
let component: NotificationPolicyComponent;
|
||||
let fixture: ComponentFixture<NotificationPolicyComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PasswordComplexityPolicyComponent],
|
||||
declarations: [NotificationPolicyComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PasswordComplexityPolicyComponent);
|
||||
fixture = TestBed.createComponent(NotificationPolicyComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { PasswordDialogComponent } from './password-dialog-sms-provider.component';
|
||||
import { PasswordDialogSMSProviderComponent } from './password-dialog-sms-provider.component';
|
||||
|
||||
describe('PasswordDialogComponent', () => {
|
||||
let component: PasswordDialogComponent;
|
||||
let fixture: ComponentFixture<PasswordDialogComponent>;
|
||||
let component: PasswordDialogSMSProviderComponent;
|
||||
let fixture: ComponentFixture<PasswordDialogSMSProviderComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [PasswordDialogComponent],
|
||||
declarations: [PasswordDialogSMSProviderComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PasswordDialogComponent);
|
||||
fixture = TestBed.createComponent(PasswordDialogSMSProviderComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { ProviderOAuthComponent } from './provider-oauth.component';
|
||||
import { ProviderGithubESComponent } from './provider-github-es.component';
|
||||
|
||||
describe('ProviderOAuthComponent', () => {
|
||||
let component: ProviderOAuthComponent;
|
||||
let fixture: ComponentFixture<ProviderOAuthComponent>;
|
||||
let component: ProviderGithubESComponent;
|
||||
let fixture: ComponentFixture<ProviderGithubESComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProviderOAuthComponent],
|
||||
declarations: [ProviderGithubESComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProviderOAuthComponent);
|
||||
fixture = TestBed.createComponent(ProviderGithubESComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { ProviderGoogleComponent } from './provider-google.component';
|
||||
import { ProviderGitlabSelfHostedComponent } from './provider-gitlab-self-hosted.component';
|
||||
|
||||
describe('ProviderGoogleComponent', () => {
|
||||
let component: ProviderGoogleComponent;
|
||||
let fixture: ComponentFixture<ProviderGoogleComponent>;
|
||||
let component: ProviderGitlabSelfHostedComponent;
|
||||
let fixture: ComponentFixture<ProviderGitlabSelfHostedComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProviderGoogleComponent],
|
||||
declarations: [ProviderGitlabSelfHostedComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProviderGoogleComponent);
|
||||
fixture = TestBed.createComponent(ProviderGitlabSelfHostedComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { ProviderGoogleComponent } from './provider-google.component';
|
||||
import { ProviderGitlabComponent } from './provider-gitlab.component';
|
||||
|
||||
describe('ProviderGoogleComponent', () => {
|
||||
let component: ProviderGoogleComponent;
|
||||
let fixture: ComponentFixture<ProviderGoogleComponent>;
|
||||
let component: ProviderGitlabComponent;
|
||||
let fixture: ComponentFixture<ProviderGitlabComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ProviderGoogleComponent],
|
||||
declarations: [ProviderGitlabComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProviderGoogleComponent);
|
||||
fixture = TestBed.createComponent(ProviderGitlabComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { ShowKeyDialogComponent } from './show-key-dialog.component';
|
||||
import { ShowTokenDialogComponent } from './show-token-dialog.component';
|
||||
|
||||
describe('ShowKeyDialogComponent', () => {
|
||||
let component: ShowKeyDialogComponent;
|
||||
let fixture: ComponentFixture<ShowKeyDialogComponent>;
|
||||
let component: ShowTokenDialogComponent;
|
||||
let fixture: ComponentFixture<ShowTokenDialogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ShowKeyDialogComponent],
|
||||
declarations: [ShowTokenDialogComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ShowKeyDialogComponent);
|
||||
fixture = TestBed.createComponent(ShowTokenDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { IdpTableComponent } from './smtp-table.component';
|
||||
import { SMTPTableComponent } from './smtp-table.component';
|
||||
|
||||
describe('UserTableComponent', () => {
|
||||
let component: IdpTableComponent;
|
||||
let fixture: ComponentFixture<IdpTableComponent>;
|
||||
let component: SMTPTableComponent;
|
||||
let fixture: ComponentFixture<SMTPTableComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [IdpTableComponent],
|
||||
declarations: [SMTPTableComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(IdpTableComponent);
|
||||
fixture = TestBed.createComponent(SMTPTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { AddKeyDialogComponent } from './add-key-dialog.component';
|
||||
import { AddActionDialogComponent } from './add-action-dialog.component';
|
||||
|
||||
describe('AddKeyDialogComponent', () => {
|
||||
let component: AddKeyDialogComponent;
|
||||
let fixture: ComponentFixture<AddKeyDialogComponent>;
|
||||
let component: AddActionDialogComponent;
|
||||
let fixture: ComponentFixture<AddActionDialogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AddKeyDialogComponent],
|
||||
declarations: [AddActionDialogComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddKeyDialogComponent);
|
||||
fixture = TestBed.createComponent(AddActionDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { AddKeyDialogComponent } from './add-key-dialog.component';
|
||||
import { AddFlowDialogComponent } from './add-flow-dialog.component';
|
||||
|
||||
describe('AddKeyDialogComponent', () => {
|
||||
let component: AddKeyDialogComponent;
|
||||
let fixture: ComponentFixture<AddKeyDialogComponent>;
|
||||
let component: AddFlowDialogComponent;
|
||||
let fixture: ComponentFixture<AddFlowDialogComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AddKeyDialogComponent],
|
||||
declarations: [AddFlowDialogComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AddKeyDialogComponent);
|
||||
fixture = TestBed.createComponent(AddFlowDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Router } from '@angular/router';
|
||||
import { Buffer } from 'buffer';
|
||||
import { BehaviorSubject, from, Observable, of, Subject, takeUntil } from 'rxjs';
|
||||
import { catchError, finalize, map } from 'rxjs/operators';
|
||||
import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
|
||||
@@ -266,10 +265,11 @@ export class OrgDetailComponent implements OnInit, OnDestroy {
|
||||
.listOrgMetadata()
|
||||
.then((resp) => {
|
||||
this.loadingMetadata = false;
|
||||
this.metadata = resp.resultList.map((md) => {
|
||||
const decoder = new TextDecoder();
|
||||
this.metadata = resp.resultList.map(({ key, value }) => {
|
||||
return {
|
||||
key: md.key,
|
||||
value: Buffer.from(md.value as string, 'base64').toString('utf-8'),
|
||||
key,
|
||||
value: atob(typeof value === 'string' ? value : decoder.decode(value)),
|
||||
};
|
||||
});
|
||||
})
|
||||
|
@@ -32,6 +32,7 @@ import { withLatestFromSynchronousFix } from 'src/app/utils/withLatestFromSynchr
|
||||
import { PasswordComplexityValidatorFactoryService } from 'src/app/services/password-complexity-validator-factory.service';
|
||||
import { NewFeatureService } from 'src/app/services/new-feature.service';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
|
||||
type PwdForm = ReturnType<UserCreateV2Component['buildPwdForm']>;
|
||||
type AuthenticationFactor =
|
||||
@@ -65,6 +66,7 @@ export class UserCreateV2Component implements OnInit {
|
||||
private readonly destroyRef: DestroyRef,
|
||||
private readonly route: ActivatedRoute,
|
||||
protected readonly location: Location,
|
||||
private readonly authService: GrpcAuthService,
|
||||
) {
|
||||
this.userForm = this.buildUserForm();
|
||||
|
||||
@@ -180,9 +182,12 @@ export class UserCreateV2Component implements OnInit {
|
||||
private async createUserV2Try(authenticationFactor: AuthenticationFactor) {
|
||||
this.loading.set(true);
|
||||
|
||||
const org = await this.authService.getActiveOrg();
|
||||
|
||||
const userValues = this.userForm.getRawValue();
|
||||
|
||||
const humanReq: MessageInitShape<typeof AddHumanUserRequestSchema> = {
|
||||
organization: { org: { case: 'orgId', value: org.id } },
|
||||
username: userValues.username,
|
||||
profile: {
|
||||
givenName: userValues.givenName,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<h1 mat-dialog-title>
|
||||
<span class="title">{{ 'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate }} {{ data?.number }}</span>
|
||||
<span class="title">{{ 'USER.MFA.DIALOG.ADD_MFA_TITLE' | translate }}</span>
|
||||
</h1>
|
||||
<div mat-dialog-content>
|
||||
<ng-container *ngIf="selectedType === undefined">
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
<div class="type-selection">
|
||||
<button
|
||||
*ngIf="data.otp$ | async"
|
||||
mat-stroked-button
|
||||
[disabled]="data.otpDisabled$ | async"
|
||||
(click)="selectType(AuthFactorType.OTP)"
|
||||
@@ -56,7 +57,7 @@
|
||||
<span>{{ 'USER.MFA.OTP' | translate }}</span>
|
||||
</div>
|
||||
</button>
|
||||
<button mat-stroked-button (click)="selectType(AuthFactorType.U2F)">
|
||||
<button *ngIf="data.u2f$ | async" mat-stroked-button (click)="selectType(AuthFactorType.U2F)">
|
||||
<div class="u2f-btn">
|
||||
<div class="icon-row">
|
||||
<svg
|
||||
@@ -78,6 +79,7 @@
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="data.otpSms$ | async"
|
||||
[disabled]="!data.phoneVerified || (data.otpSmsDisabled$ | async)"
|
||||
mat-stroked-button
|
||||
(click)="selectType(AuthFactorType.OTPSMS)"
|
||||
@@ -110,7 +112,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button [disabled]="data.otpEmailDisabled$ | async" mat-stroked-button (click)="selectType(AuthFactorType.OTPEMAIL)">
|
||||
<button
|
||||
*ngIf="data.otpEmail$ | async"
|
||||
[disabled]="data.otpEmailDisabled$ | async"
|
||||
mat-stroked-button
|
||||
(click)="selectType(AuthFactorType.OTPEMAIL)"
|
||||
>
|
||||
<div class="otp-btn">
|
||||
<div class="icon-row">
|
||||
<svg
|
||||
|
@@ -2,6 +2,7 @@ import { Component, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
@@ -16,6 +17,17 @@ export enum AuthFactorType {
|
||||
OTPEMAIL,
|
||||
}
|
||||
|
||||
export type AddAuthFactorDialogData = {
|
||||
otp$: Observable<boolean>;
|
||||
u2f$: Observable<boolean>;
|
||||
otpSms$: Observable<boolean>;
|
||||
otpEmail$: Observable<boolean>;
|
||||
otpDisabled$: Observable<boolean>;
|
||||
otpSmsDisabled$: Observable<boolean>;
|
||||
otpEmailDisabled$: Observable<boolean>;
|
||||
phoneVerified: boolean;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-auth-factor-dialog',
|
||||
templateUrl: './auth-factor-dialog.component.html',
|
||||
@@ -44,7 +56,7 @@ export class AuthFactorDialogComponent {
|
||||
private toast: ToastService,
|
||||
private translate: TranslateService,
|
||||
public dialogRef: MatDialogRef<AuthFactorDialogComponent>,
|
||||
@Inject(MAT_DIALOG_DATA) public data: any,
|
||||
@Inject(MAT_DIALOG_DATA) public data: AddAuthFactorDialogData,
|
||||
) {}
|
||||
|
||||
closeDialog(code: string = ''): void {
|
||||
|
@@ -1,24 +1,147 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { of } from 'rxjs';
|
||||
import { AuthUserMfaComponent } from './auth-user-mfa.component';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { NewAuthService } from 'src/app/services/new-auth.service';
|
||||
import { SecondFactorType } from 'src/app/proto/generated/zitadel/policy_pb';
|
||||
import { CardComponent } from 'src/app/modules/card/card.component';
|
||||
import { RefreshTableComponent } from 'src/app/modules/refresh-table/refresh-table.component';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatTableModule } from '@angular/material/table';
|
||||
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { AuthFactor, AuthFactorState } from '@zitadel/proto/zitadel/user_pb';
|
||||
|
||||
describe('AuthUserMfaComponent', () => {
|
||||
let component: AuthUserMfaComponent;
|
||||
let fixture: ComponentFixture<AuthUserMfaComponent>;
|
||||
// Create a test host component that extends the original component
|
||||
class TestHostComponent extends AuthUserMfaComponent {
|
||||
// Expose protected properties for testing
|
||||
public getOtpEmailDisabled$() {
|
||||
return this.otpEmailDisabled$;
|
||||
}
|
||||
|
||||
public getOtpDisabled$() {
|
||||
return this.otpDisabled$;
|
||||
}
|
||||
|
||||
public getOtpSmsDisabled$() {
|
||||
return this.otpSmsDisabled$;
|
||||
}
|
||||
}
|
||||
|
||||
let component: TestHostComponent;
|
||||
let fixture: ComponentFixture<TestHostComponent>;
|
||||
let serviceStub: Partial<NewAuthService>;
|
||||
let toastStub: Partial<ToastService>;
|
||||
let dialogStub: Partial<MatDialog>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
// Create stubs for required services
|
||||
serviceStub = {
|
||||
listMyMultiFactors: jasmine.createSpy('listMyMultiFactors').and.returnValue(
|
||||
Promise.resolve({
|
||||
result: [
|
||||
{ type: { case: 'otp' }, state: AuthFactorState.READY, $typeName: 'zitadel.user.v1.AuthFactor' } as AuthFactor,
|
||||
{
|
||||
type: { case: 'otpSms' },
|
||||
state: AuthFactorState.READY,
|
||||
$typeName: 'zitadel.user.v1.AuthFactor',
|
||||
} as AuthFactor,
|
||||
{
|
||||
type: { case: 'otpEmail' },
|
||||
state: AuthFactorState.READY,
|
||||
$typeName: 'zitadel.user.v1.AuthFactor',
|
||||
} as AuthFactor,
|
||||
],
|
||||
}),
|
||||
),
|
||||
getMyLoginPolicy: jasmine.createSpy('getMyLoginPolicy').and.returnValue(
|
||||
Promise.resolve({
|
||||
policy: {
|
||||
secondFactorsList: [
|
||||
SecondFactorType.SECOND_FACTOR_TYPE_OTP,
|
||||
SecondFactorType.SECOND_FACTOR_TYPE_U2F,
|
||||
SecondFactorType.SECOND_FACTOR_TYPE_OTP_EMAIL,
|
||||
SecondFactorType.SECOND_FACTOR_TYPE_OTP_SMS,
|
||||
],
|
||||
},
|
||||
}),
|
||||
),
|
||||
removeMyMultiFactorOTP: jasmine.createSpy('removeMyMultiFactorOTP').and.returnValue(Promise.resolve()),
|
||||
removeMyMultiFactorU2F: jasmine.createSpy('removeMyMultiFactorU2F').and.returnValue(Promise.resolve()),
|
||||
removeMyAuthFactorOTPEmail: jasmine.createSpy('removeMyAuthFactorOTPEmail').and.returnValue(Promise.resolve()),
|
||||
removeMyAuthFactorOTPSMS: jasmine.createSpy('removeMyAuthFactorOTPSMS').and.returnValue(Promise.resolve()),
|
||||
};
|
||||
|
||||
toastStub = {
|
||||
showInfo: jasmine.createSpy('showInfo'),
|
||||
showError: jasmine.createSpy('showError'),
|
||||
};
|
||||
|
||||
dialogStub = {
|
||||
// Opened dialog returns a truthy value after closing
|
||||
open: jasmine.createSpy('open').and.returnValue({
|
||||
afterClosed: () => of(true),
|
||||
}),
|
||||
};
|
||||
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AuthUserMfaComponent],
|
||||
declarations: [TestHostComponent, CardComponent, RefreshTableComponent], // Use TestHostComponent instead
|
||||
imports: [MatIconModule, TranslateModule.forRoot(), MatTooltipModule, MatTableModule, BrowserAnimationsModule],
|
||||
providers: [
|
||||
{ provide: NewAuthService, useValue: serviceStub },
|
||||
{ provide: ToastService, useValue: toastStub },
|
||||
{ provide: MatDialog, useValue: dialogStub },
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AuthUserMfaComponent);
|
||||
fixture = TestBed.createComponent(TestHostComponent); // Use TestHostComponent
|
||||
component = fixture.componentInstance;
|
||||
// Optionally set the phoneVerified input if needed by your tests
|
||||
component.phoneVerified = true;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call getMFAs and update dataSource and disable flags', async () => {
|
||||
// Call the method and wait for the Promise resolution
|
||||
await component.getMFAs();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(serviceStub.listMyMultiFactors).toHaveBeenCalled();
|
||||
// Our stub returns 3 items
|
||||
expect(component.dataSource.data.length).toBe(3);
|
||||
|
||||
// Use the public getter methods to access protected properties
|
||||
component.getOtpDisabled$().subscribe((value) => {
|
||||
expect(value).toBeTrue();
|
||||
});
|
||||
component.getOtpSmsDisabled$().subscribe((value) => {
|
||||
expect(value).toBeTrue();
|
||||
});
|
||||
component.getOtpEmailDisabled$().subscribe((value) => {
|
||||
expect(value).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
it('should call deleteMFA and remove OTP factor', async () => {
|
||||
// OTP is set
|
||||
const factor = {
|
||||
type: { case: 'otp' },
|
||||
state: AuthFactorState.READY,
|
||||
$typeName: 'zitadel.user.v1.AuthFactor',
|
||||
} as AuthFactor;
|
||||
await component.deleteMFA(factor);
|
||||
|
||||
// Verify that the service method for OTP removal was called
|
||||
expect(serviceStub.removeMyMultiFactorOTP).toHaveBeenCalled();
|
||||
expect(serviceStub.listMyMultiFactors).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -4,12 +4,12 @@ import { MatSort } from '@angular/material/sort';
|
||||
import { MatTable, MatTableDataSource } from '@angular/material/table';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
import { AuthFactor, AuthFactorState } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { AuthFactorState } from 'src/app/proto/generated/zitadel/user_pb';
|
||||
import { NewAuthService } from 'src/app/services/new-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component';
|
||||
|
||||
import { AddAuthFactorDialogData, AuthFactorDialogComponent } from '../auth-factor-dialog/auth-factor-dialog.component';
|
||||
import { AuthFactor } from '@zitadel/proto/zitadel/user_pb';
|
||||
import { SecondFactorType } from '@zitadel/proto/zitadel/policy_pb';
|
||||
export interface WebAuthNOptions {
|
||||
challenge: string;
|
||||
rp: { name: string; id: string };
|
||||
@@ -30,26 +30,31 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
|
||||
@ViewChild(MatTable) public table!: MatTable<AuthFactor.AsObject>;
|
||||
@ViewChild(MatTable) public table!: MatTable<AuthFactor>;
|
||||
@ViewChild(MatSort) public sort!: MatSort;
|
||||
@Input() public phoneVerified: boolean = false;
|
||||
public dataSource: MatTableDataSource<AuthFactor.AsObject> = new MatTableDataSource<AuthFactor.AsObject>([]);
|
||||
|
||||
public AuthFactorState: any = AuthFactorState;
|
||||
public dataSource: MatTableDataSource<AuthFactor> = new MatTableDataSource<AuthFactor>([]);
|
||||
|
||||
public error: string = '';
|
||||
public otpDisabled$ = new BehaviorSubject<boolean>(true);
|
||||
public otpSmsDisabled$ = new BehaviorSubject<boolean>(true);
|
||||
public otpEmailDisabled$ = new BehaviorSubject<boolean>(true);
|
||||
protected error: string = '';
|
||||
|
||||
protected otpAvailable$ = new BehaviorSubject<boolean>(false);
|
||||
protected u2fAvailable$ = new BehaviorSubject<boolean>(false);
|
||||
protected otpSmsAvailable$ = new BehaviorSubject<boolean>(false);
|
||||
protected otpEmailAvailable$ = new BehaviorSubject<boolean>(false);
|
||||
protected otpDisabled$ = new BehaviorSubject<boolean>(true);
|
||||
protected otpSmsDisabled$ = new BehaviorSubject<boolean>(true);
|
||||
protected otpEmailDisabled$ = new BehaviorSubject<boolean>(true);
|
||||
|
||||
constructor(
|
||||
private service: GrpcAuthService,
|
||||
private toast: ToastService,
|
||||
private dialog: MatDialog,
|
||||
private readonly service: NewAuthService,
|
||||
private readonly toast: ToastService,
|
||||
private readonly dialog: MatDialog,
|
||||
) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.getMFAs();
|
||||
this.applyOrgPolicy();
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
@@ -57,13 +62,19 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public addAuthFactor(): void {
|
||||
const data: AddAuthFactorDialogData = {
|
||||
otp$: this.otpAvailable$,
|
||||
u2f$: this.u2fAvailable$,
|
||||
otpSms$: this.otpSmsAvailable$,
|
||||
otpEmail$: this.otpEmailAvailable$,
|
||||
otpDisabled$: this.otpDisabled$,
|
||||
otpSmsDisabled$: this.otpSmsDisabled$,
|
||||
otpEmailDisabled$: this.otpEmailDisabled$,
|
||||
phoneVerified: this.phoneVerified,
|
||||
} as const;
|
||||
|
||||
const dialogRef = this.dialog.open(AuthFactorDialogComponent, {
|
||||
data: {
|
||||
otpDisabled$: this.otpDisabled$,
|
||||
otpSmsDisabled$: this.otpSmsDisabled$,
|
||||
otpEmailDisabled$: this.otpEmailDisabled$,
|
||||
phoneVerified: this.phoneVerified,
|
||||
},
|
||||
data: data,
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
@@ -75,48 +86,32 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
this.service
|
||||
.listMyMultiFactors()
|
||||
.then((mfas) => {
|
||||
const list = mfas.resultList;
|
||||
const list: AuthFactor[] = mfas.result;
|
||||
this.dataSource = new MatTableDataSource(list);
|
||||
this.dataSource.sort = this.sort;
|
||||
|
||||
const index = list.findIndex((mfa) => mfa.otp);
|
||||
if (index === -1) {
|
||||
this.otpDisabled$.next(false);
|
||||
}
|
||||
|
||||
const sms = list.findIndex((mfa) => mfa.otpSms);
|
||||
if (sms === -1) {
|
||||
this.otpSmsDisabled$.next(false);
|
||||
}
|
||||
|
||||
const email = list.findIndex((mfa) => mfa.otpEmail);
|
||||
if (email === -1) {
|
||||
this.otpEmailDisabled$.next(false);
|
||||
}
|
||||
this.disableAuthFactor(list, 'otp', this.otpDisabled$);
|
||||
this.disableAuthFactor(list, 'otpSms', this.otpSmsDisabled$);
|
||||
this.disableAuthFactor(list, 'otpEmail', this.otpEmailDisabled$);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.error = error.message;
|
||||
});
|
||||
}
|
||||
|
||||
private cleanupList(): void {
|
||||
const totp = this.dataSource.data.findIndex((mfa) => !!mfa.otp);
|
||||
if (totp > -1) {
|
||||
this.dataSource.data.splice(totp, 1);
|
||||
}
|
||||
|
||||
const sms = this.dataSource.data.findIndex((mfa) => !!mfa.otpSms);
|
||||
if (sms > -1) {
|
||||
this.dataSource.data.splice(sms, 1);
|
||||
}
|
||||
|
||||
const email = this.dataSource.data.findIndex((mfa) => !!mfa.otpEmail);
|
||||
if (email > -1) {
|
||||
this.dataSource.data.splice(email, 1);
|
||||
}
|
||||
public applyOrgPolicy(): void {
|
||||
this.service.getMyLoginPolicy().then((resp) => {
|
||||
if (resp && resp.policy) {
|
||||
const secondFactors = resp.policy?.secondFactors;
|
||||
this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.OTP, this.otpAvailable$);
|
||||
this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.U2F, this.u2fAvailable$);
|
||||
this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.OTP_EMAIL, this.otpEmailAvailable$);
|
||||
this.displayAuthFactorBasedOnPolicy(secondFactors, SecondFactorType.OTP_SMS, this.otpSmsAvailable$);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMFA(factor: AuthFactor.AsObject): void {
|
||||
public deleteMFA(factor: AuthFactor): void {
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
confirmKey: 'ACTIONS.DELETE',
|
||||
@@ -129,7 +124,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
|
||||
dialogRef.afterClosed().subscribe((resp) => {
|
||||
if (resp) {
|
||||
if (factor.otp) {
|
||||
if (factor.type.case === 'otp') {
|
||||
this.service
|
||||
.removeMyMultiFactorOTP()
|
||||
.then(() => {
|
||||
@@ -141,9 +136,9 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (factor.u2f) {
|
||||
} else if (factor.type.case === 'u2f') {
|
||||
this.service
|
||||
.removeMyMultiFactorU2F(factor.u2f.id)
|
||||
.removeMyMultiFactorU2F(factor.type.value.id)
|
||||
.then(() => {
|
||||
this.toast.showInfo('USER.TOAST.U2FREMOVED', true);
|
||||
|
||||
@@ -153,7 +148,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (factor.otpEmail) {
|
||||
} else if (factor.type.case === 'otpEmail') {
|
||||
this.service
|
||||
.removeMyAuthFactorOTPEmail()
|
||||
.then(() => {
|
||||
@@ -165,7 +160,7 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
} else if (factor.otpSms) {
|
||||
} else if (factor.type.case === 'otpSms') {
|
||||
this.service
|
||||
.removeMyAuthFactorOTPSMS()
|
||||
.then(() => {
|
||||
@@ -181,4 +176,22 @@ export class AuthUserMfaComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private cleanupList(): void {
|
||||
this.dataSource.data = this.dataSource.data.filter((mfa: AuthFactor) => {
|
||||
return mfa.type.case;
|
||||
});
|
||||
}
|
||||
|
||||
private disableAuthFactor(mfas: AuthFactor[], key: string, subject: BehaviorSubject<boolean>): void {
|
||||
subject.next(mfas.some((mfa) => mfa.type.case === key));
|
||||
}
|
||||
|
||||
private displayAuthFactorBasedOnPolicy(
|
||||
factors: SecondFactorType[],
|
||||
factor: SecondFactorType,
|
||||
subject: BehaviorSubject<boolean>,
|
||||
): void {
|
||||
subject.next(factors.some((f) => f === factor));
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { WarnDialogComponent } from 'src/app/modules/warn-dialog/warn-dialog.component';
|
||||
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { NewAuthService } from 'src/app/services/new-auth.service';
|
||||
import { CodeDialogComponent } from '../auth-user-detail/code-dialog/code-dialog.component';
|
||||
import { EditDialogType } from '../auth-user-detail/edit-dialog/edit-dialog.component';
|
||||
import { HumanUser, UserState } from '@zitadel/proto/zitadel/user/v2/user_pb';
|
||||
@@ -28,12 +28,12 @@ export class ContactComponent {
|
||||
public EditDialogType: any = EditDialogType;
|
||||
constructor(
|
||||
private dialog: MatDialog,
|
||||
private authService: GrpcAuthService,
|
||||
private authService: NewAuthService,
|
||||
) {}
|
||||
|
||||
async emitDeletePhone(): Promise<void> {
|
||||
const { resultList } = await this.authService.listMyMultiFactors();
|
||||
const hasSMSOTP = !!resultList.find((mfa) => mfa.otpSms);
|
||||
const { result } = await this.authService.listMyMultiFactors();
|
||||
const hasSMSOTP = !!result.some((mfa) => mfa.type.case === 'otpSms');
|
||||
|
||||
const dialogRef = this.dialog.open(WarnDialogComponent, {
|
||||
data: {
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { DetailFormComponent } from './detail-form.component';
|
||||
import { DetailFormMachineComponent } from './detail-form-machine.component';
|
||||
|
||||
describe('DetailFormComponent', () => {
|
||||
let component: DetailFormComponent;
|
||||
let fixture: ComponentFixture<DetailFormComponent>;
|
||||
let component: DetailFormMachineComponent;
|
||||
let fixture: ComponentFixture<DetailFormMachineComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [DetailFormComponent],
|
||||
declarations: [DetailFormMachineComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DetailFormComponent);
|
||||
fixture = TestBed.createComponent(DetailFormMachineComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { AuthPasswordlessComponent } from './auth-passwordless.component';
|
||||
import { PasswordlessComponent } from './passwordless.component';
|
||||
|
||||
describe('AuthPasswordlessComponent', () => {
|
||||
let component: AuthPasswordlessComponent;
|
||||
let fixture: ComponentFixture<AuthPasswordlessComponent>;
|
||||
let component: PasswordlessComponent;
|
||||
let fixture: ComponentFixture<PasswordlessComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [AuthPasswordlessComponent],
|
||||
declarations: [PasswordlessComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AuthPasswordlessComponent);
|
||||
fixture = TestBed.createComponent(PasswordlessComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
@@ -4,7 +4,6 @@ import { Validators } from '@angular/forms';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { Buffer } from 'buffer';
|
||||
import { catchError, filter, map, startWith, take } from 'rxjs/operators';
|
||||
import { ChangeType } from 'src/app/modules/changes/changes.component';
|
||||
import { phoneValidator, requiredValidator } from 'src/app/modules/form-field/validators/validators';
|
||||
@@ -582,7 +581,7 @@ export class UserDetailComponent implements OnInit {
|
||||
const setFcn = (key: string, value: string) =>
|
||||
this.newMgmtService.setUserMetadata({
|
||||
key,
|
||||
value: Buffer.from(value),
|
||||
value: new TextEncoder().encode(value),
|
||||
id: user.userId,
|
||||
});
|
||||
const removeFcn = (key: string): Promise<any> => this.newMgmtService.removeUserMetadata({ key, id: user.userId });
|
||||
|
@@ -31,8 +31,6 @@ import {
|
||||
GetMyEmailRequest,
|
||||
GetMyEmailResponse,
|
||||
GetMyLabelPolicyRequest,
|
||||
GetMyLoginPolicyRequest,
|
||||
GetMyLoginPolicyResponse,
|
||||
GetMyPasswordComplexityPolicyRequest,
|
||||
GetMyPasswordComplexityPolicyResponse,
|
||||
GetMyPhoneRequest,
|
||||
@@ -42,8 +40,6 @@ import {
|
||||
GetMyProfileResponse,
|
||||
GetMyUserRequest,
|
||||
GetMyUserResponse,
|
||||
ListMyAuthFactorsRequest,
|
||||
ListMyAuthFactorsResponse,
|
||||
ListMyLinkedIDPsRequest,
|
||||
ListMyLinkedIDPsResponse,
|
||||
ListMyMembershipsRequest,
|
||||
@@ -62,14 +58,6 @@ import {
|
||||
ListMyUserSessionsResponse,
|
||||
ListMyZitadelPermissionsRequest,
|
||||
ListMyZitadelPermissionsResponse,
|
||||
RemoveMyAuthFactorOTPEmailRequest,
|
||||
RemoveMyAuthFactorOTPEmailResponse,
|
||||
RemoveMyAuthFactorOTPRequest,
|
||||
RemoveMyAuthFactorOTPResponse,
|
||||
RemoveMyAuthFactorOTPSMSRequest,
|
||||
RemoveMyAuthFactorOTPSMSResponse,
|
||||
RemoveMyAuthFactorU2FRequest,
|
||||
RemoveMyAuthFactorU2FResponse,
|
||||
RemoveMyAvatarRequest,
|
||||
RemoveMyAvatarResponse,
|
||||
RemoveMyLinkedIDPRequest,
|
||||
@@ -357,10 +345,6 @@ export class GrpcAuthService {
|
||||
return this.grpcService.auth.getMyUser(new GetMyUserRequest(), null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public listMyMultiFactors(): Promise<ListMyAuthFactorsResponse.AsObject> {
|
||||
return this.grpcService.auth.listMyAuthFactors(new ListMyAuthFactorsRequest(), null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public async revalidateOrgs() {
|
||||
const orgs = (await this.listMyProjectOrgs(ORG_LIMIT, 0)).resultList;
|
||||
this.cachedOrgs.next(orgs);
|
||||
@@ -488,11 +472,6 @@ export class GrpcAuthService {
|
||||
return this.grpcService.auth.resendMyEmailVerification(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public getMyLoginPolicy(): Promise<GetMyLoginPolicyResponse.AsObject> {
|
||||
const req = new GetMyLoginPolicyRequest();
|
||||
return this.grpcService.auth.getMyLoginPolicy(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMyPhone(): Promise<RemoveMyPhoneResponse.AsObject> {
|
||||
return this.grpcService.auth.removeMyPhone(new RemoveMyPhoneRequest(), null).then((resp) => resp.toObject());
|
||||
}
|
||||
@@ -576,12 +555,6 @@ export class GrpcAuthService {
|
||||
return this.grpcService.auth.addMyAuthFactorU2F(new AddMyAuthFactorU2FRequest(), null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMyMultiFactorU2F(tokenId: string): Promise<RemoveMyAuthFactorU2FResponse.AsObject> {
|
||||
const req = new RemoveMyAuthFactorU2FRequest();
|
||||
req.setTokenId(tokenId);
|
||||
return this.grpcService.auth.removeMyAuthFactorU2F(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public verifyMyMultiFactorU2F(credential: string, tokenname: string): Promise<VerifyMyAuthFactorU2FResponse.AsObject> {
|
||||
const req = new VerifyMyAuthFactorU2FRequest();
|
||||
const verification = new WebAuthNVerification();
|
||||
@@ -626,24 +599,6 @@ export class GrpcAuthService {
|
||||
return this.grpcService.auth.addMyPasswordlessLink(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMyMultiFactorOTP(): Promise<RemoveMyAuthFactorOTPResponse.AsObject> {
|
||||
return this.grpcService.auth
|
||||
.removeMyAuthFactorOTP(new RemoveMyAuthFactorOTPRequest(), null)
|
||||
.then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMyAuthFactorOTPSMS(): Promise<RemoveMyAuthFactorOTPSMSResponse.AsObject> {
|
||||
return this.grpcService.auth
|
||||
.removeMyAuthFactorOTPSMS(new RemoveMyAuthFactorOTPSMSRequest(), null)
|
||||
.then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public removeMyAuthFactorOTPEmail(): Promise<RemoveMyAuthFactorOTPEmailResponse.AsObject> {
|
||||
return this.grpcService.auth
|
||||
.removeMyAuthFactorOTPEmail(new RemoveMyAuthFactorOTPEmailRequest(), null)
|
||||
.then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public verifyMyMultiFactorOTP(code: string): Promise<VerifyMyAuthFactorOTPResponse.AsObject> {
|
||||
const req = new VerifyMyAuthFactorOTPRequest();
|
||||
req.setCode(code);
|
||||
|
@@ -1,9 +1,22 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { GrpcService } from './grpc.service';
|
||||
import { create } from '@bufbuild/protobuf';
|
||||
import {
|
||||
AddMyAuthFactorOTPSMSResponse,
|
||||
GetMyLoginPolicyResponse,
|
||||
GetMyLoginPolicyRequestSchema,
|
||||
GetMyPasswordComplexityPolicyResponse,
|
||||
GetMyUserResponse,
|
||||
ListMyAuthFactorsRequestSchema,
|
||||
ListMyAuthFactorsResponse,
|
||||
RemoveMyAuthFactorOTPEmailRequestSchema,
|
||||
RemoveMyAuthFactorOTPEmailResponse,
|
||||
RemoveMyAuthFactorOTPRequestSchema,
|
||||
RemoveMyAuthFactorOTPResponse,
|
||||
RemoveMyAuthFactorU2FRequestSchema,
|
||||
RemoveMyAuthFactorU2FResponse,
|
||||
RemoveMyAuthFactorOTPSMSRequestSchema,
|
||||
RemoveMyAuthFactorOTPSMSResponse,
|
||||
ListMyMetadataResponse,
|
||||
VerifyMyPhoneResponse,
|
||||
} from '@zitadel/proto/zitadel/auth_pb';
|
||||
@@ -30,6 +43,30 @@ export class NewAuthService {
|
||||
return this.grpcService.authNew.listMyMetadata({});
|
||||
}
|
||||
|
||||
public listMyMultiFactors(): Promise<ListMyAuthFactorsResponse> {
|
||||
return this.grpcService.authNew.listMyAuthFactors(create(ListMyAuthFactorsRequestSchema), null);
|
||||
}
|
||||
|
||||
public removeMyAuthFactorOTPSMS(): Promise<RemoveMyAuthFactorOTPSMSResponse> {
|
||||
return this.grpcService.authNew.removeMyAuthFactorOTPSMS(create(RemoveMyAuthFactorOTPSMSRequestSchema), null);
|
||||
}
|
||||
|
||||
public getMyLoginPolicy(): Promise<GetMyLoginPolicyResponse> {
|
||||
return this.grpcService.authNew.getMyLoginPolicy(create(GetMyLoginPolicyRequestSchema), null);
|
||||
}
|
||||
|
||||
public removeMyMultiFactorOTP(): Promise<RemoveMyAuthFactorOTPResponse> {
|
||||
return this.grpcService.authNew.removeMyAuthFactorOTP(create(RemoveMyAuthFactorOTPRequestSchema), null);
|
||||
}
|
||||
|
||||
public removeMyMultiFactorU2F(tokenId: string): Promise<RemoveMyAuthFactorU2FResponse> {
|
||||
return this.grpcService.authNew.removeMyAuthFactorU2F(create(RemoveMyAuthFactorU2FRequestSchema, { tokenId }), null);
|
||||
}
|
||||
|
||||
public removeMyAuthFactorOTPEmail(): Promise<RemoveMyAuthFactorOTPEmailResponse> {
|
||||
return this.grpcService.authNew.removeMyAuthFactorOTPEmail(create(RemoveMyAuthFactorOTPEmailRequestSchema), null);
|
||||
}
|
||||
|
||||
public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse> {
|
||||
return this.grpcService.authNew.getMyPasswordComplexityPolicy({});
|
||||
}
|
||||
|
@@ -6038,7 +6038,7 @@ istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756"
|
||||
integrity sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==
|
||||
|
||||
istanbul-lib-instrument@^5.0.4:
|
||||
istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
|
||||
version "5.2.1"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
|
||||
integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
|
||||
@@ -6069,7 +6069,16 @@ istanbul-lib-source-maps@^3.0.6:
|
||||
rimraf "^2.6.3"
|
||||
source-map "^0.6.1"
|
||||
|
||||
istanbul-reports@^3.0.2:
|
||||
istanbul-lib-source-maps@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
|
||||
integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
|
||||
dependencies:
|
||||
debug "^4.1.1"
|
||||
istanbul-lib-coverage "^3.0.0"
|
||||
source-map "^0.6.1"
|
||||
|
||||
istanbul-reports@^3.0.2, istanbul-reports@^3.0.5:
|
||||
version "3.1.7"
|
||||
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.7.tgz#daed12b9e1dca518e15c056e1e537e741280fa0b"
|
||||
integrity sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==
|
||||
@@ -6279,6 +6288,18 @@ karma-coverage-istanbul-reporter@^3.0.3:
|
||||
istanbul-reports "^3.0.2"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
karma-coverage@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/karma-coverage/-/karma-coverage-2.2.1.tgz#e1cc074f93ace9dc4fb7e7aeca7135879c2e358c"
|
||||
integrity sha512-yj7hbequkQP2qOSb20GuNSIyE//PgJWHwC2IydLE6XRtsnaflv+/OSGNssPjobYUlhVVagy99TQpqUt3vAUG7A==
|
||||
dependencies:
|
||||
istanbul-lib-coverage "^3.2.0"
|
||||
istanbul-lib-instrument "^5.1.0"
|
||||
istanbul-lib-report "^3.0.0"
|
||||
istanbul-lib-source-maps "^4.0.1"
|
||||
istanbul-reports "^3.0.5"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
karma-jasmine-html-reporter@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/karma-jasmine-html-reporter/-/karma-jasmine-html-reporter-2.1.0.tgz#f951ad00b08d61d03595402c914d1a589c4930e3"
|
||||
|
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [require.resolve("@docusaurus/core/lib/babel/preset")],
|
||||
compact: auto
|
||||
};
|
@@ -63,7 +63,7 @@ TODO: describe the outcome of the test?
|
||||
|
||||
## Endpoint latencies
|
||||
|
||||
import OutputSource from "!!raw-loader!./output.json";
|
||||
import OutputSource from "./output.json";
|
||||
|
||||
import { BenchmarkChart } from '/src/components/benchmark_chart';
|
||||
|
||||
|
@@ -32,7 +32,7 @@ Tests are halted after this test run because of too many [client read events](ht
|
||||
|
||||
## /token endpoint latencies
|
||||
|
||||
import OutputSource from "!!raw-loader!./output.json";
|
||||
import OutputSource from "./output.json";
|
||||
|
||||
import { BenchmarkChart } from '/src/components/benchmark_chart';
|
||||
|
||||
|
@@ -33,7 +33,7 @@ The tests showed heavy database load by time by the first two database queries.
|
||||
|
||||
## Endpoint latencies
|
||||
|
||||
import OutputSource from "!!raw-loader!./output.json";
|
||||
import OutputSource from "./output.json";
|
||||
|
||||
import { BenchmarkChart } from '/src/components/benchmark_chart';
|
||||
|
||||
|
@@ -33,7 +33,7 @@ The performance goals of [this issue](https://github.com/zitadel/zitadel/issues/
|
||||
|
||||
## Endpoint latencies
|
||||
|
||||
import OutputSource from "!!raw-loader!./output.json";
|
||||
import OutputSource from "./output.json";
|
||||
|
||||
import { BenchmarkChart } from '/src/components/benchmark_chart';
|
||||
|
||||
|
@@ -35,7 +35,7 @@ The tests showed that querying the user takes too much time because Zitadel ensu
|
||||
|
||||
## Endpoint latencies
|
||||
|
||||
import OutputSource from "!!raw-loader!./output.json";
|
||||
import OutputSource from "./output.json";
|
||||
|
||||
import { BenchmarkChart } from '/src/components/benchmark_chart';
|
||||
|
||||
|
@@ -45,7 +45,7 @@ The [OIDC Playground](https://zitadel.com/playgrounds/oidc) is for testing OpenI
|
||||
|
||||
### Custom
|
||||
|
||||
ZITADEL allows to authenticate users by creating a session with the [Session API](/docs/apis/resources/session_service_v2), get OIDC authentication request details with the [OIDC service API](/docs/apis/resources/oidc_service) or get SAML request details with the [SAML service API](/docs/apis/resources/saml_service).
|
||||
ZITADEL allows to authenticate users by creating a session with the [Session API](/docs/apis/resources/session_service_v2), get OIDC authentication request details with the [OIDC service API](/docs/apis/resources/oidc_service_v2) or get SAML request details with the [SAML service API](/docs/apis/resources/saml_service_v2).
|
||||
User authorizations can be [retrieved as roles from our APIs](/docs/guides/integrate/retrieve-user-roles).
|
||||
|
||||
Refer to our guide to learn how to [build your own login UI](/docs/guides/integrate/login-ui)
|
||||
|
@@ -147,5 +147,5 @@ Zitadel currently supports PostgreSQL.
|
||||
Make sure to read our [Production Guide](/docs/self-hosting/manage/production#prefer-postgresql) before you decide on using one of them.
|
||||
|
||||
:::info
|
||||
Zitadel v2 supported CockroachDB and PostgreSQL. Zitadel v3 only supports PostgreSQL. Please refer to [the mirror guide](cli/mirror) to migrate to PostgreSQL.
|
||||
Zitadel v2 supported CockroachDB and PostgreSQL. Zitadel v3 only supports PostgreSQL. Please refer to [the mirror guide](/docs/self-hosting/manage/cli/mirror) to migrate to PostgreSQL.
|
||||
:::
|
@@ -371,7 +371,7 @@ The API documentation to create a target can be found [here](/apis/resources/act
|
||||
To ensure the integrity of request content, each call includes a 'ZITADEL-Signature' in the headers. This header contains an HMAC value computed from the request content and a timestamp, which can be used to time out requests. The logic for this process is provided in 'pkg/actions/signing.go'. The goal is to verify that the HMAC value in the header matches the HMAC value computed by the Target, ensuring that the sent and received requests are identical.
|
||||
|
||||
Each Target resource now contains also a Signing Key, which gets generated and returned when a Target is [created](/apis/resources/action_service_v2/action-service-create-target),
|
||||
and can also be newly generated when a Target is [patched](/apis/resources/action_service_v2/action-service-patch-target).
|
||||
and can also be newly generated when a Target is [patched](/apis/resources/action_service_v2/action-service-update-target).
|
||||
|
||||
For an example on how to check the signature, [refer to the example](/guides/integrate/actions/testing-request-signature).
|
||||
|
||||
|
@@ -49,7 +49,7 @@ The **JWT IdP Configuration** might then be:
|
||||
|
||||
Therefore, if the user is redirected from ZITADEL to the JWT Endpoint on the WAF (`https://apps.test.com/existing/auth-new`),
|
||||
the session cookies previously issued by the WAF, will be sent along by the browser due to the path being on the same domain as the exiting application.
|
||||
The WAF will reuse the session and send the JWT in the HTTP header `x-custom-tkn` to its upstream, the ZITADEL JWT Endpoint (`https://accounts.test.com/ui/login/login/jwt/authorize`).
|
||||
The WAF will reuse the session and send the JWT in the HTTP header `x-custom-tkn` to its upstream, the ZITADEL JWT Endpoint (`https://accounts.test.com/ipds/jwt`).
|
||||
|
||||
For the signature validation, ZITADEL must be able to connect to Keys Endpoint (`https://issuer.test.internal/keys`)
|
||||
and it will check if the token was signed (claim `iss`) by the defined Issuer (`https://issuer.test.internal`).
|
||||
|
@@ -102,7 +102,7 @@ Present the user with the information of the device authorization request and al
|
||||
### Perform Login
|
||||
|
||||
After you have initialized the OIDC flow you can implement the login.
|
||||
Implement all the steps you like the user the go trough by [creating](/docs/apis/resources/session_service_v2/session-service-create-session) and [updating](/docs/apis/resources/session_service/session-service-set-session) the user-session.
|
||||
Implement all the steps you like the user the go trough by [creating](/docs/apis/resources/session_service_v2/session-service-create-session) and [updating](/docs/apis/resources/session_service_v2/session-service-set-session) the user-session.
|
||||
|
||||
Read the following resources for more information about the different checks:
|
||||
- [Username and Password](./username-password)
|
||||
@@ -117,7 +117,7 @@ On the create and update user session request you will always get a session toke
|
||||
|
||||
The latest session token has to be sent to the following request:
|
||||
|
||||
Read more about the [Authorize or Deny Device Authorization Request Documentation](/docs/apis/resources/oidc_service_v2/oidc-service-authorize-device-authorization)
|
||||
Read more about the [Authorize or Deny Device Authorization Request Documentation](/docs/apis/resources/oidc_service_v2/oidc-service-authorize-or-deny-device-authorization)
|
||||
|
||||
Make sure that the authorization header is from an account which is permitted to finalize the Auth Request through the `IAM_LOGIN_CLIENT` role.
|
||||
```bash
|
||||
|
@@ -80,7 +80,7 @@ Response Example:
|
||||
### Perform Login
|
||||
|
||||
After you have initialized the OIDC flow you can implement the login.
|
||||
Implement all the steps you like the user the go trough by [creating](/docs/apis/resources/session_service_v2/session-service-create-session) and [updating](/docs/apis/resources/session_service/session-service-set-session) the user-session.
|
||||
Implement all the steps you like the user the go trough by [creating](/docs/apis/resources/session_service_v2/session-service-create-session) and [updating](/docs/apis/resources/session_service_v2/session-service-set-session) the user-session.
|
||||
|
||||
Read the following resources for more information about the different checks:
|
||||
- [Username and Password](./username-password)
|
||||
|
@@ -77,7 +77,7 @@ Response Example:
|
||||
### Perform Login
|
||||
|
||||
After you have initialized the SAML flow you can implement the login.
|
||||
Implement all the steps you like the user to go trough by [creating](/docs/apis/resources/session_service_v2/session-service-create-session) and [updating](/docs/apis/resources/session_service/session-service-set-session) the user-session.
|
||||
Implement all the steps you like the user to go trough by [creating](/docs/apis/resources/session_service_v2/session-service-create-session) and [updating](/docs/apis/resources/session_service_v2/session-service-set-session) the user-session.
|
||||
|
||||
Read the following resources for more information about the different checks:
|
||||
- [Username and Password](./username-password)
|
||||
|
@@ -25,7 +25,7 @@ The identity provider is not part of the original application, but a standalone
|
||||
The user will authenticate using their credentials.
|
||||
After successful authentication, the user will be redirected back to the original application.
|
||||
|
||||
If you want to read more about authenticating with OIDC, head over to our comprehensive [OpenID Connect Guide](/docs/integrate/login/oidc).
|
||||
If you want to read more about authenticating with OIDC, head over to our comprehensive [OpenID Connect Guide](/docs/guides/integrate/login/oidc).
|
||||
|
||||
### Authenticate users with SAML
|
||||
|
||||
@@ -54,7 +54,7 @@ Note that SAML might not be suitable for mobile applications.
|
||||
In case you want to integrate a mobile application, use OpenID Connect or our Session API.
|
||||
|
||||
There are more [differences between SAML and OIDC](https://zitadel.com/blog/saml-vs-oidc) that you might want to consider.
|
||||
If you want to read more about authenticating with SAML, head over to our comprehensive [SAML Guide](/docs/integrate/login/saml).
|
||||
If you want to read more about authenticating with SAML, head over to our comprehensive [SAML Guide](/docs/guides/integrate/login/saml).
|
||||
|
||||
## ZITADEL's Session API
|
||||
|
||||
|
@@ -85,7 +85,7 @@ The same counts for [zitadel/oidc](https://github.com/zitadel/oidc) Go library.
|
||||
|
||||
## Web Key management
|
||||
|
||||
ZITADEL provides a resource based [web keys API](/docs/apis/resources/webkey_service_v3).
|
||||
ZITADEL provides a resource based [web keys API](/docs/apis/resources/webkey_service_v2).
|
||||
The API allows the creation, activation, deletion and listing of web keys.
|
||||
All public keys that are stored for an instance are served on the [JWKS endpoint](#json-web-key-set).
|
||||
Applications need public keys for token verification and not all applications are capable of on-demand
|
||||
|
@@ -17,7 +17,7 @@ When you configure your default settings, you can set the following:
|
||||
|
||||
- **Organizations**: A list of your organizations
|
||||
- [**Features**](#features): Feature Settings let you try out new features before they become generally available. You can also disable features you are not interested in.
|
||||
- [**Notification settings**](#notification-providers-and-smtp): Setup Notification and Email Server settings for initialization-, verification- and other mails. Setup Twilio as SMS notification provider.
|
||||
- [**Notification settings**](#notification-settings): Setup Notification and Email Server settings for initialization-, verification- and other mails. Setup Twilio as SMS notification provider.
|
||||
- [**Login Behavior and Access**](#login-behavior-and-access): Multifactor Authentication Options and Enforcement, Define whether Passwordless authentication methods are allowed or not, Set Login Lifetimes and advanced behavour for the login interface.
|
||||
- [**Identity Providers**](#identity-providers): Define IDPs which are available for all organizations
|
||||
- [**Password Complexity**](#password-complexity): Requirements for Passwords ex. Symbols, Numbers, min length and more.
|
||||
|
@@ -46,7 +46,7 @@ If you like to trigger your settings for your applications you have different po
|
||||
Send a [reserved scope](/apis/openidoauth/scopes) with your [authorization request](../../integrate/login/oidc/login-users#auth-request) to trigger your organization.
|
||||
The primary domain scope will restrict the login to your organization, so only users of your own organization will be able to login.
|
||||
|
||||
You can use our [OpenID Authentication Request Playground](/oidc-playground) to learn more about how to trigger an [organization's policies and branding](/oidc-playground#organization-policies-and-branding).
|
||||
You can use our [OpenID Authentication Request Playground](https://zitadel.com/playgrounds/oidc) to learn more about how to trigger an [organization's policies and branding](https://zitadel.com/playgrounds/oidc#organization-policies-and-branding).
|
||||
|
||||
### 2. Setting on your Project
|
||||
|
||||
|
1
docs/docs/product/_beta-ga.mdx
Normal file
1
docs/docs/product/_beta-ga.mdx
Normal file
@@ -0,0 +1 @@
|
||||
This describes the progression of features from a limited, pre-release testing phase (Beta) to their official, stable, and publicly available version (General Availability), ready for widespread use, with the specific transitions listed below.
|
1
docs/docs/product/_breaking-changes.mdx
Normal file
1
docs/docs/product/_breaking-changes.mdx
Normal file
@@ -0,0 +1 @@
|
||||
These are modifications to existing functionalities that may require users to alter their current implementation or usage to ensure continued compatibility; see the list below for specifics.
|
1
docs/docs/product/_deprecated.mdx
Normal file
1
docs/docs/product/_deprecated.mdx
Normal file
@@ -0,0 +1 @@
|
||||
This announces that specific existing features are being phased out and are scheduled for future removal, often because they have become outdated or are being replaced by an improved alternative; please see the deprecated items listed below.
|
1
docs/docs/product/_new-feature.mdx
Normal file
1
docs/docs/product/_new-feature.mdx
Normal file
@@ -0,0 +1 @@
|
||||
These introduce brand-new functionalities or capabilities, expanding the product's offerings and value to users, as detailed below.
|
32
docs/docs/product/_sdk_v3.mdx
Normal file
32
docs/docs/product/_sdk_v3.mdx
Normal file
@@ -0,0 +1,32 @@
|
||||
import NewFeature from './_new-feature.mdx';
|
||||
|
||||
An initial version of our Software Development Kit (SDK) will be published.
|
||||
To better align our versioning with the [ZITADEL core](#zitadel-core), the SDK will be released as version 3.x.
|
||||
This strategic versioning will ensure a more consistent and intuitive development experience across our entire ecosystem.
|
||||
|
||||
<details>
|
||||
<summary>New Features</summary>
|
||||
|
||||
<NewFeature/>
|
||||
|
||||
<details>
|
||||
<summary>Machine User Authentication Methods</summary>
|
||||
|
||||
This feature introduces robust and standardized authentication methods for your machine users, enabling secure automated access to your resources.
|
||||
|
||||
Choose from the following authentication methods:
|
||||
- **Private Key JWT Authentication**: Enhance security by using asymmetric cryptography. A client with a registered public key can generate and sign a JSON Web Token (JWT) with its private key to authenticate.
|
||||
- **Client Credentials Grant**: A simple and direct method for machine-to-machine authentication where the client confidentially provides its credentials to the authorization server in exchange for an access token.
|
||||
- **Personal Access Tokens (PATs)**: Ideal for individual developers or specific scripts, PATs offer a convenient way to create long-lived, revocable tokens with specific scopes, acting as a substitute for a password.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Zitadel APIs Wrapper</summary>
|
||||
|
||||
This SDK provides a convenient client for interacting with the ZITADEL APIs, simplifying how you manage resources within your instance.
|
||||
|
||||
Currently, the client is tailored for machine-to-machine communication, enabling machine users to authenticate and manage ZITADEL resources programmatically.
|
||||
Please note that this initial version is focused on API calls for automated tasks and does not yet include support for human user authentication flows like OAuth or OIDC.
|
||||
</details>
|
||||
</details>
|
63
docs/docs/product/release-cycle.mdx
Normal file
63
docs/docs/product/release-cycle.mdx
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Release Cycle
|
||||
sidebar_label: Release Cycle
|
||||
---
|
||||
|
||||
We release a new major version of our software every three months. This predictable schedule allows us to introduce significant features and enhancements in a structured way.
|
||||
|
||||
This cadence provides enough time for thorough development and rigorous testing. Before each stable release, we engage with our community and customers to test and stabilize the new version. This ensures high quality and reliability. For our customers, this approach creates a clear and manageable upgrade path.
|
||||
|
||||
While major changes are reserved for these three-month releases, we address urgent needs by backporting smaller updates, such as critical bug and security fixes, to earlier versions. This allows us to provide essential updates without altering the predictable rhythm of our major release cycle.
|
||||
|
||||
|
||||

|
||||
|
||||
## Preparation
|
||||
|
||||
The first quarter of our cycle is for Preparation and Planning, where we create the blueprint for the upcoming major release.
|
||||
During this time, we define the core architecture, map out the implementation strategy, and finalize the design for the new features.
|
||||
|
||||
|
||||
## Implementation
|
||||
|
||||
The second month is the Implementation and Development Phase, where our engineers build the features defined in the planning stage.
|
||||
|
||||
During this period, we focus on writing the code for the new enhancements.
|
||||
We also integrate accepted contributions from our community and create the necessary documentation alongside the development work.
|
||||
This phase concludes when the new version is feature-complete and ready to enter the testing phase.
|
||||
|
||||
## Release Candidate (RC)
|
||||
|
||||
The first month of the third quarter is for the Release Candidate (RC) and Stabilization Phase.
|
||||
At the beginning of this month, we publish a Release Candidate version.
|
||||
This is a feature-complete version that we believe is ready for public release, made available to our customers and community for widespread testing.
|
||||
|
||||
This phase is critical for ensuring the quality of the final release. We have two main objectives:
|
||||
- **Community Feedback and Bug Fixing**: This is when we rely on your feedback. By testing the RC in your own environments, you help us find and fix bugs and other issues we may have missed. Your active participation is crucial for stabilizing the new version.
|
||||
- **Enhanced Internal Testing**: While the community provides feedback, our internal teams conduct enhanced quality assurance. This includes in-depth feature validation, rigorous testing of upgrade paths from previous versions, and comprehensive performance and benchmark testing.
|
||||
|
||||
The goal of this phase is to use both community feedback and internal testing to ensure the new release is robust, bug-free, and performs well, so our customers can upgrade with confidence.
|
||||
|
||||
## General Availability (GA) / Stable
|
||||
|
||||
Following the month-long Release Candidate and Stabilization phase, we publish the official General Availability (GA) / Stable Release.
|
||||
This is the final, production-ready version of our software that has been thoroughly tested by both our internal teams and the community.
|
||||
|
||||
This release is available to everyone, and we recommend that customers begin reviewing the official upgrade path for their production environments.
|
||||
The deployment of this new major version to our cloud services also happens at this time.
|
||||
|
||||
**Ongoing Maintenance: Minor and Patch Releases**
|
||||
Once a major version becomes stable, we provide ongoing support through back-porting. This means we carefully select and apply critical updates from our main development track to the stable release, ensuring it remains secure and reliable. These updates are delivered in two ways:
|
||||
|
||||
- Minor Releases: These include simple features and enhancements from the next release cycle that are safe to add, requiring no major refactoring or large database migrations.
|
||||
- Patch Releases: These are focused exclusively on high-priority bug and security fixes to address critical issues promptly.
|
||||
|
||||
This process ensures that you can benefit from the stability of a major release while still receiving important updates and fixes in a timely manner.
|
||||
|
||||
## Deprecated
|
||||
|
||||
Each major version is actively supported for a full release cycle after its launch. This means that approximately six months after its initial stable release, a version enters its deprecation period.
|
||||
|
||||
Once a version is deprecated, we strongly encourage all self-hosted customers to upgrade to a newer version as soon as possible to continue receiving the latest features, improvements, and bug fixes.
|
||||
|
||||
For our enterprise customers, we may offer extended support by providing critical security fixes for a deprecated version beyond the standard six-month lifecycle. This extended support is evaluated on a case-by-case basis to ensure a secure and manageable transition for large-scale deployments.
|
705
docs/docs/product/roadmap.mdx
Normal file
705
docs/docs/product/roadmap.mdx
Normal file
@@ -0,0 +1,705 @@
|
||||
---
|
||||
title: Zitadel Release Versions and Roadmap
|
||||
sidebar_label: Release Versions and Roadmap
|
||||
---
|
||||
|
||||
|
||||
import NewFeature from './_new-feature.mdx';
|
||||
import BreakingChanges from './_breaking-changes.mdx';
|
||||
import Deprecated from './_deprecated.mdx';
|
||||
import BetaToGA from './_beta-ga.mdx';
|
||||
import SDKv3 from './_sdk_v3.mdx';
|
||||
|
||||
|
||||
## Timeline and Overview
|
||||
|
||||
<table id="zitadel-versions">
|
||||
<thead>
|
||||
<tr id="zitadel-versions-year">
|
||||
<th scope="col"></th>
|
||||
<th scope="col" colSpan={12}>2025</th>
|
||||
<th scope="col" colSpan={12}>2026</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col" colSpan={3}>Q1</th>
|
||||
<th scope="col" colSpan={3}>Q2</th>
|
||||
<th scope="col" colSpan={3}>Q3</th>
|
||||
<th scope="col" colSpan={3}>Q4</th>
|
||||
<th scope="col" colSpan={3}>Q1</th>
|
||||
<th scope="col" colSpan={3}>Q2</th>
|
||||
<th scope="col" colSpan={3}>Q3</th>
|
||||
<th scope="col" colSpan={3}>Q4</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">Jan</th>
|
||||
<th scope="col">Feb</th>
|
||||
<th scope="col">Mar</th>
|
||||
<th scope="col">Apr</th>
|
||||
<th scope="col">May</th>
|
||||
<th scope="col">Jun</th>
|
||||
<th scope="col">Jul</th>
|
||||
<th scope="col">Aug</th>
|
||||
<th scope="col">Sep</th>
|
||||
<th scope="col">Oct</th>
|
||||
<th scope="col">Nov</th>
|
||||
<th scope="col">Dec</th>
|
||||
<th scope="col">Jan</th>
|
||||
<th scope="col">Feb</th>
|
||||
<th scope="col">Mar</th>
|
||||
<th scope="col">Apr</th>
|
||||
<th scope="col">May</th>
|
||||
<th scope="col">Jun</th>
|
||||
<th scope="col">Jul</th>
|
||||
<th scope="col">Aug</th>
|
||||
<th scope="col">Sep</th>
|
||||
<th scope="col">Oct</th>
|
||||
<th scope="col">Nov</th>
|
||||
<th scope="col">Dec</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan={25}>Zitadel Versions</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">[v2.x](/docs/product/roadmap#v2x)</th>
|
||||
<td colspan={6} class="release-stable">GA / Stable </td>
|
||||
<td colSpan={3} class="release-deprecated">Deprecated</td>
|
||||
<td colSpan={16}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">[v3.x](/docs/product/roadmap#v3x)</th>
|
||||
<td colspan={3} class="release-impl">Implementation</td>
|
||||
<td class="release-rc">RC</td>
|
||||
<td colspan={5} class="release-stable">GA / Stable </td>
|
||||
<td colSpan={3} class="release-deprecated">Deprecated</td>
|
||||
<td colSpan={12}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">[v4.x](/docs/product/roadmap#v4x)</th>
|
||||
<td colSpan={3}></td>
|
||||
<td colSpan={3} class="release-impl">Implementation</td>
|
||||
<td class="release-rc">RC</td>
|
||||
<td colspan={5} class="release-stable">GA / Stable </td>
|
||||
<td colSpan={3} class="release-deprecated">Deprecated</td>
|
||||
<td colSpan={9}></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">[v5.x](/docs/product/roadmap#v5x)</th><td colSpan={3}></td>
|
||||
<td colSpan={3}></td>
|
||||
<td colSpan={3} class="release-impl">Implementation</td>
|
||||
<td class="release-rc">RC</td>
|
||||
<td colspan={5} class="release-stable">GA / Stable </td>
|
||||
<td colSpan={3} class="release-deprecated">Deprecated</td>
|
||||
<td colSpan={6}></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
For more detailed description about the different stages and the release cycle check out the following Page: [Release Cycle](/docs/product/release-cycle)
|
||||
|
||||
|
||||
|
||||
|
||||
<table id="zitadel-versions">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">25-Q1</th>
|
||||
<th scope="col">25-Q2</th>
|
||||
<th scope="col">25-Q3</th>
|
||||
<th scope="col">25-Q4</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td colspan={4}>Zitadel Core</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row">
|
||||
[v2.x](/docs/product/roadmap#v2x)
|
||||
</td>
|
||||
<td scope="row">
|
||||
[v3.x](/docs/product/roadmap#v3x)
|
||||
<ul>
|
||||
<li>
|
||||
Actions V2
|
||||
</li>
|
||||
<li>
|
||||
Removed CockroachDB Support
|
||||
</li>
|
||||
<li>
|
||||
License Change
|
||||
</li>
|
||||
<li>
|
||||
Login v2
|
||||
<ul>
|
||||
<li>
|
||||
Initial Release
|
||||
</li>
|
||||
<li>
|
||||
All standard authentication methods
|
||||
</li>
|
||||
<li>
|
||||
OIDC & SAML
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td scope="row">
|
||||
[v4.x](/docs/product/roadmap#v4x)
|
||||
<ul>
|
||||
<li>Resource API</li>
|
||||
<li>
|
||||
Login v2 as default
|
||||
|
||||
<ul>
|
||||
<li>
|
||||
Device Authorization Flow
|
||||
</li>
|
||||
<li>
|
||||
LDAP IDP
|
||||
</li>
|
||||
<li>
|
||||
JWT IDP
|
||||
</li>
|
||||
<li>
|
||||
Custom Login UI Texts
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
</td>
|
||||
<td scope="row">
|
||||
[v5.x](/docs/product/roadmap#v5x)
|
||||
<ul>
|
||||
<li>Analytics</li>
|
||||
<li>User Groups</li>
|
||||
<li>User Uniqueness on Instance Level</li>
|
||||
<li>Remove Required Fields from User</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colspan={4}>Zitadel SDKs</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td scope="row">
|
||||
|
||||
</td>
|
||||
<td scope="row">
|
||||
|
||||
</td>
|
||||
<td scope="row">
|
||||
<ul>
|
||||
<li>
|
||||
[Initial Version of PHP SDK](/docs/product/roadmap#v3x-1)
|
||||
</li>
|
||||
<li>
|
||||
[Initial Version of Java SDK](/docs/product/roadmap#v3x-2)
|
||||
</li>
|
||||
<li>
|
||||
[Initial Version of Ruby SDK](/docs/product/roadmap#v3x-3)
|
||||
</li>
|
||||
<li>
|
||||
[Initial Version of Python SDK](/docs/product/roadmap#v3x-4)
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td scope="row">
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
## Zitadel Core
|
||||
|
||||
Check out all [Zitadel Release Versions](https://github.com/zitadel/zitadel/releases)
|
||||
|
||||
### v2.x
|
||||
|
||||
**Current State**: General Availability / Stable
|
||||
|
||||
**Release**: [v2.x](https://github.com/zitadel/zitadel/releases?q=v2.&expanded=true)
|
||||
|
||||
In Zitadel versions 2.x and earlier, new releases were deployed with a minimum frequency of every two weeks.
|
||||
This practice resulted in a significant number of individual versions.
|
||||
To review the features and bug fixes for these releases, please consult the linked release information provided above.
|
||||
|
||||
### v3.x
|
||||
|
||||
ZITADEL v3 is here, bringing key changes designed to empower your identity management experience.
|
||||
This release transitions our licensing to AGPLv3, reinforcing our commitment to open and collaborative development.
|
||||
We've streamlined our database support by removing CockroachDB.
|
||||
Excitingly, v3 introduces the foundational elements for Actions V2, opening up a world of possibilities for tailoring and extending ZITADEL to perfectly fit your unique use cases.
|
||||
|
||||
**Current State**: General Availability / Stable
|
||||
|
||||
**Release**: [v3.x](https://github.com/zitadel/zitadel/releases?q=v3.&expanded=true)
|
||||
|
||||
**Blog**: [Zitadel v3: AGPL License, Streamlined Releases, and Platform Updates](https://zitadel.com/blog/zitadel-v3-announcement)
|
||||
|
||||
|
||||
<details>
|
||||
<summary>New Features</summary>
|
||||
|
||||
<NewFeature/>
|
||||
|
||||
<details>
|
||||
<summary>Actions V2</summary>
|
||||
|
||||
Zitadel Actions V2 empowers you to customize Zitadel's workflows by executing your own logic at specific points. You define external Endpoints containing your code and configure Targets and Executions within Zitadel to trigger them based on various conditions and events.
|
||||
|
||||
Why we built it: To provide greater flexibility and control, allowing you to implement custom business rules, automate tasks, enrich user data, control access, and integrate with other systems seamlessly. Actions V2 enables you to tailor Zitadel precisely to your unique needs.
|
||||
|
||||
Read more in our [documentation](https://zitadel.com/docs/concepts/features/actions_v2)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>License Change Apache 2.0 to AGPL3</summary>
|
||||
|
||||
Zitadel is switching to the AGPL 3.0 license to ensure the project's sustainability and encourage community contributions from commercial users, while keeping the core free and open source.
|
||||
|
||||
Read more about our [decision](https://zitadel.com/blog/apache-to-agpl)
|
||||
</details>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Breaking Changes</summary>
|
||||
|
||||
<BreakingChanges/>
|
||||
|
||||
<details>
|
||||
<summary>CockroachDB Support removed</summary>
|
||||
|
||||
After careful consideration, we have made the decision to discontinue support for CockroachDB in Zitadel v3 and beyond.
|
||||
While CockroachDB is an excellent distributed SQL database, supporting multiple database backends has increased our maintenance burden and complicated our testing matrix.
|
||||
Check out our [migration guide](https://zitadel.com/docs/self-hosting/manage/cli/mirror) to migrate from CockroachDB to PostgreSQL.
|
||||
|
||||
More details can be found [here](https://github.com/zitadel/zitadel/issues/9414)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Actions API v3 alpha removed</summary>
|
||||
|
||||
With the current release we have published the Actions V2 API as a beta version, and got rid of the previously published alpha API.
|
||||
Check out the [new API](http://localhost:3000/docs/apis/resources/action_service_v2)
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
### v4.x
|
||||
|
||||
**Current State**: Implementation
|
||||
|
||||
|
||||
<details>
|
||||
<summary>New Features</summary>
|
||||
|
||||
<NewFeature/>
|
||||
|
||||
<details>
|
||||
<summary>Resource API (v2)</summary>
|
||||
|
||||
We are revamping our APIs to improve the developer experience.
|
||||
Currently, our use-case-based APIs are complex and inconsistent, causing confusion and slowing down integration.
|
||||
To fix this, we're shifting to a resource-based approach.
|
||||
This means developers will use consistent endpoints (e.g., /users) to manage resources, regardless of their own role.
|
||||
This change, along with standardized naming and improved documentation, will simplify integration, accelerate development, and create a more intuitive experience for our customers and community.
|
||||
|
||||
Resources integrated in this release:
|
||||
- Instances
|
||||
- Organizations
|
||||
- Projects
|
||||
- Users
|
||||
|
||||
For more details read the [Github Issue](https://github.com/zitadel/zitadel/issues/6305)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Login V2</summary>
|
||||
|
||||
Our new login UI has been enhanced with additional features, bringing it to feature parity with Version 1.
|
||||
|
||||
<details>
|
||||
<summary>Device Authorization Flow</summary>
|
||||
|
||||
The Device Authorization Grant is an OAuth 2.0 flow designed for devices that have limited input capabilities (like smart TVs, gaming consoles, or IoT devices) or lack a browser.
|
||||
|
||||
Read our docs about how to integrate your application using the [Device Authorization Flow](https://zitadel.com/docs/guides/integrate/login/oidc/device-authorization)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>LDAP IDP</summary>
|
||||
|
||||
This feature enables users to log in using their existing LDAP (Lightweight Directory Access Protocol) credentials.
|
||||
It integrates your system with an LDAP directory, allowing it to act as an Identity Provider (IdP) solely for authentication purposes.
|
||||
This means users can securely access the service with their familiar LDAP username and password, streamlining the login process.
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>JWT IDP</summary>
|
||||
|
||||
This "JSON Web Token Identity Provider (JWT IdP)" feature allows you to use an existing JSON Web Token (JWT) from another system (like a Web Application Firewall managing a session) as a federated identity for authentication in new applications managed by ZITADEL.
|
||||
|
||||
Essentially, it enables session reuse by letting ZITADEL trust and validate a JWT issued by an external source. This allows users already authenticated in an existing system to seamlessly access new applications without re-logging in.
|
||||
|
||||
Read more in our docs about how to login users with [JWT IDP](https://zitadel.com/docs/guides/integrate/identity-providers/jwt_idp)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Custom Login UI Texts</summary>
|
||||
|
||||
This feature provides customers with the flexibility to personalize the user experience by customizing various text elements across different screens of the login UI. Administrators can modify default messages, labels, and instructions to align with their branding, provide specific guidance, or cater to unique regional or organizational needs, ensuring a more tailored and intuitive authentication process for their users.
|
||||
</details>
|
||||
</details>
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>General Availability</summary>
|
||||
|
||||
<BetaToGA/>
|
||||
|
||||
<details>
|
||||
<summary>Hosted Login v2</summary>
|
||||
|
||||
We're officially moving our new Login UI v2 from beta to General Availability.
|
||||
Starting now, it will be the default login experience for all new customers.
|
||||
With this release, 8.0we are also focused on implementing previously missing features, such as device authorization and LDAP IDP support, to make the new UI fully feature-complete.
|
||||
|
||||
- [Hosted Login V2](http://localhost:3000/docs/guides/integrate/login/hosted-login#hosted-login-version-2-beta)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Web Keys</summary>
|
||||
|
||||
Web Keys in ZITADEL are used to sign and verify JSON Web Tokens (JWT).
|
||||
ID tokens are created, signed and returned by ZITADEL when a OpenID connect (OIDC) or OAuth2 authorization flow completes and a user is authenticated.
|
||||
Based on customer and community feedback, we've updated our key management system. You now have full manual control over key generation and rotation, instead of the previous automatic process.
|
||||
|
||||
Read the full description about Web Keys in our [Documentation](https://zitadel.com/docs/guides/integrate/login/oidc/webkeys).
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>SCIM 2.0 Server - User Resource</summary>
|
||||
|
||||
The Zitadel SCIM v2 service provider interface enables seamless integration of identity and access management (IAM) systems with Zitadel, following the System for Cross-domain Identity Management (SCIM) v2.0 specification.
|
||||
This interface allows standardized management of IAM resources, making it easier to automate user provisioning and deprovisioning.
|
||||
|
||||
- [SCIM 2.0 API](https://zitadel.com/docs/apis/scim2)
|
||||
- [Manage Users Guide](https://zitadel.com/docs/guides/manage/user/scim2)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Caches</summary>
|
||||
|
||||
ZITADEL supports the use of a caches to speed up the lookup of frequently needed objects.
|
||||
As opposed to HTTP caches which might reside between ZITADEL and end-user applications, the cache build into ZITADEL uses active invalidation when an object gets updated.
|
||||
Another difference is that HTTP caches only cache the result of a complete request and the built-in cache stores objects needed for the internal business logic.
|
||||
For example, each request made to ZITADEL needs to retrieve and set instance information in middleware.
|
||||
|
||||
Read more about Zitadel Caches [here](https://zitadel.com/docs/self-hosting/manage/cache)
|
||||
</details>
|
||||
</details>
|
||||
|
||||
### v5.x
|
||||
|
||||
**Current State**: Planning
|
||||
|
||||
<details>
|
||||
<summary>New Features</summary>
|
||||
|
||||
<NewFeature/>
|
||||
|
||||
<details>
|
||||
<summary>Analytics</summary>
|
||||
|
||||
We provide comprehensive and insightful analytics capabilities that empower you with the information needed to understand platform usage, monitor system health, and make data-driven decisions.
|
||||
|
||||
<details>
|
||||
<summary>Daily Active Users (DAU) & Monthly Active Users (MAU)</summary>
|
||||
|
||||
Administrators need to track user activity to understand platform usage and identify trends.
|
||||
This feature provides basic metrics for daily and monthly active users, allowing for filtering by date range and scope (instance-wide or within a specific organization).
|
||||
The metrics should ensure that each user is counted only once per day or month, respectively, regardless of how many actions they performed.
|
||||
This minimal feature serves as a foundation for future expansion into more detailed analytics.
|
||||
|
||||
For more details track our [github issue](https://github.com/zitadel/zitadel/issues/7506).
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Resource Count Metrics</summary>
|
||||
|
||||
To effectively manage a Zitadel instance, administrators need to understand resource utilization.
|
||||
This feature provides metrics for resource counts, including organizations, users (with filtering options), projects, applications, and authorizations.
|
||||
For users, we will offer filters to retrieve the total count, counts per organization, and counts by user type (human or machine).
|
||||
These metrics will provide administrators with valuable insights into the scale and complexity of their Zitadel instance.
|
||||
|
||||
|
||||
For more details track our [github issue](https://github.com/zitadel/zitadel/issues/9709).
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Operational Metrics</summary>
|
||||
|
||||
To empower customers to better manage and optimize their Zitadel instances, we will provide access to detailed operational metrics.
|
||||
This data will help customers identify potential issues, optimize performance, and ensure the stability of their deployments.
|
||||
The provided data will encompass basic system information, infrastructure details, configuration settings, error reports, and the health status of various Zitadel components, accessible via a user interface or an API.
|
||||
|
||||
|
||||
For more details track our [github issue](https://github.com/zitadel/zitadel/issues/9476).
|
||||
|
||||
</details>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>User Groups</summary>
|
||||
|
||||
Administrators will be able to define groups within an organization and assign users to these groups.
|
||||
More details about the feature can be found [here](https://github.com/zitadel/zitadel/issues/9702)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>User Uniqueness on Organization Level</summary>
|
||||
|
||||
Administrators will be able to define weather users should be unique across the instance or within an organization.
|
||||
This allows managing users independently and avoids conflicts due to shared user identifiers.
|
||||
Example: The user with the username user@gmail.com can be created in the Organization "Customer A" and "Customer B" if uniqueness is defined on the organization level.
|
||||
|
||||
Stay updated on the progress and details on our [GitHub Issue](https://github.com/zitadel/zitadel/issues/9535)
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Remove Required Fields</summary>
|
||||
|
||||
Currently, the user creation process requires several fields, such as email, first name, and last name, which can be restrictive in certain scenarios. This feature allows administrators to create users with only a username, making other fields optional.
|
||||
This provides flexibility for systems that don't require complete user profiles upon initial creation for example simplified onboarding flows.
|
||||
|
||||
For more details check out our [GitHub Issue](https://github.com/zitadel/zitadel/issues/4386)
|
||||
</details>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Feature Deprecation</summary>
|
||||
|
||||
<Deprecated/>
|
||||
|
||||
<details>
|
||||
<summary>Actions V1</summary>
|
||||
</details>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Breaking Changes</summary>
|
||||
|
||||
<BreakingChanges/>
|
||||
|
||||
<details>
|
||||
<summary>Hosted Login v1 will be removed</summary>
|
||||
</details>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Zitadel APIs v1 will be removed</summary>
|
||||
</details>
|
||||
</details>
|
||||
|
||||
|
||||
### v6.x
|
||||
|
||||
<details>
|
||||
<summary>New Features</summary>
|
||||
|
||||
<NewFeature/>
|
||||
|
||||
<details>
|
||||
<summary>Basic Threat Detection Framework</summary>
|
||||
|
||||
This initial version of our Threat Detection Framework is designed to enhance the security of your account by identifying and challenging potentially anomalous user behavior.
|
||||
When the system detects unusual activity, it will present a challenge, such as a reCAPTCHA, to verify that the user is legitimate and not a bot or malicious actor.
|
||||
Security administrators will also have the ability to revoke user sessions based on the output of the threat detection model, providing a crucial tool to mitigate potential security risks in real-time.
|
||||
|
||||
We are beginning with a straightforward reCAPTCHA-style challenge to build and refine the core framework.
|
||||
This foundational step will allow us to gather insights into how the system performs and how it can be improved.
|
||||
Future iterations will build upon this groundwork to incorporate more sophisticated detection methods and a wider range of challenge and response mechanisms, ensuring an increasingly robust and intelligent security posture for all users.
|
||||
|
||||
More details can be found in the (GitHub Issue](https://github.com/zitadel/zitadel/issues/9707)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>SCIM Outbound</summary>
|
||||
|
||||
Automate user provisioning to your external applications with our new SCIM Client.
|
||||
This feature ensures users are automatically created in downstream systems before their first SSO login, preventing access issues and streamlining onboarding.
|
||||
|
||||
It also synchronizes user lifecycle events, so changes like deactivations or deletions are instantly reflected across all connected applications for consistent and secure access management.
|
||||
The initial release will focus on provisioning the user resource.
|
||||
|
||||
More details can be found in the (GitHub Issue](https://github.com/zitadel/zitadel/issues/6601)
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Analytics</summary>
|
||||
|
||||
We provide comprehensive and insightful analytics capabilities that empower you with the information needed to understand platform usage, monitor system health, and make data-driven decisions.
|
||||
|
||||
<details>
|
||||
<summary>Login Insights: Successful and Failed Login Metrics</summary>
|
||||
|
||||
To enhance security monitoring and gain insights into user authentication patterns, administrators need access to login metrics.
|
||||
This feature provides data on successful and failed login attempts, allowing for filtering by time range and level (overall instance, within a specific organization, or for a particular application).
|
||||
This will enable administrators to detect suspicious login activity, analyze authentication trends, and proactively address potential security concerns.
|
||||
|
||||
For more details track our [GitHub issue](https://github.com/zitadel/zitadel/issues/9711).
|
||||
</details>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Impersonation: External Token Exchange</summary>
|
||||
|
||||
This feature expands our existing impersonation capabilities to support seamless and secure integration with external, third-party applications.
|
||||
Currently, our platform supports impersonation for internal use cases, allowing administrators or support staff to obtain a temporary token for an end-user to troubleshoot issues or provide assistance within applications that already use ZITADEL for authentication. (You can find more details in our [existing documentation](/docs/guides/integrate/token-exchange)).
|
||||
|
||||
The next evolution of this feature will focus on external applications.
|
||||
This enables scenarios where a user, already authenticated in a third-party system (like their primary e-banking portal), can seamlessly access a connected application that is secured by ZITADEL without needing to log in again.
|
||||
|
||||
For example, a user in their e-banking app could click to open an integrated "Budget Planning" tool that relies on ZITADEL for access.
|
||||
Using a secure token exchange, the budget app will grant the user a valid session on their behalf, creating a smooth, uninterrupted user experience while maintaining a high level of security.
|
||||
This enhancement bridges the authentication gap between external platforms and ZITADEL-powered applications.
|
||||
</details>
|
||||
</details>
|
||||
|
||||
### Future Vision / Upcoming Features
|
||||
|
||||
#### Fine Grained Authorization
|
||||
|
||||
We're planning the future of Zitadel and fine-grained authorization is high on our list.
|
||||
While Zitadel already offers strong role-based access (RBAC), we know many of you need more granular control.
|
||||
|
||||
**What is Fine-Grained Authorization?**
|
||||
|
||||
It's about moving beyond broad roles to define precise access based on:
|
||||
|
||||
- Attributes (ABAC): User details (department, location), resource characteristics (sensitivity), or context (time of day).
|
||||
- Relationships (ReBAC): Connections between users and resources (e.g., "owner" of a document, "manager" of a team).
|
||||
- Policies (PBAC): Explicit rules combining attributes and relationships.
|
||||
|
||||
**Why Explore This?**
|
||||
|
||||
Fine-grained authorization can offer:
|
||||
- Tighter Security: Minimize access to only what's essential.
|
||||
- Greater Flexibility: Adapt to complex and dynamic business rules.
|
||||
- Easier Compliance: Meet strict regulatory demands.
|
||||
- Scalable Permissions: Manage access effectively as you grow.
|
||||
|
||||
**We Need Your Input!** 🗣️
|
||||
|
||||
As we explore the best way to bring this to Zitadel, tell us:
|
||||
- Your Use Cases: Where do you need more detailed access control than standard roles provide?
|
||||
- Preferred Models: Are you thinking attribute-based, relationship-based, or something else?
|
||||
- Integration Preferences:
|
||||
- A fully integrated solution within Zitadel?
|
||||
- Or integration with existing authorization vendors (e.g. openFGA, cerbos, etc.)?
|
||||
|
||||
Your feedback is crucial for shaping our roadmap.
|
||||
|
||||
🔗 Share your thoughts and needs in our [discussion forum](https://discord.com/channels/927474939156643850/1368861057669533736)
|
||||
|
||||
#### Threat Detection
|
||||
|
||||
We're taking the next step in securing your applications by exploring a new Threat Detection framework for Zitadel.
|
||||
Our goal is to proactively identify and stop malicious activity in real-time.
|
||||
|
||||
**Our First Step: A Modern reCAPTCHA Alternative**
|
||||
We will begin by building a system to detect and mitigate malicious bots, serving as a smart, privacy-focused alternative to CAPTCHA.
|
||||
This initial use case will help us combat credential stuffing, spam registrations, and other automated attacks, forming the foundation of our larger framework.
|
||||
|
||||
**How We Envision It**
|
||||
|
||||
Our exploration is focused on creating an intelligent system that:
|
||||
- **Analyzes Signals**: Gathers data points like IP reputation, device characteristics, and user behavior to spot suspicious activity.
|
||||
- **Uses AI/**: Trains models to distinguish between legitimate users and bots, reducing friction for real users.
|
||||
- **Mitigates Threats**: Enables flexible responses when a threat is detected, such as blocking the attempt, requiring MFA, or sending an alert.
|
||||
|
||||
**Help Us Shape the Future** 🤝
|
||||
|
||||
As we design this framework, we need to know:
|
||||
- What are your biggest security threats today?
|
||||
- What kind of automated responses (e.g., block, notification) would be most useful for you?
|
||||
- What are your key privacy or compliance concerns regarding threat detection?
|
||||
|
||||
Your feedback will directly influence our development and ensure we build a solution that truly meets your needs.
|
||||
|
||||
🔗 Join the conversation and share your insights [here](https://discord.com/channels/927474939156643850/1375383775164235806)
|
||||
|
||||
|
||||
#### The Role of AI in Zitadel
|
||||
|
||||
As we look to the future, we believe Artificial Intelligence will be a critical tool for enhancing both user experience and security within Zitadel.
|
||||
Our vision for AI is focused on two key areas: providing intelligent, contextual assistance and building a collective defense against emerging threats.
|
||||
|
||||
1. **AI-Powered Support**
|
||||
|
||||
We want you to get fast, accurate answers to your questions without ever having to leave your workflow.
|
||||
To achieve this, we are integrating an AI-powered support assistant trained on our knowledge base, including our documentation, tutorials, and community discussions.
|
||||
|
||||
Our rollout is planned in phases to ensure we deliver a helpful experience:
|
||||
- **Phase 1 (Happening Now)**: We are currently testing a preliminary version of our AI bot within our [community channels](https://discord.com/channels/927474939156643850/1357076488825995477). This allows us to gather real-world questions and answers, refining the AI's accuracy and helpfulness based on direct feedback.
|
||||
- **Phase 2 (Next Steps)**: Once we are confident in its capabilities, we will integrate this AI assistant directly into our documentation. You'll be able to ask complex questions and get immediate, well-sourced answers.
|
||||
- **Phase 3 (The Ultimate Goal)**: The final step is to embed the assistant directly into the Zitadel Console/Customer Portal. Imagine getting help based on the exact context of what you're doing—whether you're configuring an action, setting up a new organization, or integrating social login.
|
||||
|
||||
2. **Decentralized AI for Threat Detection**
|
||||
|
||||
Security threats are constantly evolving.
|
||||
A threat vector that targets one customer today might target another tomorrow.
|
||||
We believe in the power of collective intelligence to provide proactive security for everyone.
|
||||
|
||||
This leads to our second major AI initiative: **decentralized model training** for our Threat Detection framework.
|
||||
|
||||
Here’s how it would work:
|
||||
- **Collective Data, Anonymously**: Customers across our cloud and self-hosted environments experience different user behaviors and threat vectors. We plan to offer an opt-in system where anonymized, non-sensitive data (like behavioral patterns and threat signals) can be collected from participating instances.
|
||||
- **Centralized Training**: This collective, anonymized data will be used to train powerful, next-generation AI security models. With a much larger and more diverse dataset, these models can learn to identify subtle and emerging threats far more effectively than a model trained on a single instance's data.
|
||||
- **Shared Protection**: These constantly improving models would then be distributed to all participating Zitadel instances.
|
||||
|
||||
The result is a powerful security network effect. You could receive protection from a threat vector you haven't even experienced yet, simply because the system learned from an attack on another member of the community.
|
||||
|
||||
|
||||
|
||||
## Zitadel Ecosystem
|
||||
|
||||
### PHP SDK
|
||||
|
||||
GitHub Repository: [PHP SDK](https://github.com/zitadel/client-php)
|
||||
|
||||
#### v3.x
|
||||
|
||||
<SDKv3/>
|
||||
|
||||
### Java SDK
|
||||
|
||||
GitHub Repository: [Java SDK](https://github.com/zitadel/client-java)
|
||||
|
||||
#### v3.x
|
||||
|
||||
<SDKv3/>
|
||||
|
||||
### Ruby SDK
|
||||
|
||||
GitHub Repository: [Ruby SDK](https://github.com/zitadel/client-ruby)
|
||||
|
||||
#### v3.x
|
||||
|
||||
<SDKv3/>
|
||||
|
||||
### Python SDK
|
||||
|
||||
GitHub Repository: [Python SDK](https://github.com/zitadel/client-python)
|
||||
|
||||
#### v3.x
|
||||
|
||||
<SDKv3/>
|
@@ -4,15 +4,19 @@ title: Technical Advisory 10016
|
||||
|
||||
## Date
|
||||
|
||||
Versions:[^1]
|
||||
|
||||
- v2.65.x: > v2.65.9
|
||||
- v2.66.x > v2.66.17
|
||||
- v2.67.x > v2.67.14
|
||||
- v2.68.x > v2.68.10
|
||||
- v2.69.x > v2.69.10
|
||||
- v2.70.x > v2.70.11
|
||||
- v2.71.x > v2.71.10
|
||||
- v3.x > v3.2.1
|
||||
|
||||
|
||||
Date: 2025-05-14
|
||||
|
||||
Last updated: 2025-05-14
|
||||
|
||||
[^1]: The mentioned fix is being rolled out gradually on multiple patch releases of Zitadel. This advisory will be updated as we release these versions.
|
||||
Last updated: 2025-05-19
|
||||
|
||||
## Description
|
||||
|
||||
@@ -74,6 +78,7 @@ with
|
||||
and s.aggregate_id = b.aggregate_id
|
||||
and s.aggregate_type = b.aggregate_type
|
||||
and s.sequence = b.sequence
|
||||
returning *
|
||||
)
|
||||
select
|
||||
b.projection_name,
|
||||
@@ -82,12 +87,13 @@ select
|
||||
b.aggregate_type,
|
||||
b.sequence,
|
||||
b.old_position,
|
||||
b.new_position
|
||||
b.new_position,
|
||||
b.old_position - b.new_position difference
|
||||
from
|
||||
broken b;
|
||||
```
|
||||
|
||||
If the output from the above looks reasonable, for example not a huge difference between `old_position` and `new_position`, commit the transaction:
|
||||
If the output from the above looks reasonable, for example not a huge number in the `difference` column, commit the transaction:
|
||||
|
||||
```sql
|
||||
commit;
|
||||
|
@@ -201,28 +201,6 @@ module.exports = {
|
||||
runmeLinkLabel: 'Checkout via Runme'
|
||||
},
|
||||
},
|
||||
webpack: {
|
||||
jsLoader: (isServer) => ({
|
||||
loader: require.resolve('swc-loader'),
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'typescript',
|
||||
tsx: true,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
},
|
||||
},
|
||||
target: 'es2017',
|
||||
},
|
||||
module: {
|
||||
type: isServer ? 'commonjs' : 'es6',
|
||||
},
|
||||
},
|
||||
}),
|
||||
},
|
||||
presets: [
|
||||
[
|
||||
"classic",
|
||||
@@ -364,6 +342,14 @@ module.exports = {
|
||||
categoryLinkSource: "auto",
|
||||
},
|
||||
},
|
||||
org_v2beta: {
|
||||
specPath: ".artifacts/openapi/zitadel/org/v2beta/org_service.swagger.json",
|
||||
outputDir: "docs/apis/resources/org_service_v2beta",
|
||||
sidebarOptions: {
|
||||
groupPathsBy: "tag",
|
||||
categoryLinkSource: "auto",
|
||||
},
|
||||
},
|
||||
project_v2beta: {
|
||||
specPath: ".artifacts/openapi/zitadel/project/v2beta/project_service.swagger.json",
|
||||
outputDir: "docs/apis/resources/project_service_v2",
|
||||
@@ -397,4 +383,17 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
themes: [ "docusaurus-theme-github-codeblock", "docusaurus-theme-openapi-docs"],
|
||||
future: {
|
||||
v4: false, // Disabled because of some problems related to https://github.com/facebook/docusaurus/issues/11040
|
||||
experimental_faster: {
|
||||
swcJsLoader: false, // Disabled because of memory usage > 8GB which is a problem on vercel default runners
|
||||
swcJsMinimizer: true,
|
||||
swcHtmlMinimizer : true,
|
||||
lightningCssMinimizer: true,
|
||||
mdxCrossCompilerCache: true,
|
||||
ssgWorkerThreads: false, // Disabled because of some problems related to https://github.com/facebook/docusaurus/issues/11040
|
||||
rspackBundler: true,
|
||||
rspackPersistentCache: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"docusaurus": "docusaurus",
|
||||
"start": "docusaurus start",
|
||||
"start:api": "yarn run generate && docusaurus start",
|
||||
"build": "yarn run generate && NODE_OPTIONS=--max-old-space-size=8192 docusaurus build",
|
||||
"build": "yarn run generate && docusaurus build",
|
||||
"swizzle": "docusaurus swizzle",
|
||||
"deploy": "docusaurus deploy",
|
||||
"clear": "docusaurus clear",
|
||||
@@ -22,33 +22,27 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/buf": "^1.14.0",
|
||||
"@docusaurus/core": "3.4.0",
|
||||
"@docusaurus/preset-classic": "3.4.0",
|
||||
"@docusaurus/theme-mermaid": "3.4.0",
|
||||
"@docusaurus/theme-search-algolia": "3.4.0",
|
||||
"@docusaurus/core": "^3.8.0",
|
||||
"@docusaurus/faster": "^3.8.0",
|
||||
"@docusaurus/preset-classic": "^3.8.0",
|
||||
"@docusaurus/theme-mermaid": "^3.8.0",
|
||||
"@docusaurus/theme-search-algolia": "^3.8.0",
|
||||
"@headlessui/react": "^1.7.4",
|
||||
"@heroicons/react": "^2.0.13",
|
||||
"@mdx-js/react": "^3.0.0",
|
||||
"@swc/core": "^1.3.74",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"clsx": "^1.2.1",
|
||||
"docusaurus-plugin-image-zoom": "^1.0.1",
|
||||
"docusaurus-plugin-openapi-docs": "3.0.1",
|
||||
"docusaurus-plugin-image-zoom": "^3.0.1",
|
||||
"docusaurus-plugin-openapi-docs": "4.4.0",
|
||||
"docusaurus-theme-github-codeblock": "^2.0.2",
|
||||
"docusaurus-theme-openapi-docs": "3.0.1",
|
||||
"docusaurus-theme-openapi-docs": "4.4.0",
|
||||
"mdx-mermaid": "^2.0.0",
|
||||
"mermaid": "^10.9.1",
|
||||
"postcss": "^8.4.31",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"raw-loader": "^4.0.2",
|
||||
"react": "^18.2.0",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-google-charts": "^5.2.1",
|
||||
"react-player": "^2.15.1",
|
||||
"sitemap": "7.1.1",
|
||||
"swc-loader": "^0.2.3",
|
||||
"wait-on": "6.0.1"
|
||||
"react-player": "^2.15.1"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
@@ -63,8 +57,8 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/module-type-aliases": "3.4.0",
|
||||
"@docusaurus/types": "3.4.0",
|
||||
"@docusaurus/module-type-aliases": "^3.8.0",
|
||||
"@docusaurus/types": "^3.8.0",
|
||||
"tailwindcss": "^3.2.4"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
|
@@ -10,6 +10,7 @@ const sidebar_api_oidc_service_v2 = require("./docs/apis/resources/oidc_service_
|
||||
const sidebar_api_settings_service_v2 = require("./docs/apis/resources/settings_service_v2/sidebar.ts").default
|
||||
const sidebar_api_feature_service_v2 = require("./docs/apis/resources/feature_service_v2/sidebar.ts").default
|
||||
const sidebar_api_org_service_v2 = require("./docs/apis/resources/org_service_v2/sidebar.ts").default
|
||||
const sidebar_api_org_service_v2beta = require("./docs/apis/resources/org_service_v2beta/sidebar.ts").default
|
||||
const sidebar_api_idp_service_v2 = require("./docs/apis/resources/idp_service_v2/sidebar.ts").default
|
||||
const sidebar_api_actions_v2 = require("./docs/apis/resources/action_service_v2/sidebar.ts").default
|
||||
const sidebar_api_project_service_v2 = require("./docs/apis/resources/project_service_v2/sidebar.ts").default
|
||||
@@ -182,7 +183,6 @@ module.exports = {
|
||||
items: [
|
||||
"guides/manage/user/reg-create-user",
|
||||
"guides/manage/customize/user-metadata",
|
||||
"guides/manage/customize/user-schema",
|
||||
"guides/manage/user/scim2",
|
||||
],
|
||||
},
|
||||
@@ -610,6 +610,20 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Product Information",
|
||||
collapsed: true,
|
||||
items: [
|
||||
"product/roadmap",
|
||||
"product/release-cycle",
|
||||
{
|
||||
type: "link",
|
||||
label: "Changelog",
|
||||
href: "https://zitadel.com/changelog",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Support",
|
||||
@@ -842,35 +856,6 @@ module.exports = {
|
||||
},
|
||||
items: sidebar_api_actions_v2,
|
||||
},
|
||||
{
|
||||
type: "category",
|
||||
label: "Project (Beta)",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Project Service API (Beta)",
|
||||
slug: "/apis/resources/project_service_v2",
|
||||
description:
|
||||
"This API is intended to manage projects and subresources for ZITADEL. \n"+
|
||||
"\n" +
|
||||
"This service is in beta state. It can AND will continue breaking until a stable version is released.",
|
||||
},
|
||||
items: sidebar_api_project_service_v2,
|
||||
label: "Instance (Beta)",
|
||||
link: {
|
||||
type: "generated-index",
|
||||
title: "Instance Service API (Beta)",
|
||||
slug: "/apis/resources/instance_service_v2",
|
||||
description:
|
||||
"This API is intended to manage instances, custom domains and trusted domains in ZITADEL.\n" +
|
||||
"\n" +
|
||||
"This service is in beta state. It can AND will continue breaking until a stable version is released.\n"+
|
||||
"\n" +
|
||||
"This v2 of the API provides the same functionalities as the v1, but organised on a per resource basis.\n" +
|
||||
"The whole functionality related to domains (custom and trusted) has been moved under this instance API."
|
||||
,
|
||||
},
|
||||
items: sidebar_api_instance_service_v2,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -1,11 +1,24 @@
|
||||
import React from "react";
|
||||
import Chart from "react-google-charts";
|
||||
|
||||
export function BenchmarkChart(testResults=[], height='500px') {
|
||||
export function BenchmarkChart({ testResults = [], height = '500px' } = {}) {
|
||||
if (!Array.isArray(testResults)) {
|
||||
console.error("BenchmarkChart: testResults is not an array. Received:", testResults);
|
||||
return <p>Error: Benchmark data is not available or in the wrong format.</p>;
|
||||
}
|
||||
|
||||
if (testResults.length === 0) {
|
||||
return <p>No benchmark data to display.</p>;
|
||||
}
|
||||
|
||||
const dataPerMetric = new Map();
|
||||
let maxVValue = 0;
|
||||
|
||||
JSON.parse(testResults.testResults).forEach((result) => {
|
||||
testResults.forEach((result) => {
|
||||
if (!result || typeof result.metric_name === 'undefined') {
|
||||
console.warn("BenchmarkChart: Skipping invalid result item:", result);
|
||||
return;
|
||||
}
|
||||
if (!dataPerMetric.has(result.metric_name)) {
|
||||
dataPerMetric.set(result.metric_name, [
|
||||
[
|
||||
@@ -16,17 +29,16 @@ export function BenchmarkChart(testResults=[], height='500px') {
|
||||
],
|
||||
]);
|
||||
}
|
||||
if (result.p99 > maxVValue) {
|
||||
if (result.p99 !== undefined && result.p99 > maxVValue) {
|
||||
maxVValue = result.p99;
|
||||
}
|
||||
dataPerMetric.get(result.metric_name).push([
|
||||
new Date(result.timestamp),
|
||||
result.timestamp ? new Date(result.timestamp) : null,
|
||||
result.p50,
|
||||
result.p95,
|
||||
result.p99,
|
||||
]);
|
||||
});
|
||||
|
||||
const options = {
|
||||
legend: { position: 'bottom' },
|
||||
focusTarget: 'category',
|
||||
@@ -35,17 +47,18 @@ export function BenchmarkChart(testResults=[], height='500px') {
|
||||
},
|
||||
vAxis: {
|
||||
title: 'latency (ms)',
|
||||
maxValue: maxVValue,
|
||||
maxValue: maxVValue > 0 ? maxVValue : undefined,
|
||||
},
|
||||
title: ''
|
||||
};
|
||||
const charts = [];
|
||||
|
||||
dataPerMetric.forEach((data, metric) => {
|
||||
const opt = Object.create(options);
|
||||
const opt = { ...options };
|
||||
opt.title = metric;
|
||||
charts.push(
|
||||
<Chart
|
||||
key={metric}
|
||||
chartType="LineChart"
|
||||
width="100%"
|
||||
height={height}
|
||||
@@ -56,6 +69,9 @@ export function BenchmarkChart(testResults=[], height='500px') {
|
||||
);
|
||||
});
|
||||
|
||||
if (charts.length === 0) {
|
||||
return <p>No chart data could be generated.</p>;
|
||||
}
|
||||
|
||||
return (charts);
|
||||
return <>{charts}</>;
|
||||
}
|
@@ -641,3 +641,7 @@ p strong {
|
||||
.zitadel-lifecycle-deprecated {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
table#zitadel-versions td {
|
||||
vertical-align: top;
|
||||
}
|
BIN
docs/static/img/guides/jwt_idp.png
vendored
BIN
docs/static/img/guides/jwt_idp.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 269 KiB After Width: | Height: | Size: 38 KiB |
BIN
docs/static/img/product/release-cycle.png
vendored
Normal file
BIN
docs/static/img/product/release-cycle.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 100 KiB |
5297
docs/yarn.lock
5297
docs/yarn.lock
File diff suppressed because it is too large
Load Diff
5
go.mod
5
go.mod
@@ -7,6 +7,7 @@ toolchain go1.24.1
|
||||
require (
|
||||
cloud.google.com/go/profiler v0.4.2
|
||||
cloud.google.com/go/storage v1.54.0
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/BurntSushi/toml v1.5.0
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.27.0
|
||||
@@ -34,6 +35,7 @@ require (
|
||||
github.com/go-webauthn/webauthn v0.10.2
|
||||
github.com/goccy/go-json v0.10.5
|
||||
github.com/golang/protobuf v1.5.4
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gorilla/csrf v1.7.2
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/gorilla/schema v1.4.1
|
||||
@@ -45,6 +47,7 @@ require (
|
||||
github.com/h2non/gock v1.2.0
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/improbable-eng/grpc-web v0.15.0
|
||||
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e
|
||||
github.com/jackc/pgx/v5 v5.7.5
|
||||
github.com/jarcoal/jpath v0.0.0-20140328210829-f76b8b2dbf52
|
||||
github.com/jinzhu/gorm v1.9.16
|
||||
@@ -63,8 +66,10 @@ require (
|
||||
github.com/riverqueue/river v0.22.0
|
||||
github.com/riverqueue/river/riverdriver v0.22.0
|
||||
github.com/riverqueue/river/rivertype v0.22.0
|
||||
github.com/riverqueue/rivercontrib/otelriver v0.5.0
|
||||
github.com/rs/cors v1.11.1
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
|
||||
github.com/shopspring/decimal v1.3.1
|
||||
github.com/sony/gobreaker/v2 v2.1.0
|
||||
github.com/sony/sonyflake v1.2.1
|
||||
github.com/spf13/cobra v1.9.1
|
||||
|
8
go.sum
8
go.sum
@@ -24,6 +24,8 @@ cloud.google.com/go/storage v1.54.0 h1:Du3XEyliAiftfyW0bwfdppm2MMLdpVAfiIg4T2nAI
|
||||
cloud.google.com/go/storage v1.54.0/go.mod h1:hIi9Boe8cHxTyaeqh7KMMwKg088VblFK46C2x/BWaZE=
|
||||
cloud.google.com/go/trace v1.11.3 h1:c+I4YFjxRQjvAhRmSsmjpASUKq88chOX854ied0K/pE=
|
||||
cloud.google.com/go/trace v1.11.3/go.mod h1:pt7zCYiDSQjC9Y2oqCsh9jF4GStB/hmjrYLsxRR27q8=
|
||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
|
||||
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||
@@ -442,6 +444,8 @@ github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsI
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e h1:i3gQ/Zo7sk4LUVbsAjTNeC4gIjoPNIZVzs4EXstssV4=
|
||||
github.com/jackc/pgx-shopspring-decimal v0.0.0-20220624020537-1d36b5a1853e/go.mod h1:zUHglCZ4mpDUPgIwqEKoba6+tcUQzRdb1+DPTuYe9pI=
|
||||
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
@@ -680,6 +684,8 @@ github.com/riverqueue/river/rivershared v0.22.0 h1:hLPHr98d6OEfmUJ4KpIXgoy2tbQ14
|
||||
github.com/riverqueue/river/rivershared v0.22.0/go.mod h1:BK+hvhECfdDLWNDH3xiGI95m2YoPfVtECZLT+my8XM8=
|
||||
github.com/riverqueue/river/rivertype v0.22.0 h1:rSRhbd5uV/BaFTPxReCxuYTAzx+/riBZJlZdREADvO4=
|
||||
github.com/riverqueue/river/rivertype v0.22.0/go.mod h1:lmdl3vLNDfchDWbYdW2uAocIuwIN+ZaXqAukdSCFqWs=
|
||||
github.com/riverqueue/rivercontrib/otelriver v0.5.0 h1:dZF4Fy7/3RaIRsyCPdpIJtzEip0pCvoJ44YpSDum8e4=
|
||||
github.com/riverqueue/rivercontrib/otelriver v0.5.0/go.mod h1:rXANcBrlgRvg+auD3/O6Xfs59AWeWNpa/kim62mkxGo=
|
||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
@@ -705,6 +711,8 @@ github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 h1:lZUw3E0/J3roVtGQ+SCrUrg3ON6NgVqpn3+iol9aGu4=
|
||||
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1/go.mod h1:uToXkOrWAZ6/Oc07xWQrPOhJotwFIyu2bBVN41fcDUY=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
|
@@ -2,9 +2,11 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/admin/repository/eventsourcing/view"
|
||||
@@ -63,9 +65,17 @@ func Start(ctx context.Context) {
|
||||
func ProjectInstance(ctx context.Context) error {
|
||||
for i, projection := range projections {
|
||||
logging.WithFields("name", projection.ProjectionName(), "instance", authz.GetInstance(ctx).InstanceID(), "index", fmt.Sprintf("%d/%d", i, len(projections))).Info("starting admin projection")
|
||||
_, err := projection.Trigger(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
for {
|
||||
_, err := projection.Trigger(ctx)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
var pgErr *pgconn.PgError
|
||||
errors.As(err, &pgErr)
|
||||
if pgErr.Code != database.PgUniqueConstraintErrorCode {
|
||||
return err
|
||||
}
|
||||
logging.WithFields("name", projection.ProjectionName(), "instance", authz.GetInstance(ctx).InstanceID()).WithError(err).Debug("admin projection failed because of unique constraint, retrying")
|
||||
}
|
||||
logging.WithFields("name", projection.ProjectionName(), "instance", authz.GetInstance(ctx).InstanceID(), "index", fmt.Sprintf("%d/%d", i, len(projections))).Info("admin projection done")
|
||||
}
|
||||
|
@@ -1,10 +1,28 @@
|
||||
package authz
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
|
||||
func NewMockContext(instanceID, orgID, userID string) context.Context {
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type MockContextInstanceOpts func(i *instance)
|
||||
|
||||
func WithMockDefaultLanguage(lang language.Tag) MockContextInstanceOpts {
|
||||
return func(i *instance) {
|
||||
i.defaultLanguage = lang
|
||||
}
|
||||
}
|
||||
|
||||
func NewMockContext(instanceID, orgID, userID string, opts ...MockContextInstanceOpts) context.Context {
|
||||
ctx := context.WithValue(context.Background(), dataKey, CtxData{UserID: userID, OrgID: orgID})
|
||||
return context.WithValue(ctx, instanceKey, &instance{id: instanceID})
|
||||
|
||||
i := &instance{id: instanceID}
|
||||
for _, o := range opts {
|
||||
o(i)
|
||||
}
|
||||
|
||||
return context.WithValue(ctx, instanceKey, i)
|
||||
}
|
||||
|
||||
func NewMockContextWithAgent(instanceID, orgID, userID, agentID string) context.Context {
|
||||
|
@@ -9,9 +9,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
)
|
||||
|
||||
var (
|
||||
emptyInstance = &instance{}
|
||||
)
|
||||
var emptyInstance = &instance{}
|
||||
|
||||
type Instance interface {
|
||||
InstanceID() string
|
||||
@@ -33,13 +31,13 @@ type InstanceVerifier interface {
|
||||
}
|
||||
|
||||
type instance struct {
|
||||
id string
|
||||
domain string
|
||||
projectID string
|
||||
appID string
|
||||
clientID string
|
||||
orgID string
|
||||
features feature.Features
|
||||
id string
|
||||
projectID string
|
||||
appID string
|
||||
clientID string
|
||||
orgID string
|
||||
defaultLanguage language.Tag
|
||||
features feature.Features
|
||||
}
|
||||
|
||||
func (i *instance) Block() *bool {
|
||||
@@ -67,7 +65,7 @@ func (i *instance) ConsoleApplicationID() string {
|
||||
}
|
||||
|
||||
func (i *instance) DefaultLanguage() language.Tag {
|
||||
return language.Und
|
||||
return i.defaultLanguage
|
||||
}
|
||||
|
||||
func (i *instance) DefaultOrganisationID() string {
|
||||
@@ -106,6 +104,16 @@ func WithInstanceID(ctx context.Context, id string) context.Context {
|
||||
return context.WithValue(ctx, instanceKey, &instance{id: id})
|
||||
}
|
||||
|
||||
func WithDefaultLanguage(ctx context.Context, defaultLanguage language.Tag) context.Context {
|
||||
i, ok := ctx.Value(instanceKey).(*instance)
|
||||
if !ok {
|
||||
i = new(instance)
|
||||
}
|
||||
|
||||
i.defaultLanguage = defaultLanguage
|
||||
return context.WithValue(ctx, instanceKey, i)
|
||||
}
|
||||
|
||||
func WithConsole(ctx context.Context, projectID, appID string) context.Context {
|
||||
i, ok := ctx.Value(instanceKey).(*instance)
|
||||
if !ok {
|
||||
|
@@ -8,7 +8,9 @@ import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
authn_grpc "github.com/zitadel/zitadel/internal/api/grpc/authn"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/org"
|
||||
text_grpc "github.com/zitadel/zitadel/internal/api/grpc/text"
|
||||
user_converter "github.com/zitadel/zitadel/internal/api/grpc/user"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
@@ -65,7 +67,7 @@ func (s *Server) ExportData(ctx context.Context, req *admin_pb.ExportDataRequest
|
||||
/******************************************************************************************************************
|
||||
Organization
|
||||
******************************************************************************************************************/
|
||||
org := &admin_pb.DataOrg{OrgId: queriedOrg.ID, Org: &management_pb.AddOrgRequest{Name: queriedOrg.Name}}
|
||||
org := &admin_pb.DataOrg{OrgId: queriedOrg.ID, OrgState: org.OrgStateToPb(queriedOrg.State), Org: &management_pb.AddOrgRequest{Name: queriedOrg.Name}}
|
||||
orgs[i] = org
|
||||
}
|
||||
|
||||
@@ -567,6 +569,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
|
||||
case domain.UserTypeHuman:
|
||||
dataUser := &v1_pb.DataHumanUser{
|
||||
UserId: user.ID,
|
||||
State: user_converter.UserStateToPb(user.State),
|
||||
User: &management_pb.ImportHumanUserRequest{
|
||||
UserName: user.Username,
|
||||
Profile: &management_pb.ImportHumanUserRequest_Profile{
|
||||
@@ -620,6 +623,7 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
|
||||
case domain.UserTypeMachine:
|
||||
machineUsers = append(machineUsers, &v1_pb.DataMachineUser{
|
||||
UserId: user.ID,
|
||||
State: user_converter.UserStateToPb(user.State),
|
||||
User: &management_pb.AddMachineUserRequest{
|
||||
UserName: user.Username,
|
||||
Name: user.Machine.Name,
|
||||
@@ -647,7 +651,6 @@ func (s *Server) getUsers(ctx context.Context, org string, withPasswords bool, w
|
||||
ExpirationDate: timestamppb.New(key.Expiration),
|
||||
PublicKey: key.PublicKey,
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,7 +891,6 @@ func (s *Server) getNecessaryProjectGrantMembersForOrg(ctx context.Context, org
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -940,7 +942,6 @@ func (s *Server) getNecessaryOrgMembersForOrg(ctx context.Context, org string, p
|
||||
}
|
||||
|
||||
func (s *Server) getNecessaryProjectGrantsForOrg(ctx context.Context, org string, processedOrgs []string, processedProjects []string) ([]*v1_pb.DataProjectGrant, error) {
|
||||
|
||||
projectGrantSearchOrg, err := query.NewProjectGrantResourceOwnerSearchQuery(org)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -991,7 +992,7 @@ func (s *Server) getNecessaryUserGrantsForOrg(ctx context.Context, org string, p
|
||||
for _, userGrant := range queriedUserGrants.UserGrants {
|
||||
for _, projectID := range processedProjects {
|
||||
if projectID == userGrant.ProjectID {
|
||||
//if usergrant is on a granted project
|
||||
// if usergrant is on a granted project
|
||||
if userGrant.GrantID != "" {
|
||||
for _, grantID := range processedGrants {
|
||||
if grantID == userGrant.GrantID {
|
||||
@@ -1024,6 +1025,7 @@ func (s *Server) getNecessaryUserGrantsForOrg(ctx context.Context, org string, p
|
||||
}
|
||||
return userGrants, nil
|
||||
}
|
||||
|
||||
func (s *Server) getCustomLoginTexts(ctx context.Context, org string, languages []string) ([]*management_pb.SetCustomLoginTextsRequest, error) {
|
||||
customTexts := make([]*management_pb.SetCustomLoginTextsRequest, 0, len(languages))
|
||||
for _, lang := range languages {
|
||||
|
@@ -22,6 +22,7 @@ import (
|
||||
action_grpc "github.com/zitadel/zitadel/internal/api/grpc/action"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/authn"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/management"
|
||||
org_converter "github.com/zitadel/zitadel/internal/api/grpc/org"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@@ -305,7 +306,8 @@ func importOrg1(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataEr
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
_, err = s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), []string{})
|
||||
setOrgInactive := org_converter.OrgStateToDomain(org.OrgState) == domain.OrgStateInactive
|
||||
_, err = s.command.AddOrgWithID(ctx, org.GetOrg().GetName(), ctxData.UserID, ctxData.ResourceOwner, org.GetOrgId(), setOrgInactive, []string{})
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "org", Id: org.GetOrgId(), Message: err.Error()})
|
||||
if _, err := s.query.OrgByID(ctx, true, org.OrgId); err != nil {
|
||||
@@ -474,7 +476,10 @@ func importHumanUsers(ctx context.Context, s *Server, errors *[]*admin_pb.Import
|
||||
logging.Debugf("import user: %s", user.GetUserId())
|
||||
human, passwordless, links := management.ImportHumanUserRequestToDomain(user.User)
|
||||
human.AggregateID = user.UserId
|
||||
_, _, err := s.command.ImportHuman(ctx, org.GetOrgId(), human, passwordless, links, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode)
|
||||
userState := user.State.ToDomain()
|
||||
|
||||
//nolint:staticcheck
|
||||
_, _, err := s.command.ImportHuman(ctx, org.GetOrgId(), human, passwordless, &userState, links, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode)
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "human_user", Id: user.GetUserId(), Message: err.Error()})
|
||||
if isCtxTimeout(ctx) {
|
||||
@@ -510,7 +515,8 @@ func importMachineUsers(ctx context.Context, s *Server, errors *[]*admin_pb.Impo
|
||||
}
|
||||
for _, user := range org.GetMachineUsers() {
|
||||
logging.Debugf("import user: %s", user.GetUserId())
|
||||
_, err := s.command.AddMachine(ctx, management.AddMachineUserRequestToCommand(user.GetUser(), org.GetOrgId()))
|
||||
userState := user.State.ToDomain()
|
||||
_, err := s.command.AddMachine(ctx, management.AddMachineUserRequestToCommand(user.GetUser(), org.GetOrgId()), &userState, nil)
|
||||
if err != nil {
|
||||
*errors = append(*errors, &admin_pb.ImportDataError{Type: "machine_user", Id: user.GetUserId(), Message: err.Error()})
|
||||
if isCtxTimeout(ctx) {
|
||||
@@ -609,7 +615,6 @@ func importUserLinks(ctx context.Context, s *Server, errors *[]*admin_pb.ImportD
|
||||
successOrg.UserLinks = append(successOrg.UserLinks, &admin_pb.ImportDataSuccessUserLinks{UserId: userLinks.GetUserId(), IdpId: userLinks.GetIdpId(), ExternalUserId: userLinks.GetProvidedUserId(), DisplayName: userLinks.GetProvidedUserName()})
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func importProjects(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) (err error) {
|
||||
@@ -750,6 +755,7 @@ func importActions(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDat
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func importProjectRoles(ctx context.Context, s *Server, errors *[]*admin_pb.ImportDataError, successOrg *admin_pb.ImportDataSuccessOrg, org *admin_pb.DataOrg, count *counts) (err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
@@ -805,6 +811,7 @@ func importResources(ctx context.Context, s *Server, errors *[]*admin_pb.ImportD
|
||||
importDomainClaimedMessageTexts(ctx, s, errors, org)
|
||||
importPasswordlessRegistrationMessageTexts(ctx, s, errors, org)
|
||||
importInviteUserMessageTexts(ctx, s, errors, org)
|
||||
|
||||
if err := importHumanUsers(ctx, s, errors, successOrg, org, count, initCodeGenerator, emailCodeGenerator, phoneCodeGenerator, passwordlessInitCode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@@ -78,7 +78,7 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
human := setUpOrgHumanToCommand(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) //TODO: handle machine
|
||||
human := setUpOrgHumanToCommand(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) // TODO: handle machine
|
||||
createdOrg, err := s.command.SetUpOrg(ctx, &command.OrgSetup{
|
||||
Name: req.Org.Name,
|
||||
CustomDomain: req.Org.Domain,
|
||||
@@ -93,8 +93,8 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (*
|
||||
return nil, err
|
||||
}
|
||||
var userID string
|
||||
if len(createdOrg.CreatedAdmins) == 1 {
|
||||
userID = createdOrg.CreatedAdmins[0].ID
|
||||
if len(createdOrg.OrgAdmins) == 1 {
|
||||
userID = createdOrg.OrgAdmins[0].GetID()
|
||||
}
|
||||
return &admin_pb.SetUpOrgResponse{
|
||||
Details: object.DomainToAddDetailsPb(createdOrg.ObjectDetails),
|
||||
|
50
internal/api/grpc/filter/v2/converter.go
Normal file
50
internal/api/grpc/filter/v2/converter.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/filter/v2"
|
||||
)
|
||||
|
||||
func TimestampMethodPbToQuery(method filter.TimestampFilterMethod) query.TimestampComparison {
|
||||
switch method {
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_EQUALS:
|
||||
return query.TimestampEquals
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_BEFORE:
|
||||
return query.TimestampLess
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER:
|
||||
return query.TimestampGreater
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_BEFORE_OR_EQUALS:
|
||||
return query.TimestampLessOrEquals
|
||||
case filter.TimestampFilterMethod_TIMESTAMP_FILTER_METHOD_AFTER_OR_EQUALS:
|
||||
return query.TimestampGreaterOrEquals
|
||||
default:
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func PaginationPbToQuery(defaults systemdefaults.SystemDefaults, query *filter.PaginationRequest) (offset, limit uint64, asc bool, err error) {
|
||||
limit = defaults.DefaultQueryLimit
|
||||
if query == nil {
|
||||
return 0, limit, asc, nil
|
||||
}
|
||||
offset = query.Offset
|
||||
asc = query.Asc
|
||||
if defaults.MaxQueryLimit > 0 && uint64(query.Limit) > defaults.MaxQueryLimit {
|
||||
return 0, 0, false, zerrors.ThrowInvalidArgumentf(fmt.Errorf("given: %d, allowed: %d", query.Limit, defaults.MaxQueryLimit), "QUERY-4M0fs", "Errors.Query.LimitExceeded")
|
||||
}
|
||||
if query.Limit > 0 {
|
||||
limit = uint64(query.Limit)
|
||||
}
|
||||
return offset, limit, asc, nil
|
||||
}
|
||||
|
||||
func QueryToPaginationPb(request query.SearchRequest, response query.SearchResponse) *filter.PaginationResponse {
|
||||
return &filter.PaginationResponse{
|
||||
AppliedLimit: request.Limit,
|
||||
TotalResult: response.Count,
|
||||
}
|
||||
}
|
@@ -28,7 +28,7 @@ func InstanceToPb(instance *query.Instance) *instance_pb.Instance {
|
||||
Name: instance.Name,
|
||||
Domains: DomainsToPb(instance.Domains),
|
||||
Version: build.Version(),
|
||||
State: instance_pb.State_STATE_RUNNING, //TODO: change when delete is implemented
|
||||
State: instance_pb.State_STATE_RUNNING, // TODO: change when delete is implemented
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ func InstanceDetailToPb(instance *query.Instance) *instance_pb.InstanceDetail {
|
||||
Name: instance.Name,
|
||||
Domains: DomainsToPb(instance.Domains),
|
||||
Version: build.Version(),
|
||||
State: instance_pb.State_STATE_RUNNING, //TODO: change when delete is implemented
|
||||
State: instance_pb.State_STATE_RUNNING, // TODO: change when delete is implemented
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -9,10 +9,11 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
)
|
||||
|
||||
func TestDeleteInstace(t *testing.T) {
|
||||
|
@@ -11,12 +11,13 @@ import (
|
||||
"github.com/brianvoe/gofakeit/v6"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2beta"
|
||||
instance "github.com/zitadel/zitadel/pkg/grpc/instance/v2beta"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
func TestGetInstance(t *testing.T) {
|
||||
|
@@ -26,7 +26,7 @@ func ListOrgDomainsRequestToModel(req *mgmt_pb.ListOrgDomainsRequest) (*query.Or
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
},
|
||||
//SortingColumn: //TODO: sorting
|
||||
// SortingColumn: //TODO: sorting
|
||||
Queries: queries,
|
||||
}, nil
|
||||
}
|
||||
@@ -89,7 +89,7 @@ func ListOrgMembersRequestToModel(ctx context.Context, req *mgmt_pb.ListOrgMembe
|
||||
Offset: offset,
|
||||
Limit: limit,
|
||||
Asc: asc,
|
||||
//SortingColumn: //TODO: sorting
|
||||
// SortingColumn: //TODO: sorting
|
||||
},
|
||||
Queries: queries,
|
||||
},
|
||||
|
@@ -271,7 +271,7 @@ func (s *Server) ListAppKeys(ctx context.Context, req *mgmt_pb.ListAppKeysReques
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys, err := s.query.SearchAuthNKeys(ctx, queries, false)
|
||||
keys, err := s.query.SearchAuthNKeys(ctx, queries, query.JoinFilterApp, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -109,7 +109,7 @@ func (s *Server) UpdateProjectGrant(ctx context.Context, req *mgmt_pb.UpdateProj
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateProjectGrant(ctx context.Context, req *mgmt_pb.DeactivateProjectGrantRequest) (*mgmt_pb.DeactivateProjectGrantResponse, error) {
|
||||
details, err := s.command.DeactivateProjectGrant(ctx, req.ProjectId, req.GrantId, authz.GetCtxData(ctx).OrgID)
|
||||
details, err := s.command.DeactivateProjectGrant(ctx, req.ProjectId, req.GrantId, "", authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -119,7 +119,7 @@ func (s *Server) DeactivateProjectGrant(ctx context.Context, req *mgmt_pb.Deacti
|
||||
}
|
||||
|
||||
func (s *Server) ReactivateProjectGrant(ctx context.Context, req *mgmt_pb.ReactivateProjectGrantRequest) (*mgmt_pb.ReactivateProjectGrantResponse, error) {
|
||||
details, err := s.command.ReactivateProjectGrant(ctx, req.ProjectId, req.GrantId, authz.GetCtxData(ctx).OrgID)
|
||||
details, err := s.command.ReactivateProjectGrant(ctx, req.ProjectId, req.GrantId, "", authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@@ -273,7 +273,8 @@ func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUs
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addedHuman, code, err := s.command.ImportHuman(ctx, authz.GetCtxData(ctx).OrgID, human, passwordless, links, initCodeGenerator, phoneCodeGenerator, emailCodeGenerator, passwordlessInitCode)
|
||||
//nolint:staticcheck
|
||||
addedHuman, code, err := s.command.ImportHuman(ctx, authz.GetCtxData(ctx).OrgID, human, passwordless, nil, links, initCodeGenerator, phoneCodeGenerator, emailCodeGenerator, passwordlessInitCode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -297,7 +298,7 @@ func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUs
|
||||
|
||||
func (s *Server) AddMachineUser(ctx context.Context, req *mgmt_pb.AddMachineUserRequest) (*mgmt_pb.AddMachineUserResponse, error) {
|
||||
machine := AddMachineUserRequestToCommand(req, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.AddMachine(ctx, machine)
|
||||
objectDetails, err := s.command.AddMachine(ctx, machine, nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -752,11 +753,11 @@ func (s *Server) GetMachineKeyByIDs(ctx context.Context, req *mgmt_pb.GetMachine
|
||||
}
|
||||
|
||||
func (s *Server) ListMachineKeys(ctx context.Context, req *mgmt_pb.ListMachineKeysRequest) (*mgmt_pb.ListMachineKeysResponse, error) {
|
||||
query, err := ListMachineKeysRequestToQuery(ctx, req)
|
||||
q, err := ListMachineKeysRequestToQuery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := s.query.SearchAuthNKeys(ctx, query, false)
|
||||
result, err := s.query.SearchAuthNKeys(ctx, q, query.JoinFilterUserMachine, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -774,7 +775,6 @@ func (s *Server) AddMachineKey(ctx context.Context, req *mgmt_pb.AddMachineKeyRe
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return key details only if the pubkey wasn't supplied, otherwise the user already has
|
||||
// private key locally
|
||||
var keyDetails []byte
|
||||
@@ -821,7 +821,7 @@ func (s *Server) GenerateMachineSecret(ctx context.Context, req *mgmt_pb.Generat
|
||||
}
|
||||
|
||||
func (s *Server) RemoveMachineSecret(ctx context.Context, req *mgmt_pb.RemoveMachineSecretRequest) (*mgmt_pb.RemoveMachineSecretResponse, error) {
|
||||
objectDetails, err := s.command.RemoveMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID)
|
||||
objectDetails, err := s.command.RemoveMachineSecret(ctx, req.UserId, authz.GetCtxData(ctx).OrgID, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -839,7 +839,7 @@ func (s *Server) GetPersonalAccessTokenByIDs(ctx context.Context, req *mgmt_pb.G
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := s.query.PersonalAccessTokenByID(ctx, true, req.TokenId, false, resourceOwner, aggregateID)
|
||||
token, err := s.query.PersonalAccessTokenByID(ctx, true, req.TokenId, resourceOwner, aggregateID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -853,7 +853,7 @@ func (s *Server) ListPersonalAccessTokens(ctx context.Context, req *mgmt_pb.List
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result, err := s.query.SearchPersonalAccessTokens(ctx, queries, false)
|
||||
result, err := s.query.SearchPersonalAccessTokens(ctx, queries, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -901,6 +901,7 @@ func (s *Server) ListHumanLinkedIDPs(ctx context.Context, req *mgmt_pb.ListHuman
|
||||
Details: obj_grpc.ToListDetails(res.Count, res.Sequence, res.LastRun),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) RemoveHumanLinkedIDP(ctx context.Context, req *mgmt_pb.RemoveHumanLinkedIDPRequest) (*mgmt_pb.RemoveHumanLinkedIDPResponse, error) {
|
||||
objectDetails, err := s.command.RemoveUserIDPLink(ctx, RemoveHumanLinkedIDPRequestToDomain(ctx, req))
|
||||
if err != nil {
|
||||
@@ -947,18 +948,21 @@ func cascadingIAMMembership(membership *query.IAMMembership) *command.CascadingI
|
||||
}
|
||||
return &command.CascadingIAMMembership{IAMID: membership.IAMID}
|
||||
}
|
||||
|
||||
func cascadingOrgMembership(membership *query.OrgMembership) *command.CascadingOrgMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.CascadingOrgMembership{OrgID: membership.OrgID}
|
||||
}
|
||||
|
||||
func cascadingProjectMembership(membership *query.ProjectMembership) *command.CascadingProjectMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
}
|
||||
return &command.CascadingProjectMembership{ProjectID: membership.ProjectID}
|
||||
}
|
||||
|
||||
func cascadingProjectGrantMembership(membership *query.ProjectGrantMembership) *command.CascadingProjectGrantMembership {
|
||||
if membership == nil {
|
||||
return nil
|
||||
|
49
internal/api/grpc/metadata/v2beta/metadata.go
Normal file
49
internal/api/grpc/metadata/v2beta/metadata.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
v2beta_object "github.com/zitadel/zitadel/internal/api/grpc/object/v2beta"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
meta_pb "github.com/zitadel/zitadel/pkg/grpc/metadata/v2beta"
|
||||
)
|
||||
|
||||
// code in this file is copied from internal/api/grpc/metadata/metadata.go
|
||||
|
||||
func OrgMetadataListToPb(dataList []*query.OrgMetadata) []*meta_pb.Metadata {
|
||||
mds := make([]*meta_pb.Metadata, len(dataList))
|
||||
for i, data := range dataList {
|
||||
mds[i] = OrgMetadataToPb(data)
|
||||
}
|
||||
return mds
|
||||
}
|
||||
|
||||
func OrgMetadataToPb(data *query.OrgMetadata) *meta_pb.Metadata {
|
||||
return &meta_pb.Metadata{
|
||||
Key: data.Key,
|
||||
Value: data.Value,
|
||||
CreationDate: timestamppb.New(data.CreationDate),
|
||||
ChangeDate: timestamppb.New(data.ChangeDate),
|
||||
}
|
||||
}
|
||||
|
||||
func OrgMetadataQueriesToQuery(queries []*meta_pb.MetadataQuery) (_ []query.SearchQuery, err error) {
|
||||
q := make([]query.SearchQuery, len(queries))
|
||||
for i, query := range queries {
|
||||
q[i], err = OrgMetadataQueryToQuery(query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func OrgMetadataQueryToQuery(metadataQuery *meta_pb.MetadataQuery) (query.SearchQuery, error) {
|
||||
switch q := metadataQuery.Query.(type) {
|
||||
case *meta_pb.MetadataQuery_KeyQuery:
|
||||
return query.NewOrgMetadataKeySearchQuery(q.KeyQuery.Key, v2beta_object.TextMethodToQuery(q.KeyQuery.Method))
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgument(nil, "METAD-fdg23", "List.Query.Invalid")
|
||||
}
|
||||
}
|
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
object "github.com/zitadel/zitadel/pkg/grpc/object/v2beta"
|
||||
org_pb "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
|
||||
)
|
||||
|
||||
func DomainToDetailsPb(objectDetail *domain.ObjectDetails) *object.Details {
|
||||
@@ -34,6 +35,7 @@ func ToListDetails(response query.SearchResponse) *object.ListDetails {
|
||||
|
||||
return details
|
||||
}
|
||||
|
||||
func ListQueryToQuery(query *object.ListQuery) (offset, limit uint64, asc bool) {
|
||||
if query == nil {
|
||||
return 0, 0, false
|
||||
@@ -73,3 +75,56 @@ func TextMethodToQuery(method object.TextQueryMethod) query.TextComparison {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
||||
func ListQueryToModel(query *object.ListQuery) (offset, limit uint64, asc bool) {
|
||||
if query == nil {
|
||||
return 0, 0, false
|
||||
}
|
||||
return query.Offset, uint64(query.Limit), query.Asc
|
||||
}
|
||||
|
||||
func DomainsToPb(domains []*query.Domain) []*org_pb.Domain {
|
||||
d := make([]*org_pb.Domain, len(domains))
|
||||
for i, domain := range domains {
|
||||
d[i] = DomainToPb(domain)
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
func DomainToPb(d *query.Domain) *org_pb.Domain {
|
||||
return &org_pb.Domain{
|
||||
OrganizationId: d.OrgID,
|
||||
DomainName: d.Domain,
|
||||
IsVerified: d.IsVerified,
|
||||
IsPrimary: d.IsPrimary,
|
||||
ValidationType: DomainValidationTypeFromModel(d.ValidationType),
|
||||
}
|
||||
}
|
||||
|
||||
func DomainValidationTypeFromModel(validationType domain.OrgDomainValidationType) org_pb.DomainValidationType {
|
||||
switch validationType {
|
||||
case domain.OrgDomainValidationTypeDNS:
|
||||
return org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS
|
||||
case domain.OrgDomainValidationTypeHTTP:
|
||||
return org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP
|
||||
case domain.OrgDomainValidationTypeUnspecified:
|
||||
// added to please golangci-lint
|
||||
return org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_UNSPECIFIED
|
||||
default:
|
||||
return org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func DomainValidationTypeToDomain(validationType org_pb.DomainValidationType) domain.OrgDomainValidationType {
|
||||
switch validationType {
|
||||
case org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_HTTP:
|
||||
return domain.OrgDomainValidationTypeHTTP
|
||||
case org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_DNS:
|
||||
return domain.OrgDomainValidationTypeDNS
|
||||
case org_pb.DomainValidationType_DOMAIN_VALIDATION_TYPE_UNSPECIFIED:
|
||||
// added to please golangci-lint
|
||||
return domain.OrgDomainValidationTypeUnspecified
|
||||
default:
|
||||
return domain.OrgDomainValidationTypeUnspecified
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user