@open-mercato/core 0.6.6-develop.6535.1.5cf43724de → 0.6.6-develop.6540.1.ae856df1ec
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/.turbo/turbo-build.log +1 -1
- package/dist/modules/api_keys/cli.js +2 -1
- package/dist/modules/api_keys/cli.js.map +2 -2
- package/dist/modules/attachments/api/file/[id]/route.js +9 -7
- package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +9 -7
- package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
- package/dist/modules/attachments/api/route.js +21 -17
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/lib/reconcileOrganization.js +110 -0
- package/dist/modules/attachments/lib/reconcileOrganization.js.map +7 -0
- package/dist/modules/attachments/lib/requestScope.js +10 -0
- package/dist/modules/attachments/lib/requestScope.js.map +7 -0
- package/dist/modules/auth/api/users/route.js +6 -2
- package/dist/modules/auth/api/users/route.js.map +2 -2
- package/dist/modules/auth/cli.js +3 -2
- package/dist/modules/auth/cli.js.map +2 -2
- package/dist/modules/configs/lib/upgrade-actions.js +22 -0
- package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
- package/dist/modules/dashboards/cli.js +4 -3
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +10 -3
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/entities/api/records.js +5 -4
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/cli.js +2 -1
- package/dist/modules/entities/cli.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/api_keys/cli.ts +2 -3
- package/src/modules/attachments/api/file/[id]/route.ts +11 -7
- package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +11 -7
- package/src/modules/attachments/api/route.ts +26 -21
- package/src/modules/attachments/lib/reconcileOrganization.ts +188 -0
- package/src/modules/attachments/lib/requestScope.ts +27 -0
- package/src/modules/auth/api/users/route.ts +11 -2
- package/src/modules/auth/cli.ts +3 -2
- package/src/modules/configs/i18n/de.json +5 -1
- package/src/modules/configs/i18n/en.json +5 -1
- package/src/modules/configs/i18n/es.json +5 -1
- package/src/modules/configs/i18n/pl.json +5 -1
- package/src/modules/configs/lib/upgrade-actions.ts +24 -0
- package/src/modules/dashboards/cli.ts +4 -9
- package/src/modules/directory/utils/organizationScope.ts +36 -2
- package/src/modules/entities/api/records.ts +5 -7
- package/src/modules/entities/cli.ts +2 -1
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createLogger } from "@open-mercato/shared/lib/logger";
|
|
2
|
+
import { Attachment } from "../data/entities.js";
|
|
3
|
+
const logger = createLogger("attachments").child({ component: "reconcileOrganization" });
|
|
4
|
+
const LIBRARY_ENTITY_ID = "attachments:library";
|
|
5
|
+
const DEFAULT_BATCH_SIZE = 500;
|
|
6
|
+
function readParentOrganizationId(record) {
|
|
7
|
+
const raw = record["organization_id"] ?? record["organizationId"];
|
|
8
|
+
if (typeof raw === "string" && raw.trim().length) return raw.trim();
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
function ensureBucket(report, entityId) {
|
|
12
|
+
const existing = report.byEntity[entityId];
|
|
13
|
+
if (existing) return existing;
|
|
14
|
+
const bucket = { scanned: 0, updated: 0, unresolved: 0 };
|
|
15
|
+
report.byEntity[entityId] = bucket;
|
|
16
|
+
return bucket;
|
|
17
|
+
}
|
|
18
|
+
async function reconcileAttachmentOrganizations(opts) {
|
|
19
|
+
const { em, queryEngine, tenantId } = opts;
|
|
20
|
+
const batchSize = opts.batchSize && opts.batchSize > 0 ? opts.batchSize : DEFAULT_BATCH_SIZE;
|
|
21
|
+
const report = {
|
|
22
|
+
scanned: 0,
|
|
23
|
+
updated: 0,
|
|
24
|
+
unresolved: 0,
|
|
25
|
+
skippedVirtual: 0,
|
|
26
|
+
byEntity: {}
|
|
27
|
+
};
|
|
28
|
+
const rows = await em.getConnection().execute(
|
|
29
|
+
"select id, entity_id, record_id, organization_id from attachments where tenant_id = ?",
|
|
30
|
+
[tenantId]
|
|
31
|
+
);
|
|
32
|
+
report.scanned = rows.length;
|
|
33
|
+
if (!rows.length) return report;
|
|
34
|
+
const groups = /* @__PURE__ */ new Map();
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
if (!row.id || !row.entity_id || !row.record_id) continue;
|
|
37
|
+
const list = groups.get(row.entity_id) ?? [];
|
|
38
|
+
list.push(row);
|
|
39
|
+
groups.set(row.entity_id, list);
|
|
40
|
+
}
|
|
41
|
+
const pendingUpdates = [];
|
|
42
|
+
for (const [entityId, group] of groups) {
|
|
43
|
+
const bucket = ensureBucket(report, entityId);
|
|
44
|
+
bucket.scanned += group.length;
|
|
45
|
+
if (entityId === LIBRARY_ENTITY_ID) {
|
|
46
|
+
report.skippedVirtual += group.length;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const recordIds = Array.from(
|
|
50
|
+
new Set(group.map((row) => row.record_id).filter((value) => !!value))
|
|
51
|
+
);
|
|
52
|
+
const parentOrgByRecordId = /* @__PURE__ */ new Map();
|
|
53
|
+
let resolvable = true;
|
|
54
|
+
for (let index = 0; index < recordIds.length; index += batchSize) {
|
|
55
|
+
const chunk = recordIds.slice(index, index + batchSize);
|
|
56
|
+
try {
|
|
57
|
+
const result = await queryEngine.query(entityId, {
|
|
58
|
+
fields: ["id", "organization_id"],
|
|
59
|
+
filters: { id: chunk.length === 1 ? { $eq: chunk[0] } : { $in: chunk } },
|
|
60
|
+
tenantId,
|
|
61
|
+
withDeleted: true,
|
|
62
|
+
page: { pageSize: Math.max(chunk.length, 1) }
|
|
63
|
+
});
|
|
64
|
+
for (const item of result.items ?? []) {
|
|
65
|
+
const record = item;
|
|
66
|
+
const recordId = record.id != null ? String(record.id) : null;
|
|
67
|
+
if (!recordId) continue;
|
|
68
|
+
parentOrgByRecordId.set(recordId, readParentOrganizationId(record));
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
resolvable = false;
|
|
72
|
+
logger.warn("org reconcile: cannot resolve parent organization for entity", {
|
|
73
|
+
entityId,
|
|
74
|
+
error
|
|
75
|
+
});
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (!resolvable) {
|
|
80
|
+
bucket.unresolved += group.length;
|
|
81
|
+
report.unresolved += group.length;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
for (const row of group) {
|
|
85
|
+
const target = row.record_id ? parentOrgByRecordId.get(row.record_id) : null;
|
|
86
|
+
if (!target) {
|
|
87
|
+
bucket.unresolved += 1;
|
|
88
|
+
report.unresolved += 1;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if ((row.organization_id ?? null) === target) continue;
|
|
92
|
+
pendingUpdates.push({ id: row.id, organizationId: target });
|
|
93
|
+
bucket.updated += 1;
|
|
94
|
+
report.updated += 1;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (let index = 0; index < pendingUpdates.length; index += batchSize) {
|
|
98
|
+
const chunk = pendingUpdates.slice(index, index + batchSize);
|
|
99
|
+
for (const update of chunk) {
|
|
100
|
+
const reference = em.getReference(Attachment, update.id);
|
|
101
|
+
reference.organizationId = update.organizationId;
|
|
102
|
+
}
|
|
103
|
+
await em.flush();
|
|
104
|
+
}
|
|
105
|
+
return report;
|
|
106
|
+
}
|
|
107
|
+
export {
|
|
108
|
+
reconcileAttachmentOrganizations
|
|
109
|
+
};
|
|
110
|
+
//# sourceMappingURL=reconcileOrganization.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/attachments/lib/reconcileOrganization.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport { Attachment } from '../data/entities'\n\nconst logger = createLogger('attachments').child({ component: 'reconcileOrganization' })\n\n/**\n * Virtual entity id for library attachments \u2014 these are not attached to a\n * parent record that owns an organization, so there is nothing to reconcile\n * against. They are counted as skipped rather than unresolved.\n */\nconst LIBRARY_ENTITY_ID = 'attachments:library'\n\nconst DEFAULT_BATCH_SIZE = 500\n\nexport type AttachmentOrgReconcileEntityStat = {\n scanned: number\n updated: number\n unresolved: number\n}\n\nexport type AttachmentOrgReconcileReport = {\n scanned: number\n updated: number\n unresolved: number\n skippedVirtual: number\n byEntity: Record<string, AttachmentOrgReconcileEntityStat>\n}\n\ntype AttachmentScanRow = {\n id: string\n entity_id: string | null\n record_id: string | null\n organization_id: string | null\n}\n\nfunction readParentOrganizationId(record: Record<string, unknown>): string | null {\n const raw = record['organization_id'] ?? record['organizationId']\n if (typeof raw === 'string' && raw.trim().length) return raw.trim()\n return null\n}\n\nfunction ensureBucket(\n report: AttachmentOrgReconcileReport,\n entityId: string,\n): AttachmentOrgReconcileEntityStat {\n const existing = report.byEntity[entityId]\n if (existing) return existing\n const bucket: AttachmentOrgReconcileEntityStat = { scanned: 0, updated: 0, unresolved: 0 }\n report.byEntity[entityId] = bucket\n return bucket\n}\n\n/**\n * Reconcile the `organization_id` of existing attachments to the organization\n * of the record they are attached to.\n *\n * Background (#3765): before the upload route became selected-organization\n * aware, a multi-org admin who switched the header organization uploaded files\n * that were silently stored under their *home* organization instead of the\n * selected one (the organization of the parent record). Those rows are now\n * invisible to org-scoped reads and stay orphaned. This heals them forward.\n *\n * The parent record's organization is the ground truth: an attachment is not\n * distinguishable from a legitimately home-org one by looking at the attachment\n * alone. Parent organizations are resolved generically through the Query Engine\n * by `entityId` (works for both base and custom entities, tenant-scoped and\n * organization-agnostic so the parent's real org is returned regardless of\n * which org the caller sits in) \u2014 the same mechanism the attachments module\n * already uses to enrich assignment details.\n *\n * The reconciliation is:\n * - idempotent \u2014 rows already matching their parent are left untouched;\n * - conservative \u2014 when the parent org cannot be resolved (unregistered\n * entity id, hard-deleted parent, parent without an org column) the row is\n * counted as `unresolved` and left as-is rather than guessed at;\n * - tenant-scoped \u2014 it reconciles every attachment in the tenant across all\n * organizations (the whole point is moving rows between orgs).\n *\n * Writes go through the passed `EntityManager` (which the caller runs inside a\n * transaction) so the whole reconciliation commits atomically.\n */\nexport async function reconcileAttachmentOrganizations(opts: {\n em: EntityManager\n queryEngine: QueryEngine\n tenantId: string\n batchSize?: number\n}): Promise<AttachmentOrgReconcileReport> {\n const { em, queryEngine, tenantId } = opts\n const batchSize = opts.batchSize && opts.batchSize > 0 ? opts.batchSize : DEFAULT_BATCH_SIZE\n const report: AttachmentOrgReconcileReport = {\n scanned: 0,\n updated: 0,\n unresolved: 0,\n skippedVirtual: 0,\n byEntity: {},\n }\n\n const rows = (await em.getConnection().execute(\n 'select id, entity_id, record_id, organization_id from attachments where tenant_id = ?',\n [tenantId],\n )) as AttachmentScanRow[]\n report.scanned = rows.length\n if (!rows.length) return report\n\n const groups = new Map<string, AttachmentScanRow[]>()\n for (const row of rows) {\n if (!row.id || !row.entity_id || !row.record_id) continue\n const list = groups.get(row.entity_id) ?? []\n list.push(row)\n groups.set(row.entity_id, list)\n }\n\n const pendingUpdates: Array<{ id: string; organizationId: string }> = []\n\n for (const [entityId, group] of groups) {\n const bucket = ensureBucket(report, entityId)\n bucket.scanned += group.length\n\n if (entityId === LIBRARY_ENTITY_ID) {\n report.skippedVirtual += group.length\n continue\n }\n\n const recordIds = Array.from(\n new Set(group.map((row) => row.record_id).filter((value): value is string => !!value)),\n )\n const parentOrgByRecordId = new Map<string, string | null>()\n let resolvable = true\n\n for (let index = 0; index < recordIds.length; index += batchSize) {\n const chunk = recordIds.slice(index, index + batchSize)\n try {\n const result = await queryEngine.query(entityId as any, {\n fields: ['id', 'organization_id'],\n filters: { id: chunk.length === 1 ? { $eq: chunk[0] } : { $in: chunk } },\n tenantId,\n withDeleted: true,\n page: { pageSize: Math.max(chunk.length, 1) },\n })\n for (const item of result.items ?? []) {\n const record = item as Record<string, unknown>\n const recordId = record.id != null ? String(record.id) : null\n if (!recordId) continue\n parentOrgByRecordId.set(recordId, readParentOrganizationId(record))\n }\n } catch (error) {\n resolvable = false\n logger.warn('org reconcile: cannot resolve parent organization for entity', {\n entityId,\n error,\n })\n break\n }\n }\n\n if (!resolvable) {\n bucket.unresolved += group.length\n report.unresolved += group.length\n continue\n }\n\n for (const row of group) {\n const target = row.record_id ? parentOrgByRecordId.get(row.record_id) : null\n if (!target) {\n bucket.unresolved += 1\n report.unresolved += 1\n continue\n }\n if ((row.organization_id ?? null) === target) continue\n pendingUpdates.push({ id: row.id, organizationId: target })\n bucket.updated += 1\n report.updated += 1\n }\n }\n\n for (let index = 0; index < pendingUpdates.length; index += batchSize) {\n const chunk = pendingUpdates.slice(index, index + batchSize)\n for (const update of chunk) {\n const reference = em.getReference(Attachment, update.id)\n reference.organizationId = update.organizationId\n }\n await em.flush()\n }\n\n return report\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,oBAAoB;AAE7B,SAAS,kBAAkB;AAE3B,MAAM,SAAS,aAAa,aAAa,EAAE,MAAM,EAAE,WAAW,wBAAwB,CAAC;AAOvF,MAAM,oBAAoB;AAE1B,MAAM,qBAAqB;AAuB3B,SAAS,yBAAyB,QAAgD;AAChF,QAAM,MAAM,OAAO,iBAAiB,KAAK,OAAO,gBAAgB;AAChE,MAAI,OAAO,QAAQ,YAAY,IAAI,KAAK,EAAE,OAAQ,QAAO,IAAI,KAAK;AAClE,SAAO;AACT;AAEA,SAAS,aACP,QACA,UACkC;AAClC,QAAM,WAAW,OAAO,SAAS,QAAQ;AACzC,MAAI,SAAU,QAAO;AACrB,QAAM,SAA2C,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,EAAE;AACzF,SAAO,SAAS,QAAQ,IAAI;AAC5B,SAAO;AACT;AA+BA,eAAsB,iCAAiC,MAKb;AACxC,QAAM,EAAE,IAAI,aAAa,SAAS,IAAI;AACtC,QAAM,YAAY,KAAK,aAAa,KAAK,YAAY,IAAI,KAAK,YAAY;AAC1E,QAAM,SAAuC;AAAA,IAC3C,SAAS;AAAA,IACT,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,UAAU,CAAC;AAAA,EACb;AAEA,QAAM,OAAQ,MAAM,GAAG,cAAc,EAAE;AAAA,IACrC;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AACA,SAAO,UAAU,KAAK;AACtB,MAAI,CAAC,KAAK,OAAQ,QAAO;AAEzB,QAAM,SAAS,oBAAI,IAAiC;AACpD,aAAW,OAAO,MAAM;AACtB,QAAI,CAAC,IAAI,MAAM,CAAC,IAAI,aAAa,CAAC,IAAI,UAAW;AACjD,UAAM,OAAO,OAAO,IAAI,IAAI,SAAS,KAAK,CAAC;AAC3C,SAAK,KAAK,GAAG;AACb,WAAO,IAAI,IAAI,WAAW,IAAI;AAAA,EAChC;AAEA,QAAM,iBAAgE,CAAC;AAEvE,aAAW,CAAC,UAAU,KAAK,KAAK,QAAQ;AACtC,UAAM,SAAS,aAAa,QAAQ,QAAQ;AAC5C,WAAO,WAAW,MAAM;AAExB,QAAI,aAAa,mBAAmB;AAClC,aAAO,kBAAkB,MAAM;AAC/B;AAAA,IACF;AAEA,UAAM,YAAY,MAAM;AAAA,MACtB,IAAI,IAAI,MAAM,IAAI,CAAC,QAAQ,IAAI,SAAS,EAAE,OAAO,CAAC,UAA2B,CAAC,CAAC,KAAK,CAAC;AAAA,IACvF;AACA,UAAM,sBAAsB,oBAAI,IAA2B;AAC3D,QAAI,aAAa;AAEjB,aAAS,QAAQ,GAAG,QAAQ,UAAU,QAAQ,SAAS,WAAW;AAChE,YAAM,QAAQ,UAAU,MAAM,OAAO,QAAQ,SAAS;AACtD,UAAI;AACF,cAAM,SAAS,MAAM,YAAY,MAAM,UAAiB;AAAA,UACtD,QAAQ,CAAC,MAAM,iBAAiB;AAAA,UAChC,SAAS,EAAE,IAAI,MAAM,WAAW,IAAI,EAAE,KAAK,MAAM,CAAC,EAAE,IAAI,EAAE,KAAK,MAAM,EAAE;AAAA,UACvE;AAAA,UACA,aAAa;AAAA,UACb,MAAM,EAAE,UAAU,KAAK,IAAI,MAAM,QAAQ,CAAC,EAAE;AAAA,QAC9C,CAAC;AACD,mBAAW,QAAQ,OAAO,SAAS,CAAC,GAAG;AACrC,gBAAM,SAAS;AACf,gBAAM,WAAW,OAAO,MAAM,OAAO,OAAO,OAAO,EAAE,IAAI;AACzD,cAAI,CAAC,SAAU;AACf,8BAAoB,IAAI,UAAU,yBAAyB,MAAM,CAAC;AAAA,QACpE;AAAA,MACF,SAAS,OAAO;AACd,qBAAa;AACb,eAAO,KAAK,gEAAgE;AAAA,UAC1E;AAAA,UACA;AAAA,QACF,CAAC;AACD;AAAA,MACF;AAAA,IACF;AAEA,QAAI,CAAC,YAAY;AACf,aAAO,cAAc,MAAM;AAC3B,aAAO,cAAc,MAAM;AAC3B;AAAA,IACF;AAEA,eAAW,OAAO,OAAO;AACvB,YAAM,SAAS,IAAI,YAAY,oBAAoB,IAAI,IAAI,SAAS,IAAI;AACxE,UAAI,CAAC,QAAQ;AACX,eAAO,cAAc;AACrB,eAAO,cAAc;AACrB;AAAA,MACF;AACA,WAAK,IAAI,mBAAmB,UAAU,OAAQ;AAC9C,qBAAe,KAAK,EAAE,IAAI,IAAI,IAAI,gBAAgB,OAAO,CAAC;AAC1D,aAAO,WAAW;AAClB,aAAO,WAAW;AAAA,IACpB;AAAA,EACF;AAEA,WAAS,QAAQ,GAAG,QAAQ,eAAe,QAAQ,SAAS,WAAW;AACrE,UAAM,QAAQ,eAAe,MAAM,OAAO,QAAQ,SAAS;AAC3D,eAAW,UAAU,OAAO;AAC1B,YAAM,YAAY,GAAG,aAAa,YAAY,OAAO,EAAE;AACvD,gBAAU,iBAAiB,OAAO;AAAA,IACpC;AACA,UAAM,GAAG,MAAM;AAAA,EACjB;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { resolveOrganizationScopeForRequest } from "@open-mercato/core/modules/directory/utils/organizationScope";
|
|
2
|
+
async function resolveAttachmentOrganizationId(container, auth, request) {
|
|
3
|
+
if (!auth) return null;
|
|
4
|
+
const scope = await resolveOrganizationScopeForRequest({ container, auth, request });
|
|
5
|
+
return scope?.selectedId ?? auth.orgId ?? null;
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
resolveAttachmentOrganizationId
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=requestScope.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/attachments/lib/requestScope.ts"],
|
|
4
|
+
"sourcesContent": ["import type { AwilixContainer } from 'awilix'\nimport type { AuthContext } from '@open-mercato/shared/lib/auth/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\n\n/**\n * Resolve the organization an attachment request should act within.\n *\n * `auth.orgId` alone is NOT selected-organization aware for non-superadmin\n * principals: `applySuperAdminScope` only rewrites `orgId` from the\n * `om_selected_org` cookie for superadmins, so a regular multi-org admin who\n * switches the header organization keeps `auth.orgId` pinned to their own home\n * organization. The CRUD factory and other org-scoped routes derive the active\n * organization via `resolveOrganizationScopeForRequest` (cookie-driven and\n * RBAC-validated for ALL users, falling back to the home org when the selection\n * is absent or inaccessible). Attachments must do the same so uploaded files\n * land under \u2014 and are read back from \u2014 the currently selected organization\n * rather than the uploader's home organization (#3765).\n */\nexport async function resolveAttachmentOrganizationId(\n container: AwilixContainer,\n auth: AuthContext,\n request: Request,\n): Promise<string | null> {\n if (!auth) return null\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request })\n return scope?.selectedId ?? auth.orgId ?? null\n}\n"],
|
|
5
|
+
"mappings": "AAEA,SAAS,0CAA0C;AAgBnD,eAAsB,gCACpB,WACA,MACA,SACwB;AACxB,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,QAAQ,MAAM,mCAAmC,EAAE,WAAW,MAAM,QAAQ,CAAC;AACnF,SAAO,OAAO,cAAc,KAAK,SAAS;AAC5C;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
19
19
|
import { buildPasswordSchema } from "@open-mercato/shared/lib/auth/passwordPolicy";
|
|
20
20
|
import { escapeLikePattern } from "@open-mercato/shared/lib/db/escapeLikePattern";
|
|
21
|
+
import { parseBooleanFlag } from "@open-mercato/shared/lib/boolean";
|
|
21
22
|
import { resolveSearchConfig } from "@open-mercato/shared/lib/search/config";
|
|
22
23
|
import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
|
|
23
24
|
import { sql } from "kysely";
|
|
@@ -35,6 +36,7 @@ const querySchema = z.object({
|
|
|
35
36
|
search: z.string().optional(),
|
|
36
37
|
name: z.string().optional(),
|
|
37
38
|
organizationId: z.string().uuid().optional(),
|
|
39
|
+
scopeToActiveOrganization: z.boolean().optional(),
|
|
38
40
|
roleIds: z.array(z.string().uuid()).optional()
|
|
39
41
|
}).passthrough();
|
|
40
42
|
const rawBodySchema = z.object({}).passthrough();
|
|
@@ -158,6 +160,7 @@ async function GET(req) {
|
|
|
158
160
|
search: url.searchParams.get("search") || void 0,
|
|
159
161
|
name: url.searchParams.get("name") || void 0,
|
|
160
162
|
organizationId: url.searchParams.get("organizationId") || void 0,
|
|
163
|
+
scopeToActiveOrganization: parseBooleanFlag(url.searchParams.get("scopeToActiveOrganization") || void 0),
|
|
161
164
|
roleIds: rawRoleIds.length ? rawRoleIds : void 0
|
|
162
165
|
});
|
|
163
166
|
if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 });
|
|
@@ -173,7 +176,7 @@ async function GET(req) {
|
|
|
173
176
|
} catch (err) {
|
|
174
177
|
logger.error("Failed to resolve rbac", { err });
|
|
175
178
|
}
|
|
176
|
-
const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data;
|
|
179
|
+
const { id, page, pageSize, search, name, organizationId, scopeToActiveOrganization, roleIds } = parsed.data;
|
|
177
180
|
const filters = [{ deletedAt: null }];
|
|
178
181
|
const actorTenantId = auth.tenantId ? String(auth.tenantId) : null;
|
|
179
182
|
let effectiveTenantId = null;
|
|
@@ -220,6 +223,7 @@ async function GET(req) {
|
|
|
220
223
|
}
|
|
221
224
|
const scopeOrganizationId = usesSelectedTenantScope ? effectiveSelectedOrganizationId : auth.orgId ?? null;
|
|
222
225
|
if (organizationId) filters.push({ organizationId });
|
|
226
|
+
if (scopeToActiveOrganization) filters.push({ organizationId: auth.orgId ?? null });
|
|
223
227
|
const trimmedName = typeof name === "string" ? name.trim() : "";
|
|
224
228
|
if (trimmedName) {
|
|
225
229
|
const searchPattern = `%${escapeLikePattern(trimmedName)}%`;
|
|
@@ -542,7 +546,7 @@ const openApi = {
|
|
|
542
546
|
methods: {
|
|
543
547
|
GET: {
|
|
544
548
|
summary: "List users",
|
|
545
|
-
description: "Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters.",
|
|
549
|
+
description: "Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters. Pass scopeToActiveOrganization=1 to restrict results to the caller's active organization (used by recipient/assignee pickers so suggestions stay within the org that owns the resulting record).",
|
|
546
550
|
query: querySchema,
|
|
547
551
|
responses: [
|
|
548
552
|
{ status: 200, description: "User collection", schema: userListResponseSchema }
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/auth/api/users/route.ts"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { User, Role, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'\nimport {\n assertActorCanAccessUserTarget,\n assertActorCanGrantRoleTokens,\n assertActorCanModifySuperAdminUserTarget,\n listSuperAdminUserIds,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'\nimport { sql } from 'kysely'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\nimport {\n getSelectedTenantFromRequest,\n resolveOrganizationScopeForRequest,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('auth').child({ component: 'users' })\n\nconst querySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n name: z.string().optional(),\n organizationId: z.string().uuid().optional(),\n roleIds: z.array(z.string().uuid()).optional(),\n}).passthrough()\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst passwordSchema = buildPasswordSchema()\n\nconst displayNameSchema = z.preprocess(\n normalizeDisplayNameInput,\n z.string().trim().min(1).max(120).nullable().optional(),\n)\n\nconst userCreateSchema = z.object({\n email: z.string().email(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n sendInviteEmail: z.boolean().optional(),\n organizationId: z.string().uuid(),\n roles: z.array(z.string()).optional(),\n}).refine(\n (data) => data.password || data.sendInviteEmail,\n { message: 'Either password or sendInviteEmail is required', path: ['password'] },\n)\n\nconst userUpdateSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email().optional(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n organizationId: z.string().uuid().optional(),\n roles: z.array(z.string()).optional(),\n})\n\nconst userListItemSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email(),\n name: z.string().nullable(),\n organizationId: z.string().uuid().nullable(),\n organizationName: z.string().nullable(),\n tenantId: z.string().uuid().nullable(),\n tenantName: z.string().nullable(),\n roles: z.array(z.string()),\n roleIds: z.array(z.string().uuid()).optional(),\n hasPassword: z.boolean().optional(),\n updatedAt: z.string().nullable().optional(),\n})\n\nconst userListResponseSchema = z.object({\n items: z.array(userListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n isSuperAdmin: z.boolean().optional(),\n})\n\nconst okResponseSchema = z.object({ ok: z.literal(true) })\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\ntype CrudInput = Record<string, unknown>\ntype UserListFilter = Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.users.list'] },\n POST: { requireAuth: true, requireFeatures: ['auth.users.create'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.users.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.users.delete'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: User,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n actions: {\n create: {\n commandId: 'auth.users.create',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: ({ result }) => ({\n id: String(result.user.id),\n ...(result.warning ? { _warning: result.warning } : {}),\n }),\n status: 201,\n },\n update: {\n commandId: 'auth.users.update',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n if (typeof parsed.id === 'string' && parsed.id.length) {\n await assertCanModifySuperAdminTarget(ctx.request, parsed.id)\n await assertCanAccessUserTarget(ctx.request, parsed.id)\n }\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'auth.users.delete',\n mapInput: async ({ parsed, raw, ctx }) => {\n const targetId = resolveDeleteTargetId(parsed, raw)\n if (ctx.request && targetId) {\n await assertCanModifySuperAdminTarget(ctx.request, targetId)\n await assertCanAccessUserTarget(ctx.request, targetId)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const url = new URL(req.url)\n const rawRoleIds = url.searchParams.getAll('roleId').filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n name: url.searchParams.get('name') || undefined,\n organizationId: url.searchParams.get('organizationId') || undefined,\n roleIds: rawRoleIds.length ? rawRoleIds : undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n let isSuperAdmin = auth.isSuperAdmin === true\n try {\n if (auth.sub) {\n const rbacService = container.resolve('rbacService') as any\n const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n isSuperAdmin = isSuperAdmin || !!acl?.isSuperAdmin\n }\n } catch (err) {\n logger.error('Failed to resolve rbac', { err })\n }\n const { id, page, pageSize, search, name, organizationId, roleIds } = parsed.data\n const filters: any[] = [{ deletedAt: null }]\n const actorTenantId = auth.tenantId ? String(auth.tenantId) : null\n let effectiveTenantId: string | null = null\n let effectiveOrganizationIds: string[] | null = null\n let effectiveSelectedOrganizationId: string | null = null\n let usesSelectedTenantScope = false\n if (!isSuperAdmin) {\n if (!actorTenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n effectiveTenantId = actorTenantId\n const superAdminUserIds = await listSuperAdminUserIds(em, actorTenantId)\n if (superAdminUserIds.size) {\n filters.push({ id: { $nin: Array.from(superAdminUserIds) as any } })\n }\n } else {\n const selectedTenantId = getSelectedTenantFromRequest(req)\n if (typeof selectedTenantId === 'string' && selectedTenantId.trim().length > 0) {\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n tenantId: selectedTenantId.trim(),\n })\n if (!scope.tenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n effectiveTenantId = scope.tenantId\n effectiveSelectedOrganizationId = scope.selectedId\n usesSelectedTenantScope = true\n if (Array.isArray(scope.filterIds)) {\n if (scope.filterIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n effectiveOrganizationIds = scope.filterIds\n }\n }\n }\n if (effectiveTenantId) {\n filters.push({ tenantId: effectiveTenantId })\n }\n if (effectiveOrganizationIds) {\n filters.push({ organizationId: { $in: effectiveOrganizationIds as any } })\n }\n const scopeOrganizationId = usesSelectedTenantScope\n ? effectiveSelectedOrganizationId\n : auth.orgId ?? null\n if (organizationId) filters.push({ organizationId })\n const trimmedName = typeof name === 'string' ? name.trim() : ''\n if (trimmedName) {\n const searchPattern = `%${escapeLikePattern(trimmedName)}%`\n const displayNameFilters: UserListFilter[] = [{ name: { $ilike: searchPattern } }]\n const nameTokenScope: string | null | undefined = isSuperAdmin ? (effectiveTenantId ?? undefined) : auth.tenantId ?? null\n const matchedDisplayNameIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedName, nameTokenScope, 'name')\n if (matchedDisplayNameIds && matchedDisplayNameIds.length) {\n displayNameFilters.push({ id: { $in: matchedDisplayNameIds } })\n }\n filters.push(displayNameFilters.length > 1 ? { $or: displayNameFilters } : displayNameFilters[0])\n }\n let idFilter: Set<string> | null = id ? new Set([id]) : null\n if (Array.isArray(roleIds) && roleIds.length > 0) {\n const uniqueRoleIds = Array.from(new Set(roleIds))\n const linksForRoles = await em.find(UserRole, { role: { $in: uniqueRoleIds as any } } as any)\n const roleUserIds = new Set<string>()\n for (const link of linksForRoles) {\n const uid = String((link as any).user?.id || (link as any).user || '')\n if (uid) roleUserIds.add(uid)\n }\n if (roleUserIds.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n if (idFilter) {\n for (const uid of Array.from(idFilter)) {\n if (!roleUserIds.has(uid)) idFilter.delete(uid)\n }\n } else {\n idFilter = roleUserIds\n }\n if (!idFilter || idFilter.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n }\n const trimmedSearch = typeof search === 'string' ? search.trim() : ''\n if (trimmedSearch) {\n // Email is encrypted at rest, so plaintext search must go through search_tokens.\n const tenantScope: string | null | undefined = isSuperAdmin ? (effectiveTenantId ?? undefined) : auth.tenantId ?? null\n const searchFilters: any[] = []\n\n const matchedIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedSearch, tenantScope)\n if (matchedIds && matchedIds.length) {\n searchFilters.push({ id: { $in: matchedIds as any } })\n }\n\n const searchPattern = `%${escapeLikePattern(trimmedSearch)}%`\n const organizationSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n organizationSearchFilters.push({ tenant: tenantScope })\n }\n const matchingOrganizations = await em.find(\n Organization,\n organizationSearchFilters.length > 1 ? { $and: organizationSearchFilters } : organizationSearchFilters[0],\n )\n const matchingOrganizationIds = matchingOrganizations\n .map((org) => (org?.id ? String(org.id) : null))\n .filter((orgId): orgId is string => typeof orgId === 'string' && orgId.length > 0)\n if (matchingOrganizationIds.length) {\n searchFilters.push({ organizationId: { $in: matchingOrganizationIds as any } })\n }\n\n const roleSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n roleSearchFilters.push({ $or: [{ tenantId: tenantScope }, { tenantId: null }] })\n }\n const matchingRoles = await em.find(\n Role,\n roleSearchFilters.length > 1 ? { $and: roleSearchFilters } : roleSearchFilters[0],\n )\n const matchingRoleIds = matchingRoles\n .map((role) => (role?.id ? String(role.id) : null))\n .filter((roleId): roleId is string => typeof roleId === 'string' && roleId.length > 0)\n if (matchingRoleIds.length) {\n const roleSearchLinks = await em.find(\n UserRole,\n { role: { $in: matchingRoleIds as any } } as any,\n )\n const matchingRoleUserIds = Array.from(new Set(\n roleSearchLinks\n .map((link) => {\n const userRef = (link as any).user\n const userId = userRef?.id ?? userRef\n return userId ? String(userId) : null\n })\n .filter((userId): userId is string => typeof userId === 'string' && userId.length > 0),\n ))\n if (matchingRoleUserIds.length) {\n searchFilters.push({ id: { $in: matchingRoleUserIds as any } })\n }\n }\n\n if (!searchFilters.length) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n\n filters.push(searchFilters.length > 1 ? { $or: searchFilters } : searchFilters[0])\n }\n if (idFilter && idFilter.size) {\n filters.push({ id: { $in: Array.from(idFilter) as any } })\n } else if (id) {\n filters.push({ id })\n }\n const where = filters.length > 1 ? { $and: filters } : filters[0]\n const [rows, count] = await em.findAndCount(User, where, { limit: pageSize, offset: (page - 1) * pageSize })\n const userIds = rows.map((u: any) => u.id)\n const links = userIds.length\n ? await findWithDecryption(\n em,\n UserRole,\n { user: { $in: userIds as any } } as any,\n { populate: ['role'] },\n {\n tenantId: effectiveTenantId ?? auth.tenantId ?? null,\n organizationId: scopeOrganizationId,\n },\n )\n : []\n const roleMap: Record<string, string[]> = {}\n const roleIdMap: Record<string, string[]> = {}\n for (const l of links) {\n const uid = String((l as any).user?.id || (l as any).user)\n const rname = String((l as any).role?.name || '')\n const rid = String((l as any).role?.id ?? '')\n if (!roleMap[uid]) roleMap[uid] = []\n if (!roleIdMap[uid]) roleIdMap[uid] = []\n if (rname) roleMap[uid].push(rname)\n if (rid) roleIdMap[uid].push(rid)\n }\n const orgIds = rows\n .map((u: any) => (u.organizationId ? String(u.organizationId) : null))\n .filter((id): id is string => !!id)\n const uniqueOrgIds = Array.from(new Set(orgIds))\n let orgMap: Record<string, string> = {}\n if (uniqueOrgIds.length) {\n const organizations = await em.find(\n Organization,\n { id: { $in: uniqueOrgIds as any }, deletedAt: null },\n )\n orgMap = organizations.reduce<Record<string, string>>((acc, org) => {\n const orgId = org?.id ? String(org.id) : null\n if (!orgId) return acc\n const rawName = (org as any)?.name\n const orgName = typeof rawName === 'string' && rawName.length > 0 ? rawName : orgId\n acc[orgId] = orgName\n return acc\n }, {})\n }\n const tenantIds = rows\n .map((u: any) => (u.tenantId ? String(u.tenantId) : null))\n .filter((id): id is string => !!id)\n const uniqueTenantIds = Array.from(new Set(tenantIds))\n let tenantMap: Record<string, string> = {}\n if (uniqueTenantIds.length) {\n const tenants = await em.find(\n Tenant,\n { id: { $in: uniqueTenantIds as any }, deletedAt: null },\n )\n tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tenantId = tenant?.id ? String(tenant.id) : null\n if (!tenantId) return acc\n const rawName = (tenant as any)?.name\n const tenantName = typeof rawName === 'string' && rawName.length > 0 ? rawName : tenantId\n acc[tenantId] = tenantName\n return acc\n }, {})\n }\n const tenantByUser: Record<string, string | null> = {}\n const organizationByUser: Record<string, string | null> = {}\n for (const u of rows) {\n const uid = String(u.id)\n tenantByUser[uid] = u.tenantId ? String(u.tenantId) : null\n organizationByUser[uid] = u.organizationId ? String(u.organizationId) : null\n }\n const cfByUser = userIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.auth.user,\n recordIds: userIds.map(String),\n tenantIdByRecord: tenantByUser,\n organizationIdByRecord: organizationByUser,\n tenantFallbacks: effectiveTenantId ? [effectiveTenantId] : auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const items = rows.map((u: any) => {\n const uid = String(u.id)\n const orgId = u.organizationId ? String(u.organizationId) : null\n return {\n id: uid,\n email: String(u.email),\n name: u.name ? String(u.name) : null,\n organizationId: orgId,\n organizationName: orgId ? orgMap[orgId] ?? orgId : null,\n tenantId: u.tenantId ? String(u.tenantId) : null,\n tenantName: u.tenantId ? tenantMap[String(u.tenantId)] ?? String(u.tenantId) : null,\n roles: roleMap[uid] || [],\n roleIds: roleIdMap[uid] || [],\n ...(id ? { hasPassword: !!u.passwordHash } : {}),\n updatedAt: u.updatedAt instanceof Date ? u.updatedAt.toISOString() : null,\n ...(cfByUser[uid] || {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'auth.user',\n organizationId: effectiveSelectedOrganizationId,\n tenantId: effectiveTenantId ?? auth.tenantId ?? null,\n query: parsed.data,\n accessType: id ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total: count, totalPages, isSuperAdmin })\n}\n\nexport const POST = async (req: Request) => {\n return crud.POST(req)\n}\n\nexport const PUT = async (req: Request) => {\n return crud.PUT(req)\n}\n\nexport const DELETE = async (req: Request) => {\n const targetId = new URL(req.url).searchParams.get('id')\n if (targetId) {\n try {\n await assertCanModifySuperAdminTarget(req, targetId)\n await assertCanAccessUserTarget(req, targetId)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n }\n return crud.DELETE(req)\n}\n\nasync function findUserIdsBySearchTokens(\n em: EntityManager,\n entityType: string,\n search: string,\n tenantScope: string | null | undefined,\n field?: string,\n): Promise<string[] | null> {\n const trimmed = search.trim()\n if (!trimmed) return null\n const searchConfig = resolveSearchConfig()\n if (!searchConfig.enabled) return []\n const { hashes } = tokenizeText(trimmed, searchConfig)\n if (!hashes.length) return []\n\n const db = (em as any).getKysely() as any\n let query = db\n .selectFrom('search_tokens')\n .select('entity_id')\n .where('entity_type', '=', entityType)\n .where('token_hash', 'in', hashes)\n .groupBy('entity_id')\n .having(sql<boolean>`count(distinct token_hash) >= ${hashes.length}`)\n if (field) {\n query = query.where('field', '=', field)\n }\n if (tenantScope !== undefined) {\n query = query.where(sql<boolean>`tenant_id is not distinct from ${tenantScope}`)\n }\n const rows = (await query.execute()) as Array<{ entity_id?: unknown }>\n return rows\n .map((row) => (typeof row.entity_id === 'string' ? row.entity_id : null))\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\nasync function assertCanModifySuperAdminTarget(req: Request, targetUserId: string) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n await assertActorCanModifySuperAdminUserTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId,\n })\n}\n\nasync function assertCanAccessUserTarget(req: Request, targetUserId: string) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n await assertActorCanAccessUserTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId,\n })\n}\n\nfunction resolveDeleteTargetId(parsed: unknown, raw: unknown): string | null {\n const fromParsed = readId((parsed as Record<string, unknown> | null | undefined))\n if (fromParsed) return fromParsed\n const rawRecord = raw as { body?: Record<string, unknown>; query?: Record<string, unknown> } | null | undefined\n return readId(rawRecord?.query) ?? readId(rawRecord?.body)\n}\n\nfunction readId(record: Record<string, unknown> | null | undefined): string | null {\n const value = record?.id\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nasync function assertCanAssignRoles(req: Request, roles: unknown, payload: Record<string, unknown>) {\n if (!Array.isArray(roles)) return\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const tenantId = await resolveTargetTenantIdForRoleGrant(em, payload, auth.tenantId ?? null)\n await assertActorCanGrantRoleTokens({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId,\n organizationId: auth.orgId ?? null,\n roleTokens: roles,\n })\n}\n\nasync function resolveTargetTenantIdForRoleGrant(\n em: EntityManager,\n payload: Record<string, unknown>,\n fallbackTenantId: string | null,\n): Promise<string | null> {\n const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null\n if (organizationId) {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId },\n )\n return organization?.tenant?.id ? String(organization.tenant.id) : fallbackTenantId\n }\n\n const userId = typeof payload.id === 'string' ? payload.id : null\n if (userId) {\n const user = await findOneWithDecryption(\n em,\n User,\n { id: userId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n return user?.tenantId ? String(user.tenantId) : fallbackTenantId\n }\n\n return fallbackTenantId\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'User management',\n methods: {\n GET: {\n summary: 'List users',\n description:\n 'Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters.',\n query: querySchema,\n responses: [\n { status: 200, description: 'User collection', schema: userListResponseSchema },\n ],\n },\n POST: {\n summary: 'Create user',\n description: 'Creates a new confirmed user within the specified organization, optional display name, and optional roles.',\n requestBody: {\n contentType: 'application/json',\n schema: userCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'User created',\n schema: z.object({ id: z.string().uuid() }),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or duplicate email', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update user',\n description: 'Updates profile fields including display name, organization assignment, credentials, or role memberships.',\n requestBody: {\n contentType: 'application/json',\n schema: userUpdateSchema,\n },\n responses: [\n { status: 200, description: 'User updated', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete user',\n description: 'Deletes a user by identifier. Undo support is provided via the command bus.',\n query: z.object({ id: z.string().uuid().describe('User identifier') }),\n responses: [\n { status: 200, description: 'User deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'User cannot be deleted', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { logCrudAccess, makeCrudRoute } from '@open-mercato/shared/lib/crud/factory'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { User, Role, UserRole } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { E } from '#generated/entities.ids.generated'\nimport { loadCustomFieldValues } from '@open-mercato/shared/lib/crud/custom-fields'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport { userCrudEvents, userCrudIndexer } from '@open-mercato/core/modules/auth/commands/users'\nimport {\n assertActorCanAccessUserTarget,\n assertActorCanGrantRoleTokens,\n assertActorCanModifySuperAdminUserTarget,\n listSuperAdminUserIds,\n} from '@open-mercato/core/modules/auth/lib/grantChecks'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { buildPasswordSchema } from '@open-mercato/shared/lib/auth/passwordPolicy'\nimport { escapeLikePattern } from '@open-mercato/shared/lib/db/escapeLikePattern'\nimport { parseBooleanFlag } from '@open-mercato/shared/lib/boolean'\nimport { resolveSearchConfig } from '@open-mercato/shared/lib/search/config'\nimport { tokenizeText } from '@open-mercato/shared/lib/search/tokenize'\nimport { sql } from 'kysely'\nimport { normalizeDisplayNameInput } from '@open-mercato/core/modules/auth/lib/displayName'\nimport {\n getSelectedTenantFromRequest,\n resolveOrganizationScopeForRequest,\n} from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport { createLogger } from '@open-mercato/shared/lib/logger'\n\nconst logger = createLogger('auth').child({ component: 'users' })\n\nconst querySchema = z.object({\n id: z.string().uuid().optional(),\n page: z.coerce.number().min(1).default(1),\n pageSize: z.coerce.number().min(1).max(100).default(50),\n search: z.string().optional(),\n name: z.string().optional(),\n organizationId: z.string().uuid().optional(),\n scopeToActiveOrganization: z.boolean().optional(),\n roleIds: z.array(z.string().uuid()).optional(),\n}).passthrough()\n\nconst rawBodySchema = z.object({}).passthrough()\n\nconst passwordSchema = buildPasswordSchema()\n\nconst displayNameSchema = z.preprocess(\n normalizeDisplayNameInput,\n z.string().trim().min(1).max(120).nullable().optional(),\n)\n\nconst userCreateSchema = z.object({\n email: z.string().email(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n sendInviteEmail: z.boolean().optional(),\n organizationId: z.string().uuid(),\n roles: z.array(z.string()).optional(),\n}).refine(\n (data) => data.password || data.sendInviteEmail,\n { message: 'Either password or sendInviteEmail is required', path: ['password'] },\n)\n\nconst userUpdateSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email().optional(),\n name: displayNameSchema,\n password: passwordSchema.optional(),\n organizationId: z.string().uuid().optional(),\n roles: z.array(z.string()).optional(),\n})\n\nconst userListItemSchema = z.object({\n id: z.string().uuid(),\n email: z.string().email(),\n name: z.string().nullable(),\n organizationId: z.string().uuid().nullable(),\n organizationName: z.string().nullable(),\n tenantId: z.string().uuid().nullable(),\n tenantName: z.string().nullable(),\n roles: z.array(z.string()),\n roleIds: z.array(z.string().uuid()).optional(),\n hasPassword: z.boolean().optional(),\n updatedAt: z.string().nullable().optional(),\n})\n\nconst userListResponseSchema = z.object({\n items: z.array(userListItemSchema),\n total: z.number().int().nonnegative(),\n totalPages: z.number().int().positive(),\n isSuperAdmin: z.boolean().optional(),\n})\n\nconst okResponseSchema = z.object({ ok: z.literal(true) })\n\nconst errorResponseSchema = z.object({ error: z.string() })\n\ntype CrudInput = Record<string, unknown>\ntype UserListFilter = Record<string, unknown>\n\nconst routeMetadata = {\n GET: { requireAuth: true, requireFeatures: ['auth.users.list'] },\n POST: { requireAuth: true, requireFeatures: ['auth.users.create'] },\n PUT: { requireAuth: true, requireFeatures: ['auth.users.edit'] },\n DELETE: { requireAuth: true, requireFeatures: ['auth.users.delete'] },\n}\n\nexport const metadata = routeMetadata\n\nconst crud = makeCrudRoute<CrudInput, CrudInput, Record<string, unknown>>({\n metadata: routeMetadata,\n orm: {\n entity: User,\n idField: 'id',\n orgField: null,\n tenantField: null,\n softDeleteField: 'deletedAt',\n },\n events: userCrudEvents,\n indexer: userCrudIndexer,\n actions: {\n create: {\n commandId: 'auth.users.create',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: ({ result }) => ({\n id: String(result.user.id),\n ...(result.warning ? { _warning: result.warning } : {}),\n }),\n status: 201,\n },\n update: {\n commandId: 'auth.users.update',\n schema: rawBodySchema,\n mapInput: async ({ parsed, ctx }) => {\n if (ctx.request) {\n if (typeof parsed.id === 'string' && parsed.id.length) {\n await assertCanModifySuperAdminTarget(ctx.request, parsed.id)\n await assertCanAccessUserTarget(ctx.request, parsed.id)\n }\n await assertCanAssignRoles(ctx.request, parsed.roles, parsed)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n delete: {\n commandId: 'auth.users.delete',\n mapInput: async ({ parsed, raw, ctx }) => {\n const targetId = resolveDeleteTargetId(parsed, raw)\n if (ctx.request && targetId) {\n await assertCanModifySuperAdminTarget(ctx.request, targetId)\n await assertCanAccessUserTarget(ctx.request, targetId)\n }\n return parsed\n },\n response: () => ({ ok: true }),\n },\n },\n})\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const url = new URL(req.url)\n const rawRoleIds = url.searchParams.getAll('roleId').filter((id): id is string => typeof id === 'string' && id.trim().length > 0)\n const parsed = querySchema.safeParse({\n id: url.searchParams.get('id') || undefined,\n page: url.searchParams.get('page') || undefined,\n pageSize: url.searchParams.get('pageSize') || undefined,\n search: url.searchParams.get('search') || undefined,\n name: url.searchParams.get('name') || undefined,\n organizationId: url.searchParams.get('organizationId') || undefined,\n scopeToActiveOrganization: parseBooleanFlag(url.searchParams.get('scopeToActiveOrganization') || undefined),\n roleIds: rawRoleIds.length ? rawRoleIds : undefined,\n })\n if (!parsed.success) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n const container = await createRequestContainer()\n const em = (container.resolve('em') as EntityManager)\n let isSuperAdmin = auth.isSuperAdmin === true\n try {\n if (auth.sub) {\n const rbacService = container.resolve('rbacService') as any\n const acl = await rbacService.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n isSuperAdmin = isSuperAdmin || !!acl?.isSuperAdmin\n }\n } catch (err) {\n logger.error('Failed to resolve rbac', { err })\n }\n const { id, page, pageSize, search, name, organizationId, scopeToActiveOrganization, roleIds } = parsed.data\n const filters: any[] = [{ deletedAt: null }]\n const actorTenantId = auth.tenantId ? String(auth.tenantId) : null\n let effectiveTenantId: string | null = null\n let effectiveOrganizationIds: string[] | null = null\n let effectiveSelectedOrganizationId: string | null = null\n let usesSelectedTenantScope = false\n if (!isSuperAdmin) {\n if (!actorTenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n effectiveTenantId = actorTenantId\n const superAdminUserIds = await listSuperAdminUserIds(em, actorTenantId)\n if (superAdminUserIds.size) {\n filters.push({ id: { $nin: Array.from(superAdminUserIds) as any } })\n }\n } else {\n const selectedTenantId = getSelectedTenantFromRequest(req)\n if (typeof selectedTenantId === 'string' && selectedTenantId.trim().length > 0) {\n const scope = await resolveOrganizationScopeForRequest({\n container,\n auth,\n request: req,\n tenantId: selectedTenantId.trim(),\n })\n if (!scope.tenantId) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n effectiveTenantId = scope.tenantId\n effectiveSelectedOrganizationId = scope.selectedId\n usesSelectedTenantScope = true\n if (Array.isArray(scope.filterIds)) {\n if (scope.filterIds.length === 0) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n effectiveOrganizationIds = scope.filterIds\n }\n }\n }\n if (effectiveTenantId) {\n filters.push({ tenantId: effectiveTenantId })\n }\n if (effectiveOrganizationIds) {\n filters.push({ organizationId: { $in: effectiveOrganizationIds as any } })\n }\n const scopeOrganizationId = usesSelectedTenantScope\n ? effectiveSelectedOrganizationId\n : auth.orgId ?? null\n if (organizationId) filters.push({ organizationId })\n // Recipient/assignee pickers scope to the caller's active organization so they never\n // suggest users outside it. A message composed here is stamped with the caller's\n // active org (auth.orgId), and the message detail endpoint enforces\n // hasOrganizationAccess(scope.organizationId, message.organizationId); scoping the\n // suggestions to the same org keeps a picked recipient able to open what they were sent.\n if (scopeToActiveOrganization) filters.push({ organizationId: auth.orgId ?? null })\n const trimmedName = typeof name === 'string' ? name.trim() : ''\n if (trimmedName) {\n const searchPattern = `%${escapeLikePattern(trimmedName)}%`\n const displayNameFilters: UserListFilter[] = [{ name: { $ilike: searchPattern } }]\n const nameTokenScope: string | null | undefined = isSuperAdmin ? (effectiveTenantId ?? undefined) : auth.tenantId ?? null\n const matchedDisplayNameIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedName, nameTokenScope, 'name')\n if (matchedDisplayNameIds && matchedDisplayNameIds.length) {\n displayNameFilters.push({ id: { $in: matchedDisplayNameIds } })\n }\n filters.push(displayNameFilters.length > 1 ? { $or: displayNameFilters } : displayNameFilters[0])\n }\n let idFilter: Set<string> | null = id ? new Set([id]) : null\n if (Array.isArray(roleIds) && roleIds.length > 0) {\n const uniqueRoleIds = Array.from(new Set(roleIds))\n const linksForRoles = await em.find(UserRole, { role: { $in: uniqueRoleIds as any } } as any)\n const roleUserIds = new Set<string>()\n for (const link of linksForRoles) {\n const uid = String((link as any).user?.id || (link as any).user || '')\n if (uid) roleUserIds.add(uid)\n }\n if (roleUserIds.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n if (idFilter) {\n for (const uid of Array.from(idFilter)) {\n if (!roleUserIds.has(uid)) idFilter.delete(uid)\n }\n } else {\n idFilter = roleUserIds\n }\n if (!idFilter || idFilter.size === 0) return NextResponse.json({ items: [], total: 0, totalPages: 1 })\n }\n const trimmedSearch = typeof search === 'string' ? search.trim() : ''\n if (trimmedSearch) {\n // Email is encrypted at rest, so plaintext search must go through search_tokens.\n const tenantScope: string | null | undefined = isSuperAdmin ? (effectiveTenantId ?? undefined) : auth.tenantId ?? null\n const searchFilters: any[] = []\n\n const matchedIds = await findUserIdsBySearchTokens(em, E.auth.user, trimmedSearch, tenantScope)\n if (matchedIds && matchedIds.length) {\n searchFilters.push({ id: { $in: matchedIds as any } })\n }\n\n const searchPattern = `%${escapeLikePattern(trimmedSearch)}%`\n const organizationSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n organizationSearchFilters.push({ tenant: tenantScope })\n }\n const matchingOrganizations = await em.find(\n Organization,\n organizationSearchFilters.length > 1 ? { $and: organizationSearchFilters } : organizationSearchFilters[0],\n )\n const matchingOrganizationIds = matchingOrganizations\n .map((org) => (org?.id ? String(org.id) : null))\n .filter((orgId): orgId is string => typeof orgId === 'string' && orgId.length > 0)\n if (matchingOrganizationIds.length) {\n searchFilters.push({ organizationId: { $in: matchingOrganizationIds as any } })\n }\n\n const roleSearchFilters: any[] = [\n { deletedAt: null },\n { name: { $ilike: searchPattern } },\n ]\n if (tenantScope) {\n roleSearchFilters.push({ $or: [{ tenantId: tenantScope }, { tenantId: null }] })\n }\n const matchingRoles = await em.find(\n Role,\n roleSearchFilters.length > 1 ? { $and: roleSearchFilters } : roleSearchFilters[0],\n )\n const matchingRoleIds = matchingRoles\n .map((role) => (role?.id ? String(role.id) : null))\n .filter((roleId): roleId is string => typeof roleId === 'string' && roleId.length > 0)\n if (matchingRoleIds.length) {\n const roleSearchLinks = await em.find(\n UserRole,\n { role: { $in: matchingRoleIds as any } } as any,\n )\n const matchingRoleUserIds = Array.from(new Set(\n roleSearchLinks\n .map((link) => {\n const userRef = (link as any).user\n const userId = userRef?.id ?? userRef\n return userId ? String(userId) : null\n })\n .filter((userId): userId is string => typeof userId === 'string' && userId.length > 0),\n ))\n if (matchingRoleUserIds.length) {\n searchFilters.push({ id: { $in: matchingRoleUserIds as any } })\n }\n }\n\n if (!searchFilters.length) {\n return NextResponse.json({ items: [], total: 0, totalPages: 1, isSuperAdmin })\n }\n\n filters.push(searchFilters.length > 1 ? { $or: searchFilters } : searchFilters[0])\n }\n if (idFilter && idFilter.size) {\n filters.push({ id: { $in: Array.from(idFilter) as any } })\n } else if (id) {\n filters.push({ id })\n }\n const where = filters.length > 1 ? { $and: filters } : filters[0]\n const [rows, count] = await em.findAndCount(User, where, { limit: pageSize, offset: (page - 1) * pageSize })\n const userIds = rows.map((u: any) => u.id)\n const links = userIds.length\n ? await findWithDecryption(\n em,\n UserRole,\n { user: { $in: userIds as any } } as any,\n { populate: ['role'] },\n {\n tenantId: effectiveTenantId ?? auth.tenantId ?? null,\n organizationId: scopeOrganizationId,\n },\n )\n : []\n const roleMap: Record<string, string[]> = {}\n const roleIdMap: Record<string, string[]> = {}\n for (const l of links) {\n const uid = String((l as any).user?.id || (l as any).user)\n const rname = String((l as any).role?.name || '')\n const rid = String((l as any).role?.id ?? '')\n if (!roleMap[uid]) roleMap[uid] = []\n if (!roleIdMap[uid]) roleIdMap[uid] = []\n if (rname) roleMap[uid].push(rname)\n if (rid) roleIdMap[uid].push(rid)\n }\n const orgIds = rows\n .map((u: any) => (u.organizationId ? String(u.organizationId) : null))\n .filter((id): id is string => !!id)\n const uniqueOrgIds = Array.from(new Set(orgIds))\n let orgMap: Record<string, string> = {}\n if (uniqueOrgIds.length) {\n const organizations = await em.find(\n Organization,\n { id: { $in: uniqueOrgIds as any }, deletedAt: null },\n )\n orgMap = organizations.reduce<Record<string, string>>((acc, org) => {\n const orgId = org?.id ? String(org.id) : null\n if (!orgId) return acc\n const rawName = (org as any)?.name\n const orgName = typeof rawName === 'string' && rawName.length > 0 ? rawName : orgId\n acc[orgId] = orgName\n return acc\n }, {})\n }\n const tenantIds = rows\n .map((u: any) => (u.tenantId ? String(u.tenantId) : null))\n .filter((id): id is string => !!id)\n const uniqueTenantIds = Array.from(new Set(tenantIds))\n let tenantMap: Record<string, string> = {}\n if (uniqueTenantIds.length) {\n const tenants = await em.find(\n Tenant,\n { id: { $in: uniqueTenantIds as any }, deletedAt: null },\n )\n tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tenantId = tenant?.id ? String(tenant.id) : null\n if (!tenantId) return acc\n const rawName = (tenant as any)?.name\n const tenantName = typeof rawName === 'string' && rawName.length > 0 ? rawName : tenantId\n acc[tenantId] = tenantName\n return acc\n }, {})\n }\n const tenantByUser: Record<string, string | null> = {}\n const organizationByUser: Record<string, string | null> = {}\n for (const u of rows) {\n const uid = String(u.id)\n tenantByUser[uid] = u.tenantId ? String(u.tenantId) : null\n organizationByUser[uid] = u.organizationId ? String(u.organizationId) : null\n }\n const cfByUser = userIds.length\n ? await loadCustomFieldValues({\n em,\n entityId: E.auth.user,\n recordIds: userIds.map(String),\n tenantIdByRecord: tenantByUser,\n organizationIdByRecord: organizationByUser,\n tenantFallbacks: effectiveTenantId ? [effectiveTenantId] : auth.tenantId ? [auth.tenantId] : [],\n })\n : {}\n\n const items = rows.map((u: any) => {\n const uid = String(u.id)\n const orgId = u.organizationId ? String(u.organizationId) : null\n return {\n id: uid,\n email: String(u.email),\n name: u.name ? String(u.name) : null,\n organizationId: orgId,\n organizationName: orgId ? orgMap[orgId] ?? orgId : null,\n tenantId: u.tenantId ? String(u.tenantId) : null,\n tenantName: u.tenantId ? tenantMap[String(u.tenantId)] ?? String(u.tenantId) : null,\n roles: roleMap[uid] || [],\n roleIds: roleIdMap[uid] || [],\n ...(id ? { hasPassword: !!u.passwordHash } : {}),\n updatedAt: u.updatedAt instanceof Date ? u.updatedAt.toISOString() : null,\n ...(cfByUser[uid] || {}),\n }\n })\n const totalPages = Math.max(1, Math.ceil(count / pageSize))\n await logCrudAccess({\n container,\n auth,\n request: req,\n items,\n idField: 'id',\n resourceKind: 'auth.user',\n organizationId: effectiveSelectedOrganizationId,\n tenantId: effectiveTenantId ?? auth.tenantId ?? null,\n query: parsed.data,\n accessType: id ? 'read:item' : undefined,\n })\n return NextResponse.json({ items, total: count, totalPages, isSuperAdmin })\n}\n\nexport const POST = async (req: Request) => {\n return crud.POST(req)\n}\n\nexport const PUT = async (req: Request) => {\n return crud.PUT(req)\n}\n\nexport const DELETE = async (req: Request) => {\n const targetId = new URL(req.url).searchParams.get('id')\n if (targetId) {\n try {\n await assertCanModifySuperAdminTarget(req, targetId)\n await assertCanAccessUserTarget(req, targetId)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n throw err\n }\n }\n return crud.DELETE(req)\n}\n\nasync function findUserIdsBySearchTokens(\n em: EntityManager,\n entityType: string,\n search: string,\n tenantScope: string | null | undefined,\n field?: string,\n): Promise<string[] | null> {\n const trimmed = search.trim()\n if (!trimmed) return null\n const searchConfig = resolveSearchConfig()\n if (!searchConfig.enabled) return []\n const { hashes } = tokenizeText(trimmed, searchConfig)\n if (!hashes.length) return []\n\n const db = (em as any).getKysely() as any\n let query = db\n .selectFrom('search_tokens')\n .select('entity_id')\n .where('entity_type', '=', entityType)\n .where('token_hash', 'in', hashes)\n .groupBy('entity_id')\n .having(sql<boolean>`count(distinct token_hash) >= ${hashes.length}`)\n if (field) {\n query = query.where('field', '=', field)\n }\n if (tenantScope !== undefined) {\n query = query.where(sql<boolean>`tenant_id is not distinct from ${tenantScope}`)\n }\n const rows = (await query.execute()) as Array<{ entity_id?: unknown }>\n return rows\n .map((row) => (typeof row.entity_id === 'string' ? row.entity_id : null))\n .filter((id): id is string => typeof id === 'string' && id.length > 0)\n}\n\nasync function assertCanModifySuperAdminTarget(req: Request, targetUserId: string) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n await assertActorCanModifySuperAdminUserTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId,\n })\n}\n\nasync function assertCanAccessUserTarget(req: Request, targetUserId: string) {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n await assertActorCanAccessUserTarget({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n targetUserId,\n })\n}\n\nfunction resolveDeleteTargetId(parsed: unknown, raw: unknown): string | null {\n const fromParsed = readId((parsed as Record<string, unknown> | null | undefined))\n if (fromParsed) return fromParsed\n const rawRecord = raw as { body?: Record<string, unknown>; query?: Record<string, unknown> } | null | undefined\n return readId(rawRecord?.query) ?? readId(rawRecord?.body)\n}\n\nfunction readId(record: Record<string, unknown> | null | undefined): string | null {\n const value = record?.id\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nasync function assertCanAssignRoles(req: Request, roles: unknown, payload: Record<string, unknown>) {\n if (!Array.isArray(roles)) return\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) throw new CrudHttpError(401, { error: 'Unauthorized' })\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const tenantId = await resolveTargetTenantIdForRoleGrant(em, payload, auth.tenantId ?? null)\n await assertActorCanGrantRoleTokens({\n em,\n rbacService: container.resolve('rbacService') as RbacService,\n actorUserId: auth.sub,\n tenantId,\n organizationId: auth.orgId ?? null,\n roleTokens: roles,\n })\n}\n\nasync function resolveTargetTenantIdForRoleGrant(\n em: EntityManager,\n payload: Record<string, unknown>,\n fallbackTenantId: string | null,\n): Promise<string | null> {\n const organizationId = typeof payload.organizationId === 'string' ? payload.organizationId : null\n if (organizationId) {\n const organization = await findOneWithDecryption(\n em,\n Organization,\n { id: organizationId },\n { populate: ['tenant'] },\n { tenantId: null, organizationId },\n )\n return organization?.tenant?.id ? String(organization.tenant.id) : fallbackTenantId\n }\n\n const userId = typeof payload.id === 'string' ? payload.id : null\n if (userId) {\n const user = await findOneWithDecryption(\n em,\n User,\n { id: userId, deletedAt: null },\n {},\n { tenantId: null, organizationId: null },\n )\n return user?.tenantId ? String(user.tenantId) : fallbackTenantId\n }\n\n return fallbackTenantId\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: 'Authentication & Accounts',\n summary: 'User management',\n methods: {\n GET: {\n summary: 'List users',\n description:\n 'Returns users for the effective selected tenant and organization scope. Search matches email, organization name, and role name. Super administrators may scope the response via the topbar context, organization filters, or role filters. Pass scopeToActiveOrganization=1 to restrict results to the caller\\'s active organization (used by recipient/assignee pickers so suggestions stay within the org that owns the resulting record).',\n query: querySchema,\n responses: [\n { status: 200, description: 'User collection', schema: userListResponseSchema },\n ],\n },\n POST: {\n summary: 'Create user',\n description: 'Creates a new confirmed user within the specified organization, optional display name, and optional roles.',\n requestBody: {\n contentType: 'application/json',\n schema: userCreateSchema,\n },\n responses: [\n {\n status: 201,\n description: 'User created',\n schema: z.object({ id: z.string().uuid() }),\n },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or duplicate email', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n ],\n },\n PUT: {\n summary: 'Update user',\n description: 'Updates profile fields including display name, organization assignment, credentials, or role memberships.',\n requestBody: {\n contentType: 'application/json',\n schema: userUpdateSchema,\n },\n responses: [\n { status: 200, description: 'User updated', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 403, description: 'Attempted to assign privileged roles', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n DELETE: {\n summary: 'Delete user',\n description: 'Deletes a user by identifier. Undo support is provided via the command bus.',\n query: z.object({ id: z.string().uuid().describe('User identifier') }),\n responses: [\n { status: 200, description: 'User deleted', schema: okResponseSchema },\n ],\n errors: [\n { status: 400, description: 'User cannot be deleted', schema: errorResponseSchema },\n { status: 401, description: 'Unauthorized', schema: errorResponseSchema },\n { status: 404, description: 'User not found', schema: errorResponseSchema },\n ],\n },\n },\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["id"]
|
|
7
7
|
}
|
package/dist/modules/auth/cli.js
CHANGED
|
@@ -7,6 +7,7 @@ import { Tenant, Organization } from "@open-mercato/core/modules/directory/data/
|
|
|
7
7
|
import { rebuildHierarchyForTenant } from "@open-mercato/core/modules/directory/lib/hierarchy";
|
|
8
8
|
import { ensureRoles, setupInitialTenant, ensureDefaultRoleAcls, ensureCustomRoleAcls, OrgSlugExistsError, DerivedUserPasswordRequiredError } from "./lib/setup-app.js";
|
|
9
9
|
import { normalizeTenantId } from "./lib/tenantAccess.js";
|
|
10
|
+
import { parseCommaSeparatedList } from "@open-mercato/shared/lib/string";
|
|
10
11
|
import { computeEmailHash, emailHashLookupValues } from "./lib/emailHash.js";
|
|
11
12
|
import { findWithDecryption, findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
12
13
|
import { isTenantDataEncryptionEnabled } from "@open-mercato/shared/lib/encryption/toggles";
|
|
@@ -65,7 +66,7 @@ const addUser = {
|
|
|
65
66
|
});
|
|
66
67
|
await em.persist(u).flush();
|
|
67
68
|
if (rolesCsv) {
|
|
68
|
-
const names = rolesCsv
|
|
69
|
+
const names = parseCommaSeparatedList(rolesCsv);
|
|
69
70
|
for (const name of names) {
|
|
70
71
|
const role = await resolveTenantScopedRole(em, name, normalizedTenantId);
|
|
71
72
|
const link = em.create(UserRole, { user: u, role });
|
|
@@ -430,7 +431,7 @@ const setupApp = {
|
|
|
430
431
|
}
|
|
431
432
|
const container = await createRequestContainer();
|
|
432
433
|
const em = container.resolve("em");
|
|
433
|
-
const roleNames = rolesCsv ? rolesCsv
|
|
434
|
+
const roleNames = rolesCsv ? parseCommaSeparatedList(rolesCsv) : void 0;
|
|
434
435
|
try {
|
|
435
436
|
const modules = getCliModules();
|
|
436
437
|
const result = await setupInitialTenant(em, {
|