case 4723 - direct email and direct notification for staff only, record rights

notify/direct-smtp: customer contacts refused; an address taken from a
Customer, HeadOffice, Vendor or User record needs read rights on that type
(a contact's user record: User or Customer rights). Staff may still give any
address (report scripts use it). The delivery-on check runs after these.
notify/direct-message: customer contacts and subcontractors refused, as the
client already hides it from them. F3 and F4 fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 18:30:08 -07:00
parent 52d66bcf10
commit f3ba5156c7
3 changed files with 32 additions and 9 deletions

View File

@@ -189,7 +189,7 @@ John's "next release" cases for AyaNova (exported 2026-10-24). Each is its own s
| 4720 | Restricted roles filtered in report/export data | F27. **Done** (partial fetch filtered like the full one; BizObjectFactory passed UserType.Service for everyone, now the real type; test `RestrictedReportData`) |
| 4721 | Client escaping of record text | F33. **Done** (raven-client: `escapeHtml` helper for the warranty summary; address dialog as text; search excerpt escaped with the terms highlighted as plain text; dialog messages through DOMPurify. The excerpt is not encoded on the server as well: the client escaping covers it. John checked search, warranty info with `<b>` in a vendor name, the address dialog and confirmation dialogs) |
| 4722 | DOMPurify and Vuetify upgrades (raven-client) | F34. **Done** (raven-client `44411ec1`: DOMPurify 2.5.9, Vuetify 2.7.2, builds on Node 12.22.9; Vuetify 2.7's calendar `eventSummary()` returns an element, so the six calendar event templates render it with a new `gz-vnodes` component instead of `v-html`. John checked wiki, schedules (drag, stretch, lock icons), date pickers, lists, dialogs and the reminder and review widgets. John also checked the scheduled work order widget as a Tech user. Still to do: Cypress on `devtestn10` at the final gate) |
| 4723 | Direct SMTP: address from the record only | F3, F4 |
| 4723 | Direct SMTP and direct notification: staff only, record rights | F3, F4. **Done**, plan revised with John: staff keep sending to any address (stock report template and a customer script use it); contacts refused in both actions, an address from a record needs read rights on it, direct notifications refused to subcontractors. Test `DirectSend` (the subcontractor case returned 204 on the old code). No changelog line: nothing that works changes |
| 4724 | SMTP: certificate checked, password never returned | F30, F31 |
| 4725 | Sessions: password change ends sessions, exact-path token exemption, default SuperUser refused when licensed | F7, F8, F9 |
| 4726 | Login throttling/lockout | F6 |
@@ -249,6 +249,7 @@ FastMail password after the old Sockeye server is retired), John's cases.
Found during the port, out of scope. One line each.
- `NotifyController.SendNotifySmtpDirectMessage`, User branch: the missing-address and inactive messages say "Vendor" instead of "User" (found in case 4723)
- `License.DoFetchAsync` (`License.cs:629-661`) rethrows at `x == 3`, so the fifth server (Sockeye) is never tried, and it can return `null` to a caller that dereferences it
- Stray `raven-client\ayanova\cypress.json`: the Cypress suite lives in `raven-test\e2e`
- **Race, affects production after every restart:** `FormFieldReference.FormFieldReferenceList` (`biz/FormFieldReference.cs:57-59`) builds a static dictionary lazily with no lock, assigning it before filling it. A concurrent first request sees it half-built and throws `FormFieldReferenceList: <type> is not valid`. Seen twice on .NET 10, each time on the first form-field use after boot: `VendorCrud.CRUD`, then `FormCustom.FormCustomUpdate`. Fix: build the dictionary fully before publishing it (`Lazy<T>` or a static initialiser). Pre-existing, not a port change. John's decision: its own case, fixed with the other cases going into this release, not under the port

View File

@@ -207,7 +207,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes
| F27 | High | **fixed in case 4720** | Restricted roles (TechRestricted, SubContractor, SubContractorRestricted) get whole work-order items through report data, rendering and export |
| F28 | High | **fixed in case 4713** | Users with change rights on Customer (Service, Sales, Accounting) can give customer contact logins any role, BizAdmin and OpsAdmin included |
| F33 | High | **fixed in case 4721** | Client renders some record text as HTML without escaping (search results, work-order address dialog, unit warranty info, dialogs) |
| F3 | High | confirmed (code) | Any logged-in user, customer contacts included, can send arbitrary email through the company's SMTP server |
| F3 | High | **fixed in cases 4712 and 4723** (staff sending to any address kept, by design) | Any logged-in user, customer contacts included, can send arbitrary email through the company's SMTP server |
| F23 | Medium | confirmed (code) | Logo upload accepts any content type and the anonymous logo route serves it inline from the app's origin (stored XSS) |
| F24 | Medium | **fixed in case 4718** | Translation import has no role check: any login can add translations |
| F30 | Medium | confirmed (code) | SMTP: the mail server's TLS certificate is never checked |
@@ -221,7 +221,7 @@ take the next free number; a fixed or accepted finding keeps its ID and changes
| F2 | Medium | confirmed (code) | Windows installers give all local users modify rights on the program and data folders, `config.json` included |
| F7 | Low | confirmed | Password change or reset does not end existing sessions, and makes the download key non-expiring |
| F8 | Low | confirmed | Latest-token check skipped for any URL ending in `/auth` or `notify/hello` |
| F4 | Low | confirmed (code) | Any logged-in user can send an in-app notification to any user |
| F4 | Low | **fixed in cases 4712 and 4723** | Any logged-in user can send an in-app notification to any user |
| F10 | Low | confirmed | Anyone can record acceptance of the licence agreement (`license/lc`) |
| F11 | Low | confirmed | API root page shows licensee, version, licence terms (and DB ID when unlicensed) to anyone |
| F12 | Low | confirmed | Password hashing: PBKDF2-SHA512 with 10,000 iterations |
@@ -858,6 +858,8 @@ Any logged-in user can add a general notification, with the sender's name and an
any user id. Low on its own (in-app only); relevant with F3 for social engineering of staff
by a customer contact. Pass 8: confirmed from code (`NotifyController.SendNotifyDirectMessage`, no role check, any `ToUserId`). Fix: staff only, or only to users the caller may see.
**Fixed (F3, F4), 2026-10-24.** Customer contacts were already refused by the case 4712 route allowlist; case 4723 also refuses them in both actions. `direct-smtp`: an address taken from a Customer, HeadOffice, Vendor or User record needs read rights on that type (a contact's user record: User or Customer rights, as on the Contact form); the record checks run before the check that email delivery is on. Staff may still give any `ToAddress`, which John kept on purpose: the stock report template "z_API send SMTP from work order report" (`objectId 0`, `toAddress` from the work order) and at least one customer's report script rely on it. Every send is in the event log. Accepted remaining risk: a staff login can send email with any content to any address through the company's mail server. `direct-message`: refused to subcontractors too, as the client already hides it from them. Test `DirectSend` (refusals only, so no email is sent).
## Cases
John's cases for the findings (created 2026-10-24; full list in `NET10-PORT.md`, "Release 8.3.0