Commit Graph
12 Commits
Author SHA1 Message Date
stefan.tollkuehn 9446f128c9 Rate limit contact search, skip short queries
The contact-search endpoint had no request-rate limit and ran
an address-book search even for an empty or single-character
query, the most expensive case. Authenticated users could
trigger repeated scans with simple scripted requests.

Apply the framework's per-user UserRateLimit attribute (120
requests per minute) and return an empty result without
touching the address book for fragments under two characters.
The unfiltered datalist on the confirm page is unchanged.
2026-10-26 13:43:58 +01:00
stefan.tollkuehn 2b3e96e50a Reject normalized datetime-local inputs
createFromFormat() only returns false on hard parse failures.
Impossible dates such as 2026-02-31 were silently normalized
into March, so a confirmed event could land on a different day
than the booking link supplied. Non-padded values like
2026-2-3T10:00 were also accepted and reformatted.

Parse with a leading "!" so no current-date fields leak in,
require zero warnings and errors from getLastErrors(), and
round-trip the value through the format. Impossible dates,
out-of-range components and non-padded values now throw in
both the confirmation and the creation stage.
2026-10-26 13:39:07 +01:00
stefan.tollkuehn a9b7fd11af Announce the suggestion list to screen readers
The invitee input had no combobox semantics: assistive
technology could not tell that the widget owns a listbox with
suggestions. Mark the input as role="combobox" with
aria-autocomplete="list" and an initial aria-expanded="false",
point aria-controls at the list and give the JS-built list a
matching id so the reference resolves.
2026-10-23 14:08:59 +02:00
stefan.tollkuehn 5bba3a93cd Align invitee separators with the server
The server splits the attendee input on whitespace, commas and
semicolons, but the search widget only treated commas and
semicolons as separators. Typing "a@x.com b" therefore sent the
whole string as the search query instead of "b", and picking a
contact dropped the whitespace-separated entry.

Split on /[\s;,]+/ in both fragment() and commit() so the
widget agrees with parseAttendees() on what an entry is.
2026-10-23 14:07:28 +02:00
stefan.tollkuehn e5432a0a38 Fix stale contact suggestions reopening
hide() did not invalidate in-flight search requests. Clearing
the input or picking a contact while a request was pending let
the old response re-render and pop the suggestion list back
open. Bump requestSeq in hide() so every dismissal retires
pending responses.

Add a Vitest + jsdom harness with black-box tests for the
invitee widget, covering the stale-response race both on
field clear and on contact pick.
2026-10-23 14:04:32 +02:00
stefan.tollkuehn 6312c18a91 Harden invitee input and contact names
Security audit found two gaps. Sabre only quotes ICS
parameter values containing space, newline, quote, colon,
semicolon, caret, comma or plus, so a bare carriage
return in a contact name would be written into the
serialized event unquoted and break the line structure;
names with control characters are now dropped. The raw
attendee input and the search query had no length
bound, a huge POST field was fully split, iterated and
echoed back on re-render, a cheap CPU sink; both are
now capped, the query at the longest possible email
fragment.
2026-10-22 19:33:03 +02:00
stefan.tollkuehn 97cea7e90b Make calendar owner the event organizer
The ICS organizer was taken from the external link's
organizer parameter. The CalDAV backend only schedules
invitations when the organizer matches the calendar
owner, so invites never went out under that value. The
event now names the authenticated user's email address;
the link organizer remains the fallback for users
without a profile email and stays visible on the
confirm page as meeting information.
2026-10-22 19:23:25 +02:00
stefan.tollkuehn cc1fd4e508 Add live contact search to invitee input
The static datalist only carried the first 50 address book
contacts, so typed fragments could not match anything beyond
that. A new read-only GET /contacts/search endpoint queries
the full address book per keystroke, and a small vanilla JS
combobox shows the results and replaces only the fragment
after the last separator, so comma-separated lists keep
working. The server-rendered datalist remains as the no-JS
fallback; the JS removes the list attribute at runtime so
the native popup does not double the custom one.
2026-10-22 18:50:46 +02:00
stefan.tollkuehn 2070e9c390 add option to invite users to new calendar entry 2026-10-22 18:19:12 +02:00
stefan.tollkuehn 8910a46d1d fix css never been loaded 2026-10-22 17:55:51 +02:00
stefan.tollkuehn 2703773fe1 - fix: add constructor
- test
2026-10-22 17:33:58 +02:00
stefan.tollkuehn 2d141ee46c write tests 2026-10-08 14:09:12 +02:00