fix: OnlyOffice edit session token expiration and add session expired modal
Some checks failed
Automated Container Build / build-and-push (push) Failing after 15s

This commit is contained in:
Elijah 2026-05-31 15:20:54 -07:00
parent 1f83d57942
commit 218fb40743
7 changed files with 130 additions and 16 deletions

View file

@ -210,6 +210,27 @@ func (h *AuthHandler) GetDownloadToken(c *fiber.Ctx) error {
})
}
// GetEditToken issues a 24-hour token for file editing via OnlyOffice
// GET /api/auth/edit-token
func (h *AuthHandler) GetEditToken(c *fiber.Ctx) error {
userID := c.Locals("userID")
username := c.Locals("username")
token, err := middleware.GenerateEditToken(
int(userID.(float64)),
username.(string),
h.Config.JWTSecret,
"",
)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "failed to generate edit token"})
}
return c.JSON(fiber.Map{
"token": token,
})
}
// Enable2FA generates a TOTP secret and returns the provisioning URI.
// POST /api/auth/2fa/enable
func (h *AuthHandler) Enable2FA(c *fiber.Ctx) error {

View file

@ -38,8 +38,13 @@ func (h *OnlyOfficeHandler) Callback(c *fiber.Ctx) error {
return c.Status(400).SendString("path required")
}
// Validate the token to ensure the callback is authorized
if err := middleware.ValidateDownloadToken(token, h.Config.JWTSecret, "", path); err != nil {
// Validate the token to ensure the callback is authorized.
// First check if it's a long-lived edit token, fallback to short-lived download token.
err := middleware.ValidateEditToken(token, h.Config.JWTSecret, "", path)
if err != nil {
err = middleware.ValidateDownloadToken(token, h.Config.JWTSecret, "", path)
}
if err != nil {
return c.Status(401).SendString("unauthorized")
}
@ -151,8 +156,13 @@ func (h *OnlyOfficeHandler) DownloadForEditor(c *fiber.Ctx) error {
return c.Status(400).JSON(fiber.Map{"error": "path required"})
}
// Validate the download token
if err := middleware.ValidateDownloadToken(token, h.Config.JWTSecret, "", path); err != nil {
// Validate the token.
// Allow both short-lived download tokens and long-lived edit tokens.
err := middleware.ValidateEditToken(token, h.Config.JWTSecret, "", path)
if err != nil {
err = middleware.ValidateDownloadToken(token, h.Config.JWTSecret, "", path)
}
if err != nil {
return c.Status(401).JSON(fiber.Map{"error": "unauthorized"})
}

View file

@ -160,6 +160,8 @@ func main() {
protected.Put("/auth/password", authHandler.ChangePassword)
// 2FA management
protected.Get("/auth/download-token", authHandler.GetDownloadToken)
protected.Get("/auth/edit-token", authHandler.GetEditToken)
protected.Post("/auth/2fa/enable", authHandler.Enable2FA)
protected.Post("/auth/2fa/confirm", authHandler.Confirm2FA)
protected.Post("/auth/2fa/disable", authHandler.Disable2FA)

View file

@ -137,6 +137,26 @@ func GenerateDownloadToken(userID int, username, secret, filePath string) (strin
return token.SignedString([]byte(secret))
}
}
// GenerateEditToken creates a long-lived edit token for OnlyOffice sessions.
func GenerateEditToken(userID int, username, secret, filePath string) (string, error) {
claims := jwt.MapClaims{
"sub": userID,
"username": username,
"exp": time.Now().Add(24 * time.Hour).Unix(),
"iat": time.Now().Unix(),
"type": "edit",
}
if filePath != "" {
claims["path"] = filePath
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
// ValidateDownloadToken validates a download token
func ValidateDownloadToken(tokenString, secret, expectedUsername, expectedPath string) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
@ -165,3 +185,40 @@ func ValidateDownloadToken(tokenString, secret, expectedUsername, expectedPath s
return nil
}
// ValidateEditToken validates an edit token
func ValidateEditToken(tokenString, secret, expectedUsername, expectedPath string) error {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil || !token.Valid {
return err
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return jwt.ErrSignatureInvalid
}
tokenType, _ := claims["type"].(string)
if tokenType != "edit" {
return jwt.ErrSignatureInvalid
}
if expectedUsername != "" {
if username, ok := claims["username"].(string); !ok || username != expectedUsername {
return jwt.ErrSignatureInvalid
}
}
if expectedPath != "" {
if path, ok := claims["path"].(string); ok && path != "" {
if path != expectedPath {
return jwt.ErrSignatureInvalid
}
}
}
return nil
}