@happyvertical/smrt-users 0.38.8 → 0.38.10
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.
- package/AGENTS.md +64 -1
- package/dist/chunks/{TerminalAuthService-B7799z9s.js → TerminalAuthService-BuZe5vEm.js} +112 -9
- package/dist/chunks/TerminalAuthService-BuZe5vEm.js.map +1 -0
- package/dist/collections/RoleCollection.d.ts +10 -0
- package/dist/collections/RoleCollection.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/manifest.json +15 -4
- package/dist/models/Role.d.ts +11 -0
- package/dist/models/Role.d.ts.map +1 -1
- package/dist/services/PermissionResolver.d.ts +69 -2
- package/dist/services/PermissionResolver.d.ts.map +1 -1
- package/dist/services/SessionService.d.ts.map +1 -1
- package/dist/smrt-knowledge.json +12 -6
- package/dist/sveltekit.js +1 -1
- package/package.json +8 -8
- package/dist/chunks/TerminalAuthService-B7799z9s.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -11,7 +11,7 @@ Multi-tenant user management with RBAC, hierarchical tenants, session handling,
|
|
|
11
11
|
| Tenant | **STI** + hierarchical parent-child. `hierarchyPath` (materialized path), `hierarchyLevel`. Max depth 10. |
|
|
12
12
|
| Session | Server-side. Secure UUID. TTL in **seconds** (not ms). Status auto-updates to EXPIRED on access. |
|
|
13
13
|
| MagicLinkToken | Single-use email login token. Backed by `MagicLinkService`. |
|
|
14
|
-
| Role | `tenantId = null` → system role (available to all tenants). `isSystem: true` blocks deletion. |
|
|
14
|
+
| Role | `tenantId = null` → system role (available to all tenants). `isSystem: true` blocks deletion. `inheritsToDescendants: true` (default false) opts the role's membership authority into descendant tenants. |
|
|
15
15
|
| Permission | Slug format: `resource.action`. Parsed by PermissionResolver. |
|
|
16
16
|
| Membership | User + Tenant + Role junction. UNIQUE(userId, tenantId). |
|
|
17
17
|
| Group | Team within a tenant. Multiple roles via GroupRole. |
|
|
@@ -44,6 +44,48 @@ The hard block reflects the tenant cascade's **net** resolution, not an
|
|
|
44
44
|
unconditional union of every DENY in the chain — so a more-specific tenant GRANT
|
|
45
45
|
(e.g. a child sub-tenant re-granting a permission its parent DENYs) still wins.
|
|
46
46
|
|
|
47
|
+
### Membership selection — hierarchical inheritance (opt-in, #1866)
|
|
48
|
+
|
|
49
|
+
Which membership feeds step 2 above:
|
|
50
|
+
|
|
51
|
+
1. **A direct membership row in the target tenant always pins resolution to
|
|
52
|
+
itself** — active rows resolve normally; an inactive (pending/suspended)
|
|
53
|
+
row resolves to the **empty set**. A direct row therefore *attenuates*
|
|
54
|
+
rather than unions: "viewer here, despite being network admin" gives
|
|
55
|
+
viewer, and a suspension in the child is effective even for users holding
|
|
56
|
+
inheritable authority on an ancestor.
|
|
57
|
+
2. **No direct row** → the resolver walks the tenant's ancestors (nearest
|
|
58
|
+
first, from `hierarchyPath`) and resolves through the **nearest ACTIVE
|
|
59
|
+
ancestor membership whose role has `inheritsToDescendants: true`**.
|
|
60
|
+
Unflagged or inactive ancestor memberships are skipped (they neither confer
|
|
61
|
+
nor block); there is **no union across the chain** — the nearest flagged
|
|
62
|
+
membership alone is used. To attenuate a specific descendant, create a
|
|
63
|
+
direct membership there or use a tenant-level DENY.
|
|
64
|
+
3. **No qualifying ancestor** → empty set (byte-identical to the
|
|
65
|
+
pre-inheritance resolver; with no role flagged, nothing changes).
|
|
66
|
+
|
|
67
|
+
All later layers run unchanged against the **target** tenant: the tenant
|
|
68
|
+
cascade and tenant-DENY hard block come from the target tenant (a child can
|
|
69
|
+
carve authority out of an inherited role), group roles stay exact-tenant (only
|
|
70
|
+
target-tenant groups contribute; ancestor groups never flow down), and
|
|
71
|
+
membership GRANT/DENY overrides travel with the ancestor membership used.
|
|
72
|
+
`PermissionResolutionResult.inheritedFromTenantId` reports the ancestor tenant
|
|
73
|
+
when inheritance was used (`null` for direct resolution).
|
|
74
|
+
|
|
75
|
+
Safety: resolution is bounded by `MAX_TENANT_HIERARCHY_DEPTH`; malformed
|
|
76
|
+
`hierarchyPath` values fail closed to the empty set — too deep,
|
|
77
|
+
self-referential, duplicate ancestors, or inconsistent with the actual
|
|
78
|
+
`parentTenantId` chain (the path is verified link-by-link against the loaded
|
|
79
|
+
ancestor rows before it is trusted as an authorization source). Tenant `status` is not consulted (parity with direct
|
|
80
|
+
resolution). Caching: a long-lived `(user, tenant)` permission cache must also
|
|
81
|
+
invalidate on ancestor-membership changes and on `Role.inheritsToDescendants`
|
|
82
|
+
flips — request-scoped caches (the common pattern) are unaffected.
|
|
83
|
+
|
|
84
|
+
Flag roles at seed time with
|
|
85
|
+
`seedSystemRoles({ inheritsToDescendants: ['owner', 'admin'] })` (additive:
|
|
86
|
+
listed slugs are flagged, omitted slugs are never unflagged; unknown slugs
|
|
87
|
+
throw). The default seed leaves every role exact-tenant.
|
|
88
|
+
|
|
47
89
|
## Operation Permission Guards
|
|
48
90
|
|
|
49
91
|
- Use `assertOperationPermission()` for hand-written mutations in SvelteKit form
|
|
@@ -54,9 +96,25 @@ unconditional union of every DENY in the chain — so a more-specific tenant GRA
|
|
|
54
96
|
- `assertOperationPermission()` throws fail-closed by default. Use
|
|
55
97
|
`{ onDeny: 'return' }`, `checkOperationPermission()`, or
|
|
56
98
|
`hasOperationPermission()` when a structured/boolean result is needed.
|
|
99
|
+
- **Resource-tenant calling convention**: for resource-anchored authorization,
|
|
100
|
+
pass the **resource's** tenant id — `tenantId: resourceTenantId` — not the
|
|
101
|
+
session's current tenant. With `Role.inheritsToDescendants` flagged, a
|
|
102
|
+
root-tenant admin then passes for any descendant resource with no app-side
|
|
103
|
+
authority logic (and no membership fan-out), while per-child delegation and
|
|
104
|
+
DENY precedence keep working. Do NOT pass a session-scoped `membership`
|
|
105
|
+
alongside a different resource `tenantId` — a membership/tenant mismatch
|
|
106
|
+
fails closed by design; omit `membership` and let the resolver look it up.
|
|
57
107
|
- System context and super-admin bypass context are honored for parity with
|
|
58
108
|
Postgres RLS. Pass `{ allowSuperAdminBypass: false }` on money-class or
|
|
59
109
|
separation-of-duties operations that must require an explicit permission grant.
|
|
110
|
+
- **Postgres RLS and membership inheritance**: RLS policies check the
|
|
111
|
+
session-injected `smrt.permissions` list (resolved app-side by
|
|
112
|
+
`PermissionResolver`), so a session whose `smrt.tenant_id` IS the child
|
|
113
|
+
tenant gets inherited authority in RLS automatically. But RLS row filtering
|
|
114
|
+
stays bound to the session's tenant setting — a root-tenant session acting
|
|
115
|
+
on child-tenant rows is authorized by the app-level guard (resource-tenant
|
|
116
|
+
convention above), not by RLS. This is a documented divergence, mirroring
|
|
117
|
+
how #1829 handled bypass parity.
|
|
60
118
|
- Seed role mappings with `RolePermissionCollection.seedRolePermissions()` or
|
|
61
119
|
`RoleCollection.seedSystemRoles({ seedPermissions: true })`. The default
|
|
62
120
|
matrix maps owner/admin to all catalog permissions, member to read/create for
|
|
@@ -74,6 +132,11 @@ unconditional union of every DENY in the chain — so a more-specific tenant GRA
|
|
|
74
132
|
- `moveToParent()` updates tenant + ALL descendants' paths/levels
|
|
75
133
|
- `cascadePermissions` (parent pushes down) + `inheritPermissions` (child accepts) — both must be true
|
|
76
134
|
- `getTree(rootId?)` returns nested structure for UI
|
|
135
|
+
- Two independent downward flows: the `TenantPermissionOverride` **cascade**
|
|
136
|
+
(tenant-level permission config, flags above) and **membership-role
|
|
137
|
+
inheritance** (`Role.inheritsToDescendants`, per-role opt-in — see
|
|
138
|
+
"Membership selection" above). The cascade flags do not gate membership
|
|
139
|
+
inheritance.
|
|
77
140
|
|
|
78
141
|
## SvelteKit Integration
|
|
79
142
|
|