mirror of
https://github.com/Forceu/Gokapi.git
synced 2026-05-12 17:40:08 -05:00
(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:
+73
-67
@@ -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
@@ -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`
|
||||
|
||||
|
||||
**********************************
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
+21
-5
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user