legal.<link key>.text and legal.<link key>.url define any number of legal
links, configured the same way as OpenID providers, so env vars like
VIKUNJA_LEGAL_FOOBAR_TEXT work. legal.imprinturl and legal.privacyurl keep
working but log a deprecation warning.
Add auth.ldap.attribute.id. When set (e.g. objectGUID on AD, entryUUID
on OpenLDAP), users and teams are matched by that attribute instead of
the login name and the group DN, so renaming or moving them in the
directory no longer creates duplicate accounts or new teams without the
old shares.
AD's binary objectGUID is formatted as a canonical GUID string; other
attributes must be printable text. Existing accounts and teams are
switched to the new id on the next login. An empty option keeps the old
behaviour.
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 claim timeout was measured from started_at, so a long import either
kept a dead claim for a day (the old 24h default) or had its slot taken
over while still running. A running migration now refreshes heartbeat_at
at a tenth of the timeout, clamped to 1s..30s, and a claim only counts as
stale once its last beat is older than the timeout. Rows that never beat
fall back to started_at. The default timeout drops to 5m accordingly.
Also bind the stale cutoff in the configured timezone: xorm writes times in
the engine's zone but binds query args as-is, which broke the comparison on
SQLite.
databaselevel, httplevel, eventslevel and maillevel now inherit
log.level unless set explicitly. databaselevel previously defaulted
to WARNING and now follows log.level (INFO by default).
The http request logger had a hardcoded DEBUG level, so every request
was emitted at INFO with no way to filter it short of turning the http
log off entirely. Add log.httplevel like the other categories and log
requests by status: INFO for success, WARNING for 4xx, ERROR for 5xx.
Resolves#3781
Vikunja's release binary is stripped and the image has no shell, so nothing
on the API side can be profiled from the outside. metrics.pprof (default
false, requires metrics.enabled) mounts Go's pprof handlers at their
canonical path, behind the same basic auth as /metrics when username and
password are set. Explicit handlers instead of the net/http/pprof blank
import, which would register on http.DefaultServeMux unconditionally.
database.maxconnectionlifetime is in milliseconds, so the default of 10000
recycled every pooled connection after ten seconds. Under load that is a
reconnect storm (32 fresh backends every 10 s with a pool of 32) and, more
importantly, it throws away the per-connection prepared statement cache and
the backend's plan cache, so nearly every statement is parsed and planned
again. Measured with pgx on a 3000-user load test: planning time stayed at
0.8 ms per request, more than the execution time, because no prepared
statement outlived the connection.
Thirty minutes keeps connections fresh enough to follow a failover or DNS
change and long enough for the caches to pay off.
Preflight over the declared uncompressed sizes (overflow-safe), a file
count cap and a per-user storage quota bound the import before
anything is read; the actual decompressed bytes are still counted
against the same budget while reading. Attachments and backgrounds
now stream through a lazy provider: one zip entry is decompressed
into a private temp file at a time. A failed import deletes the file
blobs it wrote, which were orphaned for every migrator before.
Capturing a config value before init returns "", and Key.Set writes at viper's
override level, which outranks defaults. Restoring a captured value therefore
pinned database.path to "" for the rest of the process, and TestRestore resolved
it to the bare rootpath and failed with "is a directory".
Only visible when VIKUNJA_TESTS_USE_CONFIG=1, which CI sets for every database
except sqlite-in-memory, which is why every test-api job failed while local runs
passed. ResetForTests restores genuine fresh-process state instead.
The refresh-token and OAuth token endpoints shared the 10 requests/minute
floor that protects login, register and password reset. Access tokens live
for 10 minutes, so every client renews its session several times an hour --
and behind a reverse proxy `service.ipextractionmethod` defaults to `direct`,
which keys all of them to the proxy's address. Routine renewals therefore
used up the login budget, and users got "Too many requests" when signing in.
This hit the desktop app hardest: it exchanges its authorization code on the
very same endpoint it refreshes tokens on.
Renewal now has a separate limiter with its own `ratelimit.tokenrefreshlimit`
setting, defaulting to 60 requests/minute. Both limits stay enforced.
service.secret had an unconditional random default, and viper's IsSet
reports defaults as set. The deprecation branch therefore always took the
"both keys are set" path and never copied service.jwtsecret over, so
instances configured with the old key silently got a fresh random secret
on every start — flaky 401s across replicas.
The random fallback is now generated after the config file and env are
read, so an empty service.secret unambiguously means "not configured".
We have recieved feedback from enterprise users who found this to be very unprofessional not not worthy of enterprise software. Therefore, we made the difficult decision to disable icon changes by default. We're looking into adding more clues that transport the same message but are less obvious.
Removes the `service.enablebotusers` config flag, the matching
`bot_users_enabled` field on /info, and the now-unused
`ErrBotUsersDisabled` error. Bot user routes and the frontend
settings tab are now always available.
https://claude.ai/code/session_01VhAR6xnoCdG1fpX52bzaCC
When running as a systemd service, the binary is often in /usr/local/bin
but WorkingDirectory points to a data directory like /var/lib/vikunja.
Previously, rootpath defaulted to the binary's directory, causing
Vikunja to attempt creating files/db in /usr/local/bin.
Now os.Getwd() is preferred, which respects systemd's WorkingDirectory
and is the intuitive default for all deployment scenarios.
Typesense was an optional external search backend. This commit fully
removes the integration, leaving the database searcher as the only
search implementation.
Changes:
- Delete pkg/models/typesense.go (core integration)
- Delete pkg/cmd/index.go (CLI command for indexing)
- Simplify task search to always use database searcher
- Remove Typesense event listeners for task sync
- Remove TypesenseSync model registration
- Remove Typesense config keys and defaults
- Remove Typesense doctor health check
- Remove Typesense initialization from startup
- Clean up benchmark test
- Add migration to drop typesense_sync table
- Remove golangci-lint suppression for typesense.go
- Remove typesense-go dependency
Adds ServiceJWTTTLShort (default 600s) to control the lifetime of
short-lived JWTs issued during token refresh. The existing jwtttl
and jwtttllong keys remain for session expiry and long sessions.
Adds `files.s3.disablesigning` config option that sends
`UNSIGNED-PAYLOAD` instead of computing SHA256 hashes for S3 request
signing which fixes `XAmzContentSHA256Mismatch` errors with
S3-compatible providers like Ceph RadosGW and Clever Cloud Cellar
Resolves https://github.com/go-vikunja/vikunja/issues/2181
This adds the ability to set a base URL for a Gravatar-compatible avatar
service (Gravatar itself, or Libravatar, for instance). The default will
be www.gravatar.com, so nothing will change from current behaviour unless
the user explicitly configures another URL.
Resolves#2082