mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-06 06:52:38 +00:00
# Which Problems Are Solved
The event execution system currently uses a projection handler that
subscribes to and processes all events for all instances. This creates a
high static cost because the system over-fetches event data, handling
many events that are not needed by most instances. This inefficiency is
also reflected in high "rows returned" metrics in the database.
# How the Problems Are Solved
Eliminate the use of a project handler. Instead, events for which
"execution targets" are defined, are directly pushed to the queue by the
eventstore. A Router is populated in the Instance object in the authz
middleware.
- By joining the execution targets to the instance, no additional
queries are needed anymore.
- As part of the instance object, execution targets are now cached as
well.
- Events are queued within the same transaction, giving transactional
guarantees on delivery.
- Uses the "insert many fast` variant of River. Multiple jobs are queued
in a single round-trip to the database.
- Fix compatibility with PostgreSQL 15
# Additional Changes
- The signing key was stored as plain-text in the river job payload in
the DB. This violated our [Secrets
Storage](https://zitadel.com/docs/concepts/architecture/secrets#secrets-storage)
principle. This change removed the field and only uses the encrypted
version of the signing key.
- Fixed the target ordering from descending to ascending.
- Some minor linter warnings on the use of `io.WriteString()`.
# Additional Context
- Introduced in https://github.com/zitadel/zitadel/pull/9249
- Closes https://github.com/zitadel/zitadel/issues/10553
- Closes https://github.com/zitadel/zitadel/issues/9832
- Closes https://github.com/zitadel/zitadel/issues/10372
- Closes https://github.com/zitadel/zitadel/issues/10492
---------
Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
(cherry picked from commit a9ebc06c77)
215 lines
8.5 KiB
Go
215 lines
8.5 KiB
Go
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/zitadel/logging"
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
"github.com/zitadel/oidc/v3/pkg/op"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
"github.com/zitadel/zitadel/internal/auth/repository"
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
"github.com/zitadel/zitadel/internal/i18n"
|
|
"github.com/zitadel/zitadel/internal/query"
|
|
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
|
)
|
|
|
|
type Server struct {
|
|
http.Handler
|
|
*op.LegacyServer
|
|
|
|
repo repository.Repository
|
|
query *query.Queries
|
|
command *command.Commands
|
|
accessTokenKeySet *oidcKeySet
|
|
idTokenHintKeySet *oidcKeySet
|
|
|
|
defaultLoginURL string
|
|
defaultLoginURLV2 string
|
|
defaultLogoutURLV2 string
|
|
defaultAccessTokenLifetime time.Duration
|
|
defaultIdTokenLifetime time.Duration
|
|
jwksCacheControlMaxAge time.Duration
|
|
|
|
fallbackLogger *slog.Logger
|
|
hasher *crypto.Hasher
|
|
signingKeyAlgorithm string
|
|
encAlg crypto.EncryptionAlgorithm
|
|
targetEncryptionAlgorithm crypto.EncryptionAlgorithm
|
|
opCrypto op.Crypto
|
|
|
|
assetAPIPrefix func(ctx context.Context) string
|
|
}
|
|
|
|
func endpoints(endpointConfig *EndpointConfig) op.Endpoints {
|
|
// some defaults. The new Server will disable endpoints that are nil.
|
|
endpoints := op.Endpoints{
|
|
Authorization: op.NewEndpoint("/oauth/v2/authorize"),
|
|
Token: op.NewEndpoint("/oauth/v2/token"),
|
|
Introspection: op.NewEndpoint("/oauth/v2/introspect"),
|
|
Userinfo: op.NewEndpoint("/oidc/v1/userinfo"),
|
|
Revocation: op.NewEndpoint("/oauth/v2/revoke"),
|
|
EndSession: op.NewEndpoint("/oidc/v1/end_session"),
|
|
JwksURI: op.NewEndpoint("/oauth/v2/keys"),
|
|
DeviceAuthorization: op.NewEndpoint("/oauth/v2/device_authorization"),
|
|
}
|
|
|
|
if endpointConfig == nil {
|
|
return endpoints
|
|
}
|
|
if endpointConfig.Auth != nil {
|
|
endpoints.Authorization = op.NewEndpointWithURL(endpointConfig.Auth.Path, endpointConfig.Auth.URL)
|
|
}
|
|
if endpointConfig.Token != nil {
|
|
endpoints.Token = op.NewEndpointWithURL(endpointConfig.Token.Path, endpointConfig.Token.URL)
|
|
}
|
|
if endpointConfig.Introspection != nil {
|
|
endpoints.Introspection = op.NewEndpointWithURL(endpointConfig.Introspection.Path, endpointConfig.Introspection.URL)
|
|
}
|
|
if endpointConfig.Userinfo != nil {
|
|
endpoints.Userinfo = op.NewEndpointWithURL(endpointConfig.Userinfo.Path, endpointConfig.Userinfo.URL)
|
|
}
|
|
if endpointConfig.Revocation != nil {
|
|
endpoints.Revocation = op.NewEndpointWithURL(endpointConfig.Revocation.Path, endpointConfig.Revocation.URL)
|
|
}
|
|
if endpointConfig.EndSession != nil {
|
|
endpoints.EndSession = op.NewEndpointWithURL(endpointConfig.EndSession.Path, endpointConfig.EndSession.URL)
|
|
}
|
|
if endpointConfig.Keys != nil {
|
|
endpoints.JwksURI = op.NewEndpointWithURL(endpointConfig.Keys.Path, endpointConfig.Keys.URL)
|
|
}
|
|
if endpointConfig.DeviceAuth != nil {
|
|
endpoints.DeviceAuthorization = op.NewEndpointWithURL(endpointConfig.DeviceAuth.Path, endpointConfig.DeviceAuth.URL)
|
|
}
|
|
return endpoints
|
|
}
|
|
|
|
func (s *Server) getLogger(ctx context.Context) *slog.Logger {
|
|
if logger, ok := logging.FromContext(ctx); ok {
|
|
return logger
|
|
}
|
|
return s.fallbackLogger
|
|
}
|
|
|
|
func (s *Server) IssuerFromRequest(r *http.Request) string {
|
|
return s.Provider().IssuerFromRequest(r)
|
|
}
|
|
|
|
func (s *Server) Health(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.Health(ctx, r)
|
|
}
|
|
|
|
func (s *Server) Ready(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.Ready(ctx, r)
|
|
}
|
|
|
|
func (s *Server) Discovery(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() {
|
|
err = oidcError(err)
|
|
span.EndWithError(err)
|
|
}()
|
|
restrictions, err := s.query.GetInstanceRestrictions(ctx)
|
|
if err != nil {
|
|
return nil, op.NewStatusError(oidc.ErrServerError().WithParent(err).WithReturnParentToClient(authz.GetFeatures(ctx).DebugOIDCParentError).WithDescription("internal server error"), http.StatusInternalServerError)
|
|
}
|
|
allowedLanguages := restrictions.AllowedLanguages
|
|
if len(allowedLanguages) == 0 {
|
|
allowedLanguages = i18n.SupportedLanguages()
|
|
}
|
|
return op.NewResponse(s.createDiscoveryConfig(ctx, allowedLanguages)), nil
|
|
}
|
|
|
|
func (s *Server) VerifyAuthRequest(ctx context.Context, r *op.Request[oidc.AuthRequest]) (_ *op.ClientRequest[oidc.AuthRequest], err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.VerifyAuthRequest(ctx, r)
|
|
}
|
|
|
|
func (s *Server) Authorize(ctx context.Context, r *op.ClientRequest[oidc.AuthRequest]) (_ *op.Redirect, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.Authorize(ctx, r)
|
|
}
|
|
|
|
func (s *Server) DeviceAuthorization(ctx context.Context, r *op.ClientRequest[oidc.DeviceAuthorizationRequest]) (_ *op.Response, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.DeviceAuthorization(ctx, r)
|
|
}
|
|
|
|
func (s *Server) Revocation(ctx context.Context, r *op.ClientRequest[oidc.RevocationRequest]) (_ *op.Response, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.Revocation(ctx, r)
|
|
}
|
|
|
|
func (s *Server) EndSession(ctx context.Context, r *op.Request[oidc.EndSessionRequest]) (_ *op.Redirect, err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
|
|
return s.LegacyServer.EndSession(ctx, r)
|
|
}
|
|
|
|
func (s *Server) createDiscoveryConfig(ctx context.Context, supportedUILocales oidc.Locales) *oidc.DiscoveryConfiguration {
|
|
issuer := op.IssuerFromContext(ctx)
|
|
backChannelLogoutSupported := authz.GetInstance(ctx).Features().EnableBackChannelLogout
|
|
|
|
return &oidc.DiscoveryConfiguration{
|
|
Issuer: issuer,
|
|
AuthorizationEndpoint: s.Endpoints().Authorization.Absolute(issuer),
|
|
TokenEndpoint: s.Endpoints().Token.Absolute(issuer),
|
|
IntrospectionEndpoint: s.Endpoints().Introspection.Absolute(issuer),
|
|
UserinfoEndpoint: s.Endpoints().Userinfo.Absolute(issuer),
|
|
RevocationEndpoint: s.Endpoints().Revocation.Absolute(issuer),
|
|
EndSessionEndpoint: s.Endpoints().EndSession.Absolute(issuer),
|
|
JwksURI: s.Endpoints().JwksURI.Absolute(issuer),
|
|
DeviceAuthorizationEndpoint: s.Endpoints().DeviceAuthorization.Absolute(issuer),
|
|
ScopesSupported: op.Scopes(s.Provider()),
|
|
ResponseTypesSupported: op.ResponseTypes(s.Provider()),
|
|
ResponseModesSupported: []string{
|
|
string(oidc.ResponseModeQuery),
|
|
string(oidc.ResponseModeFragment),
|
|
string(oidc.ResponseModeFormPost),
|
|
},
|
|
GrantTypesSupported: op.GrantTypes(s.Provider()),
|
|
SubjectTypesSupported: op.SubjectTypes(s.Provider()),
|
|
IDTokenSigningAlgValuesSupported: supportedSigningAlgs(),
|
|
RequestObjectSigningAlgValuesSupported: op.RequestObjectSigAlgorithms(s.Provider()),
|
|
TokenEndpointAuthMethodsSupported: op.AuthMethodsTokenEndpoint(s.Provider()),
|
|
TokenEndpointAuthSigningAlgValuesSupported: op.TokenSigAlgorithms(s.Provider()),
|
|
IntrospectionEndpointAuthSigningAlgValuesSupported: op.IntrospectionSigAlgorithms(s.Provider()),
|
|
IntrospectionEndpointAuthMethodsSupported: op.AuthMethodsIntrospectionEndpoint(s.Provider()),
|
|
RevocationEndpointAuthSigningAlgValuesSupported: op.RevocationSigAlgorithms(s.Provider()),
|
|
RevocationEndpointAuthMethodsSupported: op.AuthMethodsRevocationEndpoint(s.Provider()),
|
|
ClaimsSupported: op.SupportedClaims(s.Provider()),
|
|
CodeChallengeMethodsSupported: op.CodeChallengeMethods(s.Provider()),
|
|
UILocalesSupported: supportedUILocales,
|
|
RequestParameterSupported: s.Provider().RequestObjectSupported(),
|
|
BackChannelLogoutSupported: backChannelLogoutSupported,
|
|
BackChannelLogoutSessionSupported: backChannelLogoutSupported,
|
|
}
|
|
}
|
|
|
|
func response(resp any, err error) (*op.Response, error) {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return op.NewResponse(resp), nil
|
|
}
|