@consilioweb/payload-support 3.0.0 → 5.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 +58 -14
- package/dist/index.cjs +673 -107
- package/dist/index.d.cts +40 -5
- package/dist/index.d.ts +40 -5
- package/dist/index.js +673 -107
- package/dist/utils/db.d.ts +34 -0
- package/dist/utils/readSettings.d.ts +90 -0
- package/dist/views/BillingView/index.js +4 -4
- package/dist/views/ChatView/index.js +4 -4
- package/dist/views/CrmView/index.js +4 -4
- package/dist/views/EmailTrackingView/index.js +4 -4
- package/dist/views/ImportConversationView/index.js +4 -4
- package/dist/views/LogsView/index.js +4 -2
- package/dist/views/NewTicketView/index.js +4 -2
- package/dist/views/PendingEmailsView/index.js +4 -4
- package/dist/views/SupportDashboardView/index.js +4 -4
- package/dist/views/TicketDetailView/index.js +4 -4
- package/dist/views/TicketInboxView/index.js +4 -2
- package/dist/views/TicketingSettingsView/index.js +4 -4
- package/dist/views/TimeDashboardView/index.js +4 -4
- package/dist/views/shared/viewAccess.d.ts +29 -0
- package/dist/views/shared/viewAccess.js +24 -0
- package/package.json +26 -20
- 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 +68 -11
- package/src/endpoints/capabilities.ts +7 -9
- package/src/endpoints/chat.ts +7 -5
- package/src/endpoints/chatbot.ts +50 -4
- package/src/endpoints/client-intelligence.ts +4 -4
- package/src/endpoints/email-stats.ts +19 -3
- package/src/endpoints/import-conversation.ts +3 -3
- package/src/endpoints/index.ts +1 -1
- package/src/endpoints/invite-collaborator.ts +29 -3
- package/src/endpoints/login.ts +28 -5
- package/src/endpoints/oauth-google.ts +130 -8
- package/src/endpoints/push.ts +14 -1
- 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/statuses.ts +17 -0
- 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 +23 -5
- package/src/utils/fireWebhooks.ts +4 -1
- package/src/utils/push.ts +22 -0
- package/src/utils/rateLimiter.ts +136 -4
- package/src/utils/readSettings.ts +124 -14
- package/src/utils/ticketAccess.ts +16 -1
- package/src/utils/twoFactorChallenge.ts +85 -0
- package/src/utils/urlSafety.ts +265 -0
- package/src/utils/webhookDispatcher.ts +5 -1
- package/src/views/BillingView/index.tsx +4 -4
- package/src/views/ChatView/index.tsx +4 -4
- package/src/views/CrmView/index.tsx +4 -4
- package/src/views/EmailTrackingView/index.tsx +4 -4
- package/src/views/ImportConversationView/index.tsx +4 -4
- package/src/views/LogsView/index.tsx +4 -2
- package/src/views/NewTicketView/index.tsx +4 -2
- package/src/views/PendingEmailsView/index.tsx +4 -4
- package/src/views/SupportDashboardView/index.tsx +4 -4
- package/src/views/TicketDetailView/index.tsx +4 -4
- package/src/views/TicketInboxView/index.tsx +4 -2
- package/src/views/TicketingSettingsView/index.tsx +4 -4
- package/src/views/TimeDashboardView/index.tsx +4 -4
- package/src/views/shared/viewAccess.ts +73 -0
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@consilioweb/payload-support)
|
|
6
6
|
[](LICENSE)
|
|
7
|
-
[](https://payloadcms.com)
|
|
8
8
|
|
|
9
9
|
## About
|
|
10
10
|
|
|
@@ -81,12 +81,18 @@ pnpm add @consilioweb/payload-support
|
|
|
81
81
|
**Peer dependencies** — all six are required, none is optional:
|
|
82
82
|
|
|
83
83
|
```bash
|
|
84
|
-
pnpm add payload@^3.
|
|
84
|
+
pnpm add payload@^3.79.1 @payloadcms/next@^3.79.1 next@^15.2.9 react@^19 react-dom@^19 lucide-react@">=0.300.0"
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
- `payload@^3.
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
- `payload@^3.79.1` — the floor is a security floor, and it is stated as such rather than
|
|
88
|
+
dressed up as an API constraint. Below 3.79.1 Payload is vulnerable to a pre-authentication
|
|
89
|
+
account takeover (GHSA-hp5w-3hxx-vmwf) and to a SQL injection, so a range that admitted those
|
|
90
|
+
versions — the previous `^3.37.0` did — let an install satisfy this plugin's requirements while
|
|
91
|
+
remaining exploitable. The API surface allows lower: the Google OAuth endpoint imports `jwtSign`
|
|
92
|
+
and `getFieldsToSign` from the `payload` barrel, and a missing named export fails the whole
|
|
93
|
+
`payload.config.ts` rather than just that endpoint, but both have been exported since 3.37 and
|
|
94
|
+
every symbol this package imports was verified present in 3.79.1. CI builds and tests against
|
|
95
|
+
the 3.88 line; 3.79.1 to 3.87 are permitted on that verification, not on a test run.
|
|
90
96
|
- `@payloadcms/next` and `next` — the 13 admin views are registered unless you pass
|
|
91
97
|
`skipViews: true`, and each imports `DefaultTemplate` from `@payloadcms/next/templates` plus
|
|
92
98
|
`next/navigation` / `next/link` statically. Both are already present in any Payload 3 admin app.
|
|
@@ -328,6 +334,8 @@ Two flags are projections, deliberately not stored twice: `features.autoClose` m
|
|
|
328
334
|
| `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
335
|
| `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
336
|
| `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. |
|
|
337
|
+
| `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. |
|
|
338
|
+
| `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
339
|
|
|
332
340
|
### Cron jobs
|
|
333
341
|
|
|
@@ -405,7 +413,7 @@ to stay enabled for the endpoint to be registered.
|
|
|
405
413
|
|---|---|---|---|---|
|
|
406
414
|
| `GET` | `/support/search` | staff | — | Global search over tickets, messages, clients and the knowledge base. |
|
|
407
415
|
| `GET` | `/support/kb/search` | public | — | Knowledge-base search (used by the portal and the chatbot). |
|
|
408
|
-
| `GET` | `/support/statuses` |
|
|
416
|
+
| `GET` | `/support/statuses` | staff or support-client | `customStatuses` | Ticket statuses, sorted. Mirrors `ticket-statuses.access.read`: a principal from any other auth collection of your app gets 403, not the taxonomy. |
|
|
409
417
|
| `POST` | `/support/bulk-action` | staff | `bulkActions` | Apply one action to many tickets. |
|
|
410
418
|
| `POST` | `/support/merge-tickets` | staff | `merge` | Merge a source ticket into a target. |
|
|
411
419
|
| `POST` | `/support/split-ticket` | staff | `splitTicket` | Extract a message into a new ticket. |
|
|
@@ -428,12 +436,22 @@ to stay enabled for the endpoint to be registered.
|
|
|
428
436
|
| Method | Path | Access | Purpose |
|
|
429
437
|
|---|---|---|---|
|
|
430
438
|
| `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. |
|
|
439
|
+
| `POST` | `/support/2fa` | public, rate-limited | Email second factor. **Both** `action: 'send'` and `action: 'verify'` require the short-lived `challenge` returned by `/support/login` (or by the Google callback) alongside `requires2FA`; without it, an anonymous caller who merely knew an address could burn the victim's send quota *or* their 5 verification attempts and lock them out of the only route that clears the 2FA gate. Every accepted `send` returns a refreshed `challenge` — keep the latest one and pass it to `verify` along with the code. |
|
|
432
440
|
| `POST` | `/support/oauth/google` | public | Google sign-in, optionally restricted by `allowedEmailDomains`. |
|
|
441
|
+
| `GET` | `/support/email-stats` | **staff**, rate-limited | Email pipeline aggregate. Was reachable by any authenticated session before 3.0.1. |
|
|
433
442
|
| `GET` | `/support/export-data` | client | GDPR data export. |
|
|
434
443
|
| `POST` | `/support/delete-account` | client | GDPR right to erasure. |
|
|
435
444
|
| `POST` | `/support/merge-clients` | staff | Merge two client records. |
|
|
436
445
|
|
|
446
|
+
**Google OAuth, CSRF state.** `{ "action": "login" }` now answers with a `Set-Cookie:
|
|
447
|
+
support-oauth-state=…; HttpOnly; SameSite=Lax` alongside the `url` and `state` it already returned.
|
|
448
|
+
The callback reads that cookie **server-side** and compares it, in constant time, with the `state`
|
|
449
|
+
Google sends back — it no longer accepts a `cookieState` field in the request body, which any
|
|
450
|
+
non-browser caller could simply send twice. If you wrote your own Google button, drop `cookieState`
|
|
451
|
+
from the callback payload and let the browser carry the cookie (`credentials: 'include'` on a
|
|
452
|
+
cross-origin fetch). The callback also enforces 2FA: an account with `twoFactorEnabled` gets
|
|
453
|
+
`{ requires2FA: true }` and no token, exactly like `/support/login`.
|
|
454
|
+
|
|
437
455
|
### AI
|
|
438
456
|
|
|
439
457
|
| Method | Path | Access | Flag | Purpose |
|
|
@@ -483,7 +501,7 @@ await fetch('/api/support/ai-agent', {
|
|
|
483
501
|
| `GET`/`POST` | `/support/settings` | staff | — | Read and write the support settings, including the runtime feature flags. |
|
|
484
502
|
| `GET`/`POST` | `/support/round-robin-config` | staff | `roundRobin` | Read and toggle round-robin assignment. |
|
|
485
503
|
| `GET` | `/support/push/vapid-public-key` | public | — | VAPID public key for the browser subscription. |
|
|
486
|
-
| `POST` | `/support/push/subscribe` | staff | — | Register an agent's push subscription. |
|
|
504
|
+
| `POST` | `/support/push/subscribe` | staff | — | Register an agent's push subscription. The `endpoint` must be an `https://` URL on a public host — private, loopback and link-local targets are refused with 400 (SSRF), at write time and again at send time. |
|
|
487
505
|
| `GET` | `/support/auto-close` | `x-cron-secret` | `autoClose` | Remind, then close, inactive tickets. |
|
|
488
506
|
| `POST` | `/support/process-snooze` | `x-cron-secret` | `snooze` | Wake snoozed tickets. |
|
|
489
507
|
| `POST` | `/support/process-scheduled` | `x-cron-secret` | `scheduledReplies` | Release scheduled replies. |
|
|
@@ -512,6 +530,15 @@ The signature header is only sent when the endpoint has a `secret`, so always se
|
|
|
512
530
|
`X-Webhook-Secret` header any more — verify the HMAC signature instead. `lastTriggeredAt` and
|
|
513
531
|
`lastStatus` are written back on the endpoint after every attempt. Requires `features.webhooks`.
|
|
514
532
|
|
|
533
|
+
**The endpoint URL is validated as an SSRF target, not as free text.** Only `https://` is accepted
|
|
534
|
+
(set `SUPPORT_ALLOW_INSECURE_WEBHOOKS=1` for local `http://`), literal loopback / private /
|
|
535
|
+
link-local / IPv4-mapped-IPv6 hosts are rejected at save time, the hostname is re-resolved and
|
|
536
|
+
re-checked immediately before the request (DNS rebinding), and redirects are followed **manually**
|
|
537
|
+
so a `302` towards `127.0.0.1` or `169.254.169.254` cannot slip past the checks. An endpoint saved
|
|
538
|
+
before 3.0 with an `http://` URL or an internal host stops delivering and must be re-pointed — but
|
|
539
|
+
it stays editable: the check runs only on a URL you actually write, so such a row can still be
|
|
540
|
+
renamed or deactivated, and `lastStatus: 0` is still recorded on it so a dead endpoint is visible.
|
|
541
|
+
|
|
515
542
|
| Event | `data` |
|
|
516
543
|
|---|---|
|
|
517
544
|
| `ticket_created` | `ticketId`, `id`, `ticketNumber`, `subject`, `status`, `priority`, `category` |
|
|
@@ -593,16 +620,18 @@ import type { SupportPluginConfig, SupportFeatures } from '@consilioweb/payload-
|
|
|
593
620
|
| Requirement | Range | Source |
|
|
594
621
|
|---|---|---|
|
|
595
622
|
| Node.js | `>=20.9.0` | `engines.node` |
|
|
596
|
-
| Payload | `^3.
|
|
597
|
-
| `@payloadcms/next` | `^3.
|
|
623
|
+
| Payload | `^3.79.1` | `peerDependencies.payload` |
|
|
624
|
+
| `@payloadcms/next` | `^3.79.1` | `peerDependencies` |
|
|
598
625
|
| Next.js | `^15.2.9 \|\| ^16.0.0` | `peerDependencies` |
|
|
599
626
|
| React / React DOM | `^19.0.0` | `peerDependencies` |
|
|
600
627
|
| `lucide-react` | `>=0.300.0` | `peerDependencies` |
|
|
601
628
|
| `@anthropic-ai/sdk` | any recent version | Runtime-only, install it yourself if you use the AI features |
|
|
602
629
|
| `@payloadcms/richtext-lexical` | any 3.x | Only if you mount the portal FAQ page |
|
|
603
630
|
|
|
604
|
-
Installing on Node 18, React 18, Next 14 or Payload < 3.
|
|
605
|
-
`engine-strict` or a strict peer resolver.
|
|
631
|
+
Installing on Node 18, React 18, Next 14 or Payload < 3.79.1 warns, and fails outright under
|
|
632
|
+
`engine-strict` or a strict peer resolver. Payload below 3.79.1 is additionally vulnerable to a
|
|
633
|
+
pre-authentication account takeover (GHSA-hp5w-3hxx-vmwf) and to a SQL injection: upgrade rather
|
|
634
|
+
than force the install.
|
|
606
635
|
|
|
607
636
|
## Security
|
|
608
637
|
|
|
@@ -617,7 +646,15 @@ Installing on Node 18, React 18, Next 14 or Payload < 3.37 warns, and fails outr
|
|
|
617
646
|
outside development. Login, OAuth and 2FA responses never return the token in JSON.
|
|
618
647
|
- **2FA enforced server-side** (`beforeLogin`), and the endpoint refuses to run without
|
|
619
648
|
`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
|
|
649
|
+
- **Stored-XSS protection** — message HTML is sanitized on write, not on render, and the portal
|
|
650
|
+
renders plain-text bodies as JSX (no hand-rolled HTML escaping) so an inbound email cannot inject
|
|
651
|
+
an attribute into a link.
|
|
652
|
+
- **Owner-scoped preferences** — the plugin's `payload-preferences` rows (settings, per-agent
|
|
653
|
+
signature and locale) are read back with a `user.relationTo` constraint on the staff collection.
|
|
654
|
+
`payload-preferences` accepts a write from *any* authenticated principal, so reading by key alone
|
|
655
|
+
let a client own the server settings.
|
|
656
|
+
- **Auth-collection checks, not duck typing** — every guard compares `user.collection` to the
|
|
657
|
+
configured slug: a user of another auth collection of the host app is not a support client.
|
|
621
658
|
- **HMAC everywhere it matters** — webhook deliveries and tracking pixels are signed and verified in
|
|
622
659
|
constant time, with idempotent writes. Cron and webhook secrets are read from headers only; a
|
|
623
660
|
query-string secret is rejected.
|
|
@@ -626,6 +663,13 @@ Installing on Node 18, React 18, Next 14 or Payload < 3.37 warns, and fails outr
|
|
|
626
663
|
- **No silent third-party AI host** — the `ollama` provider fails loudly on a missing
|
|
627
664
|
`OLLAMA_API_URL` instead of defaulting to someone else's server.
|
|
628
665
|
- **Bounded inbound email** payloads and attachments.
|
|
666
|
+
- **Bounded outbound mail** — ticket transfers and collaborator invitations are capped per user over
|
|
667
|
+
a long window, not only per ticket (a client can create tickets at will).
|
|
668
|
+
- **SSRF-guarded outbound requests** — scheme, literal host, resolved address and every redirect hop
|
|
669
|
+
for webhooks; scheme, literal host and resolved address for Web Push subscription endpoints, which
|
|
670
|
+
`web-push` would otherwise hand host and port straight to `https.request`.
|
|
671
|
+
- **2FA also enforced on the Google OAuth path**, which mints its session outside `payload.login`
|
|
672
|
+
and therefore outside the `beforeLogin` hook.
|
|
629
673
|
|
|
630
674
|
### Reporting security issues
|
|
631
675
|
|
|
@@ -707,7 +751,7 @@ auto-migrated: generate and push it before deploying. See [Troubleshooting](#tro
|
|
|
707
751
|
recomputing it — so a stale total keeps over-billing until something touches a time entry on that
|
|
708
752
|
ticket.
|
|
709
753
|
5. **Import `./views` and `./components/TicketConversation` with ESM** — their CJS builds are gone.
|
|
710
|
-
6. Check your platform: Node 20.9+, React 19, Next 15.2.9+, Payload 3.
|
|
754
|
+
6. Check your platform: Node 20.9+, React 19, Next 15.2.9+, Payload 3.79.1+, and install
|
|
711
755
|
`lucide-react` explicitly if you were relying on it being optional.
|
|
712
756
|
|
|
713
757
|
### 1.x → 2.0
|