@consilioweb/payload-support 3.0.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -2
- package/dist/index.cjs +558 -94
- package/dist/index.d.cts +20 -5
- package/dist/index.d.ts +20 -5
- package/dist/index.js +558 -94
- package/package.json +1 -1
- package/src/collections/ChatMessages.ts +59 -2
- package/src/collections/ClientSummaries.ts +10 -4
- package/src/collections/TicketMessages.ts +4 -1
- package/src/collections/WebhookEndpoints.ts +44 -2
- package/src/endpoints/admin-chat.ts +3 -3
- package/src/endpoints/ai-agent.ts +3 -3
- package/src/endpoints/ai.ts +3 -3
- package/src/endpoints/auth-2fa.ts +16 -4
- package/src/endpoints/capabilities.ts +6 -6
- package/src/endpoints/chat.ts +7 -5
- package/src/endpoints/chatbot.ts +48 -2
- package/src/endpoints/client-intelligence.ts +4 -4
- package/src/endpoints/email-stats.ts +19 -3
- package/src/endpoints/import-conversation.ts +1 -1
- package/src/endpoints/index.ts +1 -1
- package/src/endpoints/invite-collaborator.ts +29 -3
- package/src/endpoints/login.ts +15 -2
- package/src/endpoints/oauth-google.ts +130 -8
- package/src/endpoints/resend-notification.ts +3 -3
- package/src/endpoints/send-reminder.ts +3 -3
- package/src/endpoints/signature.ts +9 -2
- package/src/endpoints/ticket-synthesis.ts +3 -3
- package/src/endpoints/transfer-ticket.ts +28 -3
- package/src/endpoints/typing.ts +117 -14
- package/src/endpoints/user-prefs.ts +5 -2
- package/src/plugin.ts +12 -0
- package/src/portal/auth/layout.tsx +19 -1
- package/src/portal/auth/tickets/detail/MessageBody.tsx +88 -0
- package/src/portal/auth/tickets/detail/page.tsx +2 -6
- package/src/portal/login/page.tsx +11 -3
- package/src/utils/fireWebhooks.ts +4 -1
- package/src/utils/rateLimiter.ts +39 -5
- package/src/utils/readSettings.ts +124 -14
- package/src/utils/ticketAccess.ts +16 -1
- package/src/utils/twoFactorChallenge.ts +80 -0
- package/src/utils/urlSafety.ts +230 -0
- package/src/utils/webhookDispatcher.ts +5 -1
package/README.md
CHANGED
|
@@ -328,6 +328,8 @@ Two flags are projections, deliberately not stored twice: `features.autoClose` m
|
|
|
328
328
|
| `NEXT_PUBLIC_SUPPORT_PHONE` | for the portal ticket page | Support phone number shown on the portal ticket detail page. Set it: the fallback is the placeholder `01 23 45 67 89`. |
|
|
329
329
|
| `NEXT_PUBLIC_SUPPORT_SEND_ALIASES` | optional | Comma-separated "send as" identities offered in the agent composer. Empty by default, and then the composer offers only the agent themselves. |
|
|
330
330
|
| `NEXT_PUBLIC_SMTP_HOST` / `NEXT_PUBLIC_SMTP_PORT` | optional | Shown read-only in the Settings view. Informational only — mail is sent through `payload.sendEmail`, so these change nothing about delivery. |
|
|
331
|
+
| `SUPPORT_CHATBOT_MAX_PER_HOUR` | optional | Global hourly ceiling on `POST /api/support/chatbot`, all callers combined (default `200`). The per-IP window is keyed on `X-Forwarded-For`, which an anonymous caller can rotate at will; this unkeyed ceiling is what bounds the Anthropic bill. Trade-off to accept knowingly: being unkeyed, it turns a cost abuse into an availability one — one anonymous caller burning the quota mutes the chatbot for every visitor until the next window. On a high-traffic public portal, raise it and enforce the per-IP limit at the reverse proxy, where the real client address is known. |
|
|
332
|
+
| `SUPPORT_ALLOW_INSECURE_WEBHOOKS` | local dev only | `1` allows `http://` webhook endpoint URLs. Without it only `https://` is accepted, at save time and at delivery time. |
|
|
331
333
|
|
|
332
334
|
### Cron jobs
|
|
333
335
|
|
|
@@ -428,12 +430,22 @@ to stay enabled for the endpoint to be registered.
|
|
|
428
430
|
| Method | Path | Access | Purpose |
|
|
429
431
|
|---|---|---|---|
|
|
430
432
|
| `POST` | `/support/login` | public, rate-limited | Portal password login. Sets an `HttpOnly` cookie; the JWT is never returned in JSON. |
|
|
431
|
-
| `POST` | `/support/2fa` | public, rate-limited | Email second factor. |
|
|
433
|
+
| `POST` | `/support/2fa` | public, rate-limited | Email second factor. `action: 'send'` requires the short-lived `challenge` returned by `/support/login` (or by the Google callback) alongside `requires2FA` — without it an anonymous caller could burn a victim's send quota and lock them out. `action: 'verify'` takes the code. |
|
|
432
434
|
| `POST` | `/support/oauth/google` | public | Google sign-in, optionally restricted by `allowedEmailDomains`. |
|
|
435
|
+
| `GET` | `/support/email-stats` | **staff**, rate-limited | Email pipeline aggregate. Was reachable by any authenticated session before 3.0.1. |
|
|
433
436
|
| `GET` | `/support/export-data` | client | GDPR data export. |
|
|
434
437
|
| `POST` | `/support/delete-account` | client | GDPR right to erasure. |
|
|
435
438
|
| `POST` | `/support/merge-clients` | staff | Merge two client records. |
|
|
436
439
|
|
|
440
|
+
**Google OAuth, CSRF state.** `{ "action": "login" }` now answers with a `Set-Cookie:
|
|
441
|
+
support-oauth-state=…; HttpOnly; SameSite=Lax` alongside the `url` and `state` it already returned.
|
|
442
|
+
The callback reads that cookie **server-side** and compares it, in constant time, with the `state`
|
|
443
|
+
Google sends back — it no longer accepts a `cookieState` field in the request body, which any
|
|
444
|
+
non-browser caller could simply send twice. If you wrote your own Google button, drop `cookieState`
|
|
445
|
+
from the callback payload and let the browser carry the cookie (`credentials: 'include'` on a
|
|
446
|
+
cross-origin fetch). The callback also enforces 2FA: an account with `twoFactorEnabled` gets
|
|
447
|
+
`{ requires2FA: true }` and no token, exactly like `/support/login`.
|
|
448
|
+
|
|
437
449
|
### AI
|
|
438
450
|
|
|
439
451
|
| Method | Path | Access | Flag | Purpose |
|
|
@@ -512,6 +524,15 @@ The signature header is only sent when the endpoint has a `secret`, so always se
|
|
|
512
524
|
`X-Webhook-Secret` header any more — verify the HMAC signature instead. `lastTriggeredAt` and
|
|
513
525
|
`lastStatus` are written back on the endpoint after every attempt. Requires `features.webhooks`.
|
|
514
526
|
|
|
527
|
+
**The endpoint URL is validated as an SSRF target, not as free text.** Only `https://` is accepted
|
|
528
|
+
(set `SUPPORT_ALLOW_INSECURE_WEBHOOKS=1` for local `http://`), literal loopback / private /
|
|
529
|
+
link-local / IPv4-mapped-IPv6 hosts are rejected at save time, the hostname is re-resolved and
|
|
530
|
+
re-checked immediately before the request (DNS rebinding), and redirects are followed **manually**
|
|
531
|
+
so a `302` towards `127.0.0.1` or `169.254.169.254` cannot slip past the checks. An endpoint saved
|
|
532
|
+
before 3.0 with an `http://` URL or an internal host stops delivering and must be re-pointed — but
|
|
533
|
+
it stays editable: the check runs only on a URL you actually write, so such a row can still be
|
|
534
|
+
renamed or deactivated, and `lastStatus: 0` is still recorded on it so a dead endpoint is visible.
|
|
535
|
+
|
|
515
536
|
| Event | `data` |
|
|
516
537
|
|---|---|
|
|
517
538
|
| `ticket_created` | `ticketId`, `id`, `ticketNumber`, `subject`, `status`, `priority`, `category` |
|
|
@@ -617,7 +638,15 @@ Installing on Node 18, React 18, Next 14 or Payload < 3.37 warns, and fails outr
|
|
|
617
638
|
outside development. Login, OAuth and 2FA responses never return the token in JSON.
|
|
618
639
|
- **2FA enforced server-side** (`beforeLogin`), and the endpoint refuses to run without
|
|
619
640
|
`PAYLOAD_SECRET` rather than falling back to an insecure default. OAuth verifies the Google email.
|
|
620
|
-
- **Stored-XSS protection** — message HTML is sanitized on write, not on render
|
|
641
|
+
- **Stored-XSS protection** — message HTML is sanitized on write, not on render, and the portal
|
|
642
|
+
renders plain-text bodies as JSX (no hand-rolled HTML escaping) so an inbound email cannot inject
|
|
643
|
+
an attribute into a link.
|
|
644
|
+
- **Owner-scoped preferences** — the plugin's `payload-preferences` rows (settings, per-agent
|
|
645
|
+
signature and locale) are read back with a `user.relationTo` constraint on the staff collection.
|
|
646
|
+
`payload-preferences` accepts a write from *any* authenticated principal, so reading by key alone
|
|
647
|
+
let a client own the server settings.
|
|
648
|
+
- **Auth-collection checks, not duck typing** — every guard compares `user.collection` to the
|
|
649
|
+
configured slug: a user of another auth collection of the host app is not a support client.
|
|
621
650
|
- **HMAC everywhere it matters** — webhook deliveries and tracking pixels are signed and verified in
|
|
622
651
|
constant time, with idempotent writes. Cron and webhook secrets are read from headers only; a
|
|
623
652
|
query-string secret is rejected.
|
|
@@ -626,6 +655,11 @@ Installing on Node 18, React 18, Next 14 or Payload < 3.37 warns, and fails outr
|
|
|
626
655
|
- **No silent third-party AI host** — the `ollama` provider fails loudly on a missing
|
|
627
656
|
`OLLAMA_API_URL` instead of defaulting to someone else's server.
|
|
628
657
|
- **Bounded inbound email** payloads and attachments.
|
|
658
|
+
- **Bounded outbound mail** — ticket transfers and collaborator invitations are capped per user over
|
|
659
|
+
a long window, not only per ticket (a client can create tickets at will).
|
|
660
|
+
- **SSRF-guarded outbound webhooks** — scheme, literal host, resolved address and every redirect hop.
|
|
661
|
+
- **2FA also enforced on the Google OAuth path**, which mints its session outside `payload.login`
|
|
662
|
+
and therefore outside the `beforeLogin` hook.
|
|
629
663
|
|
|
630
664
|
### Reporting security issues
|
|
631
665
|
|