CluPilotCloud/app
nexxo 20329b176b Refuse a portal login for an address that already belongs to an operator
Customer::ensureUser() enforced uniqueness only within users, so a
customer provisioned (or registering publicly) with an operator's email
got a second, colliding identity for the same address — the exact thing
R21 exists to prevent. Checks Operator by address now, not by session
(the deleted assertNotAdmin() checked who was signed in, which could
never be the right question).

Public registration gets the same guard via a plain unique() validation
rule, reusing Laravel's stock "already taken" message rather than a
dedicated one — naming the collision specifically would tell a public
visitor an address belongs to staff, a worse leak than the generic
refusal every other collision here already gives them.

Impersonation answers 409 instead of crashing into it. The Stripe
webhook keeps recording the paid order (same principle as
openContract()'s own comment: the order is proof money changed hands)
and withholds only the colliding login, logged for an operator to
resolve by hand.

Verified the reverse direction Codex flagged as already covered: two
Admin\Settings actions and the clupilot:create-operator command all
already refuse an operator email that collides with a customer's, and
that check is sound today because every users row is created in
lockstep with a customers row of the same email (CreateNewUser and
ensureUser are the only two creation sites, both confirmed by grep) and
nothing in this app can change a user's email afterwards (Fortify's
updateProfileInformation feature is disabled, and no other code calls
that action).
2026-07-28 14:42:16 +02:00
..
Actions Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
Console Write down that the console and the portal share no identity 2026-07-28 12:15:13 +02:00
Exceptions Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
Http Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
Livewire Give operators somewhere to actually set up two-factor 2026-07-28 14:21:02 +02:00
Mail Stop demanding a cipher, a real port, or a decrypted password a mailbox never uses 2026-07-28 06:21:28 +02:00
Models Refuse a portal login for an address that already belongs to an operator 2026-07-28 14:42:16 +02:00
Notifications Pin CloudReady's null-mailbox guard and share it with the trait 2026-07-27 23:59:05 +02:00
Policies Move the console's identity out of the customer table 2026-07-28 10:31:43 +02:00
Providers Stop the console serving the portal's sign-in page 2026-07-28 11:45:34 +02:00
Provisioning Measure availability, and let a customer move down again 2026-07-27 16:41:15 +02:00
Services Move the console's identity out of the customer table 2026-07-28 10:31:43 +02:00
Support Give the mailboxes a page, and the support sender its own capability 2026-07-28 01:57:11 +02:00