@astram/identity 0.18.1 → 0.19.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/branch.object.d.ts.map +1 -1
- package/dist/branch.object.js +20 -0
- package/dist/company-branch.object.d.ts.map +1 -1
- package/dist/company-branch.object.js +20 -0
- package/dist/company-department.object.d.ts.map +1 -1
- package/dist/company-department.object.js +20 -0
- package/dist/company-membership.object.d.ts.map +1 -1
- package/dist/company-membership.object.js +20 -0
- package/dist/company.object.d.ts.map +1 -1
- package/dist/company.object.js +21 -0
- package/dist/cross-tenant-grant.object.d.ts.map +1 -1
- package/dist/cross-tenant-grant.object.js +17 -0
- package/dist/department.object.d.ts.map +1 -1
- package/dist/department.object.js +20 -0
- package/dist/features/authz/app-release.d.ts +12 -0
- package/dist/features/authz/app-release.d.ts.map +1 -0
- package/dist/features/authz/app-release.js +26 -0
- package/dist/features/authz/catalog-bootstrap.provider.d.ts +16 -0
- package/dist/features/authz/catalog-bootstrap.provider.d.ts.map +1 -1
- package/dist/features/authz/catalog-bootstrap.provider.js +67 -3
- package/dist/features/authz/catalog-reconcile.job.d.ts +11 -3
- package/dist/features/authz/catalog-reconcile.job.d.ts.map +1 -1
- package/dist/features/authz/catalog-reconcile.job.js +17 -4
- package/dist/features/authz/grant-admin.controller.d.ts +4 -0
- package/dist/features/authz/grant-admin.controller.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.d.ts +9 -0
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +8 -1
- package/dist/features/authz/mariadb-catalog-store.d.ts +12 -2
- package/dist/features/authz/mariadb-catalog-store.d.ts.map +1 -1
- package/dist/features/authz/mariadb-catalog-store.js +64 -5
- package/dist/features/identity-feature.module.d.ts +8 -0
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +26 -13
- package/dist/membership-posting.object.d.ts.map +1 -1
- package/dist/membership-posting.object.js +22 -0
- package/dist/record-share.object.d.ts.map +1 -1
- package/dist/record-share.object.js +12 -0
- package/dist/role.object.d.ts.map +1 -1
- package/dist/role.object.js +21 -0
- package/dist/security-policy.object.d.ts.map +1 -1
- package/dist/security-policy.object.js +21 -0
- package/dist/service-account.object.d.ts.map +1 -1
- package/dist/service-account.object.js +25 -0
- package/dist/shared-resource.object.d.ts.map +1 -1
- package/dist/shared-resource.object.js +20 -0
- package/dist/stored-file.object.d.ts.map +1 -1
- package/dist/stored-file.object.js +20 -0
- package/dist/user.object.d.ts +1 -0
- package/dist/user.object.d.ts.map +1 -1
- package/dist/user.object.js +39 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"branch.object.d.ts","sourceRoot":"","sources":["../src/branch.object.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,
|
|
1
|
+
{"version":3,"file":"branch.object.d.ts","sourceRoot":"","sources":["../src/branch.object.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,qBA4Ba,MAAM;IAEjB,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAGd,WAAW,EAAG,MAAM,CAAC;CACtB"}
|
package/dist/branch.object.js
CHANGED
|
@@ -45,6 +45,26 @@ exports.Branch = Branch = __decorate([
|
|
|
45
45
|
schema: schema_1.SCHEMA,
|
|
46
46
|
access: { plane: 'control', system: true },
|
|
47
47
|
readScope: { kind: 'nodeColumn', column: 'b_company_uuid' },
|
|
48
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
49
|
+
label: 'Branch',
|
|
50
|
+
permissions: {
|
|
51
|
+
read: {
|
|
52
|
+
label: 'Branches: view',
|
|
53
|
+
description: 'See the branches of the companies they can reach.',
|
|
54
|
+
},
|
|
55
|
+
create: {
|
|
56
|
+
label: 'Branches: create',
|
|
57
|
+
description: 'Add branches to the companies they can reach.',
|
|
58
|
+
},
|
|
59
|
+
update: {
|
|
60
|
+
label: 'Branches: edit',
|
|
61
|
+
description: 'Rename branches.',
|
|
62
|
+
},
|
|
63
|
+
delete: {
|
|
64
|
+
label: 'Branches: delete',
|
|
65
|
+
description: 'Delete branches.',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
48
68
|
}),
|
|
49
69
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'b_uuid' })
|
|
50
70
|
], Branch);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company-branch.object.d.ts","sourceRoot":"","sources":["../src/company-branch.object.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,
|
|
1
|
+
{"version":3,"file":"company-branch.object.d.ts","sourceRoot":"","sources":["../src/company-branch.object.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,qBA4Ba,aAAa;IAExB,WAAW,EAAG,MAAM,CAAC;IAGrB,UAAU,EAAG,MAAM,CAAC;CACrB"}
|
|
@@ -39,6 +39,26 @@ exports.CompanyBranch = CompanyBranch = __decorate([
|
|
|
39
39
|
schema: schema_1.SCHEMA,
|
|
40
40
|
access: { plane: 'control', system: true },
|
|
41
41
|
readScope: { kind: 'nodeColumn', column: 'cb_company_uuid' },
|
|
42
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
43
|
+
label: 'Company branch link',
|
|
44
|
+
permissions: {
|
|
45
|
+
read: {
|
|
46
|
+
label: 'Company branches: view',
|
|
47
|
+
description: 'See which branches belong to each of the companies they can reach.',
|
|
48
|
+
},
|
|
49
|
+
create: {
|
|
50
|
+
label: 'Company branches: create',
|
|
51
|
+
description: 'Link a branch to its company, as happens when a branch is added.',
|
|
52
|
+
},
|
|
53
|
+
update: {
|
|
54
|
+
label: 'Company branches: edit',
|
|
55
|
+
description: 'Change the links between companies and their branches.',
|
|
56
|
+
},
|
|
57
|
+
delete: {
|
|
58
|
+
label: 'Company branches: delete',
|
|
59
|
+
description: 'Remove the link between a company and a branch.',
|
|
60
|
+
},
|
|
61
|
+
},
|
|
42
62
|
}),
|
|
43
63
|
(0, engine_1.PrimaryKey)({ kind: 'composite', columns: ['cb_company_uuid', 'cb_branch_uuid'] })
|
|
44
64
|
], CompanyBranch);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company-department.object.d.ts","sourceRoot":"","sources":["../src/company-department.object.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,
|
|
1
|
+
{"version":3,"file":"company-department.object.d.ts","sourceRoot":"","sources":["../src/company-department.object.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,qBA4Ba,iBAAiB;IAE5B,WAAW,EAAG,MAAM,CAAC;IAGrB,cAAc,EAAG,MAAM,CAAC;CACzB"}
|
|
@@ -40,6 +40,26 @@ exports.CompanyDepartment = CompanyDepartment = __decorate([
|
|
|
40
40
|
schema: schema_1.SCHEMA,
|
|
41
41
|
access: { plane: 'control', system: true },
|
|
42
42
|
readScope: { kind: 'nodeColumn', column: 'cd_company_uuid' },
|
|
43
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
44
|
+
label: 'Company department link',
|
|
45
|
+
permissions: {
|
|
46
|
+
read: {
|
|
47
|
+
label: 'Company departments: view',
|
|
48
|
+
description: 'See which departments belong to each of the companies they can reach.',
|
|
49
|
+
},
|
|
50
|
+
create: {
|
|
51
|
+
label: 'Company departments: create',
|
|
52
|
+
description: 'Link a department to its company, as happens when a department is added.',
|
|
53
|
+
},
|
|
54
|
+
update: {
|
|
55
|
+
label: 'Company departments: edit',
|
|
56
|
+
description: 'Change the links between companies and their departments.',
|
|
57
|
+
},
|
|
58
|
+
delete: {
|
|
59
|
+
label: 'Company departments: delete',
|
|
60
|
+
description: 'Remove the link between a company and a department.',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
43
63
|
}),
|
|
44
64
|
(0, engine_1.PrimaryKey)({ kind: 'composite', columns: ['cd_company_uuid', 'cd_department_uuid'] })
|
|
45
65
|
], CompanyDepartment);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company-membership.object.d.ts","sourceRoot":"","sources":["../src/company-membership.object.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,
|
|
1
|
+
{"version":3,"file":"company-membership.object.d.ts","sourceRoot":"","sources":["../src/company-membership.object.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,qBAkCa,iBAAiB;IAE5B,IAAI,EAAG,MAAM,CAAC;IAId,QAAQ,EAAG,MAAM,CAAC;IAGlB,WAAW,EAAG,MAAM,CAAC;IAGrB,QAAQ,EAAG,OAAO,CAAC;IAGnB,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAG,MAAM,CAAC;CACpB"}
|
|
@@ -66,6 +66,26 @@ exports.CompanyMembership = CompanyMembership = __decorate([
|
|
|
66
66
|
// admin's OWN node, breaking adding a user to a SUB-company (a legit hierarchy op). This
|
|
67
67
|
// filters reads only. Root spans all.
|
|
68
68
|
readScope: { kind: 'nodeColumn', column: 'cm_company_uuid' },
|
|
69
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
70
|
+
label: 'Company membership',
|
|
71
|
+
permissions: {
|
|
72
|
+
read: {
|
|
73
|
+
label: 'Memberships: view',
|
|
74
|
+
description: 'See which of the companies they can reach each user belongs to.',
|
|
75
|
+
},
|
|
76
|
+
create: {
|
|
77
|
+
label: 'Memberships: create',
|
|
78
|
+
description: 'Add users to the companies they can reach.',
|
|
79
|
+
},
|
|
80
|
+
update: {
|
|
81
|
+
label: 'Memberships: edit',
|
|
82
|
+
description: "Change a membership's status or make it the user's active company.",
|
|
83
|
+
},
|
|
84
|
+
delete: {
|
|
85
|
+
label: 'Memberships: delete',
|
|
86
|
+
description: 'Remove users from companies, together with their postings there.',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
69
89
|
}),
|
|
70
90
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'cm_uuid' })
|
|
71
91
|
], CompanyMembership);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"company.object.d.ts","sourceRoot":"","sources":["../src/company.object.ts"],"names":[],"mappings":"AAGA,kFAAkF;AAClF,
|
|
1
|
+
{"version":3,"file":"company.object.d.ts","sourceRoot":"","sources":["../src/company.object.ts"],"names":[],"mappings":"AAGA,kFAAkF;AAClF,qBA4Ca,OAAO;IAElB,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAKd,UAAU,EAAG,MAAM,CAAC;IAGpB,IAAI,EAAG,MAAM,CAAC;IAGd,MAAM,EAAG,MAAM,CAAC;IAGhB,SAAS,EAAG,MAAM,CAAC;CACpB"}
|
package/dist/company.object.js
CHANGED
|
@@ -69,6 +69,27 @@ exports.Company = Company = __decorate([
|
|
|
69
69
|
// scope closure lists it, so the post-insert create wall would deny every create. Opted out
|
|
70
70
|
// explicitly; the company service owns where a non-root create may attach (parent stamping).
|
|
71
71
|
readScope: { kind: 'nodeColumn', column: 'c_uuid', create: 'unscoped' },
|
|
72
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
73
|
+
label: 'Company',
|
|
74
|
+
permissions: {
|
|
75
|
+
read: {
|
|
76
|
+
label: 'Companies: view',
|
|
77
|
+
description: 'See the companies in their part of the organisation.',
|
|
78
|
+
},
|
|
79
|
+
create: {
|
|
80
|
+
label: 'Companies: create',
|
|
81
|
+
description: 'Add new companies under the companies they can reach.',
|
|
82
|
+
},
|
|
83
|
+
update: {
|
|
84
|
+
label: 'Companies: edit',
|
|
85
|
+
description: 'Rename companies, change their status or move them under another parent company.',
|
|
86
|
+
caution: 'Moving a company changes which administrators above it can reach it and its data.',
|
|
87
|
+
},
|
|
88
|
+
delete: {
|
|
89
|
+
label: 'Companies: delete',
|
|
90
|
+
description: 'Remove companies that have no sub-companies.',
|
|
91
|
+
},
|
|
92
|
+
},
|
|
72
93
|
}),
|
|
73
94
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'c_uuid' })
|
|
74
95
|
], Company);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cross-tenant-grant.object.d.ts","sourceRoot":"","sources":["../src/cross-tenant-grant.object.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,
|
|
1
|
+
{"version":3,"file":"cross-tenant-grant.object.d.ts","sourceRoot":"","sources":["../src/cross-tenant-grant.object.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBA2Ba,gBAAgB;IAE3B,IAAI,EAAG,MAAM,CAAC;IAGd,gBAAgB,EAAG,MAAM,CAAC;IAG1B,iBAAiB,EAAG,MAAM,CAAC;CAC5B"}
|
|
@@ -63,6 +63,23 @@ exports.CrossTenantGrant = CrossTenantGrant = __decorate([
|
|
|
63
63
|
access: { plane: 'control', system: true },
|
|
64
64
|
crudActions: false,
|
|
65
65
|
actions: guard_1.CROSS_TENANT_GRANT_ACTIONS,
|
|
66
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
67
|
+
label: 'Cross-tenant grant',
|
|
68
|
+
permissions: {
|
|
69
|
+
share: {
|
|
70
|
+
label: 'Cross-tenant grants: offer',
|
|
71
|
+
description: "Offer another tenant access to one type of their own tenant's records.",
|
|
72
|
+
caution: "Once accepted, people in the other tenant can reach this tenant's records of that type.",
|
|
73
|
+
},
|
|
74
|
+
accept: {
|
|
75
|
+
label: 'Cross-tenant grants: accept',
|
|
76
|
+
description: "Accept another tenant's offer of access to its records.",
|
|
77
|
+
},
|
|
78
|
+
revoke: {
|
|
79
|
+
label: 'Cross-tenant grants: revoke',
|
|
80
|
+
description: 'End a cross-tenant grant, or decline an offer, from either side.',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
66
83
|
}),
|
|
67
84
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'ctg_uuid' })
|
|
68
85
|
], CrossTenantGrant);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"department.object.d.ts","sourceRoot":"","sources":["../src/department.object.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,
|
|
1
|
+
{"version":3,"file":"department.object.d.ts","sourceRoot":"","sources":["../src/department.object.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AACH,qBA4Ba,UAAU;IAErB,IAAI,EAAG,MAAM,CAAC;IAGd,IAAI,EAAG,MAAM,CAAC;IAGd,WAAW,EAAG,MAAM,CAAC;CACtB"}
|
|
@@ -45,6 +45,26 @@ exports.Department = Department = __decorate([
|
|
|
45
45
|
schema: schema_1.SCHEMA,
|
|
46
46
|
access: { plane: 'control', system: true },
|
|
47
47
|
readScope: { kind: 'nodeColumn', column: 'd_company_uuid' },
|
|
48
|
+
// G-027: permission catalog text, read by every tenant's administrators (no tenant names).
|
|
49
|
+
label: 'Department',
|
|
50
|
+
permissions: {
|
|
51
|
+
read: {
|
|
52
|
+
label: 'Departments: view',
|
|
53
|
+
description: 'See the departments of the companies they can reach.',
|
|
54
|
+
},
|
|
55
|
+
create: {
|
|
56
|
+
label: 'Departments: create',
|
|
57
|
+
description: 'Add departments to the companies they can reach.',
|
|
58
|
+
},
|
|
59
|
+
update: {
|
|
60
|
+
label: 'Departments: edit',
|
|
61
|
+
description: 'Rename departments.',
|
|
62
|
+
},
|
|
63
|
+
delete: {
|
|
64
|
+
label: 'Departments: delete',
|
|
65
|
+
description: 'Delete departments.',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
48
68
|
}),
|
|
49
69
|
(0, engine_1.PrimaryKey)({ kind: 'uuid', column: 'd_uuid' })
|
|
50
70
|
], Department);
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G-027 (permission-notification spec §5H): the app release name stamped on a permission key's
|
|
3
|
+
* first catalogue insert (`perm_first_seen_version`), read ONCE by `IdentityFeatureModule.forRoot`.
|
|
4
|
+
*
|
|
5
|
+
* - `APP_RELEASE` unset or blank ⇒ `null` (the version stays NULL).
|
|
6
|
+
* - Otherwise the trimmed value, 1–64 characters of letters, digits, `.`, `_`, `+`, `-`
|
|
7
|
+
* (a git sha or tag fits).
|
|
8
|
+
* - Anything else refuses boot with an error naming the variable, never echoing the value.
|
|
9
|
+
*/
|
|
10
|
+
export declare const APP_RELEASE_ERROR = "APP_RELEASE must be 1\u201364 characters of letters, digits, . _ + -";
|
|
11
|
+
export declare function loadAppRelease(env?: NodeJS.ProcessEnv): string | null;
|
|
12
|
+
//# sourceMappingURL=app-release.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app-release.d.ts","sourceRoot":"","sources":["../../../src/features/authz/app-release.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,eAAO,MAAM,iBAAiB,yEAAoE,CAAC;AAEnG,wBAAgB,cAAc,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,IAAI,CAOlF"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* G-027 (permission-notification spec §5H): the app release name stamped on a permission key's
|
|
4
|
+
* first catalogue insert (`perm_first_seen_version`), read ONCE by `IdentityFeatureModule.forRoot`.
|
|
5
|
+
*
|
|
6
|
+
* - `APP_RELEASE` unset or blank ⇒ `null` (the version stays NULL).
|
|
7
|
+
* - Otherwise the trimmed value, 1–64 characters of letters, digits, `.`, `_`, `+`, `-`
|
|
8
|
+
* (a git sha or tag fits).
|
|
9
|
+
* - Anything else refuses boot with an error naming the variable, never echoing the value.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.APP_RELEASE_ERROR = void 0;
|
|
13
|
+
exports.loadAppRelease = loadAppRelease;
|
|
14
|
+
const APP_RELEASE_PATTERN = /^[A-Za-z0-9._+-]{1,64}$/;
|
|
15
|
+
exports.APP_RELEASE_ERROR = 'APP_RELEASE must be 1–64 characters of letters, digits, . _ + -';
|
|
16
|
+
function loadAppRelease(env = process.env) {
|
|
17
|
+
const raw = env.APP_RELEASE;
|
|
18
|
+
if (raw === undefined)
|
|
19
|
+
return null;
|
|
20
|
+
const value = raw.trim();
|
|
21
|
+
if (value === '')
|
|
22
|
+
return null;
|
|
23
|
+
if (!APP_RELEASE_PATTERN.test(value))
|
|
24
|
+
throw new Error(exports.APP_RELEASE_ERROR);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
@@ -39,8 +39,24 @@ export declare class CatalogBootstrapProvider implements OnApplicationBootstrap,
|
|
|
39
39
|
* that fires after this is a no-op. Never throws.
|
|
40
40
|
*/
|
|
41
41
|
onModuleDestroy(): Promise<void>;
|
|
42
|
+
/**
|
|
43
|
+
* G-027 (spec §5I): one `warn` for the app's catalogued keys (types the registry knows, outside
|
|
44
|
+
* the framework's identity objects) without declared text: those without a declared label
|
|
45
|
+
* (they get the generated one), and those with a declared label but no description. Either
|
|
46
|
+
* list is omitted when empty; nothing is logged when every app key has both. Public-tier
|
|
47
|
+
* actions are skipped (they are never catalogued).
|
|
48
|
+
*/
|
|
49
|
+
private warnMissingPermissionText;
|
|
42
50
|
/** One scheduled reconcile pass (catalog, then bundles, then the scope closure). */
|
|
43
51
|
private tick;
|
|
52
|
+
/**
|
|
53
|
+
* One catalog reconcile pass. G-027 spec §13: a DB error (an insert losing a race with another
|
|
54
|
+
* instance, a text UPDATE, an unreachable DB) is logged and retried on the next tick; it never
|
|
55
|
+
* aborts boot or the tick chain. Fail closed: a key the pass could not insert is not grantable
|
|
56
|
+
* (grants validate against the catalog). Logs the error's class and driver code only, never its
|
|
57
|
+
* message (it can carry SQL and values).
|
|
58
|
+
*/
|
|
59
|
+
private reconcileCatalog;
|
|
44
60
|
/** Rebuild the scope closure; a failure is logged loudly (never silently swallowed) but does
|
|
45
61
|
* not abort boot or a cron tick — the reconcile is idempotent and retried next interval. */
|
|
46
62
|
private reconcileScopeClosure;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-bootstrap.provider.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-bootstrap.provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"catalog-bootstrap.provider.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-bootstrap.provider.ts"],"names":[],"mappings":"AAIA,OAAO,EAIL,KAAK,sBAAsB,EAC3B,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC5E,OAAO,EAKL,KAAK,yBAAyB,EAC/B,MAAM,eAAe,CAAC;AAEvB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAEjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAaxE;;;;;;;;;;GAUG;AACH,qBACa,wBAAyB,YAAW,sBAAsB,EAAE,eAAe;IAQpF,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,GAAG;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe;IAChC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,mBAAmB;IAhBtC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA6C;IACpE,OAAO,CAAC,KAAK,CAA+B;IAC5C,wFAAwF;IACxF,OAAO,CAAC,iBAAiB,CAA8B;IACvD,OAAO,CAAC,SAAS,CAAS;gBAGP,SAAS,EAAE,gBAAgB,EAC3B,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,SAAS,EACpB,GAAG,EAAE,mBAAmB,EACxB,eAAe,EAAE,wBAAwB,EACzC,YAAY,EAAE,mBAAmB,EAEjC,aAAa,EAAE,SAAS,UAAU,EAAE,EAEpC,mBAAmB,EAAE,yBAAyB;IAG3D,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAsE7C;;;;;OAKG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAUtC;;;;;;OAMG;IACH,OAAO,CAAC,yBAAyB;IAiCjC,oFAAoF;IACpF,OAAO,CAAC,IAAI;IAYZ;;;;;;OAMG;YACW,gBAAgB;IAY9B;iGAC6F;YAC/E,qBAAqB;CAOpC"}
|
|
@@ -22,10 +22,15 @@ const common_1 = require("@nestjs/common");
|
|
|
22
22
|
const core_1 = require("@nestjs/core");
|
|
23
23
|
const guard_1 = require("@astram/guard");
|
|
24
24
|
const engine_1 = require("@astram/engine");
|
|
25
|
+
const objects_js_1 = require("../../objects.js");
|
|
25
26
|
const catalog_reconcile_job_js_1 = require("./catalog-reconcile.job.js");
|
|
26
27
|
const access_bundle_reconcile_job_js_1 = require("./access-bundle-reconcile.job.js");
|
|
27
28
|
const scope_closure_service_js_1 = require("../scope/scope-closure.service.js");
|
|
28
29
|
const MODULE_KEY = 'identity';
|
|
30
|
+
/** The framework's own types (their text is declared by identity, never named in the warning). */
|
|
31
|
+
function frameworkTypeKeys() {
|
|
32
|
+
return new Set(objects_js_1.identityObjects.map((input) => typeof input === 'function' ? (0, engine_1.reflectObjectType)(input).typeKey : input.typeKey));
|
|
33
|
+
}
|
|
29
34
|
const RECONCILE_INTERVAL_MS = 60 * 60 * 1000; // hourly; honors DISABLE_ALL_CRONS below
|
|
30
35
|
/**
|
|
31
36
|
* Boot: read-only verifyGatable (fail-closed — a stranded/unclassified route aborts
|
|
@@ -94,9 +99,11 @@ let CatalogBootstrapProvider = CatalogBootstrapProvider_1 = class CatalogBootstr
|
|
|
94
99
|
}
|
|
95
100
|
// Fail-closed (ADR-0002): a route that can't be gated aborts boot.
|
|
96
101
|
new guard_1.CatalogReconciler().verifyGatable(inventory, registry);
|
|
102
|
+
// G-027 (spec §5I): ONE warning naming the app keys that lack declared text (never fails boot).
|
|
103
|
+
this.warnMissingPermissionText(registry);
|
|
97
104
|
// Mutating reconcile runs AFTER boot (not blocking, not fail-closed).
|
|
98
105
|
const nowIso = new Date().toISOString();
|
|
99
|
-
await this.
|
|
106
|
+
await this.reconcileCatalog(nowIso);
|
|
100
107
|
// Bundles reconcile AFTER permissions (same post-boot pass): a bundle validates
|
|
101
108
|
// its keys against the live permission catalog, so permissions must reconcile first.
|
|
102
109
|
await this.accessBundleJob.runOnce(nowIso);
|
|
@@ -128,13 +135,53 @@ let CatalogBootstrapProvider = CatalogBootstrapProvider_1 = class CatalogBootstr
|
|
|
128
135
|
if (running !== null)
|
|
129
136
|
await running.catch(() => undefined);
|
|
130
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* G-027 (spec §5I): one `warn` for the app's catalogued keys (types the registry knows, outside
|
|
140
|
+
* the framework's identity objects) without declared text: those without a declared label
|
|
141
|
+
* (they get the generated one), and those with a declared label but no description. Either
|
|
142
|
+
* list is omitted when empty; nothing is logged when every app key has both. Public-tier
|
|
143
|
+
* actions are skipped (they are never catalogued).
|
|
144
|
+
*/
|
|
145
|
+
warnMissingPermissionText(registry) {
|
|
146
|
+
const catalogued = new Set(registry
|
|
147
|
+
.listActions()
|
|
148
|
+
.filter((a) => a.tier !== 'public')
|
|
149
|
+
.map((a) => `${a.action.typeKey}:${a.action.actionKey}`));
|
|
150
|
+
const noLabel = [];
|
|
151
|
+
const noDescription = [];
|
|
152
|
+
const framework = frameworkTypeKeys();
|
|
153
|
+
for (const reg of this.registrations) {
|
|
154
|
+
if (framework.has(reg.typeKey))
|
|
155
|
+
continue;
|
|
156
|
+
if (registry.resolveObjectType(reg.typeKey) === undefined)
|
|
157
|
+
continue;
|
|
158
|
+
for (const t of (0, engine_1.permissionTextOf)(reg)) {
|
|
159
|
+
const name = `${reg.typeKey}:${t.action}`;
|
|
160
|
+
if (!catalogued.has(name))
|
|
161
|
+
continue;
|
|
162
|
+
if (!t.declared)
|
|
163
|
+
noLabel.push(name);
|
|
164
|
+
else if (t.description === null)
|
|
165
|
+
noDescription.push(name);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const parts = [];
|
|
169
|
+
if (noLabel.length > 0) {
|
|
170
|
+
parts.push(`${noLabel.length} permission(s) have no declared label: ${noLabel.join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
if (noDescription.length > 0) {
|
|
173
|
+
parts.push(`${noDescription.length}${parts.length > 0 ? '' : ' permission(s)'} have no description: ` +
|
|
174
|
+
noDescription.join(', '));
|
|
175
|
+
}
|
|
176
|
+
if (parts.length > 0)
|
|
177
|
+
this.logger.warn(`[catalog] ${parts.join('; ')}`);
|
|
178
|
+
}
|
|
131
179
|
/** One scheduled reconcile pass (catalog, then bundles, then the scope closure). */
|
|
132
180
|
tick() {
|
|
133
181
|
if (this.destroyed)
|
|
134
182
|
return;
|
|
135
183
|
const tickIso = new Date().toISOString();
|
|
136
|
-
const run = this.
|
|
137
|
-
.runOnce(tickIso)
|
|
184
|
+
const run = this.reconcileCatalog(tickIso)
|
|
138
185
|
.then(() => this.accessBundleJob.runOnce(tickIso))
|
|
139
186
|
.then(() => this.reconcileScopeClosure());
|
|
140
187
|
// Kept so onModuleDestroy can drain it. Storing the reference attaches no handler, so a
|
|
@@ -142,6 +189,23 @@ let CatalogBootstrapProvider = CatalogBootstrapProvider_1 = class CatalogBootstr
|
|
|
142
189
|
this.inFlightReconcile = run;
|
|
143
190
|
void run;
|
|
144
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* One catalog reconcile pass. G-027 spec §13: a DB error (an insert losing a race with another
|
|
194
|
+
* instance, a text UPDATE, an unreachable DB) is logged and retried on the next tick; it never
|
|
195
|
+
* aborts boot or the tick chain. Fail closed: a key the pass could not insert is not grantable
|
|
196
|
+
* (grants validate against the catalog). Logs the error's class and driver code only, never its
|
|
197
|
+
* message (it can carry SQL and values).
|
|
198
|
+
*/
|
|
199
|
+
async reconcileCatalog(nowIso) {
|
|
200
|
+
try {
|
|
201
|
+
await this.job.runOnce(nowIso);
|
|
202
|
+
}
|
|
203
|
+
catch (cause) {
|
|
204
|
+
const name = cause instanceof Error ? cause.name : typeof cause;
|
|
205
|
+
const code = cause?.code;
|
|
206
|
+
this.logger.error(`[catalog] reconcile failed (${name}${typeof code === 'string' ? ` ${code}` : ''}); retried next interval`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
145
209
|
/** Rebuild the scope closure; a failure is logged loudly (never silently swallowed) but does
|
|
146
210
|
* not abort boot or a cron tick — the reconcile is idempotent and retried next interval. */
|
|
147
211
|
async reconcileScopeClosure() {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
|
-
import { type ReconcileReport } from '@astram/guard';
|
|
2
|
+
import { type PermissionText, type ReconcileReport } from '@astram/guard';
|
|
3
3
|
import type { ModuleRegistry } from '@astram/core';
|
|
4
4
|
import type { IdentitySchemaNames } from '../../schema.js';
|
|
5
5
|
/**
|
|
@@ -12,12 +12,20 @@ import type { IdentitySchemaNames } from '../../schema.js';
|
|
|
12
12
|
*/
|
|
13
13
|
export declare class CatalogReconcileJob {
|
|
14
14
|
private readonly registry;
|
|
15
|
+
private readonly text?;
|
|
16
|
+
private readonly release;
|
|
15
17
|
private readonly reconciler;
|
|
16
18
|
private readonly store;
|
|
17
19
|
private latest;
|
|
18
20
|
private running;
|
|
19
|
-
/**
|
|
20
|
-
|
|
21
|
+
/**
|
|
22
|
+
* @param schemas resolved framework names for the catalog store (absent ⇒ from the namespace).
|
|
23
|
+
* @param text G-027: permission key → code text (`permissionTextByKey` over the registry's
|
|
24
|
+
* types); absent ⇒ generated labels and no relabelling.
|
|
25
|
+
* @param release G-027: the app release (`loadAppRelease`) stamped as first-seen version on
|
|
26
|
+
* insert; absent ⇒ null.
|
|
27
|
+
*/
|
|
28
|
+
constructor(pool: Pool, registry: ModuleRegistry, schemas?: IdentitySchemaNames, text?: ReadonlyMap<string, PermissionText> | undefined, release?: string | null);
|
|
21
29
|
/** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
|
|
22
30
|
runOnce(nowIso: string): Promise<ReconcileReport | null>;
|
|
23
31
|
getLatest(): ReconcileReport | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"catalog-reconcile.job.d.ts","sourceRoot":"","sources":["../../../src/features/authz/catalog-reconcile.job.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,EAAqB,KAAK,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAC;AAC7F,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAE3D;;;;;;;GAOG;AACH,qBAAa,mBAAmB;IAe5B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjB1B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAA2B;IACtD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAC5C,OAAO,CAAC,MAAM,CAAgC;IAC9C,OAAO,CAAC,OAAO,CAAS;IAExB;;;;;;OAMG;gBAED,IAAI,EAAE,IAAI,EACO,QAAQ,EAAE,cAAc,EACzC,OAAO,CAAC,EAAE,mBAAmB,EACZ,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,YAAA,EAC1C,OAAO,GAAE,MAAM,GAAG,IAAW;IAKhD,6EAA6E;IACvE,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC;IAoB9D,SAAS,IAAI,eAAe,GAAG,IAAI;CAGpC"}
|
|
@@ -13,13 +13,23 @@ const mariadb_catalog_store_js_1 = require("./mariadb-catalog-store.js");
|
|
|
13
13
|
*/
|
|
14
14
|
class CatalogReconcileJob {
|
|
15
15
|
registry;
|
|
16
|
+
text;
|
|
17
|
+
release;
|
|
16
18
|
reconciler = new guard_1.CatalogReconciler();
|
|
17
19
|
store;
|
|
18
20
|
latest = null;
|
|
19
21
|
running = false;
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
+
/**
|
|
23
|
+
* @param schemas resolved framework names for the catalog store (absent ⇒ from the namespace).
|
|
24
|
+
* @param text G-027: permission key → code text (`permissionTextByKey` over the registry's
|
|
25
|
+
* types); absent ⇒ generated labels and no relabelling.
|
|
26
|
+
* @param release G-027: the app release (`loadAppRelease`) stamped as first-seen version on
|
|
27
|
+
* insert; absent ⇒ null.
|
|
28
|
+
*/
|
|
29
|
+
constructor(pool, registry, schemas, text, release = null) {
|
|
22
30
|
this.registry = registry;
|
|
31
|
+
this.text = text;
|
|
32
|
+
this.release = release;
|
|
23
33
|
this.store = new mariadb_catalog_store_js_1.MariaDbCatalogStore(pool, schemas);
|
|
24
34
|
}
|
|
25
35
|
/** Idempotent single pass. Guarded so overlapping ticks don't double-run. */
|
|
@@ -28,11 +38,14 @@ class CatalogReconcileJob {
|
|
|
28
38
|
return this.latest;
|
|
29
39
|
this.running = true;
|
|
30
40
|
try {
|
|
31
|
-
const report = await this.reconciler.reconcile(this.registry, this.store, nowIso
|
|
41
|
+
const report = await this.reconciler.reconcile(this.registry, this.store, nowIso, {
|
|
42
|
+
...(this.text !== undefined ? { text: this.text } : {}),
|
|
43
|
+
release: this.release,
|
|
44
|
+
});
|
|
32
45
|
this.latest = report;
|
|
33
46
|
const orphanNote = report.orphaned.length ? ` — ORPHANED: ${report.orphaned.join(', ')}` : '';
|
|
34
47
|
console.log(`[catalog] reconcile: +${report.added.length} added, ${report.orphaned.length} orphaned, ` +
|
|
35
|
-
`${report.reactivated.length} reactivated${orphanNote}`);
|
|
48
|
+
`${report.reactivated.length} reactivated, ${report.relabelled.length} relabelled${orphanNote}`);
|
|
36
49
|
return report;
|
|
37
50
|
}
|
|
38
51
|
finally {
|
|
@@ -51,6 +51,10 @@ export declare class GrantAdminController {
|
|
|
51
51
|
module: string;
|
|
52
52
|
typeKey: string;
|
|
53
53
|
action: string;
|
|
54
|
+
description: string | null;
|
|
55
|
+
caution: string | null;
|
|
56
|
+
firstSeenAt: string | null;
|
|
57
|
+
firstSeenVersion: string | null;
|
|
54
58
|
}[];
|
|
55
59
|
wildcards: string[];
|
|
56
60
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-admin.controller.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.controller.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAe,MAAM,0BAA0B,CAAC;AAI1E,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAaD;;;;;;;;;;;;;;GAcG;AACH,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,iBAAiB;IAEtD;;;;;;;;OAQG;IAGG,aAAa,CACV,GAAG,EAAE,aAAa,EAEzB,KAAK,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;IAcH,sDAAsD;IAGhD,OAAO
|
|
1
|
+
{"version":3,"file":"grant-admin.controller.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.controller.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAAe,MAAM,0BAA0B,CAAC;AAI1E,qFAAqF;AACrF,UAAU,aAAa;IACrB,SAAS,EAAE,SAAS,CAAC;CACtB;AAaD;;;;;;;;;;;;;;GAcG;AACH,qBAEa,oBAAoB;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,iBAAiB;IAEtD;;;;;;;;OAQG;IAGG,aAAa,CACV,GAAG,EAAE,aAAa,EAEzB,KAAK,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;;;;;;IAcH,sDAAsD;IAGhD,OAAO;;;;;;;;;;;;;;IAIb;kGAC8F;IAGxF,IAAI,CAAQ,GAAG,EAAE,aAAa,EAA2B,IAAI,EAAE,MAAM;;;IAI3E,0CAA0C;IAGpC,KAAK,CACF,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAahC,mFAAmF;IAG7E,MAAM,CACH,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM;;;IAQ3B;;;;OAIG;IAGG,OAAO;;;IAIb;;;;OAIG;IAGG,WAAW,CACR,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EAC7B,IAAI,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;;;IAahC;;;;OAIG;IAGG,YAAY,CACT,GAAG,EAAE,aAAa,EACA,IAAI,EAAE,MAAM,EACvB,GAAG,EAAE,MAAM;;;CAU5B"}
|
|
@@ -21,12 +21,21 @@ export interface SubtreeScope {
|
|
|
21
21
|
export declare class BadKeyError extends Error {
|
|
22
22
|
constructor(key: string);
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* One `GET /identity/permission-catalog` row. G-027 (spec §5J): `description`/`caution` are the
|
|
26
|
+
* declared plain text (null when absent); `firstSeenAt` is ISO (null ⇒ the key existed before
|
|
27
|
+
* tracking began), `firstSeenVersion` the `APP_RELEASE` at first insert (null when unset).
|
|
28
|
+
*/
|
|
24
29
|
type CatalogRow = {
|
|
25
30
|
key: string;
|
|
26
31
|
label: string;
|
|
27
32
|
module: string;
|
|
28
33
|
typeKey: string;
|
|
29
34
|
action: string;
|
|
35
|
+
description: string | null;
|
|
36
|
+
caution: string | null;
|
|
37
|
+
firstSeenAt: string | null;
|
|
38
|
+
firstSeenVersion: string | null;
|
|
30
39
|
};
|
|
31
40
|
/** A bundle as returned by the list API — identified by `key`, NEVER by `ab_uuid`. */
|
|
32
41
|
export interface BundleView {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AACpC,OAAO,KAAK,EAAc,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,gCAAgC,CAAC;AACzE,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAE7D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"grant-admin.service.d.ts","sourceRoot":"","sources":["../../../src/features/authz/grant-admin.service.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,6 +7,7 @@ const access_bundle_store_js_1 = require("./access-bundle-store.js");
|
|
|
7
7
|
const db_permission_facts_adapter_js_1 = require("./db-permission-facts.adapter.js");
|
|
8
8
|
const grant_ceiling_js_1 = require("./grant-ceiling.js");
|
|
9
9
|
const role_binding_js_1 = require("./role-binding.js");
|
|
10
|
+
const mariadb_catalog_store_js_1 = require("./mariadb-catalog-store.js");
|
|
10
11
|
const identity_errors_js_1 = require("../shared/identity-errors.js");
|
|
11
12
|
const lock_service_accounts_js_1 = require("../service-account/lock-service-accounts.js");
|
|
12
13
|
const schema_js_1 = require("../../schema.js");
|
|
@@ -66,7 +67,9 @@ class GrantAdminService {
|
|
|
66
67
|
async listCatalog() {
|
|
67
68
|
let rows;
|
|
68
69
|
try {
|
|
69
|
-
|
|
70
|
+
// First seen as epoch seconds (timezone-independent; see `isoFromEpochSeconds`).
|
|
71
|
+
rows = (await this.pool.query(`SELECT perm_key, perm_label, perm_module, perm_type_key, perm_action, perm_description,
|
|
72
|
+
perm_caution, UNIX_TIMESTAMP(perm_first_seen_at) AS perm_first_seen_at, perm_first_seen_version
|
|
70
73
|
FROM \`${this.schemas.authz}\`.\`permission\`
|
|
71
74
|
WHERE perm_status = 'active'
|
|
72
75
|
ORDER BY perm_type_key, perm_action`));
|
|
@@ -80,6 +83,10 @@ class GrantAdminService {
|
|
|
80
83
|
module: r.perm_module,
|
|
81
84
|
typeKey: r.perm_type_key,
|
|
82
85
|
action: r.perm_action,
|
|
86
|
+
description: r.perm_description ?? null,
|
|
87
|
+
caution: r.perm_caution ?? null,
|
|
88
|
+
firstSeenAt: (0, mariadb_catalog_store_js_1.isoFromEpochSeconds)(r.perm_first_seen_at),
|
|
89
|
+
firstSeenVersion: r.perm_first_seen_version ?? null,
|
|
83
90
|
}));
|
|
84
91
|
// Wildcards: module-wide + one per distinct type key.
|
|
85
92
|
const modules = [...new Set(catalog.map((c) => c.module))];
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { Pool } from 'mariadb';
|
|
2
|
-
import type { CatalogStore, CatalogPermissionRow } from '@astram/guard';
|
|
2
|
+
import type { CatalogStore, CatalogPermissionRow, CatalogTextUpdate } from '@astram/guard';
|
|
3
3
|
import { type IdentitySchemaNames } from '../../schema.js';
|
|
4
|
+
/** `UNIX_TIMESTAMP(col)` as the driver returns it (number, bigint or string) → ISO; NULL ⇒ null. */
|
|
5
|
+
export declare function isoFromEpochSeconds(value: unknown): string | null;
|
|
4
6
|
/**
|
|
5
7
|
* CatalogStore impl over shield_authz.permission (raw parameterized SQL, mirroring
|
|
6
8
|
* permission-facts.adapter). The reconciler (framework) calls this; guard does no DB
|
|
7
9
|
* itself (ADR-0003).
|
|
10
|
+
*
|
|
11
|
+
* G-027: `perm_first_seen_at` / `perm_first_seen_version` are written ONLY by the insert in
|
|
12
|
+
* {@link upsertPermission} (insert-only, `INSERT … SELECT … WHERE NOT EXISTS`); no other
|
|
13
|
+
* statement here names them, so an existing row keeps its first values for good.
|
|
8
14
|
*/
|
|
9
15
|
export declare class MariaDbCatalogStore implements CatalogStore {
|
|
10
16
|
private readonly pool;
|
|
@@ -12,8 +18,12 @@ export declare class MariaDbCatalogStore implements CatalogStore {
|
|
|
12
18
|
/** @param schemas resolved framework names (absent ⇒ resolved from the namespace, once). */
|
|
13
19
|
constructor(pool: Pool, schemas?: IdentitySchemaNames);
|
|
14
20
|
listPermissions(): Promise<CatalogPermissionRow[]>;
|
|
21
|
+
/** Insert-only: a key that already exists matches no row (`WHERE NOT EXISTS`) and is untouched. */
|
|
15
22
|
upsertPermission(row: Omit<CatalogPermissionRow, 'status'>): Promise<boolean>;
|
|
16
23
|
markOrphaned(keys: readonly string[]): Promise<void>;
|
|
17
|
-
|
|
24
|
+
/** Orphaned → active; with `nowIso` also stamps `perm_last_activated_at` (never first seen). */
|
|
25
|
+
markActive(keys: readonly string[], nowIso?: string): Promise<void>;
|
|
26
|
+
/** G-027: the code's text replaces the stored text, one parameterized UPDATE per key. */
|
|
27
|
+
updateText(rows: readonly CatalogTextUpdate[]): Promise<void>;
|
|
18
28
|
}
|
|
19
29
|
//# sourceMappingURL=mariadb-catalog-store.d.ts.map
|