@astram/identity 0.5.0 → 0.7.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.
Files changed (61) hide show
  1. package/README.md +15 -0
  2. package/dist/bin/sa-keys-report.d.ts +3 -0
  3. package/dist/bin/sa-keys-report.d.ts.map +1 -0
  4. package/dist/bin/sa-keys-report.js +18 -0
  5. package/dist/features/authz/grant-admin.service.d.ts +33 -1
  6. package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
  7. package/dist/features/authz/grant-admin.service.js +105 -1
  8. package/dist/features/authz/grant-ceiling.d.ts +62 -1
  9. package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
  10. package/dist/features/authz/grant-ceiling.js +145 -10
  11. package/dist/features/authz/permission-version.bumper.d.ts +7 -0
  12. package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
  13. package/dist/features/authz/permission-version.bumper.js +19 -10
  14. package/dist/features/authz/record-authorization.d.ts +16 -21
  15. package/dist/features/authz/record-authorization.d.ts.map +1 -1
  16. package/dist/features/authz/record-authorization.js +17 -22
  17. package/dist/features/company/company-hierarchy.service.d.ts +71 -4
  18. package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
  19. package/dist/features/company/company-hierarchy.service.js +197 -8
  20. package/dist/features/company/company.controller.d.ts +1 -1
  21. package/dist/features/company/company.controller.d.ts.map +1 -1
  22. package/dist/features/company/company.controller.js +9 -5
  23. package/dist/features/company/company.service.d.ts +9 -2
  24. package/dist/features/company/company.service.d.ts.map +1 -1
  25. package/dist/features/company/company.service.js +17 -2
  26. package/dist/features/identity-feature.module.d.ts +15 -6
  27. package/dist/features/identity-feature.module.d.ts.map +1 -1
  28. package/dist/features/identity-feature.module.js +79 -22
  29. package/dist/features/membership/posting-version-bump.d.ts +7 -0
  30. package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
  31. package/dist/features/membership/posting.service.d.ts +2 -0
  32. package/dist/features/membership/posting.service.d.ts.map +1 -1
  33. package/dist/features/membership/posting.service.js +9 -0
  34. package/dist/features/role/role.service.d.ts +32 -2
  35. package/dist/features/role/role.service.d.ts.map +1 -1
  36. package/dist/features/role/role.service.js +64 -4
  37. package/dist/features/scope/scope-closure.service.d.ts +11 -1
  38. package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
  39. package/dist/features/scope/scope-closure.service.js +23 -4
  40. package/dist/features/service-account/lock-service-accounts.d.ts +29 -0
  41. package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -0
  42. package/dist/features/service-account/lock-service-accounts.js +35 -0
  43. package/dist/features/service-account/no-expiry-key-report.d.ts +39 -0
  44. package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -0
  45. package/dist/features/service-account/no-expiry-key-report.js +91 -0
  46. package/dist/features/service-account/service-account.service.d.ts +15 -0
  47. package/dist/features/service-account/service-account.service.d.ts.map +1 -1
  48. package/dist/features/service-account/service-account.service.js +17 -1
  49. package/dist/features/service-account/service-account.store.d.ts +15 -0
  50. package/dist/features/service-account/service-account.store.d.ts.map +1 -1
  51. package/dist/features/service-account/service-account.store.js +29 -0
  52. package/dist/features/user/user.service.d.ts +4 -4
  53. package/dist/features/user/user.service.d.ts.map +1 -1
  54. package/dist/features/user/user.service.js +9 -6
  55. package/dist/index.d.ts +1 -9
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +8 -11
  58. package/dist/internal.d.ts +11 -0
  59. package/dist/internal.d.ts.map +1 -0
  60. package/dist/internal.js +14 -0
  61. package/package.json +18 -4
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ /**
3
+ * Lock the service accounts matching a predicate, without locking the table (Phase 9 #11).
4
+ *
5
+ * `service_account` has no index a role or scope predicate can use (`JSON_CONTAINS` on a JSON
6
+ * column, or an `OR` with one), so `SELECT … WHERE <predicate> FOR UPDATE` scans the whole table
7
+ * and InnoDB (REPEATABLE READ) keeps a next-key lock on every row it scanned: every other agent's
8
+ * edit, and every new agent, waits until the transaction ends. Instead:
9
+ * 1. a NON-locking read finds the candidate ids (a consistent read, no locks);
10
+ * 2. `FOR UPDATE` by primary key locks only those rows, sorted by id (a stable lock order), and
11
+ * re-evaluates the predicate on the latest committed version of each, so a candidate that
12
+ * stopped matching in between is dropped.
13
+ *
14
+ * A row that STARTS matching after step 1 (an agent that gains the role, or the company in its
15
+ * scope) is not locked or returned. Callers accept that (see their docs): the outcome equals the
16
+ * serial order "this transaction, then that edit", which the table-wide lock also allowed (the
17
+ * edit only waited, then applied).
18
+ *
19
+ * `columns` and `where.sql` are fixed SQL written by the caller (never client input); every value
20
+ * is `?`-bound.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.lockServiceAccountsWhere = lockServiceAccountsWhere;
24
+ const APP = 'shield_app';
25
+ async function lockServiceAccountsWhere(conn, columns, where) {
26
+ const candidates = (await conn.query(`SELECT sa_id FROM \`${APP}\`.\`service_account\` WHERE ${where.sql}`, [...where.params]));
27
+ const ids = [...new Set(candidates.map((r) => r.sa_id))].sort();
28
+ if (ids.length === 0)
29
+ return [];
30
+ return (await conn.query(`SELECT ${columns}
31
+ FROM \`${APP}\`.\`service_account\`
32
+ WHERE sa_id IN (${ids.map(() => '?').join(', ')}) AND (${where.sql})
33
+ ORDER BY sa_id
34
+ FOR UPDATE`, [...ids, ...where.params]));
35
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The non-expiring service-account key report (security hardening Phase 8 #13, shipped as the
3
+ * `astram-sa-keys-report` bin in Phase 9 #8).
4
+ *
5
+ * Keys issued before an expiry became mandatory have `sak_expires_at_ms IS NULL` and never expire
6
+ * (a key issued later with an explicit `neverExpires: true` looks the same). This read-only report
7
+ * lists each agent's CURRENT, unrevoked key with no expiry so an operator can rotate it with an
8
+ * expiry. It prints labels and company names only: never a secret, a hash or a raw id.
9
+ */
10
+ /** Read-only: the current unrevoked key of each agent that has no expiry, with display names. */
11
+ export declare const NO_EXPIRY_KEY_REPORT_SQL = "SELECT sa.sa_label AS label, c.c_name AS company,\n sa.sa_status AS status, k.sak_version AS version, k.sak_created_at_ms AS issuedAtMs\n FROM shield_authz.service_account_key k\n LEFT JOIN shield_app.service_account sa ON sa.sa_id = k.sak_account_id\n LEFT JOIN shield_app.companies c ON c.c_uuid = k.sak_tenant_id\n WHERE k.sak_expires_at_ms IS NULL\n AND k.sak_revoked_at_ms IS NULL\n AND k.sak_version = (SELECT MAX(v.sak_version)\n FROM shield_authz.service_account_key v\n WHERE v.sak_account_id = k.sak_account_id)\n ORDER BY k.sak_created_at_ms";
12
+ /** One non-expiring key, as printed (no ids, no secrets). */
13
+ export interface NonExpiringKey {
14
+ /** The agent's label; `null` when the agent row no longer exists. */
15
+ readonly label: string | null;
16
+ /** The tenant company's name; `null` when the company no longer exists. */
17
+ readonly company: string | null;
18
+ readonly status: string | null;
19
+ readonly version: number;
20
+ readonly issuedAtMs: number | null;
21
+ }
22
+ /** The query surface the report needs (a `mariadb` pool or connection satisfies it). */
23
+ export interface ReportQueryable {
24
+ query(sql: string, params?: unknown[]): Promise<unknown>;
25
+ }
26
+ /** Run the report query and map the rows. Read-only. */
27
+ export declare function findNonExpiringKeys(db: ReportQueryable): Promise<NonExpiringKey[]>;
28
+ /** Human-readable report text (one line per key, plus a summary line). */
29
+ export declare function formatNonExpiringKeyReport(keys: readonly NonExpiringKey[]): string;
30
+ /** The bin's behaviour, testable without a process: returns the exit code. */
31
+ export declare function runNonExpiringKeyReport(opts: {
32
+ readonly argv: readonly string[];
33
+ readonly openDb: () => ReportQueryable & {
34
+ end(): Promise<void>;
35
+ };
36
+ readonly out: (text: string) => void;
37
+ readonly err: (text: string) => void;
38
+ }): Promise<number>;
39
+ //# sourceMappingURL=no-expiry-key-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"no-expiry-key-report.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/no-expiry-key-report.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,iGAAiG;AACjG,eAAO,MAAM,wBAAwB,2mBAUP,CAAC;AAE/B,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAED,wFAAwF;AACxF,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CAC1D;AAMD,wDAAwD;AACxD,wBAAsB,mBAAmB,CAAC,EAAE,EAAE,eAAe,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CASxF;AAED,0EAA0E;AAC1E,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,CAalF;AAcD,8EAA8E;AAC9E,wBAAsB,uBAAuB,CAAC,IAAI,EAAE;IAClD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,eAAe,GAAG;QAAE,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC;IAClE,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,CAAC,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtC,GAAG,OAAO,CAAC,MAAM,CAAC,CAwBlB"}
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * The non-expiring service-account key report (security hardening Phase 8 #13, shipped as the
4
+ * `astram-sa-keys-report` bin in Phase 9 #8).
5
+ *
6
+ * Keys issued before an expiry became mandatory have `sak_expires_at_ms IS NULL` and never expire
7
+ * (a key issued later with an explicit `neverExpires: true` looks the same). This read-only report
8
+ * lists each agent's CURRENT, unrevoked key with no expiry so an operator can rotate it with an
9
+ * expiry. It prints labels and company names only: never a secret, a hash or a raw id.
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.NO_EXPIRY_KEY_REPORT_SQL = void 0;
13
+ exports.findNonExpiringKeys = findNonExpiringKeys;
14
+ exports.formatNonExpiringKeyReport = formatNonExpiringKeyReport;
15
+ exports.runNonExpiringKeyReport = runNonExpiringKeyReport;
16
+ /** Read-only: the current unrevoked key of each agent that has no expiry, with display names. */
17
+ exports.NO_EXPIRY_KEY_REPORT_SQL = `SELECT sa.sa_label AS label, c.c_name AS company,
18
+ sa.sa_status AS status, k.sak_version AS version, k.sak_created_at_ms AS issuedAtMs
19
+ FROM shield_authz.service_account_key k
20
+ LEFT JOIN shield_app.service_account sa ON sa.sa_id = k.sak_account_id
21
+ LEFT JOIN shield_app.companies c ON c.c_uuid = k.sak_tenant_id
22
+ WHERE k.sak_expires_at_ms IS NULL
23
+ AND k.sak_revoked_at_ms IS NULL
24
+ AND k.sak_version = (SELECT MAX(v.sak_version)
25
+ FROM shield_authz.service_account_key v
26
+ WHERE v.sak_account_id = k.sak_account_id)
27
+ ORDER BY k.sak_created_at_ms`;
28
+ const str = (v) => (v === null || v === undefined ? null : String(v));
29
+ const num = (v) => v === null || v === undefined || !Number.isFinite(Number(v)) ? null : Number(v);
30
+ /** Run the report query and map the rows. Read-only. */
31
+ async function findNonExpiringKeys(db) {
32
+ const rows = (await db.query(exports.NO_EXPIRY_KEY_REPORT_SQL));
33
+ return rows.map((r) => ({
34
+ label: str(r.label),
35
+ company: str(r.company),
36
+ status: str(r.status),
37
+ version: num(r.version) ?? 0,
38
+ issuedAtMs: num(r.issuedAtMs),
39
+ }));
40
+ }
41
+ /** Human-readable report text (one line per key, plus a summary line). */
42
+ function formatNonExpiringKeyReport(keys) {
43
+ if (keys.length === 0)
44
+ return 'No service-account keys without an expiry.\n';
45
+ const lines = keys.map((k) => {
46
+ const issued = k.issuedAtMs === null ? 'unknown' : new Date(k.issuedAtMs).toISOString();
47
+ return (`- ${k.label ?? '(deleted agent)'} — company: ${k.company ?? '(deleted company)'}` +
48
+ ` — status: ${k.status ?? 'unknown'} — key version ${k.version}, issued ${issued}`);
49
+ });
50
+ return (`${keys.length} service-account key(s) with no expiry (rotate each with an expiry):\n` +
51
+ `${lines.join('\n')}\n`);
52
+ }
53
+ const USAGE = `Usage: astram-sa-keys-report [--fail-on-found]
54
+
55
+ Lists service-account (agent) keys that never expire: each agent's current, unrevoked key with no
56
+ expiry. Read-only. Connects with the APP_DB_HOST, APP_DB_PORT, APP_DB_USER, APP_DB_PASSWORD and
57
+ APP_DB_NAME environment variables (the same as the identity module's authz pool).
58
+
59
+ --fail-on-found exit 1 if any key is listed (for CI)
60
+ --help show this text
61
+
62
+ Exit codes: 0 done (or nothing found), 1 found with --fail-on-found, 2 error.
63
+ `;
64
+ /** The bin's behaviour, testable without a process: returns the exit code. */
65
+ async function runNonExpiringKeyReport(opts) {
66
+ const known = new Set(['--fail-on-found', '--help', '-h']);
67
+ const unknown = opts.argv.filter((a) => !known.has(a));
68
+ if (unknown.length > 0) {
69
+ opts.err(`Unknown option: ${unknown.join(' ')}\n\n${USAGE}`);
70
+ return 2;
71
+ }
72
+ if (opts.argv.includes('--help') || opts.argv.includes('-h')) {
73
+ opts.out(USAGE);
74
+ return 0;
75
+ }
76
+ let db;
77
+ try {
78
+ db = opts.openDb();
79
+ const keys = await findNonExpiringKeys(db);
80
+ opts.out(formatNonExpiringKeyReport(keys));
81
+ return keys.length > 0 && opts.argv.includes('--fail-on-found') ? 1 : 0;
82
+ }
83
+ catch (e) {
84
+ // The driver's message names the failing step (host/user at most), never the password.
85
+ opts.err(`astram-sa-keys-report failed: ${e instanceof Error ? e.message : String(e)}\n`);
86
+ return 2;
87
+ }
88
+ finally {
89
+ await db?.end().catch(() => undefined);
90
+ }
91
+ }
@@ -30,6 +30,21 @@ export interface ServiceAccountView {
30
30
  readonly createdByName: string | null;
31
31
  readonly createdAtMs: number;
32
32
  readonly updatedAtMs: number | null;
33
+ /** The account's current API key (the highest version), or `null` when none was ever issued. */
34
+ readonly currentKey: ServiceAccountKeyView | null;
35
+ }
36
+ /**
37
+ * The status of an account's current key (Phase 8 #13) — never the secret or its hash.
38
+ * `neverExpires` flags an ACTIVE key with no expiry: either issued before an expiry became
39
+ * mandatory (identity 0.5.0) or issued later with an explicit `neverExpires: true`. The stored row
40
+ * cannot tell the two apart, so both are flagged; review them and rotate with an expiry.
41
+ */
42
+ export interface ServiceAccountKeyView {
43
+ readonly version: number;
44
+ readonly issuedAtMs: number;
45
+ readonly expiresAtMs: number | null;
46
+ readonly revoked: boolean;
47
+ readonly neverExpires: boolean;
33
48
  }
34
49
  /** The ONE-TIME key reveal returned by issue/rotate — the only time the raw key exists. */
35
50
  export interface IssuedKey {
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.service.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-account.service.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.service.ts"],"names":[],"mappings":"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"}
@@ -157,12 +157,27 @@ class ServiceAccountService {
157
157
  async toViews(rows) {
158
158
  if (rows.length === 0)
159
159
  return [];
160
- const [roles, nodes] = await Promise.all([
160
+ const [roles, nodes, keys] = await Promise.all([
161
161
  this.accounts.rolesByIds(rows.flatMap((r) => r.roleIds)),
162
162
  this.accounts.nodesByIds(rows.flatMap((r) => r.scopeSubtreeNodeIds)),
163
+ this.accounts.currentKeys(rows.map((r) => r.id)),
163
164
  ]);
164
165
  const roleName = new Map(roles.map((r) => [r.id, r.name]));
165
166
  const nodeName = new Map(nodes.map((n) => [n.id, n.name]));
167
+ const keyOf = new Map(keys.map((k) => [k.accountId, k]));
168
+ const keyView = (id) => {
169
+ const k = keyOf.get(id);
170
+ if (k === undefined)
171
+ return null;
172
+ const revoked = k.revokedAtMs !== null;
173
+ return {
174
+ version: k.version,
175
+ issuedAtMs: k.createdAtMs,
176
+ expiresAtMs: k.expiresAtMs,
177
+ revoked,
178
+ neverExpires: k.expiresAtMs === null && !revoked,
179
+ };
180
+ };
166
181
  return Promise.all(rows.map(async (row) => ({
167
182
  id: row.id,
168
183
  label: row.label,
@@ -174,6 +189,7 @@ class ServiceAccountService {
174
189
  createdByName: row.createdBy ? await this.resolveName(row.createdBy) : null,
175
190
  createdAtMs: row.createdAtMs,
176
191
  updatedAtMs: row.updatedAtMs,
192
+ currentKey: keyView(row.id),
177
193
  })));
178
194
  }
179
195
  /**
@@ -12,6 +12,14 @@
12
12
  * stamped `sa_tenant_id` (from the session) on create — never client input.
13
13
  */
14
14
  import type { Pool } from 'mariadb';
15
+ /** The status of an account's current key (no hash, no secret). Epoch-ms; `null` = not set. */
16
+ export interface ServiceAccountKeyRow {
17
+ readonly accountId: string;
18
+ readonly version: number;
19
+ readonly expiresAtMs: number | null;
20
+ readonly revokedAtMs: number | null;
21
+ readonly createdAtMs: number;
22
+ }
15
23
  /** The caller's reachable subtree (threaded from `req.principal`) — same shape as grant-admin. */
16
24
  export interface SubtreeScope {
17
25
  readonly nodeIds: readonly string[];
@@ -51,6 +59,13 @@ export declare class ServiceAccountStore {
51
59
  nodesByIds(ids: readonly string[]): Promise<ScopeNodeOption[]>;
52
60
  /** Every company (ROOT's assignable scope — root reaches all). */
53
61
  allNodes(): Promise<ScopeNodeOption[]>;
62
+ /**
63
+ * Phase 8 #13 — the CURRENT key (highest version: the only one the authenticator honours) of
64
+ * each account, from guard's `shield_authz.service_account_key`, in ONE batched read. Status
65
+ * columns only — never the hash. Read-only. Callers pass ids they already read through the
66
+ * own-tenant wall (`list`/`getById`); accounts with no key are simply absent.
67
+ */
68
+ currentKeys(accountIds: readonly string[]): Promise<ServiceAccountKeyRow[]>;
54
69
  /** List the caller's own-tenant accounts (newest first). Root sees all. */
55
70
  list(scope: SubtreeScope): Promise<ServiceAccountRow[]>;
56
71
  /** Fetch one account BY id, scoped to the caller's subtree. Cross-tenant/unknown → null (404). */
@@ -1 +1 @@
1
- {"version":3,"file":"service-account.store.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-account.store.d.ts","sourceRoot":"","sources":["../../../src/features/service-account/service-account.store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAMpC,+FAA+F;AAC/F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAUD,kGAAkG;AAClG,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B;AAED,qGAAqG;AACrG,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AA2DD,kGAAkG;AAClG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AAOD,qBAAa,mBAAmB;IAClB,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAMvC,0FAA0F;IACpF,UAAU,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAU/D,yDAAyD;IACnD,QAAQ,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAQvC,uFAAuF;IACjF,UAAU,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAUpE,kEAAkE;IAC5D,QAAQ,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC;IAQ5C;;;;;OAKG;IACG,WAAW,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAwBjF,2EAA2E;IACrE,IAAI,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAS7D,kGAAkG;IAC5F,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC;IASjF;;;;OAIG;IACG,MAAM,CAAC,KAAK,EAAE;QAClB,EAAE,EAAE,MAAM,CAAC;QACX,QAAQ,EAAE,MAAM,CAAC;QACjB,KAAK,EAAE,MAAM,CAAC;QACd,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;QACvC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;QAC3B,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;QAClB,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBjB;;;;OAIG;IACG,MAAM,CACV,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,YAAY,EACnB,KAAK,EAAE;QACL,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACxC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;KACjB,EACD,WAAW,EAAE,MAAM;IACnB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,GAAG,IAAI,GAClC,OAAO,CAAC,MAAM,CAAC;CAgCnB"}
@@ -2,6 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ServiceAccountStore = void 0;
4
4
  const APP = 'shield_app';
5
+ /** Guard's key table lives here (`1710000000017-CreateServiceAccountKey.cjs`); read-only here. */
6
+ const AUTHZ = 'shield_authz';
5
7
  /** Normalize a JSON-array column (driver returns array OR string) → string[]. Fail-safe to []. */
6
8
  function jsonArray(v) {
7
9
  const arr = Array.isArray(v)
@@ -83,6 +85,33 @@ class ServiceAccountStore {
83
85
  const rows = (await this.pool.query(`SELECT c_uuid AS id, c_name AS name, c_parent_uuid AS parentId FROM \`${APP}\`.\`companies\` ORDER BY c_name`, []));
84
86
  return rows.map((r) => ({ id: r.id, name: r.name, parentId: r.parentId ?? null }));
85
87
  }
88
+ /**
89
+ * Phase 8 #13 — the CURRENT key (highest version: the only one the authenticator honours) of
90
+ * each account, from guard's `shield_authz.service_account_key`, in ONE batched read. Status
91
+ * columns only — never the hash. Read-only. Callers pass ids they already read through the
92
+ * own-tenant wall (`list`/`getById`); accounts with no key are simply absent.
93
+ */
94
+ async currentKeys(accountIds) {
95
+ const u = uniqueIds(accountIds);
96
+ if (u.length === 0)
97
+ return [];
98
+ const rows = (await this.pool.query(`SELECT k.sak_account_id, k.sak_version, k.sak_expires_at_ms, k.sak_revoked_at_ms,
99
+ k.sak_created_at_ms
100
+ FROM \`${AUTHZ}\`.\`service_account_key\` k
101
+ JOIN (SELECT sak_account_id, MAX(sak_version) AS v
102
+ FROM \`${AUTHZ}\`.\`service_account_key\`
103
+ WHERE sak_account_id IN (${u.map(() => '?').join(', ')})
104
+ GROUP BY sak_account_id) m
105
+ ON m.sak_account_id = k.sak_account_id AND m.v = k.sak_version`, u));
106
+ const ms = (v) => (v === null || v === undefined ? null : Number(v));
107
+ return rows.map((r) => ({
108
+ accountId: r.sak_account_id,
109
+ version: Number(r.sak_version),
110
+ expiresAtMs: ms(r.sak_expires_at_ms),
111
+ revokedAtMs: ms(r.sak_revoked_at_ms),
112
+ createdAtMs: Number(r.sak_created_at_ms),
113
+ }));
114
+ }
86
115
  /** List the caller's own-tenant accounts (newest first). Root sees all. */
87
116
  async list(scope) {
88
117
  const t = tenantScope(scope);
@@ -17,9 +17,9 @@ export interface UserPatch {
17
17
  * Users are GLOBAL (one user can belong to many tenants) and the email is a credential attribute:
18
18
  * forgot-password mails the reset link to it. So (security hardening #3, D2):
19
19
  * - the patch is whitelisted to `{username, email, status}`;
20
- * - changing `email` or `status`, and deleting a user, require the actor to DOMINATE the target
21
- * (`GrantCeiling.assertDominates`: every membership of the target inside the actor's subtree,
22
- * and every key the target could hold is held by the actor);
20
+ * - changing `username` (Phase 9 #13a), `email` or `status`, and deleting a user, require the
21
+ * actor to DOMINATE the target (`GrantCeiling.assertDominates`: every membership of the target
22
+ * inside the actor's subtree, and every key the target could hold is held by the actor);
23
23
  * - an email change burns the target's outstanding password-reset tokens BEFORE the change and
24
24
  * notifies the OLD address after it (best-effort), when guard's reset-token store and email
25
25
  * sender are wired (`ShieldModule.forRoot({ auth: true })`).
@@ -46,7 +46,7 @@ export declare class UserService {
46
46
  listUsers(params?: ListParams): Promise<import("@astram/engine").RecordPage>;
47
47
  /** Read a single user. */
48
48
  getUser(uuid: string): Promise<import("@astram/engine").RecordView | null>;
49
- /** Update a user (whitelisted patch; email/status changes need dominance — see the class doc). */
49
+ /** Update a user (whitelisted patch; username/email/status changes need dominance — class doc). */
50
50
  updateUser(actor: Principal, uuid: string, patch: UserPatch): Promise<void>;
51
51
  /**
52
52
  * After a status change (security hardening Phase 3): bump every tenant the user is a member of
@@ -1 +1 @@
1
- {"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../../src/features/user/user.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAMzD,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,qBACa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IACY,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAMtE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAK1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAnB/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,YAAY,EAGrB,WAAW,CAAC,GAAE,eAAe,GAAG,IAAI,aAAA,EACK,WAAW,CAAC,GAAE,WAAW,GAAG,IAAI,aAAA,EAMzE,QAAQ,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAKhC,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D;;;;OAIG;IACG,UAAU,CACd,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAC5D,OAAO,CAAC,QAAQ,CAAC;IAcpB,uEAAuE;IACvE,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU;IAI7B,0BAA0B;IAC1B,OAAO,CAAC,IAAI,EAAE,MAAM;IAIpB,kGAAkG;IAC5F,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BjF;;;;;;;;;OASG;YACW,iBAAiB;IAY/B,uDAAuD;IACjD,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D,6FAA6F;YAC/E,kBAAkB;CAkBjC"}
1
+ {"version":3,"file":"user.service.d.ts","sourceRoot":"","sources":["../../../src/features/user/user.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,KAAK,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACpB,MAAM,eAAe,CAAC;AACvB,OAAO,EAEL,KAAK,kBAAkB,EACxB,MAAM,uCAAuC,CAAC;AAC/C,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAMzD,4EAA4E;AAC5E,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,qBACa,WAAW;IAIpB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAGxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IACY,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAMtE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;IAK1B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAnB/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgC;gBAGpC,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,YAAY,EAGrB,WAAW,CAAC,GAAE,eAAe,GAAG,IAAI,aAAA,EACK,WAAW,CAAC,GAAE,WAAW,GAAG,IAAI,aAAA,EAMzE,QAAQ,CAAC,GAAE,cAAc,GAAG,IAAI,aAAA,EAKhC,WAAW,CAAC,GAAE,kBAAkB,GAAG,IAAI,aAAA;IAG1D;;;;OAIG;IACG,UAAU,CACd,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE,GAC5D,OAAO,CAAC,QAAQ,CAAC;IAcpB,uEAAuE;IACvE,SAAS,CAAC,MAAM,CAAC,EAAE,UAAU;IAI7B,0BAA0B;IAC1B,OAAO,CAAC,IAAI,EAAE,MAAM;IAIpB,mGAAmG;IAC7F,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAkCjF;;;;;;;;;OASG;YACW,iBAAiB;IAY/B,uDAAuD;IACjD,UAAU,CAAC,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/D,6FAA6F;YAC/E,kBAAkB;CAkBjC"}
@@ -30,9 +30,9 @@ const USER_SORT_FIELDS = ['username', 'email', 'status', 'createdAt'];
30
30
  * Users are GLOBAL (one user can belong to many tenants) and the email is a credential attribute:
31
31
  * forgot-password mails the reset link to it. So (security hardening #3, D2):
32
32
  * - the patch is whitelisted to `{username, email, status}`;
33
- * - changing `email` or `status`, and deleting a user, require the actor to DOMINATE the target
34
- * (`GrantCeiling.assertDominates`: every membership of the target inside the actor's subtree,
35
- * and every key the target could hold is held by the actor);
33
+ * - changing `username` (Phase 9 #13a), `email` or `status`, and deleting a user, require the
34
+ * actor to DOMINATE the target (`GrantCeiling.assertDominates`: every membership of the target
35
+ * inside the actor's subtree, and every key the target could hold is held by the actor);
36
36
  * - an email change burns the target's outstanding password-reset tokens BEFORE the change and
37
37
  * notifies the OLD address after it (best-effort), when guard's reset-token store and email
38
38
  * sender are wired (`ShieldModule.forRoot({ auth: true })`).
@@ -79,13 +79,16 @@ let UserService = UserService_1 = class UserService {
79
79
  getUser(uuid) {
80
80
  return this.records.get('user', uuid);
81
81
  }
82
- /** Update a user (whitelisted patch; email/status changes need dominance — see the class doc). */
82
+ /** Update a user (whitelisted patch; username/email/status changes need dominance — class doc). */
83
83
  async updateUser(actor, uuid, patch) {
84
84
  const current = await this.records.get('user', uuid);
85
85
  if (current === null)
86
86
  throw new identity_errors_js_1.IdentityNotFoundError('User not found.');
87
87
  const fields = {};
88
- if (typeof patch?.username === 'string')
88
+ // Phase 9 #13a: the username is a sign-in identifier like the email, so changing it needs the
89
+ // same dominance (re-sending the current username changes nothing and needs none).
90
+ const usernameChanged = typeof patch?.username === 'string' && patch.username !== current.fields.username;
91
+ if (usernameChanged)
89
92
  fields.username = patch.username;
90
93
  const oldEmail = typeof current.fields.email === 'string' ? current.fields.email : '';
91
94
  const emailChanged = typeof patch?.email === 'string' && patch.email !== oldEmail;
@@ -98,7 +101,7 @@ let UserService = UserService_1 = class UserService {
98
101
  fields.email = patch.email;
99
102
  if (statusChanged)
100
103
  fields.status = patch.status;
101
- if (emailChanged || statusChanged || wantsInactive) {
104
+ if (usernameChanged || emailChanged || statusChanged || wantsInactive) {
102
105
  await this.ceiling.assertDominates(actor, uuid);
103
106
  }
104
107
  if (Object.keys(fields).length > 0) {
package/dist/index.d.ts CHANGED
@@ -36,17 +36,9 @@ export { identityAuthActionKeys, identityControlPlaneKeys } from './auth-facts-k
36
36
  * `identityObjects`, and mounts the CRUD controllers — no separate engine setup needed.
37
37
  */
38
38
  export { IdentityFeatureModule } from './features/identity-feature.module.js';
39
- /**
40
- * The record-tier authorization wiring: the lean grant-read pool factory + the helper that
41
- * assembles `RecordAuthorizationInputs` for `ShieldModule.forRoot({ recordAuthorization })`.
42
- * Once wired, `decide()` is real (the framework's record-tier adapters run over the identity
43
- * app schema) instead of the deny-everything stub.
44
- */
45
- export { createIdentityAuthzPool, buildIdentityRecordAuthorization, } from './features/authz/record-authorization.js';
46
39
  /**
47
40
  * The ADR-0011 per-request session-facts reader (ALS hot path), re-exported so a consumer
48
- * that wants to construct it directly (rather than via `buildIdentityRecordAuthorization`)
49
- * can do so.
41
+ * that wants to construct it directly can do so.
50
42
  */
51
43
  export { AlsSessionFactsReader } from './features/authz/session-facts.reader.js';
52
44
  /**
@@ -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,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,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"}
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,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAC7D,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;AAU9E;;;GAGG;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.DbMembershipChecker = exports.DbScopeSubtreeResolver = exports.AlsSessionFactsReader = exports.buildIdentityRecordAuthorization = exports.createIdentityAuthzPool = exports.IdentityFeatureModule = exports.identityControlPlaneKeys = exports.identityAuthActionKeys = exports.identityObjects = exports.identityMigrationsDir = exports.ServiceAccount = 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.IdentityFeatureModule = exports.identityControlPlaneKeys = exports.identityAuthActionKeys = exports.identityObjects = exports.identityMigrationsDir = exports.ServiceAccount = 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) +
@@ -54,19 +54,16 @@ Object.defineProperty(exports, "identityControlPlaneKeys", { enumerable: true, g
54
54
  */
55
55
  var identity_feature_module_js_1 = require("./features/identity-feature.module.js");
56
56
  Object.defineProperty(exports, "IdentityFeatureModule", { enumerable: true, get: function () { return identity_feature_module_js_1.IdentityFeatureModule; } });
57
- /**
58
- * The record-tier authorization wiring: the lean grant-read pool factory + the helper that
59
- * assembles `RecordAuthorizationInputs` for `ShieldModule.forRoot({ recordAuthorization })`.
60
- * Once wired, `decide()` is real (the framework's record-tier adapters run over the identity
61
- * app schema) instead of the deny-everything stub.
57
+ /*
58
+ * The record-tier authorization: since 0.7 (security hardening Phase 9 #7)
59
+ * `IdentityFeatureModule.forRoot({ extraObjects })` builds it and provides it to `ShieldModule`
60
+ * (guard's `SHIELD_RECORD_AUTHORIZATION`); `buildIdentityRecordAuthorization` is removed, and the
61
+ * app passes no `recordAuthorization`. `createIdentityAuthzPool` (a raw pool, e.g. for
62
+ * `DbScopeSubtreeResolver`) stays on `@astram/identity/internal`, composition root only.
62
63
  */
63
- var record_authorization_js_1 = require("./features/authz/record-authorization.js");
64
- Object.defineProperty(exports, "createIdentityAuthzPool", { enumerable: true, get: function () { return record_authorization_js_1.createIdentityAuthzPool; } });
65
- Object.defineProperty(exports, "buildIdentityRecordAuthorization", { enumerable: true, get: function () { return record_authorization_js_1.buildIdentityRecordAuthorization; } });
66
64
  /**
67
65
  * The ADR-0011 per-request session-facts reader (ALS hot path), re-exported so a consumer
68
- * that wants to construct it directly (rather than via `buildIdentityRecordAuthorization`)
69
- * can do so.
66
+ * that wants to construct it directly can do so.
70
67
  */
71
68
  var session_facts_reader_js_1 = require("./features/authz/session-facts.reader.js");
72
69
  Object.defineProperty(exports, "AlsSessionFactsReader", { enumerable: true, get: function () { return session_facts_reader_js_1.AlsSessionFactsReader; } });
@@ -0,0 +1,11 @@
1
+ /**
2
+ * @astram/identity/internal — composition-root surface (Phase 8 #5).
3
+ *
4
+ * `createIdentityAuthzPool` opens a raw `mariadb` pool, so it is NOT on the package entry point
5
+ * (`@astram/identity`). An app's `src/app.module.ts` imports it from here to wire pool-backed ports
6
+ * such as `new DbScopeSubtreeResolver(authzPool)` (the record authorization itself is built by
7
+ * `IdentityFeatureModule` since 0.7); the scaffolded app's lint wall rejects this
8
+ * subpath everywhere else. Feature code never holds a pool: it uses the RecordEngine.
9
+ */
10
+ export { createIdentityAuthzPool } from './features/authz/record-authorization.js';
11
+ //# sourceMappingURL=internal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"internal.d.ts","sourceRoot":"","sources":["../src/internal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,uBAAuB,EAAE,MAAM,0CAA0C,CAAC"}
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createIdentityAuthzPool = void 0;
4
+ /**
5
+ * @astram/identity/internal — composition-root surface (Phase 8 #5).
6
+ *
7
+ * `createIdentityAuthzPool` opens a raw `mariadb` pool, so it is NOT on the package entry point
8
+ * (`@astram/identity`). An app's `src/app.module.ts` imports it from here to wire pool-backed ports
9
+ * such as `new DbScopeSubtreeResolver(authzPool)` (the record authorization itself is built by
10
+ * `IdentityFeatureModule` since 0.7); the scaffolded app's lint wall rejects this
11
+ * subpath everywhere else. Feature code never holds a pool: it uses the RecordEngine.
12
+ */
13
+ var record_authorization_js_1 = require("./features/authz/record-authorization.js");
14
+ Object.defineProperty(exports, "createIdentityAuthzPool", { enumerable: true, get: function () { return record_authorization_js_1.createIdentityAuthzPool; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astram/identity",
3
- "version": "0.5.0",
3
+ "version": "0.7.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",
@@ -10,8 +10,22 @@
10
10
  ".": {
11
11
  "types": "./dist/index.d.ts",
12
12
  "default": "./dist/index.js"
13
+ },
14
+ "./internal": {
15
+ "types": "./dist/internal.d.ts",
16
+ "default": "./dist/internal.js"
13
17
  }
14
18
  },
19
+ "typesVersions": {
20
+ "*": {
21
+ "internal": [
22
+ "dist/internal.d.ts"
23
+ ]
24
+ }
25
+ },
26
+ "bin": {
27
+ "astram-sa-keys-report": "dist/bin/sa-keys-report.js"
28
+ },
15
29
  "files": [
16
30
  "dist",
17
31
  "migrations"
@@ -21,10 +35,10 @@
21
35
  "access": "public"
22
36
  },
23
37
  "dependencies": {
24
- "mariadb": "^3.4.0",
38
+ "mariadb": "^3.5.4",
25
39
  "@astram/core": "1.4.0",
26
- "@astram/guard": "0.8.0",
27
- "@astram/engine": "0.4.0"
40
+ "@astram/engine": "0.5.1",
41
+ "@astram/guard": "0.10.0"
28
42
  },
29
43
  "peerDependencies": {
30
44
  "@nestjs/common": "^11",