@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.
- package/README.md +15 -0
- package/dist/bin/sa-keys-report.d.ts +3 -0
- package/dist/bin/sa-keys-report.d.ts.map +1 -0
- package/dist/bin/sa-keys-report.js +18 -0
- package/dist/features/authz/grant-admin.service.d.ts +33 -1
- package/dist/features/authz/grant-admin.service.d.ts.map +1 -1
- package/dist/features/authz/grant-admin.service.js +105 -1
- package/dist/features/authz/grant-ceiling.d.ts +62 -1
- package/dist/features/authz/grant-ceiling.d.ts.map +1 -1
- package/dist/features/authz/grant-ceiling.js +145 -10
- package/dist/features/authz/permission-version.bumper.d.ts +7 -0
- package/dist/features/authz/permission-version.bumper.d.ts.map +1 -1
- package/dist/features/authz/permission-version.bumper.js +19 -10
- package/dist/features/authz/record-authorization.d.ts +16 -21
- package/dist/features/authz/record-authorization.d.ts.map +1 -1
- package/dist/features/authz/record-authorization.js +17 -22
- package/dist/features/company/company-hierarchy.service.d.ts +71 -4
- package/dist/features/company/company-hierarchy.service.d.ts.map +1 -1
- package/dist/features/company/company-hierarchy.service.js +197 -8
- package/dist/features/company/company.controller.d.ts +1 -1
- package/dist/features/company/company.controller.d.ts.map +1 -1
- package/dist/features/company/company.controller.js +9 -5
- package/dist/features/company/company.service.d.ts +9 -2
- package/dist/features/company/company.service.d.ts.map +1 -1
- package/dist/features/company/company.service.js +17 -2
- package/dist/features/identity-feature.module.d.ts +15 -6
- package/dist/features/identity-feature.module.d.ts.map +1 -1
- package/dist/features/identity-feature.module.js +79 -22
- package/dist/features/membership/posting-version-bump.d.ts +7 -0
- package/dist/features/membership/posting-version-bump.d.ts.map +1 -1
- package/dist/features/membership/posting.service.d.ts +2 -0
- package/dist/features/membership/posting.service.d.ts.map +1 -1
- package/dist/features/membership/posting.service.js +9 -0
- package/dist/features/role/role.service.d.ts +32 -2
- package/dist/features/role/role.service.d.ts.map +1 -1
- package/dist/features/role/role.service.js +64 -4
- package/dist/features/scope/scope-closure.service.d.ts +11 -1
- package/dist/features/scope/scope-closure.service.d.ts.map +1 -1
- package/dist/features/scope/scope-closure.service.js +23 -4
- package/dist/features/service-account/lock-service-accounts.d.ts +29 -0
- package/dist/features/service-account/lock-service-accounts.d.ts.map +1 -0
- package/dist/features/service-account/lock-service-accounts.js +35 -0
- package/dist/features/service-account/no-expiry-key-report.d.ts +39 -0
- package/dist/features/service-account/no-expiry-key-report.d.ts.map +1 -0
- package/dist/features/service-account/no-expiry-key-report.js +91 -0
- package/dist/features/service-account/service-account.service.d.ts +15 -0
- package/dist/features/service-account/service-account.service.d.ts.map +1 -1
- package/dist/features/service-account/service-account.service.js +17 -1
- package/dist/features/service-account/service-account.store.d.ts +15 -0
- package/dist/features/service-account/service-account.store.d.ts.map +1 -1
- package/dist/features/service-account/service-account.store.js +29 -0
- package/dist/features/user/user.service.d.ts +4 -4
- package/dist/features/user/user.service.d.ts.map +1 -1
- package/dist/features/user/user.service.js +9 -6
- package/dist/index.d.ts +1 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -11
- package/dist/internal.d.ts +11 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +14 -0
- 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":"AAkBA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhE,OAAO,KAAK,EACV,mBAAmB,EAGnB,UAAU,EACV,eAAe,EAChB,MAAM,4BAA4B,CAAC;AAEpC,gGAAgG;AAChG,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,oFAAoF;AACpF,qBAAa,0BAA2B,SAAQ,KAAK;gBACvC,OAAO,EAAE,MAAM;CAI5B;AAED,4FAA4F;AAC5F,qBAAa,2BAA4B,SAAQ,KAAK;;CAKrD;AAED,0EAA0E;AAC1E,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,OAAO,EAAE,MAAM;CAI5B;AAED,kGAAkG;AAClG,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD,2FAA2F;IAC3F,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;
|
|
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":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;
|
|
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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"}
|
|
@@ -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
|
|
21
|
-
* (`GrantCeiling.assertDominates`: every membership of the target
|
|
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 —
|
|
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,
|
|
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
|
|
34
|
-
* (`GrantCeiling.assertDominates`: every membership of the target
|
|
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 —
|
|
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
|
-
|
|
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
|
|
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
|
/**
|
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,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;
|
|
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.
|
|
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
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* app
|
|
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
|
|
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"}
|
package/dist/internal.js
ADDED
|
@@ -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.
|
|
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
|
|
38
|
+
"mariadb": "^3.5.4",
|
|
25
39
|
"@astram/core": "1.4.0",
|
|
26
|
-
"@astram/
|
|
27
|
-
"@astram/
|
|
40
|
+
"@astram/engine": "0.5.1",
|
|
41
|
+
"@astram/guard": "0.10.0"
|
|
28
42
|
},
|
|
29
43
|
"peerDependencies": {
|
|
30
44
|
"@nestjs/common": "^11",
|