@open-mercato/core 0.6.6-develop.6540.1.ae856df1ec → 0.6.6-develop.6542.1.fee3e68aa6

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.
@@ -6,7 +6,10 @@ import { DashboardRoleWidgets } from "@open-mercato/core/modules/dashboards/data
6
6
  import { Role } from "@open-mercato/core/modules/auth/data/entities";
7
7
  import { roleWidgetSettingsSchema } from "@open-mercato/core/modules/dashboards/data/validators";
8
8
  import { loadAllWidgets } from "@open-mercato/core/modules/dashboards/lib/widgets";
9
- import { resolveWidgetAssignmentReadScope } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
9
+ import {
10
+ resolveWidgetAssignmentReadScope,
11
+ resolveWidgetAssignmentTargetAccess
12
+ } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
10
13
  import { hasFeature } from "@open-mercato/shared/security/features";
11
14
  import {
12
15
  runCrudMutationGuardAfterSuccess,
@@ -61,7 +64,15 @@ async function GET(req) {
61
64
  queryOrganizationId: url.searchParams.get("organizationId")
62
65
  });
63
66
  const role = await em.findOne(Role, { id: roleId, deletedAt: null });
64
- if (!role || tenantId && role.tenantId !== tenantId) {
67
+ const access = resolveWidgetAssignmentTargetAccess({
68
+ isSuperAdmin: !!acl.isSuperAdmin,
69
+ scopeTenantId: tenantId,
70
+ target: role
71
+ });
72
+ if (access === "forbidden") {
73
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
74
+ }
75
+ if (access === "not-found") {
65
76
  return NextResponse.json({ error: "Role not found" }, { status: 404 });
66
77
  }
67
78
  const records = await em.find(DashboardRoleWidgets, { roleId, deletedAt: null });
@@ -103,7 +114,15 @@ async function PUT(req) {
103
114
  const tenantId = auth.tenantId ?? null;
104
115
  const organizationId = auth.orgId ?? null;
105
116
  const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null });
106
- if (!role || tenantId && role.tenantId !== tenantId) {
117
+ const access = resolveWidgetAssignmentTargetAccess({
118
+ isSuperAdmin: !!acl.isSuperAdmin,
119
+ scopeTenantId: tenantId,
120
+ target: role
121
+ });
122
+ if (access === "forbidden") {
123
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
124
+ }
125
+ if (access === "not-found") {
107
126
  return NextResponse.json({ error: "Role not found" }, { status: 404 });
108
127
  }
109
128
  const guardResult = await validateCrudMutationGuard(container, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/api/roles/widgets/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { roleWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardRoleWidgetsResponseSchema,\n dashboardRoleWidgetsUpdateResponseSchema,\n} from '../../openapi'\n\nconst FEATURE = 'dashboards.admin.assign-widgets'\nconst RESOURCE_KIND = 'dashboards.roleWidgets'\n\nfunction pickBestRecord(records: DashboardRoleWidgets[], tenantId: string | null, organizationId: string | null): DashboardRoleWidgets | null {\n let best: DashboardRoleWidgets | null = null\n let bestScore = -1\n for (const record of records) {\n if (record.deletedAt) continue\n if (record.tenantId && tenantId && record.tenantId !== tenantId) continue\n if (record.tenantId && !tenantId) continue\n if (record.organizationId && organizationId && record.organizationId !== organizationId) continue\n if (record.organizationId && !organizationId) continue\n const score = (record.tenantId ? 1 : 0) + (record.organizationId ? 2 : 0)\n if (score > bestScore) {\n best = record\n bestScore = score\n }\n }\n return best\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleId = url.searchParams.get('roleId')\n if (!roleId) return NextResponse.json({ error: 'roleId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const { tenantId, organizationId } = resolveWidgetAssignmentReadScope({\n auth: { tenantId: auth.tenantId ?? null, orgId: auth.orgId ?? null },\n isSuperAdmin: !!acl.isSuperAdmin,\n queryTenantId: url.searchParams.get('tenantId'),\n queryOrganizationId: url.searchParams.get('organizationId'),\n })\n\n const role = await em.findOne(Role, { id: roleId, deletedAt: null })\n if (!role || (tenantId && role.tenantId !== tenantId)) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const records = await em.find(DashboardRoleWidgets, { roleId, deletedAt: null })\n const best = pickBestRecord(records, tenantId, organizationId)\n\n const response = {\n widgetIds: best ? best.widgetIdsJson : [],\n hasCustom: !!best,\n scope: {\n tenantId,\n organizationId,\n },\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = roleWidgetSettingsSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const widgets = await loadAllWidgets()\n const validWidgetIds = new Set(widgets.map((w) => w.metadata.id))\n const widgetIds = parsed.data.widgetIds.filter((id) => validWidgetIds.has(id))\n\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })\n if (!role || (tenantId && role.tenantId !== tenantId)) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { roleId: parsed.data.roleId, widgetIds },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let record = await em.findOne(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!widgetIds.length) {\n if (record) {\n await em.remove(record).flush()\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true, widgetIds: [] })\n }\n\n if (!record) {\n record = em.create(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n widgetIdsJson: widgetIds,\n })\n em.persist(record)\n } else {\n record.widgetIdsJson = widgetIds\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, widgetIds })\n}\n\nconst roleWidgetsQuerySchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nconst roleWidgetsGetDoc: OpenApiMethodDoc = {\n summary: 'Fetch widget assignments for a role',\n description: 'Returns the widgets explicitly assigned to the given role together with the evaluation scope.',\n tags: [dashboardsTag],\n query: roleWidgetsQuerySchema,\n responses: [\n { status: 200, description: 'Current widget configuration for the role.', schema: dashboardRoleWidgetsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing role identifier', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nconst roleWidgetsPutDoc: OpenApiMethodDoc = {\n summary: 'Update widgets assigned to a role',\n description: 'Persists the widget list for a role within the provided tenant and organization scope.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: roleWidgetSettingsSchema,\n description: 'Role identifier and the widget ids that should remain assigned. Scope is derived from the authenticated session.',\n },\n responses: [\n { status: 200, description: 'Widgets updated successfully.', schema: dashboardRoleWidgetsUpdateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or unknown widgets', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage dashboard widgets assigned to a role',\n methods: {\n GET: roleWidgetsGetDoc,\n PUT: roleWidgetsPutDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { roleWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport {\n resolveWidgetAssignmentReadScope,\n resolveWidgetAssignmentTargetAccess,\n} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardRoleWidgetsResponseSchema,\n dashboardRoleWidgetsUpdateResponseSchema,\n} from '../../openapi'\n\nconst FEATURE = 'dashboards.admin.assign-widgets'\nconst RESOURCE_KIND = 'dashboards.roleWidgets'\n\nfunction pickBestRecord(records: DashboardRoleWidgets[], tenantId: string | null, organizationId: string | null): DashboardRoleWidgets | null {\n let best: DashboardRoleWidgets | null = null\n let bestScore = -1\n for (const record of records) {\n if (record.deletedAt) continue\n if (record.tenantId && tenantId && record.tenantId !== tenantId) continue\n if (record.tenantId && !tenantId) continue\n if (record.organizationId && organizationId && record.organizationId !== organizationId) continue\n if (record.organizationId && !organizationId) continue\n const score = (record.tenantId ? 1 : 0) + (record.organizationId ? 2 : 0)\n if (score > bestScore) {\n best = record\n bestScore = score\n }\n }\n return best\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleId = url.searchParams.get('roleId')\n if (!roleId) return NextResponse.json({ error: 'roleId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const { tenantId, organizationId } = resolveWidgetAssignmentReadScope({\n auth: { tenantId: auth.tenantId ?? null, orgId: auth.orgId ?? null },\n isSuperAdmin: !!acl.isSuperAdmin,\n queryTenantId: url.searchParams.get('tenantId'),\n queryOrganizationId: url.searchParams.get('organizationId'),\n })\n\n const role = await em.findOne(Role, { id: roleId, deletedAt: null })\n const access = resolveWidgetAssignmentTargetAccess({\n isSuperAdmin: !!acl.isSuperAdmin,\n scopeTenantId: tenantId,\n target: role,\n })\n if (access === 'forbidden') {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (access === 'not-found') {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const records = await em.find(DashboardRoleWidgets, { roleId, deletedAt: null })\n const best = pickBestRecord(records, tenantId, organizationId)\n\n const response = {\n widgetIds: best ? best.widgetIdsJson : [],\n hasCustom: !!best,\n scope: {\n tenantId,\n organizationId,\n },\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = roleWidgetSettingsSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const widgets = await loadAllWidgets()\n const validWidgetIds = new Set(widgets.map((w) => w.metadata.id))\n const widgetIds = parsed.data.widgetIds.filter((id) => validWidgetIds.has(id))\n\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })\n const access = resolveWidgetAssignmentTargetAccess({\n isSuperAdmin: !!acl.isSuperAdmin,\n scopeTenantId: tenantId,\n target: role,\n })\n if (access === 'forbidden') {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (access === 'not-found') {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { roleId: parsed.data.roleId, widgetIds },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let record = await em.findOne(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!widgetIds.length) {\n if (record) {\n await em.remove(record).flush()\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true, widgetIds: [] })\n }\n\n if (!record) {\n record = em.create(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n widgetIdsJson: widgetIds,\n })\n em.persist(record)\n } else {\n record.widgetIdsJson = widgetIds\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, widgetIds })\n}\n\nconst roleWidgetsQuerySchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nconst roleWidgetsGetDoc: OpenApiMethodDoc = {\n summary: 'Fetch widget assignments for a role',\n description: 'Returns the widgets explicitly assigned to the given role together with the evaluation scope.',\n tags: [dashboardsTag],\n query: roleWidgetsQuerySchema,\n responses: [\n { status: 200, description: 'Current widget configuration for the role.', schema: dashboardRoleWidgetsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing role identifier', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nconst roleWidgetsPutDoc: OpenApiMethodDoc = {\n summary: 'Update widgets assigned to a role',\n description: 'Persists the widget list for a role within the provided tenant and organization scope.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: roleWidgetSettingsSchema,\n description: 'Role identifier and the widget ids that should remain assigned. Scope is derived from the authenticated session.',\n },\n responses: [\n { status: 200, description: 'Widgets updated successfully.', schema: dashboardRoleWidgetsUpdateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or unknown widgets', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage dashboard widgets assigned to a role',\n methods: {\n GET: roleWidgetsGetDoc,\n PUT: roleWidgetsPutDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,7 +7,10 @@ import { User } from "@open-mercato/core/modules/auth/data/entities";
7
7
  import { userWidgetSettingsSchema } from "@open-mercato/core/modules/dashboards/data/validators";
8
8
  import { loadAllWidgets } from "@open-mercato/core/modules/dashboards/lib/widgets";
9
9
  import { resolveAllowedWidgetIds } from "@open-mercato/core/modules/dashboards/lib/access";
10
- import { resolveWidgetAssignmentReadScope } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
10
+ import {
11
+ resolveWidgetAssignmentReadScope,
12
+ resolveWidgetAssignmentTargetAccess
13
+ } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
11
14
  import { hasFeature } from "@open-mercato/shared/security/features";
12
15
  import {
13
16
  runCrudMutationGuardAfterSuccess,
@@ -45,7 +48,15 @@ async function GET(req) {
45
48
  queryOrganizationId: url.searchParams.get("organizationId")
46
49
  });
47
50
  const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null });
48
- if (!targetUserForRead || tenantId && (targetUserForRead.tenantId ?? null) !== tenantId) {
51
+ const readAccess = resolveWidgetAssignmentTargetAccess({
52
+ isSuperAdmin: !!acl.isSuperAdmin,
53
+ scopeTenantId: tenantId,
54
+ target: targetUserForRead
55
+ });
56
+ if (readAccess === "forbidden") {
57
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
58
+ }
59
+ if (readAccess === "not-found") {
49
60
  return NextResponse.json({ error: "User not found" }, { status: 404 });
50
61
  }
51
62
  const widgets = await loadAllWidgets();
@@ -103,7 +114,15 @@ async function PUT(req) {
103
114
  const tenantId = auth.tenantId ?? null;
104
115
  const organizationId = auth.orgId ?? null;
105
116
  const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null });
106
- if (!targetUser || tenantId && (targetUser.tenantId ?? null) !== tenantId) {
117
+ const writeAccess = resolveWidgetAssignmentTargetAccess({
118
+ isSuperAdmin: !!acl.isSuperAdmin,
119
+ scopeTenantId: tenantId,
120
+ target: targetUser
121
+ });
122
+ if (writeAccess === "forbidden") {
123
+ return NextResponse.json({ error: "Forbidden" }, { status: 403 });
124
+ }
125
+ if (writeAccess === "not-found") {
107
126
  return NextResponse.json({ error: "User not found" }, { status: 404 });
108
127
  }
109
128
  const guardResult = await validateCrudMutationGuard(container, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/api/users/widgets/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardUserWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { userWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { resolveAllowedWidgetIds } from '@open-mercato/core/modules/dashboards/lib/access'\nimport { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardUserWidgetsResponseSchema,\n dashboardUserWidgetsUpdateResponseSchema,\n} from '../../openapi'\n\nconst FEATURE = 'dashboards.admin.assign-widgets'\nconst RESOURCE_KIND = 'dashboards.userWidgets'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const userId = url.searchParams.get('userId')\n if (!userId) return NextResponse.json({ error: 'userId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const { tenantId, organizationId } = resolveWidgetAssignmentReadScope({\n auth: { tenantId: auth.tenantId ?? null, orgId: auth.orgId ?? null },\n isSuperAdmin: !!acl.isSuperAdmin,\n queryTenantId: url.searchParams.get('tenantId'),\n queryOrganizationId: url.searchParams.get('organizationId'),\n })\n\n const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null })\n if (!targetUserForRead || (tenantId && (targetUserForRead.tenantId ?? null) !== tenantId)) {\n return NextResponse.json({ error: 'User not found' }, { status: 404 })\n }\n\n const widgets = await loadAllWidgets()\n const targetAcl = await rbac.loadAcl(userId, { tenantId, organizationId })\n const allowed = await resolveAllowedWidgetIds(\n em,\n {\n userId,\n tenantId,\n organizationId,\n features: targetAcl.features ?? [],\n isSuperAdmin: !!targetAcl.isSuperAdmin,\n },\n widgets,\n )\n\n const record = await em.findOne(DashboardUserWidgets, {\n userId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n const response = {\n mode: record ? record.mode : 'inherit',\n widgetIds: record && record.mode === 'override' ? record.widgetIdsJson : [],\n hasCustom: !!record && record.mode === 'override',\n effectiveWidgetIds: allowed,\n scope: { tenantId, organizationId },\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = userWidgetSettingsSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const widgets = await loadAllWidgets()\n const validWidgetIds = new Set(widgets.map((w) => w.metadata.id))\n const widgetIds = parsed.data.widgetIds.filter((id) => validWidgetIds.has(id))\n\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null })\n if (!targetUser || (tenantId && (targetUser.tenantId ?? null) !== tenantId)) {\n return NextResponse.json({ error: 'User not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { userId: parsed.data.userId, mode: parsed.data.mode, widgetIds },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let record = await em.findOne(DashboardUserWidgets, {\n userId: parsed.data.userId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (parsed.data.mode === 'inherit') {\n if (record) {\n await em.remove(record).flush()\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true, mode: 'inherit', widgetIds: [] })\n }\n\n if (!record) {\n record = em.create(DashboardUserWidgets, {\n userId: parsed.data.userId,\n tenantId,\n organizationId,\n mode: 'override',\n widgetIdsJson: widgetIds,\n })\n em.persist(record)\n } else {\n record.mode = 'override'\n record.widgetIdsJson = widgetIds\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, mode: 'override', widgetIds })\n}\n\nconst userWidgetsQuerySchema = z.object({\n userId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nconst userWidgetsGetDoc: OpenApiMethodDoc = {\n summary: 'Read widget overrides for a user',\n description: 'Returns the widgets inherited and explicitly configured for the requested user within the current scope.',\n tags: [dashboardsTag],\n query: userWidgetsQuerySchema,\n responses: [\n { status: 200, description: 'Widget settings for the user.', schema: dashboardUserWidgetsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing user identifier', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage user widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nconst userWidgetsPutDoc: OpenApiMethodDoc = {\n summary: 'Update user-specific dashboard widgets',\n description: 'Sets the widget override mode and allowed widgets for a user. Passing `mode: inherit` clears overrides.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: userWidgetSettingsSchema,\n description: 'User identifier, override mode, and widget ids. Scope is derived from the authenticated session.',\n },\n responses: [\n { status: 200, description: 'Overrides saved.', schema: dashboardUserWidgetsUpdateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or unknown widgets', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage user widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage user-level dashboard widgets',\n methods: {\n GET: userWidgetsGetDoc,\n PUT: userWidgetsPutDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardUserWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { userWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { resolveAllowedWidgetIds } from '@open-mercato/core/modules/dashboards/lib/access'\nimport {\n resolveWidgetAssignmentReadScope,\n resolveWidgetAssignmentTargetAccess,\n} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardUserWidgetsResponseSchema,\n dashboardUserWidgetsUpdateResponseSchema,\n} from '../../openapi'\n\nconst FEATURE = 'dashboards.admin.assign-widgets'\nconst RESOURCE_KIND = 'dashboards.userWidgets'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const userId = url.searchParams.get('userId')\n if (!userId) return NextResponse.json({ error: 'userId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const { tenantId, organizationId } = resolveWidgetAssignmentReadScope({\n auth: { tenantId: auth.tenantId ?? null, orgId: auth.orgId ?? null },\n isSuperAdmin: !!acl.isSuperAdmin,\n queryTenantId: url.searchParams.get('tenantId'),\n queryOrganizationId: url.searchParams.get('organizationId'),\n })\n\n const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null })\n const readAccess = resolveWidgetAssignmentTargetAccess({\n isSuperAdmin: !!acl.isSuperAdmin,\n scopeTenantId: tenantId,\n target: targetUserForRead,\n })\n if (readAccess === 'forbidden') {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (readAccess === 'not-found') {\n return NextResponse.json({ error: 'User not found' }, { status: 404 })\n }\n\n const widgets = await loadAllWidgets()\n const targetAcl = await rbac.loadAcl(userId, { tenantId, organizationId })\n const allowed = await resolveAllowedWidgetIds(\n em,\n {\n userId,\n tenantId,\n organizationId,\n features: targetAcl.features ?? [],\n isSuperAdmin: !!targetAcl.isSuperAdmin,\n },\n widgets,\n )\n\n const record = await em.findOne(DashboardUserWidgets, {\n userId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n const response = {\n mode: record ? record.mode : 'inherit',\n widgetIds: record && record.mode === 'override' ? record.widgetIdsJson : [],\n hasCustom: !!record && record.mode === 'override',\n effectiveWidgetIds: allowed,\n scope: { tenantId, organizationId },\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = userWidgetSettingsSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const widgets = await loadAllWidgets()\n const validWidgetIds = new Set(widgets.map((w) => w.metadata.id))\n const widgetIds = parsed.data.widgetIds.filter((id) => validWidgetIds.has(id))\n\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null })\n const writeAccess = resolveWidgetAssignmentTargetAccess({\n isSuperAdmin: !!acl.isSuperAdmin,\n scopeTenantId: tenantId,\n target: targetUser,\n })\n if (writeAccess === 'forbidden') {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (writeAccess === 'not-found') {\n return NextResponse.json({ error: 'User not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { userId: parsed.data.userId, mode: parsed.data.mode, widgetIds },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let record = await em.findOne(DashboardUserWidgets, {\n userId: parsed.data.userId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (parsed.data.mode === 'inherit') {\n if (record) {\n await em.remove(record).flush()\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true, mode: 'inherit', widgetIds: [] })\n }\n\n if (!record) {\n record = em.create(DashboardUserWidgets, {\n userId: parsed.data.userId,\n tenantId,\n organizationId,\n mode: 'override',\n widgetIdsJson: widgetIds,\n })\n em.persist(record)\n } else {\n record.mode = 'override'\n record.widgetIdsJson = widgetIds\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.userId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, mode: 'override', widgetIds })\n}\n\nconst userWidgetsQuerySchema = z.object({\n userId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nconst userWidgetsGetDoc: OpenApiMethodDoc = {\n summary: 'Read widget overrides for a user',\n description: 'Returns the widgets inherited and explicitly configured for the requested user within the current scope.',\n tags: [dashboardsTag],\n query: userWidgetsQuerySchema,\n responses: [\n { status: 200, description: 'Widget settings for the user.', schema: dashboardUserWidgetsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing user identifier', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage user widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nconst userWidgetsPutDoc: OpenApiMethodDoc = {\n summary: 'Update user-specific dashboard widgets',\n description: 'Sets the widget override mode and allowed widgets for a user. Passing `mode: inherit` clears overrides.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: userWidgetSettingsSchema,\n description: 'User identifier, override mode, and widget ids. Scope is derived from the authenticated session.',\n },\n responses: [\n { status: 200, description: 'Overrides saved.', schema: dashboardUserWidgetsUpdateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or unknown widgets', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage user widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage user-level dashboard widgets',\n methods: {\n GET: userWidgetsGetDoc,\n PUT: userWidgetsPutDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,3 +1,10 @@
1
+ function resolveWidgetAssignmentTargetAccess(params) {
2
+ const { isSuperAdmin, scopeTenantId, target } = params;
3
+ if (!isSuperAdmin && !scopeTenantId) return "forbidden";
4
+ if (!target) return "not-found";
5
+ if (scopeTenantId && (target.tenantId ?? null) !== scopeTenantId) return "not-found";
6
+ return "allowed";
7
+ }
1
8
  function resolveWidgetAssignmentReadScope(params) {
2
9
  const { auth, isSuperAdmin, queryTenantId, queryOrganizationId } = params;
3
10
  if (isSuperAdmin) {
@@ -12,6 +19,7 @@ function resolveWidgetAssignmentReadScope(params) {
12
19
  };
13
20
  }
14
21
  export {
15
- resolveWidgetAssignmentReadScope
22
+ resolveWidgetAssignmentReadScope,
23
+ resolveWidgetAssignmentTargetAccess
16
24
  };
17
25
  //# sourceMappingURL=widgetAssignmentScope.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dashboards/lib/widgetAssignmentScope.ts"],
4
- "sourcesContent": ["export type AuthScopeInput = {\n tenantId?: string | null\n orgId?: string | null\n}\n\nexport type ResolvedAssignmentScope = {\n tenantId: string | null\n organizationId: string | null\n}\n\nexport function resolveWidgetAssignmentReadScope(params: {\n auth: AuthScopeInput\n isSuperAdmin: boolean\n queryTenantId?: string | null\n queryOrganizationId?: string | null\n}): ResolvedAssignmentScope {\n const { auth, isSuperAdmin, queryTenantId, queryOrganizationId } = params\n if (isSuperAdmin) {\n return {\n tenantId: (queryTenantId && queryTenantId.length ? queryTenantId : auth.tenantId) ?? null,\n organizationId:\n (queryOrganizationId && queryOrganizationId.length ? queryOrganizationId : auth.orgId) ?? null,\n }\n }\n return {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n"],
5
- "mappings": "AAUO,SAAS,iCAAiC,QAKrB;AAC1B,QAAM,EAAE,MAAM,cAAc,eAAe,oBAAoB,IAAI;AACnE,MAAI,cAAc;AAChB,WAAO;AAAA,MACL,WAAW,iBAAiB,cAAc,SAAS,gBAAgB,KAAK,aAAa;AAAA,MACrF,iBACG,uBAAuB,oBAAoB,SAAS,sBAAsB,KAAK,UAAU;AAAA,IAC9F;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,EAChC;AACF;",
4
+ "sourcesContent": ["export type AuthScopeInput = {\n tenantId?: string | null\n orgId?: string | null\n}\n\nexport type ResolvedAssignmentScope = {\n tenantId: string | null\n organizationId: string | null\n}\n\nexport type WidgetAssignmentTarget = {\n tenantId?: string | null\n}\n\nexport type WidgetAssignmentTargetAccess = 'allowed' | 'forbidden' | 'not-found'\n\nexport function resolveWidgetAssignmentTargetAccess(params: {\n isSuperAdmin: boolean\n scopeTenantId: string | null\n target: WidgetAssignmentTarget | null | undefined\n}): WidgetAssignmentTargetAccess {\n const { isSuperAdmin, scopeTenantId, target } = params\n if (!isSuperAdmin && !scopeTenantId) return 'forbidden'\n if (!target) return 'not-found'\n if (scopeTenantId && (target.tenantId ?? null) !== scopeTenantId) return 'not-found'\n return 'allowed'\n}\n\nexport function resolveWidgetAssignmentReadScope(params: {\n auth: AuthScopeInput\n isSuperAdmin: boolean\n queryTenantId?: string | null\n queryOrganizationId?: string | null\n}): ResolvedAssignmentScope {\n const { auth, isSuperAdmin, queryTenantId, queryOrganizationId } = params\n if (isSuperAdmin) {\n return {\n tenantId: (queryTenantId && queryTenantId.length ? queryTenantId : auth.tenantId) ?? null,\n organizationId:\n (queryOrganizationId && queryOrganizationId.length ? queryOrganizationId : auth.orgId) ?? null,\n }\n }\n return {\n tenantId: auth.tenantId ?? null,\n organizationId: auth.orgId ?? null,\n }\n}\n"],
5
+ "mappings": "AAgBO,SAAS,oCAAoC,QAInB;AAC/B,QAAM,EAAE,cAAc,eAAe,OAAO,IAAI;AAChD,MAAI,CAAC,gBAAgB,CAAC,cAAe,QAAO;AAC5C,MAAI,CAAC,OAAQ,QAAO;AACpB,MAAI,kBAAkB,OAAO,YAAY,UAAU,cAAe,QAAO;AACzE,SAAO;AACT;AAEO,SAAS,iCAAiC,QAKrB;AAC1B,QAAM,EAAE,MAAM,cAAc,eAAe,oBAAoB,IAAI;AACnE,MAAI,cAAc;AAChB,WAAO;AAAA,MACL,WAAW,iBAAiB,cAAc,SAAS,gBAAgB,KAAK,aAAa;AAAA,MACrF,iBACG,uBAAuB,oBAAoB,SAAS,sBAAsB,KAAK,UAAU;AAAA,IAC9F;AAAA,EACF;AACA,SAAO;AAAA,IACL,UAAU,KAAK,YAAY;AAAA,IAC3B,gBAAgB,KAAK,SAAS;AAAA,EAChC;AACF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/core",
3
- "version": "0.6.6-develop.6540.1.ae856df1ec",
3
+ "version": "0.6.6-develop.6542.1.fee3e68aa6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -253,16 +253,16 @@
253
253
  "zod": "^4.4.3"
254
254
  },
255
255
  "peerDependencies": {
256
- "@open-mercato/ai-assistant": "0.6.6-develop.6540.1.ae856df1ec",
257
- "@open-mercato/shared": "0.6.6-develop.6540.1.ae856df1ec",
258
- "@open-mercato/ui": "0.6.6-develop.6540.1.ae856df1ec",
256
+ "@open-mercato/ai-assistant": "0.6.6-develop.6542.1.fee3e68aa6",
257
+ "@open-mercato/shared": "0.6.6-develop.6542.1.fee3e68aa6",
258
+ "@open-mercato/ui": "0.6.6-develop.6542.1.fee3e68aa6",
259
259
  "react": "^19.0.0",
260
260
  "react-dom": "^19.0.0"
261
261
  },
262
262
  "devDependencies": {
263
- "@open-mercato/ai-assistant": "0.6.6-develop.6540.1.ae856df1ec",
264
- "@open-mercato/shared": "0.6.6-develop.6540.1.ae856df1ec",
265
- "@open-mercato/ui": "0.6.6-develop.6540.1.ae856df1ec",
263
+ "@open-mercato/ai-assistant": "0.6.6-develop.6542.1.fee3e68aa6",
264
+ "@open-mercato/shared": "0.6.6-develop.6542.1.fee3e68aa6",
265
+ "@open-mercato/ui": "0.6.6-develop.6542.1.fee3e68aa6",
266
266
  "@testing-library/dom": "^10.4.1",
267
267
  "@testing-library/jest-dom": "^6.9.1",
268
268
  "@testing-library/react": "^16.3.1",
@@ -6,7 +6,10 @@ import { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data
6
6
  import { Role } from '@open-mercato/core/modules/auth/data/entities'
7
7
  import { roleWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'
8
8
  import { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'
9
- import { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
9
+ import {
10
+ resolveWidgetAssignmentReadScope,
11
+ resolveWidgetAssignmentTargetAccess,
12
+ } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
10
13
  import { hasFeature } from '@open-mercato/shared/security/features'
11
14
  import {
12
15
  runCrudMutationGuardAfterSuccess,
@@ -70,7 +73,15 @@ export async function GET(req: Request) {
70
73
  })
71
74
 
72
75
  const role = await em.findOne(Role, { id: roleId, deletedAt: null })
73
- if (!role || (tenantId && role.tenantId !== tenantId)) {
76
+ const access = resolveWidgetAssignmentTargetAccess({
77
+ isSuperAdmin: !!acl.isSuperAdmin,
78
+ scopeTenantId: tenantId,
79
+ target: role,
80
+ })
81
+ if (access === 'forbidden') {
82
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
83
+ }
84
+ if (access === 'not-found') {
74
85
  return NextResponse.json({ error: 'Role not found' }, { status: 404 })
75
86
  }
76
87
 
@@ -121,7 +132,15 @@ export async function PUT(req: Request) {
121
132
  const organizationId = auth.orgId ?? null
122
133
 
123
134
  const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })
124
- if (!role || (tenantId && role.tenantId !== tenantId)) {
135
+ const access = resolveWidgetAssignmentTargetAccess({
136
+ isSuperAdmin: !!acl.isSuperAdmin,
137
+ scopeTenantId: tenantId,
138
+ target: role,
139
+ })
140
+ if (access === 'forbidden') {
141
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
142
+ }
143
+ if (access === 'not-found') {
125
144
  return NextResponse.json({ error: 'Role not found' }, { status: 404 })
126
145
  }
127
146
 
@@ -7,7 +7,10 @@ import { User } from '@open-mercato/core/modules/auth/data/entities'
7
7
  import { userWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'
8
8
  import { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'
9
9
  import { resolveAllowedWidgetIds } from '@open-mercato/core/modules/dashboards/lib/access'
10
- import { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
10
+ import {
11
+ resolveWidgetAssignmentReadScope,
12
+ resolveWidgetAssignmentTargetAccess,
13
+ } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'
11
14
  import { hasFeature } from '@open-mercato/shared/security/features'
12
15
  import {
13
16
  runCrudMutationGuardAfterSuccess,
@@ -53,7 +56,15 @@ export async function GET(req: Request) {
53
56
  })
54
57
 
55
58
  const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null })
56
- if (!targetUserForRead || (tenantId && (targetUserForRead.tenantId ?? null) !== tenantId)) {
59
+ const readAccess = resolveWidgetAssignmentTargetAccess({
60
+ isSuperAdmin: !!acl.isSuperAdmin,
61
+ scopeTenantId: tenantId,
62
+ target: targetUserForRead,
63
+ })
64
+ if (readAccess === 'forbidden') {
65
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
66
+ }
67
+ if (readAccess === 'not-found') {
57
68
  return NextResponse.json({ error: 'User not found' }, { status: 404 })
58
69
  }
59
70
 
@@ -121,7 +132,15 @@ export async function PUT(req: Request) {
121
132
  const organizationId = auth.orgId ?? null
122
133
 
123
134
  const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null })
124
- if (!targetUser || (tenantId && (targetUser.tenantId ?? null) !== tenantId)) {
135
+ const writeAccess = resolveWidgetAssignmentTargetAccess({
136
+ isSuperAdmin: !!acl.isSuperAdmin,
137
+ scopeTenantId: tenantId,
138
+ target: targetUser,
139
+ })
140
+ if (writeAccess === 'forbidden') {
141
+ return NextResponse.json({ error: 'Forbidden' }, { status: 403 })
142
+ }
143
+ if (writeAccess === 'not-found') {
125
144
  return NextResponse.json({ error: 'User not found' }, { status: 404 })
126
145
  }
127
146
 
@@ -8,6 +8,24 @@ export type ResolvedAssignmentScope = {
8
8
  organizationId: string | null
9
9
  }
10
10
 
11
+ export type WidgetAssignmentTarget = {
12
+ tenantId?: string | null
13
+ }
14
+
15
+ export type WidgetAssignmentTargetAccess = 'allowed' | 'forbidden' | 'not-found'
16
+
17
+ export function resolveWidgetAssignmentTargetAccess(params: {
18
+ isSuperAdmin: boolean
19
+ scopeTenantId: string | null
20
+ target: WidgetAssignmentTarget | null | undefined
21
+ }): WidgetAssignmentTargetAccess {
22
+ const { isSuperAdmin, scopeTenantId, target } = params
23
+ if (!isSuperAdmin && !scopeTenantId) return 'forbidden'
24
+ if (!target) return 'not-found'
25
+ if (scopeTenantId && (target.tenantId ?? null) !== scopeTenantId) return 'not-found'
26
+ return 'allowed'
27
+ }
28
+
11
29
  export function resolveWidgetAssignmentReadScope(params: {
12
30
  auth: AuthScopeInput
13
31
  isSuperAdmin: boolean