Ignoring the removed keys would leave the proxy rejecting every request
with a 407, so startup fails with a hint to move the credentials into
outgoingrequests.proxyurl.
BREAKING CHANGE: outgoingrequests.proxypassword and
webhooks.proxypassword were removed. Vikunja refuses to start when
either is set.
BREAKING CHANGE: outgoingrequests.proxypassword is no longer used. Put
the credentials into outgoingrequests.proxyurl instead, for example
http://vikunja:password@mole:8080.
The proxy dial exemption was keyed on addresses learned lazily from earlier
proxied requests, so a direct request (loopback, NO_PROXY or an unproxied
scheme) or a redirect to the proxy's host:port was dialed unguarded. Derive
the exempt addresses once from the configured or environment proxy URLs and
reject direct requests to them with ssrf.ErrProhibitedIP.
Address pr-swarm finding: SSRF guard bypass via the proxy address
outgoingrequests.proxyurl was silently ignored unless proxypassword was
set too, and the username was hardcoded to vikunja. Credentials in the
proxy url are used now, with proxypassword as the mole-compatible
fallback. Plain http requests through the proxy now authenticate too,
not only CONNECT tunnels. An unparseable proxy url fails requests
instead of connecting directly.
NewHTTPClient carries the proxy and timeout settings without the SSRF
guard, for admin-configured endpoints like OIDC providers that often
live on a private network. NewSSRFSafeHTTPClient builds on it.
Outgoing proxies usually live on a private network, so the SSRF guard
refused to connect to them unless allownonroutableips was enabled,
which disabled the guard for direct connections too. Only the dial to
the configured proxy skips the guard now; proxied targets are resolved
by the proxy.
The SSRF-safe client used a bare http.Transport, so HTTP_PROXY,
HTTPS_PROXY and NO_PROXY were ignored. Clone the default transport and
read the proxy from the environment when outgoingrequests.proxyurl is
not set.
Both `usernamefallback` + `emailfallback` on: lookup required username
AND email match, so local user with different username never linked,
duplicate account created. Now verified-email-only lookup always tried
last.
Fixes#2022
## How to verify
1. Create a local user `localuser` with email `me@example.com`.
2. Configure an OIDC provider with `usernamefallback: true` and
`emailfallback: true`. In the IdP, create a user whose email is
`me@example.com` (sent with `email_verified: true`) and whose
username/sub is different from `localuser`.
3. Log in to Vikunja through that OIDC provider.
4. **Expected:** You are logged in as `localuser`, and `vikunja user
list` shows no new user for the provider's issuer.
**Before this PR:** a second user with a random or IdP-provided username
was created.
---------
Co-authored-by: kolaente <k@knt.li>
Vue Test Utils still calls an async component's loader when stubbing it, so
TipTap kept importing in the background and could outlive the test
environment, failing CI with EnvironmentTeardownError.
Quick add "call mom every day" created a repeating task with no due date,
so it never showed up in Upcoming, never went overdue and reminders never
fired. Default to today at the user's default due time.
The spec cleared the guest's token by hand while that page's boot-time token refresh was still in flight. When the refresh resolved afterwards it persisted a fresh token, so the invite page loaded as the logged-in guest and redirected home instead of showing the invalid-link message.
Applied using this Python script:
from pathlib import Path
paths=list(Path('frontend/src/components/time-tracking').glob('*.vue'))+[Path('frontend/src/views/time-tracking/TimeTracking.vue'),Path('frontend/src/helpers/time/smartFillStart.ts')]
for p in paths:
s=p.read_text().replace("import type {ITimeEntry} from '@/modelTypes/ITimeEntry'", "import type {TimeEntry as ITimeEntry} from '@/client/generated'")
for old,new in [('startTime','start_time'),('endTime','end_time'),('userId','user_id'),('taskId','task_id'),('projectId','project_id')]:
s=s.replace('.'+old,'.'+new)
# Component props and unrelated task/project UI contracts retain their names.
s=s.replace('props.task_id','props.taskId')
p.write_text(s)