@open-mercato/enterprise 0.6.5-develop.4882.1.901c3aa813 → 0.6.5-develop.4964.1.ae0edca575

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/modules/security/api/enforcement/[id]/route.js +35 -1
  2. package/dist/modules/security/api/enforcement/[id]/route.js.map +2 -2
  3. package/dist/modules/security/api/enforcement/_shared.js +63 -1
  4. package/dist/modules/security/api/enforcement/_shared.js.map +3 -3
  5. package/dist/modules/security/api/enforcement/compliance/route.js +12 -3
  6. package/dist/modules/security/api/enforcement/compliance/route.js.map +2 -2
  7. package/dist/modules/security/api/enforcement/route.js +25 -2
  8. package/dist/modules/security/api/enforcement/route.js.map +2 -2
  9. package/dist/modules/security/api/users/[id]/mfa/reset/route.js +6 -1
  10. package/dist/modules/security/api/users/[id]/mfa/reset/route.js.map +2 -2
  11. package/dist/modules/security/api/users/[id]/mfa/status/route.js +13 -2
  12. package/dist/modules/security/api/users/[id]/mfa/status/route.js.map +2 -2
  13. package/dist/modules/security/api/users/_shared.js +56 -1
  14. package/dist/modules/security/api/users/_shared.js.map +2 -2
  15. package/dist/modules/security/api/users/mfa/compliance/route.js +17 -7
  16. package/dist/modules/security/api/users/mfa/compliance/route.js.map +2 -2
  17. package/dist/modules/security/commands/createEnforcementPolicy.js +6 -1
  18. package/dist/modules/security/commands/createEnforcementPolicy.js.map +2 -2
  19. package/dist/modules/security/commands/deleteEnforcementPolicy.js +6 -1
  20. package/dist/modules/security/commands/deleteEnforcementPolicy.js.map +2 -2
  21. package/dist/modules/security/commands/resetUserMfa.js +6 -1
  22. package/dist/modules/security/commands/resetUserMfa.js.map +2 -2
  23. package/dist/modules/security/commands/updateEnforcementPolicy.js +6 -1
  24. package/dist/modules/security/commands/updateEnforcementPolicy.js.map +2 -2
  25. package/dist/modules/security/services/MfaAdminService.js +22 -5
  26. package/dist/modules/security/services/MfaAdminService.js.map +2 -2
  27. package/dist/modules/security/services/MfaEnforcementService.js +28 -6
  28. package/dist/modules/security/services/MfaEnforcementService.js.map +2 -2
  29. package/package.json +5 -5
  30. package/src/modules/security/api/enforcement/[id]/route.ts +50 -1
  31. package/src/modules/security/api/enforcement/_shared.ts +83 -2
  32. package/src/modules/security/api/enforcement/compliance/route.ts +10 -1
  33. package/src/modules/security/api/enforcement/route.ts +30 -2
  34. package/src/modules/security/api/users/[id]/mfa/reset/route.ts +6 -1
  35. package/src/modules/security/api/users/[id]/mfa/status/route.ts +13 -2
  36. package/src/modules/security/api/users/_shared.ts +69 -1
  37. package/src/modules/security/api/users/mfa/compliance/route.ts +16 -7
  38. package/src/modules/security/commands/createEnforcementPolicy.ts +6 -1
  39. package/src/modules/security/commands/deleteEnforcementPolicy.ts +6 -1
  40. package/src/modules/security/commands/resetUserMfa.ts +6 -1
  41. package/src/modules/security/commands/updateEnforcementPolicy.ts +6 -1
  42. package/src/modules/security/services/MfaAdminService.ts +29 -6
  43. package/src/modules/security/services/MfaEnforcementService.ts +42 -2
@@ -2,6 +2,7 @@ import { z } from 'zod'
2
2
  import { registerCommand } from '@open-mercato/shared/lib/commands'
3
3
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
4
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
5
6
  import type { MfaEnforcementService } from '../services/MfaEnforcementService'
6
7
 
7
8
  export const commandId = 'security.enforcement.delete'
@@ -35,8 +36,12 @@ registerCommand({
35
36
  }
36
37
 
37
38
  const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')
39
+ const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })
38
40
  try {
39
- await enforcementService.deletePolicy(parsed.data.id)
41
+ await enforcementService.deletePolicy(parsed.data.id, {
42
+ tenantId: ctx.auth.tenantId ?? null,
43
+ isSuperAdmin,
44
+ })
40
45
  return { ok: true as const }
41
46
  } catch (error) {
42
47
  if (isEnforcementServiceError(error)) {
@@ -2,6 +2,7 @@ import { z } from 'zod'
2
2
  import { registerCommand } from '@open-mercato/shared/lib/commands'
3
3
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
4
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
5
6
  import type { MfaAdminService } from '../services/MfaAdminService'
6
7
 
7
8
  export const commandId = 'security.admin.mfa.reset'
@@ -36,8 +37,12 @@ registerCommand({
36
37
  }
37
38
 
38
39
  const mfaAdminService = ctx.container.resolve<MfaAdminService>('mfaAdminService')
40
+ const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })
39
41
  try {
40
- await mfaAdminService.resetUserMfa(ctx.auth.sub, parsed.data.userId, parsed.data.reason)
42
+ await mfaAdminService.resetUserMfa(ctx.auth.sub, parsed.data.userId, parsed.data.reason, {
43
+ tenantId: ctx.auth.tenantId ?? null,
44
+ isSuperAdmin,
45
+ })
41
46
  return { ok: true as const }
42
47
  } catch (error) {
43
48
  if (isMfaAdminServiceError(error)) {
@@ -2,6 +2,7 @@ import { z } from 'zod'
2
2
  import { registerCommand } from '@open-mercato/shared/lib/commands'
3
3
  import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
4
4
  import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
5
+ import { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'
5
6
  import { updateEnforcementPolicySchema } from '../data/validators'
6
7
  import type { MfaEnforcementService } from '../services/MfaEnforcementService'
7
8
 
@@ -37,8 +38,12 @@ registerCommand({
37
38
  }
38
39
 
39
40
  const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')
41
+ const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })
40
42
  try {
41
- await enforcementService.updatePolicy(parsed.data.id, parsed.data.data, ctx.auth.sub)
43
+ await enforcementService.updatePolicy(parsed.data.id, parsed.data.data, ctx.auth.sub, {
44
+ tenantId: ctx.auth.tenantId ?? null,
45
+ isSuperAdmin,
46
+ })
42
47
  return { ok: true as const }
43
48
  } catch (error) {
44
49
  if (isEnforcementServiceError(error)) {
@@ -1,6 +1,6 @@
1
- import type { EntityManager } from '@mikro-orm/postgresql'
1
+ import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
2
2
  import { User } from '@open-mercato/core/modules/auth/data/entities'
3
- import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
3
+ import { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
4
4
  import { MfaRecoveryCode, UserMfaMethod } from '../data/entities'
5
5
  import { emitSecurityEvent } from '../events'
6
6
  import type { MfaEnforcementService } from './MfaEnforcementService'
@@ -27,6 +27,11 @@ type BulkComplianceStatus = {
27
27
  lastLoginAt?: Date
28
28
  }
29
29
 
30
+ type ActorContext = {
31
+ tenantId: string | null
32
+ isSuperAdmin: boolean
33
+ }
34
+
30
35
  export class MfaAdminServiceError extends Error {
31
36
  constructor(
32
37
  message: string,
@@ -43,7 +48,7 @@ export class MfaAdminService {
43
48
  private readonly mfaEnforcementService: MfaEnforcementService,
44
49
  ) {}
45
50
 
46
- async resetUserMfa(adminId: string, userId: string, reason: string): Promise<void> {
51
+ async resetUserMfa(adminId: string, userId: string, reason: string, actor?: ActorContext): Promise<void> {
47
52
  if (!adminId.trim()) {
48
53
  throw new MfaAdminServiceError('Admin ID is required', 400)
49
54
  }
@@ -60,6 +65,7 @@ export class MfaAdminService {
60
65
  if (!user) {
61
66
  throw new MfaAdminServiceError('User not found', 404)
62
67
  }
68
+ this.assertActorOwnsUser(user, actor)
63
69
 
64
70
  const activeMethods = await this.em.find(UserMfaMethod, {
65
71
  userId,
@@ -95,7 +101,7 @@ export class MfaAdminService {
95
101
  })
96
102
  }
97
103
 
98
- async getUserMfaStatus(userId: string): Promise<UserMfaStatus> {
104
+ async getUserMfaStatus(userId: string, actor?: ActorContext): Promise<UserMfaStatus> {
99
105
  if (!userId.trim()) {
100
106
  throw new MfaAdminServiceError('User ID is required', 400)
101
107
  }
@@ -104,6 +110,7 @@ export class MfaAdminService {
104
110
  if (!user) {
105
111
  throw new MfaAdminServiceError('User not found', 404)
106
112
  }
113
+ this.assertActorOwnsUser(user, actor)
107
114
 
108
115
  const methods = await this.em.find(
109
116
  UserMfaMethod,
@@ -136,10 +143,13 @@ export class MfaAdminService {
136
143
  }
137
144
  }
138
145
 
139
- async bulkComplianceCheck(tenantId: string): Promise<BulkComplianceStatus[]> {
146
+ async bulkComplianceCheck(tenantId: string, actor?: ActorContext): Promise<BulkComplianceStatus[]> {
140
147
  if (!tenantId.trim()) {
141
148
  throw new MfaAdminServiceError('Tenant ID is required', 400)
142
149
  }
150
+ if (actor && !actor.isSuperAdmin && tenantId !== actor.tenantId) {
151
+ throw new MfaAdminServiceError('Not authorized for the requested tenant scope.', 403)
152
+ }
143
153
 
144
154
  const users = await findWithDecryption(
145
155
  this.em,
@@ -186,8 +196,21 @@ export class MfaAdminService {
186
196
  })
187
197
  }
188
198
 
199
+ private assertActorOwnsUser(user: User, actor?: ActorContext): void {
200
+ if (!actor || actor.isSuperAdmin) return
201
+ if (!user.tenantId || user.tenantId !== actor.tenantId) {
202
+ throw new MfaAdminServiceError('User not found', 404)
203
+ }
204
+ }
205
+
189
206
  private async findUserById(userId: string): Promise<User | null> {
190
- return this.em.findOne(User, { id: userId, deletedAt: null })
207
+ return findOneWithDecryption(
208
+ this.em,
209
+ User,
210
+ { id: userId, deletedAt: null } as FilterQuery<User>,
211
+ {},
212
+ { tenantId: null, organizationId: null },
213
+ )
191
214
  }
192
215
  }
193
216
 
@@ -28,6 +28,11 @@ type EnforcementPolicyListFilters = {
28
28
  scope?: EnforcementScope
29
29
  }
30
30
 
31
+ export type EnforcementActorContext = {
32
+ tenantId: string | null
33
+ isSuperAdmin: boolean
34
+ }
35
+
31
36
  type UserCompliance = {
32
37
  compliant: boolean
33
38
  deadline?: Date
@@ -65,12 +70,21 @@ export class MfaEnforcementService {
65
70
  return { enforced: true, policy }
66
71
  }
67
72
 
68
- async listPolicies(filters?: EnforcementPolicyListFilters): Promise<MfaEnforcementPolicy[]> {
73
+ async getPolicyById(id: string): Promise<MfaEnforcementPolicy | null> {
74
+ return this.em.findOne(MfaEnforcementPolicy, { id, deletedAt: null })
75
+ }
76
+
77
+ async listPolicies(
78
+ filters?: EnforcementPolicyListFilters,
79
+ actor?: EnforcementActorContext,
80
+ ): Promise<MfaEnforcementPolicy[]> {
81
+ const tenantConstraint = actor && !actor.isSuperAdmin ? { tenantId: actor.tenantId } : {}
69
82
  return this.em.find(
70
83
  MfaEnforcementPolicy,
71
84
  {
72
85
  deletedAt: null,
73
86
  ...(filters?.scope ? { scope: filters.scope } : {}),
87
+ ...tenantConstraint,
74
88
  },
75
89
  {
76
90
  orderBy: { updatedAt: 'desc' },
@@ -81,8 +95,10 @@ export class MfaEnforcementService {
81
95
  async getComplianceReport(
82
96
  scope: EnforcementScope,
83
97
  scopeId?: string,
98
+ actor?: EnforcementActorContext,
84
99
  ): Promise<ComplianceReport> {
85
100
  const { tenantId, organizationId } = this.resolveScopeFilters(scope, scopeId)
101
+ this.assertActorOwnsScopeFilters(actor, scope, tenantId)
86
102
  const users = await this.em.find(User, {
87
103
  deletedAt: null,
88
104
  ...(tenantId ? { tenantId } : {}),
@@ -123,8 +139,10 @@ export class MfaEnforcementService {
123
139
  async createPolicy(
124
140
  data: EnforcementPolicyInput,
125
141
  adminId: string,
142
+ actor?: EnforcementActorContext,
126
143
  ): Promise<MfaEnforcementPolicy> {
127
144
  const normalized = this.normalizePolicyInput(data)
145
+ this.assertActorOwnsScopeFilters(actor, normalized.scope, normalized.tenantId)
128
146
  const existing = await this.findPolicyByScope(
129
147
  normalized.scope,
130
148
  normalized.tenantId ?? undefined,
@@ -176,6 +194,7 @@ export class MfaEnforcementService {
176
194
  id: string,
177
195
  data: UpdateEnforcementPolicyInput,
178
196
  adminId: string,
197
+ actor?: EnforcementActorContext,
179
198
  ): Promise<MfaEnforcementPolicy> {
180
199
  const policy = await this.em.findOne(MfaEnforcementPolicy, {
181
200
  id,
@@ -184,6 +203,7 @@ export class MfaEnforcementService {
184
203
  if (!policy) {
185
204
  throw new MfaEnforcementServiceError('Enforcement policy not found', 404)
186
205
  }
206
+ this.assertActorOwnsScopeFilters(actor, policy.scope, policy.tenantId ?? null)
187
207
 
188
208
  const mergedInput = this.normalizePolicyInput({
189
209
  scope: data.scope ?? policy.scope,
@@ -197,6 +217,8 @@ export class MfaEnforcementService {
197
217
  : data.enforcementDeadline,
198
218
  })
199
219
 
220
+ this.assertActorOwnsScopeFilters(actor, mergedInput.scope, mergedInput.tenantId)
221
+
200
222
  if (
201
223
  mergedInput.scope !== policy.scope ||
202
224
  mergedInput.tenantId !== (policy.tenantId ?? null) ||
@@ -231,7 +253,7 @@ export class MfaEnforcementService {
231
253
  return policy
232
254
  }
233
255
 
234
- async deletePolicy(id: string): Promise<void> {
256
+ async deletePolicy(id: string, actor?: EnforcementActorContext): Promise<void> {
235
257
  const policy = await this.em.findOne(MfaEnforcementPolicy, {
236
258
  id,
237
259
  deletedAt: null,
@@ -239,6 +261,7 @@ export class MfaEnforcementService {
239
261
  if (!policy) {
240
262
  throw new MfaEnforcementServiceError('Enforcement policy not found', 404)
241
263
  }
264
+ this.assertActorOwnsScopeFilters(actor, policy.scope, policy.tenantId ?? null)
242
265
 
243
266
  const now = new Date()
244
267
  policy.deletedAt = now
@@ -314,6 +337,23 @@ export class MfaEnforcementService {
314
337
  )
315
338
  }
316
339
 
340
+ private assertActorOwnsScopeFilters(
341
+ actor: EnforcementActorContext | undefined,
342
+ scope: EnforcementScope,
343
+ tenantId: string | null | undefined,
344
+ ): void {
345
+ if (!actor || actor.isSuperAdmin) return
346
+ if (scope === EnforcementScope.PLATFORM) {
347
+ throw new MfaEnforcementServiceError(
348
+ 'Platform scope requires platform administrator privileges.',
349
+ 403,
350
+ )
351
+ }
352
+ if (!tenantId || tenantId !== actor.tenantId) {
353
+ throw new MfaEnforcementServiceError('Not authorized for the requested scope.', 403)
354
+ }
355
+ }
356
+
317
357
  private resolveScopeFilters(
318
358
  scope: EnforcementScope,
319
359
  scopeId?: string,