@antelopejs/interface-dms 0.7.3 → 0.8.0
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/CHANGELOG.md +129 -0
- package/dist/auth/index.d.ts +3 -3
- package/dist/auth/index.js +11 -9
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/internal/jwt-auth-decorator.d.ts +20 -0
- package/dist/auth/internal/jwt-auth-decorator.js +51 -0
- package/dist/auth/internal/jwt-auth-decorator.js.map +1 -0
- package/dist/auth/internal/request-authenticators.d.ts +12 -0
- package/dist/auth/internal/request-authenticators.js +22 -0
- package/dist/auth/internal/request-authenticators.js.map +1 -1
- package/dist/base/activity-feed.d.ts +6 -6
- package/dist/base/block-registry/types.d.ts +16 -0
- package/dist/base/block-types.d.ts +1 -0
- package/dist/base/block-types.js +1 -0
- package/dist/base/block-types.js.map +1 -1
- package/dist/base/chart-card.d.ts +15 -0
- package/dist/base/chart-card.js +4 -0
- package/dist/base/chart-card.js.map +1 -1
- package/dist/base/chart-schemas.d.ts +26 -26
- package/dist/base/code-block.d.ts +3 -3
- package/dist/base/data-types/compare-types.js +19 -13
- package/dist/base/data-types/compare-types.js.map +1 -1
- package/dist/base/data-types/core.d.ts +11 -0
- package/dist/base/data-types/core.js +14 -1
- package/dist/base/data-types/core.js.map +1 -1
- package/dist/base/data-types/default-types.d.ts +112 -2
- package/dist/base/data-types/default-types.js +90 -19
- package/dist/base/data-types/default-types.js.map +1 -1
- package/dist/base/data-types/field-types.d.ts +10 -4
- package/dist/base/data-types/field-types.js +12 -2
- package/dist/base/data-types/field-types.js.map +1 -1
- package/dist/base/data-types/internal/compare-types.d.ts +3 -0
- package/dist/base/data-types/internal/compare-types.js +7 -0
- package/dist/base/data-types/internal/compare-types.js.map +1 -1
- package/dist/base/empty-state.d.ts +4 -4
- package/dist/base/form-block-schema.d.ts +59 -3
- package/dist/base/form-block-schema.js +12 -2
- package/dist/base/form-block-schema.js.map +1 -1
- package/dist/base/form-schema.d.ts +46 -1
- package/dist/base/form-schema.js.map +1 -1
- package/dist/base/form-types.d.ts +56 -13
- package/dist/base/form-types.js +12 -4
- package/dist/base/form-types.js.map +1 -1
- package/dist/base/guard.d.ts +6 -6
- package/dist/base/helpers/archive-mode-helpers.js +6 -2
- package/dist/base/helpers/archive-mode-helpers.js.map +1 -1
- package/dist/base/index.d.ts +1 -0
- package/dist/base/index.js +1 -0
- package/dist/base/index.js.map +1 -1
- package/dist/base/internal/form-schema.js +2 -0
- package/dist/base/internal/form-schema.js.map +1 -1
- package/dist/base/internal/layouts.d.ts +4 -0
- package/dist/base/key-value-list.d.ts +12 -9
- package/dist/base/key-value-list.js +1 -1
- package/dist/base/key-value-list.js.map +1 -1
- package/dist/base/kpi-card.d.ts +39 -5
- package/dist/base/kpi-card.js +18 -6
- package/dist/base/kpi-card.js.map +1 -1
- package/dist/base/layouts.d.ts +24 -0
- package/dist/base/layouts.js.map +1 -1
- package/dist/base/live-toggle.d.ts +11 -0
- package/dist/base/live-toggle.js +8 -1
- package/dist/base/live-toggle.js.map +1 -1
- package/dist/base/page-toolbar.d.ts +158 -0
- package/dist/base/page-toolbar.js +198 -0
- package/dist/base/page-toolbar.js.map +1 -0
- package/dist/base/period-selector.d.ts +9 -2
- package/dist/base/period-selector.js.map +1 -1
- package/dist/base/placeholder.d.ts +2 -2
- package/dist/base/section.d.ts +2 -2
- package/dist/base/stack.d.ts +2 -2
- package/dist/base/stat-group.d.ts +18 -6
- package/dist/base/stat-group.js +9 -0
- package/dist/base/stat-group.js.map +1 -1
- package/dist/base/table-view/column-display.d.ts +9 -5
- package/dist/base/table-view/column-display.js +1 -1
- package/dist/base/table-view/column-display.js.map +1 -1
- package/dist/base/table-view/data-functions.d.ts +26 -7
- package/dist/base/table-view/data-functions.js +10 -6
- package/dist/base/table-view/data-functions.js.map +1 -1
- package/dist/base/table-view/factory.js +24 -13
- package/dist/base/table-view/factory.js.map +1 -1
- package/dist/base/table-view/internal/auth.d.ts +2 -0
- package/dist/base/table-view/internal/auth.js +3 -1
- package/dist/base/table-view/internal/auth.js.map +1 -1
- package/dist/base/table-view/internal/factory-helpers.d.ts +5 -0
- package/dist/base/table-view/internal/factory-helpers.js +55 -11
- package/dist/base/table-view/internal/factory-helpers.js.map +1 -1
- package/dist/base/table-view/internal/guards.js +19 -2
- package/dist/base/table-view/internal/guards.js.map +1 -1
- package/dist/base/table-view/internal/realtime.js +17 -0
- package/dist/base/table-view/internal/realtime.js.map +1 -1
- package/dist/base/table-view/internal/resource-form.js +1 -0
- package/dist/base/table-view/internal/resource-form.js.map +1 -1
- package/dist/base/table-view/internal/row-actions-schema.d.ts +296 -0
- package/dist/base/table-view/internal/row-actions-schema.js +62 -0
- package/dist/base/table-view/internal/row-actions-schema.js.map +1 -0
- package/dist/base/table-view/internal/validation.d.ts +15 -3
- package/dist/base/table-view/internal/validation.js +70 -2
- package/dist/base/table-view/internal/validation.js.map +1 -1
- package/dist/base/table-view/meta.d.ts +8 -0
- package/dist/base/table-view/meta.js +25 -3
- package/dist/base/table-view/meta.js.map +1 -1
- package/dist/base/table-view/options.d.ts +90 -16
- package/dist/base/table-view/options.js.map +1 -1
- package/dist/base/table-view/realtime.d.ts +8 -0
- package/dist/base/table-view/realtime.js.map +1 -1
- package/dist/base/table-view/routes.js +5 -2
- package/dist/base/table-view/routes.js.map +1 -1
- package/dist/base/table-view/schema.d.ts +36 -30
- package/dist/base/table-view/schema.js +11 -57
- package/dist/base/table-view/schema.js.map +1 -1
- package/dist/base/table-view/source.d.ts +1 -1
- package/dist/base/table-view/source.js +4 -1
- package/dist/base/table-view/source.js.map +1 -1
- package/dist/base/table-view.d.ts +2 -2
- package/dist/base/table-view.js.map +1 -1
- package/dist/base/top-list-card.d.ts +5 -5
- package/dist/base/types/action-target.d.ts +77 -20
- package/dist/base/types/confirm-dialog.d.ts +5 -1
- package/dist/base/types/custom-button.d.ts +5 -3
- package/dist/base/types/field-config.d.ts +4 -4
- package/dist/base/types/guards.d.ts +10 -0
- package/dist/base/types/row-action.d.ts +6 -1
- package/dist/base/types/size.d.ts +3 -1
- package/dist/client-ip.d.ts +6 -2
- package/dist/client-ip.js +4 -2
- package/dist/client-ip.js.map +1 -1
- package/dist/component.d.ts +8 -0
- package/dist/component.js.map +1 -1
- package/dist/data-controllers/members.d.ts +2 -5
- package/dist/data-controllers/members.js +38 -9
- package/dist/data-controllers/members.js.map +1 -1
- package/dist/db/internal/tenantLifecycle.model.d.ts +4 -0
- package/dist/db/internal/tenantLifecycle.model.js +26 -0
- package/dist/db/internal/tenantLifecycle.model.js.map +1 -1
- package/dist/db/internal/tenantLifecycle.table.d.ts +1 -0
- package/dist/db/internal/tenantLifecycle.table.js +4 -0
- package/dist/db/internal/tenantLifecycle.table.js.map +1 -1
- package/dist/guards.js +2 -3
- package/dist/guards.js.map +1 -1
- package/dist/hooks.d.ts +47 -0
- package/dist/hooks.js +6 -0
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/invite-membership.d.ts +9 -0
- package/dist/internal/invite-membership.js +25 -0
- package/dist/internal/invite-membership.js.map +1 -1
- package/dist/internal/member-changes.d.ts +24 -0
- package/dist/internal/member-changes.js +66 -0
- package/dist/internal/member-changes.js.map +1 -0
- package/dist/internal/tenant-ownership.d.ts +4 -1
- package/dist/internal/tenant-ownership.js +4 -1
- package/dist/internal/tenant-ownership.js.map +1 -1
- package/dist/invite-extensions.d.ts +0 -9
- package/dist/invite-extensions.js +1 -36
- package/dist/invite-extensions.js.map +1 -1
- package/dist/invite-resolution.js +6 -2
- package/dist/invite-resolution.js.map +1 -1
- package/dist/invites.js +36 -22
- package/dist/invites.js.map +1 -1
- package/dist/layout-banners.d.ts +40 -4
- package/dist/layout-banners.js +25 -3
- package/dist/layout-banners.js.map +1 -1
- package/dist/notifications/internal.d.ts +2 -2
- package/dist/notifications/internal.js.map +1 -1
- package/dist/notifications/sendable.d.ts +3 -2
- package/dist/notifications/sendable.js +2 -1
- package/dist/notifications/sendable.js.map +1 -1
- package/dist/notifications/types.d.ts +9 -0
- package/dist/page/controllers.d.ts +44 -0
- package/dist/page/controllers.js +14 -1
- package/dist/page/controllers.js.map +1 -1
- package/dist/page/internal/layout-filter.d.ts +2 -1
- package/dist/page/internal/layout-filter.js +3 -1
- package/dist/page/internal/layout-filter.js.map +1 -1
- package/dist/page/internal/metadata-helpers.d.ts +7 -0
- package/dist/page/internal/metadata-helpers.js +13 -2
- package/dist/page/internal/metadata-helpers.js.map +1 -1
- package/dist/page/internal/page-registration.d.ts +2 -0
- package/dist/page/internal/page-registration.js.map +1 -1
- package/dist/page/layout-filter.js +31 -10
- package/dist/page/layout-filter.js.map +1 -1
- package/dist/page/metadata.js +29 -24
- package/dist/page/metadata.js.map +1 -1
- package/dist/page/types.d.ts +39 -4
- package/dist/page/types.js.map +1 -1
- package/dist/page.d.ts +1 -1
- package/dist/page.js +2 -1
- package/dist/page.js.map +1 -1
- package/dist/permissions-resolver.d.ts +26 -3
- package/dist/permissions-resolver.js +2 -1
- package/dist/permissions-resolver.js.map +1 -1
- package/dist/permissions.d.ts +7 -3
- package/dist/permissions.js +8 -6
- package/dist/permissions.js.map +1 -1
- package/dist/realtime.d.ts +24 -1
- package/dist/realtime.js +7 -1
- package/dist/realtime.js.map +1 -1
- package/dist/request-tenant.d.ts +17 -13
- package/dist/request-tenant.js +54 -42
- package/dist/request-tenant.js.map +1 -1
- package/dist/tenant-access.d.ts +15 -0
- package/dist/tenant-access.js +11 -1
- package/dist/tenant-access.js.map +1 -1
- package/dist/tenant-lifecycle.d.ts +27 -0
- package/dist/tenant-lifecycle.js +35 -0
- package/dist/tenant-lifecycle.js.map +1 -1
- package/dist/tenant-members.d.ts +26 -0
- package/dist/tenant-members.js +29 -0
- package/dist/tenant-members.js.map +1 -0
- package/dist/tenant-ownership.d.ts +4 -1
- package/dist/tenant-ownership.js +15 -3
- package/dist/tenant-ownership.js.map +1 -1
- package/dist/tenant-scoped-model.d.ts +2 -1
- package/dist/tenant-scoped-model.js +4 -3
- package/dist/tenant-scoped-model.js.map +1 -1
- package/docs/1.core/10.layout-banners.md +57 -2
- package/docs/1.core/2.pages-and-modules.md +20 -11
- package/docs/1.core/4.permissions.md +34 -1
- package/docs/1.core/5.tenancy.md +51 -5
- package/docs/1.core/7.hooks.md +13 -0
- package/docs/1.core/8.realtime.md +24 -3
- package/docs/2.auth/2.authentication.md +3 -3
- package/docs/3.base/1.introduction.md +1 -0
- package/docs/3.base/10.display-blocks.md +20 -2
- package/docs/3.base/2.types.md +7 -5
- package/docs/3.base/3.forms.md +21 -6
- package/docs/3.base/4.tables.md +14 -9
- package/docs/3.base/5.charts-and-widgets.md +8 -4
- package/docs/3.base/6.layout.md +38 -1
- package/docs/3.base/8.data-types.md +14 -7
- package/docs/4.notifications/3.sending.md +4 -3
- package/docs/5.html-render/1.introduction.md +5 -7
- package/package.json +3 -7
package/dist/request-tenant.js
CHANGED
|
@@ -1,58 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getRequestTenantId = getRequestTenantId;
|
|
4
|
-
|
|
4
|
+
exports.resolveRequestTenantId = resolveRequestTenantId;
|
|
5
|
+
const auth_1 = require("./auth");
|
|
5
6
|
const request_authenticators_1 = require("./auth/internal/request-authenticators");
|
|
6
7
|
const constants_1 = require("./constants");
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
8
|
+
const BEARER_SCHEME = /^Bearer\s+(\S+)$/i;
|
|
9
|
+
const pendingResolutions = new WeakMap();
|
|
10
|
+
/**
|
|
11
|
+
* The tenant of the request's verified credential: the one bound by the DMS
|
|
12
|
+
* auth decorators (`AuthUser`, `AuthUserWithPermission`, `IfAuthUser` with a
|
|
13
|
+
* valid token…), the tenant guards or `authenticateRequestPrincipal`.
|
|
14
|
+
* Without a verified credential, `DEFAULT_TENANT_ID`: a token is never read
|
|
15
|
+
* for its tenant before its signature, user and session are checked.
|
|
16
|
+
*
|
|
17
|
+
* Call it once the route's auth decorator has run (in the handler). A
|
|
18
|
+
* parameter or property provider resolved alongside the auth decorator uses
|
|
19
|
+
* {@link resolveRequestTenantId} instead.
|
|
20
|
+
*
|
|
21
|
+
* @throws HTTPResult 401 when the request's authentication failed or its
|
|
22
|
+
* credential changed since
|
|
23
|
+
*/
|
|
24
|
+
function getRequestTenantId(ctx) {
|
|
25
|
+
return (0, request_authenticators_1.getAuthenticatedRequestTenantId)(ctx) ?? constants_1.DEFAULT_TENANT_ID;
|
|
14
26
|
}
|
|
15
|
-
function
|
|
27
|
+
async function verifyRequestTenantId(ctx, token) {
|
|
16
28
|
try {
|
|
17
|
-
const
|
|
18
|
-
if (
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
29
|
+
const data = await auth_1.internal.IfAuthUserAuthenticator(token);
|
|
30
|
+
if (!data)
|
|
31
|
+
return constants_1.DEFAULT_TENANT_ID;
|
|
32
|
+
const user = await auth_1.internal.AuthRawUserValidator(data);
|
|
33
|
+
if (!user || typeof user === "boolean")
|
|
34
|
+
return constants_1.DEFAULT_TENANT_ID;
|
|
35
|
+
const tenantId = data.tenantId || constants_1.DEFAULT_TENANT_ID;
|
|
36
|
+
(0, request_authenticators_1.bindVerifiedRequestPrincipal)(ctx, { userId: user._id, tenantId });
|
|
37
|
+
return tenantId;
|
|
23
38
|
}
|
|
24
39
|
catch {
|
|
25
|
-
return
|
|
40
|
+
return constants_1.DEFAULT_TENANT_ID;
|
|
26
41
|
}
|
|
27
|
-
return undefined;
|
|
28
42
|
}
|
|
29
43
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* The legacy value is only *decoded*: this function does NOT verify the
|
|
35
|
-
* JWT signature. It is safe when the request has already been authenticated
|
|
36
|
-
* upstream (auth decorators such as `@AuthUser`, `@AuthTenantMember`, or the
|
|
37
|
-
* gating performed by `PageController`) — those validators verify the signature
|
|
38
|
-
* before this function runs, so the decoded `tenantId` is authentic.
|
|
39
|
-
*
|
|
40
|
-
* For routes that legitimately accept anonymous traffic (e.g. `@IfAuthUser`),
|
|
41
|
-
* the fallback resolves to `DEFAULT_TENANT_ID` so public layouts can be served.
|
|
42
|
-
*
|
|
43
|
-
* Do NOT introduce new routes that rely on `tenantId` for authorization without
|
|
44
|
-
* upstream auth verification — without signature checks, the claim is forgeable.
|
|
44
|
+
* {@link getRequestTenantId} for a provider that may run before the route's
|
|
45
|
+
* auth decorator (`TenantScopedModel`, a tenant-scoped `Model`): a bearer
|
|
46
|
+
* token not yet verified is verified here, and its tenant bound to the
|
|
47
|
+
* request. An invalid token reads as anonymous: `DEFAULT_TENANT_ID`.
|
|
45
48
|
*/
|
|
46
|
-
function
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
49
|
-
return
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
49
|
+
async function resolveRequestTenantId(ctx) {
|
|
50
|
+
const bound = (0, request_authenticators_1.getAuthenticatedRequestTenantId)(ctx);
|
|
51
|
+
if (bound)
|
|
52
|
+
return bound;
|
|
53
|
+
const header = ctx.rawRequest?.headers?.authorization;
|
|
54
|
+
const token = typeof header === "string" ? header.match(BEARER_SCHEME)?.[1] : undefined;
|
|
55
|
+
if (!token)
|
|
56
|
+
return constants_1.DEFAULT_TENANT_ID;
|
|
57
|
+
const request = ctx.rawRequest;
|
|
58
|
+
const pending = pendingResolutions.get(request);
|
|
59
|
+
if (pending)
|
|
60
|
+
return pending;
|
|
61
|
+
const resolution = verifyRequestTenantId(ctx, token);
|
|
62
|
+
pendingResolutions.set(request, resolution);
|
|
63
|
+
try {
|
|
64
|
+
return await resolution;
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
pendingResolutions.delete(request);
|
|
55
68
|
}
|
|
56
|
-
return constants_1.DEFAULT_TENANT_ID;
|
|
57
69
|
}
|
|
58
70
|
//# sourceMappingURL=request-tenant.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-tenant.js","sourceRoot":"","sources":["../src/request-tenant.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"request-tenant.js","sourceRoot":"","sources":["../src/request-tenant.ts"],"names":[],"mappings":";;AA2BA,gDAEC;AAyBD,wDAmBC;AAvED,iCAAkC;AAClC,mFAGgD;AAChD,2CAAgD;AAEhD,MAAM,aAAa,GAAG,mBAAmB,CAAC;AAE1C,MAAM,kBAAkB,GAAG,IAAI,OAAO,EAAoC,CAAC;AAE3E;;;;;;;;;;;;;GAaG;AACH,SAAgB,kBAAkB,CAAC,GAAmB;IACpD,OAAO,IAAA,wDAA+B,EAAC,GAAG,CAAC,IAAI,6BAAiB,CAAC;AACnE,CAAC;AAED,KAAK,UAAU,qBAAqB,CAClC,GAAmB,EACnB,KAAa;IAEb,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,eAAQ,CAAC,uBAAuB,CAAC,KAAK,CAAC,CAAC;QAC3D,IAAI,CAAC,IAAI;YAAE,OAAO,6BAAiB,CAAC;QACpC,MAAM,IAAI,GAAG,MAAM,eAAQ,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC;QACvD,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,SAAS;YAAE,OAAO,6BAAiB,CAAC;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,6BAAiB,CAAC;QACpD,IAAA,qDAA4B,EAAC,GAAG,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;QAClE,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,6BAAiB,CAAC;IAC3B,CAAC;AACH,CAAC;AAED;;;;;GAKG;AACI,KAAK,UAAU,sBAAsB,CAC1C,GAAmB;IAEnB,MAAM,KAAK,GAAG,IAAA,wDAA+B,EAAC,GAAG,CAAC,CAAC;IACnD,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,aAAa,CAAC;IACtD,MAAM,KAAK,GACT,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5E,IAAI,CAAC,KAAK;QAAE,OAAO,6BAAiB,CAAC;IACrC,MAAM,OAAO,GAAG,GAAG,CAAC,UAAU,CAAC;IAC/B,MAAM,OAAO,GAAG,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IAChD,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,MAAM,UAAU,GAAG,qBAAqB,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IACrD,kBAAkB,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;IAC5C,IAAI,CAAC;QACH,OAAO,MAAM,UAAU,CAAC;IAC1B,CAAC;YAAS,CAAC;QACT,kBAAkB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;AACH,CAAC"}
|
package/dist/tenant-access.d.ts
CHANGED
|
@@ -50,6 +50,21 @@ export declare function RegisterTenantAccessGate(info: TenantAccessGateInfo): vo
|
|
|
50
50
|
* undefined for unauthenticated requests.
|
|
51
51
|
*/
|
|
52
52
|
export declare const CheckTenantAccess: (userId: string | undefined, tenantId: string) => Promise<TenantAccessResult>;
|
|
53
|
+
/** One denying gate, identified independently of its user-facing error code. */
|
|
54
|
+
export interface TenantAccessDenial {
|
|
55
|
+
gateId: string;
|
|
56
|
+
code: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Server-side evaluation of every registered gate, in gate order. Returns all
|
|
60
|
+
* denials (empty when allowed); any exception rejects the whole evaluation.
|
|
61
|
+
* Unlike CheckTenantAccess, an early denial does not hide later blockers.
|
|
62
|
+
* This is an observation, not an authorization receipt or an atomic snapshot
|
|
63
|
+
* of external state. Callers must preserve other holds and revalidate their
|
|
64
|
+
* operation's authorization before effects. Never infer ownership from code.
|
|
65
|
+
* Does not change the first-denial response of user-facing access checks.
|
|
66
|
+
*/
|
|
67
|
+
export declare const EvaluateTenantAccess: (userId: string | undefined, tenantId: string) => Promise<TenantAccessDenial[]>;
|
|
53
68
|
/**
|
|
54
69
|
* Assert that the tenant is accessible; throws a 403 whose body is the
|
|
55
70
|
* denying gate's `code` (a machine-readable i18n key) otherwise.
|
package/dist/tenant-access.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CheckTenantAccess = exports.internal = void 0;
|
|
3
|
+
exports.EvaluateTenantAccess = exports.CheckTenantAccess = exports.internal = void 0;
|
|
4
4
|
exports.RegisterTenantAccessGate = RegisterTenantAccessGate;
|
|
5
5
|
exports.AssertTenantAccess = AssertTenantAccess;
|
|
6
6
|
const interface_api_1 = require("@antelopejs/interface-api");
|
|
@@ -48,6 +48,16 @@ function RegisterTenantAccessGate(info) {
|
|
|
48
48
|
* undefined for unauthenticated requests.
|
|
49
49
|
*/
|
|
50
50
|
exports.CheckTenantAccess = (0, interface_core_1.InterfaceFunction)();
|
|
51
|
+
/**
|
|
52
|
+
* Server-side evaluation of every registered gate, in gate order. Returns all
|
|
53
|
+
* denials (empty when allowed); any exception rejects the whole evaluation.
|
|
54
|
+
* Unlike CheckTenantAccess, an early denial does not hide later blockers.
|
|
55
|
+
* This is an observation, not an authorization receipt or an atomic snapshot
|
|
56
|
+
* of external state. Callers must preserve other holds and revalidate their
|
|
57
|
+
* operation's authorization before effects. Never infer ownership from code.
|
|
58
|
+
* Does not change the first-denial response of user-facing access checks.
|
|
59
|
+
*/
|
|
60
|
+
exports.EvaluateTenantAccess = (0, interface_core_1.InterfaceFunction)();
|
|
51
61
|
/**
|
|
52
62
|
* Assert that the tenant is accessible; throws a 403 whose body is the
|
|
53
63
|
* denying gate's `code` (a machine-readable i18n key) otherwise.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-access.js","sourceRoot":"","sources":["../src/tenant-access.ts"],"names":[],"mappings":";;;AA0DA,4DAEC;
|
|
1
|
+
{"version":3,"file":"tenant-access.js","sourceRoot":"","sources":["../src/tenant-access.ts"],"names":[],"mappings":";;;AA0DA,4DAEC;AA0CD,gDAQC;AA9GD,6DAAuD;AACvD,+DAGoC;AAEpC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAiBlC;;GAEG;AACH,IAAiB,QAAQ,CAIxB;AAJD,WAAiB,QAAQ;IACV,iCAAwB,GAAG,IAAI,iCAAgB,EAEzD,CAAC;AACN,CAAC,EAJgB,QAAQ,wBAAR,QAAQ,QAIxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAgB,wBAAwB,CAAC,IAA0B;IACjE,QAAQ,CAAC,wBAAwB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACnD,CAAC;AAED;;;;GAIG;AACU,QAAA,iBAAiB,GAC5B,IAAA,kCAAiB,GAKd,CAAC;AAQN;;;;;;;;GAQG;AACU,QAAA,oBAAoB,GAC/B,IAAA,kCAAiB,GAKd,CAAC;AAEN;;;GAGG;AACI,KAAK,UAAU,kBAAkB,CACtC,MAA0B,EAC1B,QAAgB;IAEhB,MAAM,MAAM,GAAG,MAAM,IAAA,yBAAiB,EAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,0BAAU,CAAC,qBAAqB,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC"}
|
|
@@ -13,3 +13,30 @@ export declare function runTenantLifecycleOperation<T>(tenantId: string, work: (
|
|
|
13
13
|
* rejection requires preserving data, never timeout-based takeover or reopening.
|
|
14
14
|
*/
|
|
15
15
|
export declare function closeTenantLifecycleAdmission(tenantId: string): Promise<void>;
|
|
16
|
+
/** A non-destructive snapshot; no attempt expires or is retired by observation. */
|
|
17
|
+
export interface TenantLifecycleObservation {
|
|
18
|
+
revision: string;
|
|
19
|
+
barrierId: string | null;
|
|
20
|
+
activeAttemptIds: string[];
|
|
21
|
+
closed: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Observe outside a producer callback: an initiator must not drain itself. */
|
|
24
|
+
export declare function observeTenantLifecycle(tenantId: string): Promise<TenantLifecycleObservation>;
|
|
25
|
+
/**
|
|
26
|
+
* Publish a temporary barrier on the same CAS revision as producer admission.
|
|
27
|
+
* Use a unique identity per phase, never reuse it. Observe the revision BEFORE
|
|
28
|
+
* checking the durable business intent, then attempt this single CAS. After
|
|
29
|
+
* acquisition recheck that intent; release if abandoned. False requires a new
|
|
30
|
+
* observation and intent check, not a blind retry. Unknown outcomes throw.
|
|
31
|
+
* Existing attempts keep running; only an empty observation while this barrier
|
|
32
|
+
* remains owned permits inventory validation. No Stripe calls under a barrier.
|
|
33
|
+
*/
|
|
34
|
+
export declare function pauseTenantLifecycleAdmission(tenantId: string, barrierId: string, expectedRevision: string): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Release this owner's barrier, or invalidate the revision when none is held.
|
|
37
|
+
* The latter revokes a delayed acquisition after its business intent is aborted.
|
|
38
|
+
* Never reopens permanent deletion closure or another owner's barrier (false).
|
|
39
|
+
* Retains every active/uncertain attempt; timeout is not evidence of completion.
|
|
40
|
+
* Publish pending policy before release so newly admitted producers read it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resumeTenantLifecycleAdmission(tenantId: string, barrierId: string): Promise<boolean>;
|
package/dist/tenant-lifecycle.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.runTenantLifecycleOperation = runTenantLifecycleOperation;
|
|
4
4
|
exports.closeTenantLifecycleAdmission = closeTenantLifecycleAdmission;
|
|
5
|
+
exports.observeTenantLifecycle = observeTenantLifecycle;
|
|
6
|
+
exports.pauseTenantLifecycleAdmission = pauseTenantLifecycleAdmission;
|
|
7
|
+
exports.resumeTenantLifecycleAdmission = resumeTenantLifecycleAdmission;
|
|
5
8
|
const node_crypto_1 = require("node:crypto");
|
|
6
9
|
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
7
10
|
const tenantLifecycle_model_1 = require("./db/internal/tenantLifecycle.model");
|
|
@@ -55,4 +58,36 @@ async function runTenantLifecycleOperation(tenantId, work) {
|
|
|
55
58
|
async function closeTenantLifecycleAdmission(tenantId) {
|
|
56
59
|
await (0, interface_database_decorators_1.GetModel)(tenantLifecycle_model_1.TenantLifecycleModel, tenantId).close(tenantId);
|
|
57
60
|
}
|
|
61
|
+
/** Observe outside a producer callback: an initiator must not drain itself. */
|
|
62
|
+
async function observeTenantLifecycle(tenantId) {
|
|
63
|
+
const state = await (0, interface_database_decorators_1.GetModel)(tenantLifecycle_model_1.TenantLifecycleModel, tenantId).state(tenantId);
|
|
64
|
+
return {
|
|
65
|
+
revision: state.revision,
|
|
66
|
+
barrierId: state.barrierId ?? null,
|
|
67
|
+
activeAttemptIds: [...state.activeAttemptIds],
|
|
68
|
+
closed: state.closed,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Publish a temporary barrier on the same CAS revision as producer admission.
|
|
73
|
+
* Use a unique identity per phase, never reuse it. Observe the revision BEFORE
|
|
74
|
+
* checking the durable business intent, then attempt this single CAS. After
|
|
75
|
+
* acquisition recheck that intent; release if abandoned. False requires a new
|
|
76
|
+
* observation and intent check, not a blind retry. Unknown outcomes throw.
|
|
77
|
+
* Existing attempts keep running; only an empty observation while this barrier
|
|
78
|
+
* remains owned permits inventory validation. No Stripe calls under a barrier.
|
|
79
|
+
*/
|
|
80
|
+
async function pauseTenantLifecycleAdmission(tenantId, barrierId, expectedRevision) {
|
|
81
|
+
return (0, interface_database_decorators_1.GetModel)(tenantLifecycle_model_1.TenantLifecycleModel, tenantId).pause(tenantId, barrierId, expectedRevision);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Release this owner's barrier, or invalidate the revision when none is held.
|
|
85
|
+
* The latter revokes a delayed acquisition after its business intent is aborted.
|
|
86
|
+
* Never reopens permanent deletion closure or another owner's barrier (false).
|
|
87
|
+
* Retains every active/uncertain attempt; timeout is not evidence of completion.
|
|
88
|
+
* Publish pending policy before release so newly admitted producers read it.
|
|
89
|
+
*/
|
|
90
|
+
async function resumeTenantLifecycleAdmission(tenantId, barrierId) {
|
|
91
|
+
return (0, interface_database_decorators_1.GetModel)(tenantLifecycle_model_1.TenantLifecycleModel, tenantId).resume(tenantId, barrierId);
|
|
92
|
+
}
|
|
58
93
|
//# sourceMappingURL=tenant-lifecycle.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-lifecycle.js","sourceRoot":"","sources":["../src/tenant-lifecycle.ts"],"names":[],"mappings":";;AAkCA,kEAoBC;AAMD,sEAIC;
|
|
1
|
+
{"version":3,"file":"tenant-lifecycle.js","sourceRoot":"","sources":["../src/tenant-lifecycle.ts"],"names":[],"mappings":";;AAkCA,kEAoBC;AAMD,sEAIC;AAWD,wDAUC;AAWD,sEAUC;AASD,wEAKC;AAxHD,6CAAyC;AACzC,6FAAqE;AACrE,+EAA2E;AAE3E,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAClC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AAOlC;;;;;GAKG;AACH,SAAS,mBAAmB,CAAC,KAAc;IACzC,MAAM,SAAS,GAAI,KAAuC,EAAE,SAAS,CAAC;IACtE,IAAI,OAAO,SAAS,KAAK,UAAU;QAAE,OAAO,KAAK,CAAC;IAClD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACrC,OAAO,MAAM,IAAI,qBAAqB,IAAI,MAAM,GAAG,qBAAqB,CAAC;AAC3E,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,2BAA2B,CAC/C,QAAgB,EAChB,IAAsB;IAEtB,MAAM,KAAK,GAAG,IAAA,wCAAQ,EAAC,4CAAoB,EAAE,QAAQ,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,IAAA,wBAAU,GAAE,CAAC;IAC/B,MAAM,KAAK,CAAC,KAAK,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACvC,IAAI,MAAS,CAAC;IACd,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,IAAI,EAAE,CAAC;IACxB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC;YAC/B,wEAAwE;YACxE,mDAAmD;YACnD,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACjE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,KAAK,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,6BAA6B,CACjD,QAAgB;IAEhB,MAAM,IAAA,wCAAQ,EAAC,4CAAoB,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AACjE,CAAC;AAUD,+EAA+E;AACxE,KAAK,UAAU,sBAAsB,CAC1C,QAAgB;IAEhB,MAAM,KAAK,GAAG,MAAM,IAAA,wCAAQ,EAAC,4CAAoB,EAAE,QAAQ,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC7E,OAAO;QACL,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,SAAS,EAAE,KAAK,CAAC,SAAS,IAAI,IAAI;QAClC,gBAAgB,EAAE,CAAC,GAAG,KAAK,CAAC,gBAAgB,CAAC;QAC7C,MAAM,EAAE,KAAK,CAAC,MAAM;KACrB,CAAC;AACJ,CAAC;AAED;;;;;;;;GAQG;AACI,KAAK,UAAU,6BAA6B,CACjD,QAAgB,EAChB,SAAiB,EACjB,gBAAwB;IAExB,OAAO,IAAA,wCAAQ,EAAC,4CAAoB,EAAE,QAAQ,CAAC,CAAC,KAAK,CACnD,QAAQ,EACR,SAAS,EACT,gBAAgB,CACjB,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACI,KAAK,UAAU,8BAA8B,CAClD,QAAgB,EAChB,SAAiB;IAEjB,OAAO,IAAA,wCAAQ,EAAC,4CAAoB,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;AAC9E,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type MemberChange } from "./hooks";
|
|
2
|
+
/** What one membership or invitation write changes, and the write itself. */
|
|
3
|
+
export interface TenantMembersWrite<T> {
|
|
4
|
+
/**
|
|
5
|
+
* Announced through `Hook.MEMBER_BEING_CHANGED`, in order, before `write`
|
|
6
|
+
* runs. Empty for a write that changes no membership, such as an invitation
|
|
7
|
+
* created, edited or retired.
|
|
8
|
+
*/
|
|
9
|
+
changes: MemberChange[];
|
|
10
|
+
write: () => Promise<T>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The one entry point for writing tenant members and invitations: every
|
|
14
|
+
* insert, update or delete of a `TenantMemberModel` or `UserInviteModel` row
|
|
15
|
+
* goes through it. It admits the write under the tenant's lifecycle (refused
|
|
16
|
+
* while a barrier is held or once the tenant is closed), calls `plan` to read
|
|
17
|
+
* the current state and describe the change, fires `Hook.MEMBER_BEING_CHANGED`
|
|
18
|
+
* for each change (a throwing handler refuses the write, replays included),
|
|
19
|
+
* then runs the write and releases the admission.
|
|
20
|
+
*
|
|
21
|
+
* `plan` runs inside the admission so the change it describes is read from
|
|
22
|
+
* state no paused initiator is validating. Never call this from inside
|
|
23
|
+
* another `changeTenantMembers` or `runTenantLifecycleOperation` callback:
|
|
24
|
+
* each call takes its own admission.
|
|
25
|
+
*/
|
|
26
|
+
export declare function changeTenantMembers<T>(tenantId: string, plan: () => Promise<TenantMembersWrite<T>>): Promise<T>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.changeTenantMembers = changeTenantMembers;
|
|
4
|
+
const hooks_1 = require("./hooks");
|
|
5
|
+
const tenant_lifecycle_1 = require("./tenant-lifecycle");
|
|
6
|
+
/**
|
|
7
|
+
* The one entry point for writing tenant members and invitations: every
|
|
8
|
+
* insert, update or delete of a `TenantMemberModel` or `UserInviteModel` row
|
|
9
|
+
* goes through it. It admits the write under the tenant's lifecycle (refused
|
|
10
|
+
* while a barrier is held or once the tenant is closed), calls `plan` to read
|
|
11
|
+
* the current state and describe the change, fires `Hook.MEMBER_BEING_CHANGED`
|
|
12
|
+
* for each change (a throwing handler refuses the write, replays included),
|
|
13
|
+
* then runs the write and releases the admission.
|
|
14
|
+
*
|
|
15
|
+
* `plan` runs inside the admission so the change it describes is read from
|
|
16
|
+
* state no paused initiator is validating. Never call this from inside
|
|
17
|
+
* another `changeTenantMembers` or `runTenantLifecycleOperation` callback:
|
|
18
|
+
* each call takes its own admission.
|
|
19
|
+
*/
|
|
20
|
+
async function changeTenantMembers(tenantId, plan) {
|
|
21
|
+
return (0, tenant_lifecycle_1.runTenantLifecycleOperation)(tenantId, async () => {
|
|
22
|
+
const { changes, write } = await plan();
|
|
23
|
+
for (const change of changes) {
|
|
24
|
+
await (0, hooks_1.ExecuteHooks)(hooks_1.Hook.MEMBER_BEING_CHANGED, { tenantId, ...change });
|
|
25
|
+
}
|
|
26
|
+
return write();
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=tenant-members.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant-members.js","sourceRoot":"","sources":["../src/tenant-members.ts"],"names":[],"mappings":";;AA4BA,kDAWC;AAvCD,mCAAgE;AAChE,yDAAiE;AAajE;;;;;;;;;;;;;GAaG;AACI,KAAK,UAAU,mBAAmB,CACvC,QAAgB,EAChB,IAA0C;IAE1C,OAAO,IAAA,8CAA2B,EAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QACtD,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,CAAC;QACxC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,MAAM,IAAA,oBAAY,EAAC,YAAI,CAAC,oBAAoB,EAAE,EAAE,QAAQ,EAAE,GAAG,MAAM,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,OAAO,KAAK,EAAE,CAAC;IACjB,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -6,7 +6,10 @@ export interface TenantMembershipPatch {
|
|
|
6
6
|
/** Durable invite delivery; grants membership without replacing later edits. */
|
|
7
7
|
deliveryId?: string;
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
9
|
+
/**
|
|
10
|
+
* Adds the user to the tenant, or sets the roles and ownership of an existing
|
|
11
|
+
* member, through `changeTenantMembers`.
|
|
12
|
+
*/
|
|
10
13
|
export declare function applyTenantOwnership(userModel: UserModel, userId: string, tenantId: string, patch: TenantMembershipPatch): Promise<void>;
|
|
11
14
|
export declare function syncPlatformOwnerOnTenantOwnerChange(userModel: UserModel, userId: string, tenantId: string, nextIsTenantOwner: boolean): Promise<void>;
|
|
12
15
|
export declare function clearPlatformOwnerOnMemberRemoval(userModel: UserModel, userId: string, tenantId: string, removedMemberWasTenantOwner: boolean): Promise<void>;
|
package/dist/tenant-ownership.js
CHANGED
|
@@ -3,11 +3,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.applyTenantOwnership = applyTenantOwnership;
|
|
4
4
|
exports.syncPlatformOwnerOnTenantOwnerChange = syncPlatformOwnerOnTenantOwnerChange;
|
|
5
5
|
exports.clearPlatformOwnerOnMemberRemoval = clearPlatformOwnerOnMemberRemoval;
|
|
6
|
-
const
|
|
6
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
7
|
+
const tenantMembers_model_1 = require("./db/models/tenantMembers.model");
|
|
8
|
+
const tenant_members_1 = require("./tenant-members");
|
|
9
|
+
const member_changes_1 = require("./internal/member-changes");
|
|
7
10
|
const tenant_ownership_1 = require("./internal/tenant-ownership");
|
|
8
|
-
/**
|
|
11
|
+
/**
|
|
12
|
+
* Adds the user to the tenant, or sets the roles and ownership of an existing
|
|
13
|
+
* member, through `changeTenantMembers`.
|
|
14
|
+
*/
|
|
9
15
|
async function applyTenantOwnership(userModel, userId, tenantId, patch) {
|
|
10
|
-
await (0,
|
|
16
|
+
await (0, tenant_members_1.changeTenantMembers)(tenantId, async () => {
|
|
17
|
+
const existing = await (0, interface_database_decorators_1.GetModel)(tenantMembers_model_1.TenantMemberModel, tenantId).getByUser(userId);
|
|
18
|
+
return {
|
|
19
|
+
changes: (0, member_changes_1.describeMembershipPatch)(existing, userId, patch),
|
|
20
|
+
write: () => (0, tenant_ownership_1.applyAdmittedTenantOwnership)(userModel, userId, tenantId, patch),
|
|
21
|
+
};
|
|
22
|
+
});
|
|
11
23
|
}
|
|
12
24
|
async function syncPlatformOwnerOnTenantOwnerChange(userModel, userId, tenantId, nextIsTenantOwner) {
|
|
13
25
|
await (0, tenant_ownership_1.mirrorPlatformOwner)(userModel, userId, tenantId, nextIsTenantOwner);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-ownership.js","sourceRoot":"","sources":["../src/tenant-ownership.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"tenant-ownership.js","sourceRoot":"","sources":["../src/tenant-ownership.ts"],"names":[],"mappings":";;AAsBA,oDAgBC;AAED,oFAOC;AAED,8EAQC;AAxDD,6FAAqE;AACrE,yEAAoE;AACpE,qDAAuD;AACvD,8DAAoE;AACpE,kEAGqC;AAUrC;;;GAGG;AACI,KAAK,UAAU,oBAAoB,CACxC,SAAoB,EACpB,MAAc,EACd,QAAgB,EAChB,KAA4B;IAE5B,MAAM,IAAA,oCAAmB,EAAC,QAAQ,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,QAAQ,GAAG,MAAM,IAAA,wCAAQ,EAAC,uCAAiB,EAAE,QAAQ,CAAC,CAAC,SAAS,CACpE,MAAM,CACP,CAAC;QACF,OAAO;YACL,OAAO,EAAE,IAAA,wCAAuB,EAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC;YACzD,KAAK,EAAE,GAAG,EAAE,CACV,IAAA,+CAA4B,EAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC;SACnE,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC;AAEM,KAAK,UAAU,oCAAoC,CACxD,SAAoB,EACpB,MAAc,EACd,QAAgB,EAChB,iBAA0B;IAE1B,MAAM,IAAA,sCAAmB,EAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,CAAC,CAAC;AAC5E,CAAC;AAEM,KAAK,UAAU,iCAAiC,CACrD,SAAoB,EACpB,MAAc,EACd,QAAgB,EAChB,2BAAoC;IAEpC,IAAI,CAAC,2BAA2B;QAAE,OAAO;IACzC,MAAM,IAAA,sCAAmB,EAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -2,7 +2,8 @@ import { type Class } from "@antelopejs/interface-core/decorators";
|
|
|
2
2
|
import { type DataModel } from "@antelopejs/interface-database-decorators";
|
|
3
3
|
/**
|
|
4
4
|
* Parameter/property decorator that injects `GetModel(cl, currentTenantId)`,
|
|
5
|
-
* where `currentTenantId` is
|
|
5
|
+
* where `currentTenantId` is the tenant of the request's verified credential
|
|
6
|
+
* ({@link resolveRequestTenantId}), `DEFAULT_TENANT_ID` without one.
|
|
6
7
|
*
|
|
7
8
|
* Use on a `Model`-typed parameter of a route handler or DataAPI field. Only
|
|
8
9
|
* applicable when the target table is registered in the `dms-tenant` schema
|
|
@@ -7,15 +7,16 @@ const interface_database_decorators_1 = require("@antelopejs/interface-database-
|
|
|
7
7
|
const request_tenant_1 = require("./request-tenant");
|
|
8
8
|
/**
|
|
9
9
|
* Parameter/property decorator that injects `GetModel(cl, currentTenantId)`,
|
|
10
|
-
* where `currentTenantId` is
|
|
10
|
+
* where `currentTenantId` is the tenant of the request's verified credential
|
|
11
|
+
* ({@link resolveRequestTenantId}), `DEFAULT_TENANT_ID` without one.
|
|
11
12
|
*
|
|
12
13
|
* Use on a `Model`-typed parameter of a route handler or DataAPI field. Only
|
|
13
14
|
* applicable when the target table is registered in the `dms-tenant` schema
|
|
14
15
|
* (or any schema with one instance per tenant).
|
|
15
16
|
*/
|
|
16
17
|
exports.TenantScopedModel = (0, decorators_1.MakeParameterAndPropertyDecorator)((target, key, index, cl) => {
|
|
17
|
-
(0, interface_api_1.SetParameterProvider)(target, key, index, (ctx) => {
|
|
18
|
-
return (0, interface_database_decorators_1.GetModel)(cl, (0, request_tenant_1.
|
|
18
|
+
(0, interface_api_1.SetParameterProvider)(target, key, index, async (ctx) => {
|
|
19
|
+
return (0, interface_database_decorators_1.GetModel)(cl, await (0, request_tenant_1.resolveRequestTenantId)(ctx));
|
|
19
20
|
});
|
|
20
21
|
});
|
|
21
22
|
//# sourceMappingURL=tenant-scoped-model.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant-scoped-model.js","sourceRoot":"","sources":["../src/tenant-scoped-model.ts"],"names":[],"mappings":";;;AAAA,6DAGmC;AACnC,sEAG+C;AAC/C,6FAGmD;AACnD,
|
|
1
|
+
{"version":3,"file":"tenant-scoped-model.js","sourceRoot":"","sources":["../src/tenant-scoped-model.ts"],"names":[],"mappings":";;;AAAA,6DAGmC;AACnC,sEAG+C;AAC/C,6FAGmD;AACnD,qDAA0D;AAE1D;;;;;;;;GAQG;AACU,QAAA,iBAAiB,GAAG,IAAA,8CAAiC,EAChE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,EAA8C,EAAE,EAAE;IACrE,IAAA,oCAAoB,EAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,GAAmB,EAAE,EAAE;QACrE,OAAO,IAAA,wCAAQ,EAAC,EAAE,EAAE,MAAM,IAAA,uCAAsB,EAAC,GAAG,CAAC,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC,CACF,CAAC"}
|
|
@@ -7,7 +7,7 @@ description: "A layout banner is a global message the backend adds under the das
|
|
|
7
7
|
|
|
8
8
|
## Overview
|
|
9
9
|
|
|
10
|
-
A layout banner is a global message the backend adds under the dashboard header: a colored strip carrying a line of text and its link buttons, or a component of your frontend layer. It renders on
|
|
10
|
+
A layout banner is a global message the backend adds under the dashboard header: a colored strip carrying a line of text and its link buttons, or a component of your frontend layer. It renders on the pages its [`scope`](#scope) selects — by default the application's pages, leaving the module area under `/modules` out — and banners stack by `order`.
|
|
11
11
|
|
|
12
12
|
Whether a banner shows is decided per request, on the server, while the site layout is built. The answer is part of the server-rendered page, so a banner never flashes in or out after hydration.
|
|
13
13
|
|
|
@@ -92,6 +92,60 @@ RegisterLayoutBanner({
|
|
|
92
92
|
|
|
93
93
|
Re-registering a `key` replaces the banner.
|
|
94
94
|
|
|
95
|
+
## Scope
|
|
96
|
+
|
|
97
|
+
A banner is about the application being built — eighteen overdue invoices, a stock alert — so by default it shows on the application's pages only, and stays out of the **module area**: every page under `MODULE_URL_PREFIX` (`/modules`), that is the modules page, the module store and each module's own back-office pages. A banner about the workspace itself, which must follow the user into a module — billing past due, a suspended workspace, a maintenance window — sets `scope: LayoutBannerScope.EVERYWHERE`.
|
|
98
|
+
|
|
99
|
+
`scope` is a plain option of the banner declaration, the same on the three kinds of banner — a text banner, a resolved banner and a component banner — with the same values and the same `"app"` default:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import {
|
|
103
|
+
LayoutBannerScope,
|
|
104
|
+
LayoutBannerVariant,
|
|
105
|
+
RegisterLayoutBanner,
|
|
106
|
+
} from "@antelopejs/interface-dms/layout-banners";
|
|
107
|
+
|
|
108
|
+
// A text banner about the application: the default scope, written out.
|
|
109
|
+
RegisterLayoutBanner({
|
|
110
|
+
key: "my-app:overdue-invoices",
|
|
111
|
+
variant: LayoutBannerVariant.WARNING,
|
|
112
|
+
scope: LayoutBannerScope.APP,
|
|
113
|
+
text: "$my_app.banners.overdue_invoices",
|
|
114
|
+
actions: [{ label: "$my_app.banners.review", to: "/finance/invoices" }],
|
|
115
|
+
visible: async ({ tenantId }) => hasOverdueInvoices(tenantId),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// A resolved banner about the workspace: it follows the user into /modules.
|
|
119
|
+
RegisterLayoutBanner({
|
|
120
|
+
key: "saas:past-due",
|
|
121
|
+
variant: LayoutBannerVariant.ERROR,
|
|
122
|
+
scope: LayoutBannerScope.EVERYWHERE,
|
|
123
|
+
resolve: pastDueContent,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// A component banner about the workspace, also shown inside /modules.
|
|
127
|
+
RegisterLayoutBanner({
|
|
128
|
+
key: "my-app:maintenance",
|
|
129
|
+
variant: LayoutBannerVariant.INFO,
|
|
130
|
+
scope: LayoutBannerScope.EVERYWHERE,
|
|
131
|
+
component: "MyAppMaintenanceBanner",
|
|
132
|
+
props: { statusUrl: "https://status.example.com" },
|
|
133
|
+
visible: async () => isMaintenanceScheduled(),
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
| `scope` | Shows on |
|
|
138
|
+
| ------- | -------- |
|
|
139
|
+
| `"app"` (default) | Every page outside the module area. |
|
|
140
|
+
| `"everywhere"` | Every page of the dashboard layout, module area included. |
|
|
141
|
+
| `"modules"` | The module area only. |
|
|
142
|
+
|
|
143
|
+
The scope is decided by the page, not by the request: the site layout is fetched once and shared by every page, so each banner carries its scope and the dashboard applies it against the current route — in the server-rendered page, so a banner never flashes in or out after hydration, and again on each client-side navigation, so moving in or out of `/modules` adds or removes it without re-fetching anything. A banner's `visible` or `resolve` therefore does not see the route; the scope is the only per-page filter. A dismissal is per key, whatever page the banner was closed on.
|
|
144
|
+
|
|
145
|
+
::warning
|
|
146
|
+
**Behaviour change.** Before `scope` existed every banner showed on every page, module area included. A banner registered without `scope` now stays out of `/modules`: give `scope: LayoutBannerScope.EVERYWHERE` to the ones reporting a workspace-wide state.
|
|
147
|
+
::
|
|
148
|
+
|
|
95
149
|
## `LayoutBannerInfo`
|
|
96
150
|
|
|
97
151
|
| Field | Type | Description |
|
|
@@ -105,6 +159,7 @@ Re-registering a `key` replaces the banner.
|
|
|
105
159
|
| `order` | `number` | Stack order, ascending from the top. Defaults to `0`; ties keep the registration order. |
|
|
106
160
|
| `icon` | `string` | Leading icon. Defaults to one matching the variant. |
|
|
107
161
|
| `dismissible` | `boolean` | Whether the user can close the banner. Defaults to `false`. |
|
|
162
|
+
| `scope` | `LayoutBannerScope` | `"app"`, `"everywhere"` or `"modules"`: the pages the banner shows on. Defaults to `"app"`, every page outside `/modules`. See [Scope](#scope). |
|
|
108
163
|
| `visible` | `LayoutBannerVisibility` | Decides per request whether a `text` or `component` banner shows. Absent means always. |
|
|
109
164
|
| `resolve` | `LayoutBannerResolver` | Builds `{ text, actions? }` per request, or `undefined` to hide the banner. Exclusive with `text`, `component` and `visible`. |
|
|
110
165
|
|
|
@@ -128,6 +183,6 @@ The resolvers run whenever a client fetches its site layout — on a full page l
|
|
|
128
183
|
|
|
129
184
|
## Rendering
|
|
130
185
|
|
|
131
|
-
Banners render under the header of the dashboard layout, full width, in `order`, a text banner's `actions` on its right. Each one is its own live region: `error` banners carry `role="alert"`, `info` and `warning` banners `role="status"`.
|
|
186
|
+
Banners render under the header of the dashboard layout, on the pages their scope covers, full width, in `order`, a text banner's `actions` on its right. Each one is its own live region: `error` banners carry `role="alert"`, `info` and `warning` banners `role="status"`.
|
|
132
187
|
|
|
133
188
|
A dismissible banner shows a close button. The dismissal lasts for the browser session and is kept in the `dms-dismissed-banners` cookie, which the server reads too, so a dismissed banner is already absent from the server-rendered page. A banner that is not dismissible ignores any dismissal recorded under its key.
|
|
@@ -62,6 +62,7 @@ The third argument is the page layout, a `ComponentInfo` produced by a layout bu
|
|
|
62
62
|
| `order` | `number` | Sort order among siblings. |
|
|
63
63
|
| `icon` | `string` | Icon name. |
|
|
64
64
|
| `hidden` | `boolean` | Keep the entry out of the menu. Inherited from the resolved category when that category is hidden. |
|
|
65
|
+
| `parent` | `ControllerClass` | The page this one is a detail of: the breadcrumb shows it before this page ("Operations / Projects / <record>"), its route parameters filled from this page's own. Resolved at registration, served as `parentSlug`. Pages only. |
|
|
65
66
|
| `permission` | `Partial<Permission> \| Action` | Override the auto-derived page permission, or bind the page to an existing component `Action`. See [Permissions](/docs/interfaces/core/permissions). |
|
|
66
67
|
| `publicAccess` | `boolean` | Serve the page without authentication. Inherited from the resolved category. |
|
|
67
68
|
| `authOnly` | `boolean` | Require a logged-in user but no permission check. Inherited from the resolved category. |
|
|
@@ -212,7 +213,7 @@ Call `RegisterModule` before any page that references the module id: a page reso
|
|
|
212
213
|
| `description` | `string` | Description carried on the module root. |
|
|
213
214
|
| `icon` | `string` | Icon name. |
|
|
214
215
|
| `landingPage` | `string` | Id of the page the module opens on; `/modules/<id>` redirects to it, query and hash kept. Defaults to the first page by `order`. |
|
|
215
|
-
| `catalogCategory` | `string` | Category the module is filed under in the module catalog (`"Content"`, `"Developer"`…), may be a `$` i18n key; without one
|
|
216
|
+
| `catalogCategory` | `string` | Category the module is filed under in the module catalog (`"Content"`, `"Developer"`…), may be a `$` i18n key. The Modules page and the store both file the module under it; without one, a store module takes the category of the store catalog, and any other goes under "Other". Unrelated to menu categories (`MenuOptions.category`, `defaultCategory`). Formerly `category`. |
|
|
216
217
|
| `defaultCategory` | `{ displayName?, icon?, order?, urlSlug? }` | Groups the module's pages that declare `module` without an explicit `category`. Without it, those pages attach directly to the module root. |
|
|
217
218
|
|
|
218
219
|
The generated default category uses `urlSlug` `"pages"` unless you override it, and falls back to the label `"$menu.section.pages"` when `displayName` is empty or omitted.
|
|
@@ -388,12 +389,20 @@ interface PageLayout<T = unknown> {
|
|
|
388
389
|
Registering a page installs a `PageLayoutHandler` for its `fullSlug`:
|
|
389
390
|
|
|
390
391
|
```typescript
|
|
391
|
-
|
|
392
|
-
user: User | undefined
|
|
393
|
-
memberModel: TenantMemberModel
|
|
394
|
-
roleModel: RoleModel
|
|
395
|
-
tenantId: string
|
|
396
|
-
|
|
392
|
+
interface PageLayoutRequest {
|
|
393
|
+
user: User | undefined;
|
|
394
|
+
memberModel: TenantMemberModel;
|
|
395
|
+
roleModel: RoleModel;
|
|
396
|
+
tenantId: string;
|
|
397
|
+
/** The page URL's query, for header-button availability. */
|
|
398
|
+
query?: Readonly<Record<string, string>>;
|
|
399
|
+
/** The page URL's route parameters (`:id`), likewise. */
|
|
400
|
+
params?: Readonly<Record<string, string>>;
|
|
401
|
+
/** Passed to the permissions resolvers: `{ preview }` when a role preview reads the layout. */
|
|
402
|
+
resolverContext?: PermissionsResolverContext;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
type PageLayoutHandler = (request: PageLayoutRequest) => Promise<PageLayout>;
|
|
397
406
|
```
|
|
398
407
|
|
|
399
408
|
The handler drops the components the caller has no permission for and applies each component's own filter. Native upload tokens are bound to the exact page/component mount during page registration, not stamped by this handler; upload and metadata requests still check live authorization ([File storage](/docs/components/file-storage)). A page with `publicAccess`, `authOnly` or `noComponentPermissions` skips the filtering step and returns every component. Look a handler up with `GetPageLayoutBySlug(slug)`, which returns `undefined` for an unknown slug:
|
|
@@ -416,12 +425,12 @@ async function renderLayout(
|
|
|
416
425
|
tenantId: string,
|
|
417
426
|
): Promise<PageLayout | undefined> {
|
|
418
427
|
const handler = GetPageLayoutBySlug(slug);
|
|
419
|
-
return handler?.(
|
|
428
|
+
return handler?.({
|
|
420
429
|
user,
|
|
421
|
-
GetModel(TenantMemberModel, tenantId),
|
|
422
|
-
GetModel(RoleModel, tenantId),
|
|
430
|
+
memberModel: GetModel(TenantMemberModel, tenantId),
|
|
431
|
+
roleModel: GetModel(RoleModel, tenantId),
|
|
423
432
|
tenantId,
|
|
424
|
-
);
|
|
433
|
+
});
|
|
425
434
|
}
|
|
426
435
|
```
|
|
427
436
|
|