@astram/identity 0.2.0 → 0.3.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/dist/company-membership.object.d.ts.map +1 -1
- package/dist/company-membership.object.js +7 -0
- package/dist/company.object.d.ts.map +1 -1
- package/dist/company.object.js +5 -0
- package/dist/features/authz/catalog-bootstrap.provider.d.ts +6 -1
- package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
- package/dist/features/authz/catalog-bootstrap.provider.js +28 -4
- package/dist/features/authz/grant-admin.controller.d.ts +4 -3
- package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.controller.js +22 -9
- package/dist/features/authz/grant-admin.service.d.ts +19 -3
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +43 -5
- package/dist/features/authz/record-authorization.d.ts +11 -2
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/record-authorization.js +11 -3
- package/dist/features/cross-tenant/grant-authorize.d.ts.map +1 -1
- package/dist/features/cross-tenant/grant-authorize.js +3 -0
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +136 -4
- package/dist/features/membership/membership-checker.d.ts +43 -0
- package/dist/features/membership/membership-checker.d.ts.map +1 -0
- package/dist/features/membership/membership-checker.js +25 -0
- package/dist/features/policy/policy.controller.d.ts +46 -0
- package/dist/features/policy/policy.controller.d.ts.map +1 -0
- package/dist/features/policy/policy.controller.js +150 -0
- package/dist/features/policy/policy.service.d.ts +129 -0
- package/dist/features/policy/policy.service.d.ts.map +1 -0
- package/dist/features/policy/policy.service.js +142 -0
- package/dist/features/scope/scope-closure.service.d.ts +82 -0
- package/dist/features/scope/scope-closure.service.d.ts.map +1 -0
- package/dist/features/scope/scope-closure.service.js +222 -0
- package/dist/features/scope/scope-subtree.resolver.d.ts +47 -0
- package/dist/features/scope/scope-subtree.resolver.d.ts.map +1 -0
- package/dist/features/scope/scope-subtree.resolver.js +24 -0
- package/dist/features/share/share.controller.d.ts +35 -0
- package/dist/features/share/share.controller.d.ts.map +1 -0
- package/dist/features/share/share.controller.js +143 -0
- package/dist/features/share/share.service.d.ts +90 -0
- package/dist/features/share/share.service.d.ts.map +1 -0
- package/dist/features/share/share.service.js +58 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -1
- package/dist/membership-posting.object.d.ts.map +1 -1
- package/dist/membership-posting.object.js +10 -0
- package/dist/objects.d.ts.map +1 -1
- package/dist/objects.js +2 -1
- package/dist/record-share.object.d.ts +24 -0
- package/dist/record-share.object.d.ts.map +1 -0
- package/dist/record-share.object.js +60 -0
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +10 -0
- package/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +10 -0
- package/package.json +4 -4
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
12
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.ShareController = void 0;
|
|
16
|
+
/**
|
|
17
|
+
* `ShareController` — the OWNER-SHARE API (§9.2 / list-scoping engine Phase 6).
|
|
18
|
+
*
|
|
19
|
+
* Three routes over {@link ShareService}, gated `@Classified({ typeKey: 'record_share', ... })`
|
|
20
|
+
* (a fixed control-plane meta-type, mirroring `cross_tenant_grant`): `share` gates create/revoke,
|
|
21
|
+
* `read` gates the list. The route gate is COARSE ("may you use the share surface"); the REAL
|
|
22
|
+
* per-record authority — only the LITERAL owner may share/unshare/list — is enforced inside the
|
|
23
|
+
* `RecordShareWriter`'s fetch-then-gate owner-bound (a grantee can never re-share).
|
|
24
|
+
*
|
|
25
|
+
* ⛔ The sharer + tenant are ALWAYS `req.principal` (the enforcement guard attached it), NEVER a
|
|
26
|
+
* body field — a forged owner/tenant cannot reach the writer. ⛔ The list returns grantee NAMES
|
|
27
|
+
* (resolved server-side); a uuid rides only as the revoke API key.
|
|
28
|
+
*/
|
|
29
|
+
const common_1 = require("@nestjs/common");
|
|
30
|
+
const guard_1 = require("@astram/guard");
|
|
31
|
+
const share_service_js_1 = require("./share.service.js");
|
|
32
|
+
/** Validate + narrow a share/unshare body to a {@link ShareRequest} (grantee + action only). */
|
|
33
|
+
function toShareRequest(body) {
|
|
34
|
+
const b = (body ?? {});
|
|
35
|
+
if (!b.typeKey || typeof b.typeKey !== 'string')
|
|
36
|
+
throw new common_1.BadRequestException('typeKey required');
|
|
37
|
+
if (!b.recordId || typeof b.recordId !== 'string')
|
|
38
|
+
throw new common_1.BadRequestException('recordId required');
|
|
39
|
+
if (b.granteeKind !== 'user' && b.granteeKind !== 'role') {
|
|
40
|
+
throw new common_1.BadRequestException("granteeKind must be 'user' or 'role'");
|
|
41
|
+
}
|
|
42
|
+
if (!b.granteeUuid || typeof b.granteeUuid !== 'string')
|
|
43
|
+
throw new common_1.BadRequestException('granteeUuid required');
|
|
44
|
+
if (!b.action || typeof b.action !== 'string')
|
|
45
|
+
throw new common_1.BadRequestException('action required');
|
|
46
|
+
return {
|
|
47
|
+
typeKey: b.typeKey,
|
|
48
|
+
recordId: b.recordId,
|
|
49
|
+
granteeKind: b.granteeKind,
|
|
50
|
+
granteeUuid: b.granteeUuid,
|
|
51
|
+
action: b.action,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
let ShareController = class ShareController {
|
|
55
|
+
shares;
|
|
56
|
+
constructor(shares) {
|
|
57
|
+
this.shares = shares;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* List the shares on a record the caller owns — grantee NAMES resolved (no raw uuid).
|
|
61
|
+
* `GET /identity/record-shares?typeKey=&recordId=`. Gated `record_share:read`. Only the owner
|
|
62
|
+
* sees a record's shares (the writer's owner-bound); a non-owner → 403.
|
|
63
|
+
*/
|
|
64
|
+
async list(req, typeKey, recordId) {
|
|
65
|
+
if (!typeKey || !recordId)
|
|
66
|
+
throw new common_1.BadRequestException('typeKey and recordId required');
|
|
67
|
+
try {
|
|
68
|
+
const shares = await this.shares.listShares(req.principal, typeKey, recordId);
|
|
69
|
+
return { shares };
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
if (e instanceof guard_1.ShareNotPermittedError)
|
|
73
|
+
throw new common_1.ForbiddenException(e.message);
|
|
74
|
+
throw e; // ShareStoreUnavailableError → propagates → 503
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Share a record the caller owns. `POST /identity/record-shares` with
|
|
79
|
+
* `{ typeKey, recordId, granteeKind, granteeUuid, action }`. Gated `record_share:share`. The
|
|
80
|
+
* writer proves the caller owns the record before writing (non-owner → 403).
|
|
81
|
+
*/
|
|
82
|
+
async share(req, body) {
|
|
83
|
+
const request = toShareRequest(body);
|
|
84
|
+
try {
|
|
85
|
+
await this.shares.share(req.principal, request);
|
|
86
|
+
return { success: true };
|
|
87
|
+
}
|
|
88
|
+
catch (e) {
|
|
89
|
+
if (e instanceof guard_1.ShareNotPermittedError)
|
|
90
|
+
throw new common_1.ForbiddenException(e.message);
|
|
91
|
+
throw e;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Revoke a share the caller owns. `DELETE /identity/record-shares` with the same body shape
|
|
96
|
+
* (the exact tuple to remove). Gated `record_share:share`. Owner-only (the writer enforces it).
|
|
97
|
+
*/
|
|
98
|
+
async unshare(req, body) {
|
|
99
|
+
const request = toShareRequest(body);
|
|
100
|
+
try {
|
|
101
|
+
await this.shares.unshare(req.principal, request);
|
|
102
|
+
return { success: true };
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
if (e instanceof guard_1.ShareNotPermittedError)
|
|
106
|
+
throw new common_1.ForbiddenException(e.message);
|
|
107
|
+
throw e;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
exports.ShareController = ShareController;
|
|
112
|
+
__decorate([
|
|
113
|
+
(0, common_1.Get)('record-shares'),
|
|
114
|
+
(0, guard_1.Classified)({ typeKey: 'record_share', action: 'read' }),
|
|
115
|
+
__param(0, (0, common_1.Req)()),
|
|
116
|
+
__param(1, (0, common_1.Query)('typeKey')),
|
|
117
|
+
__param(2, (0, common_1.Query)('recordId')),
|
|
118
|
+
__metadata("design:type", Function),
|
|
119
|
+
__metadata("design:paramtypes", [Object, String, String]),
|
|
120
|
+
__metadata("design:returntype", Promise)
|
|
121
|
+
], ShareController.prototype, "list", null);
|
|
122
|
+
__decorate([
|
|
123
|
+
(0, common_1.Post)('record-shares'),
|
|
124
|
+
(0, guard_1.Classified)({ typeKey: 'record_share', action: 'share' }),
|
|
125
|
+
__param(0, (0, common_1.Req)()),
|
|
126
|
+
__param(1, (0, common_1.Body)()),
|
|
127
|
+
__metadata("design:type", Function),
|
|
128
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
129
|
+
__metadata("design:returntype", Promise)
|
|
130
|
+
], ShareController.prototype, "share", null);
|
|
131
|
+
__decorate([
|
|
132
|
+
(0, common_1.Delete)('record-shares'),
|
|
133
|
+
(0, guard_1.Classified)({ typeKey: 'record_share', action: 'share' }),
|
|
134
|
+
__param(0, (0, common_1.Req)()),
|
|
135
|
+
__param(1, (0, common_1.Body)()),
|
|
136
|
+
__metadata("design:type", Function),
|
|
137
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
138
|
+
__metadata("design:returntype", Promise)
|
|
139
|
+
], ShareController.prototype, "unshare", null);
|
|
140
|
+
exports.ShareController = ShareController = __decorate([
|
|
141
|
+
(0, common_1.Controller)('identity'),
|
|
142
|
+
__metadata("design:paramtypes", [share_service_js_1.ShareService])
|
|
143
|
+
], ShareController);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ShareService` — the backend of the OWNER-SHARE surface (§9.2 / list-scoping engine Phase 6).
|
|
3
|
+
*
|
|
4
|
+
* A thin surface over the framework-owned {@link RecordShareWriter} (the who-may-share write path)
|
|
5
|
+
* + a name resolver (no raw uuid reaches the UI). The writer already enforces the §6.2 owner
|
|
6
|
+
* bound (fetch-then-gate, tenant-scoped, only the LITERAL owner may share/unshare/list; a grantee
|
|
7
|
+
* can never re-share), so this service NEVER re-implements it. It:
|
|
8
|
+
* • builds the {@link ShareInput} from the SERVER-resolved principal — the tenant + sharer are
|
|
9
|
+
* ALWAYS from `req.principal`, NEVER a body field (a forged owner/tenant is impossible);
|
|
10
|
+
* • RESOLVES grantee uuids → human names for the list (a share row carries a uuid only as an
|
|
11
|
+
* API key; the UI sees a name, ADR-0012 / no-raw-uuid);
|
|
12
|
+
* • maps the writer's typed errors to HTTP-shaped errors (not-permitted → 403, outage → 503).
|
|
13
|
+
*
|
|
14
|
+
* Once a share is written, it is honored in the grantee's LIST automatically — the list-scoping
|
|
15
|
+
* engine's `share` term (Phase 3) unions it in; there is NO separate read path (one authority).
|
|
16
|
+
*/
|
|
17
|
+
import type { Principal } from '@astram/core';
|
|
18
|
+
import type { RecordShareWriter, ShareGranteeKind } from '@astram/guard';
|
|
19
|
+
/** A resolver from a grantee id → its display name (the identity DirectoryLabelResolver). */
|
|
20
|
+
export interface ShareLabelResolver {
|
|
21
|
+
resolve(kind: 'principal' | 'role', id: string, tenantId?: string): Promise<string>;
|
|
22
|
+
}
|
|
23
|
+
/** The minimal writer surface the service uses (the concrete guard writer satisfies it). */
|
|
24
|
+
export interface ShareWriterLike {
|
|
25
|
+
share(input: {
|
|
26
|
+
tenantId: string;
|
|
27
|
+
typeKey: string;
|
|
28
|
+
recordId: string;
|
|
29
|
+
granteeKind: ShareGranteeKind;
|
|
30
|
+
granteeUuid: string;
|
|
31
|
+
action: string;
|
|
32
|
+
sharerId: string;
|
|
33
|
+
}): Promise<void>;
|
|
34
|
+
unshare(input: {
|
|
35
|
+
tenantId: string;
|
|
36
|
+
typeKey: string;
|
|
37
|
+
recordId: string;
|
|
38
|
+
granteeKind: ShareGranteeKind;
|
|
39
|
+
granteeUuid: string;
|
|
40
|
+
action: string;
|
|
41
|
+
sharerId: string;
|
|
42
|
+
}): Promise<void>;
|
|
43
|
+
listSharesForRecord(input: {
|
|
44
|
+
tenantId: string;
|
|
45
|
+
typeKey: string;
|
|
46
|
+
recordId: string;
|
|
47
|
+
sharerId: string;
|
|
48
|
+
}): Promise<ReadonlyArray<{
|
|
49
|
+
granteeKind: ShareGranteeKind;
|
|
50
|
+
granteeUuid: string;
|
|
51
|
+
action: string;
|
|
52
|
+
grantedBy: string;
|
|
53
|
+
}>>;
|
|
54
|
+
}
|
|
55
|
+
/** A share as the UI sees it — a resolved NAME (never a raw uuid) + the API key. */
|
|
56
|
+
export interface ShareView {
|
|
57
|
+
/** The grantee's display name (resolved server-side). */
|
|
58
|
+
readonly granteeName: string;
|
|
59
|
+
readonly granteeKind: ShareGranteeKind;
|
|
60
|
+
/** The grantee uuid — an API key only (for revoke); the UI renders the NAME. */
|
|
61
|
+
readonly granteeUuid: string;
|
|
62
|
+
readonly action: string;
|
|
63
|
+
}
|
|
64
|
+
/** The share/unshare request body — grantee + action ONLY (never a tenant/sharer/owner field). */
|
|
65
|
+
export interface ShareRequest {
|
|
66
|
+
readonly typeKey: string;
|
|
67
|
+
readonly recordId: string;
|
|
68
|
+
readonly granteeKind: ShareGranteeKind;
|
|
69
|
+
readonly granteeUuid: string;
|
|
70
|
+
readonly action: string;
|
|
71
|
+
}
|
|
72
|
+
export declare class ShareService {
|
|
73
|
+
private readonly writer;
|
|
74
|
+
private readonly labels;
|
|
75
|
+
constructor(writer: RecordShareWriter | ShareWriterLike, labels: ShareLabelResolver);
|
|
76
|
+
/**
|
|
77
|
+
* Share a record the caller owns with a user/role for an action. The tenant + sharer are the
|
|
78
|
+
* caller's own (from `actor`), NEVER the body — a forged owner/tenant cannot reach the writer.
|
|
79
|
+
* The writer proves `actor.id` owns the record before writing (fail-closed).
|
|
80
|
+
*/
|
|
81
|
+
share(actor: Principal, req: ShareRequest): Promise<void>;
|
|
82
|
+
/** Revoke a share the caller owns. Same owner bound (only the owner may unshare). */
|
|
83
|
+
unshare(actor: Principal, req: ShareRequest): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* List the shares on a record the caller owns — grantee NAMES resolved (no raw uuid). Only the
|
|
86
|
+
* owner may list (the writer enforces it). A deleted grantee resolves to a fallback label.
|
|
87
|
+
*/
|
|
88
|
+
listShares(actor: Principal, typeKey: string, recordId: string): Promise<readonly ShareView[]>;
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=share.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"share.service.d.ts","sourceRoot":"","sources":["../../../src/features/share/share.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAEvB,6FAA6F;AAC7F,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACrF;AAED,4FAA4F;AAC5F,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,KAAK,EAAE;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,EAAE;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClB,mBAAmB,CAAC,KAAK,EAAE;QACzB,QAAQ,EAAE,MAAM,CAAC;QACjB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,QAAQ,EAAE,MAAM,CAAC;KAClB,GAAG,OAAO,CACT,aAAa,CAAC;QACZ,WAAW,EAAE,gBAAgB,CAAC;QAC9B,WAAW,EAAE,MAAM,CAAC;QACpB,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC,CACH,CAAC;CACH;AAED,oFAAoF;AACpF,MAAM,WAAW,SAAS;IACxB,yDAAyD;IACzD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,gBAAgB,CAAC;IACvC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,YAAY;IAErB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,MAAM,EAAE,iBAAiB,GAAG,eAAe,EAC3C,MAAM,EAAE,kBAAkB;IAG7C;;;;OAIG;IACG,KAAK,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/D,qFAAqF;IAC/E,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAYjE;;;OAGG;IACG,UAAU,CACd,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,SAAS,SAAS,EAAE,CAAC;CAoBjC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ShareService = void 0;
|
|
4
|
+
class ShareService {
|
|
5
|
+
writer;
|
|
6
|
+
labels;
|
|
7
|
+
constructor(writer, labels) {
|
|
8
|
+
this.writer = writer;
|
|
9
|
+
this.labels = labels;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Share a record the caller owns with a user/role for an action. The tenant + sharer are the
|
|
13
|
+
* caller's own (from `actor`), NEVER the body — a forged owner/tenant cannot reach the writer.
|
|
14
|
+
* The writer proves `actor.id` owns the record before writing (fail-closed).
|
|
15
|
+
*/
|
|
16
|
+
async share(actor, req) {
|
|
17
|
+
await this.writer.share({
|
|
18
|
+
tenantId: actor.tenantId, // SERVER-resolved
|
|
19
|
+
typeKey: req.typeKey,
|
|
20
|
+
recordId: req.recordId,
|
|
21
|
+
granteeKind: req.granteeKind,
|
|
22
|
+
granteeUuid: req.granteeUuid,
|
|
23
|
+
action: req.action,
|
|
24
|
+
sharerId: actor.id, // SERVER-resolved — the writer verifies ownership
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Revoke a share the caller owns. Same owner bound (only the owner may unshare). */
|
|
28
|
+
async unshare(actor, req) {
|
|
29
|
+
await this.writer.unshare({
|
|
30
|
+
tenantId: actor.tenantId,
|
|
31
|
+
typeKey: req.typeKey,
|
|
32
|
+
recordId: req.recordId,
|
|
33
|
+
granteeKind: req.granteeKind,
|
|
34
|
+
granteeUuid: req.granteeUuid,
|
|
35
|
+
action: req.action,
|
|
36
|
+
sharerId: actor.id,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* List the shares on a record the caller owns — grantee NAMES resolved (no raw uuid). Only the
|
|
41
|
+
* owner may list (the writer enforces it). A deleted grantee resolves to a fallback label.
|
|
42
|
+
*/
|
|
43
|
+
async listShares(actor, typeKey, recordId) {
|
|
44
|
+
const rows = await this.writer.listSharesForRecord({
|
|
45
|
+
tenantId: actor.tenantId,
|
|
46
|
+
typeKey,
|
|
47
|
+
recordId,
|
|
48
|
+
sharerId: actor.id,
|
|
49
|
+
});
|
|
50
|
+
return Promise.all(rows.map(async (r) => ({
|
|
51
|
+
granteeName: await this.labels.resolve(r.granteeKind === 'role' ? 'role' : 'principal', r.granteeUuid, actor.tenantId),
|
|
52
|
+
granteeKind: r.granteeKind,
|
|
53
|
+
granteeUuid: r.granteeUuid,
|
|
54
|
+
action: r.action,
|
|
55
|
+
})));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.ShareService = ShareService;
|
package/dist/index.d.ts
CHANGED
|
@@ -48,4 +48,21 @@ export { createIdentityAuthzPool, buildIdentityRecordAuthorization, } from './fe
|
|
|
48
48
|
* can do so.
|
|
49
49
|
*/
|
|
50
50
|
export { AlsSessionFactsReader } from './features/authz/session-facts.reader.js';
|
|
51
|
+
/**
|
|
52
|
+
* The Task 2.2 (ADR-0027 / ADR-0024) active-scope-subtree resolver — the identity-side impl of
|
|
53
|
+
* guard's `ScopeSubtreeResolver` port. Reads `shield_authz.scope_closure` (built + kept live by
|
|
54
|
+
* `ScopeClosureService`) to resolve a principal's subtree (self node + descendants) ONCE at login.
|
|
55
|
+
* The consumer wires it via `ShieldModule.forRoot({ scopeSubtreeResolver: new DbScopeSubtreeResolver(authzPool) })`
|
|
56
|
+
* so the per-request tenant gate reads the real subtree from the session binding with zero DB.
|
|
57
|
+
*/
|
|
58
|
+
export { DbScopeSubtreeResolver } from './features/scope/scope-subtree.resolver.js';
|
|
59
|
+
/**
|
|
60
|
+
* The Task 5.2 (ADR-0027 / ADR-0024) scope-switch membership checker — the identity-side impl of
|
|
61
|
+
* guard's `MembershipChecker` port. A single existence read over `shield_app.company_membership`
|
|
62
|
+
* (`cm_is_active = 1 AND cm_status = 'active'`) that GATES `POST /auth/scope-switch`: a principal
|
|
63
|
+
* may only switch its active scope to a node it holds an active membership at. The consumer wires
|
|
64
|
+
* it via `ShieldModule.forRoot({ membershipChecker: new DbMembershipChecker(appPool) })`; absent →
|
|
65
|
+
* the switch fails closed (deny).
|
|
66
|
+
*/
|
|
67
|
+
export { DbMembershipChecker } from './features/membership/membership-checker.js';
|
|
51
68
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;;GAMG;AACH,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAExF;;;;;GAKG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E;;;;;GAKG;AACH,OAAO,EACL,uBAAuB,EACvB,gCAAgC,GACjC,MAAM,0CAA0C,CAAC;AAElD;;;;GAIG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAE5D;;;;GAIG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C;;;;;;GAMG;AACH,OAAO,EAAE,sBAAsB,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAExF;;;;;GAKG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,uCAAuC,CAAC;AAE9E;;;;;GAKG;AACH,OAAO,EACL,uBAAuB,EACvB,gCAAgC,GACjC,MAAM,0CAA0C,CAAC;AAElD;;;;GAIG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AAEjF;;;;;;GAMG;AACH,OAAO,EAAE,sBAAsB,EAAE,MAAM,4CAA4C,CAAC;AAEpF;;;;;;;GAOG;AACH,OAAO,EAAE,mBAAmB,EAAE,MAAM,6CAA6C,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AlsSessionFactsReader = exports.buildIdentityRecordAuthorization = exports.createIdentityAuthzPool = exports.IdentityFeatureModule = exports.identityControlPlaneKeys = exports.identityAuthActionKeys = exports.identityObjects = exports.identityMigrationsDir = exports.CompanyBranch = exports.CompanyDepartment = exports.MembershipPosting = exports.CompanyMembership = exports.Branch = exports.Department = exports.Role = exports.User = exports.Company = void 0;
|
|
3
|
+
exports.DbMembershipChecker = exports.DbScopeSubtreeResolver = exports.AlsSessionFactsReader = exports.buildIdentityRecordAuthorization = exports.createIdentityAuthzPool = exports.IdentityFeatureModule = exports.identityControlPlaneKeys = exports.identityAuthActionKeys = exports.identityObjects = exports.identityMigrationsDir = exports.CompanyBranch = exports.CompanyDepartment = exports.MembershipPosting = exports.CompanyMembership = exports.Branch = exports.Department = exports.Role = exports.User = exports.Company = void 0;
|
|
4
4
|
/**
|
|
5
5
|
* @astram/identity — the framework-owned identity + tenancy model.
|
|
6
6
|
* The 9 identity object classes (relocated from the consumer app in Sub-plan B) +
|
|
@@ -68,3 +68,22 @@ Object.defineProperty(exports, "buildIdentityRecordAuthorization", { enumerable:
|
|
|
68
68
|
*/
|
|
69
69
|
var session_facts_reader_js_1 = require("./features/authz/session-facts.reader.js");
|
|
70
70
|
Object.defineProperty(exports, "AlsSessionFactsReader", { enumerable: true, get: function () { return session_facts_reader_js_1.AlsSessionFactsReader; } });
|
|
71
|
+
/**
|
|
72
|
+
* The Task 2.2 (ADR-0027 / ADR-0024) active-scope-subtree resolver — the identity-side impl of
|
|
73
|
+
* guard's `ScopeSubtreeResolver` port. Reads `shield_authz.scope_closure` (built + kept live by
|
|
74
|
+
* `ScopeClosureService`) to resolve a principal's subtree (self node + descendants) ONCE at login.
|
|
75
|
+
* The consumer wires it via `ShieldModule.forRoot({ scopeSubtreeResolver: new DbScopeSubtreeResolver(authzPool) })`
|
|
76
|
+
* so the per-request tenant gate reads the real subtree from the session binding with zero DB.
|
|
77
|
+
*/
|
|
78
|
+
var scope_subtree_resolver_js_1 = require("./features/scope/scope-subtree.resolver.js");
|
|
79
|
+
Object.defineProperty(exports, "DbScopeSubtreeResolver", { enumerable: true, get: function () { return scope_subtree_resolver_js_1.DbScopeSubtreeResolver; } });
|
|
80
|
+
/**
|
|
81
|
+
* The Task 5.2 (ADR-0027 / ADR-0024) scope-switch membership checker — the identity-side impl of
|
|
82
|
+
* guard's `MembershipChecker` port. A single existence read over `shield_app.company_membership`
|
|
83
|
+
* (`cm_is_active = 1 AND cm_status = 'active'`) that GATES `POST /auth/scope-switch`: a principal
|
|
84
|
+
* may only switch its active scope to a node it holds an active membership at. The consumer wires
|
|
85
|
+
* it via `ShieldModule.forRoot({ membershipChecker: new DbMembershipChecker(appPool) })`; absent →
|
|
86
|
+
* the switch fails closed (deny).
|
|
87
|
+
*/
|
|
88
|
+
var membership_checker_js_1 = require("./features/membership/membership-checker.js");
|
|
89
|
+
Object.defineProperty(exports, "DbMembershipChecker", { enumerable: true, get: function () { return membership_checker_js_1.DbMembershipChecker; } });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"membership-posting.object.d.ts","sourceRoot":"","sources":["../src/membership-posting.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,
|
|
1
|
+
{"version":3,"file":"membership-posting.object.d.ts","sourceRoot":"","sources":["../src/membership-posting.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAiBa,iBAAiB;IAE5B,IAAI,EAAG,MAAM,CAAC;IAGd,cAAc,EAAG,MAAM,CAAC;IAGxB,QAAQ,EAAG,MAAM,CAAC;IAGlB,cAAc,EAAG,MAAM,CAAC;IAGxB,UAAU,EAAG,MAAM,CAAC;IAQpB,aAAa,EAAG,MAAM,CAAC;IAGvB,cAAc,EAAG,MAAM,CAAC;IAGxB,SAAS,EAAG,MAAM,CAAC;CACpB"}
|
|
@@ -71,6 +71,16 @@ exports.MembershipPosting = MembershipPosting = __decorate([
|
|
|
71
71
|
table: 'membership_posting',
|
|
72
72
|
schema: schema_1.SCHEMA,
|
|
73
73
|
access: { plane: 'data', system: true },
|
|
74
|
+
// Read-scope wall (ADR-0027, Task 4.1): a posting has no company column of its own —
|
|
75
|
+
// it is in-scope iff its membership's company is in the caller's reachable subtree. The
|
|
76
|
+
// subquery is a FIXED framework template; the ONLY dynamic values are the `?`-bound
|
|
77
|
+
// subtree node ids (the `:nodes` placeholder the engine expands). DECOUPLED from writes.
|
|
78
|
+
// Root spans all.
|
|
79
|
+
readScope: {
|
|
80
|
+
kind: 'subtreeSubquery',
|
|
81
|
+
pkColumn: 'mp_membership_uuid',
|
|
82
|
+
subquerySql: `SELECT cm_uuid FROM \`${schema_1.SCHEMA}\`.\`company_membership\` WHERE cm_company_uuid IN (${engine_1.READ_SCOPE_NODES_TOKEN})`,
|
|
83
|
+
},
|
|
74
84
|
}),
|
|
75
85
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'mp_uuid' })
|
|
76
86
|
], MembershipPosting);
|
package/dist/objects.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../src/objects.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"objects.d.ts","sourceRoot":"","sources":["../src/objects.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AActD,6FAA6F;AAC7F,eAAO,MAAM,eAAe,EAAE,SAAS,eAAe,EAIrD,CAAC"}
|
package/dist/objects.js
CHANGED
|
@@ -11,10 +11,11 @@ const membership_posting_object_js_1 = require("./membership-posting.object.js")
|
|
|
11
11
|
const company_department_object_js_1 = require("./company-department.object.js");
|
|
12
12
|
const company_branch_object_js_1 = require("./company-branch.object.js");
|
|
13
13
|
const cross_tenant_grant_object_js_1 = require("./cross-tenant-grant.object.js");
|
|
14
|
+
const record_share_object_js_1 = require("./record-share.object.js");
|
|
14
15
|
const shared_resource_object_js_1 = require("./shared-resource.object.js");
|
|
15
16
|
/** All identity objects, registered with the engine at boot (same set + order as before). */
|
|
16
17
|
exports.identityObjects = [
|
|
17
18
|
company_object_js_1.Company, user_object_js_1.User, role_object_js_1.Role, department_object_js_1.Department, branch_object_js_1.Branch,
|
|
18
19
|
company_membership_object_js_1.CompanyMembership, membership_posting_object_js_1.MembershipPosting, company_department_object_js_1.CompanyDepartment, company_branch_object_js_1.CompanyBranch,
|
|
19
|
-
cross_tenant_grant_object_js_1.CrossTenantGrant, shared_resource_object_js_1.SharedResource,
|
|
20
|
+
cross_tenant_grant_object_js_1.CrossTenantGrant, record_share_object_js_1.RecordShare, shared_resource_object_js_1.SharedResource,
|
|
20
21
|
];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `record_share` — an AUTHORITY-ONLY @ObjectType, never CRUD'd through the RecordEngine pipe
|
|
3
|
+
* (list-scoping engine Phase 6, §9.2). Its sole purpose is to give `record_share` an entry in the
|
|
4
|
+
* core `ModuleRegistry` (via `module-registry.bridge.ts`) so `decide()` can answer the coarse
|
|
5
|
+
* "may this principal use the owner-share surface?" gate the SAME way it answers every other
|
|
6
|
+
* object-type gate — no bespoke rule needed. The REAL per-record authority (only the LITERAL
|
|
7
|
+
* owner may share/unshare/list) is enforced by `RecordShareWriter`'s fetch-then-gate owner-bound,
|
|
8
|
+
* NOT by this coarse gate (mirrors how `cross_tenant_grant`'s writer enforces tenant isolation).
|
|
9
|
+
*
|
|
10
|
+
* `plane: 'control'` — the share surface is administration of who-may-see (root allowed), same
|
|
11
|
+
* posture as `cross_tenant_grant`/`role`. `system: true` (never `owned`/`tenantScoped`) — the
|
|
12
|
+
* table's own columns (`rs_tenant_uuid`, `rs_granted_by`) + the writer's owner-bound encode the
|
|
13
|
+
* isolation; the pipe's owner/tenant baseline is not involved. Action vocabulary is NOT the CRUD
|
|
14
|
+
* verbs — see `@astram/guard`'s `identity-actions.ts` `actionsFor('record_share')` →
|
|
15
|
+
* `RECORD_SHARE_ACTIONS` (`share`/`read`).
|
|
16
|
+
*
|
|
17
|
+
* Mirrors `cross-tenant-grant.object.ts`'s shape (system, PK only satisfies `validateAccess`).
|
|
18
|
+
*/
|
|
19
|
+
export declare class RecordShare {
|
|
20
|
+
uuid: string;
|
|
21
|
+
tenantUuid: string;
|
|
22
|
+
recordUuid: string;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=record-share.object.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record-share.object.d.ts","sourceRoot":"","sources":["../src/record-share.object.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAOa,WAAW;IAEtB,IAAI,EAAG,MAAM,CAAC;IAGd,UAAU,EAAG,MAAM,CAAC;IAGpB,UAAU,EAAG,MAAM,CAAC;CACrB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.RecordShare = void 0;
|
|
13
|
+
const engine_1 = require("@astram/engine");
|
|
14
|
+
/** The physical schema `record_share` lives in (migration 1710000000014). */
|
|
15
|
+
const SCHEMA = 'shield_authz';
|
|
16
|
+
/**
|
|
17
|
+
* `record_share` — an AUTHORITY-ONLY @ObjectType, never CRUD'd through the RecordEngine pipe
|
|
18
|
+
* (list-scoping engine Phase 6, §9.2). Its sole purpose is to give `record_share` an entry in the
|
|
19
|
+
* core `ModuleRegistry` (via `module-registry.bridge.ts`) so `decide()` can answer the coarse
|
|
20
|
+
* "may this principal use the owner-share surface?" gate the SAME way it answers every other
|
|
21
|
+
* object-type gate — no bespoke rule needed. The REAL per-record authority (only the LITERAL
|
|
22
|
+
* owner may share/unshare/list) is enforced by `RecordShareWriter`'s fetch-then-gate owner-bound,
|
|
23
|
+
* NOT by this coarse gate (mirrors how `cross_tenant_grant`'s writer enforces tenant isolation).
|
|
24
|
+
*
|
|
25
|
+
* `plane: 'control'` — the share surface is administration of who-may-see (root allowed), same
|
|
26
|
+
* posture as `cross_tenant_grant`/`role`. `system: true` (never `owned`/`tenantScoped`) — the
|
|
27
|
+
* table's own columns (`rs_tenant_uuid`, `rs_granted_by`) + the writer's owner-bound encode the
|
|
28
|
+
* isolation; the pipe's owner/tenant baseline is not involved. Action vocabulary is NOT the CRUD
|
|
29
|
+
* verbs — see `@astram/guard`'s `identity-actions.ts` `actionsFor('record_share')` →
|
|
30
|
+
* `RECORD_SHARE_ACTIONS` (`share`/`read`).
|
|
31
|
+
*
|
|
32
|
+
* Mirrors `cross-tenant-grant.object.ts`'s shape (system, PK only satisfies `validateAccess`).
|
|
33
|
+
*/
|
|
34
|
+
let RecordShare = class RecordShare {
|
|
35
|
+
uuid;
|
|
36
|
+
tenantUuid;
|
|
37
|
+
recordUuid;
|
|
38
|
+
};
|
|
39
|
+
exports.RecordShare = RecordShare;
|
|
40
|
+
__decorate([
|
|
41
|
+
(0, engine_1.Field)({ column: 'rs_uuid', type: 'uuid' }),
|
|
42
|
+
__metadata("design:type", String)
|
|
43
|
+
], RecordShare.prototype, "uuid", void 0);
|
|
44
|
+
__decorate([
|
|
45
|
+
(0, engine_1.Field)({ column: 'rs_tenant_uuid', type: 'uuid' }),
|
|
46
|
+
__metadata("design:type", String)
|
|
47
|
+
], RecordShare.prototype, "tenantUuid", void 0);
|
|
48
|
+
__decorate([
|
|
49
|
+
(0, engine_1.Field)({ column: 'rs_record_uuid', type: 'uuid' }),
|
|
50
|
+
__metadata("design:type", String)
|
|
51
|
+
], RecordShare.prototype, "recordUuid", void 0);
|
|
52
|
+
exports.RecordShare = RecordShare = __decorate([
|
|
53
|
+
(0, engine_1.ObjectType)({
|
|
54
|
+
typeKey: 'record_share',
|
|
55
|
+
table: 'record_share',
|
|
56
|
+
schema: SCHEMA,
|
|
57
|
+
access: { plane: 'control', system: true },
|
|
58
|
+
}),
|
|
59
|
+
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'rs_uuid' })
|
|
60
|
+
], RecordShare);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"role.object.d.ts","sourceRoot":"","sources":["../src/role.object.ts"],"names":[],"mappings":"AAGA,8BAA8B;AAC9B,
|
|
1
|
+
{"version":3,"file":"role.object.d.ts","sourceRoot":"","sources":["../src/role.object.ts"],"names":[],"mappings":"AAGA,8BAA8B;AAC9B,qBAiBa,IAAI;IAEf,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAGd,SAAS,EAAG,MAAM,CAAC;CACpB"}
|
package/dist/role.object.js
CHANGED
|
@@ -37,6 +37,16 @@ exports.Role = Role = __decorate([
|
|
|
37
37
|
table: 'roles',
|
|
38
38
|
schema: schema_1.SCHEMA,
|
|
39
39
|
access: { plane: 'control', system: true },
|
|
40
|
+
// Read-scope wall (ADR-0027, Task 4.1, OQ-2 v1 "bound-in-subtree"): a role is in-scope iff
|
|
41
|
+
// it is BOUND (via a membership_posting) within the caller's reachable subtree — the SAME
|
|
42
|
+
// "a role in my subtree" derivation the Task-4.2 grant-admin predicate uses (one consistent
|
|
43
|
+
// notion). FIXED framework template; the ONLY dynamic values are the `?`-bound subtree node
|
|
44
|
+
// ids. DECOUPLED from writes. Root spans all.
|
|
45
|
+
readScope: {
|
|
46
|
+
kind: 'subtreeSubquery',
|
|
47
|
+
pkColumn: 'r_uuid',
|
|
48
|
+
subquerySql: `SELECT mp.mp_role_uuid FROM \`${schema_1.SCHEMA}\`.\`membership_posting\` mp JOIN \`${schema_1.SCHEMA}\`.\`company_membership\` cm ON cm.cm_uuid = mp.mp_membership_uuid WHERE cm.cm_company_uuid IN (${engine_1.READ_SCOPE_NODES_TOKEN})`,
|
|
49
|
+
},
|
|
40
50
|
}),
|
|
41
51
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'r_uuid' })
|
|
42
52
|
], Role);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user.object.d.ts","sourceRoot":"","sources":["../src/user.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,
|
|
1
|
+
{"version":3,"file":"user.object.d.ts","sourceRoot":"","sources":["../src/user.object.ts"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,qBAoBa,IAAI;IAEf,IAAI,EAAG,MAAM,CAAC;IAGd,QAAQ,EAAG,MAAM,CAAC;IAGlB,KAAK,EAAG,MAAM,CAAC;IAGf,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAG,MAAM,CAAC;CACpB"}
|
package/dist/user.object.js
CHANGED
|
@@ -54,6 +54,16 @@ exports.User = User = __decorate([
|
|
|
54
54
|
// capped root, the identity administrator, may list/read AND create/update/delete users). Uniform
|
|
55
55
|
// across all actions (read reclassified control 2026-07-16), so plane is per-type.
|
|
56
56
|
access: { plane: 'control', system: true },
|
|
57
|
+
// Read-scope wall (ADR-0027, Task 4.1): a user has no company column (they belong to many
|
|
58
|
+
// companies via `company_membership`) — a user is in-scope iff they have a membership in
|
|
59
|
+
// the caller's reachable subtree. A multi-tenant user correctly appears for each subtree
|
|
60
|
+
// they're a member of. FIXED framework template; the ONLY dynamic values are the `?`-bound
|
|
61
|
+
// subtree node ids. DECOUPLED from writes. Root spans all.
|
|
62
|
+
readScope: {
|
|
63
|
+
kind: 'subtreeSubquery',
|
|
64
|
+
pkColumn: 'u_uuid',
|
|
65
|
+
subquerySql: `SELECT cm_user_uuid FROM \`${schema_1.SCHEMA}\`.\`company_membership\` WHERE cm_company_uuid IN (${engine_1.READ_SCOPE_NODES_TOKEN})`,
|
|
66
|
+
},
|
|
57
67
|
}),
|
|
58
68
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'u_uuid' })
|
|
59
69
|
], User);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astram/identity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Astram framework — the framework-owned identity + tenancy model. Hosts the identity @ObjectType classes (user, company, membership, posting), framework-authored identity migrations, and the identity resolver. Top of the dependency graph: depends on @astram/engine (for @ObjectType/@Field decorators) + @astram/guard / @astram/core (for the auth ports).",
|
|
6
6
|
"type": "commonjs",
|
|
@@ -22,9 +22,9 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"mariadb": "^3.4.0",
|
|
25
|
-
"@astram/core": "1.
|
|
26
|
-
"@astram/
|
|
27
|
-
"@astram/
|
|
25
|
+
"@astram/core": "1.2.0",
|
|
26
|
+
"@astram/guard": "0.6.0",
|
|
27
|
+
"@astram/engine": "0.2.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@nestjs/common": "^11",
|