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
Copy file name to clipboardExpand all lines: CHANGELOG.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
## [Unreleased]
9
9
10
10
### Added
11
-
- **Phase-1 RBAC end-to-end enforcement (multi-tenant isolation)** — Every authenticated REST request now arrives at the SecurityPlugin middleware with a populated `ExecutionContext`, so RLS/FLS/CRUD checks actually fire. Three previously-silent context-drop sites were closed: (1) `@objectstack/objectql` `protocol.{find,get,create,update,delete}Data` now forward `request.context` into the engine call options; (2) `@objectstack/rest` `RestServer` gained `resolveExecCtx()` plus an `authServiceProvider` constructor hook (wired in `RestApiPlugin` from `ctx.getService('auth')`) that resolves the better-auth session for both single-kernel and multi-kernel deployments and threads `context` into all five CRUD handlers; (3) `@objectstack/plugin-hono-server` raw `/data/:object` fallback handlers now resolve the same context inline and map `PermissionDeniedError` → HTTP 403. `@objectstack/runtime` `resolveExecutionContext()` wraps plain header objects as Web `Headers` so better-auth's cookie lookup works. New seed link tables `sys_user_permission_set` / `sys_role_permission_set` (in `@objectstack/platform-objects`) plus default permission sets `admin_full_access` / `member_default` / `viewer_readonly`; `member_default` carries a wildcard `object: '*'` RLS policy (`tenant_id = current_user.tenant_id`) that SecurityPlugin rewrites onto the configured `tenantField` (default `organization_id`) and skips for tables that lack the field. Verified end-to-end on `pnpm dev:crm`: two users in different organizations each create records and only see their own org's rows on subsequent LISTs across multiple object types. **Known follow-up:** anonymous REST traffic still bypasses enforcement (SecurityPlugin short-circuits when `userId` is absent) — default-deny tightening, Sharing Rule evaluator, Studio RLS visual editor, per-user×org permission cache, and audit UI / denied-access logging remain queued.
11
+
- **Phase-1 RBAC end-to-end enforcement (multi-tenant isolation)** — Every authenticated REST request now arrives at the SecurityPlugin middleware with a populated `ExecutionContext`, so RLS/FLS/CRUD checks actually fire. Three previously-silent context-drop sites were closed: (1) `@objectstack/objectql` `protocol.{find,get,create,update,delete}Data` now forward `request.context` into the engine call options; (2) `@objectstack/rest` `RestServer` gained `resolveExecCtx()` plus an `authServiceProvider` constructor hook (wired in `RestApiPlugin` from `ctx.getService('auth')`) that resolves the better-auth session for both single-kernel and multi-kernel deployments and threads `context` into all five CRUD handlers; (3) `@objectstack/plugin-hono-server` raw `/data/:object` fallback handlers now resolve the same context inline and map `PermissionDeniedError` → HTTP 403. `@objectstack/runtime` `resolveExecutionContext()` wraps plain header objects as Web `Headers` so better-auth's cookie lookup works. New seed link tables `sys_user_permission_set` / `sys_role_permission_set` (in `@objectstack/platform-objects`) plus default permission sets `admin_full_access` / `member_default` / `viewer_readonly`; `member_default` carries a wildcard `object: '*'` RLS policy (`tenant_id = current_user.tenant_id`) that SecurityPlugin rewrites onto the configured `tenantField` (default `organization_id`) and skips for tables that lack the field. Two further fixes landed alongside the wiring work: (a) `@objectstack/objectql` lost its legacy `registerTenantMiddleware` (a hardcoded `where.tenant_id = ctx.tenantId` injection that pre-dated SecurityPlugin, masked RLS bugs in older snapshots, and silently broke any table without a `tenant_id` column); SecurityPlugin is now the sole authority for tenant isolation. (b) The `tenantField` rewrite in SecurityPlugin (`tenant_id` → `organization_id` on the LHS column reference) was over-eager and was also rewriting the `current_user.tenant_id` placeholder, producing `current_user.organization_id` which `RLSUserContext` doesn't expose — silently dropping every wildcard tenant policy. The regex now anchors on a non-`.` boundary so only the column reference is rewritten. `member_default` and `viewer_readonly` also gained explicit per-object overrides for the two global tables that lack `organization_id`: `sys_organization_self` (`id = current_user.tenant_id`) and `sys_user_self` (`id = current_user.id`). Verified end-to-end on `pnpm dev:crm`: two users in different organizations each create records and only see their own org's rows on subsequent LISTs across `sys_organization`, `sys_member`, `sys_user`, and the new `sys_user_permission_set` / `sys_role_permission_set` link tables. **Known follow-up:** anonymous REST traffic still bypasses enforcement (SecurityPlugin short-circuits when `userId` is absent) — default-deny tightening, Sharing Rule evaluator, Studio RLS visual editor, per-user×org permission cache, and audit UI / denied-access logging remain queued.
12
12
- **Hooks auto-register from `defineStack({ hooks })` (`@objectstack/objectql` + `@objectstack/runtime`)** — Hooks are metadata, and the runtime now treats them as such: `AppPlugin.start()`, `MultiProjectPlugin` seeders, and `ObjectQLPlugin.loadMetadataFromService` all funnel `Hook[]` through a single canonical entry point (`bindHooksToEngine` / `engine.bindHooks`), eliminating the previous boilerplate `engine.registerHook(...)` calls in user code. The binder honours every declarative field on `Hook` — `condition` (compiled as a formula), `async` (fire-and-forget on `after*` events), `retryPolicy` (max retries × linear backoff), `timeout` (Promise.race), `onError` (`'abort'` rethrows, `'log'` swallows), and `priority` — through a new `wrapDeclarativeHook` higher-order function so the engine's `triggerHooks` stays minimal. Adds `engine.registerFunction` / `resolveFunction` / `unregisterFunctionsByPackage` plus `engine.unregisterHooksByPackage(packageId)` for clean hot-reload, and a new `functions` field on `defineStack` so string-named handlers can be resolved by the binder. The built-in audit hooks in `ObjectQLPlugin.registerAuditHooks` were migrated to the same declarative form (dogfood). Example cleanup: `examples/app-crm/src/hooks/register-hooks.ts` deleted; the CRM example now just exports `allHooks` and lists them under `defineStack({ hooks })`.
13
13
-**Formula expression evaluator (`@objectstack/objectql`)** — `packages/objectql/src/formula.ts` ships a hand-written tokenizer + recursive-descent parser + tree-walking evaluator for the formula function library documented in `packages/spec/docs/formula-functions.md`. Supports text (`CONCAT`/`CONCATENATE`/`UPPER`/`LOWER`/`TEXT`/`LEN`), math (`SUM`/`AVERAGE`/`ROUND`/`CEILING`/`FLOOR`), date (`TODAY`/`NOW`/`YEAR`/`MONTH`/`DAY`/`ADDDAYS`), and logical (`IF`/`AND`/`OR`/`NOT`/`ISBLANK`) functions, plus comparison (`= == != <> < > <= >=`) and arithmetic operators with standard precedence. Public API: `compileFormula(expr)` (cached AST + dependency list) and `evaluateFormula(expr, record)`. Implementation is `eval`-free — untrusted formula strings are safe to evaluate. Used by `formula`-typed fields and decision-node conditions in flows.
14
14
-**Studio Flow Viewer + Flow Test Runner** — `apps/studio/src/components/FlowViewer.tsx` renders a flow's metadata (variables, nodes, edges, error handling) as inspector cards; `FlowTestRunner.tsx` provides an interactive form for the flow's `isInput` variables, executes the flow against the per-project kernel, and surfaces the result + run record. Wired into the Studio metadata browser via `flow-viewer-plugin.tsx` (registered in `apps/studio/src/plugins/built-in/index.ts`), so any `flow` metadata page exposes a "Run" tab. New `FlowRunsPanel.tsx` lists historical executions for the selected flow.
Copy file name to clipboardExpand all lines: content/docs/concepts/implementation-status.mdx
+4-3Lines changed: 4 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -273,7 +273,7 @@ The `auth` service in `CoreServiceName` covers both **authentication** (identity
273
273
- Client SDK supports bearer token header — but token validation requires the auth plugin
274
274
- Auth route (`/auth/*`) only appears in Discovery when the auth plugin is registered
275
275
- Fine-grained authorization (RLS, sharing, territory) is internal to the auth plugin
276
-
-**Phase-1 RBAC enforcement is live end-to-end**: REST → ObjectQL → SecurityPlugin middleware now receives a populated `ExecutionContext` (userId, tenantId, roles, permissions). Default `member_default` permission set ships a wildcard RLS rule `tenant_id = current_user.tenant_id` (rewritten to `organization_id`), which guarantees cross-organization isolation for authenticated users. **Anonymous traffic still bypasses enforcement** until a default-deny pass lands.
276
+
-**Phase-1 RBAC enforcement is live end-to-end**: REST → ObjectQL → SecurityPlugin middleware now receives a populated `ExecutionContext` (userId, tenantId, roles, permissions). Default `member_default` permission set ships a wildcard RLS rule `tenant_id = current_user.tenant_id` (rewritten to `organization_id` — only the LHS column is rewritten, the `current_user.tenant_id` placeholder is preserved) plus per-object overrides `sys_organization_self` and `sys_user_self` for the global tables that lack an `organization_id` column. The legacy `objectql.registerTenantMiddleware` (hardcoded `where.tenant_id` injection that pre-dated SecurityPlugin) has been removed; SecurityPlugin is now the sole authority for tenant isolation. Verified cross-organization isolation on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic still bypasses enforcement** until a default-deny pass lands.
277
277
278
278
---
279
279
@@ -390,8 +390,9 @@ The `auth` service in `CoreServiceName` covers both **authentication** (identity
-[x] Authorization Plugin — `@objectstack/plugin-security` enforces CRUD/FLS/RLS in the ObjectQL middleware chain; REST → ObjectQL now propagates `ExecutionContext` end-to-end (Phase-1)
393
-
-[x] Row-Level Security — default `member_default` permission set applies a wildcard `tenant_id = current_user.tenant_id` rule, rewritten to `organization_id`
394
-
-[x] Multi-tenancy — verified cross-organization isolation on `pnpm dev:crm` (Alice@OrgAlpha vs. Bob@OrgBeta only see their own records)
393
+
-[x] Row-Level Security — default `member_default` permission set applies a wildcard `tenant_id = current_user.tenant_id` rule (only the LHS column is rewritten to `organization_id`; the `current_user.tenant_id` placeholder is preserved) plus per-object overrides `sys_organization_self` / `sys_user_self`
394
+
-[x] Multi-tenancy — verified cross-organization isolation on `pnpm dev:crm` (Alice@OrgAlpha vs. Bob@OrgBeta only see their own records across `sys_organization`, `sys_member`, `sys_user`, and `sys_*_permission_set` link tables)
395
+
-[x] Legacy `objectql.registerTenantMiddleware` removed — SecurityPlugin is now the sole tenant-isolation authority
Copy file name to clipboardExpand all lines: content/docs/guides/security.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,7 +7,7 @@ description: "Complete guide to implementing enterprise-grade security in Object
7
7
8
8
Complete guide to implementing enterprise-grade security in ObjectStack with fine-grained permissions and data access controls.
9
9
10
-
> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `tenant_id = current_user.tenant_id` (rewritten onto the configured `tenantField`, default `organization_id`), giving multi-tenant isolation out of the box. **Anonymous traffic still bypasses enforcement** until a default-deny pass lands; Sharing Rules, Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and `concepts/implementation-status.mdx` for the latest matrix.
10
+
> **Implementation status — Phase-1 RBAC is live.** REST → ObjectQL now propagates a populated `ExecutionContext` (userId, tenantId, roles, permissions) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks actually fire on every authenticated request. The default `member_default` permission set ships a wildcard RLS rule `tenant_id = current_user.tenant_id` (rewritten onto the configured `tenantField`, default `organization_id` — only the LHS column is rewritten, the `current_user.tenant_id` placeholder is preserved) plus explicit per-object overrides `sys_organization_self` (`id = current_user.tenant_id`) and `sys_user_self` (`id = current_user.id`) for the two global tables that lack an `organization_id` column. The legacy `objectql.registerTenantMiddleware` (a hardcoded `where.tenant_id` injection that pre-dated SecurityPlugin) has been removed; SecurityPlugin is now the sole authority for tenant isolation. End-to-end verified on `pnpm dev:crm` across `sys_organization`, `sys_member`, `sys_user`, `sys_user_permission_set`, `sys_role_permission_set`. **Anonymous traffic still bypasses enforcement** until a default-deny pass lands; Sharing Rules, Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See `CHANGELOG.md` and `concepts/implementation-status.mdx` for the latest matrix.
0 commit comments