- 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.
79 lines
1.9 KiB
Go
79 lines
1.9 KiB
Go
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)
|
|
}
|