@open-mercato/enterprise 0.6.5-develop.5212.1.b47932beef → 0.6.5-develop.5226.1.2cf979f8a3

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,26 +6,6 @@ import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
6
6
  import { enforceTenantSelection, resolveIsSuperAdmin } from "@open-mercato/core/modules/auth/lib/tenantAccess";
7
7
  import { EnforcementScope } from "../../data/entities.js";
8
8
  import { localizeSecurityApiBody, securityApiError } from "../i18n.js";
9
- async function resolveEnforcementContext(req) {
10
- const auth = await getAuthFromRequest(req);
11
- if (!auth?.sub) {
12
- return securityApiError(401, "Unauthorized");
13
- }
14
- const container = await createRequestContainer();
15
- return {
16
- auth,
17
- container,
18
- commandContext: {
19
- container,
20
- auth,
21
- organizationScope: null,
22
- selectedOrganizationId: auth.orgId ?? null,
23
- organizationIds: auth.orgId ? [auth.orgId] : null,
24
- request: req
25
- },
26
- enforcementService: container.resolve("mfaEnforcementService")
27
- };
28
- }
29
9
  function normalizeNullableString(value) {
30
10
  return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
31
11
  }
@@ -84,6 +64,26 @@ async function assertActorOwnsEnforcementScope(ctx, scope, scopeId) {
84
64
  if (isSuperAdmin) return;
85
65
  await assertActorOwnsOrganization(ctx, organizationId);
86
66
  }
67
+ async function resolveEnforcementContext(req) {
68
+ const auth = await getAuthFromRequest(req);
69
+ if (!auth?.sub) {
70
+ return securityApiError(401, "Unauthorized");
71
+ }
72
+ const container = await createRequestContainer();
73
+ return {
74
+ auth,
75
+ container,
76
+ commandContext: {
77
+ container,
78
+ auth,
79
+ organizationScope: null,
80
+ selectedOrganizationId: auth.orgId ?? null,
81
+ organizationIds: auth.orgId ? [auth.orgId] : null,
82
+ request: req
83
+ },
84
+ enforcementService: container.resolve("mfaEnforcementService")
85
+ };
86
+ }
87
87
  async function mapEnforcementError(error) {
88
88
  if (error instanceof CrudHttpError) {
89
89
  return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/security/api/enforcement/_shared.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { CrudHttpError, forbidden } 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 { enforceTenantSelection, resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { EnforcementScope, type MfaEnforcementPolicy } from '../../data/entities'\nimport type { MfaEnforcementServiceError, MfaEnforcementService } from '../../services/MfaEnforcementService'\nimport { localizeSecurityApiBody, securityApiError } from '../i18n'\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\ntype Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>\n\nexport type EnforcementActorContext = {\n tenantId: string | null\n isSuperAdmin: boolean\n}\n\nexport type EnforcementRequestContext = {\n auth: Auth\n container: RequestContainer\n commandContext: CommandRuntimeContext\n enforcementService: MfaEnforcementService\n}\n\nexport async function resolveEnforcementContext(req: Request): Promise<EnforcementRequestContext | NextResponse> {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return securityApiError(401, 'Unauthorized')\n }\n\n const container = await createRequestContainer()\n return {\n auth,\n container,\n commandContext: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId ?? null,\n organizationIds: auth.orgId ? [auth.orgId] : null,\n request: req,\n },\n enforcementService: container.resolve<MfaEnforcementService>('mfaEnforcementService'),\n }\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n if (!Array.isArray(values)) return null\n const result: string[] = []\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (trimmed) result.push(trimmed)\n }\n return result\n}\n\nexport async function resolveActorContext(ctx: EnforcementRequestContext): Promise<EnforcementActorContext> {\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n return {\n tenantId: normalizeNullableString(ctx.auth.tenantId),\n isSuperAdmin,\n }\n}\n\nasync function assertActorOwnsOrganization(\n ctx: EnforcementRequestContext,\n organizationId: string,\n): Promise<void> {\n const rbacService = ctx.container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(ctx.auth.sub, {\n tenantId: normalizeNullableString(ctx.auth.tenantId),\n organizationId: normalizeNullableString(ctx.auth.orgId),\n })\n const organizations = normalizeOrganizationList(acl?.organizations)\n if (organizations === null || organizations.includes('__all__')) return\n if (!organizations.includes(organizationId)) {\n throw forbidden('Not authorized to target this organization.')\n }\n}\n\nexport async function assertActorOwnsEnforcementScope(\n ctx: EnforcementRequestContext,\n scope: EnforcementScope,\n scopeId: string | null | undefined,\n): Promise<void> {\n if (scope === EnforcementScope.PLATFORM) {\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n if (!isSuperAdmin) {\n throw forbidden('Platform scope requires platform administrator privileges.')\n }\n return\n }\n\n if (scope === EnforcementScope.TENANT) {\n await enforceTenantSelection({ auth: ctx.auth, container: ctx.container }, scopeId)\n return\n }\n\n const normalizedScopeId = normalizeNullableString(scopeId)\n if (!normalizedScopeId) {\n throw new CrudHttpError(400, { error: \"organisation scopeId must use '<tenantId>:<organizationId>' format\" })\n }\n const [tenantId, organizationId] = normalizedScopeId.split(':')\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: \"organisation scopeId must use '<tenantId>:<organizationId>' format\" })\n }\n\n await enforceTenantSelection({ auth: ctx.auth, container: ctx.container }, tenantId)\n\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n if (isSuperAdmin) return\n await assertActorOwnsOrganization(ctx, organizationId)\n}\n\nexport async function mapEnforcementError(error: unknown): Promise<NextResponse> {\n if (error instanceof CrudHttpError) {\n return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status })\n }\n if (isMfaEnforcementServiceError(error)) {\n return securityApiError(error.statusCode, error.message)\n }\n console.error('security.enforcement.route failure', error)\n return securityApiError(500, 'Failed to process enforcement request.')\n}\n\nexport function toPolicyResponse(policy: MfaEnforcementPolicy): {\n id: string\n scope: string\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n isEnforced: boolean\n allowedMethods: string[] | null\n enforcementDeadline: string | null\n enforcedBy: string\n createdAt: string\n updatedAt: string\n} {\n return {\n id: policy.id,\n scope: policy.scope,\n tenantId: policy.tenantId ?? null,\n tenantName: null,\n organizationId: policy.organizationId ?? null,\n organizationName: null,\n isEnforced: policy.isEnforced,\n allowedMethods: policy.allowedMethods ?? null,\n enforcementDeadline: policy.enforcementDeadline ? policy.enforcementDeadline.toISOString() : null,\n enforcedBy: policy.enforcedBy,\n createdAt: policy.createdAt.toISOString(),\n updatedAt: policy.updatedAt.toISOString(),\n }\n}\n\nexport async function attachPolicyScopeNames(\n container: RequestContainer,\n policies: MfaEnforcementPolicy[],\n): Promise<Array<ReturnType<typeof toPolicyResponse>>> {\n if (policies.length === 0) return []\n\n const em = container.resolve<EntityManager>('em')\n const tenantIds = Array.from(\n new Set(\n policies\n .map((policy) => policy.tenantId ?? null)\n .filter((tenantId): tenantId is string => typeof tenantId === 'string' && tenantId.length > 0),\n ),\n )\n const organizationIds = Array.from(\n new Set(\n policies\n .map((policy) => policy.organizationId ?? null)\n .filter((organizationId): organizationId is string => typeof organizationId === 'string' && organizationId.length > 0),\n ),\n )\n\n const [tenants, organizations] = await Promise.all([\n tenantIds.length\n ? em.find(Tenant, { id: { $in: tenantIds }, deletedAt: null })\n : Promise.resolve([]),\n organizationIds.length\n ? em.find(Organization, { id: { $in: organizationIds }, deletedAt: null })\n : Promise.resolve([]),\n ])\n\n const tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tenantId = tenant?.id ? String(tenant.id) : null\n if (!tenantId) return acc\n acc[tenantId] = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : tenantId\n return acc\n }, {})\n const organizationMap = organizations.reduce<Record<string, string>>((acc, organization) => {\n const organizationId = organization?.id ? String(organization.id) : null\n if (!organizationId) return acc\n acc[organizationId] = typeof organization.name === 'string' && organization.name.length > 0\n ? organization.name\n : organizationId\n return acc\n }, {})\n\n return policies.map((policy) => {\n const response = toPolicyResponse(policy)\n return {\n ...response,\n tenantName: response.tenantId ? tenantMap[response.tenantId] ?? response.tenantId : null,\n organizationName: response.organizationId\n ? organizationMap[response.organizationId] ?? response.organizationId\n : null,\n }\n })\n}\n\nfunction isMfaEnforcementServiceError(error: unknown): error is MfaEnforcementServiceError {\n return error instanceof Error\n && error.name === 'MfaEnforcementServiceError'\n && typeof (error as Partial<MfaEnforcementServiceError>).statusCode === 'number'\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { Organization, Tenant } from '@open-mercato/core/modules/directory/data/entities'\nimport { CrudHttpError, forbidden } 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 { enforceTenantSelection, resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { EnforcementScope, type MfaEnforcementPolicy } from '../../data/entities'\nimport type {\n EnforcementActorContext,\n MfaEnforcementServiceError,\n MfaEnforcementService,\n} from '../../services/MfaEnforcementService'\nimport { localizeSecurityApiBody, securityApiError } from '../i18n'\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\ntype Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>\n\nexport type EnforcementRequestContext = {\n auth: Auth\n container: RequestContainer\n commandContext: CommandRuntimeContext\n enforcementService: MfaEnforcementService\n}\n\nfunction normalizeNullableString(value: unknown): string | null {\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\nfunction normalizeOrganizationList(values: unknown): string[] | null {\n if (values === null || values === undefined) return null\n if (!Array.isArray(values)) return null\n const result: string[] = []\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (trimmed) result.push(trimmed)\n }\n return result\n}\n\nexport async function resolveActorContext(ctx: EnforcementRequestContext): Promise<EnforcementActorContext> {\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n return {\n tenantId: normalizeNullableString(ctx.auth.tenantId),\n isSuperAdmin,\n }\n}\n\nasync function assertActorOwnsOrganization(\n ctx: EnforcementRequestContext,\n organizationId: string,\n): Promise<void> {\n const rbacService = ctx.container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(ctx.auth.sub, {\n tenantId: normalizeNullableString(ctx.auth.tenantId),\n organizationId: normalizeNullableString(ctx.auth.orgId),\n })\n const organizations = normalizeOrganizationList(acl?.organizations)\n if (organizations === null || organizations.includes('__all__')) return\n if (!organizations.includes(organizationId)) {\n throw forbidden('Not authorized to target this organization.')\n }\n}\n\nexport async function assertActorOwnsEnforcementScope(\n ctx: EnforcementRequestContext,\n scope: EnforcementScope,\n scopeId: string | null | undefined,\n): Promise<void> {\n if (scope === EnforcementScope.PLATFORM) {\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n if (!isSuperAdmin) {\n throw forbidden('Platform scope requires platform administrator privileges.')\n }\n return\n }\n\n if (scope === EnforcementScope.TENANT) {\n await enforceTenantSelection({ auth: ctx.auth, container: ctx.container }, scopeId)\n return\n }\n\n const normalizedScopeId = normalizeNullableString(scopeId)\n if (!normalizedScopeId) {\n throw new CrudHttpError(400, { error: \"organisation scopeId must use '<tenantId>:<organizationId>' format\" })\n }\n const [tenantId, organizationId] = normalizedScopeId.split(':')\n if (!tenantId || !organizationId) {\n throw new CrudHttpError(400, { error: \"organisation scopeId must use '<tenantId>:<organizationId>' format\" })\n }\n\n await enforceTenantSelection({ auth: ctx.auth, container: ctx.container }, tenantId)\n\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n if (isSuperAdmin) return\n await assertActorOwnsOrganization(ctx, organizationId)\n}\n\nexport async function resolveEnforcementContext(req: Request): Promise<EnforcementRequestContext | NextResponse> {\n const auth = await getAuthFromRequest(req)\n if (!auth?.sub) {\n return securityApiError(401, 'Unauthorized')\n }\n\n const container = await createRequestContainer()\n return {\n auth,\n container,\n commandContext: {\n container,\n auth,\n organizationScope: null,\n selectedOrganizationId: auth.orgId ?? null,\n organizationIds: auth.orgId ? [auth.orgId] : null,\n request: req,\n },\n enforcementService: container.resolve<MfaEnforcementService>('mfaEnforcementService'),\n }\n}\n\nexport async function mapEnforcementError(error: unknown): Promise<NextResponse> {\n if (error instanceof CrudHttpError) {\n return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status })\n }\n if (isMfaEnforcementServiceError(error)) {\n return securityApiError(error.statusCode, error.message)\n }\n console.error('security.enforcement.route failure', error)\n return securityApiError(500, 'Failed to process enforcement request.')\n}\n\nexport function toPolicyResponse(policy: MfaEnforcementPolicy): {\n id: string\n scope: string\n tenantId: string | null\n tenantName: string | null\n organizationId: string | null\n organizationName: string | null\n isEnforced: boolean\n allowedMethods: string[] | null\n enforcementDeadline: string | null\n enforcedBy: string\n createdAt: string\n updatedAt: string\n} {\n return {\n id: policy.id,\n scope: policy.scope,\n tenantId: policy.tenantId ?? null,\n tenantName: null,\n organizationId: policy.organizationId ?? null,\n organizationName: null,\n isEnforced: policy.isEnforced,\n allowedMethods: policy.allowedMethods ?? null,\n enforcementDeadline: policy.enforcementDeadline ? policy.enforcementDeadline.toISOString() : null,\n enforcedBy: policy.enforcedBy,\n createdAt: policy.createdAt.toISOString(),\n updatedAt: policy.updatedAt.toISOString(),\n }\n}\n\nexport async function attachPolicyScopeNames(\n container: RequestContainer,\n policies: MfaEnforcementPolicy[],\n): Promise<Array<ReturnType<typeof toPolicyResponse>>> {\n if (policies.length === 0) return []\n\n const em = container.resolve<EntityManager>('em')\n const tenantIds = Array.from(\n new Set(\n policies\n .map((policy) => policy.tenantId ?? null)\n .filter((tenantId): tenantId is string => typeof tenantId === 'string' && tenantId.length > 0),\n ),\n )\n const organizationIds = Array.from(\n new Set(\n policies\n .map((policy) => policy.organizationId ?? null)\n .filter((organizationId): organizationId is string => typeof organizationId === 'string' && organizationId.length > 0),\n ),\n )\n\n const [tenants, organizations] = await Promise.all([\n tenantIds.length\n ? em.find(Tenant, { id: { $in: tenantIds }, deletedAt: null })\n : Promise.resolve([]),\n organizationIds.length\n ? em.find(Organization, { id: { $in: organizationIds }, deletedAt: null })\n : Promise.resolve([]),\n ])\n\n const tenantMap = tenants.reduce<Record<string, string>>((acc, tenant) => {\n const tenantId = tenant?.id ? String(tenant.id) : null\n if (!tenantId) return acc\n acc[tenantId] = typeof tenant.name === 'string' && tenant.name.length > 0 ? tenant.name : tenantId\n return acc\n }, {})\n const organizationMap = organizations.reduce<Record<string, string>>((acc, organization) => {\n const organizationId = organization?.id ? String(organization.id) : null\n if (!organizationId) return acc\n acc[organizationId] = typeof organization.name === 'string' && organization.name.length > 0\n ? organization.name\n : organizationId\n return acc\n }, {})\n\n return policies.map((policy) => {\n const response = toPolicyResponse(policy)\n return {\n ...response,\n tenantName: response.tenantId ? tenantMap[response.tenantId] ?? response.tenantId : null,\n organizationName: response.organizationId\n ? organizationMap[response.organizationId] ?? response.organizationId\n : null,\n }\n })\n}\n\nfunction isMfaEnforcementServiceError(error: unknown): error is MfaEnforcementServiceError {\n return error instanceof Error\n && error.name === 'MfaEnforcementServiceError'\n && typeof (error as Partial<MfaEnforcementServiceError>).statusCode === 'number'\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["isSuperAdmin"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/enterprise",
3
- "version": "0.6.5-develop.5212.1.b47932beef",
3
+ "version": "0.6.5-develop.5226.1.2cf979f8a3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {
@@ -64,8 +64,8 @@
64
64
  }
65
65
  },
66
66
  "dependencies": {
67
- "@open-mercato/core": "0.6.5-develop.5212.1.b47932beef",
68
- "@open-mercato/ui": "0.6.5-develop.5212.1.b47932beef",
67
+ "@open-mercato/core": "0.6.5-develop.5226.1.2cf979f8a3",
68
+ "@open-mercato/ui": "0.6.5-develop.5226.1.2cf979f8a3",
69
69
  "@simplewebauthn/browser": "^13.3.0",
70
70
  "@simplewebauthn/server": "^13.3.1",
71
71
  "@simplewebauthn/types": "^12.0.0",
@@ -75,12 +75,12 @@
75
75
  "qrcode": "^1.5.4"
76
76
  },
77
77
  "peerDependencies": {
78
- "@open-mercato/shared": "0.6.5-develop.5212.1.b47932beef",
78
+ "@open-mercato/shared": "0.6.5-develop.5226.1.2cf979f8a3",
79
79
  "react": "^19.0.0",
80
80
  "react-dom": "^19.0.0"
81
81
  },
82
82
  "devDependencies": {
83
- "@open-mercato/shared": "0.6.5-develop.5212.1.b47932beef",
83
+ "@open-mercato/shared": "0.6.5-develop.5226.1.2cf979f8a3",
84
84
  "@types/jest": "^30.0.0",
85
85
  "@types/react": "^19.2.17",
86
86
  "@types/react-dom": "^19.2.3",
@@ -8,17 +8,16 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
8
8
  import { enforceTenantSelection, resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
9
9
  import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
10
10
  import { EnforcementScope, type MfaEnforcementPolicy } from '../../data/entities'
11
- import type { MfaEnforcementServiceError, MfaEnforcementService } from '../../services/MfaEnforcementService'
11
+ import type {
12
+ EnforcementActorContext,
13
+ MfaEnforcementServiceError,
14
+ MfaEnforcementService,
15
+ } from '../../services/MfaEnforcementService'
12
16
  import { localizeSecurityApiBody, securityApiError } from '../i18n'
13
17
 
14
18
  type RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>
15
19
  type Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>
16
20
 
17
- export type EnforcementActorContext = {
18
- tenantId: string | null
19
- isSuperAdmin: boolean
20
- }
21
-
22
21
  export type EnforcementRequestContext = {
23
22
  auth: Auth
24
23
  container: RequestContainer
@@ -26,28 +25,6 @@ export type EnforcementRequestContext = {
26
25
  enforcementService: MfaEnforcementService
27
26
  }
28
27
 
29
- export async function resolveEnforcementContext(req: Request): Promise<EnforcementRequestContext | NextResponse> {
30
- const auth = await getAuthFromRequest(req)
31
- if (!auth?.sub) {
32
- return securityApiError(401, 'Unauthorized')
33
- }
34
-
35
- const container = await createRequestContainer()
36
- return {
37
- auth,
38
- container,
39
- commandContext: {
40
- container,
41
- auth,
42
- organizationScope: null,
43
- selectedOrganizationId: auth.orgId ?? null,
44
- organizationIds: auth.orgId ? [auth.orgId] : null,
45
- request: req,
46
- },
47
- enforcementService: container.resolve<MfaEnforcementService>('mfaEnforcementService'),
48
- }
49
- }
50
-
51
28
  function normalizeNullableString(value: unknown): string | null {
52
29
  return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null
53
30
  }
@@ -122,6 +99,28 @@ export async function assertActorOwnsEnforcementScope(
122
99
  await assertActorOwnsOrganization(ctx, organizationId)
123
100
  }
124
101
 
102
+ export async function resolveEnforcementContext(req: Request): Promise<EnforcementRequestContext | NextResponse> {
103
+ const auth = await getAuthFromRequest(req)
104
+ if (!auth?.sub) {
105
+ return securityApiError(401, 'Unauthorized')
106
+ }
107
+
108
+ const container = await createRequestContainer()
109
+ return {
110
+ auth,
111
+ container,
112
+ commandContext: {
113
+ container,
114
+ auth,
115
+ organizationScope: null,
116
+ selectedOrganizationId: auth.orgId ?? null,
117
+ organizationIds: auth.orgId ? [auth.orgId] : null,
118
+ request: req,
119
+ },
120
+ enforcementService: container.resolve<MfaEnforcementService>('mfaEnforcementService'),
121
+ }
122
+ }
123
+
125
124
  export async function mapEnforcementError(error: unknown): Promise<NextResponse> {
126
125
  if (error instanceof CrudHttpError) {
127
126
  return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status })