@venturekit-pro/tenancy 0.0.13 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,6 +5,7 @@ export { createTenantMiddleware, TenantNotFoundError, TenantSuspendedError, Tena
5
5
  export { createQuotaMiddleware, QuotaExceededError, checkQuotas } from './quota-middleware.js';
6
6
  export { createRuntimeTenantMiddleware, hostPrefixDomainResolver, } from './runtime-tenant-middleware.js';
7
7
  export type { RuntimeTenantResolver, RuntimeTenantMiddlewareOptions, HostPrefixDomainResolverOptions, } from './runtime-tenant-middleware.js';
8
+ export { createTenantRlsMiddleware } from './rls-middleware.js';
8
9
  export { createTenantUserScopesMiddleware, getTenantUser, requireTenantUser, } from './tenant-user-scopes-middleware.js';
9
10
  export type { TenantUserWithRole, TenantUserResolver, TenantUserScopesMiddlewareOptions, RoleScopesLookup, } from './tenant-user-scopes-middleware.js';
10
11
  export { TENANT_ROLES_CLAIM, TENANT_ROLES_MAX_LENGTH, packTenantRoles, unpackTenantRoles, } from './tenant-roles-claim.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,qBAAqB,EACrB,8BAA8B,EAC9B,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iCAAiC,EACjC,gBAAgB,GACjB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,EACf,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EACL,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AACxC,YAAY,EACV,qBAAqB,EACrB,8BAA8B,EAC9B,+BAA+B,GAChC,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAC5C,YAAY,EACV,kBAAkB,EAClB,kBAAkB,EAClB,iCAAiC,EACjC,gBAAgB,GACjB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,EACf,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AACjC,YAAY,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EACL,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EACV,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,wBAAwB,CAAC"}
@@ -4,6 +4,7 @@
4
4
  export { createTenantMiddleware, TenantNotFoundError, TenantSuspendedError, TenantInactiveError, } from './tenant-middleware.js';
5
5
  export { createQuotaMiddleware, QuotaExceededError, checkQuotas } from './quota-middleware.js';
6
6
  export { createRuntimeTenantMiddleware, hostPrefixDomainResolver, } from './runtime-tenant-middleware.js';
7
+ export { createTenantRlsMiddleware } from './rls-middleware.js';
7
8
  export { createTenantUserScopesMiddleware, getTenantUser, requireTenantUser, } from './tenant-user-scopes-middleware.js';
8
9
  export { TENANT_ROLES_CLAIM, TENANT_ROLES_MAX_LENGTH, packTenantRoles, unpackTenantRoles, } from './tenant-roles-claim.js';
9
10
  export { createTenantHandoffAuthorize, normalizeHandoffHost, } from './handoff-authorize.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAO5C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,EACf,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EACL,6BAA6B,EAC7B,wBAAwB,GACzB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EACL,gCAAgC,EAChC,aAAa,EACb,iBAAiB,GAClB,MAAM,oCAAoC,CAAC;AAO5C,OAAO,EACL,kBAAkB,EAClB,uBAAuB,EACvB,eAAe,EACf,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EACL,4BAA4B,EAC5B,oBAAoB,GACrB,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,28 @@
1
+ import type { Middleware, RequestContext } from '@venturekit/runtime';
2
+ /**
3
+ * Build the middleware that opens a `@venturekit/data` tenant scope for
4
+ * the resolved `ctx.tenant`, so Postgres RLS policies apply to every
5
+ * query in the request.
6
+ *
7
+ * Takes no options: the scope is derived entirely from `ctx.tenant`,
8
+ * and "no tenant" is not configurable because inheriting a stale scope
9
+ * is never the safe choice.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * import { handler } from '@venturekit/runtime';
14
+ * import {
15
+ * createRuntimeTenantMiddleware,
16
+ * createTenantRlsMiddleware,
17
+ * } from '@venturekit-pro/tenancy';
18
+ *
19
+ * const tenancy = createRuntimeTenantMiddleware({ resolver });
20
+ * const rls = createTenantRlsMiddleware();
21
+ *
22
+ * export const main = handler(async () => listPosts(), {
23
+ * middleware: [tenancy, rls],
24
+ * });
25
+ * ```
26
+ */
27
+ export declare function createTenantRlsMiddleware(): Middleware<RequestContext>;
28
+ //# sourceMappingURL=rls-middleware.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-middleware.d.ts","sourceRoot":"","sources":["../../src/middleware/rls-middleware.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAEtE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,yBAAyB,IAAI,UAAU,CAAC,cAAc,CAAC,CAStE"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Row-level-security scope middleware.
3
+ *
4
+ * Bridges the tenant this package RESOLVES (`ctx.tenant`, set by
5
+ * {@link createRuntimeTenantMiddleware}) to the tenant
6
+ * `@venturekit/data` PUBLISHES to Postgres, so RLS policies enforce
7
+ * isolation for every query the request makes.
8
+ *
9
+ * Without this, tenant isolation rests entirely on each query carrying
10
+ * a hand-written tenant predicate. That is unenforceable in practice:
11
+ * the common safe shape — check the tenant in one query, then act on
12
+ * the primary key in the next — is textually indistinguishable from
13
+ * the unsafe shape, so neither the compiler nor review catches an
14
+ * omission. With this middleware plus the policies described in
15
+ * `@venturekit/data`'s `query/tenant.ts`, a forgotten predicate returns
16
+ * zero rows instead of another tenant's data.
17
+ *
18
+ * # Placement (this ordering is load-bearing)
19
+ *
20
+ * ```ts
21
+ * middleware: [
22
+ * tenancy, // resolves ctx.tenant
23
+ * tenantRls(), // ← opens the RLS scope
24
+ * tenantUserScopes(), // QUERIES the membership row
25
+ * ]
26
+ * ```
27
+ *
28
+ * It must come AFTER tenant resolution (it reads `ctx.tenant`) and
29
+ * BEFORE any middleware that touches the database. In particular
30
+ * {@link createTenantUserScopesMiddleware} loads the caller's
31
+ * tenant-user row on the store-fallback path; if RLS is not yet scoped
32
+ * when that runs, the row is invisible, no scopes are granted, and
33
+ * every privileged route 403s. Ordering this wrong fails loudly rather
34
+ * than silently, which is the intended trade-off.
35
+ *
36
+ * # No tenant resolved
37
+ *
38
+ * Public routes and platform-level endpoints run through
39
+ * `runWithoutTenant()` rather than being left un-scoped. That matters:
40
+ * pooled connections are reused, so inheriting whichever tenant last
41
+ * touched the connection would be both wrong and non-deterministic.
42
+ * Clearing it means tenant-scoped tables return nothing — the
43
+ * fail-closed default. Genuinely cross-tenant work (operator tooling,
44
+ * sweeps over every tenant) belongs on a database role that bypasses
45
+ * RLS, not on an ambient scope left over from another request.
46
+ *
47
+ * Like `tenant-user-scopes-middleware.ts`, this module imports VALUES
48
+ * from an optional peer (`@venturekit/data`). That is deliberate and
49
+ * consistent: an RLS bridge is meaningless unless the app's data layer
50
+ * is the one publishing the GUC.
51
+ */
52
+ import { runWithTenant, runWithoutTenant } from '@venturekit/data';
53
+ /**
54
+ * Build the middleware that opens a `@venturekit/data` tenant scope for
55
+ * the resolved `ctx.tenant`, so Postgres RLS policies apply to every
56
+ * query in the request.
57
+ *
58
+ * Takes no options: the scope is derived entirely from `ctx.tenant`,
59
+ * and "no tenant" is not configurable because inheriting a stale scope
60
+ * is never the safe choice.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * import { handler } from '@venturekit/runtime';
65
+ * import {
66
+ * createRuntimeTenantMiddleware,
67
+ * createTenantRlsMiddleware,
68
+ * } from '@venturekit-pro/tenancy';
69
+ *
70
+ * const tenancy = createRuntimeTenantMiddleware({ resolver });
71
+ * const rls = createTenantRlsMiddleware();
72
+ *
73
+ * export const main = handler(async () => listPosts(), {
74
+ * middleware: [tenancy, rls],
75
+ * });
76
+ * ```
77
+ */
78
+ export function createTenantRlsMiddleware() {
79
+ return {
80
+ name: 'tenant-rls',
81
+ fn: async (ctx, next) => {
82
+ const tenantId = ctx.tenant?.id;
83
+ if (!tenantId)
84
+ return runWithoutTenant(next);
85
+ return runWithTenant(tenantId, next);
86
+ },
87
+ };
88
+ }
89
+ //# sourceMappingURL=rls-middleware.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rls-middleware.js","sourceRoot":"","sources":["../../src/middleware/rls-middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGnE;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,yBAAyB;IACvC,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;YACtB,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;YAChC,IAAI,CAAC,QAAQ;gBAAE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;YAC7C,OAAO,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACvC,CAAC;KACF,CAAC;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@venturekit-pro/tenancy",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Multi-tenant utilities for VentureKit",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,24 +25,29 @@
25
25
  "licenseHeader": "VentureKit Pro Commercial License — production use requires a valid VentureKit Pro license key. See https://venturekit.dev/pricing.",
26
26
  "exports": {
27
27
  ".": {
28
- "import": "./dist/index.js",
29
- "types": "./dist/index.d.ts"
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
30
  }
31
31
  },
32
32
  "dependencies": {
33
- "@venturekit/core": "0.0.13"
33
+ "@venturekit/core": "0.0.14"
34
34
  },
35
35
  "peerDependencies": {
36
- "@venturekit/runtime": "0.0.13"
36
+ "@venturekit/data": "0.0.14",
37
+ "@venturekit/runtime": "0.0.14"
37
38
  },
38
39
  "peerDependenciesMeta": {
40
+ "@venturekit/data": {
41
+ "optional": true
42
+ },
39
43
  "@venturekit/runtime": {
40
44
  "optional": true
41
45
  }
42
46
  },
43
47
  "devDependencies": {
44
48
  "@types/node": "^26.1.0",
45
- "@venturekit/runtime": "0.0.13",
49
+ "@venturekit/data": "0.0.14",
50
+ "@venturekit/runtime": "0.0.14",
46
51
  "typescript": "^5.3.0"
47
52
  },
48
53
  "scripts": {