@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.
Files changed (62) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/api_keys/api/keys/route.js +1 -1
  3. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  4. package/dist/modules/api_keys/di.js +9 -0
  5. package/dist/modules/api_keys/di.js.map +7 -0
  6. package/dist/modules/api_keys/services/principalService.js +31 -0
  7. package/dist/modules/api_keys/services/principalService.js.map +7 -0
  8. package/dist/modules/attachments/di.js +11 -1
  9. package/dist/modules/attachments/di.js.map +2 -2
  10. package/dist/modules/attachments/index.js.map +1 -1
  11. package/dist/modules/attachments/lib/attachment-service.js +313 -0
  12. package/dist/modules/attachments/lib/attachment-service.js.map +7 -0
  13. package/dist/modules/attachments/lib/scoped-upload-service.js +15 -2
  14. package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
  15. package/dist/modules/attachments/lib/upload-limits.js +6 -2
  16. package/dist/modules/attachments/lib/upload-limits.js.map +2 -2
  17. package/dist/modules/auth/data/entities.js.map +2 -2
  18. package/dist/modules/auth/di.js +35 -9
  19. package/dist/modules/auth/di.js.map +2 -2
  20. package/dist/modules/auth/services/principalService.js +200 -0
  21. package/dist/modules/auth/services/principalService.js.map +7 -0
  22. package/dist/modules/auth/services/rbacService.js +67 -24
  23. package/dist/modules/auth/services/rbacService.js.map +3 -3
  24. package/dist/modules/auth/services/roleOrganizationScope.js +41 -0
  25. package/dist/modules/auth/services/roleOrganizationScope.js.map +7 -0
  26. package/dist/modules/directory/di.js +9 -0
  27. package/dist/modules/directory/di.js.map +2 -2
  28. package/dist/modules/directory/services/organizationHierarchyService.js +29 -0
  29. package/dist/modules/directory/services/organizationHierarchyService.js.map +7 -0
  30. package/dist/modules/directory/services/organizationScopeService.js +56 -0
  31. package/dist/modules/directory/services/organizationScopeService.js.map +7 -0
  32. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  33. package/dist/modules/warranty_claims/api/portal/attachments/route.js +2 -2
  34. package/dist/modules/warranty_claims/api/portal/attachments/route.js.map +2 -2
  35. package/dist/modules/workflows/lib/activity-executor.js +4 -0
  36. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  37. package/package.json +7 -7
  38. package/src/modules/api_keys/api/keys/route.ts +3 -1
  39. package/src/modules/api_keys/di.ts +7 -0
  40. package/src/modules/api_keys/services/principalService.ts +33 -0
  41. package/src/modules/attachments/AGENTS.md +15 -0
  42. package/src/modules/attachments/di.ts +16 -0
  43. package/src/modules/attachments/i18n/de.json +5 -0
  44. package/src/modules/attachments/i18n/en.json +5 -0
  45. package/src/modules/attachments/i18n/es.json +5 -0
  46. package/src/modules/attachments/i18n/ko.json +5 -0
  47. package/src/modules/attachments/i18n/pl.json +5 -0
  48. package/src/modules/attachments/index.ts +10 -0
  49. package/src/modules/attachments/lib/attachment-service.ts +468 -0
  50. package/src/modules/attachments/lib/scoped-upload-service.ts +46 -0
  51. package/src/modules/attachments/lib/upload-limits.ts +6 -2
  52. package/src/modules/auth/data/entities.ts +4 -1
  53. package/src/modules/auth/di.ts +46 -16
  54. package/src/modules/auth/services/principalService.ts +266 -0
  55. package/src/modules/auth/services/rbacService.ts +110 -24
  56. package/src/modules/auth/services/roleOrganizationScope.ts +70 -0
  57. package/src/modules/directory/di.ts +14 -2
  58. package/src/modules/directory/services/organizationHierarchyService.ts +34 -0
  59. package/src/modules/directory/services/organizationScopeService.ts +85 -0
  60. package/src/modules/directory/utils/organizationScope.ts +4 -15
  61. package/src/modules/warranty_claims/api/portal/attachments/route.ts +2 -2
  62. 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
- ScopedAttachmentUploadError,
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 instanceof ScopedAttachmentUploadError) {
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.