@open-mercato/core 0.4.6-develop-bbfd75b1c8 → 0.4.6-develop-2ba4e02ffb
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/data_sync/api/mappings/[id]/route.js +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +2 -2
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/integrations/api/[id]/route.js +9 -1
- package/dist/modules/integrations/api/[id]/route.js.map +2 -2
- package/dist/modules/integrations/api/logs/route.js +2 -2
- package/dist/modules/integrations/api/logs/route.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/[id]/page.js +5 -1
- package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +5 -1
- package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
- package/dist/modules/integrations/lib/credentials-service.js +5 -1
- package/dist/modules/integrations/lib/credentials-service.js.map +2 -2
- package/dist/modules/integrations/lib/health-service.js.map +2 -2
- package/dist/modules/integrations/lib/log-service.js.map +2 -2
- package/dist/modules/integrations/lib/state-service.js.map +2 -2
- package/dist/modules/sales/api/quotes/convert/route.js +31 -14
- package/dist/modules/sales/api/quotes/convert/route.js.map +2 -2
- package/dist/modules/sales/api/quotes/send/route.js +31 -14
- package/dist/modules/sales/api/quotes/send/route.js.map +2 -2
- package/package.json +2 -2
- package/src/modules/data_sync/api/mappings/[id]/route.ts +2 -2
- package/src/modules/data_sync/api/mappings/route.ts +2 -2
- package/src/modules/integrations/api/[id]/route.ts +9 -1
- package/src/modules/integrations/api/logs/route.ts +3 -3
- package/src/modules/integrations/backend/integrations/[id]/page.tsx +8 -2
- package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +8 -2
- package/src/modules/integrations/lib/credentials-service.ts +6 -2
- package/src/modules/integrations/lib/health-service.ts +1 -2
- package/src/modules/integrations/lib/log-service.ts +1 -1
- package/src/modules/integrations/lib/state-service.ts +1 -1
- package/src/modules/sales/api/quotes/convert/route.ts +55 -14
- package/src/modules/sales/api/quotes/send/route.ts +55 -14
- package/dist/modules/integrations/lib/types.js +0 -1
- package/dist/modules/integrations/lib/types.js.map +0 -7
- package/src/modules/integrations/lib/types.ts +0 -4
|
@@ -12,12 +12,18 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
|
|
|
12
12
|
import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
|
|
13
13
|
import { flash } from '@open-mercato/ui/backend/FlashMessages'
|
|
14
14
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
15
|
-
import type { IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'
|
|
15
|
+
import type { CredentialFieldType, IntegrationCredentialField } from '@open-mercato/shared/modules/integrations/types'
|
|
16
16
|
import { LoadingMessage } from '@open-mercato/ui/backend/detail'
|
|
17
17
|
import { ErrorMessage } from '@open-mercato/ui/backend/detail'
|
|
18
18
|
|
|
19
19
|
type CredentialField = IntegrationCredentialField
|
|
20
20
|
|
|
21
|
+
const UNSUPPORTED_CREDENTIAL_FIELD_TYPES = new Set<CredentialFieldType>(['oauth', 'ssh_keypair'])
|
|
22
|
+
|
|
23
|
+
function isEditableCredentialField(field: CredentialField): boolean {
|
|
24
|
+
return !UNSUPPORTED_CREDENTIAL_FIELD_TYPES.has(field.type)
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
type BundleIntegration = {
|
|
22
28
|
id: string
|
|
23
29
|
title: string
|
|
@@ -131,7 +137,7 @@ export default function BundleConfigPage() {
|
|
|
131
137
|
if (isLoading) return <Page><PageBody><LoadingMessage label={t('integrations.bundle.title')} /></PageBody></Page>
|
|
132
138
|
if (error || !detail?.bundle) return <Page><PageBody><ErrorMessage label={error ?? t('integrations.detail.loadError')} /></PageBody></Page>
|
|
133
139
|
|
|
134
|
-
const credFields = detail.bundle.credentials?.fields ?? []
|
|
140
|
+
const credFields = (detail.bundle.credentials?.fields ?? []).filter(isEditableCredentialField)
|
|
135
141
|
|
|
136
142
|
return (
|
|
137
143
|
<Page>
|
|
@@ -3,10 +3,14 @@ import type { EntityManager } from '@mikro-orm/postgresql'
|
|
|
3
3
|
import { decryptWithAesGcm, encryptWithAesGcm } from '@open-mercato/shared/lib/encryption/aes'
|
|
4
4
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
5
5
|
import { createKmsService } from '@open-mercato/shared/lib/encryption/kms'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getBundle,
|
|
8
|
+
getIntegration,
|
|
9
|
+
resolveIntegrationCredentialsSchema,
|
|
10
|
+
type IntegrationScope,
|
|
11
|
+
} from '@open-mercato/shared/modules/integrations/types'
|
|
7
12
|
import { EncryptionMap } from '../../entities/data/entities'
|
|
8
13
|
import { IntegrationCredentials } from '../data/entities'
|
|
9
|
-
import type { IntegrationScope } from './types'
|
|
10
14
|
|
|
11
15
|
const ENCRYPTED_CREDENTIALS_BLOB_KEY = '__om_encrypted_credentials_blob_v1'
|
|
12
16
|
const DERIVED_KEY_CONTEXT = 'integrations.credentials'
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { AwilixContainer } from 'awilix'
|
|
2
2
|
import type { IntegrationStateService } from './state-service'
|
|
3
3
|
import type { IntegrationLogService } from './log-service'
|
|
4
|
-
import { getIntegration, getBundle } from '@open-mercato/shared/modules/integrations/types'
|
|
5
|
-
import type { IntegrationScope } from './types'
|
|
4
|
+
import { getIntegration, getBundle, type IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
|
|
6
5
|
|
|
7
6
|
type HealthCheckResult = {
|
|
8
7
|
status: 'healthy' | 'degraded' | 'unhealthy'
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
|
|
2
2
|
import { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
|
+
import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
|
|
3
4
|
import type { ListIntegrationLogsQuery } from '../data/validators'
|
|
4
5
|
import { IntegrationLog } from '../data/entities'
|
|
5
|
-
import type { IntegrationScope } from './types'
|
|
6
6
|
|
|
7
7
|
type LogInput = {
|
|
8
8
|
integrationId: string
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
2
|
import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
|
|
3
|
+
import type { IntegrationScope } from '@open-mercato/shared/modules/integrations/types'
|
|
3
4
|
import { IntegrationState } from '../data/entities'
|
|
4
|
-
import type { IntegrationScope } from './types'
|
|
5
5
|
|
|
6
6
|
export function createIntegrationStateService(em: EntityManager) {
|
|
7
7
|
return {
|
|
@@ -9,10 +9,11 @@ import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
|
9
9
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
10
10
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
|
|
12
|
+
bridgeLegacyGuard,
|
|
13
|
+
runMutationGuards,
|
|
14
|
+
type MutationGuard,
|
|
15
|
+
type MutationGuardInput,
|
|
16
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
16
17
|
import { withScopedPayload } from '../../utils'
|
|
17
18
|
|
|
18
19
|
const convertSchema = z.object({
|
|
@@ -29,9 +30,51 @@ type RequestContext = {
|
|
|
29
30
|
ctx: CommandRuntimeContext
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
function resolveUserFeatures(auth: unknown): string[] {
|
|
34
|
+
const features = (auth as { features?: unknown })?.features
|
|
35
|
+
if (!Array.isArray(features)) return []
|
|
36
|
+
return features.filter((value): value is string => typeof value === 'string')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function runGuards(
|
|
40
|
+
ctx: CommandRuntimeContext,
|
|
41
|
+
input: MutationGuardInput,
|
|
42
|
+
): Promise<{
|
|
43
|
+
ok: boolean
|
|
44
|
+
errorBody?: Record<string, unknown>
|
|
45
|
+
errorStatus?: number
|
|
46
|
+
afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
|
|
47
|
+
}> {
|
|
48
|
+
const legacyGuard = bridgeLegacyGuard(ctx.container)
|
|
49
|
+
if (!legacyGuard) {
|
|
50
|
+
return { ok: true, afterSuccessCallbacks: [] }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return runMutationGuards([legacyGuard], input, {
|
|
54
|
+
userFeatures: resolveUserFeatures(ctx.auth),
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function runGuardAfterSuccessCallbacks(
|
|
59
|
+
callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
|
|
60
|
+
input: {
|
|
61
|
+
tenantId: string
|
|
62
|
+
organizationId: string | null
|
|
63
|
+
userId: string
|
|
64
|
+
resourceKind: string
|
|
65
|
+
resourceId: string
|
|
66
|
+
operation: 'create' | 'update' | 'delete'
|
|
67
|
+
requestMethod: string
|
|
68
|
+
requestHeaders: Headers
|
|
69
|
+
},
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
for (const callback of callbacks) {
|
|
72
|
+
if (!callback.guard.afterSuccess) continue
|
|
73
|
+
await callback.guard.afterSuccess({
|
|
74
|
+
...input,
|
|
75
|
+
metadata: callback.metadata ?? null,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
35
78
|
}
|
|
36
79
|
|
|
37
80
|
async function resolveRequestContext(req: Request): Promise<RequestContext> {
|
|
@@ -70,7 +113,7 @@ export async function POST(req: Request) {
|
|
|
70
113
|
const payload = await req.json().catch(() => ({}))
|
|
71
114
|
const scoped = withScopedPayload(payload ?? {}, ctx, translate)
|
|
72
115
|
const input = convertSchema.parse(scoped)
|
|
73
|
-
const
|
|
116
|
+
const guardResult = await runGuards(ctx, {
|
|
74
117
|
tenantId: ctx.auth?.tenantId ?? '',
|
|
75
118
|
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
76
119
|
userId: ctx.auth?.sub ?? '',
|
|
@@ -80,9 +123,8 @@ export async function POST(req: Request) {
|
|
|
80
123
|
requestMethod: req.method,
|
|
81
124
|
requestHeaders: req.headers,
|
|
82
125
|
})
|
|
83
|
-
if (
|
|
84
|
-
|
|
85
|
-
if (lockErrorResponse) return lockErrorResponse
|
|
126
|
+
if (!guardResult.ok) {
|
|
127
|
+
return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
|
|
86
128
|
}
|
|
87
129
|
const commandBus = ctx.container.resolve('commandBus') as CommandBus
|
|
88
130
|
const { result, logEntry } = await commandBus.execute<
|
|
@@ -112,8 +154,8 @@ export async function POST(req: Request) {
|
|
|
112
154
|
)
|
|
113
155
|
}
|
|
114
156
|
|
|
115
|
-
if (
|
|
116
|
-
await
|
|
157
|
+
if (guardResult.afterSuccessCallbacks.length) {
|
|
158
|
+
await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
|
|
117
159
|
tenantId: ctx.auth?.tenantId ?? '',
|
|
118
160
|
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
119
161
|
userId: ctx.auth?.sub ?? '',
|
|
@@ -122,7 +164,6 @@ export async function POST(req: Request) {
|
|
|
122
164
|
operation: 'update',
|
|
123
165
|
requestMethod: req.method,
|
|
124
166
|
requestHeaders: req.headers,
|
|
125
|
-
metadata: mutationGuardValidation.metadata ?? null,
|
|
126
167
|
})
|
|
127
168
|
}
|
|
128
169
|
|
|
@@ -8,10 +8,11 @@ import { resolveTranslations, detectLocale } from '@open-mercato/shared/lib/i18n
|
|
|
8
8
|
import { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'
|
|
9
9
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
10
10
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type
|
|
14
|
-
|
|
11
|
+
bridgeLegacyGuard,
|
|
12
|
+
runMutationGuards,
|
|
13
|
+
type MutationGuard,
|
|
14
|
+
type MutationGuardInput,
|
|
15
|
+
} from '@open-mercato/shared/lib/crud/mutation-guard-registry'
|
|
15
16
|
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
16
17
|
import crypto from 'node:crypto'
|
|
17
18
|
import { withScopedPayload } from '../../utils'
|
|
@@ -29,9 +30,51 @@ type RequestContext = {
|
|
|
29
30
|
ctx: CommandRuntimeContext
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
function
|
|
33
|
-
|
|
34
|
-
|
|
33
|
+
function resolveUserFeatures(auth: unknown): string[] {
|
|
34
|
+
const features = (auth as { features?: unknown })?.features
|
|
35
|
+
if (!Array.isArray(features)) return []
|
|
36
|
+
return features.filter((value): value is string => typeof value === 'string')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function runGuards(
|
|
40
|
+
ctx: CommandRuntimeContext,
|
|
41
|
+
input: MutationGuardInput,
|
|
42
|
+
): Promise<{
|
|
43
|
+
ok: boolean
|
|
44
|
+
errorBody?: Record<string, unknown>
|
|
45
|
+
errorStatus?: number
|
|
46
|
+
afterSuccessCallbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>
|
|
47
|
+
}> {
|
|
48
|
+
const legacyGuard = bridgeLegacyGuard(ctx.container)
|
|
49
|
+
if (!legacyGuard) {
|
|
50
|
+
return { ok: true, afterSuccessCallbacks: [] }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return runMutationGuards([legacyGuard], input, {
|
|
54
|
+
userFeatures: resolveUserFeatures(ctx.auth),
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function runGuardAfterSuccessCallbacks(
|
|
59
|
+
callbacks: Array<{ guard: MutationGuard; metadata: Record<string, unknown> | null }>,
|
|
60
|
+
input: {
|
|
61
|
+
tenantId: string
|
|
62
|
+
organizationId: string | null
|
|
63
|
+
userId: string
|
|
64
|
+
resourceKind: string
|
|
65
|
+
resourceId: string
|
|
66
|
+
operation: 'create' | 'update' | 'delete'
|
|
67
|
+
requestMethod: string
|
|
68
|
+
requestHeaders: Headers
|
|
69
|
+
},
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
for (const callback of callbacks) {
|
|
72
|
+
if (!callback.guard.afterSuccess) continue
|
|
73
|
+
await callback.guard.afterSuccess({
|
|
74
|
+
...input,
|
|
75
|
+
metadata: callback.metadata ?? null,
|
|
76
|
+
})
|
|
77
|
+
}
|
|
35
78
|
}
|
|
36
79
|
|
|
37
80
|
async function resolveRequestContext(req: Request): Promise<RequestContext> {
|
|
@@ -85,7 +128,7 @@ export async function POST(req: Request) {
|
|
|
85
128
|
const payload = await req.json().catch(() => ({}))
|
|
86
129
|
const scoped = withScopedPayload(payload ?? {}, ctx, translate)
|
|
87
130
|
const input = quoteSendSchema.parse(scoped)
|
|
88
|
-
const
|
|
131
|
+
const guardResult = await runGuards(ctx, {
|
|
89
132
|
tenantId: ctx.auth?.tenantId ?? '',
|
|
90
133
|
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
91
134
|
userId: ctx.auth?.sub ?? '',
|
|
@@ -95,9 +138,8 @@ export async function POST(req: Request) {
|
|
|
95
138
|
requestMethod: req.method,
|
|
96
139
|
requestHeaders: req.headers,
|
|
97
140
|
})
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
if (lockErrorResponse) return lockErrorResponse
|
|
141
|
+
if (!guardResult.ok) {
|
|
142
|
+
return NextResponse.json(guardResult.errorBody ?? { error: 'Operation blocked by guard' }, { status: guardResult.errorStatus ?? 422 })
|
|
101
143
|
}
|
|
102
144
|
|
|
103
145
|
const em = (ctx.container.resolve('em') as EntityManager).fork()
|
|
@@ -163,8 +205,8 @@ export async function POST(req: Request) {
|
|
|
163
205
|
react: QuoteSentEmail({ url, copy }),
|
|
164
206
|
})
|
|
165
207
|
|
|
166
|
-
if (
|
|
167
|
-
await
|
|
208
|
+
if (guardResult.afterSuccessCallbacks.length) {
|
|
209
|
+
await runGuardAfterSuccessCallbacks(guardResult.afterSuccessCallbacks, {
|
|
168
210
|
tenantId: ctx.auth?.tenantId ?? '',
|
|
169
211
|
organizationId: ctx.selectedOrganizationId ?? ctx.auth?.orgId ?? null,
|
|
170
212
|
userId: ctx.auth?.sub ?? '',
|
|
@@ -173,7 +215,6 @@ export async function POST(req: Request) {
|
|
|
173
215
|
operation: 'update',
|
|
174
216
|
requestMethod: req.method,
|
|
175
217
|
requestHeaders: req.headers,
|
|
176
|
-
metadata: mutationGuardValidation.metadata ?? null,
|
|
177
218
|
})
|
|
178
219
|
}
|
|
179
220
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
//# sourceMappingURL=types.js.map
|