You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Single-tenant deployments shouldn't pay the cost of the multi-tenant
security pipeline. New `SecurityPlugin({ multiTenant: false })` option
(default `true`) disables:
1. organization_id auto-injection on insert (skips a metadata lookup
per insert; owner_id injection still runs).
2. Wildcard `tenant_isolation` RLS policy collection — any policy
whose USING clause references current_user.organization_id is
stripped from the per-request policy set, so the field-existence
safety net never has to drop them individually.
Owner-based RLS, per-object CRUD checks, and Field-Level Security are
unaffected by the flag.
Also added a positive-result cache for getObjectFieldNames so repeated
inserts/finds against the same object don't re-walk the metadata
service / ObjectQL registry on every call. Negative results are not
cached because schemas may simply not be registered yet at boot.
Wired through both runtime entry points:
- packages/cli/src/commands/serve.ts
- packages/plugins/plugin-dev/src/dev-plugin.ts
both reading OS_MULTI_TENANT (default true). Set OS_MULTI_TENANT=false
on the dev / serve command line to switch to single-tenant mode.
4 new tests in security-plugin.test.ts cover both modes (insert auto-
inject on/off, find tenant-policy applied/stripped). All 37 plugin
tests + 200 runtime tests pass.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy file name to clipboardExpand all lines: CHANGELOG.md
+2Lines changed: 2 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
## [Unreleased]
9
9
10
10
### Added
11
+
- **`multiTenant` switch on `SecurityPlugin`** (`@objectstack/plugin-security`) — `new SecurityPlugin({ multiTenant: false })` disables the two pieces of the security pipeline that exist solely to support multi-organization deployments: the `organization_id` auto-injection on insert and the wildcard `tenant_isolation` RLS policy (`organization_id = current_user.organization_id`) shipped with the default `member_default` / `viewer_readonly` permission sets. In single-tenant mode every insert skips a metadata lookup, every find skips the field-existence safety net + RLS compile/AND-merge for the wildcard tenant policy, and the per-object schema lookup is now cached (positive results only — a `null` may simply mean the schema isn't registered yet at boot, so we let the next call retry). Owner-based RLS, per-object CRUD checks, and Field-Level Security are unaffected. Both the CLI dev server (`packages/cli/src/commands/serve.ts`) and the dev plugin (`packages/plugins/plugin-dev`) read `OS_MULTI_TENANT` (default `true`); set `OS_MULTI_TENANT=false` to switch a deployment to single-tenant. Four new tests in `security-plugin.test.ts` exercise both modes.
12
+
11
13
-**Auto-injection of tenancy fields on insert** (`@objectstack/plugin-security`) — When an authenticated, non-system user inserts a record, SecurityPlugin now auto-populates `organization_id` (from `ctx.tenantId`) and `owner_id` (from `ctx.userId`) **only when the field exists on the target object** (looked up via `getObjectFieldNames(metadata, object, ql)`) **and the payload has not already specified it**. This closes the gap that previously caused logged-in users to create rows with `organization_id = NULL`, which the default `tenant_isolation` RLS policy (`organization_id = current_user.organization_id`) would then hide on subsequent reads. System contexts (`ctx.isSystem === true`) are skipped — seeds and platform-admin operations remain explicit. Caller-wins semantics: explicit `organization_id` / `owner_id` in the payload are never overwritten, so cross-org admin grants and cross-tenant link tables (e.g. `sys_user_permission_set` with `organization_id = NULL`) still work.
12
14
-**Seed loader runs as system context** (`@objectstack/runtime`) — `SeedLoaderService.writeRecord` and the `app-plugin.ts` basic-insert fallback now pass `{ context: { isSystem: true } }` on every `engine.insert` / `engine.update` / `engine.find` call. This (a) bypasses RBAC checks so seeds can target system tables (`sys_*`) without granting wildcard permissions to a notional seed user, and (b) **disables auto-injection of tenancy fields**, ensuring seed records land exactly as authored — either with an explicit `organization_id` (org-scoped seeds) or with `organization_id = NULL` (intentionally cross-tenant / global metadata such as default permission sets). Combined with the auto-inject change above, this is the missing other half of "新增记录的时候也没有自动加上": user inserts get auto-tagged, seed inserts don't.
0 commit comments