mirror of
https://github.com/zitadel/zitadel.git
synced 2025-07-28 16:03:41 +00:00
feat: dynamic issuer (#3481)
* feat: dynamic issuer * dynamic domain handling * key rotation durations * feat: dynamic issuer * make webauthn displayname dynamic
This commit is contained in:
parent
3d5891eb11
commit
75ec73ca4a
@ -11,7 +11,6 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
crypto_db "github.com/caos/zitadel/internal/crypto/database"
|
crypto_db "github.com/caos/zitadel/internal/crypto/database"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
webauthn_helper "github.com/caos/zitadel/internal/webauthn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DefaultInstance struct {
|
type DefaultInstance struct {
|
||||||
@ -47,8 +46,7 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
|
|||||||
mig.zitadelRoles,
|
mig.zitadelRoles,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
//TODO: Livio will fix this, but it ZITADEL doesn't run without this
|
nil,
|
||||||
webauthn_helper.Config{DisplayName: "HELLO LIVIO", ID: "RPID"},
|
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
nil,
|
nil,
|
||||||
|
@ -2,11 +2,12 @@ package start
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/zitadel/internal/command"
|
|
||||||
"github.com/caos/zitadel/internal/config/hook"
|
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/command"
|
||||||
|
"github.com/caos/zitadel/internal/config/hook"
|
||||||
|
|
||||||
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
|
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
|
||||||
internal_authz "github.com/caos/zitadel/internal/api/authz"
|
internal_authz "github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
@ -31,6 +32,7 @@ type Config struct {
|
|||||||
ExternalSecure bool
|
ExternalSecure bool
|
||||||
HTTP2HostHeader string
|
HTTP2HostHeader string
|
||||||
HTTP1HostHeader string
|
HTTP1HostHeader string
|
||||||
|
WebAuthNName string
|
||||||
Database database.Config
|
Database database.Config
|
||||||
Projections projection.Config
|
Projections projection.Config
|
||||||
AuthZ authz.Config
|
AuthZ authz.Config
|
||||||
|
@ -13,8 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/oidc/pkg/op"
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/system"
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
@ -29,6 +28,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/api/grpc/admin"
|
"github.com/caos/zitadel/internal/api/grpc/admin"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/auth"
|
"github.com/caos/zitadel/internal/api/grpc/auth"
|
||||||
"github.com/caos/zitadel/internal/api/grpc/management"
|
"github.com/caos/zitadel/internal/api/grpc/management"
|
||||||
|
"github.com/caos/zitadel/internal/api/grpc/system"
|
||||||
http_util "github.com/caos/zitadel/internal/api/http"
|
http_util "github.com/caos/zitadel/internal/api/http"
|
||||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
"github.com/caos/zitadel/internal/api/oidc"
|
"github.com/caos/zitadel/internal/api/oidc"
|
||||||
@ -49,10 +49,6 @@ import (
|
|||||||
"github.com/caos/zitadel/openapi"
|
"github.com/caos/zitadel/openapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
flagMasterKey = "masterkey"
|
|
||||||
)
|
|
||||||
|
|
||||||
func New() *cobra.Command {
|
func New() *cobra.Command {
|
||||||
start := &cobra.Command{
|
start := &cobra.Command{
|
||||||
Use: "start",
|
Use: "start",
|
||||||
@ -78,7 +74,6 @@ Requirements:
|
|||||||
|
|
||||||
func startZitadel(config *Config, masterKey string) error {
|
func startZitadel(config *Config, masterKey string) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
keyChan := make(chan interface{})
|
|
||||||
|
|
||||||
dbClient, err := database.Connect(config.Database)
|
dbClient, err := database.Connect(config.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,7 +94,7 @@ func startZitadel(config *Config, masterKey string) error {
|
|||||||
return fmt.Errorf("cannot start eventstore for queries: %w", err)
|
return fmt.Errorf("cannot start eventstore for queries: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, keyChan, config.InternalAuthZ.RolePermissionMappings)
|
queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, config.InternalAuthZ.RolePermissionMappings)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot start queries: %w", err)
|
return fmt.Errorf("cannot start queries: %w", err)
|
||||||
}
|
}
|
||||||
@ -113,10 +108,9 @@ func startZitadel(config *Config, masterKey string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot start asset storage client: %w", err)
|
return fmt.Errorf("cannot start asset storage client: %w", err)
|
||||||
}
|
}
|
||||||
webAuthNConfig := webauthn.Config{
|
webAuthNConfig := &webauthn.Config{
|
||||||
ID: config.ExternalDomain,
|
DisplayName: config.WebAuthNName,
|
||||||
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
|
ExternalSecure: config.ExternalSecure,
|
||||||
DisplayName: "ZITADEL",
|
|
||||||
}
|
}
|
||||||
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ.RolePermissionMappings, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.User, keys.DomainVerification, keys.OIDC)
|
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ.RolePermissionMappings, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.User, keys.DomainVerification, keys.OIDC)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -126,14 +120,14 @@ func startZitadel(config *Config, masterKey string) error {
|
|||||||
notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.User, keys.SMTP, keys.SMS)
|
notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.User, keys.SMTP, keys.SMS)
|
||||||
|
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo, keys)
|
err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, config, storage, authZRepo, keys)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return listen(ctx, router, config.Port)
|
return listen(ctx, router, config.Port)
|
||||||
}
|
}
|
||||||
|
|
||||||
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error {
|
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error {
|
||||||
repo := struct {
|
repo := struct {
|
||||||
authz_repo.Repository
|
authz_repo.Repository
|
||||||
*query.Queries
|
*query.Queries
|
||||||
@ -168,13 +162,12 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
|
|||||||
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader)
|
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader)
|
||||||
authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler))
|
authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler))
|
||||||
|
|
||||||
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure)
|
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator, config.ExternalSecure)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
issuer := oidc.Issuer(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler)
|
||||||
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, issuer, login.DefaultLoggedOutPath, commands, queries, authRepo, config.SystemDefaults.KeyConfig, keys.OIDC, keys.OIDCKey, eventstore, dbClient, keyChan, userAgentInterceptor, instanceInterceptor.Handler)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start oidc provider: %w", err)
|
return fmt.Errorf("unable to start oidc provider: %w", err)
|
||||||
}
|
}
|
||||||
@ -187,13 +180,13 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
|
|||||||
authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
|
authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
|
||||||
|
|
||||||
baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
|
||||||
c, err := console.Start(config.Console, config.ExternalDomain, baseURL, issuer, instanceInterceptor.Handler)
|
c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, instanceInterceptor.Handler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start console: %w", err)
|
return fmt.Errorf("unable to start console: %w", err)
|
||||||
}
|
}
|
||||||
authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c)
|
authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c)
|
||||||
|
|
||||||
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to start login: %w", err)
|
return fmt.Errorf("unable to start login: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,8 @@ ExternalSecure: true
|
|||||||
HTTP2HostHeader: ":authority"
|
HTTP2HostHeader: ":authority"
|
||||||
HTTP1HostHeader: "host"
|
HTTP1HostHeader: "host"
|
||||||
|
|
||||||
|
WebAuthNName: ZITADEL
|
||||||
|
|
||||||
Database:
|
Database:
|
||||||
Host: localhost
|
Host: localhost
|
||||||
Port: 26257
|
Port: 26257
|
||||||
|
4
go.mod
4
go.mod
@ -13,7 +13,7 @@ require (
|
|||||||
github.com/allegro/bigcache v1.2.1
|
github.com/allegro/bigcache v1.2.1
|
||||||
github.com/boombuler/barcode v1.0.1
|
github.com/boombuler/barcode v1.0.1
|
||||||
github.com/caos/logging v0.3.1
|
github.com/caos/logging v0.3.1
|
||||||
github.com/caos/oidc v1.2.0
|
github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1
|
||||||
github.com/cockroachdb/cockroach-go/v2 v2.2.4
|
github.com/cockroachdb/cockroach-go/v2 v2.2.4
|
||||||
github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51
|
github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51
|
||||||
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
|
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
|
||||||
@ -47,7 +47,7 @@ require (
|
|||||||
github.com/sony/sonyflake v1.0.0
|
github.com/sony/sonyflake v1.0.0
|
||||||
github.com/spf13/cobra v1.3.0
|
github.com/spf13/cobra v1.3.0
|
||||||
github.com/spf13/viper v1.10.1
|
github.com/spf13/viper v1.10.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.1
|
||||||
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
|
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
|
||||||
github.com/ttacon/libphonenumber v1.2.1
|
github.com/ttacon/libphonenumber v1.2.1
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.27.0
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.27.0
|
||||||
|
11
go.sum
11
go.sum
@ -123,13 +123,10 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
|
|||||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
|
||||||
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||||
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
|
||||||
github.com/caos/logging v0.3.1 h1:892AMeHs09D0e3ZcGB+QDRsZ5+2xtPAsAhOy8eKfztc=
|
github.com/caos/logging v0.3.1 h1:892AMeHs09D0e3ZcGB+QDRsZ5+2xtPAsAhOy8eKfztc=
|
||||||
github.com/caos/logging v0.3.1/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo=
|
github.com/caos/logging v0.3.1/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo=
|
||||||
github.com/caos/oidc v1.0.1 h1:8UHAPynCObwaqortppDtJFktjqLDLYSLidkNy0Num4o=
|
github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1 h1:xJuhRlhJf9huGX/+c/mEN2QaFiYlSMHd4QfsWkDoCGI=
|
||||||
github.com/caos/oidc v1.0.1/go.mod h1:4l0PPwdc6BbrdCFhNrRTUddsG292uHGa7gE2DSEIqoU=
|
github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1/go.mod h1:bdIIx0WCNjbGqpi7o4PifypVoeCAD3yTncL03rogNKw=
|
||||||
github.com/caos/oidc v1.2.0 h1:dTy5bcT2WQbwPgytEZiG8SV1bCgHUXyDdaPDCNtRdEU=
|
|
||||||
github.com/caos/oidc v1.2.0/go.mod h1:4l0PPwdc6BbrdCFhNrRTUddsG292uHGa7gE2DSEIqoU=
|
|
||||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
|
||||||
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
|
||||||
@ -895,8 +892,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
|
||||||
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
|
||||||
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 h1:1SWXcTphBQjYGWRRxLFIAR1LVtQEj4eR7xPtyeOVM/c=
|
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 h1:1SWXcTphBQjYGWRRxLFIAR1LVtQEj4eR7xPtyeOVM/c=
|
||||||
@ -1192,7 +1190,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
@ -3,7 +3,7 @@ package actions
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Context map[string]interface{}
|
type Context map[string]interface{}
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
"google.golang.org/protobuf/types/known/durationpb"
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package http
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/gorilla/securecookie"
|
"github.com/gorilla/securecookie"
|
||||||
|
|
||||||
@ -20,7 +21,6 @@ type CookieHandler struct {
|
|||||||
sameSite http.SameSite
|
sameSite http.SameSite
|
||||||
path string
|
path string
|
||||||
maxAge int
|
maxAge int
|
||||||
domain string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
|
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
|
||||||
@ -76,12 +76,6 @@ func WithMaxAge(maxAge int) CookieHandlerOpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func WithDomain(domain string) CookieHandlerOpt {
|
|
||||||
return func(c *CookieHandler) {
|
|
||||||
c.domain = domain
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
|
func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
|
||||||
if !secureOnly {
|
if !secureOnly {
|
||||||
return name
|
return name
|
||||||
@ -101,7 +95,7 @@ func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
|
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
|
||||||
cookie, err := r.Cookie(SetCookiePrefix(name, c.domain, c.path, c.secureOnly))
|
cookie, err := r.Cookie(SetCookiePrefix(name, r.Host, c.path, c.secureOnly))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -111,11 +105,11 @@ func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, va
|
|||||||
return c.securecookie.Decode(name, cookie.Value, value)
|
return c.securecookie.Decode(name, cookie.Value, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) {
|
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, domain, value string) {
|
||||||
c.httpSet(w, name, value, c.maxAge)
|
c.httpSet(w, name, domain, value, c.maxAge)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, value interface{}) error {
|
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name, domain string, value interface{}) error {
|
||||||
if c.securecookie == nil {
|
if c.securecookie == nil {
|
||||||
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
|
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
|
||||||
}
|
}
|
||||||
@ -123,19 +117,19 @@ func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, v
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
c.httpSet(w, name, encoded, c.maxAge)
|
c.httpSet(w, name, domain, encoded, c.maxAge)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
|
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, r *http.Request, name string) {
|
||||||
c.httpSet(w, name, "", -1)
|
c.httpSet(w, name, r.Host, "", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
|
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, domain, value string, maxage int) {
|
||||||
http.SetCookie(w, &http.Cookie{
|
http.SetCookie(w, &http.Cookie{
|
||||||
Name: SetCookiePrefix(name, c.domain, c.path, c.secureOnly),
|
Name: SetCookiePrefix(name, domain, c.path, c.secureOnly),
|
||||||
Value: value,
|
Value: value,
|
||||||
Domain: c.domain,
|
Domain: strings.Split(domain, ":")[0],
|
||||||
Path: c.path,
|
Path: c.path,
|
||||||
MaxAge: maxage,
|
MaxAge: maxage,
|
||||||
HttpOnly: c.httpOnly,
|
HttpOnly: c.httpOnly,
|
||||||
|
@ -34,7 +34,7 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (csp *CSP) Value(nonce string) string {
|
func (csp *CSP) Value(nonce string, host string) string {
|
||||||
valuesMap := csp.asMap()
|
valuesMap := csp.asMap()
|
||||||
|
|
||||||
values := make([]string, 0, len(valuesMap))
|
values := make([]string, 0, len(valuesMap))
|
||||||
@ -43,7 +43,7 @@ func (csp *CSP) Value(nonce string) string {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce)))
|
values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce, host)))
|
||||||
}
|
}
|
||||||
|
|
||||||
return strings.Join(values, ";")
|
return strings.Join(values, ";")
|
||||||
@ -99,24 +99,33 @@ func (srcOpts CSPSourceOptions) AddHost(h ...string) CSPSourceOptions {
|
|||||||
return append(srcOpts, h...)
|
return append(srcOpts, h...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (srcOpts CSPSourceOptions) AddOwnHost() CSPSourceOptions {
|
||||||
|
return append(srcOpts, placeHolderHost)
|
||||||
|
}
|
||||||
|
|
||||||
func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions {
|
func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions {
|
||||||
return srcOpts.add(s, "%v:")
|
return srcOpts.add(s, "%v:")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srcOpts CSPSourceOptions) AddNonce() CSPSourceOptions {
|
func (srcOpts CSPSourceOptions) AddNonce() CSPSourceOptions {
|
||||||
return append(srcOpts, "'nonce-%v'")
|
return append(srcOpts, fmt.Sprintf("'nonce-%s'", placeHolderNonce))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
placeHolderNonce = "{{nonce}}"
|
||||||
|
placeHolderHost = "{{host}}"
|
||||||
|
)
|
||||||
|
|
||||||
func (srcOpts CSPSourceOptions) AddHash(alg, b64v string) CSPSourceOptions {
|
func (srcOpts CSPSourceOptions) AddHash(alg, b64v string) CSPSourceOptions {
|
||||||
return append(srcOpts, fmt.Sprintf("'%v-%v'", alg, b64v))
|
return append(srcOpts, fmt.Sprintf("'%v-%v'", alg, b64v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srcOpts CSPSourceOptions) String(nonce string) string {
|
func (srcOpts CSPSourceOptions) String(nonce, host string) string {
|
||||||
value := strings.Join(srcOpts, " ")
|
value := strings.Join(srcOpts, " ")
|
||||||
if !strings.Contains(value, "%v") {
|
if !strings.Contains(value, placeHolderNonce) && !strings.Contains(value, placeHolderHost) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(value, nonce)
|
return strings.ReplaceAll(strings.ReplaceAll(value, placeHolderHost, host), placeHolderNonce, nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srcOpts CSPSourceOptions) add(values []string, format string) CSPSourceOptions {
|
func (srcOpts CSPSourceOptions) add(values []string, format string) CSPSourceOptions {
|
||||||
|
@ -63,7 +63,7 @@ func (h *headers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
r = saveContext(r, nonceKey, nonce)
|
r = saveContext(r, nonceKey, nonce)
|
||||||
}
|
}
|
||||||
headers := w.Header()
|
headers := w.Header()
|
||||||
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce))
|
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce, r.Host))
|
||||||
headers.Set(http_utils.XXSSProtection, "1; mode=block")
|
headers.Set(http_utils.XXSSProtection, "1; mode=block")
|
||||||
headers.Set(http_utils.StrictTransportSecurity, "max-age=31536000; includeSubDomains")
|
headers.Set(http_utils.StrictTransportSecurity, "max-age=31536000; includeSubDomains")
|
||||||
headers.Set(http_utils.XFrameOptions, "DENY")
|
headers.Set(http_utils.XFrameOptions, "DENY")
|
||||||
|
@ -21,10 +21,6 @@ func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
|
|||||||
return userAgentID, ok
|
return userAgentID, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func InstanceIDFromCtx(ctx context.Context) string {
|
|
||||||
return "" //TODO: implement
|
|
||||||
}
|
|
||||||
|
|
||||||
type UserAgent struct {
|
type UserAgent struct {
|
||||||
ID string
|
ID string
|
||||||
}
|
}
|
||||||
@ -41,10 +37,9 @@ type UserAgentCookieConfig struct {
|
|||||||
MaxAge time.Duration
|
MaxAge time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) {
|
func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) {
|
||||||
opts := []http_utils.CookieHandlerOpt{
|
opts := []http_utils.CookieHandlerOpt{
|
||||||
http_utils.WithEncryption(cookieKey, cookieKey),
|
http_utils.WithEncryption(cookieKey, cookieKey),
|
||||||
http_utils.WithDomain(domain),
|
|
||||||
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
|
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
|
||||||
}
|
}
|
||||||
if !externalSecure {
|
if !externalSecure {
|
||||||
@ -68,7 +63,7 @@ func (ua *userAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
if err == nil {
|
if err == nil {
|
||||||
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
|
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
|
||||||
r = r.WithContext(ctx)
|
r = r.WithContext(ctx)
|
||||||
ua.setUserAgent(w, agent)
|
ua.setUserAgent(w, r.Host, agent)
|
||||||
}
|
}
|
||||||
ua.nextHandler.ServeHTTP(w, r)
|
ua.nextHandler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
@ -90,8 +85,8 @@ func (ua *userAgentHandler) getUserAgent(r *http.Request) (*UserAgent, error) {
|
|||||||
return userAgent, nil
|
return userAgent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, agent *UserAgent) error {
|
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, host string, agent *UserAgent) error {
|
||||||
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, agent)
|
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, host, agent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
|
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
|
||||||
}
|
}
|
||||||
|
@ -32,9 +32,13 @@ func IsOrigin(rawOrigin string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func BuildHTTP(hostname string, externalPort uint16, secure bool) string {
|
func BuildHTTP(hostname string, externalPort uint16, secure bool) string {
|
||||||
|
return BuildOrigin(fmt.Sprintf("%s:%d", hostname, externalPort), secure)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildOrigin(host string, secure bool) string {
|
||||||
schema := "https"
|
schema := "https"
|
||||||
if !secure {
|
if !secure {
|
||||||
schema = "http"
|
schema = "http"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s://%s:%d", schema, hostname, externalPort)
|
return fmt.Sprintf("%s://%s", schema, host)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"github.com/caos/oidc/pkg/op"
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"github.com/caos/oidc/pkg/op"
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
@ -5,8 +5,8 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"github.com/caos/oidc/pkg/op"
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
@ -43,7 +43,7 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.ThrowInternal(err, "OIDC-mPxqP", "Errors.Internal")
|
return nil, errors.ThrowInternal(err, "OIDC-mPxqP", "Errors.Internal")
|
||||||
}
|
}
|
||||||
projectRoles, err := o.query.SearchProjectRoles(context.TODO(), &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
projectRoles, err := o.query.SearchProjectRoles(ctx, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"github.com/caos/oidc/pkg/op"
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/errors"
|
"github.com/caos/zitadel/internal/errors"
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
"gopkg.in/square/go-jose.v2"
|
"gopkg.in/square/go-jose.v2"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
@ -20,99 +21,121 @@ import (
|
|||||||
const (
|
const (
|
||||||
locksTable = "projections.locks"
|
locksTable = "projections.locks"
|
||||||
signingKey = "signing_key"
|
signingKey = "signing_key"
|
||||||
|
oidcUser = "OIDC"
|
||||||
|
|
||||||
|
retryBackoff = 500 * time.Millisecond
|
||||||
|
retryCount = 3
|
||||||
|
lockDuration = retryCount * retryBackoff * 5
|
||||||
|
gracefulPeriod = 10 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err error) {
|
//SigningKey wraps the query.PrivateKey to implement the op.SigningKey interface
|
||||||
|
type SigningKey struct {
|
||||||
|
algorithm jose.SignatureAlgorithm
|
||||||
|
id string
|
||||||
|
key interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigningKey) SignatureAlgorithm() jose.SignatureAlgorithm {
|
||||||
|
return s.algorithm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigningKey) Key() interface{} {
|
||||||
|
return s.key
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SigningKey) ID() string {
|
||||||
|
return s.id
|
||||||
|
}
|
||||||
|
|
||||||
|
//PublicKey wraps the query.PublicKey to implement the op.Key interface
|
||||||
|
type PublicKey struct {
|
||||||
|
key query.PublicKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKey) Algorithm() jose.SignatureAlgorithm {
|
||||||
|
return jose.SignatureAlgorithm(s.key.Algorithm())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKey) Use() string {
|
||||||
|
return s.key.Use().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKey) Key() interface{} {
|
||||||
|
return s.key.Key()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicKey) ID() string {
|
||||||
|
return s.key.ID()
|
||||||
|
}
|
||||||
|
|
||||||
|
//KeySet implements the op.Storage interface
|
||||||
|
func (o *OPStorage) KeySet(ctx context.Context) (keys []op.Key, err error) {
|
||||||
ctx, span := tracing.NewSpan(ctx)
|
ctx, span := tracing.NewSpan(ctx)
|
||||||
defer func() { span.EndWithError(err) }()
|
defer func() { span.EndWithError(err) }()
|
||||||
keys, err := o.query.ActivePublicKeys(ctx, time.Now())
|
err = retry(func() error {
|
||||||
|
publicKeys, err := o.query.ActivePublicKeys(ctx, time.Now())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
keys = make([]op.Key, len(publicKeys.Keys))
|
||||||
|
for i, key := range publicKeys.Keys {
|
||||||
|
keys[i] = &PublicKey{key}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return keys, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//SignatureAlgorithms implements the op.Storage interface
|
||||||
|
func (o *OPStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||||
|
key, err := o.SigningKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
webKeys := make([]jose.JSONWebKey, len(keys.Keys))
|
return []jose.SignatureAlgorithm{key.SignatureAlgorithm()}, nil
|
||||||
for i, key := range keys.Keys {
|
|
||||||
webKeys[i] = jose.JSONWebKey{
|
|
||||||
KeyID: key.ID(),
|
|
||||||
Algorithm: key.Algorithm(),
|
|
||||||
Use: key.Use().String(),
|
|
||||||
Key: key.Key(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &jose.JSONWebKeySet{Keys: webKeys}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) {
|
//SigningKey implements the op.Storage interface
|
||||||
renewTimer := time.NewTimer(0)
|
func (o *OPStorage) SigningKey(ctx context.Context) (key op.SigningKey, err error) {
|
||||||
go func() {
|
err = retry(func() error {
|
||||||
for {
|
key, err = o.getSigningKey(ctx)
|
||||||
select {
|
if err != nil {
|
||||||
case <-ctx.Done():
|
return err
|
||||||
return
|
|
||||||
case <-o.keyChan:
|
|
||||||
if !renewTimer.Stop() {
|
|
||||||
<-renewTimer.C
|
|
||||||
}
|
|
||||||
checkAfter := o.resetTimer(renewTimer, true)
|
|
||||||
logging.Infof("requested next signing key check in %s", checkAfter)
|
|
||||||
case <-renewTimer.C:
|
|
||||||
o.getSigningKey(ctx, renewTimer, keyCh)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}()
|
if key == nil {
|
||||||
|
return errors.ThrowInternal(nil, "test", "test")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return key, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) getSigningKey(ctx context.Context, renewTimer *time.Timer, keyCh chan<- jose.SigningKey) {
|
func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||||
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(o.signingKeyGracefulPeriod))
|
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(gracefulPeriod))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
checkAfter := o.resetTimer(renewTimer, true)
|
return nil, err
|
||||||
logging.Infof("next signing key check in %s", checkAfter)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
if len(keys.Keys) == 0 {
|
if len(keys.Keys) > 0 {
|
||||||
var sequence uint64
|
return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
|
||||||
if keys.LatestSequence != nil {
|
|
||||||
sequence = keys.LatestSequence.Sequence
|
|
||||||
}
|
|
||||||
o.refreshSigningKey(ctx, keyCh, o.signingKeyAlgorithm, sequence)
|
|
||||||
checkAfter := o.resetTimer(renewTimer, true)
|
|
||||||
logging.Infof("next signing key check in %s", checkAfter)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = o.exchangeSigningKey(selectSigningKey(keys.Keys), keyCh)
|
var sequence uint64
|
||||||
logging.OnError(err).Error("could not exchange signing key")
|
if keys.LatestSequence != nil {
|
||||||
checkAfter := o.resetTimer(renewTimer, err != nil)
|
sequence = keys.LatestSequence.Sequence
|
||||||
logging.Infof("next signing key check in %s", checkAfter)
|
}
|
||||||
|
return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, sequence)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) resetTimer(timer *time.Timer, shortRefresh bool) (nextCheck time.Duration) {
|
func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, sequence uint64) error {
|
||||||
nextCheck = o.signingKeyRotationCheck
|
|
||||||
defer func() { timer.Reset(nextCheck) }()
|
|
||||||
if shortRefresh || o.currentKey == nil {
|
|
||||||
return nextCheck
|
|
||||||
}
|
|
||||||
maxLifetime := time.Until(o.currentKey.Expiry())
|
|
||||||
if maxLifetime < o.signingKeyGracefulPeriod+2*o.signingKeyRotationCheck {
|
|
||||||
return nextCheck
|
|
||||||
}
|
|
||||||
return maxLifetime - o.signingKeyGracefulPeriod - o.signingKeyRotationCheck
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *OPStorage) refreshSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, algorithm string, sequence uint64) {
|
|
||||||
if o.currentKey != nil && o.currentKey.Expiry().Before(time.Now().UTC()) {
|
|
||||||
logging.Info("unset current signing key")
|
|
||||||
keyCh <- jose.SigningKey{}
|
|
||||||
}
|
|
||||||
ok, err := o.ensureIsLatestKey(ctx, sequence)
|
ok, err := o.ensureIsLatestKey(ctx, sequence)
|
||||||
if err != nil {
|
if err != nil || !ok {
|
||||||
logging.New().WithError(err).Error("could not ensure latest key")
|
return errors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
|
||||||
return
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
logging.Warn("view not up to date, retrying later")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = o.lockAndGenerateSigningKeyPair(ctx, algorithm)
|
err = o.lockAndGenerateSigningKeyPair(ctx, algorithm)
|
||||||
logging.OnError(err).Warn("could not create signing key")
|
if err != nil {
|
||||||
|
return errors.ThrowInternal(err, "OIDC-ADh31", "could not create signing key")
|
||||||
|
}
|
||||||
|
return errors.ThrowInternal(nil, "OIDC-Df1bh", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (bool, error) {
|
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (bool, error) {
|
||||||
@ -123,29 +146,20 @@ func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (boo
|
|||||||
return sequence == maxSequence, nil
|
return sequence == maxSequence, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) exchangeSigningKey(key query.PrivateKey, keyCh chan<- jose.SigningKey) (err error) {
|
func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) {
|
||||||
if o.currentKey != nil && o.currentKey.ID() == key.ID() {
|
|
||||||
logging.Info("no new signing key")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
keyData, err := crypto.Decrypt(key.Key(), o.encAlg)
|
keyData, err := crypto.Decrypt(key.Key(), o.encAlg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
privateKey, err := crypto.BytesToPrivateKey(keyData)
|
privateKey, err := crypto.BytesToPrivateKey(keyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
keyCh <- jose.SigningKey{
|
return &SigningKey{
|
||||||
Algorithm: jose.SignatureAlgorithm(key.Algorithm()),
|
algorithm: jose.SignatureAlgorithm(key.Algorithm()),
|
||||||
Key: jose.JSONWebKey{
|
key: privateKey,
|
||||||
KeyID: key.ID(),
|
id: key.ID(),
|
||||||
Key: privateKey,
|
}, nil
|
||||||
},
|
|
||||||
}
|
|
||||||
o.currentKey = key
|
|
||||||
logging.WithFields("keyID", key.ID()).Info("exchanged signing key")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||||
@ -154,7 +168,7 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
|
|||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
errs := o.locker.Lock(ctx, o.signingKeyRotationCheck*2, authz.GetInstance(ctx).InstanceID())
|
errs := o.locker.Lock(ctx, lockDuration, authz.GetInstance(ctx).InstanceID())
|
||||||
err, ok := <-errs
|
err, ok := <-errs
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
if errors.IsErrorAlreadyExists(err) {
|
if errors.IsErrorAlreadyExists(err) {
|
||||||
@ -164,13 +178,13 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.command.GenerateSigningKeyPair(ctx, algorithm)
|
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
|
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
|
||||||
return o.eventstore.LatestSequence(ctx,
|
return o.eventstore.LatestSequence(ctx,
|
||||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
|
||||||
ResourceOwner("system"). //TODO: change with multi issuer
|
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
|
||||||
AddQuery().
|
AddQuery().
|
||||||
AggregateTypes(keypair.AggregateType).
|
AggregateTypes(keypair.AggregateType).
|
||||||
Builder(),
|
Builder(),
|
||||||
@ -180,3 +194,18 @@ func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
|
|||||||
func selectSigningKey(keys []query.PrivateKey) query.PrivateKey {
|
func selectSigningKey(keys []query.PrivateKey) query.PrivateKey {
|
||||||
return keys[len(keys)-1]
|
return keys[len(keys)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func setOIDCCtx(ctx context.Context) context.Context {
|
||||||
|
return authz.SetCtxData(ctx, authz.CtxData{UserID: oidcUser, OrgID: authz.GetInstance(ctx).InstanceID()})
|
||||||
|
}
|
||||||
|
|
||||||
|
func retry(retryable func() error) (err error) {
|
||||||
|
for i := 0; i < retryCount; i++ {
|
||||||
|
time.Sleep(retryBackoff)
|
||||||
|
err = retryable()
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/op"
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
"github.com/rakyll/statik/fs"
|
"github.com/rakyll/statik/fs"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
@ -17,7 +17,6 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/api/ui/login"
|
"github.com/caos/zitadel/internal/api/ui/login"
|
||||||
"github.com/caos/zitadel/internal/auth/repository"
|
"github.com/caos/zitadel/internal/auth/repository"
|
||||||
"github.com/caos/zitadel/internal/command"
|
"github.com/caos/zitadel/internal/command"
|
||||||
"github.com/caos/zitadel/internal/config/systemdefaults"
|
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"github.com/caos/zitadel/internal/crypto"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
@ -74,26 +73,23 @@ type OPStorage struct {
|
|||||||
defaultRefreshTokenIdleExpiration time.Duration
|
defaultRefreshTokenIdleExpiration time.Duration
|
||||||
defaultRefreshTokenExpiration time.Duration
|
defaultRefreshTokenExpiration time.Duration
|
||||||
encAlg crypto.EncryptionAlgorithm
|
encAlg crypto.EncryptionAlgorithm
|
||||||
keyChan <-chan interface{}
|
|
||||||
currentKey query.PrivateKey
|
|
||||||
signingKeyRotationCheck time.Duration
|
|
||||||
signingKeyGracefulPeriod time.Duration
|
|
||||||
locker crdb.Locker
|
locker crdb.Locker
|
||||||
assetAPIPrefix string
|
assetAPIPrefix string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
|
func NewProvider(ctx context.Context, config Config, defaultLogoutRedirectURI string, externalSecure bool, command *command.Commands, query *query.Queries, repo repository.Repository, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
|
||||||
opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey)
|
opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
|
return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
|
||||||
}
|
}
|
||||||
storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan)
|
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections)
|
||||||
options, err := createOptions(config, userAgentCookie, instanceHandler)
|
options, err := createOptions(config, externalSecure, userAgentCookie, instanceHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
|
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
|
||||||
}
|
}
|
||||||
provider, err := op.NewOpenIDProvider(
|
provider, err := op.NewDynamicOpenIDProvider(
|
||||||
ctx,
|
ctx,
|
||||||
|
HandlerPrefix,
|
||||||
opConfig,
|
opConfig,
|
||||||
storage,
|
storage,
|
||||||
options...,
|
options...,
|
||||||
@ -104,17 +100,12 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire
|
|||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Issuer(domain string, port uint16, externalSecure bool) string {
|
func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
|
||||||
return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix
|
|
||||||
}
|
|
||||||
|
|
||||||
func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
|
|
||||||
supportedLanguages, err := getSupportedLanguages()
|
supportedLanguages, err := getSupportedLanguages()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
opConfig := &op.Config{
|
opConfig := &op.Config{
|
||||||
Issuer: issuer,
|
|
||||||
DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
|
DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
|
||||||
CodeMethodS256: config.CodeMethodS256,
|
CodeMethodS256: config.CodeMethodS256,
|
||||||
AuthMethodPost: config.AuthMethodPost,
|
AuthMethodPost: config.AuthMethodPost,
|
||||||
@ -130,21 +121,26 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryp
|
|||||||
return opConfig, nil
|
return opConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createOptions(config Config, userAgentCookie, instanceHandler func(http.Handler) http.Handler) ([]op.Option, error) {
|
func createOptions(config Config, externalSecure bool, userAgentCookie, instanceHandler func(http.Handler) http.Handler) ([]op.Option, error) {
|
||||||
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
|
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
|
||||||
interceptor := op.WithHttpInterceptors(
|
options := []op.Option{
|
||||||
middleware.MetricsHandler(metricTypes),
|
op.WithHttpInterceptors(
|
||||||
middleware.TelemetryHandler(),
|
middleware.MetricsHandler(metricTypes),
|
||||||
middleware.NoCacheInterceptor,
|
middleware.TelemetryHandler(),
|
||||||
instanceHandler,
|
middleware.NoCacheInterceptor,
|
||||||
userAgentCookie,
|
instanceHandler,
|
||||||
http_utils.CopyHeadersToContext,
|
userAgentCookie,
|
||||||
)
|
http_utils.CopyHeadersToContext,
|
||||||
endpoints := customEndpoints(config.CustomEndpoints)
|
),
|
||||||
if len(endpoints) == 0 {
|
|
||||||
return []op.Option{interceptor}, nil
|
|
||||||
}
|
}
|
||||||
return append(endpoints, interceptor), nil
|
if !externalSecure {
|
||||||
|
options = append(options, op.WithAllowInsecure())
|
||||||
|
}
|
||||||
|
endpoints := customEndpoints(config.CustomEndpoints)
|
||||||
|
if len(endpoints) != 0 {
|
||||||
|
options = append(options, endpoints...)
|
||||||
|
}
|
||||||
|
return options, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
|
func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
|
||||||
@ -176,7 +172,7 @@ func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
|
|||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}) *OPStorage {
|
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB) *OPStorage {
|
||||||
return &OPStorage{
|
return &OPStorage{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
command: command,
|
command: command,
|
||||||
@ -189,10 +185,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries,
|
|||||||
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
|
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
|
||||||
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
|
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
|
||||||
encAlg: encAlg,
|
encAlg: encAlg,
|
||||||
signingKeyGracefulPeriod: keyConfig.SigningKeyGracefulPeriod,
|
|
||||||
signingKeyRotationCheck: keyConfig.SigningKeyRotationCheck,
|
|
||||||
locker: crdb.NewLocker(projections, locksTable, signingKey),
|
locker: crdb.NewLocker(projections, locksTable, signingKey),
|
||||||
keyChan: keyChan,
|
|
||||||
assetAPIPrefix: assets.HandlerPrefix,
|
assetAPIPrefix: assets.HandlerPrefix,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,13 +8,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
|
"github.com/caos/oidc/v2/pkg/op"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
http_util "github.com/caos/zitadel/internal/api/http"
|
||||||
"github.com/caos/zitadel/internal/api/http/middleware"
|
"github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ func (i *spaHandler) Open(name string) (http.File, error) {
|
|||||||
return i.fileSystem.Open("/index.html")
|
return i.fileSystem.Open("/index.html")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(config Config, domain, url, issuer string, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) {
|
func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) {
|
||||||
fSys, err := fs.Sub(static, "static")
|
fSys, err := fs.Sub(static, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -70,7 +70,7 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
|
|||||||
config.LongCache.MaxAge,
|
config.LongCache.MaxAge,
|
||||||
config.LongCache.SharedMaxAge,
|
config.LongCache.SharedMaxAge,
|
||||||
)
|
)
|
||||||
security := middleware.SecurityHeaders(csp(domain), nil)
|
security := middleware.SecurityHeaders(csp(), nil)
|
||||||
|
|
||||||
handler := &http.ServeMux{}
|
handler := &http.ServeMux{}
|
||||||
handler.Handle("/", cache(security(http.FileServer(&spaHandler{http.FS(fSys)}))))
|
handler.Handle("/", cache(security(http.FileServer(&spaHandler{http.FS(fSys)}))))
|
||||||
@ -80,7 +80,8 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
|
|||||||
http.Error(w, "empty instanceID", http.StatusInternalServerError)
|
http.Error(w, "empty instanceID", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
environmentJSON, err := createEnvironmentJSON(url, issuer, instance.ConsoleClientID())
|
url := http_util.BuildOrigin(r.Host, externalSecure)
|
||||||
|
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
|
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@ -91,15 +92,12 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
|
|||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func csp(zitadelDomain string) *middleware.CSP {
|
func csp() *middleware.CSP {
|
||||||
if !strings.HasPrefix(zitadelDomain, "*.") {
|
|
||||||
zitadelDomain = "*." + zitadelDomain
|
|
||||||
}
|
|
||||||
csp := middleware.DefaultSCP
|
csp := middleware.DefaultSCP
|
||||||
csp.StyleSrc = csp.StyleSrc.AddInline()
|
csp.StyleSrc = csp.StyleSrc.AddInline()
|
||||||
csp.ScriptSrc = csp.ScriptSrc.AddEval()
|
csp.ScriptSrc = csp.ScriptSrc.AddEval()
|
||||||
csp.ConnectSrc = csp.ConnectSrc.AddHost(zitadelDomain)
|
csp.ConnectSrc = csp.ConnectSrc.AddOwnHost()
|
||||||
csp.ImgSrc = csp.ImgSrc.AddHost(zitadelDomain).AddScheme("blob")
|
csp.ImgSrc = csp.ImgSrc.AddOwnHost().AddScheme("blob")
|
||||||
return &csp
|
return &csp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,10 +3,10 @@ package login
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"github.com/caos/zitadel/internal/api/authz"
|
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/actions"
|
"github.com/caos/zitadel/internal/actions"
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
)
|
)
|
||||||
|
@ -7,8 +7,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/client/rp"
|
"github.com/caos/oidc/v2/pkg/client/rp"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/client/rp"
|
"github.com/caos/oidc/v2/pkg/client/rp"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/oidc/pkg/client/rp"
|
"github.com/caos/oidc/v2/pkg/client/rp"
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
|
|
||||||
http_util "github.com/caos/zitadel/internal/api/http"
|
http_util "github.com/caos/zitadel/internal/api/http"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
@ -25,18 +25,17 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Login struct {
|
type Login struct {
|
||||||
endpoint string
|
endpoint string
|
||||||
router http.Handler
|
router http.Handler
|
||||||
renderer *Renderer
|
renderer *Renderer
|
||||||
parser *form.Parser
|
parser *form.Parser
|
||||||
command *command.Commands
|
command *command.Commands
|
||||||
query *query.Queries
|
query *query.Queries
|
||||||
staticStorage static.Storage
|
staticStorage static.Storage
|
||||||
//staticCache cache.Cache //TODO: enable when storage is implemented again
|
|
||||||
authRepo auth_repository.Repository
|
authRepo auth_repository.Repository
|
||||||
baseURL string
|
baseURL string
|
||||||
consolePath string
|
consolePath string
|
||||||
oidcAuthCallbackURL func(string) string
|
oidcAuthCallbackURL func(context.Context, string) string
|
||||||
idpConfigAlg crypto.EncryptionAlgorithm
|
idpConfigAlg crypto.EncryptionAlgorithm
|
||||||
userCodeAlg crypto.EncryptionAlgorithm
|
userCodeAlg crypto.EncryptionAlgorithm
|
||||||
iamDomain string
|
iamDomain string
|
||||||
@ -46,7 +45,6 @@ type Config struct {
|
|||||||
LanguageCookieName string
|
LanguageCookieName string
|
||||||
CSRFCookieName string
|
CSRFCookieName string
|
||||||
Cache middleware.CacheConfig
|
Cache middleware.CacheConfig
|
||||||
//StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -64,9 +62,10 @@ func CreateLogin(config Config,
|
|||||||
consolePath,
|
consolePath,
|
||||||
domain,
|
domain,
|
||||||
baseURL string,
|
baseURL string,
|
||||||
oidcAuthCallbackURL func(string) string,
|
oidcAuthCallbackURL func(context.Context, string) string,
|
||||||
externalSecure bool,
|
externalSecure bool,
|
||||||
userAgentCookie,
|
userAgentCookie,
|
||||||
|
issuerInterceptor,
|
||||||
instanceHandler mux.MiddlewareFunc,
|
instanceHandler mux.MiddlewareFunc,
|
||||||
userCodeAlg crypto.EncryptionAlgorithm,
|
userCodeAlg crypto.EncryptionAlgorithm,
|
||||||
idpConfigAlg crypto.EncryptionAlgorithm,
|
idpConfigAlg crypto.EncryptionAlgorithm,
|
||||||
@ -85,12 +84,6 @@ func CreateLogin(config Config,
|
|||||||
idpConfigAlg: idpConfigAlg,
|
idpConfigAlg: idpConfigAlg,
|
||||||
userCodeAlg: userCodeAlg,
|
userCodeAlg: userCodeAlg,
|
||||||
}
|
}
|
||||||
//TODO: enable when storage is implemented again
|
|
||||||
//login.staticCache, err = config.StaticCache.Config.NewCache()
|
|
||||||
//if err != nil {
|
|
||||||
// return nil, fmt.Errorf("unable to create storage cache: %w", err)
|
|
||||||
//}
|
|
||||||
|
|
||||||
statikFS, err := fs.NewWithNamespace("login")
|
statikFS, err := fs.NewWithNamespace("login")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to create filesystem: %w", err)
|
return nil, fmt.Errorf("unable to create filesystem: %w", err)
|
||||||
@ -105,7 +98,8 @@ func CreateLogin(config Config,
|
|||||||
return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err)
|
return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err)
|
||||||
}
|
}
|
||||||
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
|
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
|
||||||
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources))
|
|
||||||
|
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources), issuerInterceptor)
|
||||||
login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage)
|
login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage)
|
||||||
login.parser = form.NewParser()
|
login.parser = form.NewParser()
|
||||||
return login, nil
|
return login, nil
|
||||||
|
@ -43,11 +43,11 @@ func (l *Login) renderSuccessAndCallback(w http.ResponseWriter, r *http.Request,
|
|||||||
userData: l.getUserData(r, authReq, "Login Successful", errID, errMessage),
|
userData: l.getUserData(r, authReq, "Login Successful", errID, errMessage),
|
||||||
}
|
}
|
||||||
if authReq != nil {
|
if authReq != nil {
|
||||||
data.RedirectURI = l.oidcAuthCallbackURL("") //the id will be set via the html (maybe change this with the login refactoring)
|
data.RedirectURI = l.oidcAuthCallbackURL(r.Context(), "") //the id will be set via the html (maybe change this with the login refactoring)
|
||||||
}
|
}
|
||||||
l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplLoginSuccess], data, nil)
|
l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplLoginSuccess], data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
|
func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
|
||||||
http.Redirect(w, r, l.oidcAuthCallbackURL(authReq.ID), http.StatusFound)
|
http.Redirect(w, r, l.oidcAuthCallbackURL(r.Context(), authReq.ID), http.StatusFound)
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ type Commands struct {
|
|||||||
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
|
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
|
||||||
|
|
||||||
multifactors domain.MultifactorConfigs
|
multifactors domain.MultifactorConfigs
|
||||||
webauthn *webauthn_helper.WebAuthN
|
webauthnConfig *webauthn_helper.Config
|
||||||
keySize int
|
keySize int
|
||||||
keyAlgorithm crypto.EncryptionAlgorithm
|
keyAlgorithm crypto.EncryptionAlgorithm
|
||||||
privateKeyLifetime time.Duration
|
privateKeyLifetime time.Duration
|
||||||
@ -60,7 +60,7 @@ func StartCommands(es *eventstore.Eventstore,
|
|||||||
zitadelRoles []authz.RoleMapping,
|
zitadelRoles []authz.RoleMapping,
|
||||||
staticStore static.Storage,
|
staticStore static.Storage,
|
||||||
authZRepo authz_repo.Repository,
|
authZRepo authz_repo.Repository,
|
||||||
webAuthN webauthn_helper.Config,
|
webAuthN *webauthn_helper.Config,
|
||||||
idpConfigEncryption,
|
idpConfigEncryption,
|
||||||
otpEncryption,
|
otpEncryption,
|
||||||
smtpEncryption,
|
smtpEncryption,
|
||||||
@ -84,6 +84,7 @@ func StartCommands(es *eventstore.Eventstore,
|
|||||||
userEncryption: userEncryption,
|
userEncryption: userEncryption,
|
||||||
domainVerificationAlg: domainVerificationEncryption,
|
domainVerificationAlg: domainVerificationEncryption,
|
||||||
keyAlgorithm: oidcEncryption,
|
keyAlgorithm: oidcEncryption,
|
||||||
|
webauthnConfig: webAuthN,
|
||||||
}
|
}
|
||||||
|
|
||||||
instance_repo.RegisterEventMappers(repo.eventstore)
|
instance_repo.RegisterEventMappers(repo.eventstore)
|
||||||
@ -107,11 +108,6 @@ func StartCommands(es *eventstore.Eventstore,
|
|||||||
|
|
||||||
repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg)
|
repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg)
|
||||||
repo.domainVerificationValidator = http.ValidateDomain
|
repo.domainVerificationValidator = http.ValidateDomain
|
||||||
web, err := webauthn_helper.StartServer(webAuthN)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
repo.webauthn = web
|
|
||||||
|
|
||||||
repo.tokenVerifier = authZRepo
|
repo.tokenVerifier = authZRepo
|
||||||
return repo, nil
|
return repo, nil
|
||||||
|
@ -28,16 +28,6 @@ const (
|
|||||||
consolePostLogoutPath = console.HandlerPrefix + "/signedout"
|
consolePostLogoutPath = console.HandlerPrefix + "/signedout"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AddInstance struct {
|
|
||||||
InstanceName string
|
|
||||||
CustomDomain string
|
|
||||||
FirstOrgName string
|
|
||||||
OwnerEmail string
|
|
||||||
OwnerUsername string
|
|
||||||
OwnerFirstName string
|
|
||||||
OwnerLastName string
|
|
||||||
}
|
|
||||||
|
|
||||||
type InstanceSetup struct {
|
type InstanceSetup struct {
|
||||||
zitadel ZitadelConfig
|
zitadel ZitadelConfig
|
||||||
InstanceName string
|
InstanceName string
|
||||||
@ -301,7 +291,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup, exte
|
|||||||
}
|
}
|
||||||
|
|
||||||
if setup.CustomDomain != "" {
|
if setup.CustomDomain != "" {
|
||||||
validations = append(validations, c.addInstanceDomain(instanceAgg, setup.CustomDomain, false))
|
validations = append(validations, addInstanceDomain(instanceAgg, setup.CustomDomain, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
console := &addOIDCApp{
|
console := &addOIDCApp{
|
||||||
|
@ -15,7 +15,7 @@ import (
|
|||||||
|
|
||||||
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
validation := c.addInstanceDomain(instanceAgg, instanceDomain, false)
|
validation := addInstanceDomain(instanceAgg, instanceDomain, false)
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -33,7 +33,7 @@ func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string)
|
|||||||
|
|
||||||
func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
validation := c.setPrimaryInstanceDomain(instanceAgg, instanceDomain)
|
validation := setPrimaryInstanceDomain(instanceAgg, instanceDomain)
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -51,7 +51,7 @@ func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain
|
|||||||
|
|
||||||
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
|
||||||
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
|
||||||
validation := c.removeInstanceDomain(instanceAgg, instanceDomain)
|
validation := removeInstanceDomain(instanceAgg, instanceDomain)
|
||||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -69,16 +69,16 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
|
|||||||
|
|
||||||
func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation {
|
func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation {
|
||||||
domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain)
|
domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain)
|
||||||
return c.addInstanceDomain(a, domain, true)
|
return addInstanceDomain(a, domain, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
|
func addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
|
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -88,7 +88,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
|||||||
events := []eventstore.Command{
|
events := []eventstore.Command{
|
||||||
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
|
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
|
||||||
}
|
}
|
||||||
appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
|
appWriteModel, err := getOIDCAppWriteModel(ctx, filter, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -115,13 +115,13 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
func setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
|
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -133,13 +133,13 @@ func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomai
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
func removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
|
||||||
return func() (preparation.CreateCommands, error) {
|
return func() (preparation.CreateCommands, error) {
|
||||||
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
|
||||||
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
|
||||||
}
|
}
|
||||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||||
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain)
|
domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -154,11 +154,16 @@ func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain st
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Commands) getInstanceDomainWriteModel(ctx context.Context, domain string) (*InstanceDomainWriteModel, error) {
|
func getInstanceDomainWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, domain string) (*InstanceDomainWriteModel, error) {
|
||||||
domainWriteModel := NewInstanceDomainWriteModel(ctx, domain)
|
domainWriteModel := NewInstanceDomainWriteModel(ctx, domain)
|
||||||
err := c.eventstore.FilterToQueryReducer(ctx, domainWriteModel)
|
events, err := filter(ctx, domainWriteModel.Query())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return domainWriteModel, nil
|
if len(events) == 0 {
|
||||||
|
return domainWriteModel, nil
|
||||||
|
}
|
||||||
|
domainWriteModel.AppendEvents(events...)
|
||||||
|
err = domainWriteModel.Reduce()
|
||||||
|
return domainWriteModel, err
|
||||||
}
|
}
|
||||||
|
@ -10,12 +10,7 @@ import (
|
|||||||
"github.com/caos/zitadel/internal/repository/keypair"
|
"github.com/caos/zitadel/internal/repository/keypair"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
oidcUser = "OIDC"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||||
ctx = setOIDCCtx(ctx)
|
|
||||||
privateCrypto, publicCrypto, err := crypto.GenerateEncryptedKeyPair(c.keySize, c.keyAlgorithm)
|
privateCrypto, publicCrypto, err := crypto.GenerateEncryptedKeyPair(c.keySize, c.keyAlgorithm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -28,8 +23,7 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
|
|||||||
privateKeyExp := time.Now().UTC().Add(c.privateKeyLifetime)
|
privateKeyExp := time.Now().UTC().Add(c.privateKeyLifetime)
|
||||||
publicKeyExp := time.Now().UTC().Add(c.publicKeyLifetime)
|
publicKeyExp := time.Now().UTC().Add(c.publicKeyLifetime)
|
||||||
|
|
||||||
//TODO: InstanceID not available here?
|
keyPairWriteModel := NewKeyPairWriteModel(keyID, authz.GetInstance(ctx).InstanceID())
|
||||||
keyPairWriteModel := NewKeyPairWriteModel(keyID, "system") //TODO: change with multi issuer
|
|
||||||
keyAgg := KeyPairAggregateFromWriteModel(&keyPairWriteModel.WriteModel)
|
keyAgg := KeyPairAggregateFromWriteModel(&keyPairWriteModel.WriteModel)
|
||||||
_, err = c.eventstore.Push(ctx, keypair.NewAddedEvent(
|
_, err = c.eventstore.Push(ctx, keypair.NewAddedEvent(
|
||||||
ctx,
|
ctx,
|
||||||
@ -40,8 +34,3 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
|
|||||||
privateKeyExp, publicKeyExp))
|
privateKeyExp, publicKeyExp))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func setOIDCCtx(ctx context.Context) context.Context {
|
|
||||||
//TODO: InstanceID not available here?
|
|
||||||
return authz.SetCtxData(ctx, authz.CtxData{UserID: oidcUser, OrgID: authz.GetInstance(ctx).InstanceID()})
|
|
||||||
}
|
|
||||||
|
@ -3,8 +3,7 @@ package command
|
|||||||
import (
|
import (
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
keypair "github.com/caos/zitadel/internal/repository/keypair"
|
"github.com/caos/zitadel/internal/repository/keypair"
|
||||||
"github.com/caos/zitadel/internal/repository/project"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type KeyPairWriteModel struct {
|
type KeyPairWriteModel struct {
|
||||||
@ -52,7 +51,7 @@ func (wm *KeyPairWriteModel) Query() *eventstore.SearchQueryBuilder {
|
|||||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||||
ResourceOwner(wm.ResourceOwner).
|
ResourceOwner(wm.ResourceOwner).
|
||||||
AddQuery().
|
AddQuery().
|
||||||
AggregateTypes(project.AggregateType).
|
AggregateTypes(keypair.AggregateType).
|
||||||
AggregateIDs(wm.AggregateID).
|
AggregateIDs(wm.AggregateID).
|
||||||
EventTypes(keypair.AddedEventType).
|
EventTypes(keypair.AddedEventType).
|
||||||
Builder()
|
Builder()
|
||||||
|
@ -321,3 +321,17 @@ func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, r
|
|||||||
}
|
}
|
||||||
return appWriteModel, nil
|
return appWriteModel, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getOIDCAppWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) {
|
||||||
|
appWriteModel := NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
|
||||||
|
events, err := filter(ctx, appWriteModel.Query())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(events) == 0 {
|
||||||
|
return appWriteModel, nil
|
||||||
|
}
|
||||||
|
appWriteModel.AppendEvents(events...)
|
||||||
|
err = appWriteModel.Reduce()
|
||||||
|
return appWriteModel, err
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/oidc/pkg/oidc"
|
"github.com/caos/oidc/v2/pkg/oidc"
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner s
|
|||||||
if accountName == "" {
|
if accountName == "" {
|
||||||
accountName = user.EmailAddress
|
accountName = user.EmailAddress
|
||||||
}
|
}
|
||||||
webAuthN, err := c.webauthn.BeginRegistration(user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
webAuthN, err := c.webauthnConfig.BeginRegistration(ctx, user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@ -272,7 +272,7 @@ func (c *Commands) verifyHumanWebAuthN(ctx context.Context, userID, resourceowne
|
|||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
_, token := domain.GetTokenToVerify(tokens)
|
_, token := domain.GetTokenToVerify(tokens)
|
||||||
webAuthN, err := c.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "")
|
webAuthN, err := c.webauthnConfig.FinishRegistration(ctx, user, token, tokenName, credentialData, userAgentID != "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
@ -343,7 +343,7 @@ func (c *Commands) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
webAuthNLogin, err := c.webauthn.BeginLogin(human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
webAuthNLogin, err := c.webauthnConfig.BeginLogin(ctx, human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
@ -454,7 +454,7 @@ func (c *Commands) finishWebAuthNLogin(ctx context.Context, userID, resourceOwne
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
keyID, signCount, err := c.webauthn.FinishLogin(human, webAuthN, credentialData, isLoginUI, tokens...)
|
keyID, signCount, err := c.webauthnConfig.FinishLogin(ctx, human, webAuthN, credentialData, isLoginUI, tokens...)
|
||||||
if err != nil && keyID == nil {
|
if err != nil && keyID == nil {
|
||||||
return nil, nil, 0, err
|
return nil, nil, 0, err
|
||||||
}
|
}
|
||||||
|
@ -56,9 +56,7 @@ type Endpoints struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type KeyConfig struct {
|
type KeyConfig struct {
|
||||||
Size int
|
Size int
|
||||||
PrivateKeyLifetime time.Duration
|
PrivateKeyLifetime time.Duration
|
||||||
PublicKeyLifetime time.Duration
|
PublicKeyLifetime time.Duration
|
||||||
SigningKeyRotationCheck time.Duration
|
|
||||||
SigningKeyGracefulPeriod time.Duration
|
|
||||||
}
|
}
|
||||||
|
@ -145,8 +145,8 @@ func (t *Translator) Lang(r *http.Request) language.Tag {
|
|||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) {
|
func (t *Translator) SetLangCookie(w http.ResponseWriter, r *http.Request, lang language.Tag) {
|
||||||
t.cookieHandler.SetCookie(w, t.cookieName, lang.String())
|
t.cookieHandler.SetCookie(w, t.cookieName, r.Host, lang.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
|
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
|
||||||
@ -177,7 +177,7 @@ func (t *Translator) langsFromCtx(ctx context.Context) []string {
|
|||||||
langs := t.preferredLanguages
|
langs := t.preferredLanguages
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
ctxData := authz.GetCtxData(ctx)
|
ctxData := authz.GetCtxData(ctx)
|
||||||
if ctxData.PreferredLanguage != "" {
|
if ctxData.PreferredLanguage != language.Und.String() {
|
||||||
langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage)
|
langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage)
|
||||||
}
|
}
|
||||||
langs = append(langs, getAcceptLanguageHeader(ctx))
|
langs = append(langs, getAcceptLanguageHeader(ctx))
|
||||||
@ -190,6 +190,10 @@ func (t *Translator) SetPreferredLanguages(langs ...string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getAcceptLanguageHeader(ctx context.Context) string {
|
func getAcceptLanguageHeader(ctx context.Context) string {
|
||||||
|
acceptLanguage := metautils.ExtractIncoming(ctx).Get("accept-language")
|
||||||
|
if acceptLanguage != "" {
|
||||||
|
return acceptLanguage
|
||||||
|
}
|
||||||
return metautils.ExtractIncoming(ctx).Get("grpcgateway-accept-language")
|
return metautils.ExtractIncoming(ctx).Get("grpcgateway-accept-language")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -199,7 +203,7 @@ func localize(localizer *i18n.Localizer, id string, args map[string]interface{})
|
|||||||
TemplateData: args,
|
TemplateData: args,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.LogWithFields("I18N-MsF5sx", "id", id, "args", args).WithError(err).Warnf("missing translation")
|
logging.WithFields("id", id, "args", args).WithError(err).Warnf("missing translation")
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
errs "errors"
|
errs "errors"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
@ -163,9 +164,9 @@ func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
|
func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
|
||||||
stmt, scan := prepareInstanceQuery(host)
|
stmt, scan := prepareInstanceDomainQuery(host)
|
||||||
query, args, err := stmt.Where(sq.Eq{
|
query, args, err := stmt.Where(sq.Eq{
|
||||||
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
|
InstanceDomainDomainCol.identifier(): strings.Split(host, ":")[0],
|
||||||
}).ToSql()
|
}).ToSql()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement")
|
return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement")
|
||||||
@ -279,3 +280,47 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
|
||||||
|
return sq.Select(
|
||||||
|
InstanceColumnID.identifier(),
|
||||||
|
InstanceColumnCreationDate.identifier(),
|
||||||
|
InstanceColumnChangeDate.identifier(),
|
||||||
|
InstanceColumnSequence.identifier(),
|
||||||
|
InstanceColumnGlobalOrgID.identifier(),
|
||||||
|
InstanceColumnProjectID.identifier(),
|
||||||
|
InstanceColumnConsoleID.identifier(),
|
||||||
|
InstanceColumnConsoleAppID.identifier(),
|
||||||
|
InstanceColumnSetupStarted.identifier(),
|
||||||
|
InstanceColumnSetupDone.identifier(),
|
||||||
|
InstanceColumnDefaultLanguage.identifier(),
|
||||||
|
).
|
||||||
|
From(instanceTable.identifier()).
|
||||||
|
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)).
|
||||||
|
PlaceholderFormat(sq.Dollar),
|
||||||
|
func(row *sql.Row) (*Instance, error) {
|
||||||
|
instance := &Instance{Host: host}
|
||||||
|
lang := ""
|
||||||
|
err := row.Scan(
|
||||||
|
&instance.ID,
|
||||||
|
&instance.CreationDate,
|
||||||
|
&instance.ChangeDate,
|
||||||
|
&instance.Sequence,
|
||||||
|
&instance.GlobalOrgID,
|
||||||
|
&instance.IAMProjectID,
|
||||||
|
&instance.ConsoleID,
|
||||||
|
&instance.ConsoleAppID,
|
||||||
|
&instance.SetupStarted,
|
||||||
|
&instance.SetupDone,
|
||||||
|
&lang,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
if errs.Is(err, sql.ErrNoRows) {
|
||||||
|
return nil, errors.ThrowNotFound(err, "QUERY-n0wng", "Errors.IAM.NotFound")
|
||||||
|
}
|
||||||
|
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
|
||||||
|
}
|
||||||
|
instance.DefaultLanguage = language.Make(lang)
|
||||||
|
return instance, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -40,10 +40,9 @@ const (
|
|||||||
type KeyProjection struct {
|
type KeyProjection struct {
|
||||||
crdb.StatementHandler
|
crdb.StatementHandler
|
||||||
encryptionAlgorithm crypto.EncryptionAlgorithm
|
encryptionAlgorithm crypto.EncryptionAlgorithm
|
||||||
keyChan chan<- interface{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) *KeyProjection {
|
func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) *KeyProjection {
|
||||||
p := new(KeyProjection)
|
p := new(KeyProjection)
|
||||||
config.ProjectionName = KeyProjectionTable
|
config.ProjectionName = KeyProjectionTable
|
||||||
config.Reducers = p.reducers()
|
config.Reducers = p.reducers()
|
||||||
@ -56,7 +55,7 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
|
|||||||
crdb.NewColumn(KeyColumnInstanceID, crdb.ColumnTypeText),
|
crdb.NewColumn(KeyColumnInstanceID, crdb.ColumnTypeText),
|
||||||
crdb.NewColumn(KeyColumnSequence, crdb.ColumnTypeInt64),
|
crdb.NewColumn(KeyColumnSequence, crdb.ColumnTypeInt64),
|
||||||
crdb.NewColumn(KeyColumnAlgorithm, crdb.ColumnTypeText, crdb.Default("")),
|
crdb.NewColumn(KeyColumnAlgorithm, crdb.ColumnTypeText, crdb.Default("")),
|
||||||
crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeText, crdb.Default("")),
|
crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeEnum, crdb.Default(0)),
|
||||||
},
|
},
|
||||||
crdb.NewPrimaryKey(KeyColumnInstanceID, KeyColumnID),
|
crdb.NewPrimaryKey(KeyColumnInstanceID, KeyColumnID),
|
||||||
crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{KeyColumnID})),
|
crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{KeyColumnID})),
|
||||||
@ -79,7 +78,6 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
p.StatementHandler = crdb.NewStatementHandler(ctx, config)
|
||||||
p.keyChan = keyChan
|
|
||||||
p.encryptionAlgorithm = keyEncryptionAlgorithm
|
p.encryptionAlgorithm = keyEncryptionAlgorithm
|
||||||
|
|
||||||
return p
|
return p
|
||||||
@ -130,9 +128,6 @@ func (p *KeyProjection) reduceKeyPairAdded(event eventstore.Event) (*handler.Sta
|
|||||||
},
|
},
|
||||||
crdb.WithTableSuffix(privateKeyTableSuffix),
|
crdb.WithTableSuffix(privateKeyTableSuffix),
|
||||||
))
|
))
|
||||||
if p.keyChan != nil {
|
|
||||||
p.keyChan <- true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if e.PublicKey.Expiry.After(time.Now()) {
|
if e.PublicKey.Expiry.After(time.Now()) {
|
||||||
publicKey, err := crypto.Decrypt(e.PublicKey.Key, p.encryptionAlgorithm)
|
publicKey, err := crypto.Decrypt(e.PublicKey.Key, p.encryptionAlgorithm)
|
||||||
|
@ -17,7 +17,7 @@ const (
|
|||||||
FailedEventsTable = "projections.failed_events"
|
FailedEventsTable = "projections.failed_events"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) error {
|
func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
|
||||||
projectionConfig := crdb.StatementHandlerConfig{
|
projectionConfig := crdb.StatementHandlerConfig{
|
||||||
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
|
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
|
||||||
HandlerConfig: handler.HandlerConfig{
|
HandlerConfig: handler.HandlerConfig{
|
||||||
@ -74,7 +74,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
|
|||||||
NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"]))
|
NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"]))
|
||||||
NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"]))
|
NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"]))
|
||||||
NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
|
NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
|
||||||
NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, keyChan)
|
NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ type Queries struct {
|
|||||||
zitadelRoles []authz.RoleMapping
|
zitadelRoles []authz.RoleMapping
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
|
func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) {
|
||||||
statikLoginFS, err := fs.NewWithNamespace("login")
|
statikLoginFS, err := fs.NewWithNamespace("login")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to start login statik dir")
|
return nil, fmt.Errorf("unable to start login statik dir")
|
||||||
@ -66,7 +66,7 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql
|
|||||||
keypair.RegisterEventMappers(repo.eventstore)
|
keypair.RegisterEventMappers(repo.eventstore)
|
||||||
usergrant.RegisterEventMappers(repo.eventstore)
|
usergrant.RegisterEventMappers(repo.eventstore)
|
||||||
|
|
||||||
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm, keyChan)
|
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -2,38 +2,22 @@ package webauthn
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/duo-labs/webauthn/protocol"
|
"github.com/duo-labs/webauthn/protocol"
|
||||||
"github.com/duo-labs/webauthn/webauthn"
|
"github.com/duo-labs/webauthn/webauthn"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/api/authz"
|
||||||
|
"github.com/caos/zitadel/internal/api/http"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WebAuthN struct {
|
|
||||||
webAuthN *webauthn.WebAuthn
|
|
||||||
}
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
ID string
|
DisplayName string
|
||||||
Origin string
|
ExternalSecure bool
|
||||||
DisplayName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartServer(config Config) (*WebAuthN, error) {
|
|
||||||
webAuthN, err := webauthn.New(&webauthn.Config{
|
|
||||||
RPDisplayName: config.DisplayName,
|
|
||||||
RPID: config.ID,
|
|
||||||
RPOrigin: config.Origin,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &WebAuthN{
|
|
||||||
webAuthN: webAuthN,
|
|
||||||
}, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type webUser struct {
|
type webUser struct {
|
||||||
@ -54,7 +38,10 @@ func (u *webUser) WebAuthnName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *webUser) WebAuthnDisplayName() string {
|
func (u *webUser) WebAuthnDisplayName() string {
|
||||||
return u.DisplayName
|
if u.DisplayName != "" {
|
||||||
|
return u.DisplayName
|
||||||
|
}
|
||||||
|
return u.GetUsername()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *webUser) WebAuthnIcon() string {
|
func (u *webUser) WebAuthnIcon() string {
|
||||||
@ -65,7 +52,11 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
|
|||||||
return u.credentials
|
return u.credentials
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
|
func (w *Config) BeginRegistration(ctx context.Context, user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
|
||||||
|
webAuthNServer, err := w.serverFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
creds := WebAuthNsToCredentials(webAuthNs)
|
creds := WebAuthNsToCredentials(webAuthNs)
|
||||||
existing := make([]protocol.CredentialDescriptor, len(creds))
|
existing := make([]protocol.CredentialDescriptor, len(creds))
|
||||||
for i, cred := range creds {
|
for i, cred := range creds {
|
||||||
@ -74,7 +65,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
|
|||||||
CredentialID: cred.ID,
|
CredentialID: cred.ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
credentialOptions, sessionData, err := w.webAuthN.BeginRegistration(
|
credentialOptions, sessionData, err := webAuthNServer.BeginRegistration(
|
||||||
&webUser{
|
&webUser{
|
||||||
Human: user,
|
Human: user,
|
||||||
accountName: accountName,
|
accountName: accountName,
|
||||||
@ -102,7 +93,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
func (w *Config) FinishRegistration(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) {
|
||||||
if webAuthN == nil {
|
if webAuthN == nil {
|
||||||
return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound")
|
return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound")
|
||||||
}
|
}
|
||||||
@ -113,7 +104,11 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
|
|||||||
return nil, caos_errs.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential")
|
return nil, caos_errs.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential")
|
||||||
}
|
}
|
||||||
sessionData := WebAuthNToSessionData(webAuthN)
|
sessionData := WebAuthNToSessionData(webAuthN)
|
||||||
credential, err := w.webAuthN.CreateCredential(
|
webAuthNServer, err := w.serverFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
credential, err := webAuthNServer.CreateCredential(
|
||||||
&webUser{
|
&webUser{
|
||||||
Human: user,
|
Human: user,
|
||||||
},
|
},
|
||||||
@ -132,8 +127,12 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
|
|||||||
return webAuthN, nil
|
return webAuthN, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
|
func (w *Config) BeginLogin(ctx context.Context, user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
|
||||||
assertion, sessionData, err := w.webAuthN.BeginLogin(&webUser{
|
webAuthNServer, err := w.serverFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
assertion, sessionData, err := webAuthNServer.BeginLogin(&webUser{
|
||||||
Human: user,
|
Human: user,
|
||||||
credentials: WebAuthNsToCredentials(webAuthNs),
|
credentials: WebAuthNsToCredentials(webAuthNs),
|
||||||
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
|
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
|
||||||
@ -152,7 +151,7 @@ func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVe
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) {
|
func (w *Config) FinishLogin(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) {
|
||||||
assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData))
|
assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
|
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||||
@ -161,7 +160,11 @@ func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogi
|
|||||||
Human: user,
|
Human: user,
|
||||||
credentials: WebAuthNsToCredentials(webAuthNs),
|
credentials: WebAuthNsToCredentials(webAuthNs),
|
||||||
}
|
}
|
||||||
credential, err := w.webAuthN.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
|
webAuthNServer, err := w.serverFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
credential, err := webAuthNServer.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed")
|
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed")
|
||||||
}
|
}
|
||||||
@ -171,3 +174,12 @@ func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogi
|
|||||||
}
|
}
|
||||||
return credential.ID, credential.Authenticator.SignCount, nil
|
return credential.ID, credential.Authenticator.SignCount, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Config) serverFromContext(ctx context.Context) (*webauthn.WebAuthn, error) {
|
||||||
|
host := authz.GetInstance(ctx).RequestedDomain()
|
||||||
|
return webauthn.New(&webauthn.Config{
|
||||||
|
RPDisplayName: w.DisplayName,
|
||||||
|
RPID: host,
|
||||||
|
RPOrigin: http.BuildOrigin(host, w.ExternalSecure),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user