Commit Graph

1888 Commits

Author SHA1 Message Date
kolaente 79dbb40985
fix(db): prevent SQLite "database is locked" errors under concurrent writes
Configure SQLite connections with WAL journal mode, a 5-second busy
timeout, shared cache, and a max of 1 open connection. SQLite only
supports a single writer at a time, so without these settings concurrent
API requests (e.g. bulk task creation) would immediately fail with
"database is locked" instead of waiting and retrying.
2026-03-02 12:37:55 +01:00
Weijie Zhao 3ca4913fcb
fix: use MinPositionSpacing threshold in calculateNewPositionForTask (#2320)
calculateNewPositionForTask only checked for lowestPosition == 0 before
triggering a full position recalculation. Extremely small position
values (e.g. 3.16e-285) passed this check, causing new tasks to get
meaningless positions via the index * 2^16 fallback, breaking sort
order.

Use the same < MinPositionSpacing threshold that
createPositionsForTasksInView and the position update handler already
use.

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-02 09:14:38 +01:00
kolaente 23d84e7811
fix(views): assign default position when creating new project views
When creating a new view without specifying a position, it defaulted to
0, causing it to always sort before all other views. Apply
calculateDefaultPosition to assign a unique position based on the view
ID, consistent with how projects, tasks, and buckets handle this.

Fixes go-vikunja/vikunja#2319
2026-03-02 08:35:35 +01:00
kolaente a7e4a4f4af
fix(migration): support space-separated date format in TickTick importer
Fixes https://github.com/go-vikunja/vikunja/issues/2324
2026-03-02 08:35:35 +01:00
kolaente 412215ee2f fix(auth): correctly delete older password reset tokens in cron 2026-02-27 14:44:26 +01:00
kolaente 5c2195f9fc fix(auth): remove password reset token after use 2026-02-27 14:44:26 +01:00
kolaente 3d5ad73d4f
fix(filter): recover from datemath panic on malformed date filter values
The go-datemath lexer panics with "scanner internal error" when given
certain malformed inputs like "no" (it starts recognizing "now" but
hits EOF). Wrap datemath.Parse in a recover so the panic becomes a
regular error, allowing the fallback date parser to handle it gracefully.

Closes go-vikunja/vikunja#2307
2026-02-26 16:09:13 +01:00
Frederick [Bot] 599d05dd5e [skip ci] Updated swagger docs 2026-02-25 13:05:32 +00:00
kolaente d1e1cb3b4f test(api): add tests for password validation in reset and update flows
- Add httpCodeGetter interface to handle ValidationHTTPError in test helper
- Add test case for password too short in password reset
- Add test case for password too short in password update
- Fix existing test data to use valid 8+ char passwords
2026-02-25 13:44:56 +01:00
kolaente 89c17d3b23 feat(api): enforce password validation on reset and update flows
Add bcrypt_password validation to password reset and update endpoints:
- Add validation tag to PasswordReset.NewPassword struct field
- Add validation tag to UserPassword.NewPassword struct field
- Add c.Validate() calls in both handlers
- Fix off-by-one error in bcrypt_password validator (use <= 72 not < 72)

Password requirements: min 8 chars, max 72 bytes (bcrypt limit)
2026-02-25 13:44:56 +01:00
kolaente 39da47e435 fix: detect and fail on oversized zip entries instead of silent truncation
Replace io.LimitReader with a new readZipEntry helper that reads one extra
byte to detect when content exceeds maxZipEntrySize (500MB). This prevents
silent data corruption where partial file bytes would be stored as if the
upload succeeded.

The import now fails with ErrFileTooLarge instead of accepting truncated
content for attachments and background blobs.
2026-02-25 13:01:00 +01:00
kolaente db4fa9a4b6 fix(restore): extract preValidateTableData to reduce cyclomatic complexity 2026-02-25 13:01:00 +01:00
kolaente f3ac0574c0 fix(auth): use checked type assertions for all JWT claims 2026-02-25 13:01:00 +01:00
kolaente 1b3d8dc59c fix(restore): pre-validate all table data JSON before wiping database 2026-02-25 13:01:00 +01:00
kolaente 9fd5b62fde fix(restore): limit zip entry read size to prevent decompression bombs 2026-02-25 13:01:00 +01:00
kolaente 329c07f24b fix(attachments): use mime.FormatMediaType for Content-Disposition header 2026-02-25 13:01:00 +01:00
kolaente 7e7e778d49 fix(db): validate table names and quote identifiers in raw SQL 2026-02-25 13:01:00 +01:00
kolaente 9d19a04550 fix(migration): use checked type assertion for background file id 2026-02-25 13:01:00 +01:00
kolaente fc5ab844de fix(migration): limit zip entry read size to prevent decompression bombs 2026-02-25 13:01:00 +01:00
kolaente 6815cdbda4 fix(migration): reject zip entries with path traversal in vikunja-file import 2026-02-25 13:01:00 +01:00
kolaente bbe1a2bbd0 refactor(utils): extract ContainsPathTraversal to shared utils package 2026-02-25 13:01:00 +01:00
kolaente c2cf5ba1c5 fix(restore): validate migration data before wiping database
Move archive validation (migration file existence and slice bounds
check) before the database wipe. Previously a malformed archive
would first destroy the database and then panic, leaving the
instance in an irrecoverable state with total data loss.

Now the migration data is fully parsed and validated before any
destructive operations occur.
2026-02-25 13:01:00 +01:00
kolaente 3c0ea7099e fix(restore): validate database file names in zip archive
Check that database entries in the zip have a .json suffix and a
non-empty base name before slicing the extension off. This prevents
a panic from index-out-of-range when the filename is too short.
Also use TrimPrefix instead of ReplaceAll for correctness.
2026-02-25 13:01:00 +01:00
kolaente 7971500467 fix(restore): sanitize config file path to prevent zip slip
Use filepath.Base() on the config file name from the zip archive
before passing it to os.OpenFile, ensuring the config file is
always written to the current directory regardless of what path
the zip entry claims to have.
2026-02-25 13:01:00 +01:00
kolaente 12dca5f0b0 fix(restore): reject zip entries with path traversal sequences
Validate all zip entry names during restore to reject entries
containing directory traversal sequences (e.g. ../../../pwned.txt).
This prevents a Zip Slip attack where a malicious archive could
write files outside the intended extraction directory.
2026-02-25 13:01:00 +01:00
kolaente b6155d525c
feat(cli): reorganize repair commands under unified 'vikunja repair' parent (#2300)
Consolidate four scattered repair/maintenance CLI commands into a unified `vikunja repair` parent command with subcommands.
2026-02-25 11:50:09 +00:00
kolaente a5b1a90c42 refactor: remove typesense support
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
2026-02-25 12:15:28 +01:00
kolaente 71657fce30 feat: add repair-projects CLI command 2026-02-25 11:56:25 +01:00
kolaente ad307a3499 feat: add RepairOrphanedProjects function 2026-02-25 11:56:25 +01:00
kolaente 963235c0ce test: add failing tests for RepairOrphanedProjects 2026-02-25 11:56:25 +01:00
kolaente 9e050fe40e test: add orphaned project fixture for repair-projects command 2026-02-25 11:56:25 +01:00
kolaente 107a92f573 fix: commit transaction in session cleanup cron
RegisterSessionCleanupCron opens a transaction via db.NewSession() but
never calls s.Commit(). The deferred s.Close() auto-rolls-back, making
the DELETE a no-op. Add the missing commit.
2026-02-25 11:03:02 +01:00
kolaente 2f680d041c fix: address review comments on session lifecycle
- user_export.go: Remove defer s.Close() from checkExportRequest since
  it returns the session to callers. Callers now own the session
  lifecycle with their own defer s.Close(). Close session on all error
  paths within checkExportRequest.

- user_delete.go: Close the read session immediately after Find() before
  the per-user deletion loop, avoiding a long-lived transaction holding
  locks unnecessarily.

- user/delete.go: Remove double s.Close() in notifyUsersScheduledForDeletion
  by closing immediately after Find() instead of using both defer and
  explicit close.

- caldav_token.go: Return nil token on Commit() error to prevent callers
  from using an unpersisted token.
2026-02-25 11:03:02 +01:00
kolaente b3d8a56364 fix: use caller's session in LDAP syncUserGroups to avoid nested transactions
syncUserGroups created its own db.NewSession() internally while being
called from AuthenticateUserInLDAP which already has an active session
with writes. In SQLite shared-cache mode this causes a lock conflict.

Pass the caller's session through instead, and add s.Commit() before
db.AssertExists calls in LDAP tests.
2026-02-25 11:03:02 +01:00
kolaente 2f718206f9 fix: add TestMain to caldav tests and fix session conflicts
Add a proper main_test.go for the caldav test package that initializes
the logger, config, test database, and event system. Previously, these
were initialized inline in TestSubTask_Create and TestSubTask_Update
relied on running after it (fragile test ordering).

Fix session handling in TestSubTask_Update: close the read session
before calling UpdateResource (which creates its own internal session)
to avoid SQLite lock conflicts from concurrent transactions.
2026-02-25 11:03:02 +01:00
kolaente a7086e5e49 fix: prevent session leaks and visibility issues in model tests
Two categories of fixes:

1. Use defer s.Close() instead of explicit s.Close() to prevent session
   leaks when require.FailNow() triggers runtime.Goexit(), which skips
   explicit close calls but runs deferred functions. Leaked sessions
   hold SQLite write locks that block all subsequent fixture loading.

2. Add s.Commit() before db.AssertExists/db.AssertMissing calls. These
   assertion helpers query via the global engine (not the test session),
   so they cannot see uncommitted data from the session's transaction.

For block-scoped sessions (kanban_task_bucket_test.go), wrap each block
in an anonymous function so defer runs at block boundary rather than
deferring to the enclosing test function.
2026-02-25 11:03:02 +01:00
kolaente 2a10b22c5c fix: use session-aware file creation to avoid nested transactions
files.Create() and files.CreateWithMime() internally create their own
sessions and transactions. When called from within an existing
transaction (now that db.NewSession() auto-begins), this creates nested
transactions that deadlock on SQLite.

Switch to files.CreateWithSession() and files.CreateWithMimeAndSession()
to participate in the caller's existing transaction instead.
2026-02-25 11:03:02 +01:00
kolaente cbfd0e63ed fix: pass pointer to xorm Update to avoid hash panic in transaction mode
In transaction mode, xorm stores the bean argument as a map key in
afterUpdateBeans. Since Task contains slices and maps (unhashable
types), passing a Task value causes "hash of unhashable type" panic.
Passing a pointer (&ot) fixes this since pointers are always hashable.
2026-02-25 11:03:02 +01:00
kolaente 2188c7a79d fix: add missing Commit() to event listeners and cron jobs
With db.NewSession() now starting real transactions, all sessions that
do writes must explicitly commit. These listeners and cron jobs were
previously relying on auto-commit mode where each SQL statement was
committed immediately. Without explicit Commit(), the writes are
silently rolled back on Close(), and the held write locks cause
"database is locked" errors for subsequent requests on SQLite.
2026-02-25 11:03:02 +01:00
kolaente eea59c33c7 fix: isolate deletion notifications into per-user transactions
On Postgres, a failed operation puts the transaction in an error state
where subsequent operations fail. The previous loop with continue would
keep trying to use a broken transaction. Each user now gets its own
transaction so a single notification failure doesn't affect others.
2026-02-25 11:03:02 +01:00
kolaente 312648d7d6 fix: remove transaction control from File.Delete to prevent premature commit/rollback
File.Delete() had s.Commit() and s.Rollback() calls that could
prematurely commit or abort an outer transaction when using a shared
session. The caller is now responsible for transaction management.
2026-02-25 11:03:02 +01:00
kolaente 1167b08e70 fix: handle Begin() error in db.NewSession() instead of ignoring it 2026-02-25 11:03:02 +01:00
kolaente 23176bb8e1 test: add regression test for atomic parent project deletion
Verify that deleting a parent project atomically deletes all child
projects, including archived children and deeply nested hierarchies.
Also add missing defer s.Close() to existing delete test cases.
2026-02-25 11:03:02 +01:00
kolaente 49bba7f830 fix: eliminate nested database sessions to prevent table locks
Refactor functions that created their own sessions when called from
within existing transactions, which caused "database table is locked"
errors in SQLite's shared-cache mode.

Changes:
- Add files.CreateWithSession() to reuse caller's session
- Refactor DeleteBackgroundFileIfExists() to accept session parameter
- Add variadic session parameter to notifications.Notify() and
  Notifiable.ShouldNotify() interface
- Update all Notify callers (~17 sites) to pass their session through
- Use files.CreateWithSession in SaveBackgroundFile and NewAttachment
- Fix test code to commit sessions before assertions
2026-02-25 11:03:02 +01:00
kolaente a6e6f252db refactor: remove redundant Begin() calls after NewSession auto-begins
Since NewSession() now auto-begins a transaction, explicit Begin()
calls are redundant (xorm's Begin() is a no-op when already in a
transaction). Removing them reduces confusion.

Special case: user_delete.go's loop previously called Begin/Commit
per user on a shared session. Restructured to create a new session
per user deletion so each gets its own transaction.
2026-02-25 11:03:02 +01:00
kolaente 764d3569ce fix: close leaked database sessions
Add defer s.Close() to sessions that were never closed:
- auth.GetAuthFromClaims inline session
- models.deleteUsers cron function
- notifications.notify database insert
2026-02-25 11:03:02 +01:00
kolaente c9c250fb1c fix: add missing Commit() to write callers
After NewSession() auto-begins a transaction, callers that perform
writes must explicitly call Commit() for changes to persist. Without
this, writes are silently rolled back when Close() is called.

Affected callers:
- user deletion notification cron
- caldav token generation/deletion
- token cleanup cron
- mark-all-notifications-read endpoint
- saved filter view cron
- project background delete
- typesense reindex
- export cleanup cron
- task last-updated listener
- saved filter view listener
- SSO team cleanup cron
- migration status start/finish
- background set/remove handlers
- orphaned task position cleanup
- file creation
2026-02-25 11:03:02 +01:00
kolaente fd77e041a1 fix: add transaction begin to db.NewSession()
All sessions now start with an active transaction. This makes
multi-statement write operations atomic — if any step fails, all
changes are rolled back instead of leaving the database in an
inconsistent state.

Callers must call s.Commit() for writes to persist. s.Close()
auto-rollbacks uncommitted transactions.
2026-02-25 11:03:02 +01:00
Frederick [Bot] 2cb0b84602 [skip ci] Updated swagger docs 2026-02-25 09:39:04 +00:00
kolaente 2ef693a7cf test: add session lifecycle tests
Integration tests covering session creation on login, refresh token
rotation, session listing, deletion, and session invalidation on
password change.
2026-02-25 10:30:25 +01:00