diff --git a/satellite/console/consoleweb/consoleapi/auth.go b/satellite/console/consoleweb/consoleapi/auth.go index 7505dbd01..5ce89937f 100644 --- a/satellite/console/consoleweb/consoleapi/auth.go +++ b/satellite/console/consoleweb/consoleapi/auth.go @@ -1062,6 +1062,8 @@ func (a *Auth) serveJSONError(w http.ResponseWriter, err error) { // getStatusCode returns http.StatusCode depends on console error class. func (a *Auth) getStatusCode(err error) int { + var maxBytesError *http.MaxBytesError + switch { case console.ErrValidation.Has(err), console.ErrCaptcha.Has(err), console.ErrMFAMissing.Has(err), console.ErrMFAPasscode.Has(err), console.ErrMFARecoveryCode.Has(err), console.ErrChangePassword.Has(err): return http.StatusBadRequest @@ -1071,6 +1073,8 @@ func (a *Auth) getStatusCode(err error) int { return http.StatusConflict case errors.Is(err, errNotImplemented): return http.StatusNotImplemented + case errors.As(err, &maxBytesError): + return http.StatusRequestEntityTooLarge default: return http.StatusInternalServerError } @@ -1078,6 +1082,8 @@ func (a *Auth) getStatusCode(err error) int { // getUserErrorMessage returns a user-friendly representation of the error. func (a *Auth) getUserErrorMessage(err error) string { + var maxBytesError *http.MaxBytesError + switch { case console.ErrCaptcha.Has(err): return "Validation of captcha was unsuccessful" @@ -1104,6 +1110,8 @@ func (a *Auth) getUserErrorMessage(err error) string { return err.Error() case errors.Is(err, errNotImplemented): return "The server is incapable of fulfilling the request" + case errors.As(err, &maxBytesError): + return "Request body is too large" default: return "There was an error processing your request" } diff --git a/satellite/console/consoleweb/consoleapi/payments.go b/satellite/console/consoleweb/consoleapi/payments.go index f29b1e5a9..7d39317b3 100644 --- a/satellite/console/consoleweb/consoleapi/payments.go +++ b/satellite/console/consoleweb/consoleapi/payments.go @@ -343,8 +343,7 @@ func (p *Payments) ApplyCouponCode(w http.ResponseWriter, r *http.Request) { var err error defer mon.Task()(&ctx)(&err) - // limit the size of the body to prevent excessive memory usage - bodyBytes, err := io.ReadAll(io.LimitReader(r.Body, 1*1024*1024)) + bodyBytes, err := io.ReadAll(r.Body) if err != nil { p.serveJSONError(w, http.StatusInternalServerError, err) return diff --git a/satellite/console/consoleweb/server.go b/satellite/console/consoleweb/server.go index 5bf4fcee7..9df82f1ff 100644 --- a/satellite/console/consoleweb/server.go +++ b/satellite/console/consoleweb/server.go @@ -32,6 +32,7 @@ import ( "golang.org/x/sync/errgroup" "storj.io/common/errs2" + "storj.io/common/memory" "storj.io/common/storj" "storj.io/storj/private/web" "storj.io/storj/satellite/abtesting" @@ -106,6 +107,8 @@ type Config struct { OauthAccessTokenExpiry time.Duration `help:"how long oauth access tokens are issued for" default:"24h"` OauthRefreshTokenExpiry time.Duration `help:"how long oauth refresh tokens are issued for" default:"720h"` + BodySizeLimit memory.Size `help:"The maximum body size allowed to be received by the API" default:"100.00 KB"` + // RateLimit defines the configuration for the IP and userID rate limiters. RateLimit web.RateLimiterConfig @@ -239,6 +242,9 @@ func NewServer(logger *zap.Logger, config Config, service *console.Service, oidc // the earliest in the HTTP chain. router.Use(newTraceRequestMiddleware(logger, router)) + // limit body size + router.Use(newBodyLimiterMiddleware(logger.Named("body-limiter-middleware"), config.BodySizeLimit)) + if server.config.GeneratedAPIEnabled { consoleapi.NewProjectManagement(logger, mon, server.service, router, &apiAuth{&server}) consoleapi.NewAPIKeyManagement(logger, mon, server.service, router, &apiAuth{&server}) @@ -980,3 +986,18 @@ func newTraceRequestMiddleware(log *zap.Logger, root *mux.Router) mux.Middleware }) } } + +// newBodyLimiterMiddleware returns a middleware that places a length limit on each request's body. +func newBodyLimiterMiddleware(log *zap.Logger, limit memory.Size) mux.MiddlewareFunc { + return func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.ContentLength > limit.Int64() { + web.ServeJSONError(log, w, http.StatusRequestEntityTooLarge, errs.New("Request body is too large")) + return + } + + r.Body = http.MaxBytesReader(w, r.Body, limit.Int64()) + next.ServeHTTP(w, r) + }) + } +} diff --git a/scripts/testdata/satellite-config.yaml.lock b/scripts/testdata/satellite-config.yaml.lock index 3ef15cbc2..3e3ea3661 100755 --- a/scripts/testdata/satellite-config.yaml.lock +++ b/scripts/testdata/satellite-config.yaml.lock @@ -190,6 +190,9 @@ compensation.withheld-percents: 75,75,75,50,50,50,25,25,25,0,0,0,0,0,0 # url link for for beta satellite support # console.beta-satellite-support-url: "" +# The maximum body size allowed to be received by the API +# console.body-size-limit: 100.00 KB + # whether or not captcha is enabled # console.captcha.login.hcaptcha.enabled: false diff --git a/web/satellite/src/components/account/NewSettingsArea.vue b/web/satellite/src/components/account/NewSettingsArea.vue index d11560a45..20802abf0 100644 --- a/web/satellite/src/components/account/NewSettingsArea.vue +++ b/web/satellite/src/components/account/NewSettingsArea.vue @@ -262,6 +262,12 @@ onMounted(() => { font-size: 14px; line-height: 20px; color: var(--c-grey-6); + word-wrap: break-word; + overflow: hidden; + + @media screen and (width <= 500px) { + width: 100%; + } } &__actions { diff --git a/web/satellite/src/components/common/RegistrationSuccess.vue b/web/satellite/src/components/common/RegistrationSuccess.vue index 15f376986..d7f2bac33 100644 --- a/web/satellite/src/components/common/RegistrationSuccess.vue +++ b/web/satellite/src/components/common/RegistrationSuccess.vue @@ -9,7 +9,7 @@

You're almost there!

-
+
If an account with the email address exists, a verification email has been sent. @@ -197,6 +197,10 @@ onBeforeUnmount(() => { text-align: center; margin-bottom: 27px; + &.fill { + max-width: unset; + } + &__email { font-family: 'font_bold', sans-serif; } diff --git a/web/satellite/src/components/common/VSearch.vue b/web/satellite/src/components/common/VSearch.vue index 43db1329b..ef3676d3a 100644 --- a/web/satellite/src/components/common/VSearch.vue +++ b/web/satellite/src/components/common/VSearch.vue @@ -11,6 +11,7 @@ :style="style" type="text" autocomplete="off" + maxlength="72" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave" @input="processSearchQuery" diff --git a/web/satellite/src/components/common/VSearchAlternateStyling.vue b/web/satellite/src/components/common/VSearchAlternateStyling.vue index 76a4bcd5d..44ca2794f 100644 --- a/web/satellite/src/components/common/VSearchAlternateStyling.vue +++ b/web/satellite/src/components/common/VSearchAlternateStyling.vue @@ -9,6 +9,7 @@ type="text" autocomplete="off" readonly + maxlength="72" @input="processSearchQuery" @focus="removeReadOnly" @blur="addReadOnly" diff --git a/web/satellite/src/components/modals/EditProfileModal.vue b/web/satellite/src/components/modals/EditProfileModal.vue index b46231136..6a7db2a9b 100644 --- a/web/satellite/src/components/modals/EditProfileModal.vue +++ b/web/satellite/src/components/modals/EditProfileModal.vue @@ -14,6 +14,7 @@