Identity-provider avatars are cropped through imageutils.Decode. When the
server is busy the avatar is skipped and logged, like any other avatar
sync error, so the login still succeeds.
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.
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.
Attachment previews now decode through imageutils.Decode, which holds an
image's pixels against one process-wide budget until the caller is done
with the decoded image. Other decode sites move onto it next.
Address pr-swarm finding: the item cap ran only after data.json was fully
decoded into tasks, so a file of millions of bare objects still exhausted
memory.
The archive holds the config, the full database and every blob, but was
created with the umask mode and followed or clobbered an existing path.
Refs GHSA-rp64-xg8g-9mpr
Checklist, custom field and link attachment names were concatenated raw
into the description markdown, so brackets, parens or HTML in names broke
links or were dropped.
The memory keyvalue store only shallow-copies Provider, so Oauth2Config
was shared between the cache and all requests. Concurrent callbacks
overwrote each other's RedirectURL, causing intermittent login failures.
A provider's discovery document could advertise a javascript: URL as
end_session_endpoint, which the frontend then navigated to on logout,
running script on Vikunja's origin. Only absolute http(s) URLs are now
accepted, both when caching the discovered endpoint and when building
the logout URL. Invalid endpoints are dropped so logout stays local.
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.
Support {userdn} and {username} placeholders in auth.ldap.groupsyncfilter.
When present, every matching group is a membership and the member
attribute is not fetched, so the cost scales with the user's own
memberships and nested AD groups can be resolved with
LDAP_MATCHING_RULE_IN_CHAIN. Filters without a placeholder keep the
previous client-side member matching.
The group search now uses paged results in both modes so directories
with more groups than the server's size limit no longer fail the login.
A CSV saved as UTF-16 (Excel's "Unicode Text") was read as UTF-8, leaving
a NUL between every character. MySQL and SQLite store that, Postgres text
columns reject it with SQLSTATE 22021 and the whole import fails.
The file is now decoded to UTF-8 when read: a UTF-16 BOM switches to
UTF-16 decoding, remaining NUL bytes are dropped and invalid UTF-8 becomes
U+FFFD, matching what encoding/json does for the JSON importers.
Fixes API-CLOUD-4G
Detect, preview and migrate each repeated the size checks and file read.
Extracting readCSVFile gives the raw bytes a single entry point, and
projectsFromCSV makes the migrate conversion testable without a database.
Over https the refresh cookie used SameSite=None. CORS allows localhost
origins by default for the desktop app, so a page on any localhost port
could refresh a session with the user's cookie and read the new token.
The cookie is now always SameSite=Lax. SameSite=None existed for the
desktop app's cookie refresh, which it replaced with OAuth over IPC in
v2.3.0.
Changelog:
- Desktop v2.2.x has to log in again more often; upgrade to v2.3.0 or later.
- Deployments with the frontend and API on different registrable domains
lose cookie refresh, as before v2.2.0.
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.
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
Both `usernamefallback` + `emailfallback` on: lookup required username
AND email match, so local user with different username never linked,
duplicate account created. Now verified-email-only lookup always tried
last.
Fixes#2022
## How to verify
1. Create a local user `localuser` with email `me@example.com`.
2. Configure an OIDC provider with `usernamefallback: true` and
`emailfallback: true`. In the IdP, create a user whose email is
`me@example.com` (sent with `email_verified: true`) and whose
username/sub is different from `localuser`.
3. Log in to Vikunja through that OIDC provider.
4. **Expected:** You are logged in as `localuser`, and `vikunja user
list` shows no new user for the provider's issuer.
**Before this PR:** a second user with a random or IdP-provided username
was created.
---------
Co-authored-by: kolaente <k@knt.li>
The files row is inserted before the blob is written, and the local backend creates the file before copying into it, so a failed copy left a partial blob behind once the row was rolled back.