Documentation
¶
Overview ¶
Package tenanturl answers one question for the whole server: which origin should an absolute URL that filex hands out be built on?
⚠ Every absolute URL filex mints for a person or a client — share links, file-request links, the wss:// realtime endpoint, upload-ticket URLs and every link inside an e-mail — MUST be built from a Resolver. Reaching for config.PublicURL directly is correct only in a single-tenant install; in a multi-tenant one it hands the OPERATOR's hostname to a customer. That is a dead link at best, and at worst (handlers.Grants.Invite, the mail carrying a brand-new account's temporary password) a login page on somebody else's domain.
Two ways in, because not every URL is built while a browser waits:
- FromRequest(r) the browser is right there, so use the host it asked for — once that host is proven to be a tenant's.
- ForStorage / ForProvider no *http.Request in scope (an async op, a queue worker, an MCP/AI call). The tenant then comes from the DATA — the node's storage, or the provider id — never from a guess.
Forged Host headers ¶
A request host is never echoed back unvalidated. In multi-tenant mode the host is resolved with GetProviderByHost, which matches an ENABLED provider row on an exact host equality; the origin is then assembled from that ROW's Host column — a value an operator provisioned — and not from the request string. An unknown, disabled or absent host falls through to PublicURL, which is the rule handlers.Auth.redirectBase has always used. So a request carrying `Host: evil.example` mints the operator's configured URL; evil.example never appears in a link or an e-mail.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func RequestHost ¶
RequestHost extracts the bare lowercase hostname (no port) the client asked for. Behind the reverse proxy filex trusts the proxied Host header — the proxy is the only reachable path in the documented deployments (§13).
Types ¶
type Resolver ¶
type Resolver struct {
// PublicURL is the install's configured base (FILEX_PUBLIC_URL). It is the
// answer in single-tenant mode and the fallback in every other case.
PublicURL string
// MultiTenant mirrors config.MultiTenant. While false the request is never
// consulted at all, so a single-tenant install cannot be talked into
// minting a different origin, and call sites that have no request lose
// nothing.
MultiTenant bool
// Store reads provider rows. nil disables tenant resolution (fallback only).
Store Store
}
Resolver builds the origin ("https://host", no trailing slash) that absolute URLs should be assembled on.
The zero value is usable and behaves as a single-tenant install with an empty PublicURL — i.e. it yields "", so callers that concatenate produce the relative "/s/<token>" they produced before this package existed.
func New ¶
New builds a resolver. publicURL is normalised once here so no caller has to remember to trim it.
func (Resolver) Fallback ¶
Fallback is the configured origin, trimmed. Exported because a caller that already knows it wants the install's own URL (an operator-facing screen) should say so rather than reading PublicURL raw.
func (Resolver) ForProvider ¶
ForProvider returns a tenant's origin without a request — for the e-mails and async work that run after the browser has gone.
func (Resolver) ForStorage ¶
ForStorage returns the origin of the tenant a storage belongs to. A storage linked to no provider (the operator's own, or any storage in a single-tenant install) falls back to PublicURL.
func (Resolver) FromRequest ¶
FromRequest returns the origin for a URL minted while r is being served.
Single-tenant: PublicURL, and r is not read — see the MultiTenant field. Multi-tenant: the request's Host when (and only when) it resolves to an enabled provider row; anything else falls back to PublicURL.
type Store ¶
type Store interface {
GetProviderByHost(ctx context.Context, host string) (*model.Provider, error)
GetProvider(ctx context.Context, id int64) (*model.Provider, error)
GetProviderIDForStorage(ctx context.Context, storageID int64) (int64, bool, error)
}
Store is the slice of db.Store this package needs. Narrow on purpose: it keeps the resolver trivially fakeable and documents that resolving an origin reads provider rows and nothing else.