feat: added contracts for link-settings (#11975)

# Which Problems Are Solved

A new model is needed for links in settings.

# How the Problems Are Solved

Created the new contracts for `LinkSettings`

# Additional Changes


# Additional Context

Replace this example with links to related issues, discussions, discord
threads, or other sources with more context.
Use the Closing #issue syntax for issues that are resolved with this PR.
- Closes #11959
- Discussion #xxx
- Follow-up for PR #xxx
- https://discord.com/channels/xxx/xxx

---------

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Wim Van Laer
2026-04-10 09:24:49 +00:00
committed by GitHub
co-authored by Copilot
parent ce31ee2272
commit e65cc3007d
5 changed files with 251 additions and 0 deletions
@@ -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;
}
@@ -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 [
@@ -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:[email protected]").
// 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;
}
@@ -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;
+20
View File
@@ -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;
}