diff --git a/backend/config/config.go b/backend/config/config.go
index 7960e0a..aafe61d 100644
--- a/backend/config/config.go
+++ b/backend/config/config.go
@@ -1,6 +1,7 @@
package config
import (
+ "log"
"os"
"strconv"
)
@@ -49,6 +50,11 @@ func Load() *Config {
cfg.VersionsDir = cfg.StorageDir + "/.versions"
cfg.BackupDir = cfg.StorageDir + "/.backups"
+ if cfg.OnlyOfficeJWT == "" {
+ log.Println("WARNING: ONLYOFFICE_JWT_SECRET not set \u2014 Document Server requests will NOT be authenticated.")
+ log.Println(" Anyone with network access to the Document Server can open/edit documents.")
+ }
+
return cfg
}
diff --git a/backend/handlers/files.go b/backend/handlers/files.go
index 3684aaa..bfb68f6 100644
--- a/backend/handlers/files.go
+++ b/backend/handlers/files.go
@@ -554,7 +554,7 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
isPermanent := c.Query("permanent") == "true"
if isPermanent {
// Ensure we don't delete the root
- if resolvedPath == c.Locals("resolvedPath").(string) && relativePath == "." {
+ if resolvedPath == h.Config.StorageDir && relativePath == "." {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{"error": "cannot permanently delete root"})
}
@@ -592,8 +592,14 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
if err := os.Rename(resolvedPath, trashPath); err != nil {
fmt.Printf("Rename error %s -> %s: %v. Attempting copy & delete fallback...\n", resolvedPath, trashPath, err)
- if err := copyFile(resolvedPath, trashPath); err != nil {
- return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move to trash"})
+ if isDir == 1 {
+ if err := copyDir(resolvedPath, trashPath); err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move directory to trash"})
+ }
+ } else {
+ if err := copyFile(resolvedPath, trashPath); err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move file to trash"})
+ }
}
safeRemovePath(h.Config.StorageDir, resolvedPath)
}
diff --git a/backend/handlers/sharing.go b/backend/handlers/sharing.go
index 24c05ba..8910c52 100644
--- a/backend/handlers/sharing.go
+++ b/backend/handlers/sharing.go
@@ -119,13 +119,11 @@ func (h *ShareHandler) ListShares(c *fiber.Ctx) error {
// Skip expired shares
if expiresAt != nil {
- t, err := time.Parse("2006-01-02 15:04:05-07:00", *expiresAt)
- if err == nil && time.Now().After(t) {
- // To handle different time formats, we could parse it more robustly
- // but for now, we'll let frontend also check or we just check here.
+ t, err := time.Parse(time.RFC3339, *expiresAt)
+ if err != nil {
+ t, err = time.Parse("2006-01-02 15:04:05", *expiresAt)
}
- t2, err2 := time.Parse(time.RFC3339, *expiresAt)
- if err2 == nil && time.Now().After(t2) {
+ if err == nil && time.Now().After(t) {
continue
}
}
diff --git a/backend/handlers/webdav.go b/backend/handlers/webdav.go
index 37ca0b1..3a66a0b 100644
--- a/backend/handlers/webdav.go
+++ b/backend/handlers/webdav.go
@@ -21,6 +21,16 @@ import (
"golang.org/x/net/webdav"
)
+type statusCapture struct {
+ http.ResponseWriter
+ statusCode int
+}
+
+func (sc *statusCapture) WriteHeader(code int) {
+ sc.statusCode = code
+ sc.ResponseWriter.WriteHeader(code)
+}
+
type WebDAVHandler struct {
Handler *webdav.Handler
DB *database.DB
@@ -117,11 +127,11 @@ func (h *WebDAVHandler) Handle(c *fiber.Ctx) error {
// Adapt Fiber's FastHTTP to standard net/http for the WebDAV handler
fasthttpadaptor.NewFastHTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
relPath := strings.TrimPrefix(r.URL.Path, "/webdav")
- // Native WebDAV handles overwriting automatically
- h.Handler.ServeHTTP(w, r)
- // If it was a PUT request, we should sync it with our DB (checksum, size, etc.)
- if r.Method == "PUT" && r.Response != nil && r.Response.StatusCode >= 200 && r.Response.StatusCode < 300 {
+ capture := &statusCapture{ResponseWriter: w, statusCode: 200}
+ h.Handler.ServeHTTP(capture, r)
+
+ if r.Method == "PUT" && capture.statusCode >= 200 && capture.statusCode < 300 {
h.syncToDB(relPath)
}
})(c.Context())
diff --git a/backend/main.go b/backend/main.go
index b7f608f..774249c 100644
--- a/backend/main.go
+++ b/backend/main.go
@@ -104,9 +104,14 @@ func main() {
return c.Next()
})
app.Use(helmet.New())
+ app.Use(func(c *fiber.Ctx) error {
+ c.Set("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' wss: https: http:;")
+ return c.Next()
+ })
allowedOrigins := os.Getenv("ALLOWED_ORIGINS")
if allowedOrigins == "" {
- allowedOrigins = "http://localhost:3000"
+ log.Println("WARNING: ALLOWED_ORIGINS not set \u2014 CORS will reject all cross-origin requests")
+ allowedOrigins = "https://no-origin-configured.invalid"
}
app.Use(cors.New(cors.Config{
diff --git a/backend/middleware/auth.go b/backend/middleware/auth.go
index e125e43..2b0db6b 100644
--- a/backend/middleware/auth.go
+++ b/backend/middleware/auth.go
@@ -23,8 +23,17 @@ func AuthMiddleware(jwtSecret string) fiber.Handler {
isQueryToken := false
if tokenString == "" {
- tokenString = c.Query("token")
- isQueryToken = true
+ allowedPaths := []string{"/api/onlyoffice/callback", "/api/files/download", "/api/files/thumbnail"}
+ path := c.Path()
+ for _, p := range allowedPaths {
+ if strings.HasPrefix(path, p) {
+ tokenString = c.Query("token")
+ if tokenString != "" {
+ isQueryToken = true
+ }
+ break
+ }
+ }
}
if tokenString == "" {
diff --git a/frontend/package.json b/frontend/package.json
index 482cedf..b8edf55 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -6,7 +6,8 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint"
+ "lint": "next lint",
+ "postinstall": "node -e \"require('fs').copyFileSync('node_modules/pdfjs-dist/build/pdf.worker.min.mjs', 'public/pdf.worker.min.mjs')\""
},
"dependencies": {
"@onlyoffice/document-editor-react": "^2.2.0",
diff --git a/frontend/src/components/PdfViewer.tsx b/frontend/src/components/PdfViewer.tsx
index f5f7b3b..c14caa9 100644
--- a/frontend/src/components/PdfViewer.tsx
+++ b/frontend/src/components/PdfViewer.tsx
@@ -7,7 +7,7 @@ import 'react-pdf/dist/Page/AnnotationLayer.css';
import 'react-pdf/dist/Page/TextLayer.css';
if (typeof window !== 'undefined') {
- pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;
+ pdfjs.GlobalWorkerOptions.workerSrc = `/pdf.worker.min.mjs`;
}
export default function PdfViewer({ url }: { url: string }) {
diff --git a/frontend/src/components/SettingsPage.tsx b/frontend/src/components/SettingsPage.tsx
index 18fc23b..5207a26 100644
--- a/frontend/src/components/SettingsPage.tsx
+++ b/frontend/src/components/SettingsPage.tsx
@@ -72,7 +72,14 @@ export default function SettingsPage({ onLogout }: SettingsPageProps) {
setSuccess(false);
try {
- await api.updateSettings(settings);
+ const existing = await api.getSettings();
+ const merged = {
+ ...(existing?.settings || existing || {}),
+ theme: settings.theme,
+ grid_size: settings.grid_size,
+ trash_auto_purge_days: settings.trash_auto_purge_days,
+ };
+ await api.updateSettings(merged);
setSuccess(true);
setTimeout(() => setSuccess(false), 3000);
applyTheme(settings.theme);
diff --git a/frontend/src/components/ShareModal.tsx b/frontend/src/components/ShareModal.tsx
index 696d086..b4af917 100644
--- a/frontend/src/components/ShareModal.tsx
+++ b/frontend/src/components/ShareModal.tsx
@@ -85,7 +85,8 @@ export default function ShareModal({ filePath, onClose }: ShareModalProps) {
Password (Optional)
setPassword(e.target.value)}
placeholder="Leave blank for no password"