mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-12 16:47:32 +00:00
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:
@@ -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 {
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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()
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
@@ -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 (
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
}
|
||||
|
@@ -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))
|
||||
}
|
@@ -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
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
@@ -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
|
||||
}
|
@@ -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"
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"context"
|
@@ -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"))
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -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"
|
||||
)
|
||||
|
@@ -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
|
@@ -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 (
|
152
internal/api/ui/login/login.go
Normal file
152
internal/api/ui/login/login.go
Normal 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)
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,8 +1,9 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
@@ -1,8 +1,9 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
@@ -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"
|
||||
|
@@ -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"
|
||||
)
|
||||
|
@@ -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 (
|
@@ -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"
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -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"
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
@@ -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"
|
||||
)
|
||||
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -1,8 +1,9 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
)
|
||||
|
||||
const (
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -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 {
|
97
internal/api/ui/login/resources_handler.go
Normal file
97
internal/api/ui/login/resources_handler.go
Normal 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
|
||||
}
|
@@ -1,4 +1,4 @@
|
||||
package handler
|
||||
package login
|
||||
|
||||
import (
|
||||
"net/http"
|
@@ -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
Reference in New Issue
Block a user