Commit Graph
315 Commits
Author SHA1 Message Date
kolaente 96496e1dce fix(background): bound decode memory
Project background uploads (and migrated backgrounds) are decoded through
imageutils.Decode, which also covers the blur hash computed from the
decoded image. A busy server answers 503. Drops the unused CreateBlurHash,
which decoded without any bound.
2026-10-20 23:05:30 +02:00
kolaente bcdd4c0cba fix(avatar): bound decode memory
Uploaded avatars are decoded through imageutils.Decode on upload and when
resizing per size, so a busy server answers 503 instead of decoding in
parallel. The old avatar file is removed only after the new one decoded,
since a storage delete is not rolled back with the session.
2026-10-20 23:05:30 +02:00
kolaente da41604b64 fix(auth): reject user JWTs whose session was revoked on the HTTP path 2026-10-20 15:13:02 +02:00
kolaente 5406ad912f fix(webhooks): derive user- vs project-level webhooks from the route, never the body 2026-10-20 15:13:02 +02:00
kolaente 7e77498fa7 fix(webhooks): require project write access to list webhook targets 2026-10-20 15:13:02 +02:00
kolaente a0454051cb fix(auth): refuse email-unconfirmed accounts on every credential path 2026-10-20 15:13:02 +02:00
kolaente 2a134005f5 fix(api-tokens): name non-CRUD permission keys by action across v1 and v2
Colliding non-CRUD routes got a method suffix in registration order, per
version, so one key granted different actions on v1 and v2: a read-only
projects.background scope deleted backgrounds through v2, and bucket
create/update keys swapped meaning (GHSA-j5v7-qcwp-5g32). Keys now derive
from the full v1+v2 route set and carry the action (read/create/update/
delete) once a subkey has more than one. Marking all notifications as
read gets its own key on both versions instead of overwriting update.
2026-10-20 15:13:02 +02:00
kolaente 7b100668fe fix(teams): require membership for team self-removal 2026-10-20 15:13:02 +02:00
kolaente fd3e77445f fix(totp): require the current password to enroll and enable totp 2026-10-20 15:13:02 +02:00
kolaente a89f7dd596 fix(migration): bind oauth migration callbacks to the user who requested the auth url 2026-10-20 15:13:02 +02:00
kolaente 9599ca2b0e fix(totp): require a current passcode to disable totp 2026-10-20 15:13:02 +02:00
kolaente 6c3ba90ec3 fix(kanban): only move tasks into buckets of their own project's view 2026-10-20 15:13:02 +02:00
kolaente 0b788b83d2 fix(user): throttle password re-checks per user on sensitive account actions 2026-10-20 15:13:02 +02:00
kolaente 1852e3b873 fix(migration): reject wekan boards with duplicate ids or too many items 2026-10-20 15:13:02 +02:00
kolaente 2bb52b6b4d fix(teams): ignore client-set external_id on team create and update 2026-10-20 15:13:02 +02:00
kolaente 4069873723 fix(routes): reject form-encoded bodies outside the OAuth token endpoint
A cross-site <form> needs no CORS preflight, and v2 decoded form bodies
API-wide, so a form POST to /login or the OIDC callback minted a session
in the victim's browser (GHSA-frf4-3prf-3vch). Form decoding is now
scoped to /oauth/token, and both API groups answer 415 elsewhere.
2026-10-20 15:13:02 +02:00
kolaente 2dd4ab14be fix(avatar): round requested size up to a fixed allowlist
Providers cache each size without eviction, so arbitrary, zero and
negative sizes minted unbounded cache entries.

Resolves GHSA-3x3m-2j93-w46j
2026-10-20 15:13:02 +02:00
kolaente 20560e58cf fix(kanban): reject bucket actions through another view's url 2026-10-20 15:13:02 +02:00
kolaente 47aa7d39a8 fix(kanban): cap manual buckets at 100 per view
Each bucket costs a task query on every kanban read.

Resolves GHSA-p697-wq4x-grpj
2026-10-20 15:13:02 +02:00
kolaente e1e617fb9b fix(api): treat page=0 as the first page on v1 list endpoints 2026-10-20 15:13:02 +02:00
kolaente 5cb840c90a fix(attachments): dispatch events after uploading attachments 2026-10-20 13:34:51 +02:00
kolaenteandTink a4e5a65b06 fix(attachments): reject v1 preview requests that cannot return a preview
Address review comment: v1 fell back to the original when a preview was asked for. An empty or invalid preview_size now fails with 422 (4039), an attachment without a preview with 422 (4038).
2026-10-19 20:33:06 +02:00
kolaenteandTink f2fbbcf40a feat(attachments)!: always return the original from the v2 download route
Address review comment: previews moved to their own route, so the download route no longer takes preview_size.

BREAKING CHANGE: the v2 attachment download route ignores preview_size. Request previews from GET /tasks/{task}/attachments/{attachment}/preview?size= instead.
2026-10-19 20:33:06 +02:00
kolaenteandTink 0914ea413f feat(attachments): serve v2 previews from their own route
Address review comment: GET /tasks/{task}/attachments/{attachment}/preview?size= never returns the original. A missing or invalid size, or an attachment with no preview, fails with 422.
2026-10-19 20:33:06 +02:00
kolaenteandTink bd2493b2e0 feat(attachments): fail a v2 preview request that cannot produce a preview
Address review comment: v2 answered a preview request with the original when no preview could be made. It now fails with 422 (code 4038) and the client decides whether to fetch the original. v1 keeps the fallback.
2026-10-19 20:33:06 +02:00
kolaenteandTink fcb74ab111 feat(attachments): encode opaque previews as jpeg
A PNG preview of a photo is several times the size of a JPEG. Previews
with any transparency stay PNG, since JPEG has no alpha channel. The
format is decided in encodePreview alone; the Content-Type of a
response follows from the preview's bytes.
2026-10-19 20:33:06 +02:00
kolaenteandTink 796bbec03a refactor(attachments): cut comment noise
Address pr-swarm comment sweep.
2026-10-19 20:33:06 +02:00
kolaenteandTink bc8d8c1001 test(attachments): pin image preview and fallback to the original 2026-10-19 20:33:06 +02:00
kolaente eee047a638 fix(caldav): bound PROPPATCH response size
Each submitted property was echoed back with its full namespace URI, so a
namespace declared once on the root and reused by many short elements
amplified a ~22MB body into terabytes of buffered response.

Cap the PROPPATCH body at 64KB, the property count at 100 and the
namespace length at 256 bytes, and dedupe repeated properties.
2026-10-19 16:05:12 +02:00
kolaente b3055f393c feat(mcp): point filter params, errors and instructions at filter_syntax 2026-10-19 13:25:00 +02:00
kolaente da2d3b01f0 feat(mcp): add filter_syntax tool and resource 2026-10-19 13:25:00 +02:00
kolaente 34202ac822 fix(tasks): reject bulk updates without fields
A bulk update with no fields fell through to the single-task path and
replaced every task wholesale with values, clearing description, dates,
priority and more on all of them. Reject it with a new
ErrBulkTaskFieldsRequired (4036) and document the accepted bulk fields.

Fixes #4109
2026-10-16 15:49:28 +02:00
kolaente 7e991c4cc9 test(mcp): drop session ID handling from stateless MCP test client
go-sdk v1.7.0 stops emitting and reading Mcp-Session-Id on stateless
streamable HTTP servers.
2026-10-16 14:41:21 +02:00
Mohd Quamar TyagiandGitHub 84a071a46d fix(models): return 404 for missing api tokens on delete (#3944) (#4119) 2026-10-15 21:01:39 +00:00
kolaente 9125e1a93a fix: reject hex_color values that are not hex colors
The length-only check let through 7 character values without a leading #
and multi-byte strings, which do not fit the varchar(6) column after
normalizing. Tasks, labels and projects now only accept an optional #
followed by 3 or 6 hex digits.
2026-10-13 23:32:15 +02:00
kolaente 8729efa4fc feat(api): return per-view positions from task create
Create, bulk create and duplicate responses now carry the task's stored
position in every view, so clients can place the new card without
refetching the board.
2026-10-13 11:54:08 +02:00
kolaente 46ae983554 fix(websocket): check the session and close sockets on revocation
The socket only checked the token's signature and expiry. A token of a
deleted session or of a disabled user still authenticated, and an open
socket kept receiving notifications after logout, session revocation,
password change or account deletion.

Auth now requires the token's session to exist for that user and the
user to be active; the connection registers first and is checked once,
so a concurrent revocation can't slip in between. Transient errors close
with try-again-later instead of invalid_token. Each socket closes when
its token expires, which bounds exposure even on nodes that miss a
revocation event.

Deleting sessions dispatches SessionsRevokedEvent on commit, every caller
flushes it after committing, and the hub closes the matching sockets.
Disabling or locking a user and deleting a user revoke their sessions,
also from the CLI.
2026-10-02 19:08:02 +02:00
kolaente cb1d7818d2 fix(sharing): delete a user's webhooks when they lose project access
Webhooks kept delivering task and project data to their creator after the
creator was removed from the project.

When a user can no longer read a project, or any project under it, their
webhooks there are deleted together with their task assignments and
subscriptions. This runs in the request's transaction for every explicit
removal: unsharing a user or team, removing a team member (including
external team sync, limited to teams the user was in), deleting a team
and reassigning a project's owner. Removing a user who is not a team
member is a no-op. It replaces the async listener that only cleaned up
after team member removal. Deleting a user deletes all webhooks they
created. Access is resolved in one batch per user and task data is
deleted via subquery, so large projects stay within bind limits.

As a backstop for access lost indirectly, e.g. by moving a project to
another parent, webhook delivery skips webhooks whose creator is not
active or can no longer read the event's project. A downgrade to read
keeps the webhook. Link shares are left alone by design.
2026-10-02 19:08:02 +02:00
kolaente c96f335cc9 fix(projects): count a project as default only for its owner
Any user pointing their default at a foreign project blocked its owner from archiving or deleting it. Deleting a project now clears every user's default that points at it, not only the owner's.
2026-10-02 19:08:02 +02:00
kolaente 39b3f87e45 fix(user): only allow writable projects as the default project
default_project_id accepted any id. Pointing it at a foreign project blocked the owner from archiving or deleting that project. It must now be 0 or a writable, non-archived project of the user; an unchanged value is not re-checked so a default the user lost access to doesn't block saving other settings.
2026-10-02 19:08:02 +02:00
kolaente 60e543777b fix(sharing): require project admin to manage link shares
The delete permission check read the share's tier from the URL-bound
struct, which is always the zero value (read), so a write member could
delete any link share, including admin ones.

Creating, changing or deleting link shares now requires project admin,
whatever the share's tier. Listing and reading by ID already did. Shares
created by write members stay valid. Deleting a share that does not exist
in the project in the path now returns 404.

Changelog: Creating, changing or deleting link shares now requires project admin.
2026-10-02 19:08:02 +02:00
kolaenteandTink d851612413 feat(api): read the current user's general settings on v2
A GET next to the existing PUT lets AutoPatch expose PATCH
/user/settings/general, so clients can change single settings without
re-sending the rest.
2026-10-02 09:47:32 +02:00
kolaente ecfbd5b7ec fix(routes): send and expose Retry-After on rate-limited responses
Every 429 from the rate limiters now carries Retry-After so clients know
when the window ends. The basic-auth limiter keys on aligned buckets, so
its value comes from the bucket end. Retry-After isn't CORS-safelisted,
so the CORS config exposes it for the desktop app and frontends on
another origin.
2026-10-01 17:34:38 +02:00
kolaenteandTink 82a263cbe9 fix(caldav-tokens): honour page in the v2 token list
The handler returned every token on every page while reporting
total_pages, so paging through the list repeated the same rows.

Fixes #4001
2026-10-01 17:29:11 +02:00
kolaenteandTink 30c468b1d5 fix(bots): order the bot list by id so pages are stable
Without an ORDER BY the paged LIMIT/OFFSET query could return overlapping
or missing bots across pages.
2026-10-01 17:29:11 +02:00
kolaenteandTink 41bb5e1cfb fix(api-tokens): order token list by id before paging
Without an ORDER BY, LIMIT/OFFSET pages are not stable, so tokens could
repeat or go missing across pages.
2026-10-01 17:29:11 +02:00
kolaenteandTink 45001e8827 fix(webhooks): add stable id ordering to Webhook.ReadAll
Webhook.ReadAll paged with Limit/Offset but no ORDER BY, so the row
order (and thus page contents) was undefined across requests.
2026-10-01 17:29:11 +02:00
kolaente f5c68ac049 fix(bots): 404 unowned bots, require status on PUT, audit owner status changes
- Owners can manage bots in any status (disabled or locked); unowned,
  missing and non-bot ids all return 404 so existence never leaks.
- PUT /api/v2/user/bots/{bot} requires status, so a partial body can no
  longer re-enable a disabled bot. A bot_status validator tag limits it
  to active/disabled.
- BotUser.Status is a pointer: the v1 update route keeps the current
  status when the body omits it instead of unlocking the bot.
- Owner status changes dispatch bot.status.changed for the audit log.
- Regenerate the frontend client and adapt the bot update callers.

Refs #3999
2026-10-01 15:27:34 +02:00
kolaente 76c906bb44 test(admin): make admin project search tests ParadeDB independent 2026-10-01 03:18:23 +02:00
kolaente c650f7ddfe feat(admin): filter and sort the admin project list 2026-10-01 03:18:23 +02:00