feat: complete remediation phase 3
Some checks failed
Automated Container Build / build-and-push (push) Failing after 15s
Some checks failed
Automated Container Build / build-and-push (push) Failing after 15s
This commit is contained in:
parent
0221e277a6
commit
6772ba8c43
10 changed files with 63 additions and 20 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
@ -49,6 +50,11 @@ func Load() *Config {
|
||||||
cfg.VersionsDir = cfg.StorageDir + "/.versions"
|
cfg.VersionsDir = cfg.StorageDir + "/.versions"
|
||||||
cfg.BackupDir = cfg.StorageDir + "/.backups"
|
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
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -554,7 +554,7 @@ func (h *FSHandler) Delete(c *fiber.Ctx) error {
|
||||||
isPermanent := c.Query("permanent") == "true"
|
isPermanent := c.Query("permanent") == "true"
|
||||||
if isPermanent {
|
if isPermanent {
|
||||||
// Ensure we don't delete the root
|
// 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"})
|
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 {
|
if err := os.Rename(resolvedPath, trashPath); err != nil {
|
||||||
fmt.Printf("Rename error %s -> %s: %v. Attempting copy & delete fallback...\n", resolvedPath, trashPath, err)
|
fmt.Printf("Rename error %s -> %s: %v. Attempting copy & delete fallback...\n", resolvedPath, trashPath, err)
|
||||||
|
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 {
|
if err := copyFile(resolvedPath, trashPath); err != nil {
|
||||||
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move to trash"})
|
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to move file to trash"})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
safeRemovePath(h.Config.StorageDir, resolvedPath)
|
safeRemovePath(h.Config.StorageDir, resolvedPath)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,13 +119,11 @@ func (h *ShareHandler) ListShares(c *fiber.Ctx) error {
|
||||||
|
|
||||||
// Skip expired shares
|
// Skip expired shares
|
||||||
if expiresAt != nil {
|
if expiresAt != nil {
|
||||||
t, err := time.Parse("2006-01-02 15:04:05-07:00", *expiresAt)
|
t, err := time.Parse(time.RFC3339, *expiresAt)
|
||||||
if err == nil && time.Now().After(t) {
|
if err != nil {
|
||||||
// To handle different time formats, we could parse it more robustly
|
t, err = time.Parse("2006-01-02 15:04:05", *expiresAt)
|
||||||
// but for now, we'll let frontend also check or we just check here.
|
|
||||||
}
|
}
|
||||||
t2, err2 := time.Parse(time.RFC3339, *expiresAt)
|
if err == nil && time.Now().After(t) {
|
||||||
if err2 == nil && time.Now().After(t2) {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,16 @@ import (
|
||||||
"golang.org/x/net/webdav"
|
"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 {
|
type WebDAVHandler struct {
|
||||||
Handler *webdav.Handler
|
Handler *webdav.Handler
|
||||||
DB *database.DB
|
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
|
// Adapt Fiber's FastHTTP to standard net/http for the WebDAV handler
|
||||||
fasthttpadaptor.NewFastHTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
fasthttpadaptor.NewFastHTTPHandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
relPath := strings.TrimPrefix(r.URL.Path, "/webdav")
|
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.)
|
capture := &statusCapture{ResponseWriter: w, statusCode: 200}
|
||||||
if r.Method == "PUT" && r.Response != nil && r.Response.StatusCode >= 200 && r.Response.StatusCode < 300 {
|
h.Handler.ServeHTTP(capture, r)
|
||||||
|
|
||||||
|
if r.Method == "PUT" && capture.statusCode >= 200 && capture.statusCode < 300 {
|
||||||
h.syncToDB(relPath)
|
h.syncToDB(relPath)
|
||||||
}
|
}
|
||||||
})(c.Context())
|
})(c.Context())
|
||||||
|
|
|
||||||
|
|
@ -104,9 +104,14 @@ func main() {
|
||||||
return c.Next()
|
return c.Next()
|
||||||
})
|
})
|
||||||
app.Use(helmet.New())
|
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")
|
allowedOrigins := os.Getenv("ALLOWED_ORIGINS")
|
||||||
if allowedOrigins == "" {
|
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{
|
app.Use(cors.New(cors.Config{
|
||||||
|
|
|
||||||
|
|
@ -23,9 +23,18 @@ func AuthMiddleware(jwtSecret string) fiber.Handler {
|
||||||
|
|
||||||
isQueryToken := false
|
isQueryToken := false
|
||||||
if tokenString == "" {
|
if tokenString == "" {
|
||||||
|
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")
|
tokenString = c.Query("token")
|
||||||
|
if tokenString != "" {
|
||||||
isQueryToken = true
|
isQueryToken = true
|
||||||
}
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tokenString == "" {
|
if tokenString == "" {
|
||||||
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"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": {
|
"dependencies": {
|
||||||
"@onlyoffice/document-editor-react": "^2.2.0",
|
"@onlyoffice/document-editor-react": "^2.2.0",
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import 'react-pdf/dist/Page/AnnotationLayer.css';
|
||||||
import 'react-pdf/dist/Page/TextLayer.css';
|
import 'react-pdf/dist/Page/TextLayer.css';
|
||||||
|
|
||||||
if (typeof window !== 'undefined') {
|
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 }) {
|
export default function PdfViewer({ url }: { url: string }) {
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,14 @@ export default function SettingsPage({ onLogout }: SettingsPageProps) {
|
||||||
setSuccess(false);
|
setSuccess(false);
|
||||||
|
|
||||||
try {
|
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);
|
setSuccess(true);
|
||||||
setTimeout(() => setSuccess(false), 3000);
|
setTimeout(() => setSuccess(false), 3000);
|
||||||
applyTheme(settings.theme);
|
applyTheme(settings.theme);
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,8 @@ export default function ShareModal({ filePath, onClose }: ShareModalProps) {
|
||||||
<Shield className="w-4 h-4" /> Password (Optional)
|
<Shield className="w-4 h-4" /> Password (Optional)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="password"
|
||||||
|
autoComplete="new-password"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Leave blank for no password"
|
placeholder="Leave blank for no password"
|
||||||
|
|
|
||||||
Reference in a new issue