Commit Graph
350 Commits
Author SHA1 Message Date
kolaente b36581bcc1 fix(routes): only bind JSON, form and multipart request bodies
Form and multipart only bind fields with an explicit form tag. Other
content types are answered with 415, or 400 (2004) on v1 CRUD routes.
2026-11-05 15:42:45 +01:00
kolaente 27ec08bb03 fix(images): limit the side length of decoded images
Images longer than MaxSide (65535) pixels on either side are now rejected
before decoding, like images over the pixel limit.

Attachments with such images are still stored; only their preview is not
generated. Project backgrounds and avatars reject them with the existing
image too large error.
2026-11-05 15:42:45 +01:00
kolaente 1f79a8e01a fix(migration): reject repeated csv column mappings, cap labels per row
- Reject a config that maps a column more than once, already when the
  import is started or previewed.
- Import repeated labels of a row once and reject rows with more than
  MaxLabelsPerTask (100) labels.
- Count items while converting rows so a file over the import item limit
  fails before the whole structure is built.
- Walk mappings sorted by column so a row only visits the cells it has.
2026-11-05 15:42:45 +01:00
kolaente c6f18e405c fix(caldav): build iCalendar output linearly, cap reminders and titles
Every VALARM repeats the task title and the output was built with string
concatenation, so rendering cost grew quadratically with the number of
reminders. Build it with a strings.Builder instead.

Tasks may now have at most MaxRemindersPerTask (100) reminders and a
title of at most MaxTaskTitleLength (1000) characters, checked through
v1, v2 and CalDAV. On update the reminder limit only applies when the
count grows and the title limit only when the title changes, so existing
tasks stay editable.
2026-11-05 15:42:45 +01:00
kolaente 46279ab3c5 fix(richtext): bound the cells tables expand to during conversion
Rowspan/colspan and short-row padding make both converters materialize
far more cells than the input contains. HTML to markdown now renders
tables past MaxTableCells as plain rows, so stored descriptions still
read fine. Markdown to HTML rejects such input, which the v2 API reports
as invalid data.
2026-11-05 15:42:45 +01:00
kolaente 111e332379 fix(filters): limit business day offsets in date math
The business day offsets in the date values of a task or time entry
filter may now add up to at most MaxDatemathBusinessDays (10000). Larger
filters are rejected with the invalid filter value error before any value
is evaluated. The frontend date math parser applies the same limit.
2026-11-05 15:42:45 +01:00
kolaente a8501c28e5 fix(mcp): hint at listing all actions when find_action matches nothing
An empty find_action result looked identical to "not authorized", so
agents guessing a wrong resource prefix (task_relations instead of
tasks_relations) concluded their token lacked permission.

Refs #4342
2026-11-05 13:34:47 +01:00
kolaenteandTink b2c1686d11 feat(auth): preview verified OIDC accounts and reject unavailable identities 2026-11-05 12:44:50 +01:00
kolaenteandTink f0625e6d1c feat(api): add reauthenticated OIDC identity management endpoints 2026-11-05 12:44:50 +01:00
kolaenteandTink 61f8af603f refactor(user): share password and TOTP reauthentication 2026-11-05 12:44:49 +01:00
kolaenteandTink 205f721ded test(projects): share trash admin and webhook test setup
Address pr-swarm finding: setup copied inline
2026-11-04 23:14:28 +01:00
kolaenteandTink a7ed70ca22 feat(projects): count trash and preserve other users deletions 2026-11-04 23:14:28 +01:00
kolaenteandTink 96150b84f8 fix(projects): share background removal across API versions
Address pr-swarm finding: removal passed a stub project, file never deleted
2026-11-04 23:14:28 +01:00
kolaenteandTink 9bc2fbf399 fix(tasks): preserve project trash details in task errors
Address pr-swarm finding: database errors fell through to CanWrite
2026-11-04 23:14:28 +01:00
kolaenteandTink 1deda2d1c8 feat(projects): expose blocked restore root details
Address pr-swarm finding: missing IsErr helpers
2026-11-04 23:14:28 +01:00
kolaenteandTink c429112b2a refactor(trash): share the retention setting
rg -l TaskDeleteRetention pkg | xargs sed -i 's/TaskDeleteRetention/TrashRetention/g'
2026-11-04 23:14:28 +01:00
kolaenteandTink 83a2a09613 feat(frontend): log and edit private time entries 2026-11-04 23:14:28 +01:00
kolaenteandTink 13acda8fa4 feat(time-tracking): support entries without a container 2026-11-04 23:14:28 +01:00
kolaenteandTink 82dcceca55 fix(caldav): invalidate sync tokens after permanent deletion
Implement the approved last_purged_at decision with an advancing purge generation so same-second purges invalidate old tokens and full resync tokens remain usable.
2026-11-04 23:14:28 +01:00
kolaenteandTink 94f501c61d feat(tasks): add task trash lifecycle and permissions 2026-11-04 23:14:28 +01:00
kolaente c5a8e3d8bb fix(user): count wrong passwords on password change toward account lockout
Checking the old password directly skipped the failed-attempt counter that login and the email-change prompt use. Extract CheckUserPasswordCountingFailures from CheckUserCredentials and use it for the password change too.
2026-11-03 20:15:14 +01:00
kolaente cda6d88330 feat(user): lock account after 10 failed password attempts
Mirrors the TOTP lockout: issue a password reset token, send a mail, set
the account to locked and revoke its sessions. Counts every wrong password
checked by CheckUserCredentials, including the password and email change
re-auth prompts, so a stolen session can no longer guess the password
indefinitely.

The 3-attempt warning mail no longer resets the counter, otherwise it
could never reach the lockout threshold.
2026-11-03 10:41:13 +01:00
kolaente f77a1f9ea0 feat: add basic auth config for health endpoints 2026-11-03 09:12:02 +01:00
kolaente fe509c0366 test(api): cover NUL rejection in comment bodies on v1 and v2 2026-11-02 11:58:07 +01:00
kolaente e45ed46cf8 feat(api): include bot owner on bot user endpoints and created_by 2026-10-30 22:57:12 +01:00
kolaente 2f94323e54 feat(api): return all legal links from /info
legal.links contains the deprecated imprint and privacy urls first, then the
configured links sorted by key. imprint_url and privacy_policy_url stay in the
response for older clients and are marked deprecated.
2026-10-28 23:02:31 +01:00
kolaente 4ae5262537 feat(api): return the user object instead of user_id on time entries
The websocket timer push now routes by entry.User, so delete loads the user before dispatching.
2026-10-22 11:14:14 +02:00
kolaente 01c3884bb1 feat(auth): sync provider avatars in the background after login
Address review decision: the post-commit avatar sync still blocked the
LDAP/OIDC login response for the download and up to 30s of decode
queueing. It now runs in a goroutine on a context that outlives the
request, with panics recovered and failures logged.
2026-10-20 23:05:30 +02:00
kolaente d2ec3fefc5 fix(avatar): remove a replaced avatar's blob only after commit
Address review decision: SaveAvatar removed the old file's blob from
storage inside the transaction, so a later rollback left the user on an
avatar whose content was gone. The files row is still deleted in the
session; the blob and its previews go once the session commits.
2026-10-20 23:05:30 +02:00
kolaente 98abca499a fix(auth): sync OIDC and LDAP avatars after the login commits
Address review decision: the provider avatar was decoded inside the login
transaction after the user row was written, holding it while queued for
decode capacity, and a later rollback (TOTP, disabled user) discarded the
work. Logins now commit first; the avatar is decoded without a session
and saved in its own short transaction.
2026-10-20 23:05:30 +02:00
kolaente e7a2daa3ff fix(background): decode backgrounds before opening the write session
Address review decision: a background upload queued for decode capacity
held its transaction for up to 30s. The permission check now runs in a
short read session, the image is decoded and encoded without a session,
and a fresh write session re-checks permission and stores the result.
2026-10-20 23:05:30 +02:00
kolaente 9467ad5787 fix(avatar): decode avatars before opening a database session
Address review decision: a decode queued for capacity held the request's
transaction for up to 30s, so on SQLite a concurrent write made the
upload fail with database is locked. Uploads and avatar reads now decode
and encode first; only the encoded PNG enters the transaction.
2026-10-20 23:05:30 +02:00
kolaente 36136362a2 fix(background): name the right mime type when rejecting an image
Address pr-swarm finding: SaveBackgroundFile sniffed an already-read
stream, so the 400 reported text/plain.
2026-10-20 23:05:30 +02:00
kolaente f773755997 fix(background): answer 400 for any image the decoder rejects
Address pr-swarm finding: only "unknown format" errors mapped to 400, so
truncated data or a header past the read limit answered 500.
2026-10-20 23:05:30 +02:00
kolaente 34434f9434 fix(avatar): answer 400 for uploads the decoder rejects
Address pr-swarm finding: StoreUploadedAvatar ran its own header check
without the read limit, and anything it let through that Decode then
rejected (truncated data, header past 12 MiB) answered 500.
2026-10-20 23:05:30 +02:00
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