satellite/{consoleapi,web}: limit user input

This change limits the length of user input fields like search, email,
username. It also limits the receivable size of request payloads.
This is to prevent potential DDoS attacks resulting from receiving
large payloads.
Improvements are also made to the accounts page and register success
pages to display long names/emails better.

Issue: https://github.com/storj/storj-private/issues/201

Change-Id: I5d36eb83609b3605335a8d150b275c89deaf3b43
This commit is contained in:
Wilfred Asomani 2023-05-18 10:07:36 +00:00 committed by Storj Robot
parent 61ad5751f6
commit 2e3ed973de
11 changed files with 52 additions and 3 deletions

View File

@ -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"
}

View File

@ -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

View File

@ -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)
})
}
}

View File

@ -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

View File

@ -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 {

View File

@ -9,7 +9,7 @@
<div class="register-success-area__container">
<MailIcon />
<h2 class="register-success-area__container__title" aria-roledescription="title">You're almost there!</h2>
<div v-if="showManualActivationMsg" class="register-success-area__container__sub-title">
<div v-if="showManualActivationMsg" class="register-success-area__container__sub-title fill">
If an account with the email address
<p class="register-success-area__container__sub-title__email">{{ userEmail }}</p>
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;
}

View File

@ -11,6 +11,7 @@
:style="style"
type="text"
autocomplete="off"
maxlength="72"
@mouseenter="onMouseEnter"
@mouseleave="onMouseLeave"
@input="processSearchQuery"

View File

@ -9,6 +9,7 @@
type="text"
autocomplete="off"
readonly
maxlength="72"
@input="processSearchQuery"
@focus="removeReadOnly"
@blur="addReadOnly"

View File

@ -14,6 +14,7 @@
<VInput
label="Full Name"
placeholder="Enter Full Name"
max-symbols="72"
:error="fullNameError"
:init-value="userInfo.fullName"
@setData="setFullName"

View File

@ -50,6 +50,7 @@
<div class="forgot-area__content-area__container__input-wrapper">
<VInput
label="Email Address"
max-symbols="72"
placeholder="user@example.com"
:error="emailError"
@setData="setEmail"

View File

@ -115,6 +115,7 @@
<div class="register-area__input-wrapper first-input">
<VInput
label="Full Name"
max-symbols="72"
placeholder="Enter Full Name"
:error="fullNameError"
role-description="name"
@ -124,6 +125,7 @@
<div class="register-area__input-wrapper">
<VInput
label="Email Address"
max-symbols="72"
placeholder="user@example.com"
:error="emailError"
role-description="email"
@ -134,6 +136,7 @@
<div class="register-area__input-wrapper">
<VInput
label="Company Name"
max-symbols="72"
placeholder="Acme Corp."
:error="companyNameError"
role-description="company-name"
@ -143,6 +146,7 @@
<div class="register-area__input-wrapper">
<VInput
label="Position"
max-symbols="72"
placeholder="Position Title"
:error="positionError"
role-description="position"