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,67 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"aether/pkg/response"
|
||||
)
|
||||
|
||||
type contextKey int
|
||||
|
||||
const keyUser contextKey = iota
|
||||
|
||||
// RequireAuth loads the session user and injects it into context.
|
||||
// Returns 401 if not authenticated.
|
||||
func RequireAuth(ss *SessionStore, store *Store) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
id, ok := ss.GetUserID(r)
|
||||
if !ok {
|
||||
response.Error(w, http.StatusUnauthorized, "authentication required")
|
||||
return
|
||||
}
|
||||
user, err := store.GetByID(r.Context(), id)
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusUnauthorized, "authentication required")
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), keyUser, user)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// RequireAdmin additionally checks that the user is an admin.
|
||||
func RequireAdmin(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := UserFromContext(r.Context())
|
||||
if user == nil || !user.IsAdmin {
|
||||
response.Error(w, http.StatusForbidden, "admin access required")
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// CSRF validates the double-submit CSRF token for state-changing requests.
|
||||
func CSRF(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet, http.MethodHead, http.MethodOptions:
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if !ValidateCSRF(r) {
|
||||
response.Error(w, http.StatusForbidden, "invalid CSRF token")
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// UserFromContext retrieves the authenticated user from context (may be nil).
|
||||
func UserFromContext(ctx context.Context) *User {
|
||||
u, _ := ctx.Value(keyUser).(*User)
|
||||
return u
|
||||
}
|
||||
Reference in new issue
Block a user