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>
64 lines
2.4 KiB
Go
64 lines
2.4 KiB
Go
package files
|
||
|
||
import (
|
||
"errors"
|
||
"net/http"
|
||
|
||
"github.com/google/uuid"
|
||
|
||
"aether/internal/acl"
|
||
"aether/internal/auth"
|
||
"aether/pkg/response"
|
||
)
|
||
|
||
// Zugriffsprüfung für Dateien und Ordner, die dem Anfragenden nicht selbst
|
||
// gehören müssen.
|
||
//
|
||
// Die Datenbankschicht arbeitet durchgehend eigentümerbezogen (`WHERE owner_id
|
||
// = $x`) – daran ändert sich nichts. Stattdessen klären diese Helfer vorab, in
|
||
// wessen Namen die Operation läuft: bei eigenen Daten der Anfragende selbst,
|
||
// bei geliehenen der Eigentümer. Danach sieht der Rest des Handlers aus wie
|
||
// vorher.
|
||
|
||
// resolveFile liefert den Zugriffskontext für eine Datei. Ist der Zugriff nicht
|
||
// erlaubt, ist die Antwort bereits geschrieben und der zweite Rückgabewert
|
||
// false – der Handler bricht dann einfach ab.
|
||
func (h *Handler) resolveFile(w http.ResponseWriter, r *http.Request, id uuid.UUID, needWrite bool) (acl.Access, bool) {
|
||
user := auth.UserFromContext(r.Context())
|
||
if h.acl == nil {
|
||
return acl.Access{OwnerID: user.ID, OwnerUsername: user.Username, OwnerQuota: user.QuotaBytes, Permission: acl.PermWrite}, true
|
||
}
|
||
a, err := h.acl.ResolveFile(r.Context(), user.ID, id)
|
||
return finish(w, a, err, needWrite, "file")
|
||
}
|
||
|
||
// resolveFolder ist dasselbe für Ordner. folderID darf nil sein – das ist die
|
||
// eigene Wurzel, die niemandem sonst gehört.
|
||
func (h *Handler) resolveFolder(w http.ResponseWriter, r *http.Request, id *uuid.UUID, needWrite bool) (acl.Access, bool) {
|
||
user := auth.UserFromContext(r.Context())
|
||
own := acl.Access{OwnerID: user.ID, OwnerUsername: user.Username, OwnerQuota: user.QuotaBytes, Permission: acl.PermWrite}
|
||
if h.acl == nil || id == nil {
|
||
return own, true
|
||
}
|
||
a, err := h.acl.ResolveFolder(r.Context(), user.ID, *id)
|
||
return finish(w, a, err, needWrite, "folder")
|
||
}
|
||
|
||
func finish(w http.ResponseWriter, a acl.Access, err error, needWrite bool, kind string) (acl.Access, bool) {
|
||
switch {
|
||
case errors.Is(err, acl.ErrNotFound), errors.Is(err, acl.ErrNoAccess):
|
||
// Absichtlich 404 statt 403: Ein „verboten" verriete, dass es die ID
|
||
// gibt. Wer keinen Zugriff hat, soll nicht einmal das erfahren.
|
||
response.Error(w, http.StatusNotFound, kind+" not found")
|
||
return acl.Access{}, false
|
||
case err != nil:
|
||
response.Error(w, http.StatusInternalServerError, "database error")
|
||
return acl.Access{}, false
|
||
}
|
||
if needWrite && !a.CanWrite() {
|
||
response.Error(w, http.StatusForbidden, "you only have read access to this "+kind)
|
||
return acl.Access{}, false
|
||
}
|
||
return a, true
|
||
}
|