mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 19:14:23 +00:00
26d1563643
* feat(api): feature API proto definitions * update proto based on discussion with @livio-a * cleanup old feature flag stuff * authz instance queries * align defaults * projection definitions * define commands and event reducers * implement system and instance setter APIs * api getter implementation * unit test repository package * command unit tests * unit test Get queries * grpc converter unit tests * migrate the V1 features * migrate oidc to dynamic features * projection unit test * fix instance by host * fix instance by id data type in sql * fix linting errors * add system projection test * fix behavior inversion * resolve proto file comments * rename SystemDefaultLoginInstanceEventType to SystemLoginDefaultOrgEventType so it's consistent with the instance level event * use write models and conditional set events * system features integration tests * instance features integration tests * error on empty request * documentation entry * typo in feature.proto * fix start unit tests * solve linting error on key case switch * remove system defaults after discussion with @eliobischof * fix system feature projection * resolve comments in defaults.yaml --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
212 lines
8.2 KiB
Go
212 lines
8.2 KiB
Go
package oidc
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/zitadel/oidc/v3/pkg/oidc"
|
|
"github.com/zitadel/oidc/v3/pkg/op"
|
|
"golang.org/x/exp/slog"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/assets"
|
|
http_utils "github.com/zitadel/zitadel/internal/api/http"
|
|
"github.com/zitadel/zitadel/internal/api/http/middleware"
|
|
"github.com/zitadel/zitadel/internal/api/ui/login"
|
|
"github.com/zitadel/zitadel/internal/auth/repository"
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
"github.com/zitadel/zitadel/internal/database"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
|
"github.com/zitadel/zitadel/internal/query"
|
|
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
|
"github.com/zitadel/zitadel/internal/zerrors"
|
|
)
|
|
|
|
type Config struct {
|
|
CodeMethodS256 bool
|
|
AuthMethodPost bool
|
|
AuthMethodPrivateKeyJWT bool
|
|
GrantTypeRefreshToken bool
|
|
RequestObjectSupported bool
|
|
SigningKeyAlgorithm string
|
|
DefaultAccessTokenLifetime time.Duration
|
|
DefaultIdTokenLifetime time.Duration
|
|
DefaultRefreshTokenIdleExpiration time.Duration
|
|
DefaultRefreshTokenExpiration time.Duration
|
|
UserAgentCookieConfig *middleware.UserAgentCookieConfig
|
|
Cache *middleware.CacheConfig
|
|
CustomEndpoints *EndpointConfig
|
|
DeviceAuth *DeviceAuthorizationConfig
|
|
DefaultLoginURLV2 string
|
|
DefaultLogoutURLV2 string
|
|
PublicKeyCacheMaxAge time.Duration
|
|
}
|
|
|
|
type EndpointConfig struct {
|
|
Auth *Endpoint
|
|
Token *Endpoint
|
|
Introspection *Endpoint
|
|
Userinfo *Endpoint
|
|
Revocation *Endpoint
|
|
EndSession *Endpoint
|
|
Keys *Endpoint
|
|
DeviceAuth *Endpoint
|
|
}
|
|
|
|
type Endpoint struct {
|
|
Path string
|
|
URL string
|
|
}
|
|
|
|
type OPStorage struct {
|
|
repo repository.Repository
|
|
command *command.Commands
|
|
query *query.Queries
|
|
eventstore *eventstore.Eventstore
|
|
defaultLoginURL string
|
|
defaultLoginURLV2 string
|
|
defaultLogoutURLV2 string
|
|
defaultAccessTokenLifetime time.Duration
|
|
defaultIdTokenLifetime time.Duration
|
|
signingKeyAlgorithm string
|
|
defaultRefreshTokenIdleExpiration time.Duration
|
|
defaultRefreshTokenExpiration time.Duration
|
|
encAlg crypto.EncryptionAlgorithm
|
|
locker crdb.Locker
|
|
assetAPIPrefix func(ctx context.Context) string
|
|
}
|
|
|
|
func NewServer(
|
|
config Config,
|
|
defaultLogoutRedirectURI string,
|
|
externalSecure bool,
|
|
command *command.Commands,
|
|
query *query.Queries,
|
|
repo repository.Repository,
|
|
encryptionAlg crypto.EncryptionAlgorithm,
|
|
cryptoKey []byte,
|
|
es *eventstore.Eventstore,
|
|
projections *database.DB,
|
|
userAgentCookie, instanceHandler func(http.Handler) http.Handler,
|
|
accessHandler *middleware.AccessInterceptor,
|
|
fallbackLogger *slog.Logger,
|
|
) (*Server, error) {
|
|
opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
|
|
}
|
|
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, externalSecure)
|
|
keyCache := newPublicKeyCache(context.TODO(), config.PublicKeyCacheMaxAge, query.GetPublicKeyByID)
|
|
accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true))
|
|
idTokenHintKeySet := newOidcKeySet(keyCache)
|
|
|
|
options := []op.Option{
|
|
op.WithAccessTokenKeySet(accessTokenKeySet),
|
|
op.WithIDTokenHintKeySet(idTokenHintKeySet),
|
|
}
|
|
if !externalSecure {
|
|
options = append(options, op.WithAllowInsecure())
|
|
}
|
|
provider, err := op.NewProvider(
|
|
opConfig,
|
|
storage,
|
|
op.IssuerFromForwardedOrHost("", op.WithIssuerFromCustomHeaders("forwarded", "x-zitadel-forwarded")),
|
|
options...,
|
|
)
|
|
if err != nil {
|
|
return nil, zerrors.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider")
|
|
}
|
|
|
|
server := &Server{
|
|
LegacyServer: op.NewLegacyServer(provider, endpoints(config.CustomEndpoints)),
|
|
repo: repo,
|
|
query: query,
|
|
command: command,
|
|
accessTokenKeySet: accessTokenKeySet,
|
|
idTokenHintKeySet: idTokenHintKeySet,
|
|
defaultLoginURL: fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID),
|
|
defaultLoginURLV2: config.DefaultLoginURLV2,
|
|
defaultLogoutURLV2: config.DefaultLogoutURLV2,
|
|
defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime,
|
|
defaultIdTokenLifetime: config.DefaultIdTokenLifetime,
|
|
fallbackLogger: fallbackLogger,
|
|
hashAlg: crypto.NewBCrypt(10), // as we are only verifying in oidc, the cost is already part of the hash string and the config here is irrelevant.
|
|
signingKeyAlgorithm: config.SigningKeyAlgorithm,
|
|
assetAPIPrefix: assets.AssetAPI(externalSecure),
|
|
}
|
|
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
|
|
server.Handler = op.RegisterLegacyServer(server,
|
|
op.WithFallbackLogger(fallbackLogger),
|
|
op.WithHTTPMiddleware(
|
|
middleware.MetricsHandler(metricTypes),
|
|
middleware.TelemetryHandler(),
|
|
middleware.NoCacheInterceptor().Handler,
|
|
instanceHandler,
|
|
userAgentCookie,
|
|
http_utils.CopyHeadersToContext,
|
|
accessHandler.HandleWithPublicAuthPathPrefixes(publicAuthPathPrefixes(config.CustomEndpoints)),
|
|
middleware.ActivityHandler,
|
|
))
|
|
|
|
return server, nil
|
|
}
|
|
|
|
func publicAuthPathPrefixes(endpoints *EndpointConfig) []string {
|
|
authURL := op.DefaultEndpoints.Authorization.Relative()
|
|
keysURL := op.DefaultEndpoints.JwksURI.Relative()
|
|
if endpoints == nil {
|
|
return []string{oidc.DiscoveryEndpoint, authURL, keysURL}
|
|
}
|
|
if endpoints.Auth != nil && endpoints.Auth.Path != "" {
|
|
authURL = endpoints.Auth.Path
|
|
}
|
|
if endpoints.Keys != nil && endpoints.Keys.Path != "" {
|
|
keysURL = endpoints.Keys.Path
|
|
}
|
|
return []string{oidc.DiscoveryEndpoint, authURL, keysURL}
|
|
}
|
|
|
|
func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
|
|
opConfig := &op.Config{
|
|
DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
|
|
CodeMethodS256: config.CodeMethodS256,
|
|
AuthMethodPost: config.AuthMethodPost,
|
|
AuthMethodPrivateKeyJWT: config.AuthMethodPrivateKeyJWT,
|
|
GrantTypeRefreshToken: config.GrantTypeRefreshToken,
|
|
RequestObjectSupported: config.RequestObjectSupported,
|
|
DeviceAuthorization: config.DeviceAuth.toOPConfig(),
|
|
}
|
|
if cryptoLength := len(cryptoKey); cryptoLength != 32 {
|
|
return nil, zerrors.ThrowInternalf(nil, "OIDC-D43gf", "crypto key must be 32 bytes, but is %d", cryptoLength)
|
|
}
|
|
copy(opConfig.CryptoKey[:], cryptoKey)
|
|
return opConfig, nil
|
|
}
|
|
|
|
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, db *database.DB, externalSecure bool) *OPStorage {
|
|
return &OPStorage{
|
|
repo: repo,
|
|
command: command,
|
|
query: query,
|
|
eventstore: es,
|
|
defaultLoginURL: fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID),
|
|
defaultLoginURLV2: config.DefaultLoginURLV2,
|
|
defaultLogoutURLV2: config.DefaultLogoutURLV2,
|
|
signingKeyAlgorithm: config.SigningKeyAlgorithm,
|
|
defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime,
|
|
defaultIdTokenLifetime: config.DefaultIdTokenLifetime,
|
|
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
|
|
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
|
|
encAlg: encAlg,
|
|
locker: crdb.NewLocker(db.DB, locksTable, signingKey),
|
|
assetAPIPrefix: assets.AssetAPI(externalSecure),
|
|
}
|
|
}
|
|
|
|
func (o *OPStorage) Health(ctx context.Context) error {
|
|
return o.repo.Health(ctx)
|
|
}
|