diff --git a/proto/zitadel/settings/v2/effective_settings.proto b/proto/zitadel/settings/v2/effective_settings.proto new file mode 100644 index 000000000..187c29b9b --- /dev/null +++ b/proto/zitadel/settings/v2/effective_settings.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +package zitadel.settings.v2; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings"; + +import "zitadel/settings/v2/link_settings.proto"; + +// ============================================================================ +// Enums +// ============================================================================ + +enum SettingsType { + SETTINGS_TYPE_UNSPECIFIED = 0; + SETTINGS_TYPE_LINK_SETTINGS = 1; +} + +// ============================================================================ +// Get +// ============================================================================ + +message GetEffectiveSettingsRequest { + optional string organization_id = 1; + optional string project_id = 2; + optional string application_id = 3; + repeated SettingsType types = 4; // empty or omitted = return all +} + +message GetEffectiveSettingsResponse { + optional LinkSettings links = 7; +} diff --git a/proto/zitadel/settings/v2/legal_settings.proto b/proto/zitadel/settings/v2/legal_settings.proto index 288712e33..8ec75922c 100644 --- a/proto/zitadel/settings/v2/legal_settings.proto +++ b/proto/zitadel/settings/v2/legal_settings.proto @@ -8,6 +8,7 @@ import "protoc-gen-openapiv2/options/annotations.proto"; import "zitadel/settings/v2/settings.proto"; import "validate/validate.proto"; +// Deprecated: use GetLinkSettings instead message LegalAndSupportSettings { // Link to the Terms of Service. Can be a relative or absolute URL. string tos_link = 1 [ diff --git a/proto/zitadel/settings/v2/link_settings.proto b/proto/zitadel/settings/v2/link_settings.proto new file mode 100644 index 000000000..df4618897 --- /dev/null +++ b/proto/zitadel/settings/v2/link_settings.proto @@ -0,0 +1,133 @@ +syntax = "proto3"; + +package zitadel.settings.v2; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings"; + +import "validate/validate.proto"; + +import "google/protobuf/timestamp.proto"; +import "zitadel/object/v2/object.proto"; +import "zitadel/settings/v2/source.proto"; + +// ============================================================================ +// Messages +// ============================================================================ + +// Ordered list of links displayed in the login footer. +// The array order determines display order — there is no separate order field. +// Protobuf `repeated` fields preserve insertion order across all languages. +message LinkSettings { + repeated Link links = 1; +} + +message Link { + LinkType type = 1; + // Target URL or mailto address for the link. + // Must be either a valid absolute URL (e.g., "https://example.com/tos") + // or a mailto address (e.g., "mailto:support@example.com"). + // May contain language placeholders ({{.Lang}}) to support localized destinations. + string url = 2 [ + (validate.rules).string = { + pattern: "^(https?://|mailto:).+" + min_len: 1; + } + ]; + // Translation key for the link text. + // Required for CUSTOM links; must be empty for well-known types. + // + // Well-known types: labels come from the frontend i18n files, not this field. + // Custom links: this is a translation key resolved via GetHostedLoginTranslation. + // Register display text per language via SetHostedLoginTranslation. + string translation_key = 3 [ + (validate.rules).string = { + min_len: 1; + ignore_empty: true; + } + ]; + LinkTarget target = 4; +} + +// ============================================================================ +// Enums +// ============================================================================ + +enum LinkType { + LINK_TYPE_UNSPECIFIED = 0; + LINK_TYPE_TERMS_OF_SERVICE = 1; + LINK_TYPE_PRIVACY_POLICY = 2; + LINK_TYPE_HELP = 3; + LINK_TYPE_SUPPORT = 4; + LINK_TYPE_DOCS = 5; + LINK_TYPE_CUSTOM = 6; +} + +enum LinkTarget { + LINK_TARGET_UNSPECIFIED = 0; + LINK_TARGET_SELF = 1; // opens in same window + LINK_TARGET_BLANK = 2; // opens in new tab +} + +// ============================================================================ +// Get +// ============================================================================ + +// Returns the effective links for the requested scope. +// Source is tracked for the list as a whole — the entire list +// is either inherited or overridden, never partially merged. + +message GetLinkSettingsRequest { + // ctx specifies the context for which the link settings should be returned. + // This can be the instance, organization,... according to the hierarchical + // model of the settings. + // If a lower level of the model is requested but not found, the fallback of + // the parent is returned. + zitadel.object.v2.RequestContext ctx = 1; +} + +message GetLinkSettingsResponse { + LinkSettings settings = 1; + Source source = 2; +} + +// ============================================================================ +// Set +// ============================================================================ + +message SetLinkSettingsRequest { + // ctx specifies the context for which the link settings should be set. + // This can be the instance, organization,... according to the hierarchical + // model of the settings. + zitadel.object.v2.RequestContext ctx = 1; + // link is the collection of links to which the setting should be set. + // + // Validation: + // - Each well-known type (TERMS_OF_SERVICE, PRIVACY_POLICY, etc.) + // may appear at most once. + // - CUSTOM links may appear multiple times. + // - url must be a valid absolute URL or a mailto address. + // - translation_key is required when type is CUSTOM. + // - translation_key must be empty for well-known types. + repeated Link links = 2; +} + +message SetLinkSettingsResponse { + google.protobuf.Timestamp change_date = 1; + LinkSettings settings = 2; +} + +// ============================================================================ +// Reset +// ============================================================================ + +message ResetLinkSettingsRequest { + // ctx specifies the context for which the link settings should be reset. + // This can be the instance, organization,... according to the hierarchical + // model of the settings. + zitadel.object.v2.RequestContext ctx = 1; +} + +message ResetLinkSettingsResponse { + google.protobuf.Timestamp change_date = 1; + LinkSettings settings = 2; +} diff --git a/proto/zitadel/settings/v2/settings_service.proto b/proto/zitadel/settings/v2/settings_service.proto index 8d5fe8eea..45f7a3410 100644 --- a/proto/zitadel/settings/v2/settings_service.proto +++ b/proto/zitadel/settings/v2/settings_service.proto @@ -20,6 +20,8 @@ import "zitadel/settings/v2/settings.proto"; import "google/protobuf/timestamp.proto"; import "zitadel/filter/v2/filter.proto"; import "zitadel/settings/v2/organization_settings.proto"; +import "zitadel/settings/v2/link_settings.proto"; +import "zitadel/settings/v2/effective_settings.proto"; option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings"; @@ -329,7 +331,11 @@ service SettingsService { // // Required permissions: // - `policy.read` + // + // Deprecated: use GetLinkSettings instead rpc GetLegalAndSupportSettings (GetLegalAndSupportSettingsRequest) returns (GetLegalAndSupportSettingsResponse) { + option deprecated = true; + option (google.api.http) = { get: "/v2/settings/legal_support" }; @@ -341,6 +347,7 @@ service SettingsService { }; option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + deprecated: true, responses: { key: "200" value: { @@ -574,6 +581,63 @@ service SettingsService { } }; } + + // Get Link settings + // + // Returns the effective links for the requested scope. + // Source is tracked for the list as a whole — the entire list + // is either inherited or overridden, never partially merged. + rpc GetLinkSettings(GetLinkSettingsRequest) returns (GetLinkSettingsResponse){ + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "iam.policy.read" + } + }; + } + + // Set Link settings + // + // Replaces the entire link list at the requested scope. + // This is a full replacement, not a merge — omitted links are removed. + // + // To add a link, include the full list with the new link appended. + // To remove a link, include the full list without it. + rpc SetLinkSettings(SetLinkSettingsRequest) returns (SetLinkSettingsResponse) { + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "iam.policy.write" + } + }; + } + + // Reset link settings + // + // Removes all link overrides at the requested scope. + // The scope falls back to its parent's values. + // + // Unlike other settings, there is no per-field reset — links are + // managed as a list, so reset always clears the entire list. + rpc ResetLinkSettings(ResetLinkSettingsRequest) returns (ResetLinkSettingsResponse) { + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "iam.policy.write" + } + }; + } + + // Get effective settings + // + // Gets all settings which are requested in the request. + // The settings values are resolved using the hierarchical model. + // Only the resolved settings are returned, without the source + // which describes from which hierarchical level they came. + rpc GetEffectiveSettings(GetEffectiveSettingsRequest) returns (GetEffectiveSettingsResponse) { + option (zitadel.protoc_gen_zitadel.v2.options) = { + auth_option: { + permission: "iam.policy.read" + } + }; + } } message GetLoginSettingsRequest { @@ -641,6 +705,7 @@ message GetDomainSettingsResponse { zitadel.settings.v2.DomainSettings settings = 2; } +// Deprecated: use GetLinkSettings instead message GetLegalAndSupportSettingsRequest { // Specify the context for which the legal and support settings should be returned. // This can be the instance or an organization. @@ -649,6 +714,7 @@ message GetLegalAndSupportSettingsRequest { zitadel.object.v2.RequestContext ctx = 1; } +// Deprecated: use GetLinkSettings instead message GetLegalAndSupportSettingsResponse { zitadel.object.v2.Details details = 1; zitadel.settings.v2.LegalAndSupportSettings settings = 2; diff --git a/proto/zitadel/settings/v2/source.proto b/proto/zitadel/settings/v2/source.proto new file mode 100644 index 000000000..1a8f7b686 --- /dev/null +++ b/proto/zitadel/settings/v2/source.proto @@ -0,0 +1,20 @@ +syntax = "proto3"; + +package zitadel.settings.v2; + +option go_package = "github.com/zitadel/zitadel/pkg/grpc/settings/v2;settings"; + +// ============================================================================ +// Enums +// ============================================================================ + +// Source defines the levels of hierarchy for which settings can be +// written/retrieved. +enum Source { + SOURCE_UNSPECIFIED = 0; + SOURCE_SYSTEM = 1; // hard-coded default, not settable via API + SOURCE_INSTANCE = 2; + SOURCE_ORGANIZATION = 3; + SOURCE_PROJECT = 4; + SOURCE_APPLICATION = 5; +}