@open-mercato/enterprise 0.6.4-develop.4371.1.8f3030407e → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js +1 -1
- package/dist/modules/record_locks/widgets/injection/record-locking/widget.client.js.map +2 -2
- 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/mfa/prepare/route.js +1 -1
- package/dist/modules/security/api/mfa/prepare/route.js.map +2 -2
- package/dist/modules/security/api/mfa/recovery/route.js +1 -1
- package/dist/modules/security/api/mfa/recovery/route.js.map +2 -2
- package/dist/modules/security/api/mfa/verify/route.js +1 -1
- package/dist/modules/security/api/mfa/verify/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/dist/modules/security/services/MfaVerificationService.js +30 -10
- package/dist/modules/security/services/MfaVerificationService.js.map +2 -2
- package/dist/modules/security/services/SudoChallengeService.js +14 -3
- package/dist/modules/security/services/SudoChallengeService.js.map +2 -2
- package/dist/modules/sso/api/callback/oidc/route.js +2 -2
- package/dist/modules/sso/api/callback/oidc/route.js.map +2 -2
- package/dist/modules/sso/i18n/de.json +2 -0
- package/dist/modules/sso/i18n/en.json +2 -0
- package/dist/modules/sso/i18n/es.json +2 -0
- package/dist/modules/sso/i18n/pl.json +2 -0
- package/dist/modules/sso/lib/errors.js +21 -0
- package/dist/modules/sso/lib/errors.js.map +7 -0
- package/dist/modules/sso/services/accountLinkingService.js +2 -1
- package/dist/modules/sso/services/accountLinkingService.js.map +2 -2
- package/package.json +7 -8
- package/src/modules/record_locks/widgets/injection/record-locking/widget.client.tsx +1 -1
- 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/mfa/prepare/route.ts +1 -1
- package/src/modules/security/api/mfa/recovery/route.ts +1 -1
- package/src/modules/security/api/mfa/verify/route.ts +1 -1
- 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
- package/src/modules/security/services/MfaVerificationService.ts +33 -10
- package/src/modules/security/services/SudoChallengeService.ts +16 -11
- package/src/modules/sso/api/callback/oidc/route.ts +2 -2
- package/src/modules/sso/i18n/de.json +2 -0
- package/src/modules/sso/i18n/en.json +2 -0
- package/src/modules/sso/i18n/es.json +2 -0
- package/src/modules/sso/i18n/pl.json +2 -0
- package/src/modules/sso/lib/errors.ts +35 -0
- package/src/modules/sso/services/accountLinkingService.ts +2 -1
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { NextResponse } from "next/server";
|
|
2
|
-
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
2
|
+
import { CrudHttpError, forbidden } from "@open-mercato/shared/lib/crud/errors";
|
|
3
3
|
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
|
|
4
4
|
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
|
|
5
|
+
import { findOneWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
6
|
+
import { enforceTenantSelection, resolveIsSuperAdmin } from "@open-mercato/core/modules/auth/lib/tenantAccess";
|
|
7
|
+
import { User } from "@open-mercato/core/modules/auth/data/entities";
|
|
5
8
|
import { isSudoRequiredError } from "../../lib/sudo-middleware.js";
|
|
6
9
|
import { localizeSecurityApiBody, securityApiError } from "../i18n.js";
|
|
7
10
|
async function resolveSecurityUsersContext(req) {
|
|
@@ -24,6 +27,56 @@ async function resolveSecurityUsersContext(req) {
|
|
|
24
27
|
mfaAdminService: container.resolve("mfaAdminService")
|
|
25
28
|
};
|
|
26
29
|
}
|
|
30
|
+
function normalizeNullableString(value) {
|
|
31
|
+
return typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
|
|
32
|
+
}
|
|
33
|
+
function normalizeOrganizationList(values) {
|
|
34
|
+
if (values === null || values === void 0) return null;
|
|
35
|
+
if (!Array.isArray(values)) return null;
|
|
36
|
+
const result = [];
|
|
37
|
+
for (const value of values) {
|
|
38
|
+
if (typeof value !== "string") continue;
|
|
39
|
+
const trimmed = value.trim();
|
|
40
|
+
if (trimmed) result.push(trimmed);
|
|
41
|
+
}
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
async function assertActorCanAccessSecurityUserTarget(ctx, targetUserId) {
|
|
45
|
+
const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container });
|
|
46
|
+
if (isSuperAdmin) return;
|
|
47
|
+
const em = ctx.container.resolve("em");
|
|
48
|
+
const target = await findOneWithDecryption(
|
|
49
|
+
em,
|
|
50
|
+
User,
|
|
51
|
+
{ id: targetUserId, deletedAt: null },
|
|
52
|
+
{},
|
|
53
|
+
{ tenantId: null, organizationId: null }
|
|
54
|
+
);
|
|
55
|
+
if (!target) {
|
|
56
|
+
throw new CrudHttpError(404, { error: "User not found" });
|
|
57
|
+
}
|
|
58
|
+
const actorTenantId = normalizeNullableString(ctx.auth.tenantId);
|
|
59
|
+
const targetTenantId = normalizeNullableString(target.tenantId);
|
|
60
|
+
if (!targetTenantId || targetTenantId !== actorTenantId) {
|
|
61
|
+
throw new CrudHttpError(404, { error: "User not found" });
|
|
62
|
+
}
|
|
63
|
+
const rbacService = ctx.container.resolve("rbacService");
|
|
64
|
+
const acl = await rbacService.loadAcl(ctx.auth.sub, {
|
|
65
|
+
tenantId: actorTenantId,
|
|
66
|
+
organizationId: normalizeNullableString(ctx.auth.orgId)
|
|
67
|
+
});
|
|
68
|
+
const organizations = normalizeOrganizationList(acl?.organizations);
|
|
69
|
+
if (organizations !== null && !organizations.includes("__all__")) {
|
|
70
|
+
const targetOrganizationId = normalizeNullableString(target.organizationId);
|
|
71
|
+
if (!targetOrganizationId || !organizations.includes(targetOrganizationId)) {
|
|
72
|
+
throw forbidden("Not authorized to access this user.");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async function assertActorOwnsTenantScope(ctx, requestedTenantId) {
|
|
77
|
+
const resolved = await enforceTenantSelection({ auth: ctx.auth, container: ctx.container }, requestedTenantId);
|
|
78
|
+
return resolved ?? ctx.auth.tenantId ?? null;
|
|
79
|
+
}
|
|
27
80
|
async function mapSecurityUsersError(error) {
|
|
28
81
|
if (error instanceof CrudHttpError) {
|
|
29
82
|
return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status });
|
|
@@ -41,6 +94,8 @@ function isMfaAdminServiceError(error) {
|
|
|
41
94
|
return error instanceof Error && error.name === "MfaAdminServiceError" && typeof error.statusCode === "number";
|
|
42
95
|
}
|
|
43
96
|
export {
|
|
97
|
+
assertActorCanAccessSecurityUserTarget,
|
|
98
|
+
assertActorOwnsTenantScope,
|
|
44
99
|
mapSecurityUsersError,
|
|
45
100
|
resolveSecurityUsersContext
|
|
46
101
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/security/api/users/_shared.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { isSudoRequiredError } from '../../lib/sudo-middleware'\nimport type { MfaAdminService, MfaAdminServiceError } from '../../services/MfaAdminService'\nimport { localizeSecurityApiBody, securityApiError } from '../i18n'\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\ntype Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>\n\nexport type SecurityUsersRequestContext = {\n auth: Auth\n container: RequestContainer\n commandContext: CommandRuntimeContext\n mfaAdminService: MfaAdminService\n}\n\nexport async function resolveSecurityUsersContext(\n req: Request,\n): Promise<SecurityUsersRequestContext | 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 mfaAdminService: container.resolve<MfaAdminService>('mfaAdminService'),\n }\n}\n\nexport async function mapSecurityUsersError(error: unknown): Promise<NextResponse> {\n if (error instanceof CrudHttpError) {\n return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status })\n }\n if (isSudoRequiredError(error)) {\n return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.statusCode })\n }\n if (isMfaAdminServiceError(error)) {\n return securityApiError(error.statusCode, error.message)\n }\n\n console.error('security.users.route failure', error)\n return securityApiError(500, 'Failed to process user security request.')\n}\n\nfunction isMfaAdminServiceError(error: unknown): error is MfaAdminServiceError {\n return error instanceof Error\n && error.name === 'MfaAdminServiceError'\n && typeof (error as Partial<MfaAdminServiceError>).statusCode === 'number'\n}\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { CrudHttpError, forbidden } from '@open-mercato/shared/lib/crud/errors'\nimport type { CommandRuntimeContext } from '@open-mercato/shared/lib/commands'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { enforceTenantSelection, resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'\nimport { isSudoRequiredError } from '../../lib/sudo-middleware'\nimport type { MfaAdminService, MfaAdminServiceError } from '../../services/MfaAdminService'\nimport { localizeSecurityApiBody, securityApiError } from '../i18n'\n\ntype RequestContainer = Awaited<ReturnType<typeof createRequestContainer>>\ntype Auth = NonNullable<Awaited<ReturnType<typeof getAuthFromRequest>>>\n\nexport type SecurityUsersRequestContext = {\n auth: Auth\n container: RequestContainer\n commandContext: CommandRuntimeContext\n mfaAdminService: MfaAdminService\n}\n\nexport async function resolveSecurityUsersContext(\n req: Request,\n): Promise<SecurityUsersRequestContext | 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 mfaAdminService: container.resolve<MfaAdminService>('mfaAdminService'),\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 assertActorCanAccessSecurityUserTarget(\n ctx: SecurityUsersRequestContext,\n targetUserId: string,\n): Promise<void> {\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n if (isSuperAdmin) return\n\n const em = ctx.container.resolve<EntityManager>('em')\n const target = await findOneWithDecryption(\n em,\n User,\n { id: targetUserId, deletedAt: null } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n if (!target) {\n throw new CrudHttpError(404, { error: 'User not found' })\n }\n\n const actorTenantId = normalizeNullableString(ctx.auth.tenantId)\n const targetTenantId = normalizeNullableString((target as { tenantId?: string | null }).tenantId)\n if (!targetTenantId || targetTenantId !== actorTenantId) {\n throw new CrudHttpError(404, { error: 'User not found' })\n }\n\n const rbacService = ctx.container.resolve<RbacService>('rbacService')\n const acl = await rbacService.loadAcl(ctx.auth.sub, {\n tenantId: actorTenantId,\n organizationId: normalizeNullableString(ctx.auth.orgId),\n })\n const organizations = normalizeOrganizationList(acl?.organizations)\n if (organizations !== null && !organizations.includes('__all__')) {\n const targetOrganizationId = normalizeNullableString((target as { organizationId?: string | null }).organizationId)\n if (!targetOrganizationId || !organizations.includes(targetOrganizationId)) {\n throw forbidden('Not authorized to access this user.')\n }\n }\n}\n\nexport async function assertActorOwnsTenantScope(\n ctx: SecurityUsersRequestContext,\n requestedTenantId: string | null | undefined,\n): Promise<string | null> {\n const resolved = await enforceTenantSelection({ auth: ctx.auth, container: ctx.container }, requestedTenantId)\n return resolved ?? ctx.auth.tenantId ?? null\n}\n\nexport async function mapSecurityUsersError(error: unknown): Promise<NextResponse> {\n if (error instanceof CrudHttpError) {\n return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.status })\n }\n if (isSudoRequiredError(error)) {\n return NextResponse.json(await localizeSecurityApiBody(error.body), { status: error.statusCode })\n }\n if (isMfaAdminServiceError(error)) {\n return securityApiError(error.statusCode, error.message)\n }\n\n console.error('security.users.route failure', error)\n return securityApiError(500, 'Failed to process user security request.')\n}\n\nfunction isMfaAdminServiceError(error: unknown): error is MfaAdminServiceError {\n return error instanceof Error\n && error.name === 'MfaAdminServiceError'\n && typeof (error as Partial<MfaAdminServiceError>).statusCode === 'number'\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -2,7 +2,12 @@ import { NextResponse } from "next/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { buildSecurityOpenApi, securityErrorSchema } from "../../../openapi.js";
|
|
4
4
|
import { securityApiError } from "../../../i18n.js";
|
|
5
|
-
import {
|
|
5
|
+
import { resolveIsSuperAdmin } from "@open-mercato/core/modules/auth/lib/tenantAccess";
|
|
6
|
+
import {
|
|
7
|
+
assertActorOwnsTenantScope,
|
|
8
|
+
mapSecurityUsersError,
|
|
9
|
+
resolveSecurityUsersContext
|
|
10
|
+
} from "../../_shared.js";
|
|
6
11
|
const querySchema = z.object({
|
|
7
12
|
tenantId: z.string().uuid().optional()
|
|
8
13
|
});
|
|
@@ -30,12 +35,16 @@ async function GET(req) {
|
|
|
30
35
|
if (!parsedQuery.success) {
|
|
31
36
|
return securityApiError(400, "Invalid query parameters", { issues: parsedQuery.error.issues });
|
|
32
37
|
}
|
|
33
|
-
const tenantId = parsedQuery.data.tenantId ?? context.auth.tenantId ?? null;
|
|
34
|
-
if (!tenantId) {
|
|
35
|
-
return securityApiError(400, "Tenant context is required.");
|
|
36
|
-
}
|
|
37
38
|
try {
|
|
38
|
-
const
|
|
39
|
+
const tenantId = await assertActorOwnsTenantScope(context, parsedQuery.data.tenantId);
|
|
40
|
+
if (!tenantId) {
|
|
41
|
+
return securityApiError(400, "Tenant context is required.");
|
|
42
|
+
}
|
|
43
|
+
const isSuperAdmin = await resolveIsSuperAdmin({ auth: context.auth, container: context.container });
|
|
44
|
+
const items = await context.mfaAdminService.bulkComplianceCheck(tenantId, {
|
|
45
|
+
tenantId: context.auth.tenantId ?? null,
|
|
46
|
+
isSuperAdmin
|
|
47
|
+
});
|
|
39
48
|
return NextResponse.json({ items });
|
|
40
49
|
} catch (error) {
|
|
41
50
|
return await mapSecurityUsersError(error);
|
|
@@ -52,7 +61,8 @@ const openApi = buildSecurityOpenApi({
|
|
|
52
61
|
],
|
|
53
62
|
errors: [
|
|
54
63
|
{ status: 400, description: "Invalid query or missing tenant context", schema: securityErrorSchema },
|
|
55
|
-
{ status: 401, description: "Unauthorized", schema: securityErrorSchema }
|
|
64
|
+
{ status: 401, description: "Unauthorized", schema: securityErrorSchema },
|
|
65
|
+
{ status: 403, description: "Not authorized for the requested tenant scope", schema: securityErrorSchema }
|
|
56
66
|
]
|
|
57
67
|
}
|
|
58
68
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/security/api/users/mfa/compliance/route.ts"],
|
|
4
|
-
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { buildSecurityOpenApi, securityErrorSchema } from '../../../openapi'\nimport { securityApiError } from '../../../i18n'\nimport { mapSecurityUsersError
|
|
5
|
-
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,sBAAsB,2BAA2B;AAC1D,SAAS,wBAAwB;AACjC,SAAS,
|
|
4
|
+
"sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { buildSecurityOpenApi, securityErrorSchema } from '../../../openapi'\nimport { securityApiError } from '../../../i18n'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport {\n assertActorOwnsTenantScope,\n mapSecurityUsersError,\n resolveSecurityUsersContext,\n} from '../../_shared'\n\nconst querySchema = z.object({\n tenantId: z.string().uuid().optional(),\n})\n\nconst complianceItemSchema = z.object({\n userId: z.string().uuid(),\n email: z.string().email(),\n enrolled: z.boolean(),\n methodCount: z.number().int().nonnegative(),\n compliant: z.boolean(),\n lastLoginAt: z.string().datetime().optional(),\n})\n\nconst complianceListResponseSchema = z.object({\n items: z.array(complianceItemSchema),\n})\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['security.admin.manage'] },\n}\n\nexport async function GET(req: Request) {\n const context = await resolveSecurityUsersContext(req)\n if (context instanceof NextResponse) return context\n\n const url = new URL(req.url)\n const parsedQuery = querySchema.safeParse({\n tenantId: url.searchParams.get('tenantId') ?? undefined,\n })\n if (!parsedQuery.success) {\n return securityApiError(400, 'Invalid query parameters', { issues: parsedQuery.error.issues })\n }\n\n try {\n const tenantId = await assertActorOwnsTenantScope(context, parsedQuery.data.tenantId)\n if (!tenantId) {\n return securityApiError(400, 'Tenant context is required.')\n }\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: context.auth, container: context.container })\n const items = await context.mfaAdminService.bulkComplianceCheck(tenantId, {\n tenantId: context.auth.tenantId ?? null,\n isSuperAdmin,\n })\n return NextResponse.json({ items })\n } catch (error) {\n return await mapSecurityUsersError(error)\n }\n}\n\nexport const openApi = buildSecurityOpenApi({\n summary: 'Admin users MFA compliance routes',\n methods: {\n GET: {\n summary: 'List MFA compliance for tenant users',\n query: querySchema,\n responses: [\n { status: 200, description: 'MFA compliance list', schema: complianceListResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid query or missing tenant context', schema: securityErrorSchema },\n { status: 401, description: 'Unauthorized', schema: securityErrorSchema },\n { status: 403, description: 'Not authorized for the requested tenant scope', schema: securityErrorSchema },\n ],\n },\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,oBAAoB;AAC7B,SAAS,SAAS;AAClB,SAAS,sBAAsB,2BAA2B;AAC1D,SAAS,wBAAwB;AACjC,SAAS,2BAA2B;AACpC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAEP,MAAM,cAAc,EAAE,OAAO;AAAA,EAC3B,UAAU,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS;AACvC,CAAC;AAED,MAAM,uBAAuB,EAAE,OAAO;AAAA,EACpC,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,OAAO,EAAE,OAAO,EAAE,MAAM;AAAA,EACxB,UAAU,EAAE,QAAQ;AAAA,EACpB,aAAa,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY;AAAA,EAC1C,WAAW,EAAE,QAAQ;AAAA,EACrB,aAAa,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAC9C,CAAC;AAED,MAAM,+BAA+B,EAAE,OAAO;AAAA,EAC5C,OAAO,EAAE,MAAM,oBAAoB;AACrC,CAAC;AAEM,MAAM,WAAW;AAAA,EACtB,KAAK,EAAE,aAAa,MAAM,iBAAiB,CAAC,uBAAuB,EAAE;AACvE;AAEA,eAAsB,IAAI,KAAc;AACtC,QAAM,UAAU,MAAM,4BAA4B,GAAG;AACrD,MAAI,mBAAmB,aAAc,QAAO;AAE5C,QAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAC3B,QAAM,cAAc,YAAY,UAAU;AAAA,IACxC,UAAU,IAAI,aAAa,IAAI,UAAU,KAAK;AAAA,EAChD,CAAC;AACD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,iBAAiB,KAAK,4BAA4B,EAAE,QAAQ,YAAY,MAAM,OAAO,CAAC;AAAA,EAC/F;AAEA,MAAI;AACF,UAAM,WAAW,MAAM,2BAA2B,SAAS,YAAY,KAAK,QAAQ;AACpF,QAAI,CAAC,UAAU;AACb,aAAO,iBAAiB,KAAK,6BAA6B;AAAA,IAC5D;AACA,UAAM,eAAe,MAAM,oBAAoB,EAAE,MAAM,QAAQ,MAAM,WAAW,QAAQ,UAAU,CAAC;AACnG,UAAM,QAAQ,MAAM,QAAQ,gBAAgB,oBAAoB,UAAU;AAAA,MACxE,UAAU,QAAQ,KAAK,YAAY;AAAA,MACnC;AAAA,IACF,CAAC;AACD,WAAO,aAAa,KAAK,EAAE,MAAM,CAAC;AAAA,EACpC,SAAS,OAAO;AACd,WAAO,MAAM,sBAAsB,KAAK;AAAA,EAC1C;AACF;AAEO,MAAM,UAAU,qBAAqB;AAAA,EAC1C,SAAS;AAAA,EACT,SAAS;AAAA,IACP,KAAK;AAAA,MACH,SAAS;AAAA,MACT,OAAO;AAAA,MACP,WAAW;AAAA,QACT,EAAE,QAAQ,KAAK,aAAa,uBAAuB,QAAQ,6BAA6B;AAAA,MAC1F;AAAA,MACA,QAAQ;AAAA,QACN,EAAE,QAAQ,KAAK,aAAa,2CAA2C,QAAQ,oBAAoB;AAAA,QACnG,EAAE,QAAQ,KAAK,aAAa,gBAAgB,QAAQ,oBAAoB;AAAA,QACxE,EAAE,QAAQ,KAAK,aAAa,iDAAiD,QAAQ,oBAAoB;AAAA,MAC3G;AAAA,IACF;AAAA,EACF;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { registerCommand } from "@open-mercato/shared/lib/commands";
|
|
2
2
|
import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
|
|
3
3
|
import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
|
|
4
|
+
import { resolveIsSuperAdmin } from "@open-mercato/core/modules/auth/lib/tenantAccess";
|
|
4
5
|
import { enforcementPolicySchema } from "../data/validators.js";
|
|
5
6
|
const commandId = "security.enforcement.create";
|
|
6
7
|
const commandSchema = enforcementPolicySchema;
|
|
@@ -20,8 +21,12 @@ registerCommand({
|
|
|
20
21
|
throw new CrudHttpError(400, { error: "Invalid payload", issues: parsed.error.issues });
|
|
21
22
|
}
|
|
22
23
|
const enforcementService = ctx.container.resolve("mfaEnforcementService");
|
|
24
|
+
const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container });
|
|
23
25
|
try {
|
|
24
|
-
const policy = await enforcementService.createPolicy(parsed.data, ctx.auth.sub
|
|
26
|
+
const policy = await enforcementService.createPolicy(parsed.data, ctx.auth.sub, {
|
|
27
|
+
tenantId: ctx.auth.tenantId ?? null,
|
|
28
|
+
isSuperAdmin
|
|
29
|
+
});
|
|
25
30
|
return { id: policy.id };
|
|
26
31
|
} catch (error) {
|
|
27
32
|
if (isEnforcementServiceError(error)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/commands/createEnforcementPolicy.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { enforcementPolicySchema } from '../data/validators'\nimport type { MfaEnforcementService } from '../services/MfaEnforcementService'\n\nexport const commandId = 'security.enforcement.create'\n\nconst commandSchema = enforcementPolicySchema\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype EnforcementServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isEnforcementServiceError(error: unknown): error is EnforcementServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<EnforcementServiceErrorLike>\n return error.name === 'MfaEnforcementServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')\n try {\n const policy = await enforcementService.createPolicy(parsed.data, ctx.auth.sub)\n return { id: policy.id }\n } catch (error) {\n if (isEnforcementServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, result, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n const commandResult = result as { id: string }\n return {\n actionLabel: translate('security.audit.enforcement.create', 'Create enforcement policy'),\n resourceKind: 'security.enforcement_policy',\n resourceId: commandResult.id,\n tenantId: payload.tenantId ?? null,\n organizationId: payload.organizationId ?? null,\n actorUserId: ctx.auth?.sub ?? null,\n payload,\n context: {\n source: 'security.enforcement',\n },\n }\n },\n})\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAGjC,MAAM,YAAY;AAEzB,MAAM,gBAAgB;AAQtB,SAAS,0BAA0B,OAAsD;AACvF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,gCAAgC,OAAO,MAAM,eAAe;AACpF;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,qBAAqB,IAAI,UAAU,QAA+B,uBAAuB;AAC/F,QAAI;AACF,YAAM,SAAS,MAAM,mBAAmB,aAAa,OAAO,MAAM,IAAI,KAAK,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { enforcementPolicySchema } from '../data/validators'\nimport type { MfaEnforcementService } from '../services/MfaEnforcementService'\n\nexport const commandId = 'security.enforcement.create'\n\nconst commandSchema = enforcementPolicySchema\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype EnforcementServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isEnforcementServiceError(error: unknown): error is EnforcementServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<EnforcementServiceErrorLike>\n return error.name === 'MfaEnforcementServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n try {\n const policy = await enforcementService.createPolicy(parsed.data, ctx.auth.sub, {\n tenantId: ctx.auth.tenantId ?? null,\n isSuperAdmin,\n })\n return { id: policy.id }\n } catch (error) {\n if (isEnforcementServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, result, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n const commandResult = result as { id: string }\n return {\n actionLabel: translate('security.audit.enforcement.create', 'Create enforcement policy'),\n resourceKind: 'security.enforcement_policy',\n resourceId: commandResult.id,\n tenantId: payload.tenantId ?? null,\n organizationId: payload.organizationId ?? null,\n actorUserId: ctx.auth?.sub ?? null,\n payload,\n context: {\n source: 'security.enforcement',\n },\n }\n },\n})\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,+BAA+B;AAGjC,MAAM,YAAY;AAEzB,MAAM,gBAAgB;AAQtB,SAAS,0BAA0B,OAAsD;AACvF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,gCAAgC,OAAO,MAAM,eAAe;AACpF;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,qBAAqB,IAAI,UAAU,QAA+B,uBAAuB;AAC/F,UAAM,eAAe,MAAM,oBAAoB,EAAE,MAAM,IAAI,MAAM,WAAW,IAAI,UAAU,CAAC;AAC3F,QAAI;AACF,YAAM,SAAS,MAAM,mBAAmB,aAAa,OAAO,MAAM,IAAI,KAAK,KAAK;AAAA,QAC9E,UAAU,IAAI,KAAK,YAAY;AAAA,QAC/B;AAAA,MACF,CAAC;AACD,aAAO,EAAE,IAAI,OAAO,GAAG;AAAA,IACzB,SAAS,OAAO;AACd,UAAI,0BAA0B,KAAK,GAAG;AACpC,cAAM,IAAI,cAAc,MAAM,YAAY,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,MACpE;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,SAAS,EAAE,OAAO,QAAQ,IAAI,GAAG;AACrC,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,UAAU;AAChB,UAAM,gBAAgB;AACtB,WAAO;AAAA,MACL,aAAa,UAAU,qCAAqC,2BAA2B;AAAA,MACvF,cAAc;AAAA,MACd,YAAY,cAAc;AAAA,MAC1B,UAAU,QAAQ,YAAY;AAAA,MAC9B,gBAAgB,QAAQ,kBAAkB;AAAA,MAC1C,aAAa,IAAI,MAAM,OAAO;AAAA,MAC9B;AAAA,MACA,SAAS;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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
|
const commandId = "security.enforcement.delete";
|
|
6
7
|
const commandSchema = z.object({
|
|
7
8
|
id: z.string().uuid()
|
|
@@ -22,8 +23,12 @@ registerCommand({
|
|
|
22
23
|
throw new CrudHttpError(400, { error: "Invalid payload", issues: parsed.error.issues });
|
|
23
24
|
}
|
|
24
25
|
const enforcementService = ctx.container.resolve("mfaEnforcementService");
|
|
26
|
+
const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container });
|
|
25
27
|
try {
|
|
26
|
-
await enforcementService.deletePolicy(parsed.data.id
|
|
28
|
+
await enforcementService.deletePolicy(parsed.data.id, {
|
|
29
|
+
tenantId: ctx.auth.tenantId ?? null,
|
|
30
|
+
isSuperAdmin
|
|
31
|
+
});
|
|
27
32
|
return { ok: true };
|
|
28
33
|
} catch (error) {
|
|
29
34
|
if (isEnforcementServiceError(error)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/commands/deleteEnforcementPolicy.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { MfaEnforcementService } from '../services/MfaEnforcementService'\n\nexport const commandId = 'security.enforcement.delete'\n\nconst commandSchema = z.object({\n id: z.string().uuid(),\n})\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype EnforcementServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isEnforcementServiceError(error: unknown): error is EnforcementServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<EnforcementServiceErrorLike>\n return error.name === 'MfaEnforcementServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')\n try {\n await enforcementService.deletePolicy(parsed.data.id)\n return { ok: true as const }\n } catch (error) {\n if (isEnforcementServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n return {\n actionLabel: translate('security.audit.enforcement.delete', 'Delete enforcement policy'),\n resourceKind: 'security.enforcement_policy',\n resourceId: payload.id,\n actorUserId: ctx.auth?.sub ?? null,\n payload,\n context: {\n source: 'security.enforcement',\n },\n }\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AAG7B,MAAM,YAAY;AAEzB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC;AAQD,SAAS,0BAA0B,OAAsD;AACvF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,gCAAgC,OAAO,MAAM,eAAe;AACpF;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,qBAAqB,IAAI,UAAU,QAA+B,uBAAuB;AAC/F,QAAI;AACF,YAAM,mBAAmB,aAAa,OAAO,KAAK,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { MfaEnforcementService } from '../services/MfaEnforcementService'\n\nexport const commandId = 'security.enforcement.delete'\n\nconst commandSchema = z.object({\n id: z.string().uuid(),\n})\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype EnforcementServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isEnforcementServiceError(error: unknown): error is EnforcementServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<EnforcementServiceErrorLike>\n return error.name === 'MfaEnforcementServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n try {\n await enforcementService.deletePolicy(parsed.data.id, {\n tenantId: ctx.auth.tenantId ?? null,\n isSuperAdmin,\n })\n return { ok: true as const }\n } catch (error) {\n if (isEnforcementServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n return {\n actionLabel: translate('security.audit.enforcement.delete', 'Delete enforcement policy'),\n resourceKind: 'security.enforcement_policy',\n resourceId: payload.id,\n actorUserId: ctx.auth?.sub ?? null,\n payload,\n context: {\n source: 'security.enforcement',\n },\n }\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AAG7B,MAAM,YAAY;AAEzB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,IAAI,EAAE,OAAO,EAAE,KAAK;AACtB,CAAC;AAQD,SAAS,0BAA0B,OAAsD;AACvF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,gCAAgC,OAAO,MAAM,eAAe;AACpF;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,qBAAqB,IAAI,UAAU,QAA+B,uBAAuB;AAC/F,UAAM,eAAe,MAAM,oBAAoB,EAAE,MAAM,IAAI,MAAM,WAAW,IAAI,UAAU,CAAC;AAC3F,QAAI;AACF,YAAM,mBAAmB,aAAa,OAAO,KAAK,IAAI;AAAA,QACpD,UAAU,IAAI,KAAK,YAAY;AAAA,QAC/B;AAAA,MACF,CAAC;AACD,aAAO,EAAE,IAAI,KAAc;AAAA,IAC7B,SAAS,OAAO;AACd,UAAI,0BAA0B,KAAK,GAAG;AACpC,cAAM,IAAI,cAAc,MAAM,YAAY,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,MACpE;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,SAAS,EAAE,OAAO,IAAI,GAAG;AAC7B,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,UAAU;AAChB,WAAO;AAAA,MACL,aAAa,UAAU,qCAAqC,2BAA2B;AAAA,MACvF,cAAc;AAAA,MACd,YAAY,QAAQ;AAAA,MACpB,aAAa,IAAI,MAAM,OAAO;AAAA,MAC9B;AAAA,MACA,SAAS;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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
|
const commandId = "security.admin.mfa.reset";
|
|
6
7
|
const commandSchema = z.object({
|
|
7
8
|
userId: z.string().uuid(),
|
|
@@ -23,8 +24,12 @@ registerCommand({
|
|
|
23
24
|
throw new CrudHttpError(400, { error: "Invalid payload", issues: parsed.error.issues });
|
|
24
25
|
}
|
|
25
26
|
const mfaAdminService = ctx.container.resolve("mfaAdminService");
|
|
27
|
+
const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container });
|
|
26
28
|
try {
|
|
27
|
-
await mfaAdminService.resetUserMfa(ctx.auth.sub, parsed.data.userId, parsed.data.reason
|
|
29
|
+
await mfaAdminService.resetUserMfa(ctx.auth.sub, parsed.data.userId, parsed.data.reason, {
|
|
30
|
+
tenantId: ctx.auth.tenantId ?? null,
|
|
31
|
+
isSuperAdmin
|
|
32
|
+
});
|
|
28
33
|
return { ok: true };
|
|
29
34
|
} catch (error) {
|
|
30
35
|
if (isMfaAdminServiceError(error)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/commands/resetUserMfa.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { MfaAdminService } from '../services/MfaAdminService'\n\nexport const commandId = 'security.admin.mfa.reset'\n\nconst commandSchema = z.object({\n userId: z.string().uuid(),\n reason: z.string().min(1),\n})\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype MfaAdminServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isMfaAdminServiceError(error: unknown): error is MfaAdminServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<MfaAdminServiceErrorLike>\n return error.name === 'MfaAdminServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const mfaAdminService = ctx.container.resolve<MfaAdminService>('mfaAdminService')\n try {\n await mfaAdminService.resetUserMfa(ctx.auth.sub, parsed.data.userId, parsed.data.reason)\n return { ok: true as const }\n } catch (error) {\n if (isMfaAdminServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n return {\n actionLabel: translate('security.audit.mfa.reset', 'Reset user MFA'),\n resourceKind: 'security.user_mfa',\n resourceId: payload.userId,\n actorUserId: ctx.auth?.sub ?? null,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n payload,\n context: {\n source: 'security.admin.users',\n },\n }\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AAG7B,MAAM,YAAY;AAEzB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAC1B,CAAC;AAQD,SAAS,uBAAuB,OAAmD;AACjF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,0BAA0B,OAAO,MAAM,eAAe;AAC9E;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,kBAAkB,IAAI,UAAU,QAAyB,iBAAiB;AAChF,QAAI;AACF,YAAM,gBAAgB,aAAa,IAAI,KAAK,KAAK,OAAO,KAAK,QAAQ,OAAO,KAAK,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport type { MfaAdminService } from '../services/MfaAdminService'\n\nexport const commandId = 'security.admin.mfa.reset'\n\nconst commandSchema = z.object({\n userId: z.string().uuid(),\n reason: z.string().min(1),\n})\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype MfaAdminServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isMfaAdminServiceError(error: unknown): error is MfaAdminServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<MfaAdminServiceErrorLike>\n return error.name === 'MfaAdminServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const mfaAdminService = ctx.container.resolve<MfaAdminService>('mfaAdminService')\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n try {\n await mfaAdminService.resetUserMfa(ctx.auth.sub, parsed.data.userId, parsed.data.reason, {\n tenantId: ctx.auth.tenantId ?? null,\n isSuperAdmin,\n })\n return { ok: true as const }\n } catch (error) {\n if (isMfaAdminServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n return {\n actionLabel: translate('security.audit.mfa.reset', 'Reset user MFA'),\n resourceKind: 'security.user_mfa',\n resourceId: payload.userId,\n actorUserId: ctx.auth?.sub ?? null,\n tenantId: ctx.auth?.tenantId ?? null,\n organizationId: ctx.auth?.orgId ?? null,\n payload,\n context: {\n source: 'security.admin.users',\n },\n }\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AAG7B,MAAM,YAAY;AAEzB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,QAAQ,EAAE,OAAO,EAAE,KAAK;AAAA,EACxB,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAC1B,CAAC;AAQD,SAAS,uBAAuB,OAAmD;AACjF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,0BAA0B,OAAO,MAAM,eAAe;AAC9E;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,kBAAkB,IAAI,UAAU,QAAyB,iBAAiB;AAChF,UAAM,eAAe,MAAM,oBAAoB,EAAE,MAAM,IAAI,MAAM,WAAW,IAAI,UAAU,CAAC;AAC3F,QAAI;AACF,YAAM,gBAAgB,aAAa,IAAI,KAAK,KAAK,OAAO,KAAK,QAAQ,OAAO,KAAK,QAAQ;AAAA,QACvF,UAAU,IAAI,KAAK,YAAY;AAAA,QAC/B;AAAA,MACF,CAAC;AACD,aAAO,EAAE,IAAI,KAAc;AAAA,IAC7B,SAAS,OAAO;AACd,UAAI,uBAAuB,KAAK,GAAG;AACjC,cAAM,IAAI,cAAc,MAAM,YAAY,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,MACpE;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,SAAS,EAAE,OAAO,IAAI,GAAG;AAC7B,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,UAAU;AAChB,WAAO;AAAA,MACL,aAAa,UAAU,4BAA4B,gBAAgB;AAAA,MACnE,cAAc;AAAA,MACd,YAAY,QAAQ;AAAA,MACpB,aAAa,IAAI,MAAM,OAAO;AAAA,MAC9B,UAAU,IAAI,MAAM,YAAY;AAAA,MAChC,gBAAgB,IAAI,MAAM,SAAS;AAAA,MACnC;AAAA,MACA,SAAS;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -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.js";
|
|
6
7
|
const commandId = "security.enforcement.update";
|
|
7
8
|
const commandSchema = z.object({
|
|
@@ -24,8 +25,12 @@ registerCommand({
|
|
|
24
25
|
throw new CrudHttpError(400, { error: "Invalid payload", issues: parsed.error.issues });
|
|
25
26
|
}
|
|
26
27
|
const enforcementService = ctx.container.resolve("mfaEnforcementService");
|
|
28
|
+
const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container });
|
|
27
29
|
try {
|
|
28
|
-
await enforcementService.updatePolicy(parsed.data.id, parsed.data.data, ctx.auth.sub
|
|
30
|
+
await enforcementService.updatePolicy(parsed.data.id, parsed.data.data, ctx.auth.sub, {
|
|
31
|
+
tenantId: ctx.auth.tenantId ?? null,
|
|
32
|
+
isSuperAdmin
|
|
33
|
+
});
|
|
29
34
|
return { ok: true };
|
|
30
35
|
} catch (error) {
|
|
31
36
|
if (isEnforcementServiceError(error)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/commands/updateEnforcementPolicy.ts"],
|
|
4
|
-
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { updateEnforcementPolicySchema } from '../data/validators'\nimport type { MfaEnforcementService } from '../services/MfaEnforcementService'\n\nexport const commandId = 'security.enforcement.update'\n\nconst commandSchema = z.object({\n id: z.string().uuid(),\n data: updateEnforcementPolicySchema,\n})\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype EnforcementServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isEnforcementServiceError(error: unknown): error is EnforcementServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<EnforcementServiceErrorLike>\n return error.name === 'MfaEnforcementServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')\n try {\n await enforcementService.updatePolicy(parsed.data.id, parsed.data.data, ctx.auth.sub)\n return { ok: true as const }\n } catch (error) {\n if (isEnforcementServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n return {\n actionLabel: translate('security.audit.enforcement.update', 'Update enforcement policy'),\n resourceKind: 'security.enforcement_policy',\n resourceId: payload.id,\n tenantId: payload.data.tenantId ?? null,\n organizationId: payload.data.organizationId ?? null,\n actorUserId: ctx.auth?.sub ?? null,\n payload,\n context: {\n source: 'security.enforcement',\n },\n }\n },\n})\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,qCAAqC;AAGvC,MAAM,YAAY;AAEzB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,MAAM;AACR,CAAC;AAQD,SAAS,0BAA0B,OAAsD;AACvF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,gCAAgC,OAAO,MAAM,eAAe;AACpF;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,qBAAqB,IAAI,UAAU,QAA+B,uBAAuB;AAC/F,QAAI;AACF,YAAM,mBAAmB,aAAa,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,KAAK,
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\nimport { registerCommand } from '@open-mercato/shared/lib/commands'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveIsSuperAdmin } from '@open-mercato/core/modules/auth/lib/tenantAccess'\nimport { updateEnforcementPolicySchema } from '../data/validators'\nimport type { MfaEnforcementService } from '../services/MfaEnforcementService'\n\nexport const commandId = 'security.enforcement.update'\n\nconst commandSchema = z.object({\n id: z.string().uuid(),\n data: updateEnforcementPolicySchema,\n})\n\ntype CommandInput = z.infer<typeof commandSchema>\n\ntype EnforcementServiceErrorLike = Error & {\n statusCode: number\n}\n\nfunction isEnforcementServiceError(error: unknown): error is EnforcementServiceErrorLike {\n if (!(error instanceof Error)) return false\n const maybe = error as Partial<EnforcementServiceErrorLike>\n return error.name === 'MfaEnforcementServiceError' && typeof maybe.statusCode === 'number'\n}\n\nregisterCommand({\n id: commandId,\n async execute(rawInput, ctx) {\n if (!ctx.auth?.sub) {\n throw new CrudHttpError(401, { error: 'Unauthorized' })\n }\n\n const parsed = commandSchema.safeParse(rawInput)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: 'Invalid payload', issues: parsed.error.issues })\n }\n\n const enforcementService = ctx.container.resolve<MfaEnforcementService>('mfaEnforcementService')\n const isSuperAdmin = await resolveIsSuperAdmin({ auth: ctx.auth, container: ctx.container })\n try {\n await enforcementService.updatePolicy(parsed.data.id, parsed.data.data, ctx.auth.sub, {\n tenantId: ctx.auth.tenantId ?? null,\n isSuperAdmin,\n })\n return { ok: true as const }\n } catch (error) {\n if (isEnforcementServiceError(error)) {\n throw new CrudHttpError(error.statusCode, { error: error.message })\n }\n throw error\n }\n },\n async buildLog({ input, ctx }) {\n const { translate } = await resolveTranslations()\n const payload = input as CommandInput\n return {\n actionLabel: translate('security.audit.enforcement.update', 'Update enforcement policy'),\n resourceKind: 'security.enforcement_policy',\n resourceId: payload.id,\n tenantId: payload.data.tenantId ?? null,\n organizationId: payload.data.organizationId ?? null,\n actorUserId: ctx.auth?.sub ?? null,\n payload,\n context: {\n source: 'security.enforcement',\n },\n }\n },\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAClB,SAAS,uBAAuB;AAChC,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,2BAA2B;AACpC,SAAS,qCAAqC;AAGvC,MAAM,YAAY;AAEzB,MAAM,gBAAgB,EAAE,OAAO;AAAA,EAC7B,IAAI,EAAE,OAAO,EAAE,KAAK;AAAA,EACpB,MAAM;AACR,CAAC;AAQD,SAAS,0BAA0B,OAAsD;AACvF,MAAI,EAAE,iBAAiB,OAAQ,QAAO;AACtC,QAAM,QAAQ;AACd,SAAO,MAAM,SAAS,gCAAgC,OAAO,MAAM,eAAe;AACpF;AAEA,gBAAgB;AAAA,EACd,IAAI;AAAA,EACJ,MAAM,QAAQ,UAAU,KAAK;AAC3B,QAAI,CAAC,IAAI,MAAM,KAAK;AAClB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,IACxD;AAEA,UAAM,SAAS,cAAc,UAAU,QAAQ;AAC/C,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,IAAI,cAAc,KAAK,EAAE,OAAO,mBAAmB,QAAQ,OAAO,MAAM,OAAO,CAAC;AAAA,IACxF;AAEA,UAAM,qBAAqB,IAAI,UAAU,QAA+B,uBAAuB;AAC/F,UAAM,eAAe,MAAM,oBAAoB,EAAE,MAAM,IAAI,MAAM,WAAW,IAAI,UAAU,CAAC;AAC3F,QAAI;AACF,YAAM,mBAAmB,aAAa,OAAO,KAAK,IAAI,OAAO,KAAK,MAAM,IAAI,KAAK,KAAK;AAAA,QACpF,UAAU,IAAI,KAAK,YAAY;AAAA,QAC/B;AAAA,MACF,CAAC;AACD,aAAO,EAAE,IAAI,KAAc;AAAA,IAC7B,SAAS,OAAO;AACd,UAAI,0BAA0B,KAAK,GAAG;AACpC,cAAM,IAAI,cAAc,MAAM,YAAY,EAAE,OAAO,MAAM,QAAQ,CAAC;AAAA,MACpE;AACA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EACA,MAAM,SAAS,EAAE,OAAO,IAAI,GAAG;AAC7B,UAAM,EAAE,UAAU,IAAI,MAAM,oBAAoB;AAChD,UAAM,UAAU;AAChB,WAAO;AAAA,MACL,aAAa,UAAU,qCAAqC,2BAA2B;AAAA,MACvF,cAAc;AAAA,MACd,YAAY,QAAQ;AAAA,MACpB,UAAU,QAAQ,KAAK,YAAY;AAAA,MACnC,gBAAgB,QAAQ,KAAK,kBAAkB;AAAA,MAC/C,aAAa,IAAI,MAAM,OAAO;AAAA,MAC9B;AAAA,MACA,SAAS;AAAA,QACP,QAAQ;AAAA,MACV;AAAA,IACF;AAAA,EACF;AACF,CAAC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { User } from "@open-mercato/core/modules/auth/data/entities";
|
|
2
|
-
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { findOneWithDecryption, findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
3
3
|
import { MfaRecoveryCode, UserMfaMethod } from "../data/entities.js";
|
|
4
4
|
import { emitSecurityEvent } from "../events.js";
|
|
5
5
|
class MfaAdminServiceError extends Error {
|
|
@@ -14,7 +14,7 @@ class MfaAdminService {
|
|
|
14
14
|
this.em = em;
|
|
15
15
|
this.mfaEnforcementService = mfaEnforcementService;
|
|
16
16
|
}
|
|
17
|
-
async resetUserMfa(adminId, userId, reason) {
|
|
17
|
+
async resetUserMfa(adminId, userId, reason, actor) {
|
|
18
18
|
if (!adminId.trim()) {
|
|
19
19
|
throw new MfaAdminServiceError("Admin ID is required", 400);
|
|
20
20
|
}
|
|
@@ -29,6 +29,7 @@ class MfaAdminService {
|
|
|
29
29
|
if (!user) {
|
|
30
30
|
throw new MfaAdminServiceError("User not found", 404);
|
|
31
31
|
}
|
|
32
|
+
this.assertActorOwnsUser(user, actor);
|
|
32
33
|
const activeMethods = await this.em.find(UserMfaMethod, {
|
|
33
34
|
userId,
|
|
34
35
|
isActive: true,
|
|
@@ -60,7 +61,7 @@ class MfaAdminService {
|
|
|
60
61
|
resetAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
61
62
|
});
|
|
62
63
|
}
|
|
63
|
-
async getUserMfaStatus(userId) {
|
|
64
|
+
async getUserMfaStatus(userId, actor) {
|
|
64
65
|
if (!userId.trim()) {
|
|
65
66
|
throw new MfaAdminServiceError("User ID is required", 400);
|
|
66
67
|
}
|
|
@@ -68,6 +69,7 @@ class MfaAdminService {
|
|
|
68
69
|
if (!user) {
|
|
69
70
|
throw new MfaAdminServiceError("User not found", 404);
|
|
70
71
|
}
|
|
72
|
+
this.assertActorOwnsUser(user, actor);
|
|
71
73
|
const methods = await this.em.find(
|
|
72
74
|
UserMfaMethod,
|
|
73
75
|
{
|
|
@@ -95,10 +97,13 @@ class MfaAdminService {
|
|
|
95
97
|
compliant: compliance.compliant
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
|
-
async bulkComplianceCheck(tenantId) {
|
|
100
|
+
async bulkComplianceCheck(tenantId, actor) {
|
|
99
101
|
if (!tenantId.trim()) {
|
|
100
102
|
throw new MfaAdminServiceError("Tenant ID is required", 400);
|
|
101
103
|
}
|
|
104
|
+
if (actor && !actor.isSuperAdmin && tenantId !== actor.tenantId) {
|
|
105
|
+
throw new MfaAdminServiceError("Not authorized for the requested tenant scope.", 403);
|
|
106
|
+
}
|
|
102
107
|
const users = await findWithDecryption(
|
|
103
108
|
this.em,
|
|
104
109
|
User,
|
|
@@ -138,8 +143,20 @@ class MfaAdminService {
|
|
|
138
143
|
};
|
|
139
144
|
});
|
|
140
145
|
}
|
|
146
|
+
assertActorOwnsUser(user, actor) {
|
|
147
|
+
if (!actor || actor.isSuperAdmin) return;
|
|
148
|
+
if (!user.tenantId || user.tenantId !== actor.tenantId) {
|
|
149
|
+
throw new MfaAdminServiceError("User not found", 404);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
141
152
|
async findUserById(userId) {
|
|
142
|
-
return
|
|
153
|
+
return findOneWithDecryption(
|
|
154
|
+
this.em,
|
|
155
|
+
User,
|
|
156
|
+
{ id: userId, deletedAt: null },
|
|
157
|
+
{},
|
|
158
|
+
{ tenantId: null, organizationId: null }
|
|
159
|
+
);
|
|
143
160
|
}
|
|
144
161
|
}
|
|
145
162
|
var MfaAdminService_default = MfaAdminService;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/modules/security/services/MfaAdminService.ts"],
|
|
4
|
-
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { MfaRecoveryCode, UserMfaMethod } from '../data/entities'\nimport { emitSecurityEvent } from '../events'\nimport type { MfaEnforcementService } from './MfaEnforcementService'\n\ntype MfaMethodStatus = {\n type: string\n label?: string\n lastUsed?: Date\n}\n\ntype UserMfaStatus = {\n enrolled: boolean\n methods: MfaMethodStatus[]\n recoveryCodesRemaining: number\n compliant: boolean\n}\n\ntype BulkComplianceStatus = {\n userId: string\n email: string\n enrolled: boolean\n methodCount: number\n compliant: boolean\n lastLoginAt?: Date\n}\n\nexport class MfaAdminServiceError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'MfaAdminServiceError'\n }\n}\n\nexport class MfaAdminService {\n constructor(\n private readonly em: EntityManager,\n private readonly mfaEnforcementService: MfaEnforcementService,\n ) {}\n\n async resetUserMfa(adminId: string, userId: string, reason: string): Promise<void> {\n if (!adminId.trim()) {\n throw new MfaAdminServiceError('Admin ID is required', 400)\n }\n if (!userId.trim()) {\n throw new MfaAdminServiceError('User ID is required', 400)\n }\n\n const normalizedReason = reason.trim()\n if (!normalizedReason) {\n throw new MfaAdminServiceError('Reset reason is required', 400)\n }\n\n const user = await this.findUserById(userId)\n if (!user) {\n throw new MfaAdminServiceError('User not found', 404)\n }\n\n const activeMethods = await this.em.find(UserMfaMethod, {\n userId,\n isActive: true,\n deletedAt: null,\n })\n const activeRecoveryCodes = await this.em.find(MfaRecoveryCode, {\n userId,\n isUsed: false,\n })\n\n const now = new Date()\n for (const method of activeMethods) {\n method.isActive = false\n method.deletedAt = now\n method.updatedAt = now\n }\n for (const recoveryCode of activeRecoveryCodes) {\n recoveryCode.isUsed = true\n recoveryCode.usedAt = now\n }\n await this.em.flush()\n\n await emitSecurityEvent('security.mfa.reset', {\n adminId,\n targetUserId: userId,\n tenantId: user.tenantId,\n organizationId: user.organizationId ?? null,\n reason: normalizedReason,\n methodCount: activeMethods.length,\n recoveryCodesInvalidated: activeRecoveryCodes.length,\n resetAt: new Date().toISOString(),\n })\n }\n\n async getUserMfaStatus(userId: string): Promise<UserMfaStatus> {\n if (!userId.trim()) {\n throw new MfaAdminServiceError('User ID is required', 400)\n }\n\n const user = await this.findUserById(userId)\n if (!user) {\n throw new MfaAdminServiceError('User not found', 404)\n }\n\n const methods = await this.em.find(\n UserMfaMethod,\n {\n userId,\n isActive: true,\n deletedAt: null,\n },\n {\n orderBy: { createdAt: 'desc' },\n },\n )\n\n const recoveryCodesRemaining = await this.em.count(MfaRecoveryCode, {\n userId,\n isUsed: false,\n })\n\n const compliance = await this.mfaEnforcementService.checkUserCompliance(userId)\n\n return {\n enrolled: methods.length > 0,\n methods: methods.map((method) => ({\n type: method.type,\n ...(method.label ? { label: method.label } : {}),\n ...(method.lastUsedAt ? { lastUsed: method.lastUsedAt } : {}),\n })),\n recoveryCodesRemaining,\n compliant: compliance.compliant,\n }\n }\n\n async bulkComplianceCheck(tenantId: string): Promise<BulkComplianceStatus[]> {\n if (!tenantId.trim()) {\n throw new MfaAdminServiceError('Tenant ID is required', 400)\n }\n\n const users = await findWithDecryption(\n this.em,\n User,\n {\n tenantId,\n deletedAt: null,\n },\n {\n orderBy: { createdAt: 'asc' },\n },\n { tenantId, organizationId: null },\n )\n\n const userIds = users.map((user) => user.id)\n const activeMethods = userIds.length\n ? await this.em.find(UserMfaMethod, {\n userId: { $in: userIds },\n isActive: true,\n deletedAt: null,\n })\n : []\n const methodCountByUserId = new Map<string, number>()\n for (const method of activeMethods) {\n const currentCount = methodCountByUserId.get(method.userId) ?? 0\n methodCountByUserId.set(method.userId, currentCount + 1)\n }\n\n const complianceResults = await Promise.all(\n users.map((user) => this.mfaEnforcementService.checkUserCompliance(user.id)),\n )\n\n return users.map((user, index) => {\n const methodCount = methodCountByUserId.get(user.id) ?? 0\n const compliance = complianceResults[index]\n return {\n userId: user.id,\n email: user.email,\n enrolled: methodCount > 0,\n methodCount,\n compliant: compliance.compliant,\n ...(user.lastLoginAt ? { lastLoginAt: user.lastLoginAt } : {}),\n }\n })\n }\n\n private async findUserById(userId: string): Promise<User | null> {\n return this.em
|
|
5
|
-
"mappings": "AACA,SAAS,YAAY;AACrB,SAAS,0BAA0B;
|
|
4
|
+
"sourcesContent": ["import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'\nimport { User } from '@open-mercato/core/modules/auth/data/entities'\nimport { findOneWithDecryption, findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { MfaRecoveryCode, UserMfaMethod } from '../data/entities'\nimport { emitSecurityEvent } from '../events'\nimport type { MfaEnforcementService } from './MfaEnforcementService'\n\ntype MfaMethodStatus = {\n type: string\n label?: string\n lastUsed?: Date\n}\n\ntype UserMfaStatus = {\n enrolled: boolean\n methods: MfaMethodStatus[]\n recoveryCodesRemaining: number\n compliant: boolean\n}\n\ntype BulkComplianceStatus = {\n userId: string\n email: string\n enrolled: boolean\n methodCount: number\n compliant: boolean\n lastLoginAt?: Date\n}\n\ntype ActorContext = {\n tenantId: string | null\n isSuperAdmin: boolean\n}\n\nexport class MfaAdminServiceError extends Error {\n constructor(\n message: string,\n public readonly statusCode: number,\n ) {\n super(message)\n this.name = 'MfaAdminServiceError'\n }\n}\n\nexport class MfaAdminService {\n constructor(\n private readonly em: EntityManager,\n private readonly mfaEnforcementService: MfaEnforcementService,\n ) {}\n\n async resetUserMfa(adminId: string, userId: string, reason: string, actor?: ActorContext): Promise<void> {\n if (!adminId.trim()) {\n throw new MfaAdminServiceError('Admin ID is required', 400)\n }\n if (!userId.trim()) {\n throw new MfaAdminServiceError('User ID is required', 400)\n }\n\n const normalizedReason = reason.trim()\n if (!normalizedReason) {\n throw new MfaAdminServiceError('Reset reason is required', 400)\n }\n\n const user = await this.findUserById(userId)\n if (!user) {\n throw new MfaAdminServiceError('User not found', 404)\n }\n this.assertActorOwnsUser(user, actor)\n\n const activeMethods = await this.em.find(UserMfaMethod, {\n userId,\n isActive: true,\n deletedAt: null,\n })\n const activeRecoveryCodes = await this.em.find(MfaRecoveryCode, {\n userId,\n isUsed: false,\n })\n\n const now = new Date()\n for (const method of activeMethods) {\n method.isActive = false\n method.deletedAt = now\n method.updatedAt = now\n }\n for (const recoveryCode of activeRecoveryCodes) {\n recoveryCode.isUsed = true\n recoveryCode.usedAt = now\n }\n await this.em.flush()\n\n await emitSecurityEvent('security.mfa.reset', {\n adminId,\n targetUserId: userId,\n tenantId: user.tenantId,\n organizationId: user.organizationId ?? null,\n reason: normalizedReason,\n methodCount: activeMethods.length,\n recoveryCodesInvalidated: activeRecoveryCodes.length,\n resetAt: new Date().toISOString(),\n })\n }\n\n async getUserMfaStatus(userId: string, actor?: ActorContext): Promise<UserMfaStatus> {\n if (!userId.trim()) {\n throw new MfaAdminServiceError('User ID is required', 400)\n }\n\n const user = await this.findUserById(userId)\n if (!user) {\n throw new MfaAdminServiceError('User not found', 404)\n }\n this.assertActorOwnsUser(user, actor)\n\n const methods = await this.em.find(\n UserMfaMethod,\n {\n userId,\n isActive: true,\n deletedAt: null,\n },\n {\n orderBy: { createdAt: 'desc' },\n },\n )\n\n const recoveryCodesRemaining = await this.em.count(MfaRecoveryCode, {\n userId,\n isUsed: false,\n })\n\n const compliance = await this.mfaEnforcementService.checkUserCompliance(userId)\n\n return {\n enrolled: methods.length > 0,\n methods: methods.map((method) => ({\n type: method.type,\n ...(method.label ? { label: method.label } : {}),\n ...(method.lastUsedAt ? { lastUsed: method.lastUsedAt } : {}),\n })),\n recoveryCodesRemaining,\n compliant: compliance.compliant,\n }\n }\n\n async bulkComplianceCheck(tenantId: string, actor?: ActorContext): Promise<BulkComplianceStatus[]> {\n if (!tenantId.trim()) {\n throw new MfaAdminServiceError('Tenant ID is required', 400)\n }\n if (actor && !actor.isSuperAdmin && tenantId !== actor.tenantId) {\n throw new MfaAdminServiceError('Not authorized for the requested tenant scope.', 403)\n }\n\n const users = await findWithDecryption(\n this.em,\n User,\n {\n tenantId,\n deletedAt: null,\n },\n {\n orderBy: { createdAt: 'asc' },\n },\n { tenantId, organizationId: null },\n )\n\n const userIds = users.map((user) => user.id)\n const activeMethods = userIds.length\n ? await this.em.find(UserMfaMethod, {\n userId: { $in: userIds },\n isActive: true,\n deletedAt: null,\n })\n : []\n const methodCountByUserId = new Map<string, number>()\n for (const method of activeMethods) {\n const currentCount = methodCountByUserId.get(method.userId) ?? 0\n methodCountByUserId.set(method.userId, currentCount + 1)\n }\n\n const complianceResults = await Promise.all(\n users.map((user) => this.mfaEnforcementService.checkUserCompliance(user.id)),\n )\n\n return users.map((user, index) => {\n const methodCount = methodCountByUserId.get(user.id) ?? 0\n const compliance = complianceResults[index]\n return {\n userId: user.id,\n email: user.email,\n enrolled: methodCount > 0,\n methodCount,\n compliant: compliance.compliant,\n ...(user.lastLoginAt ? { lastLoginAt: user.lastLoginAt } : {}),\n }\n })\n }\n\n private assertActorOwnsUser(user: User, actor?: ActorContext): void {\n if (!actor || actor.isSuperAdmin) return\n if (!user.tenantId || user.tenantId !== actor.tenantId) {\n throw new MfaAdminServiceError('User not found', 404)\n }\n }\n\n private async findUserById(userId: string): Promise<User | null> {\n return findOneWithDecryption(\n this.em,\n User,\n { id: userId, deletedAt: null } as FilterQuery<User>,\n {},\n { tenantId: null, organizationId: null },\n )\n }\n}\n\nexport default MfaAdminService\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -30,20 +30,26 @@ class MfaEnforcementService {
|
|
|
30
30
|
}
|
|
31
31
|
return { enforced: true, policy };
|
|
32
32
|
}
|
|
33
|
-
async
|
|
33
|
+
async getPolicyById(id) {
|
|
34
|
+
return this.em.findOne(MfaEnforcementPolicy, { id, deletedAt: null });
|
|
35
|
+
}
|
|
36
|
+
async listPolicies(filters, actor) {
|
|
37
|
+
const tenantConstraint = actor && !actor.isSuperAdmin ? { tenantId: actor.tenantId } : {};
|
|
34
38
|
return this.em.find(
|
|
35
39
|
MfaEnforcementPolicy,
|
|
36
40
|
{
|
|
37
41
|
deletedAt: null,
|
|
38
|
-
...filters?.scope ? { scope: filters.scope } : {}
|
|
42
|
+
...filters?.scope ? { scope: filters.scope } : {},
|
|
43
|
+
...tenantConstraint
|
|
39
44
|
},
|
|
40
45
|
{
|
|
41
46
|
orderBy: { updatedAt: "desc" }
|
|
42
47
|
}
|
|
43
48
|
);
|
|
44
49
|
}
|
|
45
|
-
async getComplianceReport(scope, scopeId) {
|
|
50
|
+
async getComplianceReport(scope, scopeId, actor) {
|
|
46
51
|
const { tenantId, organizationId } = this.resolveScopeFilters(scope, scopeId);
|
|
52
|
+
this.assertActorOwnsScopeFilters(actor, scope, tenantId);
|
|
47
53
|
const users = await this.em.find(User, {
|
|
48
54
|
deletedAt: null,
|
|
49
55
|
...tenantId ? { tenantId } : {},
|
|
@@ -75,8 +81,9 @@ class MfaEnforcementService {
|
|
|
75
81
|
overdue
|
|
76
82
|
};
|
|
77
83
|
}
|
|
78
|
-
async createPolicy(data, adminId) {
|
|
84
|
+
async createPolicy(data, adminId, actor) {
|
|
79
85
|
const normalized = this.normalizePolicyInput(data);
|
|
86
|
+
this.assertActorOwnsScopeFilters(actor, normalized.scope, normalized.tenantId);
|
|
80
87
|
const existing = await this.findPolicyByScope(
|
|
81
88
|
normalized.scope,
|
|
82
89
|
normalized.tenantId ?? void 0,
|
|
@@ -119,7 +126,7 @@ class MfaEnforcementService {
|
|
|
119
126
|
await this.emitDeadlineReminderRequest(policy.id);
|
|
120
127
|
return policy;
|
|
121
128
|
}
|
|
122
|
-
async updatePolicy(id, data, adminId) {
|
|
129
|
+
async updatePolicy(id, data, adminId, actor) {
|
|
123
130
|
const policy = await this.em.findOne(MfaEnforcementPolicy, {
|
|
124
131
|
id,
|
|
125
132
|
deletedAt: null
|
|
@@ -127,6 +134,7 @@ class MfaEnforcementService {
|
|
|
127
134
|
if (!policy) {
|
|
128
135
|
throw new MfaEnforcementServiceError("Enforcement policy not found", 404);
|
|
129
136
|
}
|
|
137
|
+
this.assertActorOwnsScopeFilters(actor, policy.scope, policy.tenantId ?? null);
|
|
130
138
|
const mergedInput = this.normalizePolicyInput({
|
|
131
139
|
scope: data.scope ?? policy.scope,
|
|
132
140
|
tenantId: data.tenantId ?? policy.tenantId ?? void 0,
|
|
@@ -135,6 +143,7 @@ class MfaEnforcementService {
|
|
|
135
143
|
allowedMethods: data.allowedMethods ?? policy.allowedMethods ?? null,
|
|
136
144
|
enforcementDeadline: data.enforcementDeadline === void 0 ? policy.enforcementDeadline ?? null : data.enforcementDeadline
|
|
137
145
|
});
|
|
146
|
+
this.assertActorOwnsScopeFilters(actor, mergedInput.scope, mergedInput.tenantId);
|
|
138
147
|
if (mergedInput.scope !== policy.scope || mergedInput.tenantId !== (policy.tenantId ?? null) || mergedInput.organizationId !== (policy.organizationId ?? null)) {
|
|
139
148
|
const conflict = await this.findPolicyByScope(
|
|
140
149
|
mergedInput.scope,
|
|
@@ -162,7 +171,7 @@ class MfaEnforcementService {
|
|
|
162
171
|
await this.emitDeadlineReminderRequest(policy.id);
|
|
163
172
|
return policy;
|
|
164
173
|
}
|
|
165
|
-
async deletePolicy(id) {
|
|
174
|
+
async deletePolicy(id, actor) {
|
|
166
175
|
const policy = await this.em.findOne(MfaEnforcementPolicy, {
|
|
167
176
|
id,
|
|
168
177
|
deletedAt: null
|
|
@@ -170,6 +179,7 @@ class MfaEnforcementService {
|
|
|
170
179
|
if (!policy) {
|
|
171
180
|
throw new MfaEnforcementServiceError("Enforcement policy not found", 404);
|
|
172
181
|
}
|
|
182
|
+
this.assertActorOwnsScopeFilters(actor, policy.scope, policy.tenantId ?? null);
|
|
173
183
|
const now = /* @__PURE__ */ new Date();
|
|
174
184
|
policy.deletedAt = now;
|
|
175
185
|
policy.updatedAt = now;
|
|
@@ -227,6 +237,18 @@ class MfaEnforcementService {
|
|
|
227
237
|
}
|
|
228
238
|
);
|
|
229
239
|
}
|
|
240
|
+
assertActorOwnsScopeFilters(actor, scope, tenantId) {
|
|
241
|
+
if (!actor || actor.isSuperAdmin) return;
|
|
242
|
+
if (scope === EnforcementScope.PLATFORM) {
|
|
243
|
+
throw new MfaEnforcementServiceError(
|
|
244
|
+
"Platform scope requires platform administrator privileges.",
|
|
245
|
+
403
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
if (!tenantId || tenantId !== actor.tenantId) {
|
|
249
|
+
throw new MfaEnforcementServiceError("Not authorized for the requested scope.", 403);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
230
252
|
resolveScopeFilters(scope, scopeId) {
|
|
231
253
|
if (scope === EnforcementScope.PLATFORM) {
|
|
232
254
|
return {};
|