mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:17:32 +00:00
feat: specify login UI version on instance and apps (#9071)
# Which Problems Are Solved To be able to migrate or test the new login UI, admins might want to (temporarily) switch individual apps. At a later point admin might want to make sure all applications use the new login UI. # How the Problems Are Solved - Added a feature flag `` on instance level to require all apps to use the new login and provide an optional base url. - if the flag is enabled, all (OIDC) applications will automatically use the v2 login. - if disabled, applications can decide based on their configuration - Added an option on OIDC apps to use the new login UI and an optional base url. - Removed the requirement to use `x-zitadel-login-client` to be redirected to the login V2 and retrieve created authrequest and link them to SSO sessions. - Added a new "IAM_LOGIN_CLIENT" role to allow management of users, sessions, grants and more without `x-zitadel-login-client`. # Additional Changes None # Additional Context closes https://github.com/zitadel/zitadel/issues/8702
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
package feature
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
@@ -8,7 +12,11 @@ import (
|
||||
feature_pb "github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
)
|
||||
|
||||
func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.SystemFeatures {
|
||||
func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) (*command.SystemFeatures, error) {
|
||||
loginV2, err := loginV2ToDomain(req.GetLoginV2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.SystemFeatures{
|
||||
LoginDefaultOrg: req.LoginDefaultOrg,
|
||||
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
||||
@@ -20,7 +28,8 @@ func systemFeaturesToCommand(req *feature_pb.SetSystemFeaturesRequest) *command.
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
||||
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
||||
}
|
||||
LoginV2: loginV2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesResponse {
|
||||
@@ -36,10 +45,15 @@ func systemFeaturesToPb(f *query.SystemFeatures) *feature_pb.GetSystemFeaturesRe
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
||||
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
||||
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
|
||||
}
|
||||
}
|
||||
|
||||
func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *command.InstanceFeatures {
|
||||
func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) (*command.InstanceFeatures, error) {
|
||||
loginV2, err := loginV2ToDomain(req.GetLoginV2())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &command.InstanceFeatures{
|
||||
LoginDefaultOrg: req.LoginDefaultOrg,
|
||||
TriggerIntrospectionProjections: req.OidcTriggerIntrospectionProjections,
|
||||
@@ -53,7 +67,8 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
||||
EnableBackChannelLogout: req.EnableBackChannelLogout,
|
||||
}
|
||||
LoginV2: loginV2,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeaturesResponse {
|
||||
@@ -71,6 +86,7 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
||||
EnableBackChannelLogout: featureSourceToFlagPb(&f.EnableBackChannelLogout),
|
||||
LoginV2: loginV2ToLoginV2FlagPb(f.LoginV2),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +97,39 @@ func featureSourceToImprovedPerformanceFlagPb(fs *query.FeatureSource[[]feature.
|
||||
}
|
||||
}
|
||||
|
||||
func loginV2ToDomain(loginV2 *feature_pb.LoginV2) (_ *feature.LoginV2, err error) {
|
||||
if loginV2 == nil {
|
||||
return nil, nil
|
||||
}
|
||||
var baseURI *url.URL
|
||||
if loginV2.GetBaseUri() != "" {
|
||||
baseURI, err = url.Parse(loginV2.GetBaseUri())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &feature.LoginV2{
|
||||
Required: loginV2.GetRequired(),
|
||||
BaseURI: baseURI,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func loginV2ToLoginV2FlagPb(f query.FeatureSource[*feature.LoginV2]) *feature_pb.LoginV2FeatureFlag {
|
||||
var required bool
|
||||
var baseURI *string
|
||||
if f.Value != nil {
|
||||
required = f.Value.Required
|
||||
if f.Value.BaseURI != nil && f.Value.BaseURI.String() != "" {
|
||||
baseURI = gu.Ptr(f.Value.BaseURI.String())
|
||||
}
|
||||
}
|
||||
return &feature_pb.LoginV2FeatureFlag{
|
||||
Required: required,
|
||||
BaseUri: baseURI,
|
||||
Source: featureLevelToSourcePb(f.Level),
|
||||
}
|
||||
}
|
||||
|
||||
func featureSourceToFlagPb(fs *query.FeatureSource[bool]) *feature_pb.FeatureFlag {
|
||||
return &feature_pb.FeatureFlag{
|
||||
Enabled: fs.Value,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package feature
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -26,6 +27,10 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
LoginV2: &feature_pb.LoginV2{
|
||||
Required: true,
|
||||
BaseUri: gu.Ptr("https://login.com"),
|
||||
},
|
||||
}
|
||||
want := &command.SystemFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -36,9 +41,14 @@ func Test_systemFeaturesToCommand(t *testing.T) {
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
LoginV2: &feature.LoginV2{
|
||||
Required: true,
|
||||
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToCommand(arg)
|
||||
got, err := systemFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_systemFeaturesToPb(t *testing.T) {
|
||||
@@ -84,6 +94,13 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: true,
|
||||
},
|
||||
LoginV2: query.FeatureSource[*feature.LoginV2]{
|
||||
Level: feature.LevelSystem,
|
||||
Value: &feature.LoginV2{
|
||||
Required: true,
|
||||
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetSystemFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -131,6 +148,11 @@ func Test_systemFeaturesToPb(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
LoginV2: &feature_pb.LoginV2FeatureFlag{
|
||||
Required: true,
|
||||
BaseUri: gu.Ptr("https://login.com"),
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
}
|
||||
got := systemFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
@@ -149,6 +171,10 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
DebugOidcParentError: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
LoginV2: &feature_pb.LoginV2{
|
||||
Required: true,
|
||||
BaseUri: gu.Ptr("https://login.com"),
|
||||
},
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
LoginDefaultOrg: gu.Ptr(true),
|
||||
@@ -162,9 +188,14 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
DebugOIDCParentError: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
LoginV2: &feature.LoginV2{
|
||||
Required: true,
|
||||
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
got, err := instanceFeaturesToCommand(arg)
|
||||
assert.Equal(t, want, got)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
@@ -214,6 +245,13 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
LoginV2: query.FeatureSource[*feature.LoginV2]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: &feature.LoginV2{
|
||||
Required: true,
|
||||
BaseURI: &url.URL{Scheme: "https", Host: "login.com"},
|
||||
},
|
||||
},
|
||||
}
|
||||
want := &feature_pb.GetInstanceFeaturesResponse{
|
||||
Details: &object.Details{
|
||||
@@ -269,6 +307,11 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
LoginV2: &feature_pb.LoginV2FeatureFlag{
|
||||
Required: true,
|
||||
BaseUri: gu.Ptr("https://login.com"),
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
}
|
||||
got := instanceFeaturesToPb(arg)
|
||||
assert.Equal(t, want, got)
|
||||
|
@@ -11,7 +11,11 @@ import (
|
||||
)
|
||||
|
||||
func (s *Server) SetSystemFeatures(ctx context.Context, req *feature.SetSystemFeaturesRequest) (_ *feature.SetSystemFeaturesResponse, err error) {
|
||||
details, err := s.command.SetSystemFeatures(ctx, systemFeaturesToCommand(req))
|
||||
features, err := systemFeaturesToCommand(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.SetSystemFeatures(ctx, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -39,7 +43,11 @@ func (s *Server) GetSystemFeatures(ctx context.Context, req *feature.GetSystemFe
|
||||
}
|
||||
|
||||
func (s *Server) SetInstanceFeatures(ctx context.Context, req *feature.SetInstanceFeaturesRequest) (_ *feature.SetInstanceFeaturesResponse, err error) {
|
||||
details, err := s.command.SetInstanceFeatures(ctx, instanceFeaturesToCommand(req))
|
||||
features, err := instanceFeaturesToCommand(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.SetInstanceFeatures(ctx, features)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
Reference in New Issue
Block a user