feat(storage): add S3 backend support and advanced upload limits
- Introduce S3-compatible storage backend support using minio-go. - Add configuration options for local storage limits, box limits, and rate limiting. - Implement storage backend selection (local vs S3) for anonymous and registered users. - Add an `/admin/storage` management interface. - Update documentation and environment examples with the new configuration variables.
This commit is contained in:
78
backend/libs/handlers/security.go
Normal file
78
backend/libs/handlers/security.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const csrfCookieName = "warpbox_csrf"
|
||||
|
||||
type rateLimiter struct {
|
||||
mu sync.Mutex
|
||||
records map[string]rateRecord
|
||||
}
|
||||
|
||||
type rateRecord struct {
|
||||
StartedAt time.Time
|
||||
Count int
|
||||
}
|
||||
|
||||
func newRateLimiter() *rateLimiter {
|
||||
return &rateLimiter{records: make(map[string]rateRecord)}
|
||||
}
|
||||
|
||||
func (l *rateLimiter) Allow(key string, limit int, window time.Duration, now time.Time) bool {
|
||||
if limit <= 0 || window <= 0 {
|
||||
return true
|
||||
}
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
record := l.records[key]
|
||||
if record.StartedAt.IsZero() || now.Sub(record.StartedAt) >= window {
|
||||
l.records[key] = rateRecord{StartedAt: now, Count: 1}
|
||||
return true
|
||||
}
|
||||
record.Count++
|
||||
l.records[key] = record
|
||||
return record.Count <= limit
|
||||
}
|
||||
|
||||
func (a *App) csrfToken(w http.ResponseWriter, r *http.Request) string {
|
||||
if cookie, err := r.Cookie(csrfCookieName); err == nil && cookie.Value != "" {
|
||||
return cookie.Value
|
||||
}
|
||||
token := randomToken(32)
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: csrfCookieName,
|
||||
Value: token,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
Secure: r.TLS != nil,
|
||||
Expires: time.Now().Add(12 * time.Hour),
|
||||
})
|
||||
return token
|
||||
}
|
||||
|
||||
func (a *App) validateCSRF(w http.ResponseWriter, r *http.Request) bool {
|
||||
if r.Method == http.MethodGet || r.Method == http.MethodHead || r.Method == http.MethodOptions {
|
||||
return true
|
||||
}
|
||||
cookie, err := r.Cookie(csrfCookieName)
|
||||
if err != nil || cookie.Value == "" || r.FormValue("csrf_token") != cookie.Value {
|
||||
http.Error(w, "invalid form token", http.StatusForbidden)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func randomToken(byteCount int) string {
|
||||
data := make([]byte, byteCount)
|
||||
if _, err := rand.Read(data); err != nil {
|
||||
return base64.RawURLEncoding.EncodeToString([]byte(time.Now().String()))
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(data)
|
||||
}
|
||||
Reference in New Issue
Block a user