Commit Graph

106 Commits

Author SHA1 Message Date
Tink 81791fd346
fix(auth): link OIDC username fallback on preferred_username, not just sub (#2945) 2026-06-19 20:47:05 +02:00
Tink 7208694960
fix(auth): build OIDC end-session URL with RP-Initiated Logout params (#2943) 2026-06-19 18:27:33 +02:00
kolaente 78f79accb5 refactor(auth): extract transport-agnostic login, logout and OIDC cores
Pull the credential/TOTP check, session deletion, user-token issuance and
OIDC callback flow out of the v1 echo handlers and into reusable helpers so
both /api/v1 and the upcoming /api/v2 share one implementation:

- auth.IssueUserToken + auth.WriteUserAuthCookies split the token/cookie
  machinery from the echo response; NewUserAuthTokenResponse now wraps them.
- auth.SessionIDFromContext reads the sid claim for logout.
- shared.AuthenticateUserCredentials, shared.DeleteSession hold the login
  and logout cores.
- openid.AuthenticateCallback holds the OIDC exchange/getOrCreate/TOTP/team
  sync, returning the user; HandleCallback issues the token as before.

v1 behaviour is unchanged on the wire.
2026-06-17 19:43:41 +00:00
kolaente b86710903b fix: dispatch pending events after user creation commits
The register handler, local/LDAP login and the OIDC callback all queue
the user.created event via DispatchOnCommit but never called
DispatchPending, so the event was silently dropped and its queue entry
leaked. Flush after commit and discard on rollback.
2026-06-12 08:56:08 +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 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
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
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
MidoriKurage 68a74416a4 fix(openid): Merge VikunjaGroups and ExtraSettingsLinks from userinfo
When `forceuserinfo: true`, `mergeClaims` discards `vikunja_groups`
and `extra_settings_links` claims fetched from the userinfo endpoint,
failing team sync for opaque tokens.

Fixes team sync for OIDC providers using opaque tokens.
2026-03-23 16:11:17 +00:00
kolaente a7a8ae072a fix(auth): return correct error type for locked users in OIDC callback
Return ErrAccountLocked for locked users instead of ErrAccountDisabled.
Also skip profile updates and avatar sync for disabled/locked users
found during OIDC login — HandleCallback rejects the auth anyway.
2026-03-23 12:06:16 +00:00
kolaente 8409bdb120 refactor(user): export IsErrUserStatusError for use across packages
Make isErrUserStatusError public and replace all verbose
!IsErrAccountDisabled(err) && !IsErrAccountLocked(err) checks
with the shorter IsErrUserStatusError(err) call.
2026-03-23 12:06:16 +00:00
kolaente 22a4b6fbb8 fix(auth): reject disabled/locked users in OIDC callback 2026-03-23 12:06:16 +00:00
kolaente ea4ba18def fix(user): handle status errors across the codebase, remove redundant checks 2026-03-23 12:06:16 +00: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 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
renovate[bot] 9a61453e86
fix(deps): update module github.com/labstack/echo/v4 to v5 (#2131)
Closes https://github.com/go-vikunja/vikunja/pull/2133

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: kolaente <k@knt.li>
2026-01-24 20:38:32 +01:00
kolaente 4df8da549e fix(auth): scope query binding
Resolves https://github.com/go-vikunja/vikunja/issues/2146
2026-01-24 17:51:35 +01:00
kolaente 39b4568bc5
refactor: centralize HTTP error handling (#2062)
This changes the error handling to a centralized HTTP error handler in `pkg/routes/error_handler.go` that converts all error types to proper HTTP responses. This simplifies the overall error handling because http handler now only need to return the error instead of calling HandleHTTPError as previously.
It also removes the duplication between handling errors with and without Sentry.

🐰 Hop along, dear errors, no more wrapping today!
We've centralized handlers in a shiny new way,
From scattered to unified, the code flows so clean,
ValidationHTTPError marshals JSON supreme!
Direct propagation hops forward with glee,
A refactor so grand—what a sight to see! 🎉
2026-01-08 10:02:59 +00:00
kolaente c6fe4c1a6e fix(auth): retry up to three times when an auth provider cannot be reached
Resolves https://github.com/go-vikunja/vikunja/issues/2050
2026-01-05 21:50:40 +01:00
kolaente 541a38456e
chore(deps): update golangci-lint to 2.6.0 (#1737) 2025-10-31 17:28:52 +00:00
Copilot c7a26d81fe
fix(auth): do not panic with invalid openid provider configuration (#1354) 2025-08-31 07:17:50 +00:00
Copilot 5ca637a7e6
feat(auth): add oauth require availability configuration on startup (#1358) 2025-08-30 22:15:20 +00:00
kolaente da0f6fb366 feat(auth): allow passing custom settings links to user account via openid claims 2025-08-03 13:25:32 +02:00
kolaente de917467cb
fix(openid): manually fetch providers
Partially reverts fcdcdcf46a
Resolves https://github.com/go-vikunja/vikunja/issues/1165
2025-07-28 11:40:09 +02:00
kolaente bbd3567e43
chore: add debug logging around provider failure
https://github.com/go-vikunja/vikunja/issues/1165
2025-07-24 16:00:03 +02:00
kolaente 7243a10fb2
fix(openid): check different provider types
Related to https://github.com/go-vikunja/vikunja/issues/1165
2025-07-23 15:40:51 +02:00
kolaente 2b497e6265
fix: pass pointer when fetching provider
Resolves https://github.com/go-vikunja/vikunja/issues/1165
2025-07-23 11:09:09 +02:00
kolaente ca83ad1f98 feat: move to slog for logging 2025-07-21 18:15:39 +02:00
kolaente 566657c54a fix: correctly return cached provider 2025-07-18 18:38:12 +02:00
kolaente fcdcdcf46a feat: use keyvalue.Remember where it makes sense 2025-07-17 16:19:13 +02:00
kolaente 0ecbd9e1a3
feat(user): add avatar cache flushing (#1041) 2025-06-27 14:01:43 +02:00
Weijie Zhao a214d68a44
feat(auth): sync avatar from OpenID providers (#821) 2025-06-16 15:59:31 +02:00
Dominik Pschenitschni 296577a875
fix: correct license header references (#882)
See originals:
- https://www.gnu.org/licenses/agpl-3.0.txt
- https://www.gnu.org/licenses/gpl-3.0.txt
2025-06-10 12:18:38 +02:00
Weijie Zhao 00c4148f05
feat(auth): add ForceUserInfo option to OpenID provider (#797)
Problem:

When using Casdoor as an OpenID provider, there's an inconsistency between the user information in the JWT token and the UserInfo endpoint. The token contains the user's unique ID in the `name` field, while the UserInfo endpoint correctly returns the user's display name.

Solution:

This PR adds a new `ForceUserInfo` option to the OpenID provider configuration. When enabled, it forces the use of the UserInfo endpoint to retrieve user information instead of relying on claims from the ID token.

Impact:

- Default behavior remains unchanged (backward compatible)
- New option allows administrators to force using UserInfo endpoint data
- Particularly useful for providers like Casdoor that don't fully comply with OIDC standards

Related:

I've opened an issue in the Casdoor repository (https://github.com/casdoor/casdoor/issues/3806) to discuss the root cause. However, changing Casdoor's token structure might cause significant compatibility issues for existing integrations, so it's unclear if this can be fixed at the provider level. This PR provides a workaround in Vikunja that doesn't affect existing functionality.
2025-05-20 08:06:34 +00:00
kolaente 6847f44058
chore(openid): add more debug logging when retrieving token 2025-03-29 19:24:07 +01:00
kolaente 4ea3c01b5f
chore: add more debug logging when returning error 2025-03-29 18:21:34 +01:00
kolaente 99213c66ee chore(openid): use general external team sync 2025-03-18 16:36:00 +00:00
kolaente 9f5c761fd9 chore(auth): rename error 2025-03-18 16:36:00 +00:00
kolaente 06851ca639 chore(auth): rename external team id find methods 2025-03-18 16:36:00 +00:00
kolaente 62beb3db2d feat(auth): rename oidc_id to external_id 2025-03-18 16:36:00 +00:00
kolaente 12aba8e9b1
chore(openid): move openid team struct to openid package 2025-03-17 17:34:49 +01:00
kolaente 87cfe89441
feat(ldap): add tests 2025-03-16 18:23:55 +01:00
Marc f4a0c0ef31 feat(auth): sso fallback mapping (#3068)
Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/3068
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Marc <marc88@free.fr>
Co-committed-by: Marc <marc88@free.fr>
2025-03-02 15:21:09 +00:00