Pass expand=task_counts to get each project's done and undone task
counts. The v2 expand param is now repeatable so it combines with
expand=permissions.
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.
Team.ReadAll had no ORDER BY under LIMIT/OFFSET, so pages could overlap or
skip teams, and an exact search match could fall off the first page the
pickers fetch.
The reminder cron only ran, and only created the in-app notification,
when the mailer was enabled and the user had email reminders on. The
database notification is now always stored; the mail is skipped in
ReminderDueNotification.ToMail instead.
Fixes#4072
- 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
The gantt view of a saved filter sends filter_include_nulls to show tasks
without dates, but the flag was dropped when merging the request filter into
the saved filter. It now applies to the request filter only, so it does not
leak into the saved filter's own conditions (e.g. pulling in unassigned tasks).
Fixes#4009
The saved filter view listener only inserted bucket rows for tasks not
yet in a view, so a task completed in its own project stayed in the
filter's default bucket. Move it into the view's done bucket when done,
and back to the default bucket when undone.
Fixes#4018
The migration to outgoingrequests.* ran while setting defaults, before
the config file and environment were read, so it only ever saw default
values. webhooks.proxyurl, webhooks.proxypassword and
webhooks.allownonroutableips have been ignored since they were
deprecated.
The proxy dial exemption was keyed on addresses learned lazily from earlier
proxied requests, so a direct request (loopback, NO_PROXY or an unproxied
scheme) or a redirect to the proxy's host:port was dialed unguarded. Derive
the exempt addresses once from the configured or environment proxy URLs and
reject direct requests to them with ssrf.ErrProhibitedIP.
Address pr-swarm finding: SSRF guard bypass via the proxy address
outgoingrequests.proxyurl was silently ignored unless proxypassword was
set too, and the username was hardcoded to vikunja. Credentials in the
proxy url are used now, with proxypassword as the mole-compatible
fallback. Plain http requests through the proxy now authenticate too,
not only CONNECT tunnels. An unparseable proxy url fails requests
instead of connecting directly.
NewHTTPClient carries the proxy and timeout settings without the SSRF
guard, for admin-configured endpoints like OIDC providers that often
live on a private network. NewSSRFSafeHTTPClient builds on it.