14876 Commits
Author SHA1 Message Date
kolaente ef2200e942 chore: v2.5.0 release preparations v2.5.0 2026-08-04 13:42:43 +02:00
kolaente d545e3bbb1 refactor: drop redundant link share branch in comment permissions
GetID() delegates to getUserID(), so both branches compute the same value.
2026-08-04 13:40:11 +02:00
kolaente ed8dc89f75 fix: report the real link share id in saved filter errors
auth.GetID() is now negated, so ErrSavedFilterNotAvailableForLinkShare
carried a value that is not a link share id.
2026-08-04 13:40:11 +02:00
kolaente ccba2e4546 fix: avoid panic on null jwt claim values
reflect.TypeOf returns a nil Type for a null claim value, so building the
error message panicked.
2026-08-04 13:40:11 +02:00
kolaente dbe664dec5 fix(security): reject non-user tokens in GetUserFromClaims
GetUserFromClaims read the id claim straight into User.ID without checking the
token type. A link share JWT carries the raw, positive share id in that claim
and reaches every authenticated route, so the only thing preventing
impersonation was the incidental absence of a username claim in link share
tokens. Adding one would have reintroduced the confusion with a positive id,
bypassing the GetID negation entirely.

AuthTypeUser moves to pkg/user, which parses the claims and cannot import
pkg/modules/auth, so the value exists once.
2026-08-04 13:40:11 +02:00
kolaente 4b35f5d7e2 test: assert link share attachments are not attributed to a colliding user
files.Create stores GetID() straight into files.created_by_id, so a share
uploading an attachment used to persist a row indistinguishable from one
created by the user with the same id.
2026-08-04 13:40:11 +02:00
kolaente 2b1558e8df test: cover link share principal confusion at the HTTP layer
A link share JWT reaches every authenticated route; nothing rejects it at the
route group.
2026-08-04 13:40:11 +02:00
kolaente ce0d1355cf fix(security): reject link shares in Webhook.ReadAll
The guard on Webhook.CanRead is unreachable: no route exposes a read-one
webhook, and DoReadAll never calls CanRead. Two paths were left open:

- the v2 user-webhook list passes a.GetID() into Webhook.UserID, which is
  negative for a link share, so the w.UserID > 0 branch and its link share
  check were skipped and the request fell through to the project branch with
  project id 0, returning 404 instead of 403.
- the project branch never rejected link shares at all, so any holder of a
  public share link could list the project's webhooks. target_url is a bearer
  secret for Slack, Discord, Teams and Zapier.

Guard both by rejecting link shares at the top of ReadAll.
2026-08-04 13:40:11 +02:00
kolaente a717d64d56 fix(security): reject link shares at team, bot and webhook permission checks
These checks all compare the auth id against a users.id column, so a link
share whose id collided with a user id passed them as that user.
2026-08-04 13:40:11 +02:00
kolaente 6ffe265efa fix(security): return the negated user id from LinkSharing.GetID
web.Auth is satisfied by both *user.User and *LinkSharing, so returning the
raw positive share.ID made a share with id N indistinguishable from the user
with id N at every permission check comparing against a users.id column.

The rest of the codebase already keys shares negatively
(getUsersOrLinkSharesFromIDs, toUser), so this makes GetID consistent with
that contract instead of an exception to it.
2026-08-04 13:40:11 +02:00
renovate[bot]andkolaente febb83bcd6 chore(deps): update dependency rollup to v4.62.4 2026-08-04 09:31:52 +02:00
renovate[bot]andkolaente 4d92ed94ff fix(deps): update module github.com/redis/go-redis/v9 to v9.22.0 2026-08-04 09:00:41 +02:00
renovate[bot]andkolaente 3b6e969f9b fix(deps): update module github.com/aws/aws-sdk-go-v2/service/s3 to v1.106.4 2026-08-04 09:00:28 +02:00
renovate[bot]andkolaente bc2af328ef chore(deps): update dependency js-yaml to v5.2.3 2026-08-04 09:00:05 +02:00
renovate[bot]andkolaente b4ca03b2db chore(deps): update dependency js-yaml@4 to v5.2.3 2026-08-04 08:59:53 +02:00
Frederick [Bot] 0ccecb36c9 chore(i18n): update translations via Crowdin 2026-08-04 00:23:49 +00:00
kolaente 5737d0df6f fix(views): drop deleted bucket from view state after deleting it
The store kept a deleted bucket as the view's default or done bucket,
so the next view save echoed the stale id and failed.
2026-08-04 01:09:10 +02:00
kolaente e329197211 fix(views): preselect manual bucket mode in the view edit form
Neither bucket mode radio matches the none mode a view has before it
becomes a kanban view, so both stayed unselected. A single watcher
normalizes the mode for kanban views, on load as well as when the kind
changes.
2026-08-04 01:09:10 +02:00
kolaente 7eeb88da23 fix(migration): repair kanban views without a bucket configuration mode
Views broken by the bug above keep their state until something writes
them again, so repair them on startup: set the manual mode, seed the
default buckets when the view has none and place the project's tasks in
the default bucket.

Views whose project or saved filter is gone are skipped rather than
repaired with a dangling creator, soft-deleted tasks stay out of the
backfill, and the mode flip is the last write per view so an
interrupted run picks the view up again.
2026-08-04 01:09:10 +02:00
kolaente 2d8198e942 fix(views): seed and maintain kanban buckets when a view's kind changes
Switching an existing view to kanban left bucket_configuration_mode at
none, so the tasks endpoint returned a flat task list which the
frontend rendered as empty bucket columns.

The mode is now normalized on create and update: a kanban view without
a mode becomes manual, a non-kanban view loses its mode, and an update
which omits the mode keeps the stored one together with its bucket
configuration. Becoming a manual kanban view seeds the default buckets
and backfills task_buckets rows for tasks which have none in that view,
so tasks created while the view was of a different kind stay visible.
Bucket ids from the request are validated against the view: an id of a
bucket which is gone resets to zero instead of locking the view, an id
belonging to another view is rejected, and non-kanban views no longer
write those columns at all so a round trip can restore them.

The backfill selects only task ids, scopes saved filter views to the
projects their owner can see, batches its inserts and ignores conflicts
with concurrently placed tasks.

Fixes https://github.com/go-vikunja/vikunja/issues/3386
2026-08-04 01:09:10 +02:00
renovate[bot]andkolaente f045a59ea7 chore(deps): update node.js to v24.19.0 2026-08-04 00:30:22 +02:00
kolaente d1cec84b11 feat(dev): add paseo config 2026-08-04 00:24:53 +02:00
renovate[bot]andkolaente 9c565bc722 fix(deps): update module github.com/danielgtaylor/huma/v2 to v2.39.1 2026-08-03 23:49:30 +02:00
renovate[bot]andkolaente 15ed7e2dfd chore(deps): update playwright to v1.62.1 2026-08-03 23:49:13 +02:00
renovate[bot]andkolaente dd661882d3 chore(deps): update dependency ip-address to v10.4.0 2026-08-03 23:48:53 +02:00
renovate[bot]andkolaente 91ce89d0f3 chore(deps): update danielroe/provenance-action digest to 193227b 2026-08-03 23:48:32 +02:00
renovate[bot]andkolaente 5c0297edd4 chore(deps): update pnpm to v11.19.0 2026-08-03 23:48:06 +02:00
renovate[bot]andkolaente 2a9d543442 chore(deps): update pnpm/action-setup action to v6.0.10 2026-08-03 23:47:26 +02:00
renovate[bot]andkolaente c72e0777d4 chore(deps): update dependency markdown-it to v15 2026-08-03 23:47:17 +02:00
renovate[bot]andkolaente cf79ad5a87 chore(deps): update dev-dependencies 2026-08-03 23:47:15 +02:00
kolaente 4c6fb252bc fix(ci): pin LLM-action to fork with reasoning model support
Upstream rejects max_tokens/temperature for gpt-5 series models,
breaking auto-label with gpt-5.6-luna. Pin to the fork carrying
appleboy/LLM-action#24 until it is merged and released.
2026-08-03 23:46:29 +02:00
TinkandGitHub 855f6a24a0 fix(ci): replace retired GitHub Models inference in auto-label workflow (#3415) 2026-08-03 12:01:17 +02:00
kolaente 6f473835f9 test(e2e): cover multiline creation order and subtasks
Order asserted both right after creation and after a reload; the
subtask test pins which task became the parent.
2026-08-02 21:11:28 +02:00
kolaente 0e47556956 feat(tasks): create quick-add tasks through the bulk endpoint
Multiline quick add previously created each line with its own request
plus client-side index guessing — parallel creates raced and persisted
scrambled task order. All input now goes through the atomic bulk
endpoint (single lines too, per review), so the server assigns indexes
and positions in input order. This drops the defaultPosition prop and
ProjectList's firstNewPosition.

Created tasks are emitted once as a batch: per-task emits made
non-position-sorted lists reload per task and forced consumers'
insert order into AddTask. Relations run serialized through runWrites,
and a relation failure toasts instead of restoring input for tasks that
already exist. Creation failure restores the original input string.
E2E waits pinned to the old v1 create request follow the flow to the
bulk endpoint.

Fixes #3288
2026-08-02 21:11:28 +02:00
kolaente e106150ed4 feat(tasks): add bulk quick-add creation to the task store
createNewTasksBulk builds full task payloads from quick add magic
titles (extracted from createNewTask so both paths share it) and hands
them to the service in one call. Label application failures toast and
continue — the tasks already exist server-side, failing the whole
action would invite duplicate resubmits.

runWrites moves to helpers/ so components can use the write-serializing
util without importing the store module.
2026-08-02 21:11:28 +02:00
kolaente aa0e9d0c01 feat(tasks): add bulk creation to the task service
bulkCreate posts to /api/v2/projects/{id}/tasks/bulk: tasks grouped per
project, chunked at the endpoint's 100-task limit, requests strictly
sequential (server assigns indexes at insert time and concurrent bulk
writes fail under write contention), batches of one project posted last
chunk first because the server places each batch on top of every view.
Returns slots aligned 1:1 with the input plus the first error, so
partial progress survives a failed batch.

The payload is an explicit allowlist — the v2 schema rejects unknown
properties, so sending the full processModel output 422s. Response
shape is guarded and the error is translated via the message module
(direct i18n.global.t in the service trips vue-i18n's type
instantiation limit).
2026-08-02 21:11:28 +02:00
kolaente 96dd78371d fix(ci): describe PR head instead of base branch for preview version
On pull_request_target, github.sha points to the base branch head, so
gh-describe baked main's version string into the preview image while the
code itself was the PR head. Pass the PR head sha explicitly.
2026-08-02 20:50:36 +02:00
kolaente cb5b9bd974 fix(ci): tag preview images with the PR head sha
Under pull_request_target, github.sha resolves to the base branch tip,
so every PR on the same main tip pushed the same sha-<base> tag and
preview images overwrote each other. DOCKER_METADATA_PR_HEAD_SHA makes
metadata-action use the PR head sha; the deploy comment derives its sha
from the meta output, so its per-commit URLs now match the pushed tag.

Fixes #3407
2026-08-02 19:13:29 +02:00
kolaente eef917db8c docs(agents): explicit mention of no raw sql [skip ci] 2026-08-02 16:40:29 +02:00
Frederick [Bot] 0ad7a4a4db [skip ci] Updated yaegi symbols 2026-08-02 14:37:40 +00:00
kolaente 935c4c9b21 test(api-tokens): match permission checkbox name exactly
The new create_bulk token permission makes the non-exact
'Checkbox create' locator ambiguous — strict mode resolves 2 elements.
2026-08-02 16:21:50 +02:00
kolaente ba980b1b8e fix(migration): abort import on unexpected task creation error
Non-empty-title errors from task creation fell through silently and the
loop kept using a task with ID 0, attaching relations and labels to id
0. Return the error instead; empty titles keep being skipped.
2026-08-02 16:21:50 +02:00
kolaente 57bfd1cae2 feat(api/v2): add bulk task creation endpoint
POST /api/v2/projects/{project}/tasks/bulk creates up to 100 tasks in
one atomic request. The URL project wins over any body project_id, and
rich-text descriptions honor the format query param like single create.
Bucket limits are only enforced for explicitly provided bucket ids.

The route files under the tasks API-token group as create_bulk,
matching the v1 bulk naming, so a token scoped to tasks can use it.
2026-08-02 16:21:50 +02:00
kolaente 42fdacd26d feat(tasks): update saved-filter views once per creation batch
The resource-heavy saved-filter evaluation moves from TaskCreatedEvent
to the batch event: filters, views and the fallback timezone are loaded
once per batch, then every task is evaluated in one pass. Buckets and
positions are inserted per task so a mid-loop position recalculation
sees earlier members' rows.
2026-08-02 16:21:50 +02:00
kolaente e86c17faa9 feat(tasks): add BulkTaskCreation model
Creates up to 100 tasks in one project atomically. One project write
check covers the batch; any invalid task rolls the whole batch back
with error 4031 naming the offending payload index, and an
invalid batch size returns 4030. The 1..100 bound is declared as
minItems/maxItems in the schema (pinned to the constant by a test) with
the model check as backstop for non-HTTP callers. Payload positions are
zeroed — positions are always calculated server-side.
2026-08-02 16:21:50 +02:00
kolaente c385c0e72a feat(tasks): generalize task creation to batches
createTask becomes a wrapper around createTasks, which creates a whole
batch in one pass: project, creator, views and default buckets are
looked up once, indexes are assigned from a single max-index query
(preset indexes kept when free, collisions get the next free one), and
explicitly provided buckets are resolved once per distinct bucket —
verified to belong to the target project and checked against their
limit with the batch's own members counted. The row insert stays per
task because multi-row inserts don't reliably return autoincrement ids
on all supported databases.

Validation errors always carry the payload index; the single-create
wrapper unwraps them so existing callers keep their raw error types.

A new TasksBatchCreatedEvent fires once per batch (single create is a
batch of one) for listeners which can process all new tasks in one
pass; per-task TaskCreatedEvent semantics stay unchanged for webhooks,
mentions and audit.

Split for reviewability: builds together with the follow-up commit
adding the BulkTaskCreation model and error types.
2026-08-02 16:21:50 +02:00
kolaente 852dca94a4 feat(tasks): calculate top-of-view positions per batch
calculateNewPositionsForTasks places a whole creation batch on top of a
view with a single lowest-position query: evenly spaced below the
current lowest, payload order preserved, no collisions by construction.
When the spacing trips a full recalculation, the batch's own freshly
inserted rows are snapshot-scoped and removed again afterwards so the
queued top positions land while rows written earlier in the creation
survive.

It replaces the two previous implementations of the same idea:
calculateNewPositionForTask is now a one-element wrapper and the
saved-filter healing path calls it directly. The empty-view default
falls back to payload-order spacing when task indexes repeat or are
zero, since index-derived defaults collide in saved-filter views
spanning projects.
2026-08-02 16:21:50 +02:00
kolaente 3855517a7a fix(api/v2): validate slice-of-struct body fields at the boundary
govalidator does not recurse into []*T fields, so elements of
slice-of-pointer-struct body fields skipped the valid-tag rules that
nested struct fields get. Validate each element of exported, writable
[]*struct body fields, reporting errors as body.<field>[<index>].<name>
like Huma's own schema errors. readOnly fields are skipped since Huma
deliberately accepts round-tripped values on write.
2026-08-02 16:21:50 +02:00
kolaente d0a0efd403 ci: auto-regenerate yaegi symbols alongside swagger docs 2026-08-02 16:16:06 +02:00
kolaente a854ad6641 fix(plugins): regenerate stale yaegi symbol tables
The symbol tables in pkg/yaegi_symbols were generated once by hand and
never updated, so newer exported API like models.TimeEntry was invisible
to interpreted plugins.

The logFatal/logFatalf wrappers in symbols.go exist because yaegi
extract treats Fatal* in any package named "log" as restricted and
references those local names; previously the generated file was
hand-patched instead, which made regeneration produce uncompilable
output.

Fixes https://github.com/go-vikunja/vikunja/issues/3387
2026-08-02 16:16:06 +02:00