Files
projectsend/app/Modules/Audit/Events/ResolvingActivityOrigin.php
ignacionelson 91d34b204c Let something other than a browser session identify itself to the audit log
An actor with no personal access token has always meant a browser, and
for as long as a session and a Sanctum token were the only two ways to
authenticate, that was true. It stops being true the moment anything else
can, and the failure is silent: the action gets recorded as a person
clicking, in the one table whose whole purpose is answering "did I do
that, or did something acting for me?"

Nothing misreports today — every call site that passes an explicit actor
is a browser request, an API request whose actor carries the token, or a
console command with no actor at all. This closes the trap before the AI
connector in cloud-modules walks into it.

ActivityOrigin is a closed enum, so core has to publish both the case and
the hook before a package can use either. ResolvingActivityOrigin is
asked only in the ambiguous case: a request carrying a token is the API
and a request with nobody signed in is public or system, and neither is
in any doubt, so neither is offered — one package must not be able to
quietly relabel how every integration's actions are attributed.

The person stays the actor. They authorised it, and a log naming the
assistant instead would lose the only fact that matters. What the
connector was called goes in api_token_name, beside a null token id,
because that column means a row in personal_access_tokens and this is not
one.

The new origin is kept out of the activity filter unless the edition can
actually produce it. A filter option that can only ever return nothing is
a feature dangled at an edition that does not have it, which is the one
thing the edition boundary exists not to do.
2026-08-25 14:19:32 -03:00

54 lines
1.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Audit\Events;
use App\Models\User;
use App\Modules\Audit\ActivityOrigin;
/**
* "This request has a signed-in actor and no personal access token — was
* it really a browser?"
*
* Asked only in that one ambiguous case. A request carrying a Sanctum
* token is the API, a request with nobody signed in is public or system,
* and neither is in any doubt — so neither is offered here.
*
* The doubt exists because "no token" has always meant "a session", and
* that stops being true the moment anything else can authenticate a
* request. `ActivityOrigin` is a closed enum a package cannot extend, so
* core has to publish both the case and this hook before a package can
* say "that was mine". Without it a new credential would be recorded as
* a person clicking in a browser — silently, and in the one table whose
* whole purpose is answering "did I do that, or did something acting for
* me?"
*
* Set `$origin` only if you recognise the credential on the current
* request. Leaving it null means "not mine", which is the honest answer
* for every listener that is not looking at its own guard.
*
* Listened to by *string* class name from a package, same as every other
* hook here — see docs/extension-points-architecture.md.
*/
final class ResolvingActivityOrigin
{
/**
* What actually authenticated this request. Null until a listener
* claims it, after which core stops assuming a browser session.
*/
public ?ActivityOrigin $origin = null;
/**
* What to show beside the entry — the name of the connector or
* application acting, not the person. Snapshotted into the same
* column an API token's name goes in, for the same reason: revoking
* the credential must not leave the entry pointing at nothing.
*/
public ?string $credentialName = null;
public function __construct(
public readonly User $actor,
) {}
}