Skip to content

[upstream #4937] feat(core): RoleAssignmentPlugin (WIP) - #24

Draft
ayim wants to merge 72 commits into
masterfrom
mirror/upstream-4937
Draft

[upstream #4937] feat(core): RoleAssignmentPlugin (WIP)#24
ayim wants to merge 72 commits into
masterfrom
mirror/upstream-4937

Conversation

@ayim

@ayim ayim commented Jul 28, 2026

Copy link
Copy Markdown
Owner

Mirrored from vendurehq#4937 for the Overwatch review demo.

Original author: @BibiSebi


Description

First slice of channel-scoped role assignments for multi-vendor setups (design discussion in vendurehq#3779): a skeleton RoleAssignmentPlugin shipped inside @vendure/core, activated only via a new experimental.roleAssignments.enabled config flag.

  • New RoleAssignment entity: (user, role, channel) bridge with a unique constraint on the triple, non-nullable FK columns (@EntityId()), CASCADE deletes, and indexes on roleId/channelId (userId lookups are covered by the unique constraint's leftmost column)
  • Minimal @VendurePlugin shell that registers the entity — no permission resolution, services, or API extensions yet
  • New experimental section on VendureConfig; when roleAssignments.enabled is set, preBootstrapConfig registers the plugin internally (users never touch plugins: [])
  • Legacy-data backfill (RoleAssignmentMigrationService), run on server bootstrap only while the role_assignment table is empty (run-once semantics; deleting all rows re-triggers it, and the service can be invoked manually):
    • Candidate rows come from a single join across the legacy tables: user_roles_rolerole_channels_channel, one row per (user, role, channel)
    • Customer users are additionally restricted to the channels they actually belong to (via customer_channels_channel) — without this, every customer would receive an assignment on every channel, because the Customer role itself is auto-assigned to all channels. Administrator users have no channel membership of their own, so their assignments follow the role's channels directly (a user which is both admin and customer is treated as a customer). Row count therefore scales with customers × own channels (typically 1) + admins × channels-per-role
    • Memory-bounded regardless of store size: rows are processed in keyset-paginated batches, with already-migrated rows excluded DB-side via an anti-join instead of being loaded and diffed in memory; the whole run is one transaction so a mid-run failure cannot leave the table partially populated (which would defeat the empty-table check on next boot)
    • Additive and idempotent (INSERT ... orIgnore); legacy relations are never modified, so disabling the flag stays non-destructive
    • Server process only; soft-deleted users excluded; if the flag is on but the table is missing, a clear "generate and run a migration" error is logged instead of an opaque SQL failure
  • New serverConfig.experimentalFeatures admin API field listing the enabled experimental config keys, so clients can adapt their UI to experimental features
  • Dashboard: when the flag is enabled, the administrator detail page shows an assignment channel picker (UI scaffolding only — selections are not yet persisted), and the role detail page hides its role-level channel picker (channels move to the assignment side)
  • Commented-out flag in the dev-server config for local testing

With the flag disabled (default) there is zero schema or behavior change. With it enabled, the effects are the new role_assignment table (requires a migration, same as DefaultJobQueuePlugin) and the one-time backfill. Permission resolution still uses only the legacy relations — the rows are inert until the resolver-strategy pass, which follows in subsequent PRs per the plan on the linked issue.

Relates to vendurehq#3779

TODOs

Migration tooling
  • Consider exporting the backfill as a standalone migrateRoleAssignmentData(queryRunner) helper in migration-utils/ (the v3.6 migrateProductOptionGroupData() pattern), so it can run inside the same TypeORM migration that creates the table instead of boot-time run-once logic
  • Equivalence check: recompute per-user-per-channel permissions before/after migration and assert they match
  • Phase 2: dry-run CLI for consolidating duplicated per-channel roles (vendorX-* copies)

Breaking changes

None. The feature is opt-in via an experimental flag; disabled by default.

Screenshots

N/A

Checklist

📌 Always:

  • I have set a clear title
  • My PR is small and contains a single feature
  • I have checked my own PR

👍 Most of the time:

  • I have added or updated test cases
  • I have updated the README if needed

BibiSebi and others added 30 commits July 9, 2026 15:50
dlhck and others added 30 commits July 15, 2026 15:25
…low (vendurehq#4974)

Co-authored-by: vendure-ci-automation-bot[bot] <221402278+vendure-ci-automation-bot[bot]@users.noreply.github.com>
…-instance, config, filters, new widgets) (vendurehq#4976)

Co-authored-by: vendure-ci-automation-bot[bot] <221402278+vendure-ci-automation-bot[bot]@users.noreply.github.com>
…vements (vendurehq#4983)

Co-authored-by: vendure-ci-automation-bot[bot] <221402278+vendure-ci-automation-bot[bot]@users.noreply.github.com>
)

Co-authored-by: vendure-ci-automation-bot[bot] <221402278+vendure-ci-automation-bot[bot]@users.noreply.github.com>
Co-authored-by: vendure-ci-automation-bot[bot] <221402278+vendure-ci-automation-bot[bot]@users.noreply.github.com>
…0-cannot-share-roles-across-multiple-channels-in-multi-vendor

# Conflicts:
#	packages/dashboard/src/app/routes/_authenticated/_administrators/administrators_.$id.tsx
#	schema-admin.json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants