All checks were successful
Automated Container Build / build-and-push (push) Successful in 1m1s
159 lines
4.1 KiB
Go
159 lines
4.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"git.elijahkuntz.com/Elijah/drive/config"
|
|
"git.elijahkuntz.com/Elijah/drive/middleware"
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/golang-jwt/jwt/v5"
|
|
)
|
|
|
|
type OnlyOfficeHandler struct {
|
|
Config *config.Config
|
|
}
|
|
|
|
type CallbackRequest struct {
|
|
Status int `json:"status"`
|
|
Url string `json:"url"`
|
|
Key string `json:"key"`
|
|
}
|
|
|
|
func (h *OnlyOfficeHandler) Callback(c *fiber.Ctx) error {
|
|
path := c.Query("path")
|
|
token := c.Query("token")
|
|
|
|
if path == "" {
|
|
return c.Status(400).SendString("path required")
|
|
}
|
|
|
|
// Validate the token to ensure the callback is authorized
|
|
if err := middleware.ValidateDownloadToken(token, h.Config.JWTSecret, ""); err != nil {
|
|
return c.Status(401).SendString("unauthorized")
|
|
}
|
|
|
|
var req CallbackRequest
|
|
if err := c.BodyParser(&req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "invalid json"})
|
|
}
|
|
|
|
// Status 2: Document is ready for saving
|
|
// Status 6: Document is being edited, force save was triggered
|
|
if req.Status == 2 || req.Status == 6 {
|
|
if req.Url == "" {
|
|
return c.Status(400).JSON(fiber.Map{"error": "url required"})
|
|
}
|
|
|
|
// If the server cannot resolve the public URL due to hairpin NAT,
|
|
// we can rewrite it to use the internal Docker IP
|
|
downloadUrl := req.Url
|
|
|
|
// Download modified file from Document Server
|
|
resp, err := http.Get(downloadUrl)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": "failed to download file"})
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
targetPath := filepath.Join(h.Config.StorageDir, path)
|
|
|
|
// Save file
|
|
out, err := os.Create(targetPath)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": "failed to open file for writing"})
|
|
}
|
|
defer out.Close()
|
|
|
|
if _, err := io.Copy(out, resp.Body); err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": "failed to write file"})
|
|
}
|
|
}
|
|
|
|
// Send successful response according to OnlyOffice API
|
|
return c.JSON(fiber.Map{"error": 0})
|
|
}
|
|
|
|
// CreateBlank creates an empty document for Docs or Slides mode
|
|
func (h *OnlyOfficeHandler) CreateBlank(c *fiber.Ctx) error {
|
|
req := new(struct {
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
})
|
|
|
|
if err := c.BodyParser(req); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "invalid request"})
|
|
}
|
|
|
|
if req.Name == "" {
|
|
req.Name = "Untitled Document"
|
|
if req.Type == "slide" {
|
|
req.Name = "Untitled Presentation"
|
|
}
|
|
}
|
|
|
|
ext := ".docx"
|
|
if req.Type == "slide" {
|
|
ext = ".pptx"
|
|
}
|
|
|
|
// Add extension if missing
|
|
if filepath.Ext(req.Name) != ext {
|
|
req.Name += ext
|
|
}
|
|
|
|
// Make sure the file name doesn't collide
|
|
basePath := filepath.Join(h.Config.StorageDir, req.Name)
|
|
finalPath := basePath
|
|
counter := 1
|
|
for {
|
|
if _, err := os.Stat(finalPath); os.IsNotExist(err) {
|
|
break
|
|
}
|
|
finalPath = filepath.Join(h.Config.StorageDir, fmt.Sprintf("%s (%d)%s", req.Name[:len(req.Name)-len(ext)], counter, ext))
|
|
counter++
|
|
}
|
|
|
|
// Create an empty file. OnlyOffice will initialize it when opened.
|
|
f, err := os.Create(finalPath)
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": "failed to create file"})
|
|
}
|
|
f.Close()
|
|
|
|
// Return the relative path
|
|
relativePath, _ := filepath.Rel(h.Config.StorageDir, finalPath)
|
|
|
|
return c.JSON(fiber.Map{
|
|
"path": filepath.ToSlash(relativePath),
|
|
"name": filepath.Base(finalPath),
|
|
})
|
|
}
|
|
|
|
// SignConfig takes the OnlyOffice configuration payload and signs it with the JWT secret
|
|
func (h *OnlyOfficeHandler) SignConfig(c *fiber.Ctx) error {
|
|
if h.Config.OnlyOfficeJWT == "" {
|
|
return c.Status(500).JSON(fiber.Map{"error": "JWT secret is not configured on the server"})
|
|
}
|
|
|
|
var payload map[string]interface{}
|
|
if err := c.BodyParser(&payload); err != nil {
|
|
return c.Status(400).JSON(fiber.Map{"error": "invalid json payload"})
|
|
}
|
|
|
|
// Create a new JWT token using the HS256 algorithm
|
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims(payload))
|
|
|
|
// Sign the token with our secret
|
|
signedToken, err := token.SignedString([]byte(h.Config.OnlyOfficeJWT))
|
|
if err != nil {
|
|
return c.Status(500).JSON(fiber.Map{"error": "failed to sign token"})
|
|
}
|
|
|
|
return c.JSON(fiber.Map{
|
|
"token": signedToken,
|
|
})
|
|
}
|