Add a CalDAV token fixture (kind=4) for user10 who has TOTP enabled,
and implement the previously-skipped test proving token-based auth
still works when TOTP is active.
Internal callers (reactions) look up comments by ID without knowing
the task. The IDOR protection is still effective because ReadOne
always has TaskID set from the URL parameter.
RemoveProjectBackground previously used checkProjectBackgroundRights
which only checks CanRead, allowing read-only users to delete project
backgrounds. Added checkProjectBackgroundWriteRights that checks
CanUpdate and use it in RemoveProjectBackground.
Ref: GHSA-564f-wx8x-878h
Proves that a user with read-only access to a project can delete its
background image. The test expects a 403 Forbidden but the operation
proceeds because RemoveProjectBackground only checks CanRead.
Adds fixture entry giving user 15 read-only access to project 35
(which has a background_file_id).
Ref: GHSA-564f-wx8x-878h
Add task_id check to getTaskCommentSimple so that a comment can only
be loaded if it actually belongs to the task specified in the URL.
Previously, any valid comment ID could be read through any accessible
task endpoint.
Ref: GHSA-mr3j-p26x-72x4
Proves that a user can read a comment from an inaccessible task by
supplying an accessible task ID in the URL. Comment 18 belongs to
task 34 (owned by user 13), but testuser1 can read it via task 1.
Ref: GHSA-mr3j-p26x-72x4
File.File is now io.ReadCloser (no Seek). Buffer the file bytes
once via io.ReadAll, then use bytes.NewReader for both DecodeConfig
and Decode. Test updated to use io.NopCloser instead of afero.
Replace the github.com/spf13/afero dependency with a purpose-built
FileStorage interface (Open, Write, Stat, Remove, MkdirAll) with three
implementations: localStorage (with basePath), s3Storage (with key
prefix), and memStorage (for tests).
Each implementation owns its base path — callers pass only file IDs.
Delete s3fs.go, change File.File from afero.File to io.ReadCloser,
and fix duplication flows to buffer content for seeking.
http.ServeContent requires io.ReadSeeker which S3 files don't support.
Add S3 branch using io.Copy with explicit headers, matching the pattern
already used in task attachment downloads.
Refs: #2347
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the third-party afero-s3 library (and its temporary fork) with
a minimal in-tree afero.Fs implementation (~200 lines) that only supports
the three S3 operations Vikunja actually uses: Open (GetObject), Remove
(DeleteObject), and Stat (HeadObject).
The s3File implementation lazily opens a GetObject stream on first Read
and supports Seek by closing and re-opening the stream from the new offset,
matching the behavior of the fixed afero-s3 fork.
All other afero.Fs/File methods return ErrS3NotSupported since they are
never called in the S3 code path (writes already use direct PutObject).
This removes the fclairamb/afero-s3 dependency and the temporary replace
directive in go.mod entirely.
Closes#2347
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When attachments are uploaded (either via file picker or pasting into
the description editor), update both the local task ref and the kanban
store so that the attachment list and kanban card icons stay in sync.
The attachment store was a global singleton shared between concurrent
TaskDetailView instances, causing a race condition when navigating
between tasks via related tasks from the Kanban view. Attachments
now live on the task ref like every other task field.
The Attachments component now reads attachments from its task prop
and emits update:attachments events instead of using the global
attachment store singleton.
Resolves issue #475 by modifying CalDAV discovery so Apple Reminders can
use /dav/projects/ as the home set without exposing that synthetic path
as a real task list, preserving the existing principal-based flow. This
is because Apple Reminders defaults back to the /dav/projects/ URL,
rather than accepting the /dav/principals/username/ URL specified in
Vikunja.
Resolves#475
Block webhook requests to non-globally-routable IP addresses by default.
Uses net.Dialer.Control hook to validate resolved IPs against IANA
Special Purpose Registries after DNS resolution, preventing DNS rebinding.
Configurable via webhooks.allownonroutableips (default: false).