On OIDC logout Vikunja redirected to the configured `logouturl` with no query
parameters, so it never sent `id_token_hint` or `post_logout_redirect_uri`.
RP-Initiated-Logout-compliant providers (e.g. PocketID) then ignored the
post-logout redirect and left the user on the IdP's own login page.
This builds the end-session URL server-side from the OpenID Connect
RP-Initiated Logout 1.0 spec:
- id_token_hint (§2, RECOMMENDED): the ID token previously issued to the
session. It lets the OP skip the logout-confirmation prompt and is what makes
the OP honor post_logout_redirect_uri (the OP MAY require it, §3).
- post_logout_redirect_uri (§2, OPTIONAL): where the OP redirects after logout.
MUST be pre-registered with the OP. Defaults to service.publicurl so the user
lands back on Vikunja.
- client_id (§2, OPTIONAL): the RP client id; the OP verifies it matches the
id_token_hint.
The end_session_endpoint is discovered from the provider's discovery document
(§2.1, REQUIRED metadata) and falls back to the static `logouturl` config when
the provider does not publish one.
To replay id_token_hint, the raw ID token (and the provider key) are persisted
on the session at the OIDC callback (new migration adds oidc_id_token /
oidc_provider_key columns to the sessions table). At logout the server reads
them, builds the URL, deletes the session, and returns the URL in the logout
response so the frontend redirects to it.
Security note: the raw ID token is stored at rest in the sessions table
(json:"-", never exposed over the API) and removed when the session is deleted
on logout.
Spec: OpenID Connect RP-Initiated Logout 1.0
https://openid.net/specs/openid-connect-rpinitiated-1_0.htmlFixes#2820
A pair returned by the GroupBy was just reported as duplicated, so a row
must exist. Continuing on !has would let the delete loop drop every row
for that pair without re-inserting one, silently losing positions. Abort
the migration instead.
A task could end up with more than one task_positions row for the same
(task_id, project_view_id): rapid/concurrent creation raced the
check-then-insert paths, and the create path could insert a position that
a triggered RecalculateTaskPositions had already persisted for the new
task. The table had no unique constraint, so the duplicates were stored
silently (#2844).
In the table view this made the LEFT JOIN on task_positions emit the task
twice; getTasksForProjects enriched only the map entry, so the duplicate
slice row kept an empty identifier and rendered as "#N" instead of
"PREFIX-N" (#2725).
- Add a unique index on task_positions(task_id, project_view_id) via a
dedup migration (mirrors the task_buckets fix in 20250624092830) plus the
unique(task_view) struct tag so fresh installs get it too.
- Harden the create path: only queue a position insert when one does not
already exist for the task+view, and dedupe within the batch.
- Dedupe the task slice returned by getTasksForProjects by id, returning
the enriched entry, so duplicate position rows can never surface a task
twice or with a missing identifier.
Fixes#2844Fixes#2725
Implement the license validation system with:
- Server communication with retry logic and exponential backoff
- In-memory state management for feature flags and user limits
- Cached validation with 72h expiry stored in database
- Background goroutine with adaptive check intervals (24h/1h)
- Graceful degradation to community mode on failure
- Instance ID generation and persistence
Add the OAuthCode model for storing short-lived authorization codes
with PKCE challenges. Codes are hashed (SHA-256) before storage and
are single-use with a 10-minute expiry. Add the database migration
and OAuth-specific error types.
MySQL does not support CREATE INDEX IF NOT EXISTS syntax. Switch on
database type to use IF NOT EXISTS for Postgres/SQLite and plain
CREATE INDEX with duplicate key error suppression for MySQL.
Fixes#2431
Add user_id column to webhooks table (nullable, for user-level webhooks
vs project-level). Extend webhook model, permissions, and listener to
support user-level webhooks that fire for user-directed events like
task reminders and overdue task notifications.
Add TasksOverdueEvent for dispatching overdue notifications via webhooks.
Update webhook permissions to handle both user-level and project-level
ownership. Add webhook test fixture and register webhooks table in test
fixture loader.
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 the `sessions` table for storing server-side session records.
Each row tracks a session ID (UUID), user ID, hashed refresh token,
device info, IP address, and timestamps.
Avoid using the generic renameColumn helper for this migration on MySQL because it renames columns as BIGINT. Handle the teams oidc_id -> external_id rename with a MySQL-specific CHANGE statement that keeps VARCHAR(250) and remains idempotent.
The new catchup migration compared the postgres JSON column with string literals using !=, which fails in migration smoke tests. Use a PostgreSQL-specific WHERE clause with ::text and cover it with unit tests.
Convert bucket_configuration filters regardless of bucket_configuration_mode and catch remaining view-level filter values still stored in the legacy string format. Includes focused tests for the bucket conversion helper logic.
Fixes#2172Fixes#2188Fixes#2202
Use the shared renameColumn helper for non-SQLite databases and skip the SQLite table rebuild when oidc_id is already absent. This prevents failures after partial upgrades where the column was already renamed.
Refs #2172
Refs #2285
tx.Sync() fails on PostgreSQL because it tries to reconcile primary key constraints/indexes, causing index-drop errors. Use explicit ALTER TABLE ADD COLUMN with existence checks instead.
Fixes#2215
This change modifies the migration `20251001113831` to flexibly parse bucket configuration filters.
This fixes this migration issue:
```
json: cannot unmarshal object into Go struct field bucketConfigurationCatchup.filter of type string
```
This occurred when a single `bucket_configuration` JSON array contained
mixed formats - some buckets with old string filters and some with
already-converted object filters.
This fixes a bug where old filters where not converted to the new json format correctly, leading to issues when trying to decode the raw filter string as json.
It is unclear whether that old migration had a bug or was not executed at all.
This change adds a new migration to fix all filters in views still stuck in the old filter string format.
Resolves https://github.com/go-vikunja/vikunja/issues/420