diff --git a/.dockerignore b/.dockerignore index 5d4e0f6ced..0fea514232 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,6 +7,7 @@ /k8s/ /node_modules/ /console/src/app/proto/generated/ +/console/.angular /console/tmp/ .releaserc.js changelog.config.js @@ -18,3 +19,4 @@ pkg/grpc/*/*.pb.* pkg/grpc/*/*.swagger.json .goreleaser.yaml .artifacts/ +.vscode diff --git a/.github/workflows/zitadel.yml b/.github/workflows/zitadel.yml index 2691109f80..c5512d5290 100644 --- a/.github/workflows/zitadel.yml +++ b/.github/workflows/zitadel.yml @@ -80,7 +80,7 @@ jobs: name: go-codecov - name: Bump Chart Version uses: peter-evans/repository-dispatch@v2 - if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/main' + if: steps.semantic.outputs.new_release_published == 'true' && github.ref == 'refs/heads/next' with: token: ${{ steps.generate-token.outputs.token }} repository: zitadel/zitadel-charts diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d35289418..35c27ef3b6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -338,6 +338,7 @@ Please refer to the [README](./docs/README.md) for more information and local te - **Code with variables**: Make sure that code snippets can be used by setting environment variables, instead of manually replacing a placeholder. - **Embedded files**: When embedding mdx files, make sure the template ist prefixed by "_" (lowdash). The content will be rendered inside the parent page, but is not accessible individually (eg, by search). +- **Don't repeat yourself**: When using the same content in multiple places, save and manage the content as separate file and make use of embedded files to import it into other docs pages. - **Embedded code**: You can embed code snippets from a repository. See the [plugin](https://github.com/saucelabs/docusaurus-theme-github-codeblock#usage) for usage. ### Docs Pull Request diff --git a/cmd/start/start.go b/cmd/start/start.go index 0d2d973690..efa0e9326f 100644 --- a/cmd/start/start.go +++ b/cmd/start/start.go @@ -306,9 +306,8 @@ func startAPIs( http_util.WithNonHttpOnly(), http_util.WithMaxAge(int(math.Floor(config.Quotas.Access.ExhaustedCookieMaxAge.Seconds()))), ) - limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, config.Quotas.Access, false) - nonLimitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, nil, config.Quotas.Access, true) - apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, accessSvc, exhaustedCookieHandler, config.Quotas.Access) + limitingAccessInterceptor := middleware.NewAccessInterceptor(accessSvc, exhaustedCookieHandler, config.Quotas.Access) + apis, err := api.New(ctx, config.Port, router, queries, verifier, config.InternalAuthZ, tlsConfig, config.HTTP2HostHeader, config.HTTP1HostHeader, limitingAccessInterceptor) if err != nil { return fmt.Errorf("error creating api %w", err) } @@ -376,7 +375,7 @@ func startAPIs( } apis.RegisterHandlerOnPrefix(saml.HandlerPrefix, samlProvider.HttpHandler()) - c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, nonLimitingAccessInterceptor.Handle, config.CustomerPortal) + c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, middleware.CallDurationHandler, instanceInterceptor.Handler, limitingAccessInterceptor, config.CustomerPortal) if err != nil { return fmt.Errorf("unable to start console: %w", err) } diff --git a/docs/.gitignore b/docs/.gitignore index aa32254704..0b04043a5e 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -9,6 +9,18 @@ .cache-loader .artifacts +# Generated by docusaurus-plugin-openapi-docs +docs/apis/auth +docs/apis/mgmt +docs/apis/admin +docs/apis/system +docs/apis/user_service +docs/apis/session_service +docs/apis/system +docs/apis/user_service +docs/apis/session_service +docs/apis/settings_service + # Misc .DS_Store .env.local diff --git a/docs/docs/legal/introduction.mdx b/docs/docs/legal/introduction.mdx deleted file mode 100644 index 00bebc4a6a..0000000000 --- a/docs/docs/legal/introduction.mdx +++ /dev/null @@ -1,27 +0,0 @@ ---- -title: Overview ---- - -import {ListElement, ListWrapper, ICONTYPE} from '../../src/components/list'; -import Column from '../../src/components/column'; - -This section contains important agreements, policies and appendices relevant for users of our websites and services. - -All documents will be provided in English language. - - - - - - - - - - - - - - - - - diff --git a/docs/docs/legal/terms-of-service-dedicated.md b/docs/docs/legal/terms-of-service-dedicated.md deleted file mode 100644 index d2bae23355..0000000000 --- a/docs/docs/legal/terms-of-service-dedicated.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Dedicated Instance Terms -custom_edit_url: null ---- -## General - -Last revised: June 3, 2022 - -### Background - -Within the scope of the Framework Agreement, the Customer may choose to purchase a subscription that requires a dedicated instance of ZITADEL. These additional terms for dedicated instance ("**Dedicated Instance Terms**") apply in addition to the Framework Agreement. - -### Service - -CAOS operates and manages a **Dedicated Instance** of ZITADEL in a private infrastructure environment dedicated for the Customer and provides support services for the Customer according the Purchase Order, these terms, agreed [**Service Level Description**](service-level-description), and [**Support Service Descriptions**](support-services). - -Each Dedicated Instance consists, except agreed otherwise in writing, of a multi-zonal high-availability configuration that guarantees loads up to the specified [rate limits](rate-limit-policy#what-rate-limits-do-apply). - -### Operations - -CAOS will install and manage the Dedicated Instance on infracstructure provided by preferred cloud providers. Costs for infrastructure or cloud providers are not included in the Subscription, if not agreed otherwise in writing. - -You may choose to provide the required infrastructure yourself. You must comply with the requirements and prerequisites outlined in the purchase order. - -You may not modify, maintain or attempt to modify the Dedicated Instance, except with prior instructions by CAOS. - -CAOS will use the same backup strategy as for ZITADEL Cloud (public cloud) services, except otherwise agreed between you and CAOS in writing. - -### Maintenance and Updates - -We will access, modify, and maintain the Dedicated Instance at times solely determined by CAOS (**"Regular Maintenance"**). - -Under certain subscription plans, the Customer may agree a custom frequency and times for changes and updates. CAOS will coordinate the cadence and the changes with the Customer. To guarantee the quality of service, maintenance will occur on regular basis, typically monthly or sooner for security or performance related patches (**"Emergency Maintenance"**), but no longer than on quarterly basis. - -If you fail to permit CAOS to conduct Regular Maintenance for 3 consecutive months or Emergency Maintenance within 5 days of notification, then CAOS will raise this issue with the Customer via Escalation Process. In case the issue is not resolved 5 days after such an escalation, CAOS may terminate the subscription with 30 days prior written notice to Customer. CAOS is not obligated to provide the service according to the terms and SLA, nor is CAOS liable to any security breach or damages after failure to permit Regular Maintenance for 3 consecutive months, or Emergency Maintenance for 5 days after notification. - -### Incidents - -Incidents are handled as documented in the [**Support Service Descriptions**](support-services). If the Customer choose in Purchase Order to provide the required infrastructure, then any incidents related to the infrastructure of the Dedicated Instance have to be resolved through the Customer directly. diff --git a/docs/docs/legal/terms-of-service.md b/docs/docs/legal/terms-of-service.md index ee2b8be36d..e963ae58d2 100644 --- a/docs/docs/legal/terms-of-service.md +++ b/docs/docs/legal/terms-of-service.md @@ -25,10 +25,6 @@ The following policies complement the TOS. When accepting the TOS, you accept th * [**Acceptable Use Policy**](acceptable-use-policy) - What we understand as acceptable and fair use of our Services * [**Rate Limit Policy**](rate-limit-policy) - How we avoid overloads of our services -This Agreement is extended with additional terms, in case your Subscription requires a Dedicated Instance. When you enter the Agreement with us, you accept these additional agreements. - -* [**Dedicated Instance Terms**](terms-of-service-dedicated) - How we provide our services for a dedicated instance - ### Alterations Any provisions which deviate from these TOS must be agreed in writing between the Customer and us. Such agreements shall take precedence over the TOS outlined in this document. @@ -195,7 +191,7 @@ Should any provision of these TOS be or become invalid, this shall not affect th These TOS shall enter into force as of 15.07.2022. -Last revised: June 14, 2022 +Last revised: May 12, 2023 ### Amendments diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 409f3905e3..058d2c4626 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -83,7 +83,7 @@ module.exports = { }, { type: "doc", - docId: "legal/introduction", + docId: "legal", label: "Legal", position: "right", }, diff --git a/docs/nginx.conf b/docs/nginx.conf index 1af667f386..7b64550084 100644 --- a/docs/nginx.conf +++ b/docs/nginx.conf @@ -15,7 +15,7 @@ http { } location /docs { - root /usr/share/nginx/html; + alias /usr/share/nginx/html; index /docs/index.html; try_files $uri $uri/ /docs/index.html?q=$query_string; } @@ -23,7 +23,7 @@ http { location = /docs/proxy/js/script.js { proxy_pass https://plausible.io/js/script.js; proxy_set_header Host plausible.io; - } + } location = /docs/proxy/api/event { proxy_pass https://plausible.io/api/event; @@ -53,4 +53,4 @@ http { application/xml application/json application/ld+json; -} \ No newline at end of file +} diff --git a/docs/sidebars.js b/docs/sidebars.js index 54ca65bb18..709c99b928 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -540,38 +540,50 @@ module.exports = { support: [ ], legal: [ - "legal/introduction", - "legal/terms-of-service", - "legal/data-processing-agreement", { type: "category", - label: "Service Description", + label: "Legal Agreements", collapsed: false, + link: { + type: "generated-index", + title: "Legal Agreements", + slug: "legal", + description: + "This section contains important agreements, policies and appendices relevant for users of our websites and services. All documents will be provided in English language.", + }, items: [ - "legal/cloud-service-description", - "legal/service-level-description", - "legal/support-services", - ], - }, - { - type: "category", - label: "Additional terms", - collapsed: true, - items: [ - "legal/terms-support-service", - "legal/terms-of-service-dedicated", - ], - }, - { - type: "category", - label: "Policies", - collapsed: false, - items: [ - "legal/privacy-policy", - "legal/acceptable-use-policy", - "legal/rate-limit-policy", - "legal/vulnerability-disclosure-policy", - ], + "legal/terms-of-service", + "legal/data-processing-agreement", + { + type: "category", + label: "Service Description", + collapsed: false, + items: [ + "legal/cloud-service-description", + "legal/service-level-description", + "legal/support-services", + ], + }, + { + type: "category", + label: "Support Program", + collapsed: true, + items: [ + "legal/terms-support-service", + ], + }, + { + type: "category", + label: "Policies", + collapsed: false, + items: [ + "legal/privacy-policy", + "legal/acceptable-use-policy", + "legal/rate-limit-policy", + "legal/vulnerability-disclosure-policy", + ], + }, + ] }, ], }; diff --git a/internal/api/api.go b/internal/api/api.go index 768f0f1a2e..578bae3a45 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -19,24 +19,22 @@ import ( http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/api/ui/login" "github.com/zitadel/zitadel/internal/errors" - "github.com/zitadel/zitadel/internal/logstore" "github.com/zitadel/zitadel/internal/query" "github.com/zitadel/zitadel/internal/telemetry/metrics" "github.com/zitadel/zitadel/internal/telemetry/tracing" ) type API struct { - port uint16 - grpcServer *grpc.Server - verifier *internal_authz.TokenVerifier - health healthCheck - router *mux.Router - http1HostName string - grpcGateway *server.Gateway - healthServer *health.Server - cookieHandler *http_util.CookieHandler - cookieConfig *http_mw.AccessConfig - queries *query.Queries + port uint16 + grpcServer *grpc.Server + verifier *internal_authz.TokenVerifier + health healthCheck + router *mux.Router + http1HostName string + grpcGateway *server.Gateway + healthServer *health.Server + accessInterceptor *http_mw.AccessInterceptor + queries *query.Queries } type healthCheck interface { @@ -51,23 +49,20 @@ func New( verifier *internal_authz.TokenVerifier, authZ internal_authz.Config, tlsConfig *tls.Config, http2HostName, http1HostName string, - accessSvc *logstore.Service, - cookieHandler *http_util.CookieHandler, - cookieConfig *http_mw.AccessConfig, + accessInterceptor *http_mw.AccessInterceptor, ) (_ *API, err error) { api := &API{ - port: port, - verifier: verifier, - health: queries, - router: router, - http1HostName: http1HostName, - cookieConfig: cookieConfig, - cookieHandler: cookieHandler, - queries: queries, + port: port, + verifier: verifier, + health: queries, + router: router, + http1HostName: http1HostName, + queries: queries, + accessInterceptor: accessInterceptor, } - api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessSvc) - api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, cookieHandler, cookieConfig) + api.grpcServer = server.CreateServer(api.verifier, authZ, queries, http2HostName, tlsConfig, accessInterceptor.AccessService()) + api.grpcGateway, err = server.CreateGateway(ctx, port, http1HostName, accessInterceptor) if err != nil { return nil, err } @@ -90,8 +85,7 @@ func (a *API) RegisterServer(ctx context.Context, grpcServer server.WithGatewayP grpcServer, a.port, a.http1HostName, - a.cookieHandler, - a.cookieConfig, + a.accessInterceptor, a.queries, ) if err != nil { diff --git a/internal/api/grpc/server/gateway.go b/internal/api/grpc/server/gateway.go index 9798e5dbd0..eed0234be9 100644 --- a/internal/api/grpc/server/gateway.go +++ b/internal/api/grpc/server/gateway.go @@ -16,7 +16,6 @@ import ( client_middleware "github.com/zitadel/zitadel/internal/api/grpc/client/middleware" "github.com/zitadel/zitadel/internal/api/grpc/server/middleware" - http_utils "github.com/zitadel/zitadel/internal/api/http" http_mw "github.com/zitadel/zitadel/internal/api/http/middleware" "github.com/zitadel/zitadel/internal/query" ) @@ -66,16 +65,15 @@ var ( ) type Gateway struct { - mux *runtime.ServeMux - http1HostName string - connection *grpc.ClientConn - cookieHandler *http_utils.CookieHandler - cookieConfig *http_mw.AccessConfig - queries *query.Queries + mux *runtime.ServeMux + http1HostName string + connection *grpc.ClientConn + accessInterceptor *http_mw.AccessInterceptor + queries *query.Queries } func (g *Gateway) Handler() http.Handler { - return addInterceptors(g.mux, g.http1HostName, g.cookieHandler, g.cookieConfig, g.queries) + return addInterceptors(g.mux, g.http1HostName, g.accessInterceptor, g.queries) } type CustomHTTPResponse interface { @@ -89,8 +87,7 @@ func CreateGatewayWithPrefix( g WithGatewayPrefix, port uint16, http1HostName string, - cookieHandler *http_utils.CookieHandler, - cookieConfig *http_mw.AccessConfig, + accessInterceptor *http_mw.AccessInterceptor, queries *query.Queries, ) (http.Handler, string, error) { runtimeMux := runtime.NewServeMux(serveMuxOptions...) @@ -106,10 +103,10 @@ func CreateGatewayWithPrefix( if err != nil { return nil, "", fmt.Errorf("failed to register grpc gateway: %w", err) } - return addInterceptors(runtimeMux, http1HostName, cookieHandler, cookieConfig, queries), g.GatewayPathPrefix(), nil + return addInterceptors(runtimeMux, http1HostName, accessInterceptor, queries), g.GatewayPathPrefix(), nil } -func CreateGateway(ctx context.Context, port uint16, http1HostName string, cookieHandler *http_utils.CookieHandler, cookieConfig *http_mw.AccessConfig) (*Gateway, error) { +func CreateGateway(ctx context.Context, port uint16, http1HostName string, accessInterceptor *http_mw.AccessInterceptor) (*Gateway, error) { connection, err := dial(ctx, port, []grpc.DialOption{ @@ -121,11 +118,10 @@ func CreateGateway(ctx context.Context, port uint16, http1HostName string, cooki } runtimeMux := runtime.NewServeMux(append(serveMuxOptions, runtime.WithHealthzEndpoint(healthpb.NewHealthClient(connection)))...) return &Gateway{ - mux: runtimeMux, - http1HostName: http1HostName, - connection: connection, - cookieHandler: cookieHandler, - cookieConfig: cookieConfig, + mux: runtimeMux, + http1HostName: http1HostName, + connection: connection, + accessInterceptor: accessInterceptor, }, nil } @@ -163,8 +159,7 @@ func dial(ctx context.Context, port uint16, opts []grpc.DialOption) (*grpc.Clien func addInterceptors( handler http.Handler, http1HostName string, - cookieHandler *http_utils.CookieHandler, - cookieConfig *http_mw.AccessConfig, + accessInterceptor *http_mw.AccessInterceptor, queries *query.Queries, ) http.Handler { handler = http_mw.CallDurationHandler(handler) @@ -174,7 +169,7 @@ func addInterceptors( handler = http_mw.DefaultTelemetryHandler(handler) // For some non-obvious reason, the exhaustedCookieInterceptor sends the SetCookie header // only if it follows the http_mw.DefaultTelemetryHandler - handler = exhaustedCookieInterceptor(handler, cookieHandler, cookieConfig, queries) + handler = exhaustedCookieInterceptor(handler, accessInterceptor, queries) handler = http_mw.DefaultMetricsHandler(handler) return handler } @@ -193,35 +188,32 @@ func http1Host(next http.Handler, http1HostName string) http.Handler { func exhaustedCookieInterceptor( next http.Handler, - cookieHandler *http_utils.CookieHandler, - cookieConfig *http_mw.AccessConfig, + accessInterceptor *http_mw.AccessInterceptor, queries *query.Queries, ) http.Handler { return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) { next.ServeHTTP(&cookieResponseWriter{ - ResponseWriter: writer, - cookieHandler: cookieHandler, - cookieConfig: cookieConfig, - request: request, - queries: queries, + ResponseWriter: writer, + accessInterceptor: accessInterceptor, + request: request, + queries: queries, }, request) }) } type cookieResponseWriter struct { http.ResponseWriter - cookieHandler *http_utils.CookieHandler - cookieConfig *http_mw.AccessConfig - request *http.Request - queries *query.Queries + accessInterceptor *http_mw.AccessInterceptor + request *http.Request + queries *query.Queries } func (r *cookieResponseWriter) WriteHeader(status int) { if status >= 200 && status < 300 { - http_mw.DeleteExhaustedCookie(r.cookieHandler, r.ResponseWriter, r.request, r.cookieConfig) + r.accessInterceptor.DeleteExhaustedCookie(r.ResponseWriter, r.request) } if status == http.StatusTooManyRequests { - http_mw.SetExhaustedCookie(r.cookieHandler, r.ResponseWriter, r.cookieConfig, r.request) + r.accessInterceptor.SetExhaustedCookie(r.ResponseWriter, r.request) } r.ResponseWriter.WriteHeader(status) } diff --git a/internal/api/http/middleware/access_interceptor.go b/internal/api/http/middleware/access_interceptor.go index cf52a597d6..469fdd16d7 100644 --- a/internal/api/http/middleware/access_interceptor.go +++ b/internal/api/http/middleware/access_interceptor.go @@ -1,6 +1,7 @@ package middleware import ( + "context" "net" "net/http" "net/url" @@ -32,15 +33,54 @@ type AccessConfig struct { // NewAccessInterceptor intercepts all requests and stores them to the logstore. // If storeOnly is false, it also checks if requests are exhausted. // If requests are exhausted, it also returns http.StatusTooManyRequests and sets a cookie -func NewAccessInterceptor(svc *logstore.Service, cookieHandler *http_utils.CookieHandler, cookieConfig *AccessConfig, storeOnly bool) *AccessInterceptor { +func NewAccessInterceptor(svc *logstore.Service, cookieHandler *http_utils.CookieHandler, cookieConfig *AccessConfig) *AccessInterceptor { return &AccessInterceptor{ svc: svc, cookieHandler: cookieHandler, limitConfig: cookieConfig, - storeOnly: storeOnly, } } +func (a *AccessInterceptor) WithoutLimiting() *AccessInterceptor { + return &AccessInterceptor{ + svc: a.svc, + cookieHandler: a.cookieHandler, + limitConfig: a.limitConfig, + storeOnly: true, + } +} + +func (a *AccessInterceptor) AccessService() *logstore.Service { + return a.svc +} + +func (a *AccessInterceptor) Limit(ctx context.Context) bool { + if !a.svc.Enabled() || a.storeOnly { + return false + } + instance := authz.GetInstance(ctx) + remaining := a.svc.Limit(ctx, instance.InstanceID()) + return remaining != nil && *remaining <= 0 +} + +func (a *AccessInterceptor) SetExhaustedCookie(writer http.ResponseWriter, request *http.Request) { + cookieValue := "true" + host := request.Header.Get(middleware.HTTP1Host) + domain := host + if strings.ContainsAny(host, ":") { + var err error + domain, _, err = net.SplitHostPort(host) + if err != nil { + logging.WithError(err).WithField("host", host).Warning("failed to extract cookie domain from request host") + } + } + a.cookieHandler.SetCookie(writer, a.limitConfig.ExhaustedCookieKey, domain, cookieValue) +} + +func (a *AccessInterceptor) DeleteExhaustedCookie(writer http.ResponseWriter, request *http.Request) { + a.cookieHandler.DeleteCookie(writer, request, a.limitConfig.ExhaustedCookieKey) +} + func (a *AccessInterceptor) Handle(next http.Handler) http.Handler { if !a.svc.Enabled() { return next @@ -49,23 +89,16 @@ func (a *AccessInterceptor) Handle(next http.Handler) http.Handler { ctx := request.Context() tracingCtx, checkSpan := tracing.NewNamedSpan(ctx, "checkAccess") wrappedWriter := &statusRecorder{ResponseWriter: writer, status: 0} - instance := authz.GetInstance(ctx) - limit := false - if !a.storeOnly { - remaining := a.svc.Limit(tracingCtx, instance.InstanceID()) - limit = remaining != nil && *remaining == 0 - } + limited := a.Limit(tracingCtx) checkSpan.End() - if limit { - // Limit can only be true when storeOnly is false, so set the cookie and the response code - SetExhaustedCookie(a.cookieHandler, wrappedWriter, a.limitConfig, request) + if limited { + a.SetExhaustedCookie(wrappedWriter, request) http.Error(wrappedWriter, "quota for authenticated requests is exhausted", http.StatusTooManyRequests) - } else { - if !a.storeOnly { - // If not limited and not storeOnly, ensure the cookie is deleted - DeleteExhaustedCookie(a.cookieHandler, wrappedWriter, request, a.limitConfig) - } - // Always serve if not limited + } + if !limited && !a.storeOnly { + a.DeleteExhaustedCookie(wrappedWriter, request) + } + if !limited { next.ServeHTTP(wrappedWriter, request) } tracingCtx, writeSpan := tracing.NewNamedSpan(tracingCtx, "writeAccess") @@ -75,6 +108,7 @@ func (a *AccessInterceptor) Handle(next http.Handler) http.Handler { if err != nil { logging.WithError(err).WithField("url", requestURL).Warning("failed to unescape request url") } + instance := authz.GetInstance(tracingCtx) a.svc.Handle(tracingCtx, &access.Record{ LogDate: time.Now(), Protocol: access.HTTP, @@ -90,24 +124,6 @@ func (a *AccessInterceptor) Handle(next http.Handler) http.Handler { }) } -func SetExhaustedCookie(cookieHandler *http_utils.CookieHandler, writer http.ResponseWriter, cookieConfig *AccessConfig, request *http.Request) { - cookieValue := "true" - host := request.Header.Get(middleware.HTTP1Host) - domain := host - if strings.ContainsAny(host, ":") { - var err error - domain, _, err = net.SplitHostPort(host) - if err != nil { - logging.WithError(err).WithField("host", host).Warning("failed to extract cookie domain from request host") - } - } - cookieHandler.SetCookie(writer, cookieConfig.ExhaustedCookieKey, domain, cookieValue) -} - -func DeleteExhaustedCookie(cookieHandler *http_utils.CookieHandler, writer http.ResponseWriter, request *http.Request, cookieConfig *AccessConfig) { - cookieHandler.DeleteCookie(writer, request, cookieConfig.ExhaustedCookieKey) -} - type statusRecorder struct { http.ResponseWriter status int diff --git a/internal/api/ui/console/console.go b/internal/api/ui/console/console.go index 45503ca0c1..1980f6cc5f 100644 --- a/internal/api/ui/console/console.go +++ b/internal/api/ui/console/console.go @@ -91,7 +91,7 @@ func (f *file) Stat() (_ fs.FileInfo, err error) { return f, nil } -func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler, accessInterceptor func(http.Handler) http.Handler, customerPortal string) (http.Handler, error) { +func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, callDurationInterceptor, instanceHandler func(http.Handler) http.Handler, limitingAccessInterceptor *middleware.AccessInterceptor, customerPortal string) (http.Handler, error) { fSys, err := fs.Sub(static, "static") if err != nil { return nil, err @@ -106,10 +106,11 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call handler := mux.NewRouter() - handler.Use(callDurationInterceptor, instanceHandler, security, accessInterceptor) + handler.Use(callDurationInterceptor, instanceHandler, security, limitingAccessInterceptor.WithoutLimiting().Handle) handler.Handle(envRequestPath, middleware.TelemetryHandler()(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { url := http_util.BuildOrigin(r.Host, externalSecure) - instance := authz.GetInstance(r.Context()) + ctx := r.Context() + instance := authz.GetInstance(ctx) instanceMgmtURL, err := templateInstanceManagementURL(config.InstanceManagementURL, instance) if err != nil { http.Error(w, fmt.Sprintf("unable to template instance management url for console: %v", err), http.StatusInternalServerError) @@ -120,6 +121,11 @@ func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, call http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError) return } + if limitingAccessInterceptor.Limit(ctx) { + limitingAccessInterceptor.SetExhaustedCookie(w, r) + } else { + limitingAccessInterceptor.DeleteExhaustedCookie(w, r) + } _, err = w.Write(environmentJSON) logging.OnError(err).Error("error serving environment.json") }))) diff --git a/internal/api/ui/login/renderer.go b/internal/api/ui/login/renderer.go index a9b12f19dc..5c20168d35 100644 --- a/internal/api/ui/login/renderer.go +++ b/internal/api/ui/login/renderer.go @@ -327,7 +327,12 @@ func (l *Login) chooseNextStep(w http.ResponseWriter, r *http.Request, authReq * func (l *Login) renderInternalError(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, err error) { var msg string if err != nil { - logging.WithError(err).WithField("auth_req_id", authReq.ID).Error() + log := logging.WithError(err) + if authReq != nil { + log = log.WithField("auth_req_id", authReq.ID) + } + log.Error() + _, msg = l.getErrorMessage(r, err) } data := l.getBaseData(r, authReq, "Errors.Internal", "", "Internal", msg) diff --git a/proto/zitadel/system.proto b/proto/zitadel/system.proto index b34977e621..4bfec35e6e 100644 --- a/proto/zitadel/system.proto +++ b/proto/zitadel/system.proto @@ -229,7 +229,7 @@ service SystemService { }; } - // Returns the domain of an instance + // Removes the domain of an instance rpc RemoveDomain(RemoveDomainRequest) returns (RemoveDomainResponse) { option (google.api.http) = { delete: "/instances/{instance_id}/domains/{domain}"; @@ -240,7 +240,7 @@ service SystemService { }; } - // Returns the domain of an instance + // Sets the primary domain of an instance rpc SetPrimaryDomain(SetPrimaryDomainRequest) returns (SetPrimaryDomainResponse) { option (google.api.http) = { post: "/instances/{instance_id}/domains/_set_primary";