feat: run on a single port (#3163)

* start v2

* start

* run

* some cleanup

* remove v2 pkg again

* simplify

* webauthn

* remove unused config

* fix login path in Dockerfile

* fix asset_generator.go

* health handler

* fix grpc web

* refactor

* merge

* build new main.go

* run new main.go

* update logging pkg

* fix error msg

* update logging

* cleanup

* cleanup

* go mod tidy

* change localDevMode

* fix customEndpoints

* update logging

* comments

* change local flag to external configs

* fix location generated go code

* fix

Co-authored-by: fforootd <florian@caos.ch>
This commit is contained in:
Livio Amstutz
2022-02-14 17:22:30 +01:00
committed by GitHub
parent 2f3a482ade
commit 389eb4a27a
306 changed files with 1708 additions and 1567 deletions

View File

@@ -4,9 +4,6 @@ import (
"time"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/eventstore/v1/query"
"github.com/caos/zitadel/internal/static"
@@ -15,7 +12,7 @@ import (
type Configs map[string]*Config
type Config struct {
MinimumCycleDuration types.Duration
MinimumCycleDuration time.Duration
}
type handler struct {
@@ -31,13 +28,13 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es
}
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, defaults systemdefaults.SystemDefaults, command *command.Commands, static static.Storage, localDevMode bool) []query.Handler {
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, static static.Storage, loginPrefix string) []query.Handler {
handlers := []query.Handler{}
if static != nil {
handlers = append(handlers, newStyling(
handler{view, bulkLimit, configs.cycleDuration("Styling"), errorCount, es},
static,
localDevMode))
loginPrefix))
}
return handlers
}
@@ -47,7 +44,7 @@ func (configs Configs) cycleDuration(viewModel string) time.Duration {
if !ok {
return 3 * time.Minute
}
return c.MinimumCycleDuration.Duration
return c.MinimumCycleDuration
}
func (h *handler) MinimumCycleDuration() time.Duration {

View File

@@ -33,16 +33,12 @@ type Styling struct {
resourceUrl string
}
func newStyling(handler handler, static static.Storage, localDevMode bool) *Styling {
func newStyling(handler handler, static static.Storage, loginPrefix string) *Styling {
h := &Styling{
handler: handler,
static: static,
}
prefix := ""
if localDevMode {
prefix = "/login"
}
h.resourceUrl = prefix + "/resources/dynamic" //TODO: ?
h.resourceUrl = loginPrefix + "/resources/dynamic" //TODO: ?
h.subscribe()

View File

@@ -2,13 +2,11 @@ package eventsourcing
import (
"context"
"database/sql"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/eventstore"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/spooler"
admin_view "github.com/caos/zitadel/internal/admin/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
es_spol "github.com/caos/zitadel/internal/eventstore/v1/spooler"
"github.com/caos/zitadel/internal/static"
@@ -16,11 +14,7 @@ import (
type Config struct {
SearchLimit uint64
Eventstore v1.Config
View types.SQL
Spooler spooler.SpoolerConfig
Domain string
APIDomain string
}
type EsRepository struct {
@@ -28,21 +22,17 @@ type EsRepository struct {
eventstore.AdministratorRepo
}
func Start(ctx context.Context, conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, static static.Storage, localDevMode bool) (*EsRepository, error) {
es, err := v1.Start(conf.Eventstore)
func Start(conf Config, static static.Storage, dbClient *sql.DB, loginPrefix string) (*EsRepository, error) {
es, err := v1.Start(dbClient)
if err != nil {
return nil, err
}
sqlClient, err := conf.View.Start()
if err != nil {
return nil, err
}
view, err := admin_view.StartView(sqlClient)
view, err := admin_view.StartView(dbClient)
if err != nil {
return nil, err
}
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, command, static, localDevMode)
spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, static, loginPrefix)
return &EsRepository{
spooler: spool,

View File

@@ -3,9 +3,7 @@ package spooler
import (
"database/sql"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/eventstore/v1"
v1 "github.com/caos/zitadel/internal/eventstore/v1"
"github.com/caos/zitadel/internal/static"
"github.com/caos/zitadel/internal/admin/repository/eventsourcing/handler"
@@ -20,12 +18,12 @@ type SpoolerConfig struct {
Handlers handler.Configs
}
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, defaults systemdefaults.SystemDefaults, command *command.Commands, static static.Storage, localDevMode bool) *spooler.Spooler {
func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, static static.Storage, loginPrefix string) *spooler.Spooler {
spoolerConfig := spooler.Config{
Eventstore: es,
Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers,
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, defaults, command, static, localDevMode),
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, static, loginPrefix),
}
spool := spoolerConfig.New()
spool.Start()

View File

@@ -3,100 +3,101 @@ package api
import (
"context"
"net/http"
"strings"
"github.com/caos/logging"
sentryhttp "github.com/getsentry/sentry-go/http"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/metric"
"github.com/gorilla/mux"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"google.golang.org/grpc"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
"github.com/caos/zitadel/internal/api/authz"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
internal_authz "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/server"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/oidc"
auth_es "github.com/caos/zitadel/internal/auth/repository/eventsourcing"
authz_repo "github.com/caos/zitadel/internal/authz/repository"
"github.com/caos/zitadel/internal/authz/repository"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/telemetry/metrics"
"github.com/caos/zitadel/internal/telemetry/metrics/otel"
"github.com/caos/zitadel/internal/telemetry/tracing"
view_model "github.com/caos/zitadel/internal/view/model"
)
type Config struct {
GRPC grpc_util.Config
OIDC oidc.OPHandlerConfig
Domain string
}
type API struct {
port uint16
grpcServer *grpc.Server
gatewayHandler *server.GatewayHandler
verifier *authz.TokenVerifier
serverPort string
verifier *internal_authz.TokenVerifier
health health
auth auth
admin admin
router *mux.Router
externalSecure bool
}
type health interface {
Health(ctx context.Context) error
IAMByID(ctx context.Context, id string) (*query.IAM, error)
VerifierClientID(ctx context.Context, appName string) (string, string, error)
}
type auth interface {
ActiveUserSessionCount() int64
}
type admin interface {
GetViews() ([]*view_model.View, error)
GetSpoolerDiv(database, viewName string) int64
}
func Create(config Config, authZ authz.Config, q *query.Queries, authZRepo authz_repo.Repository, authRepo *auth_es.EsRepository, adminRepo *admin_es.EsRepository, sd systemdefaults.SystemDefaults) *API {
func New(
port uint16,
router *mux.Router,
repo *struct {
repository.Repository
*query.Queries
},
authZ internal_authz.Config,
sd systemdefaults.SystemDefaults,
externalSecure bool,
) *API {
verifier := internal_authz.Start(repo)
api := &API{
serverPort: config.GRPC.ServerPort,
port: port,
verifier: verifier,
health: repo,
router: router,
externalSecure: externalSecure,
}
repo := struct {
authz_repo.Repository
query.Queries
}{
authZRepo,
*q,
}
api.verifier = authz.Start(&repo)
api.health = &repo
api.auth = authRepo
api.admin = adminRepo
api.grpcServer = server.CreateServer(api.verifier, authZ, sd.DefaultLanguage)
api.gatewayHandler = server.CreateGatewayHandler(config.GRPC)
api.RegisterHandler("", api.healthHandler())
api.routeGRPC()
api.RegisterHandler("/debug", api.healthHandler())
return api
}
func (a *API) RegisterServer(ctx context.Context, server server.Server) {
server.RegisterServer(a.grpcServer)
a.gatewayHandler.RegisterGateway(ctx, server)
a.verifier.RegisterServer(server.AppName(), server.MethodPrefix(), server.AuthMethods())
func (a *API) RegisterServer(ctx context.Context, grpcServer server.Server) error {
grpcServer.RegisterServer(a.grpcServer)
handler, prefix, err := server.CreateGateway(ctx, grpcServer, a.port)
if err != nil {
return err
}
a.RegisterHandler(prefix, handler)
a.verifier.RegisterServer(grpcServer.AppName(), grpcServer.MethodPrefix(), grpcServer.AuthMethods())
return nil
}
func (a *API) RegisterHandler(prefix string, handler http.Handler) {
prefix = strings.TrimSuffix(prefix, "/")
sentryHandler := sentryhttp.New(sentryhttp.Options{})
a.gatewayHandler.RegisterHandler(prefix, sentryHandler.Handle(handler))
subRouter := a.router.PathPrefix(prefix).Subrouter()
subRouter.PathPrefix("/").Handler(http.StripPrefix(prefix, sentryHandler.Handle(handler)))
}
func (a *API) Start(ctx context.Context) {
server.Serve(ctx, a.grpcServer, a.serverPort)
a.gatewayHandler.Serve(ctx)
func (a *API) routeGRPC() {
http2Route := a.router.Methods(http.MethodPost).
MatcherFunc(func(r *http.Request, _ *mux.RouteMatch) bool {
return r.ProtoMajor == 2
}).
Subrouter()
http2Route.Headers("Content-Type", "application/grpc").Handler(a.grpcServer)
if !a.externalSecure {
a.routeGRPCWeb(a.router)
return
}
a.routeGRPCWeb(http2Route)
}
func (a *API) routeGRPCWeb(router *mux.Router) {
router.NewRoute().HeadersRegexp("Content-Type", "application/grpc-web.*").Handler(grpcweb.WrapServer(a.grpcServer))
}
func (a *API) healthHandler() http.Handler {
@@ -125,99 +126,46 @@ func (a *API) healthHandler() http.Handler {
handler.HandleFunc("/healthz", handleHealth)
handler.HandleFunc("/ready", handleReadiness(checks))
handler.HandleFunc("/validate", handleValidate(checks))
handler.HandleFunc("/clientID", a.handleClientID)
handler.Handle("/metrics", a.handleMetrics())
return handler
}
func handleHealth(w http.ResponseWriter, r *http.Request) {
_, err := w.Write([]byte("ok"))
logging.Log("API-Hfss2").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(r.Context())).Error("error writing ok for health")
logging.WithFields("traceID", tracing.TraceIDFromCtx(r.Context())).OnError(err).Error("error writing ok for health")
}
func handleReadiness(checks []ValidationFunction) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
errors := validate(r.Context(), checks)
if len(errors) == 0 {
errs := validate(r.Context(), checks)
if len(errs) == 0 {
http_util.MarshalJSON(w, "ok", nil, http.StatusOK)
return
}
http_util.MarshalJSON(w, nil, errors[0], http.StatusPreconditionFailed)
http_util.MarshalJSON(w, nil, errs[0], http.StatusPreconditionFailed)
}
}
func handleValidate(checks []ValidationFunction) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
errors := validate(r.Context(), checks)
if len(errors) == 0 {
errs := validate(r.Context(), checks)
if len(errs) == 0 {
http_util.MarshalJSON(w, "ok", nil, http.StatusOK)
return
}
http_util.MarshalJSON(w, errors, nil, http.StatusOK)
http_util.MarshalJSON(w, errs, nil, http.StatusOK)
}
}
func (a *API) handleClientID(w http.ResponseWriter, r *http.Request) {
id, _, err := a.health.VerifierClientID(r.Context(), "Zitadel Console")
if err != nil {
http_util.MarshalJSON(w, nil, err, http.StatusPreconditionFailed)
return
}
http_util.MarshalJSON(w, id, nil, http.StatusOK)
}
func (a *API) handleMetrics() http.Handler {
a.registerActiveSessionCounters()
a.registerSpoolerDivCounters()
return metrics.GetExporter()
}
func (a *API) registerActiveSessionCounters() {
metrics.RegisterValueObserver(
metrics.ActiveSessionCounter,
metrics.ActiveSessionCounterDescription,
func(ctx context.Context, result metric.Int64ObserverResult) {
result.Observe(
a.auth.ActiveUserSessionCount(),
)
},
)
}
func (a *API) registerSpoolerDivCounters() {
views, err := a.admin.GetViews()
if err != nil {
logging.Log("API-3M8sd").WithError(err).Error("could not read views for metrics")
return
}
metrics.RegisterValueObserver(
metrics.SpoolerDivCounter,
metrics.SpoolerDivCounterDescription,
func(ctx context.Context, result metric.Int64ObserverResult) {
for _, view := range views {
labels := map[string]attribute.Value{
metrics.Database: attribute.StringValue(view.Database),
metrics.ViewName: attribute.StringValue(view.ViewName),
}
result.Observe(
a.admin.GetSpoolerDiv(view.Database, view.ViewName),
otel.MapToKeyValue(labels)...,
)
}
},
)
}
type ValidationFunction func(ctx context.Context) error
func validate(ctx context.Context, validations []ValidationFunction) []error {
errors := make([]error, 0)
errs := make([]error, 0)
for _, validation := range validations {
if err := validation(ctx); err != nil {
logging.Log("API-vf823").WithError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Error("validation failed")
errors = append(errors, err)
logging.WithFields("traceID", tracing.TraceIDFromCtx(ctx)).WithError(err).Error("validation failed")
errs = append(errs, err)
}
}
return errors
return errs
}

View File

@@ -21,6 +21,10 @@ import (
"github.com/caos/zitadel/internal/static"
)
const (
HandlerPrefix = "/assets/v1"
)
type Handler struct {
errorHandler ErrorHandler
storage static.Storage
@@ -66,14 +70,7 @@ func DefaultErrorHandler(w http.ResponseWriter, r *http.Request, err error, code
http.Error(w, err.Error(), code)
}
func NewHandler(
commands *command.Commands,
verifier *authz.TokenVerifier,
authConfig authz.Config,
idGenerator id.Generator,
storage static.Storage,
queries *query.Queries,
) http.Handler {
func NewHandler(commands *command.Commands, verifier *authz.TokenVerifier, authConfig authz.Config, idGenerator id.Generator, storage static.Storage, queries *query.Queries) http.Handler {
h := &Handler{
commands: commands,
errorHandler: DefaultErrorHandler,

View File

@@ -12,20 +12,21 @@ import (
)
var (
directory = flag.String("directory", "./", "working directory: asset.yaml must be in this directory, files will be generated into parent directory")
assets = flag.String("assets", "../../../../docs/docs/apis/assets/assets.md", "path where the assets.md will be generated")
directory = flag.String("directory", "./", "working directory: asset.yaml must be in this directory, files will be generated into parent directory")
assetsDocs = flag.String("assets", "../../../../docs/docs/apis/assets/assets.md", "path where the assets.md will be generated")
assetPrefix = flag.String("handler-prefix", "/assets/v1", "prefix of the handler paths")
)
func main() {
flag.Parse()
configFile := *directory + "asset.yaml"
authz, err := os.OpenFile(*directory+"../authz.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-Gn31f").OnError(err).Fatal("cannot open authz file")
logging.OnError(err).Fatal("cannot open authz file")
router, err := os.OpenFile(*directory+"../router.go", os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-ABen3").OnError(err).Fatal("cannot open router file")
docs, err := os.OpenFile(*assets, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.Log("ASSETS-Dfvsd").OnError(err).Fatal("cannot open docs file")
GenerateAssetHandler(configFile, authz, router, docs)
logging.OnError(err).Fatal("cannot open router file")
docs, err := os.OpenFile(*assetsDocs, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0755)
logging.OnError(err).Fatal("cannot open docs file")
GenerateAssetHandler(configFile, *assetPrefix, authz, router, docs)
}
type Method struct {
@@ -97,7 +98,7 @@ type Service struct {
Methods map[string]Method
}
func GenerateAssetHandler(configFilePath string, authz, router, docs io.Writer) {
func GenerateAssetHandler(configFilePath, handlerPrefix string, authz, router, docs io.Writer) {
conf := new(struct {
Services Services
})
@@ -117,7 +118,7 @@ func GenerateAssetHandler(configFilePath string, authz, router, docs io.Writer)
}{
GoPkgName: "assets",
Name: "AssetsService",
Prefix: "/assets/v1",
Prefix: handlerPrefix,
Services: conf.Services,
}
err = tmplAuthz.Execute(authz, data)

View File

@@ -2,52 +2,51 @@ package server
import (
"context"
"fmt"
"net/http"
"strings"
"github.com/caos/logging"
grpc_util "github.com/caos/zitadel/internal/api/grpc"
client_middleware "github.com/caos/zitadel/internal/api/grpc/client/middleware"
http_util "github.com/caos/zitadel/internal/api/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/telemetry/tracing"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/encoding/protojson"
client_middleware "github.com/caos/zitadel/internal/api/grpc/client/middleware"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)
const (
defaultGatewayPort = "8080"
mimeWildcard = "*/*"
mimeWildcard = "*/*"
)
var (
DefaultJSONMarshaler = &runtime.JSONPb{
customHeaders = []string{
"x-zitadel-",
}
jsonMarshaler = &runtime.JSONPb{
UnmarshalOptions: protojson.UnmarshalOptions{
DiscardUnknown: true,
},
}
DefaultServeMuxOptions = func(customHeaders ...string) []runtime.ServeMuxOption {
return []runtime.ServeMuxOption{
runtime.WithMarshalerOption(DefaultJSONMarshaler.ContentType(nil), DefaultJSONMarshaler),
runtime.WithMarshalerOption(mimeWildcard, DefaultJSONMarshaler),
runtime.WithMarshalerOption(runtime.MIMEWildcard, DefaultJSONMarshaler),
runtime.WithIncomingHeaderMatcher(DefaultHeaderMatcher(customHeaders...)),
runtime.WithOutgoingHeaderMatcher(runtime.DefaultHeaderMatcher),
}
serveMuxOptions = []runtime.ServeMuxOption{
runtime.WithMarshalerOption(jsonMarshaler.ContentType(nil), jsonMarshaler),
runtime.WithMarshalerOption(mimeWildcard, jsonMarshaler),
runtime.WithMarshalerOption(runtime.MIMEWildcard, jsonMarshaler),
runtime.WithIncomingHeaderMatcher(headerMatcher),
runtime.WithOutgoingHeaderMatcher(runtime.DefaultHeaderMatcher),
}
DefaultHeaderMatcher = func(customHeaders ...string) runtime.HeaderMatcherFunc {
return func(header string) (string, bool) {
headerMatcher = runtime.HeaderMatcherFunc(
func(header string) (string, bool) {
for _, customHeader := range customHeaders {
if strings.HasPrefix(strings.ToLower(header), customHeader) {
return header, true
}
}
return runtime.DefaultHeaderMatcher(header)
}
}
},
)
)
type Gateway interface {
@@ -57,92 +56,21 @@ type Gateway interface {
type GatewayFunc func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) error
//optional extending interfaces of Gateway below
type gatewayCustomServeMuxOptions interface {
GatewayServeMuxOptions() []runtime.ServeMuxOption
}
type grpcGatewayCustomInterceptor interface {
GatewayHTTPInterceptor(http.Handler) http.Handler
}
type gatewayCustomCallOptions interface {
GatewayCallOptions() []grpc.DialOption
}
type GatewayHandler struct {
mux *http.ServeMux
serverPort string
gatewayPort string
customHeaders []string
}
func CreateGatewayHandler(config grpc_util.Config) *GatewayHandler {
return &GatewayHandler{
mux: http.NewServeMux(),
serverPort: config.ServerPort,
gatewayPort: config.GatewayPort,
customHeaders: config.CustomHeaders,
}
}
//RegisterGateway registers a handler (Gateway interface) on defined port
//Gateway interface may be extended with optional implementation of interfaces (gatewayCustomServeMuxOptions, ...)
func (g *GatewayHandler) RegisterGateway(ctx context.Context, gateway Gateway) {
handler := createGateway(ctx, gateway, g.serverPort, g.customHeaders...)
prefix := gateway.GatewayPathPrefix()
g.RegisterHandler(prefix, handler)
}
func (g *GatewayHandler) RegisterHandler(prefix string, handler http.Handler) {
http_util.RegisterHandler(g.mux, prefix, handler)
}
func (g *GatewayHandler) Serve(ctx context.Context) {
http_util.Serve(ctx, g.mux, g.gatewayPort, "api")
}
func createGateway(ctx context.Context, g Gateway, port string, customHeaders ...string) http.Handler {
mux := createMux(g, customHeaders...)
opts := createDialOptions(g)
err := g.RegisterGateway()(ctx, mux, "localhost"+http_util.Endpoint(port), opts)
logging.Log("SERVE-7B7G0E").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("failed to register grpc gateway")
return addInterceptors(mux, g)
}
func createMux(g Gateway, customHeaders ...string) *runtime.ServeMux {
muxOptions := DefaultServeMuxOptions(customHeaders...)
if customOpts, ok := g.(gatewayCustomServeMuxOptions); ok {
muxOptions = customOpts.GatewayServeMuxOptions()
}
return runtime.NewServeMux(muxOptions...)
}
func createDialOptions(g Gateway) []grpc.DialOption {
func CreateGateway(ctx context.Context, g Gateway, port uint16) (http.Handler, string, error) {
runtimeMux := runtime.NewServeMux(serveMuxOptions...)
opts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(client_middleware.DefaultTracingClient()),
}
if customOpts, ok := g.(gatewayCustomCallOptions); ok {
opts = append(opts, customOpts.GatewayCallOptions()...)
err := g.RegisterGateway()(ctx, runtimeMux, fmt.Sprintf("localhost:%d", port), opts)
if err != nil {
return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err)
}
return opts
return addInterceptors(runtimeMux), g.GatewayPathPrefix(), nil
}
func addInterceptors(handler http.Handler, g Gateway) http.Handler {
func addInterceptors(handler http.Handler) http.Handler {
handler = http_mw.DefaultMetricsHandler(handler)
handler = http_mw.DefaultTelemetryHandler(handler)
if interceptor, ok := g.(grpcGatewayCustomInterceptor); ok {
handler = interceptor.GatewayHTTPInterceptor(handler)
}
return http_mw.CORSInterceptor(handler)
}
func gatewayPort(port string) string {
if port == "" {
return defaultGatewayPort
}
return port
}

View File

@@ -1,23 +1,15 @@
package server
import (
"context"
grpc_api "github.com/caos/zitadel/internal/api/grpc"
"github.com/caos/zitadel/internal/telemetry/metrics"
"github.com/caos/logging"
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
"golang.org/x/text/language"
"google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/server/middleware"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
const (
defaultGrpcPort = "80"
)
type Server interface {
@@ -46,24 +38,3 @@ func CreateServer(verifier *authz.TokenVerifier, authConfig authz.Config, lang l
),
)
}
func Serve(ctx context.Context, server *grpc.Server, port string) {
go func() {
<-ctx.Done()
server.GracefulStop()
}()
go func() {
listener := http.CreateListener(port)
err := server.Serve(listener)
logging.Log("SERVE-Ga3e94").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("grpc server serve failed")
}()
logging.LogWithFields("SERVE-bZ44QM", "port", port).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Info("grpc server is listening")
}
func grpcPort(port string) string {
if port == "" {
return defaultGrpcPort
}
return port
}

View File

@@ -1,21 +0,0 @@
package http
import (
"net"
"strings"
"github.com/caos/logging"
)
func CreateListener(endpoint string) net.Listener {
l, err := net.Listen("tcp", Endpoint(endpoint))
logging.Log("SERVE-6vasef").OnError(err).Fatal("creating listener failed")
return l
}
func Endpoint(endpoint string) string {
if strings.Contains(endpoint, ":") {
return endpoint
}
return ":" + endpoint
}

View File

@@ -8,7 +8,6 @@ import (
"time"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/config/types"
)
type Cache struct {
@@ -38,8 +37,8 @@ const (
)
type CacheConfig struct {
MaxAge types.Duration
SharedMaxAge types.Duration
MaxAge time.Duration
SharedMaxAge time.Duration
}
var (

View File

@@ -3,9 +3,9 @@ package middleware
import (
"context"
"net/http"
"time"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/id"
@@ -35,12 +35,11 @@ type userAgentHandler struct {
type UserAgentCookieConfig struct {
Name string
Domain string
Key *crypto.KeyConfig
MaxAge types.Duration
MaxAge time.Duration
}
func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator, localDevMode bool) (func(http.Handler) http.Handler, error) {
func NewUserAgentHandler(config *UserAgentCookieConfig, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) {
key, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID)
if err != nil {
return nil, err
@@ -48,10 +47,10 @@ func NewUserAgentHandler(config *UserAgentCookieConfig, idGenerator id.Generator
cookieKey := []byte(key)
opts := []http_utils.CookieHandlerOpt{
http_utils.WithEncryption(cookieKey, cookieKey),
http_utils.WithDomain(config.Domain),
http_utils.WithDomain(domain),
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
}
if localDevMode {
if !externalSecure {
opts = append(opts, http_utils.WithUnsecure())
}
return func(handler http.Handler) http.Handler {

View File

@@ -30,3 +30,11 @@ func IsOrigin(rawOrigin string) bool {
}
return parsedUrl.Scheme != "" && parsedUrl.Host != "" && parsedUrl.Path == "" && len(parsedUrl.Query()) == 0 && parsedUrl.Fragment == ""
}
func BuildHTTP(hostname string, externalPort uint16, secure bool) string {
schema := "https"
if !secure {
schema = "http"
}
return fmt.Sprintf("%s://%s:%d", schema, hostname, externalPort)
}

View File

@@ -1,33 +0,0 @@
package http
import (
"context"
"net/http"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
func Serve(ctx context.Context, handler http.Handler, port, servername string) {
server := &http.Server{
Handler: handler,
}
listener := CreateListener(port)
go func() {
<-ctx.Done()
err := server.Shutdown(ctx)
logging.LogWithFields("HTTP-m7kBlq", "name", servername).WithField("traceID", tracing.TraceIDFromCtx(ctx)).OnError(err).Warnf("error during graceful shutdown of http server (%s)", servername)
}()
go func() {
err := server.Serve(listener)
logging.LogWithFields("HTTP-tBHR60", "name", servername).OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panicf("http serve (%s) failed", servername)
}()
logging.LogWithFields("HTTP-KHh0Cb", "name", servername, "port", port).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Infof("http server (%s) is listening", servername)
}
func RegisterHandler(mux *http.ServeMux, prefix string, handler http.Handler) {
mux.Handle(prefix+"/", http.StripPrefix(prefix, handler))
}

View File

@@ -2,44 +2,50 @@ package oidc
import (
"context"
"database/sql"
"fmt"
"net/http"
"time"
"github.com/caos/logging"
"github.com/caos/oidc/pkg/op"
"github.com/rakyll/statik/fs"
"golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/assets"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/api/ui/login"
"github.com/caos/zitadel/internal/auth/repository"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/config/types"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/eventstore/handler/crdb"
"github.com/caos/zitadel/internal/i18n"
"github.com/caos/zitadel/internal/id"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/telemetry/metrics"
"github.com/caos/zitadel/internal/telemetry/tracing"
)
type OPHandlerConfig struct {
OPConfig *op.Config
StorageConfig StorageConfig
UserAgentCookieConfig *middleware.UserAgentCookieConfig
Cache *middleware.CacheConfig
Endpoints *EndpointConfig
}
const (
HandlerPrefix = "/oauth/v2"
AuthCallback = HandlerPrefix + "/authorize/callback?id="
)
type StorageConfig struct {
DefaultLoginURL string
type Config struct {
CodeMethodS256 bool
AuthMethodPost bool
AuthMethodPrivateKeyJWT bool
GrantTypeRefreshToken bool
RequestObjectSupported bool
SigningKeyAlgorithm string
DefaultAccessTokenLifetime types.Duration
DefaultIdTokenLifetime types.Duration
DefaultRefreshTokenIdleExpiration types.Duration
DefaultRefreshTokenExpiration types.Duration
DefaultAccessTokenLifetime time.Duration
DefaultIdTokenLifetime time.Duration
DefaultRefreshTokenIdleExpiration time.Duration
DefaultRefreshTokenExpiration time.Duration
UserAgentCookieConfig *middleware.UserAgentCookieConfig
Cache *middleware.CacheConfig
KeyConfig *crypto.KeyConfig
CustomEndpoints *EndpointConfig
}
type EndpointConfig struct {
@@ -77,55 +83,116 @@ type OPStorage struct {
assetAPIPrefix string
}
func NewProvider(ctx context.Context, config OPHandlerConfig, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, localDevMode bool, es *eventstore.Eventstore, projections types.SQL, keyChan <-chan interface{}, assetAPIPrefix string) op.OpenIDProvider {
cookieHandler, err := middleware.NewUserAgentHandler(config.UserAgentCookieConfig, id.SonyFlakeGenerator, localDevMode)
logging.Log("OIDC-sd4fd").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("cannot user agent handler")
tokenKey, err := crypto.LoadKey(keyConfig.EncryptionConfig, keyConfig.EncryptionConfig.EncryptionKeyID)
logging.Log("OIDC-ADvbv").OnError(err).Panic("cannot load OP crypto key")
cryptoKey := []byte(tokenKey)
if len(cryptoKey) != 32 {
logging.Log("OIDC-Dsfds").Panic("OP crypto key must be exactly 32 bytes")
func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI)
if err != nil {
return nil, fmt.Errorf("cannot create op config: %w", err)
}
storage, err := newStorage(config, command, query, repo, keyConfig, config.KeyConfig, es, projections, keyChan)
if err != nil {
return nil, fmt.Errorf("cannot create storage: %w", err)
}
options, err := createOptions(config, userAgentCookie)
if err != nil {
return nil, fmt.Errorf("cannot create options: %w", err)
}
copy(config.OPConfig.CryptoKey[:], cryptoKey)
config.OPConfig.CodeMethodS256 = true
config.OPConfig.AuthMethodPost = true
config.OPConfig.AuthMethodPrivateKeyJWT = true
config.OPConfig.GrantTypeRefreshToken = true
supportedLanguages, err := getSupportedLanguages()
logging.Log("OIDC-GBd3t").OnError(err).Panic("cannot get supported languages")
config.OPConfig.SupportedUILocales = supportedLanguages
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
storage, err := newStorage(config.StorageConfig, command, query, repo, keyConfig, es, projections, keyChan, assetAPIPrefix)
logging.Log("OIDC-Jdg2k").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("cannot create storage")
provider, err := op.NewOpenIDProvider(
ctx,
config.OPConfig,
opConfig,
storage,
op.WithHttpInterceptors(
middleware.MetricsHandler(metricTypes),
middleware.TelemetryHandler(),
middleware.NoCacheInterceptor,
cookieHandler,
http_utils.CopyHeadersToContext,
),
op.WithCustomAuthEndpoint(op.NewEndpointWithURL(config.Endpoints.Auth.Path, config.Endpoints.Auth.URL)),
op.WithCustomTokenEndpoint(op.NewEndpointWithURL(config.Endpoints.Token.Path, config.Endpoints.Token.URL)),
op.WithCustomIntrospectionEndpoint(op.NewEndpointWithURL(config.Endpoints.Introspection.Path, config.Endpoints.Introspection.URL)),
op.WithCustomUserinfoEndpoint(op.NewEndpointWithURL(config.Endpoints.Userinfo.Path, config.Endpoints.Userinfo.URL)),
op.WithCustomRevocationEndpoint(op.NewEndpointWithURL(config.Endpoints.Revocation.Path, config.Endpoints.Revocation.URL)),
op.WithCustomEndSessionEndpoint(op.NewEndpointWithURL(config.Endpoints.EndSession.Path, config.Endpoints.EndSession.URL)),
op.WithCustomKeysEndpoint(op.NewEndpointWithURL(config.Endpoints.Keys.Path, config.Endpoints.Keys.URL)),
options...,
)
logging.Log("OIDC-asf13").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("cannot create provider")
return provider
if err != nil {
return nil, fmt.Errorf("cannot create provider: %w", err)
}
return provider, nil
}
func newStorage(config StorageConfig, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, es *eventstore.Eventstore, projections types.SQL, keyChan <-chan interface{}, assetAPIPrefix string) (*OPStorage, error) {
encAlg, err := crypto.NewAESCrypto(keyConfig.EncryptionConfig)
func Issuer(domain string, port uint16, externalSecure bool) string {
return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix
}
func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string) (*op.Config, error) {
supportedLanguages, err := getSupportedLanguages()
if err != nil {
return nil, err
}
sqlClient, err := projections.Start()
opConfig := &op.Config{
Issuer: issuer,
DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
CodeMethodS256: config.CodeMethodS256,
AuthMethodPost: config.AuthMethodPost,
AuthMethodPrivateKeyJWT: config.AuthMethodPrivateKeyJWT,
GrantTypeRefreshToken: config.GrantTypeRefreshToken,
RequestObjectSupported: config.RequestObjectSupported,
SupportedUILocales: supportedLanguages,
}
if err := cryptoKey(opConfig, config.KeyConfig); err != nil {
return nil, err
}
return opConfig, nil
}
func cryptoKey(config *op.Config, keyConfig *crypto.KeyConfig) error {
tokenKey, err := crypto.LoadKey(keyConfig, keyConfig.EncryptionKeyID)
if err != nil {
return fmt.Errorf("cannot load OP crypto key: %w", err)
}
cryptoKey := []byte(tokenKey)
if len(cryptoKey) != 32 {
return fmt.Errorf("OP crypto key must be exactly 32 bytes")
}
copy(config.CryptoKey[:], cryptoKey)
return nil
}
func createOptions(config Config, userAgentCookie func(http.Handler) http.Handler) ([]op.Option, error) {
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
interceptor := op.WithHttpInterceptors(
middleware.MetricsHandler(metricTypes),
middleware.TelemetryHandler(),
middleware.NoCacheInterceptor,
userAgentCookie,
http_utils.CopyHeadersToContext,
)
endpoints := customEndpoints(config.CustomEndpoints)
if len(endpoints) == 0 {
return []op.Option{interceptor}, nil
}
return append(endpoints, interceptor), nil
}
func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
if endpointConfig == nil {
return nil
}
options := []op.Option{}
if endpointConfig.Auth != nil {
options = append(options, op.WithCustomAuthEndpoint(op.NewEndpointWithURL(endpointConfig.Auth.Path, endpointConfig.Auth.URL)))
}
if endpointConfig.Token != nil {
options = append(options, op.WithCustomTokenEndpoint(op.NewEndpointWithURL(endpointConfig.Token.Path, endpointConfig.Token.URL)))
}
if endpointConfig.Introspection != nil {
options = append(options, op.WithCustomIntrospectionEndpoint(op.NewEndpointWithURL(endpointConfig.Introspection.Path, endpointConfig.Introspection.URL)))
}
if endpointConfig.Userinfo != nil {
options = append(options, op.WithCustomUserinfoEndpoint(op.NewEndpointWithURL(endpointConfig.Userinfo.Path, endpointConfig.Userinfo.URL)))
}
if endpointConfig.Revocation != nil {
options = append(options, op.WithCustomRevocationEndpoint(op.NewEndpointWithURL(endpointConfig.Revocation.Path, endpointConfig.Revocation.URL)))
}
if endpointConfig.EndSession != nil {
options = append(options, op.WithCustomEndSessionEndpoint(op.NewEndpointWithURL(endpointConfig.EndSession.Path, endpointConfig.EndSession.URL)))
}
if endpointConfig.Keys != nil {
options = append(options, op.WithCustomKeysEndpoint(op.NewEndpointWithURL(endpointConfig.Keys.Path, endpointConfig.Keys.URL)))
}
return options
}
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, c *crypto.KeyConfig, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}) (*OPStorage, error) {
encAlg, err := crypto.NewAESCrypto(c)
if err != nil {
return nil, err
}
@@ -134,18 +201,18 @@ func newStorage(config StorageConfig, command *command.Commands, query *query.Qu
command: command,
query: query,
eventstore: es,
defaultLoginURL: config.DefaultLoginURL,
defaultLoginURL: fmt.Sprintf("%s%s?%s=", login.HandlerPrefix, login.EndpointLogin, login.QueryAuthRequestID),
signingKeyAlgorithm: config.SigningKeyAlgorithm,
defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime.Duration,
defaultIdTokenLifetime: config.DefaultIdTokenLifetime.Duration,
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration.Duration,
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration.Duration,
defaultAccessTokenLifetime: config.DefaultAccessTokenLifetime,
defaultIdTokenLifetime: config.DefaultIdTokenLifetime,
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
encAlg: encAlg,
signingKeyGracefulPeriod: keyConfig.SigningKeyGracefulPeriod.Duration,
signingKeyRotationCheck: keyConfig.SigningKeyRotationCheck.Duration,
locker: crdb.NewLocker(sqlClient, locksTable, signingKey),
signingKeyGracefulPeriod: keyConfig.SigningKeyGracefulPeriod,
signingKeyRotationCheck: keyConfig.SigningKeyRotationCheck,
locker: crdb.NewLocker(projections, locksTable, signingKey),
keyChan: keyChan,
assetAPIPrefix: assetAPIPrefix,
assetAPIPrefix: assets.HandlerPrefix,
}, nil
}

View File

@@ -1,21 +1,23 @@
package console
import (
"encoding/json"
"fmt"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/api/http/middleware"
)
type Config struct {
Port string
ConsoleOverwriteDir string
ShortCache middleware.CacheConfig
LongCache middleware.CacheConfig
CSPDomain string
}
type spaHandler struct {
@@ -25,7 +27,7 @@ type spaHandler struct {
const (
envRequestPath = "/assets/environment.json"
consoleDefaultDir = "./console/"
handlerPrefix = "/console"
HandlerPrefix = "/ui/console"
)
var (
@@ -49,23 +51,33 @@ func (i *spaHandler) Open(name string) (http.File, error) {
return i.fileSystem.Open("/index.html")
}
func Start(config Config) (http.Handler, string, error) {
func Start(config Config, domain, url, issuer, clientID string) (http.Handler, error) {
environmentJSON, err := createEnvironmentJSON(url, issuer, clientID)
if err != nil {
return nil, fmt.Errorf("unable to marshal env for console: %w", err)
}
consoleDir := consoleDefaultDir
if config.ConsoleOverwriteDir != "" {
consoleDir = config.ConsoleOverwriteDir
}
consoleHTTPDir := http.Dir(consoleDir)
cache := AssetsCacheInterceptorIgnoreManifest(
config.ShortCache.MaxAge.Duration,
config.ShortCache.SharedMaxAge.Duration,
config.LongCache.MaxAge.Duration,
config.LongCache.SharedMaxAge.Duration,
cache := assetsCacheInterceptorIgnoreManifest(
config.ShortCache.MaxAge,
config.ShortCache.SharedMaxAge,
config.LongCache.MaxAge,
config.LongCache.SharedMaxAge,
)
security := middleware.SecurityHeaders(csp(config.CSPDomain), nil)
security := middleware.SecurityHeaders(csp(domain), nil)
handler := &http.ServeMux{}
handler.Handle("/", cache(security(http.FileServer(&spaHandler{consoleHTTPDir}))))
handler.Handle(envRequestPath, cache(security(http.StripPrefix("/assets", http.FileServer(consoleHTTPDir)))))
return handler, handlerPrefix, nil
handler.Handle(envRequestPath, cache(security(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, err := w.Write(environmentJSON)
logging.OnError(err).Error("error serving environment.json")
}))))
return handler, nil
}
func csp(zitadelDomain string) *middleware.CSP {
@@ -80,7 +92,28 @@ func csp(zitadelDomain string) *middleware.CSP {
return &csp
}
func AssetsCacheInterceptorIgnoreManifest(shortMaxAge, shortSharedMaxAge, longMaxAge, longSharedMaxAge time.Duration) func(http.Handler) http.Handler {
func createEnvironmentJSON(url, issuer, clientID string) ([]byte, error) {
environment := struct {
AuthServiceUrl string `json:"authServiceUrl,omitempty"`
MgmtServiceUrl string `json:"mgmtServiceUrl,omitempty"`
AdminServiceUrl string `json:"adminServiceUrl,omitempty"`
SubscriptionServiceUrl string `json:"subscriptionServiceUrl,omitempty"`
AssetServiceUrl string `json:"assetServiceUrl,omitempty"`
Issuer string `json:"issuer,omitempty"`
ClientID string `json:"clientid,omitempty"`
}{
AuthServiceUrl: url,
MgmtServiceUrl: url,
AdminServiceUrl: url,
SubscriptionServiceUrl: url,
AssetServiceUrl: url,
Issuer: issuer,
ClientID: clientID,
}
return json.Marshal(environment)
}
func assetsCacheInterceptorIgnoreManifest(shortMaxAge, shortSharedMaxAge, longMaxAge, longSharedMaxAge time.Duration) func(http.Handler) http.Handler {
return func(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
for _, file := range shortCacheFiles {

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"
@@ -9,12 +9,12 @@ import (
)
const (
queryAuthRequestID = "authRequestID"
QueryAuthRequestID = "authRequestID"
queryUserAgentID = "userAgentID"
)
func (l *Login) getAuthRequest(r *http.Request) (*domain.AuthRequest, error) {
authRequestID := r.FormValue(queryAuthRequestID)
authRequestID := r.FormValue(QueryAuthRequestID)
if authRequestID == "" {
return nil, nil
}

View File

@@ -1,9 +1,10 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"context"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"encoding/base64"
@@ -115,7 +115,7 @@ func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authR
return
}
q := redirect.Query()
q.Set(queryAuthRequestID, authReq.ID)
q.Set(QueryAuthRequestID, authReq.ID)
userAgentID, ok := http_mw.UserAgentIDFromCtx(r.Context())
if !ok {
l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,10 +1,11 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"strconv"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
)

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"context"
@@ -180,7 +180,7 @@ func (l *Login) redirectToJWTCallback(authReq *domain.AuthRequest) (string, erro
return "", err
}
q := redirect.Query()
q.Set(queryAuthRequestID, authReq.ID)
q.Set(QueryAuthRequestID, authReq.ID)
nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(authReq.AgentID))
if err != nil {
return "", err

View File

@@ -1,9 +1,10 @@
package handler
package login
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/domain"
"net/http"
)
const (

View File

@@ -0,0 +1,152 @@
package login
import (
"context"
"fmt"
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
"github.com/caos/zitadel/internal/api/authz"
http_utils "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/http/middleware"
_ "github.com/caos/zitadel/internal/api/ui/login/statik"
auth_repository "github.com/caos/zitadel/internal/auth/repository"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/form"
"github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/internal/static"
)
type Login struct {
endpoint string
router http.Handler
renderer *Renderer
parser *form.Parser
command *command.Commands
query *query.Queries
staticStorage static.Storage
//staticCache cache.Cache //TODO: enable when storage is implemented again
authRepo auth_repository.Repository
baseURL string
zitadelURL string
oidcAuthCallbackURL string
IDPConfigAesCrypto crypto.EncryptionAlgorithm
iamDomain string
}
type Config struct {
LanguageCookieName string
CSRF CSRF
Cache middleware.CacheConfig
//StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again
}
type CSRF struct {
CookieName string
Key *crypto.KeyConfig
}
const (
login = "LOGIN"
HandlerPrefix = "/ui/login"
DefaultLoggedOutPath = HandlerPrefix + EndpointLogoutDone
)
func CreateLogin(config Config, command *command.Commands, query *query.Queries, authRepo *eventsourcing.EsRepository, staticStorage static.Storage, systemDefaults systemdefaults.SystemDefaults, zitadelURL, domain, oidcAuthCallbackURL string, externalSecure bool, userAgentCookie mux.MiddlewareFunc) (*Login, error) {
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey)
if err != nil {
return nil, fmt.Errorf("error create new aes crypto: %w", err)
}
login := &Login{
oidcAuthCallbackURL: oidcAuthCallbackURL,
baseURL: HandlerPrefix,
zitadelURL: zitadelURL,
command: command,
query: query,
staticStorage: staticStorage,
authRepo: authRepo,
IDPConfigAesCrypto: aesCrypto,
iamDomain: domain,
}
//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")
if err != nil {
return nil, fmt.Errorf("unable to create filesystem: %w", err)
}
csrfInterceptor, err := createCSRFInterceptor(config.CSRF, externalSecure, login.csrfErrorHandler())
if err != nil {
return nil, fmt.Errorf("unable to create csrfInterceptor: %w", err)
}
cacheInterceptor, err := middleware.DefaultCacheInterceptor(EndpointResources, config.Cache.MaxAge, config.Cache.SharedMaxAge)
if err != nil {
return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err)
}
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
login.router = CreateRouter(login, statikFS, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources))
login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage)
login.parser = form.NewParser()
return login, nil
}
func csp() *middleware.CSP {
csp := middleware.DefaultSCP
csp.ObjectSrc = middleware.CSPSourceOptsSelf()
csp.StyleSrc = csp.StyleSrc.AddNonce()
csp.ScriptSrc = csp.ScriptSrc.AddNonce()
return &csp
}
func createCSRFInterceptor(config CSRF, externalSecure bool, errorHandler http.Handler) (func(http.Handler) http.Handler, error) {
csrfKey, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID)
if err != nil {
return nil, err
}
path := "/"
return csrf.Protect([]byte(csrfKey),
csrf.Secure(externalSecure),
csrf.CookieName(http_utils.SetCookiePrefix(config.CookieName, "", path, externalSecure)),
csrf.Path(path),
csrf.ErrorHandler(errorHandler),
), nil
}
func (l *Login) Handler() http.Handler {
return l.router
}
func (l *Login) getClaimedUserIDsOfOrgDomain(ctx context.Context, orgName string) ([]string, error) {
loginName, err := query.NewUserPreferredLoginNameSearchQuery("@"+domain.NewIAMDomainName(orgName, l.iamDomain), query.TextEndsWithIgnoreCase)
if err != nil {
return nil, err
}
users, err := l.query.SearchUsers(ctx, &query.UserSearchQueries{Queries: []query.SearchQuery{loginName}})
if err != nil {
return nil, err
}
userIDs := make([]string, len(users.Users))
for i, user := range users.Users {
userIDs[i] = user.ID
}
return userIDs, nil
}
func setContext(ctx context.Context, resourceOwner string) context.Context {
data := authz.CtxData{
UserID: login,
OrgID: resourceOwner,
}
return authz.SetCtxData(ctx, data)
}

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,8 +1,9 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
)
const (

View File

@@ -1,8 +1,9 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
)
const (

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"encoding/base64"

View File

@@ -1,10 +1,11 @@
package handler
package login
import (
"bytes"
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
svg "github.com/ajstarks/svgo"
"github.com/boombuler/barcode/qr"

View File

@@ -1,9 +1,10 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors"
)

View File

@@ -1,9 +1,10 @@
package handler
package login
import (
"net/http"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/domain"
"net/http"
)
const (

View File

@@ -1,10 +1,11 @@
package handler
package login
import (
"encoding/base64"
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)

View File

@@ -1,9 +1,10 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"

View File

@@ -1,10 +1,11 @@
package handler
package login
import (
"encoding/base64"
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"encoding/base64"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,8 +1,9 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
)
const (

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"errors"
@@ -110,19 +110,19 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
return path.Join(r.pathPrefix, EndpointLogin)
},
"externalIDPAuthURL": func(authReqID, idpConfigID string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalLogin, queryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalLogin, QueryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
},
"externalIDPRegisterURL": func(authReqID, idpConfigID string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalRegister, queryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s&%s=%s", EndpointExternalRegister, QueryAuthRequestID, authReqID, queryIDPConfigID, idpConfigID))
},
"registerUrl": func(id string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointRegister, queryAuthRequestID, id))
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointRegister, QueryAuthRequestID, id))
},
"loginNameUrl": func() string {
return path.Join(r.pathPrefix, EndpointLoginName)
},
"loginNameChangeUrl": func(id string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointLoginName, queryAuthRequestID, id))
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointLoginName, QueryAuthRequestID, id))
},
"userSelectionUrl": func() string {
return path.Join(r.pathPrefix, EndpointUserSelection)
@@ -137,7 +137,7 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
return path.Join(r.pathPrefix, EndpointPasswordlessPrompt)
},
"passwordResetUrl": func(id string) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointPasswordReset, queryAuthRequestID, id))
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s", EndpointPasswordReset, QueryAuthRequestID, id))
},
"passwordUrl": func() string {
return path.Join(r.pathPrefix, EndpointPassword)
@@ -149,7 +149,7 @@ func CreateRenderer(pathPrefix string, staticDir http.FileSystem, staticStorage
return path.Join(r.pathPrefix, EndpointMFAPrompt)
},
"mfaPromptChangeUrl": func(id string, provider domain.MFAType) string {
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s;%s=%v", EndpointMFAPrompt, queryAuthRequestID, id, "provider", provider))
return path.Join(r.pathPrefix, fmt.Sprintf("%s?%s=%s;%s=%v", EndpointMFAPrompt, QueryAuthRequestID, id, "provider", provider))
},
"mfaInitVerifyUrl": func() string {
return path.Join(r.pathPrefix, EndpointMFAInitVerify)
@@ -490,7 +490,7 @@ func getRequestID(authReq *domain.AuthRequest, r *http.Request) string {
if authReq != nil {
return authReq.ID
}
return r.FormValue(queryAuthRequestID)
return r.FormValue(QueryAuthRequestID)
}
func (l *Login) csrfErrorHandler() http.Handler {

View File

@@ -0,0 +1,97 @@
package login
import (
"context"
"net/http"
"github.com/caos/zitadel/internal/domain"
)
type dynamicResourceData struct {
OrgID string `schema:"orgId"`
DefaultPolicy bool `schema:"default-policy"`
FileName string `schema:"filename"`
}
func (l *Login) handleResources(staticDir http.FileSystem) http.Handler {
return http.FileServer(staticDir)
}
func (l *Login) handleDynamicResources(w http.ResponseWriter, r *http.Request) {
data := new(dynamicResourceData)
err := l.getParseData(r, data)
if err != nil {
return
}
bucketName := domain.IAMID
if data.OrgID != "" && !data.DefaultPolicy {
bucketName = data.OrgID
}
etag := r.Header.Get("If-None-Match")
asset, info, err := l.getStatic(r.Context(), bucketName, data.FileName)
if info != nil && info.ETag == etag {
w.WriteHeader(304)
return
}
if err != nil {
return
}
//TODO: enable again when assets are implemented again
_ = asset
//w.Header().Set("content-length", strconv.FormatInt(info.Size, 10))
//w.Header().Set("content-type", info.ContentType)
//w.Header().Set("ETag", info.ETag)
//w.Write(asset)
}
func (l *Login) getStatic(ctx context.Context, bucketName, fileName string) ([]byte, *domain.AssetInfo, error) {
s := new(staticAsset)
//TODO: enable again when assets are implemented again
//key := bucketName + "-" + fileName
//err := l.staticCache.Get(key, s)
//if err == nil && s.Info != nil && (s.Info.Expiration.After(time.Now().Add(-1 * time.Minute))) { //TODO: config?
// return s.Data, s.Info, nil
//}
//info, err := l.staticStorage.GetObjectInfo(ctx, bucketName, fileName)
//if err != nil {
// if caos_errs.IsNotFound(err) {
// return nil, nil, err
// }
// return s.Data, s.Info, err
//}
//if s.Info != nil && s.Info.ETag == info.ETag {
// if info.Expiration.After(s.Info.Expiration) {
// s.Info = info
// //l.cacheStatic(bucketName, fileName, s)
// }
// return s.Data, s.Info, nil
//}
//
//reader, _, err := l.staticStorage.GetObject(ctx, bucketName, fileName)
//if err != nil {
// return s.Data, s.Info, err
//}
//s.Data, err = ioutil.ReadAll(reader)
//if err != nil {
// return nil, nil, err
//}
//s.Info = info
//l.cacheStatic(bucketName, fileName, s)
return s.Data, s.Info, nil
}
//TODO: enable again when assets are implemented again
//
//func (l *Login) cacheStatic(bucketName, fileName string, s *staticAsset) {
// key := bucketName + "-" + fileName
// err := l.staticCache.Set(key, &s)
// logging.Log("HANDLER-dfht2").OnError(err).Warnf("caching of asset %s: %s failed", bucketName, fileName)
//}
type staticAsset struct {
Data []byte
Info *domain.AssetInfo
}

View File

@@ -1,4 +1,4 @@
package handler
package login
import (
"net/http"

View File

@@ -1,9 +1,10 @@
package handler
package login
import (
"github.com/caos/zitadel/internal/domain"
"net/http"
"github.com/caos/zitadel/internal/domain"
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
)

Some files were not shown because too many files have changed in this diff Show More