onlyoffice

package
v0.46.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 26, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package onlyoffice integrates a self-hosted OnlyOffice Document Server with filex storage backends.

Three pieces cooperate:

  • Config: builds a JWT-signed editor descriptor for the embed iframe. Includes a signed fetch URL the document server uses to pull source bytes from filex.
  • Fetch: serves source bytes back to the document server (HMAC-signed URL). Public — no session required, but the URL is unguessable and short-lived.
  • Callback: receives save events from the document server, validates the JWT it sends, downloads the saved revision, and writes back to storage.

All signing uses HS256 (HMAC-SHA256) with the JWTSecret loaded from config (FILEX_ONLYOFFICE_JWT). That same secret is what the document server has in its `local.json` -> `services.CoAuthoring.token.enable` stanza.

Index

Constants

View Source
const (
	StatusBeingEdited    = 1
	StatusReadyForSaving = 2
	StatusSavingError    = 3
	StatusClosedNoChange = 4
	StatusForceSave      = 6
	StatusForceSaveError = 7
)

Status codes per OnlyOffice callback spec.

View Source
const ProbePath = "/api/files/onlyoffice/probe"

ProbePath is the endpoint the document server is asked to download.

Variables

This section is empty.

Functions

func DocumentType

func DocumentType(ext string) string

DocumentType returns "word", "cell", "slide", or "" for an extension.

Matches the official OnlyOffice mapping. If the extension is unknown we return "" so callers can 415.

Types

type CallbackPayload

type CallbackPayload struct {
	Key    string `json:"key"`
	Status int    `json:"status"`
	URL    string `json:"url"`
	Token  string `json:"token,omitempty"`
}

CallbackPayload is the body the document server posts on save / status transitions. See https://api.onlyoffice.com/editors/callback for the full schema; we use the fields filex actually needs.

type EditorConfig

type EditorConfig struct {
	DocumentServerURL string         `json:"documentServerUrl"`
	Config            map[string]any `json:"config"`
}

EditorConfig is the descriptor we send to the embed iframe.

type ReverseResult

type ReverseResult struct {
	// Checked is false when the question could not be put at all — nothing
	// configured, or the document server did not answer. A false here must
	// never be rendered as a failure of the route.
	Checked bool `json:"checked"`
	// OK is true only when the probe request actually ARRIVED at filex.
	OK bool `json:"ok"`
	// URL is the address the document server was asked to fetch, so an
	// operator reading a red result can try it themselves.
	URL string `json:"url,omitempty"`
	// Code is the document server's own error number when it returned one.
	// -4 is "error while downloading the document file" — the shape of a
	// broken reverse path. 0 means it did not report an error.
	Code int `json:"code,omitempty"`
	// Detail is the sentence to show the operator.
	Detail string `json:"detail,omitempty"`
}

ReverseResult is one answer to "can the document server reach filex?".

type Service

type Service struct {
	Store           db.Store
	StorageResolver func(int64) (storage.Driver, error)

	// DocumentServerURL / JWTSecret are the BOOT-TIME values (env/YAML). They
	// are the fallback only: when Live is wired the running configuration is
	// read from the `external_services` row on every call, so an operator who
	// configures OnlyOffice in the admin UI does not have to restart filex.
	// See internal/external and issue #17.
	DocumentServerURL string // e.g. https://docs.example.com
	JWTSecret         string // shared secret with the doc server
	// Live, when non-nil, returns the current url + secret. Nil means "use the
	// fields", which is what the unit tests and any no-DB caller want.
	Live      func(ctx context.Context) (url, secret string)
	PublicURL string // filex public base URL — used to build callbacks
	// LiveCallbackURL, when non-nil, returns the address the DOCUMENT SERVER
	// should use to reach filex. Empty result means "the public URL".
	//
	// ⚠ These are two different addresses for two different readers. PublicURL
	// is what a person's browser uses and what every share link is built from;
	// this one is only ever read by the document server's container. They are
	// usually the same string, and on podman or a private network they cannot
	// be — which is why a document opened and its save never came back with
	// nothing in filex's log to show for it (issue #17).
	LiveCallbackURL func(ctx context.Context) string
	FetchTTL        time.Duration

	// Sync is the shared post-write gate every other write surface in filex
	// goes through: it upserts the node row, re-indexes the document,
	// dispatches a thumbnail, tells open explorers, emits the canonical
	// `file.updated` webhook event and schedules the antivirus scan.
	//
	// ⚠ Optional, and its absence degrades gracefully rather than silently:
	// an unwired Service falls back to a Syncer with no search index (see
	// syncer), so a save still announces itself and is still scanned — only
	// the re-index is skipped. Wire it with AttachSync at boot.
	Sync *protocolsync.Syncer
	// contains filtered or unexported fields
}

Service builds editor configs and validates document-server callbacks.

func New

func New(store db.Store, resolver func(int64) (storage.Driver, error), docURL, jwtSecret, publicURL string, fetchTTL time.Duration) *Service

New constructs a Service. fetchTTL defaults to 1 hour when zero.

func (*Service) AttachSync

func (s *Service) AttachSync(sy *protocolsync.Syncer)

AttachSync wires the post-write gate. ⚠ Without it an edited document keeps its PRE-EDIT text in content search, because nothing else ever revisits a file whose path did not change.

func (*Service) BuildConfigForNode

func (s *Service) BuildConfigForNode(ctx context.Context, node *model.Node, user *model.User, lang, mode string) (*EditorConfig, error)

BuildConfigForNode resolves the node, presigns the fetch URL, and signs the JSON descriptor with HS256. `mode` selects "edit" or "view"; any value other than "edit" is treated as read-only and toggles the permissions block so OnlyOffice renders the document with toolbars disabled.

func (*Service) Enabled

func (s *Service) Enabled() bool

Enabled reports whether the service has the minimum config to run, as of this instant — not as of the last restart.

func (*Service) EnabledCtx

func (s *Service) EnabledCtx(ctx context.Context) bool

EnabledCtx is Enabled with a caller-supplied context, so the DB read that backs it inherits the request's deadline and cancellation.

func (*Service) HandleCallback

func (s *Service) HandleCallback(r *http.Request, nodeID int64) (map[string]any, error)

HandleCallback validates the JWT, downloads the saved version, and writes it back to storage.

Returns the OnlyOffice expected JSON envelope.

func (*Service) ServeProbe

func (s *Service) ServeProbe(token string) (body string, ok bool)

ServeProbe answers the document server's download of a probe URL. It reports whether the token was one filex issued and is still live; an unknown or expired token is served as not-found, so the endpoint says nothing to a stranger who guesses at it.

func (*Service) SignedFetchURL

func (s *Service) SignedFetchURL(nodeID, exp int64) string

SignedFetchURL returns an HMAC-signed URL the document server can use to pull document bytes from filex without authenticating.

The URL embeds node id + expiry + sig=base64-url(HMAC-SHA256(secret, "n=<id>&exp=<exp>")).

func (*Service) VerifyFetchSignature

func (s *Service) VerifyFetchSignature(nodeID, exp int64, sig string) error

VerifyFetchSignature validates a query against the fetch HMAC.

func (*Service) VerifyFetchSignatureCtx

func (s *Service) VerifyFetchSignatureCtx(ctx context.Context, nodeID, exp int64, sig string) error

VerifyFetchSignatureCtx is VerifyFetchSignature with the request context, so the secret is the one in force now.

func (*Service) VerifyReversePath

func (s *Service) VerifyReversePath(ctx context.Context) ReverseResult

VerifyReversePath asks the document server to download a one-shot URL from filex and reports whether that request arrived.

⚠ The verdict is the ARRIVAL. The conversion result is a hint for the message, never the answer: a document server that downloaded our probe and then failed to convert it has still proved the only thing being asked.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL