@open-mercato/enterprise 0.6.5-develop.4863.1.169bfbb3a3 → 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.
- package/dist/modules/security/api/enforcement/[id]/route.js +35 -1
- package/dist/modules/security/api/enforcement/[id]/route.js.map +2 -2
- package/dist/modules/security/api/enforcement/_shared.js +63 -1
- package/dist/modules/security/api/enforcement/_shared.js.map +3 -3
- package/dist/modules/security/api/enforcement/compliance/route.js +12 -3
- package/dist/modules/security/api/enforcement/compliance/route.js.map +2 -2
- package/dist/modules/security/api/enforcement/route.js +25 -2
- package/dist/modules/security/api/enforcement/route.js.map +2 -2
- package/dist/modules/security/api/users/[id]/mfa/reset/route.js +6 -1
- package/dist/modules/security/api/users/[id]/mfa/reset/route.js.map +2 -2
- package/dist/modules/security/api/users/[id]/mfa/status/route.js +13 -2
- package/dist/modules/security/api/users/[id]/mfa/status/route.js.map +2 -2
- package/dist/modules/security/api/users/_shared.js +56 -1
- package/dist/modules/security/api/users/_shared.js.map +2 -2
- package/dist/modules/security/api/users/mfa/compliance/route.js +17 -7
- package/dist/modules/security/api/users/mfa/compliance/route.js.map +2 -2
- package/dist/modules/security/commands/createEnforcementPolicy.js +6 -1
- package/dist/modules/security/commands/createEnforcementPolicy.js.map +2 -2
- package/dist/modules/security/commands/deleteEnforcementPolicy.js +6 -1
- package/dist/modules/security/commands/deleteEnforcementPolicy.js.map +2 -2
- package/dist/modules/security/commands/resetUserMfa.js +6 -1
- package/dist/modules/security/commands/resetUserMfa.js.map +2 -2
- package/dist/modules/security/commands/updateEnforcementPolicy.js +6 -1
- package/dist/modules/security/commands/updateEnforcementPolicy.js.map +2 -2
- package/dist/modules/security/services/MfaAdminService.js +22 -5
- package/dist/modules/security/services/MfaAdminService.js.map +2 -2
- package/dist/modules/security/services/MfaEnforcementService.js +28 -6
- package/dist/modules/security/services/MfaEnforcementService.js.map +2 -2
- package/package.json +5 -5
- package/src/modules/security/api/enforcement/[id]/route.ts +50 -1
- package/src/modules/security/api/enforcement/_shared.ts +83 -2
- package/src/modules/security/api/enforcement/compliance/route.ts +10 -1
- package/src/modules/security/api/enforcement/route.ts +30 -2
- package/src/modules/security/api/users/[id]/mfa/reset/route.ts +6 -1
- package/src/modules/security/api/users/[id]/mfa/status/route.ts +13 -2
- package/src/modules/security/api/users/_shared.ts +69 -1
- package/src/modules/security/api/users/mfa/compliance/route.ts +16 -7
- package/src/modules/security/commands/createEnforcementPolicy.ts +6 -1
- package/src/modules/security/commands/deleteEnforcementPolicy.ts +6 -1
- package/src/modules/security/commands/resetUserMfa.ts +6 -1
- package/src/modules/security/commands/updateEnforcementPolicy.ts +6 -1
- package/src/modules/security/services/MfaAdminService.ts +29 -6
- 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
|
|
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
|
|
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,
|