The view sets the message before populateMessageBody() fills its
attachments, so attachments() is still empty when the plugin looks for
the calendar part, and it never looked again: the event panel did not
show up for most invitations. Subscribe to msg.attachments until the
part is found or another message is shown, and drop a late result that
belongs to a message no longer displayed.
With 2-step verification enabled, these plugin actions needed nothing but a
session (and the CSRF token that comes with it):
- EnableTwoFactor with Enable=0 switched it off,
- ClearTwoFactorInfo deleted it,
- CreateTwoFactorSecret overwrote the secret with Enable false, i.e. also
switched it off,
- ShowTwoFactorSecret returned the secret and QR code.
A stolen cookie or an unlocked machine was enough to drop or copy the second
factor. While 2FA is enabled, all four now require a current TOTP or backup
code, checked as at login; a backup code used here is spent. Enabling and the
first setup are unchanged. Backup codes come from random_int() instead of rand().
The settings page asks for the code (the existing "Verification Code" label)
before those actions and sends it. A refused change reloads the real state;
before, any error made the page show 2FA as not configured. A cancelled or
refused "off" puts the checkbox back. TwoFactorAuthSettings.min.js, which is
what is served, is now the same file as the source rather than a separate
hand-minified copy.
test/two-factor-reauth.php drives the real gate with storage and provider
stubbed: open while not enabled; refused with no, a wrong or an unknown backup
code while enabled; allowed with a valid TOTP or backup code, which is spent.
A directory rarely keeps everything worth suggesting in one branch: people
sit in a people branch, meeting rooms and other bookable resources in a
separate one. Searching a single subtree misses them, and widening the base
to the domain root drags every service and role account into the list.
base_dn now accepts several branches separated by '|', which cannot appear
unescaped in a DN — an existing single-branch value parses to a one-element
list and behaves exactly as before. A branch that fails to answer is logged
with its own DN and no longer discards the whole lookup.
The panel never appears on a real invitation.
msg.attachments is a ko.observableArray, so msg.attachments.find() is
undefined and throws inside the message subscription - the rest of the
handler, including the JSON-LD path, dies with it. The app itself always
writes attachments().
A scheduling mail also does not necessarily carry a text/calendar
attachment. Evolution and Exchange put the scheduling part inline in
multipart/alternative as text/calendar and repeat it as an
application/ics attachment, so such a message shows two .ics files and
the current test matches neither reliably. Match text/calendar,
application/ics, text/x-vcalendar or an .ics file name - which is what
the commented-out line above was reaching for.
The "Check Proxy" option inspected the forwarded client IP returned
by Http::GetClientIp(true), which resolves to HTTP_X_FORWARDED_FOR
first and only falls back to REMOTE_ADDR. In any typical reverse-proxy
deployment that header carries the end-user's IP, not the proxy's, so
the option never actually validated that the request came from the
trusted proxy. It only constrained the set of end-user IPs allowed
to use SSO, which is not a meaningful guarantee on its own.
Drop the option, the paired proxy_ip subnet field, and the
ip_in_range helper. Document that access must be gated at the
network layer (firewall / docker network / bound interface) and
that the upstream proxy must strip any client-supplied value of
the configured remote-user header.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add README note explaining that AUTHENTICATIONFAILED after a
container rebuild or data reset is typically caused by an
APP_SALT change invalidating the encrypted master credentials,
and that re-entering them in the admin UI restores access.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The proxy IP check was never enforced: ServiceProxyAuth read config
key 'proxy_check' but the property is registered as 'check_proxy',
so the lookup always returned the default empty string and the
else-branch set $sProxyRequest = true unconditionally.
The BOOL property also had SetEncrypted(), which caused getDecrypted()
to call DecryptFromJSON() on a literal bool and fail with
"DecryptFromJSON() invalid $data", so the admin UI never reflected
the saved value. BOOLs are stored as plain bools in JSON; switch to
Get() and drop SetEncrypted() on the property.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>