feat(form-builder): broadcast channel auth + listener layout comment update
Per RFC-WS-6 §Q1 v1.3 addition 2.
- routes/channels.php (NEW): authorization callback for the
submission.{id} private channel. v1 authz scope is submitter-only
(matches submitted_by_user_id); org-admin access is deferred per
BACKLOG TECH-CHANNEL-AUTH-ORG-ADMIN. Frontend Echo subscription
lands as a separate frontend follow-up.
- bootstrap/app.php: registers routes/channels.php via withRouting()
channels: parameter. This is NEW broadcasting wiring — Laravel's
broadcasting auth middleware was not previously connected to the
framework. Without this registration the channels file is dead code.
- AppServiceProvider:👢 comment block updated to v1.3 listener
layout (1 sync ApplyBindings + N queued, all gated on
apply_status=COMPLETED per ARCH-BINDINGS §5.6). Comment on
TriggerPersonIdentityMatch flipped from "(sync)" to "(queued
post-v1.3)".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -163,16 +163,24 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
FormField::observe(FormFieldChildTablesCascadeObserver::class);
|
FormField::observe(FormFieldChildTablesCascadeObserver::class);
|
||||||
FormFieldLibrary::observe(FormFieldChildTablesCascadeObserver::class);
|
FormFieldLibrary::observe(FormFieldChildTablesCascadeObserver::class);
|
||||||
|
|
||||||
// RFC-WS-6 §3 (Q1) — sync chain on FormSubmissionSubmitted, in
|
// RFC-WS-6 v1.3 §Q1 — FormSubmissionSubmitted listener layout.
|
||||||
// this exact order:
|
//
|
||||||
// 1. ApplyBindingsOnFormSubmit (sync)
|
// SYNC chain (single listener):
|
||||||
// 2. TriggerPersonIdentityMatchOnFormSubmit (sync)
|
// 1. ApplyBindingsOnFormSubmit
|
||||||
// Queued listeners on the same event (SyncTagPickerSelectionsOnSubmit,
|
//
|
||||||
// future webhook dispatcher, mailables) run in parallel after the
|
// QUEUED listeners (parallel, all gated on apply_status=COMPLETED
|
||||||
// sync chain via the queue. Their relative registration position
|
// per ARCH-BINDINGS §5.6 — PARTIAL and FAILED both fall through to
|
||||||
// is irrelevant.
|
// the early-return):
|
||||||
|
// - TriggerPersonIdentityMatchOnFormSubmit (gates + invariant + broadcast)
|
||||||
|
// - SyncTagPickerSelectionsOnSubmit
|
||||||
|
//
|
||||||
|
// The listener-ordering structural test
|
||||||
|
// (FormSubmissionSubmittedListenerOrderTest) asserts ApplyBindings
|
||||||
|
// is the only sync listener and that every queued listener has
|
||||||
|
// the apply_status=COMPLETED gate as its first executable
|
||||||
|
// statement.
|
||||||
|
|
||||||
// RFC Q1 — applies bindings sync before identity match runs.
|
// RFC Q1 — applies bindings sync before queued siblings fire.
|
||||||
\Illuminate\Support\Facades\Event::listen(
|
\Illuminate\Support\Facades\Event::listen(
|
||||||
FormSubmissionSubmitted::class,
|
FormSubmissionSubmitted::class,
|
||||||
ApplyBindingsOnFormSubmit::class,
|
ApplyBindingsOnFormSubmit::class,
|
||||||
@@ -184,7 +192,8 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
SyncTagPickerSelectionsOnSubmit::class,
|
SyncTagPickerSelectionsOnSubmit::class,
|
||||||
);
|
);
|
||||||
|
|
||||||
// ARCH §31.1 — identity-match trigger on event_registration (sync).
|
// ARCH §31.1 — identity-match trigger on event_registration (queued
|
||||||
|
// post-v1.3; was sync in v1.0/v1.2 layout).
|
||||||
\Illuminate\Support\Facades\Event::listen(
|
\Illuminate\Support\Facades\Event::listen(
|
||||||
FormSubmissionSubmitted::class,
|
FormSubmissionSubmitted::class,
|
||||||
TriggerPersonIdentityMatchOnFormSubmit::class,
|
TriggerPersonIdentityMatchOnFormSubmit::class,
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ return Application::configure(basePath: dirname(__DIR__))
|
|||||||
web: __DIR__.'/../routes/web.php',
|
web: __DIR__.'/../routes/web.php',
|
||||||
api: __DIR__.'/../routes/api.php',
|
api: __DIR__.'/../routes/api.php',
|
||||||
commands: __DIR__.'/../routes/console.php',
|
commands: __DIR__.'/../routes/console.php',
|
||||||
|
// Per RFC-WS-6 §Q1 v1.3 addition 2 — broadcast channel auth callbacks
|
||||||
|
// live in routes/channels.php. Registers Laravel's broadcasting auth
|
||||||
|
// middleware so private/presence channel subscriptions reach the
|
||||||
|
// closures defined there.
|
||||||
|
channels: __DIR__.'/../routes/channels.php',
|
||||||
health: '/up',
|
health: '/up',
|
||||||
apiPrefix: 'api/v1',
|
apiPrefix: 'api/v1',
|
||||||
)
|
)
|
||||||
|
|||||||
57
api/routes/channels.php
Normal file
57
api/routes/channels.php
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use App\Models\FormBuilder\FormSubmission;
|
||||||
|
use App\Models\User;
|
||||||
|
use Illuminate\Support\Facades\Broadcast;
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Broadcast Channel Authorization
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
| Channel-level authorization callbacks for private and presence channels.
|
||||||
|
| Laravel's broadcasting auth middleware invokes these on subscription
|
||||||
|
| attempts; returning truthy authorises, falsy denies.
|
||||||
|
|
|
||||||
|
| File registered in bootstrap/app.php via withRouting(channels: ...).
|
||||||
|
| Without that registration this file is dead code.
|
||||||
|
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Per RFC-WS-6 §Q1 v1.3 addition 2.
|
||||||
|
*
|
||||||
|
* Authorises private subscriptions to a form-submission's identity-match
|
||||||
|
* resolution channel. The TriggerPersonIdentityMatchOnFormSubmit listener
|
||||||
|
* dispatches FormSubmissionIdentityMatchResolved on this channel after
|
||||||
|
* writing the final identity_match_status; the frontend portal
|
||||||
|
* IdentityMatchBanner subscribes via Echo.private('submission.{id}') and
|
||||||
|
* refetches the submission resource on receipt.
|
||||||
|
*
|
||||||
|
* v1 authz scope: only the submitter who created the submission via an
|
||||||
|
* authenticated session is allowed to subscribe. Org-admin access is
|
||||||
|
* deferred — see BACKLOG entry TECH-CHANNEL-AUTH-ORG-ADMIN. Public
|
||||||
|
* (token-based) submitters are not on this channel; their flow is
|
||||||
|
* already polling-based and they don't have a User to authenticate with.
|
||||||
|
*/
|
||||||
|
Broadcast::channel(
|
||||||
|
'submission.{submissionId}',
|
||||||
|
function (User $user, string $submissionId): bool {
|
||||||
|
$submission = FormSubmission::query()
|
||||||
|
->withoutGlobalScopes()
|
||||||
|
->find($submissionId);
|
||||||
|
|
||||||
|
if ($submission === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO TECH-CHANNEL-AUTH-ORG-ADMIN — extend to organisation admins
|
||||||
|
// once we audit the Spatie Permission helper for an
|
||||||
|
// organisation-scoped role check (hasRoleInOrganisation or
|
||||||
|
// similar). Until that audit lands, only the submitter has
|
||||||
|
// channel access.
|
||||||
|
return $submission->submitted_by_user_id === $user->id;
|
||||||
|
},
|
||||||
|
);
|
||||||
Reference in New Issue
Block a user