(Breaking) Add better rate limiting for invalid logins or invalid file IDs

Safer IP lookup, BREAKING: Trusted_Proxies must be set if not 127.0.0.1
This commit is contained in:
Marc Ole Bulling
2026-01-30 00:39:36 +01:00
parent c90e54e4d6
commit c348f4f373
9 changed files with 181 additions and 123 deletions
+73 -67
View File
@@ -59,73 +59,79 @@ Available environment variables
==================================
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| Name | Action | Persistent [*]_ | Default |
+================================+=====================================================================================+=================+=============================+
| GOKAPI_CHUNK_SIZE_MB | Sets the size of chunks that are uploaded in MB | Yes | 45 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_CONFIG_DIR | Sets the directory for the config file | No | config |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_CONFIG_FILE | Sets the name of the config file | No | config.json |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_DATA_DIR | Sets the directory for the data | Yes | data |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_DISABLE_CORS_CHECK | Disables the CORS check on startup and during setup, if set to true | No | false |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_ENABLE_HOTLINK_VIDEOS | Allow hotlinking of videos. Note: Due to buffering, playing a video might count as | No | false |
| | | | |
| | multiple downloads. It is only recommended to use video hotlinking for uploads with | | |
| | | | |
| | unlimited downloads enabled | | |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_GUEST_UPLOAD_BY_DEFAULT | Allows all users by default to create file requests, if set to true | No | false |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_LENGTH_HOTLINK_ID | Sets the length of the hotlink IDs. Value must be 8 or greater | No | 40 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_LENGTH_ID | Sets the length of the download IDs. Value must be 5 or greater | No | 15 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_LOG_STDOUT | Also outputs all log file entries to the console output, if set to true | No | false |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_FILESIZE | Sets the maximum allowed file size in MB | Yes | 102400 |
| | | | |
| | Default 102400 = 100GB | | |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_FILES_GUESTUPLOAD | Sets the maximum number of files that can be uploaded per file requests created by | No | 100 |
| | | | |
| | non-admin users | | |
| | | | |
| | Set to 0 to allow unlimited file count for all users | | |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_MEMORY_UPLOAD | Sets the amount of RAM in MB that can be allocated for an upload chunk or file | Yes | 50 |
| | | | |
| | Any chunk or file with a size greater than that will be written to a temporary file | | |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_PARALLEL_UPLOADS | Set the number of chunks that are uploaded in parallel for a single file | Yes | 3 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_SIZE_GUESTUPLOAD | Sets the maximum file size for file requests created by | No | 10240 |
| | | | |
| | non-admin users | | |
| | | | |
| | Set to 0 to allow files with a size of up to a value set with GOKAPI_MAX_FILESIZE | | |
| | | | |
| | for all users | | |
| | | | |
| | Default 10240 = 10GB | | |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MIN_FREE_SPACE | Sets the minium free space on the disk in MB for accepting an upload | No | 400 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MIN_LENGTH_PASSWORD | Sets the minium password length. Value must be 6 or greater | No | 8 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
| | | | |
| | | | Docker: [DATA_DIR] |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
| DOCKER_NONROOT | DEPRECATED. | No | false |
| | | | |
| | Docker only: Runs the binary in the container as a non-root user, if set to "true" | | |
+--------------------------------+-------------------------------------------------------------------------------------+-----------------+-----------------------------+
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| Name | Action | Persistent [*]_ | Default |
+================================+========================================================================================+=================+=============================+
| GOKAPI_CHUNK_SIZE_MB | Sets the size of chunks that are uploaded in MB | Yes | 45 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_CONFIG_DIR | Sets the directory for the config file | No | config |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_CONFIG_FILE | Sets the name of the config file | No | config.json |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_DATA_DIR | Sets the directory for the data | Yes | data |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_DISABLE_CORS_CHECK | Disables the CORS check on startup and during setup, if set to true | No | false |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_ENABLE_HOTLINK_VIDEOS | Allow hotlinking of videos. Note: Due to buffering, playing a video might count as | No | false |
| | | | |
| | multiple downloads. It is only recommended to use video hotlinking for uploads with | | |
| | | | |
| | unlimited downloads enabled | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_GUEST_UPLOAD_BY_DEFAULT | Allows all users by default to create file requests, if set to true | No | false |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_LENGTH_HOTLINK_ID | Sets the length of the hotlink IDs. Value must be 8 or greater | No | 40 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_LENGTH_ID | Sets the length of the download IDs. Value must be 5 or greater | No | 15 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_LOG_STDOUT | Also outputs all log file entries to the console output, if set to true | No | false |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_FILESIZE | Sets the maximum allowed file size in MB | Yes | 102400 |
| | | | |
| | Default 102400 = 100GB | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_FILES_GUESTUPLOAD | Sets the maximum number of files that can be uploaded per file requests created by | No | 100 |
| | | | |
| | non-admin users | | |
| | | | |
| | Set to 0 to allow unlimited file count for all users | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_MEMORY_UPLOAD | Sets the amount of RAM in MB that can be allocated for an upload chunk or file | Yes | 50 |
| | | | |
| | Any chunk or file with a size greater than that will be written to a temporary file | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_PARALLEL_UPLOADS | Set the number of chunks that are uploaded in parallel for a single file | Yes | 3 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MAX_SIZE_GUESTUPLOAD | Sets the maximum file size for file requests created by | No | 10240 |
| | | | |
| | non-admin users | | |
| | | | |
| | Set to 0 to allow files with a size of up to a value set with GOKAPI_MAX_FILESIZE | | |
| | | | |
| | for all users | | |
| | | | |
| | Default 10240 = 10GB | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MIN_FREE_SPACE | Sets the minium free space on the disk in MB for accepting an upload | No | 400 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_MIN_LENGTH_PASSWORD | Sets the minium password length. Value must be 6 or greater | No | 8 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_PORT | Sets the webserver port | Yes | 53842 |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| GOKAPI_TRUSTED_PROXIES | Sets a list of trusted proxies. If set, the webserver will trust the IP addresses sent | No | 127.0.0.1 |
| | | | |
| | by these proxies with the X-Forwarded-For and X-REAL-IP header | | |
| | | | |
| | List is comma separated | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| TMPDIR | Sets the path which contains temporary files | No | Non-Docker: Default OS path |
| | | | |
| | | | Docker: [DATA_DIR] |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
| DOCKER_NONROOT | DEPRECATED. | No | false |
| | | | |
| | Docker only: Runs the binary in the container as a non-root user, if set to "true" | | |
+--------------------------------+----------------------------------------------------------------------------------------+-----------------+-----------------------------+
.. [*] Variables that are persistent must be submitted during the first start when Gokapi creates a new config file. They can be omitted afterwards. Non-persistent variables need to be set on every start.
+4 -2
View File
@@ -395,9 +395,11 @@ If you are using Docker, shut down the running instance and create a new tempora
Reverse Proxy
**********************************
It is highly recommended to run Gokapi behind a reverse proxy. Make sure to select a high timeout (recommended: 300 seconds) and increase the allowed body size.
Running Gokapi behind a reverse proxy is strongly recommended. Configure the proxy with a sufficiently high timeout (recommended: 300 seconds) and ensure that the maximum allowed request body size is increased accordingly.
An example for Nginx can be found here: :ref:`nginx_config`
If your reverse proxy does not use 127.0.0.1 as its source IP, you must explicitly specify the trusted proxy IP address(es) using the environment variable ``GOKAPI_TRUSTED_PROXIES``, see :ref:`availenvvar`
An example configuration for Nginx is available here: :ref:`nginx_config`
**********************************
+10 -6
View File
@@ -30,12 +30,16 @@ type Environment struct {
ConfigPath string
// Sets the directory for the data
DataDir string `env:"DATA_DIR" envDefault:"data" persistent:"true"`
// Disables the CORS check on startup and during setup, if set to true
DisableCorsCheck bool `env:"DISABLE_CORS_CHECK" envDefault:"false"`
// Sets the size of chunks that are uploaded in MB
ChunkSizeMB int `env:"CHUNK_SIZE_MB" envDefault:"45" onlyPositive:"true" persistent:"true"`
// Sets the length of the download IDs
LengthId int `env:"LENGTH_ID" envDefault:"15" minValue:"5"`
// Sets the length of the hotlink IDs
LengthHotlinkId int `env:"LENGTH_HOTLINK_ID" envDefault:"40" minValue:"8"`
// Also outputs all log file entries to the console output, if set to true
LogToStdout bool `env:"LOG_STDOUT" envDefault:"false"`
// Sets the maximum allowed file size in MB
// Default 102400 = 100GB
MaxFileSize int `env:"MAX_FILESIZE" envDefault:"102400" onlyPositive:"true" persistent:"true"`
@@ -58,14 +62,14 @@ type Environment struct {
MinFreeSpaceMB int `env:"MIN_FREE_SPACE" envDefault:"400" onlyPositive:"true"`
// Sets the minium password length
MinLengthPassword int `env:"MIN_LENGTH_PASSWORD" envDefault:"8" minValue:"6"`
// Sets the webserver port
WebserverPort int `env:"PORT" envDefault:"53842" onlyPositive:"true" persistent:"true"`
// Disables the CORS check on startup and during setup, if set to true
DisableCorsCheck bool `env:"DISABLE_CORS_CHECK" envDefault:"false"`
// Allows all users by default to create file requests, if set to true
PermRequestGrantedByDefault bool `env:"GUEST_UPLOAD_BY_DEFAULT" envDefault:"false"`
// Also outputs all log file entries to the console output, if set to true
LogToStdout bool `env:"LOG_STDOUT" envDefault:"false"`
// Sets a list of trusted proxies. If set, the webserver will trust the IP addresses sent
// by these proxies with the X-Forwarded-For and X-REAL-IP header
// List is comma separated
TrustedProxies []string `env:"TRUSTED_PROXIES" envSeparator:"," envDefault:"127.0.0.1"`
// Sets the webserver port
WebserverPort int `env:"PORT" envDefault:"53842" onlyPositive:"true" persistent:"true"`
// Allow hotlinking of videos. Note: Due to buffering, playing a video might count as
// multiple downloads. It is only recommended to use video hotlinking for uploads with
// unlimited downloads enabled
+49 -27
View File
@@ -27,12 +27,14 @@ const categoryAuth = "auth"
const categoryWarning = "warning"
var outputToStdout = false
var trustedProxies []string
// Init sets the path where to write the log file to
func Init(filePath string) {
logPath = filePath + "/log.txt"
env := environment.New()
outputToStdout = env.LogToStdout
trustedProxies = env.TrustedProxies
}
// GetAll returns all log entries as a single string and if the log file exists
@@ -207,10 +209,15 @@ func LogUserCreation(modifiedUser, userEditor models.User) {
modifiedUser.Name, modifiedUser.Id, userEditor.Name, userEditor.Id), false)
}
// LogInvalidLogin adds a log entry to indicate that an invalid login was attempted. Non-blocking
func LogInvalidLogin(username, ip string) {
createLogEntry(categoryAuth, fmt.Sprintf("Invalid login for user %s by IP %s", username, ip), false)
}
// LogDownload adds a log entry when a download was requested. Non-Blocking
func LogDownload(file models.File, r *http.Request, saveIp bool) {
if saveIp {
createLogEntry(categoryDownload, fmt.Sprintf("%s, IP %s, ID %s, Useragent %s", file.Name, getIpAddress(r), file.Id, r.UserAgent()), false)
createLogEntry(categoryDownload, fmt.Sprintf("%s, IP %s, ID %s, Useragent %s", file.Name, GetIpAddress(r), file.Id, r.UserAgent()), false)
} else {
createLogEntry(categoryDownload, fmt.Sprintf("%s, ID %s, Useragent %s", file.Name, file.Id, r.UserAgent()), false)
}
@@ -305,7 +312,7 @@ func parseTimeLogEntry(input string) (time.Time, error) {
func getLogDeletionMessage(userName string, userId int, r *http.Request, timestamp time.Time) string {
return createLogFormatCustomTimestamp(categoryWarning, fmt.Sprintf("Previous logs deleted by %s (user #%d) on %s. IP: %s\n",
userName, userId, getDate(time.Now()), getIpAddress(r)), timestamp)
userName, userId, getDate(time.Now()), GetIpAddress(r)), timestamp)
}
func deleteAllLogs(userName string, userId int, r *http.Request) {
@@ -330,32 +337,47 @@ func getDate(timestamp time.Time) string {
return timestamp.UTC().Format(time.RFC1123)
}
func getIpAddress(r *http.Request) string {
// Get IP from X-FORWARDED-FOR header
ips := r.Header.Get("X-FORWARDED-FOR")
splitIps := strings.Split(ips, ",")
for _, ip := range splitIps {
netIP := net.ParseIP(ip)
if netIP != nil {
return ip
func isTrustedProxy(ip string) bool {
for _, proxy := range trustedProxies {
if ip == proxy {
return true
}
}
return false
}
// GetIpAddress returns the IP address of the requester
func GetIpAddress(r *http.Request) string {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
ip = r.RemoteAddr
}
// Clean up if it's an IPv6 zone
netIP := net.ParseIP(ip)
// Check if the immediate connector is a Trusted Proxy and if yes, use their header for IP
// Otherwise this returns the actual IP used for the connection
if netIP != nil && isTrustedProxy(netIP.String()) {
// Check X-Forwarded-For
// Ideally, use the last IP in the list if a proxy appends to it
xff := r.Header.Get("X-FORWARDED-FOR")
if xff != "" {
ips := strings.Split(xff, ",")
//Take the last IP or investigate based on proxy setup
realIP := strings.TrimSpace(ips[len(ips)-1])
if net.ParseIP(realIP) != nil {
return realIP
}
}
// Fallback to X-Real-Ip if XFF fails
xri := r.Header.Get("X-REAL-IP")
if net.ParseIP(xri) != nil {
return xri
}
}
// Get IP from the X-REAL-IP header
ip := r.Header.Get("X-REAL-IP")
netIP := net.ParseIP(ip)
if netIP != nil {
return ip
}
// Get IP from RemoteAddr
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return "Unknown IP"
}
netIP = net.ParseIP(ip)
if netIP != nil {
return ip
}
return "Unknown IP"
return ip
}
+9 -8
View File
@@ -1,14 +1,15 @@
package logging
import (
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/test"
"github.com/forceu/gokapi/internal/test/testconfiguration"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/test"
"github.com/forceu/gokapi/internal/test/testconfiguration"
)
func TestMain(m *testing.M) {
@@ -20,16 +21,16 @@ func TestMain(m *testing.M) {
func TestGetIpAddress(t *testing.T) {
r := httptest.NewRequest("GET", "/test", nil)
test.IsEqualString(t, getIpAddress(r), "192.0.2.1")
test.IsEqualString(t, GetIpAddress(r), "192.0.2.1")
r = httptest.NewRequest("GET", "/test", nil)
r.RemoteAddr = "127.0.0.1:1234"
test.IsEqualString(t, getIpAddress(r), "127.0.0.1")
test.IsEqualString(t, GetIpAddress(r), "127.0.0.1")
r.RemoteAddr = "invalid"
test.IsEqualString(t, getIpAddress(r), "Unknown IP")
test.IsEqualString(t, GetIpAddress(r), "Unknown IP")
r.Header.Add("X-REAL-IP", "1.1.1.1")
test.IsEqualString(t, getIpAddress(r), "1.1.1.1")
test.IsEqualString(t, GetIpAddress(r), "1.1.1.1")
r.Header.Add("X-FORWARDED-FOR", "1.1.1.2")
test.IsEqualString(t, getIpAddress(r), "1.1.1.2")
test.IsEqualString(t, GetIpAddress(r), "1.1.1.2")
}
func TestInit(t *testing.T) {
+13 -6
View File
@@ -29,6 +29,7 @@ import (
"github.com/forceu/gokapi/internal/encryption"
"github.com/forceu/gokapi/internal/environment"
"github.com/forceu/gokapi/internal/helper"
"github.com/forceu/gokapi/internal/logging"
"github.com/forceu/gokapi/internal/logging/serverstats"
"github.com/forceu/gokapi/internal/models"
"github.com/forceu/gokapi/internal/storage"
@@ -41,6 +42,7 @@ import (
"github.com/forceu/gokapi/internal/webserver/authentication/tokengeneration"
"github.com/forceu/gokapi/internal/webserver/favicon"
"github.com/forceu/gokapi/internal/webserver/fileupload"
"github.com/forceu/gokapi/internal/webserver/ratelimiter"
"github.com/forceu/gokapi/internal/webserver/sse"
"github.com/forceu/gokapi/internal/webserver/ssl"
)
@@ -226,6 +228,11 @@ func redirect(w http.ResponseWriter, url string) {
_, _ = io.WriteString(w, "<html><head><meta http-equiv=\"Refresh\" content=\"0; URL=./"+url+"\"></head></html>")
}
func redirectOnIncorrectId(w http.ResponseWriter, r *http.Request, url string) {
ratelimiter.WaitOnFailedId(r)
redirect(w, url)
}
type redirectValues struct {
FileId string
RedirectUrl string
@@ -510,9 +517,9 @@ func showLogin(w http.ResponseWriter, r *http.Request) {
redirect(w, "admin")
return
}
select {
case <-time.After(3 * time.Second):
}
ip := logging.GetIpAddress(r)
logging.LogInvalidLogin(user, ip)
ratelimiter.WaitOnFailedLogin(ip)
failedLogin = true
}
err = templateFolder.ExecuteTemplate(w, "login", LoginView{
@@ -543,7 +550,7 @@ func showDownload(w http.ResponseWriter, r *http.Request) {
keyId := queryUrl(w, r, "id", "error")
file, ok := storage.GetFile(keyId)
if !ok || file.IsFileRequest() {
redirect(w, "error")
redirectOnIncorrectId(w, r, "error")
return
}
@@ -1031,9 +1038,9 @@ func serveFile(id string, isRootUrl bool, w http.ResponseWriter, r *http.Request
savedFile, ok := storage.GetFile(id)
if !ok || savedFile.IsFileRequest() {
if isRootUrl {
redirect(w, "error")
redirectOnIncorrectId(w, r, "error")
} else {
redirect(w, "../../error")
redirectOnIncorrectId(w, r, "../../error")
}
return
}
+1 -1
View File
@@ -20,11 +20,11 @@ import (
"github.com/forceu/gokapi/internal/storage/chunking"
"github.com/forceu/gokapi/internal/storage/chunking/chunkreservation"
"github.com/forceu/gokapi/internal/storage/filerequest"
"github.com/forceu/gokapi/internal/storage/filerequest/ratelimiter"
"github.com/forceu/gokapi/internal/storage/presign"
"github.com/forceu/gokapi/internal/webserver/api/errorcodes"
"github.com/forceu/gokapi/internal/webserver/authentication/users"
"github.com/forceu/gokapi/internal/webserver/fileupload"
"github.com/forceu/gokapi/internal/webserver/ratelimiter"
)
// LengthPublicId is the length of the public ID used for API keys
@@ -1,16 +1,18 @@
package ratelimiter
import (
"context"
"net/http"
"sync"
"time"
"github.com/forceu/gokapi/internal/logging"
"golang.org/x/time/rate"
)
var uuidLimiter = newLimiter()
// Currently unused
var byteLimiter = newLimiter()
var newUuidLimiter = newLimiter()
var failedLoginLimiter = newLimiter()
var failedIdLimiter = newLimiter()
type limiterEntry struct {
limiter *rate.Limiter
@@ -29,9 +31,23 @@ func newLimiter() *store {
}
}
// WaitOnFailedLogin blocks the current goroutine until the rate limiter allows a request
// Two failed attempts without limiting, thereafter one attempt every 3 seconds
func WaitOnFailedLogin(ip string) {
_ = failedLoginLimiter.Get(ip, 1, 6).WaitN(context.Background(), 3)
}
// WaitOnFailedId blocks the current goroutine until the rate limiter allows a request
// Ten failed attempts without limiting, thereafter one attempt every second
func WaitOnFailedId(r *http.Request) {
ip := logging.GetIpAddress(r)
_ = failedIdLimiter.Get(ip, 1, 10).Wait(context.Background())
}
// IsAllowedNewUuid returns true if a new uuid is not rate-limited
// Four initial requests are allowed without rate limiting, thereafter one every second
func IsAllowedNewUuid(key string) bool {
return uuidLimiter.Get(key, 1, 4).Allow()
return newUuidLimiter.Get(key, 1, 4).Allow()
}
// Get returns the rate limiter for the given key
@@ -96,7 +96,7 @@
<select id="logFilter" class="form-select log-dark-input" onchange="filterLogs(this.value)">
<option value="all">All Events</option>
<option value="warning">Warnings</option>
<option value="auth">Security</option>
<option value="auth">Authentication</option>
<option value="upload">Uploads</option>
<option value="edit">Modifications</option>
<option value="download">Downloads</option>