When a user below editor duplicates a sub-document, the backend creates
the copy as a new root document owned by the user, since only editors
and above can write in the tree. The frontend still inserted the copy
in the displayed tree as a sibling of the original, showing a document
that does not belong there.
Only add the duplicate to the tree when the user can edit the document,
and make the toast "Undo" action leave the tree alone in the other case.
The sub-docs confirmation modal was skipped when duplicating a doc
right after creating, deleting or moving one of its sub-docs: the
duplicate action read a stale `numchild`.
Two copies of the parent were out of date. The doc page keeps it
cached for 30 seconds, and creating a sub-doc never invalidated it.
The doc tree also keeps its own copy, which react-query invalidations
do not reach, so it is now updated when a sub-doc is created, deleted
or moved by drag and drop. The move updates the nodes in place before
the move, as `updateNode` would bring the moved doc back under its old
parent.
The sub-docs confirmation modal was not displayed
in some cases when duplicating a document with sub-docs.
Closes#2797
Signed-off-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
uv before 0.13.4 bundles the emSign root certificate whose eMudhra
intermediate CA keys are compromised (GHSA-hq85-5cpj-mr62), the same
compromise that led to the certifi upgrade.
Its version was repeated in two stages of the Dockerfile. A named "uv"
stage now holds it and both stages use it. The uv_build bound of the
build system is moved to 0.13 so that uv keeps building the package
with its built-in backend.
Pillow 13.0.0 fixes many security advisories in its image decoders and
encoders (JPEG, TIFF, PDF, BMP, GIF...), two of them critical. There is
no 12.x release carrying these fixes. Pillow is pulled by
easy-thumbnails; none of the APIs removed in 13.0.0 are used by it or by
our code.
Hide heading levels 4 to 6 from the slash menu and formatting toolbar.
Keep the schema unchanged so existing headings continue to load
and render.
Signed-off-by: Manixhor <manigururam@hotmail.com>
media-auth is the nginx auth_request subrequest, called once per
attachment displayed in a document. It shared the 80/minute "document"
bucket with every other document endpoint, so a document with many
images, or anonymous readers behind a shared NAT (keyed by IP), got 429
answers and broken images, reported to Sentry as "Rate limit exceeded
for scope document".
DRF's ScopedRateThrottle reads the scope from the view's throttle_scope
(the class attribute of DocumentThrottle is overwritten), so the action
now sets throttle_scope="media_auth", with its own rate configurable by
API_MEDIA_AUTH_THROTTLE_RATE. The default, 200/minute, no longer shares
its budget with the other document endpoints and leaves room for a
document holding many images to be opened several times a minute. The
check is a read-only authorization (two indexed queries and a HEAD on
the object storage), whose real protection is the permission check.
Signed-off-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
BlockNote 0.54 draws the nesting line with a more specific selector, so
the existing override no longer matches and Word users read it as track
changes.
Fixes#2731
Signed-off-by: Ovgodd <c.gromoff@gmail.com>
When searching for a destination in the move modal, sub-documents
were listed by their title only. Several docs can share the same
title in different trees, so it was hard to pick the right one.
Pass the parent title as a breadcrumb to DocsGridItemTitle, which
forwards it to SimpleDocItem, so each search result shows the doc
it belongs to.
Fixes#2814. `migrate_documents` stops with an `IntegrityError` when the
wall clock steps backwards during a migration. yhub reports `durationMs`
as `Date.now() - start`, which comes out negative, and
`DocumentMigration.duration_ms` is a `PositiveIntegerField`.
Signed-off-by: Dor Zairi <dorzairidev@gmail.com>
The title of a document is limited to 255 characters, but marking the
top duplicate as a copy appended " (copy)" to its title without checking
this limit, so duplicating a document with a long title could exceed it.
The original title is now truncated, with an ellipsis, so that the title
of the copy fits within the limit and keeps its copy marker.
Signed-off-by: DevKaurav-14 <devkaurav2025@gmail.com>
Duplicating a document prefixed "Copy of" to the title of every duplicated
document, descendants included. The prefix pushed a leading emoji, used as
the document icon, out of first position, and cluttered the whole subtree.
Only the top duplicate is now marked, with " (copy)" at the end of its
title; descendants keep their title untouched.
Signed-off-by: DevKaurav-14 <devkaurav2025@gmail.com>
Fixes#1942
### Problem
Action-level DRF permission declarations replaced the resource-server
permission list, allowing configured resource-server routes to bypass
the `EXTERNAL_API` action allowlist.
### Change
- Enforce `ResourceServerClientPermission` in the shared resource-server
mixin.
- Require explicit configuration for `favorite_list` and `duplicate`.
- Correct the resource-server action documentation.
### Validation
- Python compilation and whitespace validation passed.
- Added authorization regression tests for denied and explicitly allowed
favorites and duplicate actions.
- The Docker-based backend test runner is unavailable on this machine;
CI will run the focused tests.
Signed-off-by: Manixhor <manigururam@hotmail.com>
A long unbroken string raised the min-content width of its block,
which spread through the flex containers and widened the whole page,
so every line in the doc ran off the page, spaced ones included.
Signed-off-by: Parman Mohammadalizadeh <prmma23@gmail.com>
The comment permission/abilities were not checking if the current user
still have access to the document, it was only checking if it is the
creator to allow it to perform update and destroy actions.
We now check the document's comment abilities before checking if it can
update or destroy a comment/thread
The find-and-replace highlight uses mix-blend-mode: darken
so it blends nicely over coloured inline backgrounds on
regular text. Inside code blocks the background is
near-black and darken collapses the yellow highlight
to black, hiding the matched text.
Use mix-blend-mode: hard-light on the highlights
inside code blocks so they stay visible on the dark
background, for both the regular and the active
(current) result.
Fixes#2681
Signed-off-by: harsh472000 <harshmeghani777@gmail.com>
The left panel home button was a plain button calling
router.push, so it could not be opened in a new
tab with Ctrl/Cmd+click or the context menu.
Render it with ButtonLink so it is a real link to /,
keeping client-side navigation on a normal click.
Signed-off-by: Jul11us <233702296+Jul11us@users.noreply.github.com>
Bind the thread to the document named in the URL path: a comment must
never be stored in a thread that belongs to another document, even when
the caller is allowed to comment on that other document.
Drag-and-drop to move a document was disabled below a viewport
width breakpoint.
Browser zoom shrinks window.innerWidth like a narrower window
does, so zooming in on a desktop with a mouse silently disabled
moving documents, in the docs grid and in the doc tree.
A fix was made on the doc grid, but not in the tree,
we updated useResponsiveStore to tell us if the window
has coarse pointer or not (hasCoarsePointer).
Closes#2599
Last version of @y/hub contains a fix we need, it fixes the websocket
reconnection loop for anonymous user on a read only document sending a
keepalive every 20 seconds. In both version 0.10.0 and 0.10.1 breaking
changes are introduced, one is impacting us by removing the
computePool.patchYdoc function used by the create-ydoc endpoint declated
in our yhub server. To fix it, we are doing what the version 0.9.0 was
doing and we keep the same behavior.
A new environment variable YHUB_AUTH_TIMEOUT_MS is added to configure
the server.authTimeout parameters in the createYhub options
The loader used Card, adding an unnamed region per upload, and neither
the analysis start nor the antivirus warning was announced. Use Box
again, announce both through announce(), hide the decorative warning
icon and keep upload error toasts 10s so the cause can be read.
We updated the UI to provide a clearer indication
when an unexpected upload error occurs.
Before the error was displayed on the top
of the editor, making it less noticeable to the user,
now we display the error directly within the
upload block, making it more visible to the user
and more user-friendly.
BlockNote XL packages moved from AGPL-3.0 to GPL-3.0 (or a
proprietary license) between v0.30 and v0.40.
We renamed the occurences of AGPL to GPL in the codebase.
Signed-off-by: Roee Sivan <roee.sivan@post.runi.ac.il>
DocSpec now returns 415 when the file type is rejected, 422 when it
can't process the file content, and 500 when it crashes, instead of
one generic failure. Propagate those statuses through the API and show
a matching toast message per case on the frontend.
Closes#2552
Added:
- ✨(backend) add a setting for the http fallback polling interval
- ✨(backend) add a setting to turn off awareness on the http fallback
- ✨(backend) add a setting to send json on the http fallback
- ⚡️(yhub) cache authz verdicts to cut backend calls on connections
Changed:
- ⚡️(yhub) skip the `users/me` call for callers without a session
cookie
- 🔧(helm) allow to set the period and failure threshold of probes
- ♿️(frontend) fix the accessibility of the document tree
Fixed:
- 🐛(frontend) stop the http fallback when the collaboration server
refuses it
- 🐛(backend) bypass throttling on access/me action from yhub
- 🐛(frontend) switch language when the current one is untranslated
- 🐛(frontend) keep the loading skeleton until an empty doc has synced
Removed:
- 🔥(backend) remove `ForceSessionMiddleware`, an orphan of the removed
non-websocket save safeguard
Signed-off-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
## Purpose
A connection to the collaboration server — websocket or http fallback —
can chain up to three calls to the backend: users/me, documents/{id} and
accesses/me. On the fallback poll path this repeats every few seconds per
client, and the calls bypass the backend throttling (X-Y-Provider-Key),
so a busy instance hammers the backend unthrottled.
Add an opt-in authz cache in yhub, three layers with different
invalidation physics:
- identity: users/me -> userid, in-process per replica, keyed on the
SHA-256 of the Cookie header. Nothing can invalidate it and nothing
needs to: a dead session fails closed at the abilities fetch. Kills
the users/me call on every poll and REST request.
- abilities: the documents/{id} verdict, in the shared redis per
(document, cookie hash). The one payload a stale entry would
over-grant, so reset-connections retires every verdict of the
document — a single generation write — BEFORE triggering the recheck;
the recheck then re-runs authorize against post-invalidation state
and the revocation stays immediate. The TTL is only the backstop for
a lost kick. Anonymous callers share one entry per document: what
they may do is the link configuration, and nothing about them
distinguishes one from another.
- access date: the accesses/me created_at bounding the history a user
may read. Append-mostly by design, retired by the same per-document
kick as the abilities — a stale earlier date would widen the readable
history, hence the longest TTL of the three.
All three TTLs default to 0: caching off, byte-identical behavior.
Backend refusals (401/403/404) are cached like any verdict, so a client
that lost access costs one call per TTL instead of one per poll. An
unanswered backend is never cached — an error is not a permission
decision. Keys carry only sha256 hashes, never session ids.
Warm-state connections and polls make zero backend calls. Watch
yhub_authz_cache_lookups_total{layer,result} against
yhub_backend_request_duration_seconds{route} before raising the TTLs.
The dev-backend chart 0.0.10 started the media storage with a hardcoded
`minio server` shell command and pulled quay.io/minio/mc to create the
bucket. Since the switch to pgsty/silo, the first one failed with
"minio: command not found" and the second one with a 401 from quay.io, so
the feature deployments (ArgoCD) did not come up.
Chart 0.0.15 defaults to pgsty/silo and pgsty/mc, and starts the container
with `args` instead of a shell command, so the name of the executable no
longer matters.
Some firewalls refuse the application/x-lib0any content type the http
fallback gets from the collaboration server, and the octet-stream it
sends, so the browsers behind them cannot sync when their websocket is
blocked too.
Add COLLABORATION_HTTP_FALLBACK_JSON_ENABLED (default False), served by
the config endpoint. On, the fallback sends and receives application/json
with the binary parts in base64, which the collaboration server already
supports. The cost is a document about a third larger on each transfer.
After removing the ForceSessionMiddleware we can detect without calling
the /users/me endpoint if a user is anonymous or not. We now check the
presence of the cookie session to avoid the http call and directly
return an anonymous user.
This middleware was introduced in 651f2d1d to create a session for every
user — connected to the collaboration websocket or not — so the backend
could arbitrate who may save a document (websocket payload flag and save
lock). The whole safeguard was removed in 68f5f5cdf when the content and
its permissions moved to yhub, leaving the middleware orphaned: nothing
reads an anonymous session anymore (the OIDC login flow saves its own
session, CSRF is cookie-based, throttling is not session-keyed).
It only kept a cost: a Redis write plus a Set-Cookie on every anonymous
response, which had already required two bugfix commits to exempt the
probes. A regression test pins the probe invariant going forward.
Where websockets are blocked, a tab whose access was revoked or whose
session expired took a 403 on every PATCH, forever, since only the
websocket's 4401 close made the tab fetch the document again. Treat a
401, 403 or 404 on the fallback the same way: stop both transports and
fetch the document again.
An editor on the http fallback publishes its presence every 10 seconds,
a request on its own, and the library then never polls less often than
every 15 seconds. On a deployment where many browsers cannot open the
websocket, this is most of the traffic and each request is authorized
against the backend.
Add COLLABORATION_HTTP_FALLBACK_AWARENESS_ENABLED (default True, so
nothing changes unless it is set), served by the config endpoint. Off,
an editor sends a request only when it has changes and
COLLABORATION_HTTP_FALLBACK_SYNC_INTERVAL applies in full. The cost is
no remote cursors on the fallback.
Signed-off-by: Anthony LC <anthony.le-courric@mail.numerique.gouv.fr>
Each poll of a browser on the http fallback is authorized against the
backend, and the 10 second interval was hardcoded. Add
COLLABORATION_HTTP_FALLBACK_SYNC_INTERVAL (milliseconds, default 10000,
so nothing changes unless it is set), served by the config endpoint, so
operators can lower that load without a new release.
A doc never opened on this device has an empty local copy, and the
socket opening is not the content arriving either. Rendering on either
showed the empty-doc placeholder for a moment, which reads as a doc whose
content was deleted.
Signed-off-by: Dor Zairi <dorzairidev@gmail.com>