v0.4.2 — Activities Calendar, iCal Feeds, SSO & Magic-Link Login
Added
Calendar
- Full-page Calendar at
/calendarthat visualises scheduled activities, gated on the existingactivities.accesspermission — no new entity, table, or permission - Three views — Day (staff columns against a vertical hour-axis), Week (seven day-columns against an hour-axis, coloured by owner; the default view), and Month (a seven-column grid of day-cells with event chips)
- Pixel-precise hour-axis placement in Day and Week views; the visible hour window auto-expands to include any event outside working hours so nothing is clipped
- All-day handling computed from the activity's times (starts at
00:00with no end or ending at23:59/next midnight), rendered in a band above the grid and as month chips - Pickable start date defaulting to today and looking forwards, with Today / Previous / Next navigation that steps by the active view
- Owner filter restricting the calendar to selected staff users, with deterministic per-user colours shared across columns, blocks, chips, and avatars
- Owner and participant avatars on each event (owner first, then participants), showing each user's photo where available and coloured initials otherwise
- Unscheduled tray listing activities with no start time, each opening its detail modal so it can be scheduled
- View, date, and filter state persisted in the URL so a calendar link reopens exactly as shared
Modals & Realtime
- Add modal opened from an empty slot (pre-filled owner and start time) or a month day-cell (pre-filled date), reusing the existing activity form
- Detail modal showing an activity's subject, type, status, owner, time, and location, with Complete, Edit, and Delete actions and a link to the full
/activities/{id}page - Edit modal using the same form as create, reached from the detail modal
- All writes flow through the existing
CreateActivity/UpdateActivity/CompleteActivity/DeleteActivityactions — same validation, authorisation, audit logging, andactivity.*webhooks — and the calendar refreshes instantly and locally after every change
iCal Feeds
- Secure global iCal feed at
/calendar/feed.ics(every scheduled activity) and per-user feeds at/calendar/feed/{user}.ics(a single owner's activities) - Feeds secured with Laravel signed URLs (no expiry); a missing or tampered signature returns
403, and the feeds serve without an authenticated session - Feed window covers activities from one year ago with no forward bound; activities without a start time are excluded
- Hand-rolled RFC 5545 output (no new Composer dependency) — one
VEVENTper activity withUID,DTSTAMP,DTSTART/DTENDin UTC,SUMMARY,LOCATION,DESCRIPTION,STATUS,TRANSP, andORGANIZER, with proper text escaping and 75-octet line folding; all-day events emit;VALUE=DATE - Copyable subscribe URLs surfaced in a feed modal on the calendar page (global and own feed for every user; full per-user list for administrators)
Settings
- New Settings → Calendar page at
/settings/calendarshowing the current user's personal feed: a subscribe URL with a copy button, a Download .ics button, and Google/Apple/Outlook subscription help; administrators also see the global feed URL
Navigation
- Calendar opened from the top navigation bar's calendar icon (gated on
activities.access), with Activities relocated into the CRM menu; a "Go to Calendar" command-palette navigation entry is also available
Documentation
- Platform documentation page for the Calendar (views, navigation, owner filter, modals, unscheduled tray, the settings page, and the week-start / working-hours / weekend settings that affect it)
- API documentation page for the calendar feed endpoints, covering the signed-URL security model and its global-only revocation tradeoff
Single Sign-On (SSO)
- SSO login via Google and Microsoft 365 (Azure
commontenant) powered by Laravel Socialite — staff can sign in with their Google or Microsoft account without a Signals password - Auto-link by verified email: on first SSO login the provider's verified email is matched to an existing, active Signals user and the identity is linked automatically; unknown or unverified emails are denied and the user must be invited before using SSO
- Subsequent logins match by stored provider identity (robust to later email changes in Signals)
- Self-hosted credential configuration in Settings → Integrations → Single Sign-On: per-provider enable toggle, Client ID, and Client Secret (stored encrypted at rest; write-only — not re-displayed after save), with the correct callback URL shown for copy-paste into the Google Cloud Console or Azure portal
- Signals Cloud mode: credential fields are hidden in the Integrations UI; only enable/disable toggles are shown (credentials are managed centrally by Signals Cloud)
- Per-role SSO enforcement in Settings → Security → SSO Enforcement: admins can require specific roles to use SSO; password login for enforced roles is blocked with an inline guidance message and SSO buttons; the Owner role is always exempt (break-glass — cannot be selected)
- 2FA still applies: SSO replaces only the password step; users with 2FA enabled are redirected to the existing one-time-code challenge after a successful OAuth callback
- Allowed email domains allow-list (Settings → Integrations → Single Sign-On): when set, only users whose IdP email domain is on the list may sign in or auto-link — enforced on both the auto-link and existing-link paths — strongly recommended with Microsoft's multitenant
commonsetting to prevent cross-tenant account takeover; an empty list permits any domain - Client secrets are now write-only end-to-end: they are never loaded back into the Integrations form, never rendered to the browser, and a blank secret field on save keeps the existing stored secret (enter a new value to rotate)
- Routes:
GET /auth/google/redirect,GET /auth/google/callback,GET /auth/microsoft/redirect,GET /auth/microsoft/callback; unknown providers return404 oauth_identitiestable andOAuthIdentitymodel recording the(provider, provider_id, user_id, email)link
Authentication — Password Manager & Accessibility
- One-time-code 2FA input now auto-submits when six digits are entered, removing the need to press Enter or click a button
/.well-known/change-passwordwell-known URL pointing to the profile password-change page, so browsers and password managers can locate the change-password flow automatically- Invitation acceptance page includes a username field pre-filled with the invited email, giving password managers the anchor they need to save the credential against the right account on first use
- Login and password forms now carry canonical
autocomplete="username"/autocomplete="current-password"/autocomplete="new-password"attributes throughout the auth flow for consistent password-manager integration
Documentation
- Platform documentation page for Single Sign-On — covers enabling Google and Microsoft (self-hosted and Signals Cloud), callback URL registration, auto-link matching, per-role enforcement, and 2FA interaction
Magic-Link Login
- Passwordless login for existing users via a single-use link emailed to their address — an opt-in alternative to password login, enabled in Settings → Security → Magic-Link Login (off by default)
- Links are valid for 15 minutes and can only be consumed once; requesting a new link invalidates any prior unconsumed link for that account
- 2FA is not bypassed — users with 2FA enabled (or enforced) are redirected to the existing one-time-code challenge after the link is consumed
- Anti-enumeration: Signals always returns the same neutral response ("if an account exists, we've emailed a link") regardless of whether the email is registered, so no information about account existence is leaked; the email is dispatched asynchronously so response timing cannot be used to infer an account's existence
- Blocked for SSO-enforced roles at both the request step and the consume step, so a policy change after issue invalidates the outstanding link; the Owner role is always exempt
- Tokens are stored as a SHA-256 hash only — the plaintext travels only in the email and is compared with
hash_equals; request and consume endpoints are rate-limited per email and per IP - Platform documentation page for Magic-Link Login — covers the login flow, enabling the feature, anti-enumeration behaviour, SSO interaction, and the security model
Changed
Activities
- Activity type is now a user-editable list of values — the seeded "Activity Type" list (Task, Call, Fax, Email, Meeting, Note, Letter), manageable in Settings → List Names — replacing the previous fixed enum. The change applies across the Activities pages, the Calendar create/edit/detail modals, and the API, where
type_idnow references alist_valuesid and responses includeactivity_type_name - Activity participants now resolve to the linked user's name and avatar (with photo where available) on the activity page, in the calendar, and in the owner/participant pickers; the activity page lists the owner first followed by participants
- Activities API operations now publish full OpenAPI request/response schemas — the paginated list envelope, the single-resource envelope, and the create/update request bodies
Notes
- The calendar reuses the existing Activities write path and
activities.*permissions; no new permissions, webhook events, or database tables were introduced - Feed URLs are not individually revocable — rotating the application's
APP_KEYinvalidates all existing feed URLs at once
Also in this release: the 12-module scaffolding audit & remediation, UAT fixes, the Phase-2 Definition of Done, and the Phase-2 completion review — correctness, security, and quality fixes across the v0.4.x work.
Full Changelog: v0.4.1...v0.4.2