Commit Graph

430 Commits

Author SHA1 Message Date
kolaente
0b08131dad style(spike): fix lint issues introduced by Huma spike
License headers on new files, gofmt on routes.go import ordering,
unused imports in test files, testifylint assert.Contains/InDelta,
and infertypeargs cleanups.
2026-04-20 11:14:29 +02:00
kolaente
abc0cdfc6a feat(auth): add GetAuthFromContext for Huma handlers 2026-04-20 10:43:15 +02:00
kolaente
1d6c1cf838 test: humaecho5 adapter roundtrip and spec serving 2026-04-20 10:41:45 +02:00
kolaente
35f9c4b684 feat: vendor humaecho adapter for echo/v5 2026-04-20 10:41:25 +02:00
kolaente
85836076be feat(migration/wekan): import attachments from board export
Parse the top-level `attachments` array in WeKan board JSON exports,
group them by card ID, base64-decode the payload, and attach the
resulting files to the generated tasks so they land in Vikunja as
task attachments. Orphaned attachments (cardId with no matching card)
are silently skipped; decode errors are logged and skipped.
2026-04-13 16:04:14 +00:00
Claude
85cfadc5b0 fix: fatal with clear message when keyvalue type is redis but redis is not enabled
Instead of panicking with a nil pointer dereference when keyvalue.type
is set to "redis" but redis.enabled is false, log a fatal error with a
clear, actionable message telling the user to enable redis.

Closes go-vikunja/vikunja#2608

https://claude.ai/code/session_01TRuPTGYDQjxqHRFWQaJGvy
2026-04-12 09:43:31 +00:00
kolaente
ced7ebd97f fix(auth): tolerate string booleans in oidc provider config (#2599)
The four boolean OIDC provider fields (emailfallback, usernamefallback,
forceuserinfo, requireavailability) were parsed with a strict .(bool)
type assertion. That works for YAML/JSON config where leaves are native
bools, but fails for every other input path: env vars always arrive as
strings, and GetConfigValueFromFile (used by the *.file Docker secret
convention) also always returns strings. The assertion would silently
zero the field for emailfallback and usernamefallback, and log an error
and zero the field for forceuserinfo and requireavailability, which is
what #2599 reports.

Extract a small parseBoolField helper that accepts both native bools and
strings (via strconv.ParseBool) and logs a parse error from each call
site. This also fixes the previously-silent drop of stringified
emailfallback / usernamefallback values — those now log an error if the
input is garbage, matching the behaviour of the other two fields.

Fixes #2599
2026-04-11 19:10:26 +00:00
kolaente
3008dc09db test(auth): cover env-var string booleans for oidc providers (#2599)
Regression test for #2599. Exercises getProviderFromMap with native
bools and with stringified booleans ("true"/"false"/"1"/"0") for all
four boolean provider fields — emailfallback, usernamefallback,
forceuserinfo, requireavailability. From env vars and from the
GetConfigValueFromFile path every leaf arrives as a string, so the
current .(bool) assertion silently zeros these fields.
2026-04-11 19:10:26 +00:00
kolaente
d58dd7a7c6 fix(auth): enforce TOTP on OIDC callback for users with 2FA enabled
The OIDC callback handler previously issued a JWT without ever
checking TOTP state. For installations with EmailFallback (or
UsernameFallback) enabled, this allowed an attacker who could
authenticate at the IdP with a matching email to log in as a local
user with TOTP enrolled, bypassing the second factor entirely.

HandleCallback now runs enforceTOTPIfRequired after resolving the
user and before any team sync writes, returning 412/1017 when the
passcode is missing or invalid. Clients resubmit the OIDC flow with
the totp_passcode field populated.

Fixes GHSA-8jvc-mcx6-r4cg
2026-04-09 17:25:47 +00:00
kolaente
c52b2a4f83 feat(auth): add enforceTOTPIfRequired helper for OIDC flow
Extracts a TOTP gate that the OIDC callback will use to enforce 2FA
for users with TOTP enabled. Mirrors the local-login TOTP flow in
pkg/routes/api/v1/login.go. Not yet wired into HandleCallback.

Refs GHSA-8jvc-mcx6-r4cg
2026-04-09 17:25:47 +00:00
kolaente
d291e3effe test(auth): add failing unit tests for OIDC TOTP enforcement
Covers the four states the OIDC TOTP gate must handle: user without
TOTP, TOTP enabled with missing passcode, invalid passcode, and
valid passcode. The helper function under test does not exist yet,
so the package currently fails to compile.

Refs GHSA-8jvc-mcx6-r4cg
2026-04-09 17:25:47 +00:00
kolaente
2b980be20d refactor(auth): add TOTPPasscode to OIDC Callback payload
Prepares the OIDC callback struct to carry a TOTP passcode so the
handler can enforce 2FA for users with TOTP enabled. No behaviour
change yet.

Refs GHSA-8jvc-mcx6-r4cg
2026-04-09 17:25:47 +00:00
kolaente
8db4ba8a26 test(todoist): serve attachment from local test server
The test previously fetched the attachment from https://vikunja.io/testimage.jpg,
which caused flaky failures in CI when the external host was unreachable
(context deadline exceeded). Serve the local testimage.jpg via httptest and
temporarily allow non-routable IPs for the SSRF-safe client so the test is
hermetic and deterministic.
2026-04-09 16:22:56 +00:00
kolaente
33389bb0b3 test(migration): regression test for forged attachment size
Builds an in-memory export zip with a 2 MB payload and a data.json
that claims size: 0, then asserts neither the honest 2 MB row nor
the forged 0-size row ends up in the files table. Covers
GHSA-qh78-rvg3-cv54.
2026-04-09 16:22:56 +00:00
kolaente
abfbcb4cf3 fix(migration): bound per-entry zip cap by configured files.maxsize
The hard-coded 500 MB per-entry cap meant operators who set a tighter
files.maxsize could not actually enforce it on imports. Derive the cap
from files.maxsize with a floor so data.json / filters.json / VERSION
entries can still be read when the configured limit is tiny.

Clamp the uint64->int64 conversion and the LimitReader cap so absurd
configuration values do not overflow into MinInt64 and cause
io.LimitReader to treat every entry as EOF.
2026-04-09 16:22:56 +00:00
kolaente
db7f1445a8 fix(migration): compute attachment size from content during import
Import metadata is attacker-controlled and can forge a small size to
bypass the attachment size limit (GHSA-qh78-rvg3-cv54). Compute the
size from the decoded content instead of trusting a.File.Size.
2026-04-09 16:22:56 +00:00
kolaente
667f229d8c refactor(files): derive attachment size from content in sibling callers
Task/project duplication and the Todoist migration were passing stored
or API-reported sizes into NewAttachment. Derive the size from the
actual buffered content so every caller matches the hardened boundary
behaviour (GHSA-qh78-rvg3-cv54 defence-in-depth).
2026-04-09 16:22:56 +00:00
kolaente
0f3730d045 fix(notifications): escape markdown in user-controlled strings in email lines
Task titles, project titles, team names, doer/assignee names, and API
token titles were interpolated raw into Line(...) calls whose content is
rendered to HTML by goldmark and then sanitized with bluemonday UGCPolicy.
UGCPolicy intentionally allows safe <a href> and <img src> with
http/https URLs, so a title containing Markdown link or image syntax
would survive sanitization as a working phishing link or tracking pixel
in a legitimate Vikunja email.

Introduce notifications.EscapeMarkdown, which prefixes every CommonMark
§2.4 backslash-escapable ASCII punctuation character — including '<' so
autolinks like `<https://evil.com>` are neutralized before reaching
goldmark — with a backslash. Apply it to every user-controlled argument
of every Line(...) call in pkg/models that feeds into an i18n template,
and to the hand-built "* [title](url) (project)" Markdown link in the
overdue-tasks digest notification.

Also escape the migration error string in MigrationFailedNotification,
an additional sink not listed in the advisory (error messages can carry
user-controlled content from the external migration source).

Subject(...), Greeting(...), and CreateConversationalHeader(...) are
left unchanged: Subject is passed directly to the mail library and is
not markdown-rendered, Greeting is rendered via html/template's built-in
HTML escaping without markdown, and the conversational header is
sanitized as raw HTML by bluemonday in mail_render.go.

Fixes GHSA-45q4-x4r9-8fqj.
2026-04-09 15:44:04 +00:00
kolaente
e025209e3c fix(security): validate link share JWTs against DB on every request
Previously GetLinkShareFromClaims built a *LinkSharing entirely from JWT
claims with no DB interaction, so deleted shares and permission downgrades
took up to 72h (the JWT TTL) to take effect. The permission and sharedByID
claims were trusted blindly.

GetLinkShareFromClaims now takes an *xorm.Session, looks up the share via
GetLinkShareByID, verifies the hash claim against the DB row, and returns
ErrLinkShareTokenInvalid when the row is missing or the hash mismatches.
The permission and sharedByID claims are discarded; the DB row is
authoritative. GetAuthFromClaims opens a read session for the link-share
branch, mirroring the existing API-token branch.

Token creation and the JWT format are unchanged, so already-issued tokens
keep working except when the underlying share has been deleted or its hash
no longer matches.

Fixes GHSA-96q5-xm3p-7m84 / CVE-2026-35594.
2026-04-09 15:38:07 +00:00
Rhys McNeill
699c766049 fix: add timeouts to Gravatar, Unsplash, and SSRF-safe HTTP clients 2026-04-09 07:31:08 +00:00
kolaente
119d7df796 fix: use assert.Empty instead of assert.Equal for empty string check 2026-04-08 09:49:14 +00:00
kolaente
a5fb01cc3d fix: reset SSO avatar provider to default when picture claim is removed 2026-04-08 09:49:14 +00:00
kolaente
1065bdd84c test: add tests for SSO avatar provider reset on empty picture URL 2026-04-08 09:49:14 +00:00
kolaente
d5051c97e4 fix(background): use targeted column update when removing background
Fixes #2552. RemoveProjectBackground was passing a minimal Project struct
(only ID set) through UpdateProject, which always includes 'title' in its
Cols() list. This caused XORM to write the zero-value empty title to the
DB, wiping the real project title. Now uses ClearProjectBackground which
only updates background_file_id and background_blur_hash.
2026-04-08 09:07:15 +00:00
kolaente
c166eff95f test: remove obsolete invalid-cache-type test for avatar upload
RememberValue[T] always gob-decodes to the correct type, so the
corrupted-cache recovery path no longer exists.
2026-04-08 08:56:22 +00:00
kolaente
59b047f76a fix: register gob types and use RememberValue for avatar and unsplash cache
Register CachedAvatar and Photo with encoding/gob so Redis can properly
deserialize them. Migrate both to use RememberValue[T] which calls
GetWithValue() internally, fixing the broken type assertion when Redis
is the keyvalue backend.

Also removes the recursion-depth fallback in upload.go since
RememberValue eliminates the type mismatch failure mode entirely.
2026-04-08 08:56:22 +00:00
kolaente
e2de681b71 feat: add generic RememberValue[T] for type-safe keyvalue caching
RememberValue uses GetWithValue() internally for proper gob-decoding,
which is required when Redis is used as the keyvalue backend.
2026-04-08 08:56:22 +00:00
kolaente
bc0bb556ad feat(migration): flatten project hierarchy for single-project imports 2026-04-07 15:20:06 +00:00
kolaente
3437f98dc3 feat(migration): add skip rows option to CSV import
Allow users to skip the first N data rows when importing CSV files.
This is useful when the CSV contains metadata rows before the actual
task data begins. Adds skip_rows to ImportConfig (backend) and a
number input in the parsing options UI (frontend).
2026-04-07 15:20:06 +00:00
Claude
f555762def feat(migration): add generic CSV import with column mapping
Add a new CSV migration module that allows users to import tasks from
any CSV file with custom column mapping and parsing options.

Backend changes:
- New CSV migrator module with detection, preview, and import endpoints
- Auto-detection of delimiter, quote character, and date format
- Suggested column mappings based on column name patterns
- Transactional import using InsertFromStructure

Frontend changes:
- New CSV migration UI with two-step flow (upload -> mapping -> import)
- Column mapping selectors for all task attributes
- Live preview showing first 5 tasks with current mapping
- Parsing option controls for delimiter and date format

The CSV migrator creates a parent "Imported from CSV" project with
child projects based on the project column if provided, or a default
"Tasks" project for tasks without a specified project.
2026-04-07 15:20:06 +00:00
kolaente
e40877cca1 fix(migration): delete all default buckets when migration provides its own
Previously only the "To-Do" default bucket was deleted, leaving "Doing"
and "Done" as duplicates alongside migration-provided buckets. Now all
default-created buckets are removed when migration data already provides
bucket assignments for all tasks.
2026-04-07 12:05:47 +00:00
kolaente
ccf1468884 fix(migration): correct TickTick swagger annotation to PUT 2026-04-07 12:05:47 +00:00
kolaente
56ce73738d test(migration): add WeKan migration tests and fixture
Add comprehensive tests for the WeKan conversion function including
edge cases (empty board, orphan cards, color mapping, multiple
checklists, unsupported fields) and a realistic JSON fixture file.
2026-04-07 12:05:47 +00:00
kolaente
4cc7715951 feat(migration): add WeKan board JSON import
Add a file-based migration importer that reads WeKan board JSON exports
and creates Vikunja projects with kanban buckets, tasks, labels,
checklists, and comments.

WeKan lists become kanban buckets. Checklists are converted to HTML
task lists in the description. Card descriptions and comments are
converted from markdown to HTML using goldmark. Label colors are
mapped from WeKan's CSS color names to their actual hex values.
2026-04-07 12:05:47 +00:00
kolaente
55ea5bd966 refactor(auth): extract shared token validation into auth package
Move JWT parsing (GetUserIDFromToken) and API token validation
(ValidateAPITokenString) into pkg/modules/auth so both HTTP middleware
and WebSocket auth use the same logic. This ensures consistent token
validity checks including expiry and user status (disabled/locked).

The HTTP API token middleware now delegates to the shared function,
removing duplicated lookup/expiry logic.
2026-04-02 16:30:23 +00:00
Claude
fc9c21915d feat(auth): enforce OpenID Connect issuer uniqueness across providers
Detect when two configured OIDC providers resolve to the same issuer URL
at startup and halt with a fatal error, preventing team sync data
corruption caused by ambiguous (external_id, issuer) matching.

Also adds duplicate issuer detection to the doctor service diagnostics
and comprehensive tests with mock OIDC discovery servers.
2026-03-30 22:41:50 +00:00
kolaente
83bac15841 feat: rename ServiceJWTSecret to ServiceSecret with deprecation (#2502) 2026-03-30 12:07:01 +02:00
kolaente
649043aceb test: add tests for OAuth 2.0 authorization flow
Add web tests covering the authorize endpoint, token exchange, PKCE
verification, single-use codes, and refresh token rotation. Add unit
tests for redirect URI validation and PKCE. Add E2E test for the full
browser-based authorization code flow with login redirect.

Extract setupApiUrl helper for E2E tests to avoid duplication.
2026-03-27 23:05:04 +00:00
kolaente
7827ff64b9 feat: add OAuth 2.0 token endpoint
Add POST /api/v1/oauth/token supporting authorization_code and
refresh_token grant types. Validates PKCE, exchanges codes for
JWT access tokens with refresh token rotation. Uses the shared
RefreshSession helper for the refresh grant.
2026-03-27 23:05:04 +00:00
kolaente
8b379b7466 feat: add OAuth 2.0 authorize endpoint
Add POST /api/v1/oauth/authorize behind auth middleware. Validates
OAuth parameters (response_type, redirect_uri, PKCE), fetches the
authenticated user, creates an authorization code, and returns it
as JSON for the frontend to handle the redirect.
2026-03-27 23:05:04 +00:00
kolaente
a6e7475153 feat: add OAuth client validation and PKCE verification
Add redirect URI validation that allowlists vikunja-* custom protocol
schemes, rejecting http/https and dangerous schemes like javascript:.
Add PKCE S256 verification following RFC 7636.
2026-03-27 23:05:04 +00:00
kolaente
7a258f67c7 refactor: extract shared RefreshSession helper
The cookie-based /user/token/refresh handler had session refresh logic
(lookup, expiry check, token rotation, user fetch, JWT generation)
that will be reused by the OAuth token endpoint. Extract it into
auth.RefreshSession() and rewrite RefreshToken to use it.
2026-03-27 23:05:04 +00:00
kolaente
112e486314 test: add test for deeply nested TickTick task ordering 2026-03-26 15:08:12 +00:00
kolaente
9b1c52e9e3 fix: sort TickTick tasks so parents come before children
TickTick CSV exports don't guarantee parent tasks appear before their
subtasks. When a child row came first, the shared migration pipeline
tried to create a title-less placeholder for the missing parent, which
failed with 'Task title cannot be empty'.

Resolves go-vikunja/vikunja#2487
2026-03-26 15:08:12 +00:00
kolaente
c49636430f test: add failing test for TickTick child-before-parent CSV order 2026-03-26 15:08:12 +00:00
Claude
121fd3c9f1 feat: use openid provider name instead of generic "OIDC" in synced team names
Teams synced from OpenID Connect providers were always named with "(OIDC)"
suffix (e.g., "DevTeam (OIDC)"). This changes it to use the configured
provider name instead (e.g., "DevTeam (Keycloak)"), making it easier to
identify which provider a team came from when multiple OIDC providers are
configured. Existing team names will be updated automatically on next user
login.

https://claude.ai/code/session_012LXXPvYe6i27WTcha1PL7A
2026-03-24 12:30:06 +00:00
kolaente
fd452b9cb6 fix(auth): skip profile updates for disabled LDAP users
When a disabled/locked LDAP user authenticates, return early from
getOrCreateLdapUser without updating their profile info or syncing
avatar. The login handler already rejects them, but this avoids
unnecessary database writes.

Ref: GHSA-94xm-jj8x-3cr4
2026-03-23 16:37:26 +00:00
kolaente
cc22acdf3e chore(lint): suppress gosec false positives on SSRF-safe HTTP client calls 2026-03-23 16:34:22 +00:00
kolaente
a94109e1be fix: prevent SSRF via Unsplash background image download 2026-03-23 16:34:22 +00:00
kolaente
73edbb6d46 fix: prevent SSRF via Microsoft Todo migration pagination links 2026-03-23 16:34:22 +00:00