feat(aether): initial commit – self-hosted Cloud, Phasen 1–7
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA. Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb, Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming, Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office (Collabora/OnlyOffice). Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort) sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
31a027c93a
225 files changed
+38042
No files matched your search
@@ -0,0 +1,75 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"aether/pkg/config"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
const (
|
||||
keyRealIP contextKey = iota
|
||||
keyIsHTTPS contextKey = iota
|
||||
)
|
||||
|
||||
// RealIP returns the real client IP stored in context by TrustedProxy middleware.
|
||||
// Falls back to r.RemoteAddr if the middleware was not applied.
|
||||
func RealIP(r *http.Request) string {
|
||||
if ip, ok := r.Context().Value(keyRealIP).(string); ok && ip != "" {
|
||||
return ip
|
||||
}
|
||||
host, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
return r.RemoteAddr
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
// IsHTTPS reports whether the original client connection was HTTPS,
|
||||
// either directly or via a trusted proxy's X-Forwarded-Proto header.
|
||||
func IsHTTPS(r *http.Request) bool {
|
||||
if v, ok := r.Context().Value(keyIsHTTPS).(bool); ok {
|
||||
return v
|
||||
}
|
||||
return r.TLS != nil
|
||||
}
|
||||
|
||||
// TrustedProxy extracts the real client IP and protocol from X-Forwarded-*
|
||||
// headers, but only when the immediate peer is in the trusted proxy list.
|
||||
// Untrusted peers never override RemoteAddr.
|
||||
func TrustedProxy(cfg *config.Config) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
remoteHost = r.RemoteAddr
|
||||
}
|
||||
peerIP := net.ParseIP(remoteHost)
|
||||
|
||||
realIP := remoteHost
|
||||
isHTTPS := r.TLS != nil
|
||||
|
||||
if peerIP != nil && cfg.IsTrustedProxy(peerIP) {
|
||||
if xfp := r.Header.Get("X-Forwarded-Proto"); xfp != "" {
|
||||
isHTTPS = strings.EqualFold(strings.TrimSpace(xfp), "https")
|
||||
}
|
||||
// X-Forwarded-For may be a comma-separated chain; the leftmost
|
||||
// entry is the original client (set by the first proxy).
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
parts := strings.SplitN(xff, ",", 2)
|
||||
if candidate := strings.TrimSpace(parts[0]); candidate != "" {
|
||||
realIP = candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), keyRealIP, realIP)
|
||||
ctx = context.WithValue(ctx, keyIsHTTPS, isHTTPS)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user