TECH-CHANNEL-AUTH-ORG-ADMIN — Extend submission.{id} channel auth to organisation admins #13
Reference in New Issue
Block a user
Delete Branch "feat/channel-auth-org-admin"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
TECH-CHANNEL-AUTH-ORG-ADMIN — Extend submission.{id} channel auth to organisation admins
Closes BACKLOG entry
TECH-CHANNEL-AUTH-ORG-ADMIN. Extends thesubmission.{submissionId}private broadcast channel authorization from submitter-only (introduced in WS-6 v1.3-delta D2, PR #11) to a three-path check: submitter, super_admin (app-wide bypass), or organisation admin of the submission's organisation.The original deferral reason (Spatie Permission helper convention not yet audited) is now closed: Phase A audit established the codebase's canonical pattern, used in 17+ policy sites.
Refs
TECH-CHANNEL-AUTH-ORG-ADMIN— closed by this PRFRONTEND-ECHO-IDENTITY-MATCH-SUBSCRIPTION— sibling, still open (frontend portal Echo subscription)23a5696) — introduced the channel + submitter-only auth + denied-by-default org-admin contract testWhat this PR delivers
api/routes/channels.php— three-path auth callback (commitf5cb371)Direct port of the
FormSubmissionActionFailurePolicy::canAccesspattern (codebase canonical, used in 17+ policy sites). Auth resolution:submitted_by_user_id === user.id— submitter short-circuit (fast path, no DB lookup)$user->hasRole('super_admin')— Spatie HasRoles app-wide bypass$organisation->users()->where('user_id', $user->id)->wherePivot('role', 'org_admin')->exists()— org-scoped admin authorizationwithoutGlobalScopes()preserved on bothFormSubmissionandOrganisationlookups; channel auth is a structural gate, not a tenant-scoped query.Why three paths instead of two: The super_admin bypass was an audit-surfaced bonus addition. Every analogous policy in the codebase includes it; without it super_admins (impersonating, debugging, troubleshooting in production) would mysteriously fail to subscribe even though they can do everything else in the platform. Pattern consistency wins.
Why pivot-table check instead of Spatie scoped roles: Spatie's
teamsfeature is disabled in this codebase (config/permission.php). Org-scoping lives in theuser_organisationpivot'srolecolumn, not in Spatie'smodel_has_rolestable.Role::findOrCreate('org_admin', 'web')exists in Spatie too (seeRoleSeeder.php) but no code uses$user->hasRole('org_admin')without scope — the pivot-table check is the canonical pattern.Tests (commit
e04b084)test_super_admin_can_subscribetest_organisation_admin_of_submission_org_can_subscribetest_organisation_admin_of_different_org_cannot_subscribetest_regular_organisation_member_cannot_subscribetest_org_admin_is_currently_denied_per_backlog_entryExisting tests preserved (per Phase A compatibility check — they test scenarios orthogonal to the new path):
test_submitter_is_authorisedtest_other_authenticated_user_is_denied(no org membership at all)test_subscription_is_denied_when_submission_does_not_existTest fixture refinement:
makeSubmission(?User $submitter)now accepts an explicit submitter argument. Positive role-based tests use a separate User as submitter so the submitter short-circuit doesn't accidentally authorise role-based test subjects.BACKLOG closure (commit
5d53cca)TECH-CHANNEL-AUTH-ORG-ADMINmoved from open Technische schuld to## Opgeloste items (mei 2026)with closure summary citing the PR + the three-path auth pattern + super_admin bypass additionFRONTEND-ECHO-IDENTITY-MATCH-SUBSCRIPTIONentry updated to reflect the post-PR three-path auth (no-compromises hygiene — same pattern as the FORM-05 stub-status touch-up from the WS-6 closure docs-PR)Test counts
Out-of-scope (NOT in this PR)
FormSubmissionIdentityMatchResolved— sibling BACKLOG entryFRONTEND-ECHO-IDENTITY-MATCH-SUBSCRIPTION. Backend infrastructure now fully ready (auth callback, broadcast event class, channel registration); frontend follow-up subscribes the portal IdentityMatchBanner to live updates.User::isOrgAdminOf($organisation)helper orOrganisationPolicy::adminis a separate concern. Phase A's directive was "discovery, not standardisation."apply_status=failed AND form_schema.has_public_token=true— operational task, configured manually in the GlitchTip web UI onmonitoring.hausdesign.nl. Runbook procedure documented indev-docs/runbooks/observability-triage.md§7.Audit findings (Phase A)
config/permission.phpinspection.Role::findOrCreate('org_admin', 'web')is registered in Spatie (RoleSeeder.php) but no code uses$user->hasRole('org_admin')(without scope). Spatie-side org_admin role exists for compatibility/future-use but is not the canonical check. Potential dual-source tech debt — out of scope for this PR.makeSubmission()was necessary because positive role-based tests would otherwise be authorised via the submitter short-circuit instead of the role check. Adding an explicit$submitterparameter ensures we test the path we think we're testing.test_other_authenticated_user_is_deniedcreates a non-submitter User with NO organisation membership — falls through all three auth branches and stays denied. Test still holds post-change; no collision with the new "regular member cannot subscribe" test.Commits
f5cb371— feat(broadcasting): extend submission.{id} channel auth to organisation adminse04b084— test(broadcasting): add org-admin auth + cross-tenant guard tests5d53cca— docs(backlog): close TECH-CHANNEL-AUTH-ORG-ADMINReview hints
test_organisation_admin_of_different_org_cannot_subscribe) is the critical assertion. If this passes, an admin of organisation A genuinely cannot subscribe to a submission in organisation B — the multi-tenancy guarantee Crewli depends on for broadcast channels.FormSubmissionActionFailurePolicy::canAccessexactly.FormBindingApplicator,TriggerPersonIdentityMatchOnFormSubmit, or any listener. This PR only touches the auth callback, its tests, and BACKLOG hygiene.🤖 Co-Authored-By: Claude Opus 4.7
Per BACKLOG TECH-CHANNEL-AUTH-ORG-ADMIN. Four new tests + one deleted; existing three preserved. NEW: - test_super_admin_can_subscribe (positive, app-wide bypass via Spatie HasRoles assignRole('super_admin')) - test_organisation_admin_of_submission_org_can_subscribe (positive, pivot-table org_admin → submission's organisation) - test_organisation_admin_of_different_org_cannot_subscribe (CRITICAL cross-tenant guard — admin of org B cannot subscribe to a submission in org A) - test_regular_organisation_member_cannot_subscribe (org_member role on the pivot is NOT enough; only org_admin passes) DELETED: - test_org_admin_is_currently_denied_per_backlog_entry (the "should flip" denied-by-default test from PR #11; superseded by the four positive/negative tests above) PRESERVED: - test_submitter_is_authorised - test_other_authenticated_user_is_denied (User with no organisation membership → falls through every auth branch) - test_subscription_is_denied_when_submission_does_not_exist Test-fixture refinement: makeSubmission() now accepts an explicit $submitter so positive role-based tests can use a separate User as submitter, ensuring the submitter short-circuit doesn't accidentally authorise role-based test subjects. Test results: 7 passed in this file; 1624 in full suite (was 1621). 0 Larastan errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>