@open-mercato/core 0.6.8-develop.7015.1.af90a2ddc7 → 0.6.8-develop.7016.1.4ed7b1e49d
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/api_keys/api/keys/route.js +1 -1
- package/dist/modules/api_keys/api/keys/route.js.map +2 -2
- package/dist/modules/api_keys/di.js +9 -0
- package/dist/modules/api_keys/di.js.map +7 -0
- package/dist/modules/api_keys/services/principalService.js +31 -0
- package/dist/modules/api_keys/services/principalService.js.map +7 -0
- package/dist/modules/attachments/di.js +11 -1
- package/dist/modules/attachments/di.js.map +2 -2
- package/dist/modules/attachments/index.js.map +1 -1
- package/dist/modules/attachments/lib/attachment-service.js +313 -0
- package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
- package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
- package/dist/modules/attachments/lib/upload-limits.js +6 -2
- package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
- package/dist/modules/auth/data/entities.js.map +2 -2
- package/dist/modules/auth/di.js +35 -9
- package/dist/modules/auth/di.js.map +2 -2
- package/dist/modules/auth/services/principalService.js +200 -0
- package/dist/modules/auth/services/principalService.js.map +7 -0
- package/dist/modules/auth/services/rbacService.js +67 -24
- package/dist/modules/auth/services/rbacService.js.map +3 -3
- package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
- package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
- package/dist/modules/directory/di.js +9 -0
- package/dist/modules/directory/di.js.map +2 -2
- package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
- package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
- package/dist/modules/directory/services/organizationScopeService.js +56 -0
- package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
- package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +4 -0
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/api_keys/api/keys/route.ts +3 -1
- package/src/modules/api_keys/di.ts +7 -0
- package/src/modules/api_keys/services/principalService.ts +33 -0
- package/src/modules/attachments/AGENTS.md +15 -0
- package/src/modules/attachments/di.ts +16 -0
- package/src/modules/attachments/i18n/de.json +5 -0
- package/src/modules/attachments/i18n/en.json +5 -0
- package/src/modules/attachments/i18n/es.json +5 -0
- package/src/modules/attachments/i18n/ko.json +5 -0
- package/src/modules/attachments/i18n/pl.json +5 -0
- package/src/modules/attachments/index.ts +10 -0
- package/src/modules/attachments/lib/attachment-service.ts +468 -0
- package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
- package/src/modules/attachments/lib/upload-limits.ts +6 -2
- package/src/modules/auth/data/entities.ts +4 -1
- package/src/modules/auth/di.ts +46 -16
- package/src/modules/auth/services/principalService.ts +266 -0
- package/src/modules/auth/services/rbacService.ts +110 -24
- package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
- package/src/modules/directory/di.ts +14 -2
- package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
- package/src/modules/directory/services/organizationScopeService.ts +85 -0
- package/src/modules/directory/utils/organizationScope.ts +4 -15
- package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
- package/src/modules/workflows/lib/activity-executor.ts +9 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
2
|
+
import type { AwilixContainer } from 'awilix'
|
|
3
|
+
import type {
|
|
4
|
+
OrganizationScopeService,
|
|
5
|
+
OrganizationScopeRequest,
|
|
6
|
+
} from '@open-mercato/shared/lib/auth/principal-service'
|
|
7
|
+
import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
8
|
+
import { resolveOrganizationScope, resolveOrganizationScopeForRequest } from '../utils/organizationScope'
|
|
9
|
+
|
|
10
|
+
type OrganizationScopeRbac = {
|
|
11
|
+
invalidateUserCache(userId: string): Promise<void>
|
|
12
|
+
loadAcl(
|
|
13
|
+
userId: string,
|
|
14
|
+
scope: { tenantId: string | null; organizationId: string | null },
|
|
15
|
+
): Promise<{ isSuperAdmin: boolean; features: string[]; organizations: string[] | null }>
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class DefaultOrganizationScopeService implements OrganizationScopeService {
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly em: EntityManager,
|
|
21
|
+
private readonly rbac: OrganizationScopeRbac,
|
|
22
|
+
private readonly container: AwilixContainer,
|
|
23
|
+
) {}
|
|
24
|
+
|
|
25
|
+
resolve(input: {
|
|
26
|
+
auth: AuthContext | null | undefined
|
|
27
|
+
selectedId?: string | null
|
|
28
|
+
tenantId?: string | null
|
|
29
|
+
freshAcl?: boolean
|
|
30
|
+
}) {
|
|
31
|
+
const auth = input.auth
|
|
32
|
+
if (input.freshAcl && auth?.sub) {
|
|
33
|
+
return this.resolveFresh({
|
|
34
|
+
auth,
|
|
35
|
+
selectedId: input.selectedId,
|
|
36
|
+
tenantId: input.tenantId,
|
|
37
|
+
}).then((result) => result.scope)
|
|
38
|
+
}
|
|
39
|
+
return resolveOrganizationScope({
|
|
40
|
+
em: this.em,
|
|
41
|
+
rbac: this.rbac as Parameters<typeof resolveOrganizationScope>[0]['rbac'],
|
|
42
|
+
auth: input.auth,
|
|
43
|
+
selectedId: input.selectedId,
|
|
44
|
+
tenantId: input.tenantId,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async resolveFresh(input: {
|
|
49
|
+
auth: NonNullable<AuthContext>
|
|
50
|
+
selectedId?: string | null
|
|
51
|
+
tenantId?: string | null
|
|
52
|
+
}) {
|
|
53
|
+
await this.rbac.invalidateUserCache(input.auth.sub)
|
|
54
|
+
const acl = await this.rbac.loadAcl(input.auth.sub, {
|
|
55
|
+
tenantId: input.tenantId ?? input.auth.tenantId ?? null,
|
|
56
|
+
organizationId: input.selectedId ?? input.auth.orgId ?? null,
|
|
57
|
+
})
|
|
58
|
+
const freshRbac: Parameters<typeof resolveOrganizationScope>[0]['rbac'] = {
|
|
59
|
+
loadAcl: async () => acl,
|
|
60
|
+
}
|
|
61
|
+
const scope = await resolveOrganizationScope({
|
|
62
|
+
em: this.em,
|
|
63
|
+
rbac: freshRbac,
|
|
64
|
+
auth: input.auth,
|
|
65
|
+
selectedId: input.selectedId,
|
|
66
|
+
tenantId: input.tenantId,
|
|
67
|
+
})
|
|
68
|
+
return { scope, acl }
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
resolveForRequest(input: {
|
|
72
|
+
auth: AuthContext | null | undefined
|
|
73
|
+
request?: OrganizationScopeRequest
|
|
74
|
+
selectedId?: string | null
|
|
75
|
+
tenantId?: string | null
|
|
76
|
+
}) {
|
|
77
|
+
return resolveOrganizationScopeForRequest({
|
|
78
|
+
container: this.container,
|
|
79
|
+
auth: input.auth,
|
|
80
|
+
request: input.request,
|
|
81
|
+
selectedId: input.selectedId,
|
|
82
|
+
tenantId: input.tenantId,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -5,6 +5,7 @@ import { Organization } from '@open-mercato/core/modules/directory/data/entities
|
|
|
5
5
|
import { isAllOrganizationsSelection } from '@open-mercato/core/modules/directory/constants'
|
|
6
6
|
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
7
|
import type { AuthContext } from '@open-mercato/shared/lib/auth/server'
|
|
8
|
+
import type { OrganizationScope } from '@open-mercato/shared/lib/auth/principal-service'
|
|
8
9
|
import { getCurrentCacheTenant, runWithCacheTenant, type CacheStrategy } from '@open-mercato/cache'
|
|
9
10
|
import { parseSelectedOrganizationCookie, parseSelectedTenantCookie } from './scopeCookies'
|
|
10
11
|
import { createLogger } from '@open-mercato/shared/lib/logger'
|
|
@@ -13,19 +14,7 @@ const logger = createLogger('directory').child({ component: 'org-scope-cache' })
|
|
|
13
14
|
|
|
14
15
|
export { parseSelectedOrganizationCookie, parseSelectedTenantCookie }
|
|
15
16
|
|
|
16
|
-
export type OrganizationScope
|
|
17
|
-
selectedId: string | null
|
|
18
|
-
filterIds: string[] | null
|
|
19
|
-
allowedIds: string[] | null
|
|
20
|
-
tenantId: string | null
|
|
21
|
-
// True when the caller explicitly selected a concrete organization (cookie /
|
|
22
|
-
// selectedId param) that could not be honored — it does not exist for the
|
|
23
|
-
// tenant or is not accessible. Reads degrade gracefully (filterIds/selectedId
|
|
24
|
-
// fall back to the caller's accessible orgs), but writes MUST fail loudly on
|
|
25
|
-
// this so a record never lands under an org the caller did not intend. Absent
|
|
26
|
-
// (undefined) means the selection — if any — was honored.
|
|
27
|
-
selectionRejected?: boolean
|
|
28
|
-
}
|
|
17
|
+
export type { OrganizationScope }
|
|
29
18
|
|
|
30
19
|
// Phase 4 — short-TTL cache for resolveOrganizationScopeForRequest.
|
|
31
20
|
// OrganizationScope is a pure function of (userId, tenantId, selectedOrgId,
|
|
@@ -249,8 +238,8 @@ export async function resolveOrganizationScope({
|
|
|
249
238
|
tenantId: tenantIdOverride,
|
|
250
239
|
}: {
|
|
251
240
|
em: EntityManager
|
|
252
|
-
rbac: RbacService
|
|
253
|
-
auth: AuthContext
|
|
241
|
+
rbac: Pick<RbacService, 'loadAcl'>
|
|
242
|
+
auth: AuthContext | null | undefined
|
|
254
243
|
selectedId?: string | null
|
|
255
244
|
tenantId?: string | null
|
|
256
245
|
}): Promise<OrganizationScope> {
|
|
@@ -19,7 +19,7 @@ import { readAttachmentMetadata } from '@open-mercato/core/modules/attachments/l
|
|
|
19
19
|
import { clearAttachmentThumbnailCache } from '@open-mercato/core/modules/attachments/lib/thumbnailCache'
|
|
20
20
|
import { isMultipartRequestWithinUploadLimit } from '@open-mercato/core/modules/attachments/lib/upload-limits'
|
|
21
21
|
import {
|
|
22
|
-
|
|
22
|
+
isScopedAttachmentUploadError,
|
|
23
23
|
type ScopedAttachmentUploadErrorCode,
|
|
24
24
|
} from '@open-mercato/core/modules/attachments/lib/scoped-upload-service'
|
|
25
25
|
import type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'
|
|
@@ -413,7 +413,7 @@ async function handleUpload(req: Request, replacement: boolean): Promise<Respons
|
|
|
413
413
|
tags: [CUSTOMER_VISIBLE_ATTACHMENT_TAG],
|
|
414
414
|
})
|
|
415
415
|
} catch (error) {
|
|
416
|
-
if (error
|
|
416
|
+
if (isScopedAttachmentUploadError(error)) {
|
|
417
417
|
const { t } = await resolveTranslations()
|
|
418
418
|
const [key, fallback] = ATTACHMENT_ERROR_TRANSLATIONS[error.code]
|
|
419
419
|
?? ['warranty_claims.portal.detail.attachmentError', 'Attachment upload failed.']
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
type HostLookup,
|
|
23
23
|
} from '@open-mercato/shared/lib/url-safety'
|
|
24
24
|
import { parseBooleanWithDefault } from '@open-mercato/shared/lib/boolean'
|
|
25
|
+
import { isPrivateCrossProcessBroadcastEvent } from '@open-mercato/shared/modules/events'
|
|
25
26
|
import { callWebhookConfigSchema } from '../data/validators'
|
|
26
27
|
import { WorkflowActivityJob, WORKFLOW_ACTIVITIES_QUEUE_NAME } from './activity-queue-types'
|
|
27
28
|
import { logWorkflowEvent } from './event-logger'
|
|
@@ -578,6 +579,14 @@ export async function executeEmitEvent(
|
|
|
578
579
|
throw new Error('EMIT_EVENT requires "eventName" field')
|
|
579
580
|
}
|
|
580
581
|
|
|
582
|
+
// Workflow definitions are tenant-managed input. Private cross-process
|
|
583
|
+
// events coordinate trusted server state (for example closing an in-memory
|
|
584
|
+
// collaboration room), so allowing a workflow to name one would turn a
|
|
585
|
+
// regular event activity into a cross-process invalidation primitive.
|
|
586
|
+
if (isPrivateCrossProcessBroadcastEvent(eventName)) {
|
|
587
|
+
throw new Error(`EMIT_EVENT cannot emit private cross-process event "${eventName}"`)
|
|
588
|
+
}
|
|
589
|
+
|
|
581
590
|
// Emissions are fire-and-forget and no subscriber validates the payload shape,
|
|
582
591
|
// so an unresolved `{{context.x}}` here is even quieter than on the command
|
|
583
592
|
// path — it ships the literal template to every consumer. Fail loudly instead.
|