@open-mercato/core 0.10.1-develop.7679.1.cb2c13f49e → 0.10.1-develop.7687.1.e1a6f9de51

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 (52) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/api/users/route.js +1 -1
  3. package/dist/modules/auth/api/users/route.js.map +2 -2
  4. package/dist/modules/customer_accounts/api/magic-link/request.js +15 -1
  5. package/dist/modules/customer_accounts/api/magic-link/request.js.map +2 -2
  6. package/dist/modules/customer_accounts/api/password/reset-request.js +15 -1
  7. package/dist/modules/customer_accounts/api/password/reset-request.js.map +2 -2
  8. package/dist/modules/customer_accounts/emails/CustomerAuthLinkEmail.js +26 -0
  9. package/dist/modules/customer_accounts/emails/CustomerAuthLinkEmail.js.map +7 -0
  10. package/dist/modules/customer_accounts/lib/authLinkEmails.js +67 -0
  11. package/dist/modules/customer_accounts/lib/authLinkEmails.js.map +7 -0
  12. package/dist/modules/messages/commands/actions.js +8 -1
  13. package/dist/modules/messages/commands/actions.js.map +2 -2
  14. package/dist/modules/payment_gateways/data/entities.js +4 -0
  15. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  16. package/dist/modules/payment_gateways/lib/session-idempotency.js +29 -5
  17. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +2 -2
  18. package/dist/modules/payment_gateways/migrations/Migration20261018180000_payment_gateways.js +19 -0
  19. package/dist/modules/payment_gateways/migrations/Migration20261018180000_payment_gateways.js.map +7 -0
  20. package/dist/modules/payment_gateways/workers/session-initialization-prune.js +3 -3
  21. package/dist/modules/payment_gateways/workers/session-initialization-prune.js.map +2 -2
  22. package/dist/modules/portal/frontend/[orgSlug]/portal/magic-link/page.js +59 -0
  23. package/dist/modules/portal/frontend/[orgSlug]/portal/magic-link/page.js.map +7 -0
  24. package/dist/modules/portal/frontend/[orgSlug]/portal/magic-link/page.meta.js +11 -0
  25. package/dist/modules/portal/frontend/[orgSlug]/portal/magic-link/page.meta.js.map +7 -0
  26. package/dist/modules/query_index/lib/engine.js +60 -13
  27. package/dist/modules/query_index/lib/engine.js.map +2 -2
  28. package/package.json +7 -7
  29. package/src/modules/auth/api/users/route.ts +1 -1
  30. package/src/modules/customer_accounts/api/magic-link/request.ts +16 -3
  31. package/src/modules/customer_accounts/api/password/reset-request.ts +16 -3
  32. package/src/modules/customer_accounts/emails/CustomerAuthLinkEmail.tsx +45 -0
  33. package/src/modules/customer_accounts/i18n/de.json +12 -0
  34. package/src/modules/customer_accounts/i18n/en.json +12 -0
  35. package/src/modules/customer_accounts/i18n/es.json +12 -0
  36. package/src/modules/customer_accounts/i18n/ko.json +12 -0
  37. package/src/modules/customer_accounts/i18n/pl.json +12 -0
  38. package/src/modules/customer_accounts/lib/authLinkEmails.ts +77 -0
  39. package/src/modules/messages/commands/actions.ts +8 -1
  40. package/src/modules/payment_gateways/data/entities.ts +4 -0
  41. package/src/modules/payment_gateways/lib/session-idempotency.ts +41 -5
  42. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +14 -1
  43. package/src/modules/payment_gateways/migrations/Migration20261018180000_payment_gateways.ts +18 -0
  44. package/src/modules/payment_gateways/workers/session-initialization-prune.ts +3 -3
  45. package/src/modules/portal/frontend/[orgSlug]/portal/magic-link/page.meta.ts +9 -0
  46. package/src/modules/portal/frontend/[orgSlug]/portal/magic-link/page.tsx +71 -0
  47. package/src/modules/portal/i18n/de.json +7 -0
  48. package/src/modules/portal/i18n/en.json +7 -0
  49. package/src/modules/portal/i18n/es.json +7 -0
  50. package/src/modules/portal/i18n/ko.json +7 -0
  51. package/src/modules/portal/i18n/pl.json +7 -0
  52. package/src/modules/query_index/lib/engine.ts +70 -12
@@ -12,6 +12,13 @@ import { sql } from "kysely";
12
12
  import { readCoverageSnapshot, refreshCoverageSnapshot } from "./coverage.js";
13
13
  import { createProfiler, shouldEnableProfiler } from "@open-mercato/shared/lib/profiler";
14
14
  import { decryptIndexDocCustomFields } from "@open-mercato/shared/lib/encryption/indexDoc";
15
+ import {
16
+ DECLINED_ENCRYPTED_SORT_LOG_MESSAGE,
17
+ DECRYPT_REFUSAL_LOG_MESSAGE,
18
+ DecryptRefusalTally,
19
+ resolveDecryptEnabled,
20
+ resolveDecryptScope
21
+ } from "@open-mercato/shared/lib/encryption/decryptScope";
15
22
  import {
16
23
  buildCustomFieldKindIndex,
17
24
  createCustomFieldKindMapResolver,
@@ -37,7 +44,13 @@ import { tokenizeText } from "@open-mercato/shared/lib/search/tokenize";
37
44
  import { buildContainmentPatterns } from "@open-mercato/shared/lib/search/containment";
38
45
  import { runBeforeQueryPipeline, runAfterQueryPipeline } from "@open-mercato/shared/lib/query/query-extension-runner";
39
46
  import { warnOnCiphertextLikeFallback } from "@open-mercato/shared/lib/query/ciphertext-search-warning";
40
- import { resolveEncryptedSortFields, resolveEncryptedSortMaxRows, sortRowsInMemory } from "@open-mercato/shared/lib/query/encrypted-sort";
47
+ import {
48
+ matchEncryptedSortFields,
49
+ resolveEncryptedFieldNames,
50
+ resolveEncryptedSortFields,
51
+ resolveEncryptedSortMaxRows,
52
+ sortRowsInMemory
53
+ } from "@open-mercato/shared/lib/query/encrypted-sort";
41
54
  import { resolveListCountCap } from "@open-mercato/shared/lib/query/count-cap";
42
55
  import { resolveCfDefIndexOrgCandidates } from "@open-mercato/shared/lib/crud/custom-field-definition-index";
43
56
  import { mapWithConcurrency } from "@open-mercato/shared/lib/query/bounded-decrypt";
@@ -584,7 +597,8 @@ class HybridQueryEngine {
584
597
  return null;
585
598
  };
586
599
  const fallbackOrgId = opts.organizationId ?? (Array.isArray(opts.organizationIds) && opts.organizationIds.length === 1 ? opts.organizationIds[0] : null);
587
- const encSvc = this.getEncryptionService();
600
+ const decryptEnabled = resolveDecryptEnabled(opts);
601
+ const encSvc = decryptEnabled ? this.getEncryptionService() : null;
588
602
  const resolvedSorts = [];
589
603
  for (const sort of opts.sort || []) {
590
604
  const field = String(sort.field);
@@ -595,14 +609,32 @@ class HybridQueryEngine {
595
609
  if (baseField) resolvedSorts.push({ ...sort, field: baseField });
596
610
  }
597
611
  }
598
- const encryptedSortFields = await resolveEncryptedSortFields(
612
+ const plainSortFields = resolvedSorts.filter((sort) => !sort.field.startsWith("cf:")).map((sort) => sort.field);
613
+ const encryptedFieldNames = await resolveEncryptedFieldNames(
599
614
  encSvc,
600
615
  entity,
601
- resolvedSorts.filter((sort) => !sort.field.startsWith("cf:")).map((sort) => sort.field),
602
616
  opts.tenantId ?? null,
603
617
  fallbackOrgId
604
618
  );
619
+ const encryptedSortFields = matchEncryptedSortFields(encryptedFieldNames, plainSortFields);
605
620
  const requiresPlaintextSort = encryptedSortFields.size > 0;
621
+ if (!decryptEnabled && plainSortFields.length > 0) {
622
+ const declinedSortFields = await resolveEncryptedSortFields(
623
+ this.getEncryptionService(),
624
+ entity,
625
+ plainSortFields,
626
+ opts.tenantId ?? null,
627
+ fallbackOrgId
628
+ );
629
+ if (declinedSortFields.size > 0) {
630
+ logger.warn(DECLINED_ENCRYPTED_SORT_LOG_MESSAGE, {
631
+ entity: String(entity),
632
+ sortFields: Array.from(declinedSortFields)
633
+ });
634
+ }
635
+ }
636
+ const requestedFields = opts.fields && opts.fields.length ? opts.fields.map(String) : null;
637
+ const injectsTenantIdForDecrypt = encSvc != null && hasTenantColumn && requestedFields != null && !requestedFields.includes("tenant_id") && !requestedFields.includes("tenantId") && (encryptedFieldNames === null || encryptedFieldNames.length > 0);
606
638
  const cfSortKeys = Array.from(new Set(
607
639
  resolvedSorts.filter((sort) => sort.field.startsWith("cf:")).map((sort) => sort.field.slice(3))
608
640
  ));
@@ -923,6 +955,7 @@ class HybridQueryEngine {
923
955
  if (requiresPlaintextSort) {
924
956
  for (const field of encryptedSortFields) selectFieldSet.add(field);
925
957
  }
958
+ if (injectsTenantIdForDecrypt) selectFieldSet.add("tenant_id");
926
959
  if (opts.includeCustomFields === true) {
927
960
  const entityIds = Array.from(new Set(indexSources.map((src) => String(src.entityId))));
928
961
  try {
@@ -1020,18 +1053,24 @@ class HybridQueryEngine {
1020
1053
  }
1021
1054
  return customFieldKinds;
1022
1055
  };
1056
+ const refusalTally = new DecryptRefusalTally();
1023
1057
  const decryptRow = async (item) => {
1024
1058
  let next = item;
1059
+ if (!encSvc) return next;
1060
+ const decision = resolveDecryptScope({
1061
+ rowTenantId: next?.tenant_id ?? next?.tenantId ?? null,
1062
+ rowOrganizationId: next?.organization_id ?? next?.organizationId ?? null,
1063
+ callerTenantId: opts.tenantId ?? null,
1064
+ callerOrganizationId: fallbackOrgId ?? null
1065
+ });
1066
+ if (!decision.decrypt) {
1067
+ refusalTally.record(decision, next?.id == null ? null : String(next.id));
1068
+ return next;
1069
+ }
1025
1070
  if (encSvc?.decryptEntityPayload) {
1026
1071
  const decrypt = encSvc.decryptEntityPayload.bind(encSvc);
1027
1072
  try {
1028
- const decrypted = await decrypt(
1029
- entity,
1030
- next,
1031
- next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
1032
- next?.organization_id ?? next?.organizationId ?? fallbackOrgId ?? null,
1033
- { em: this.em }
1034
- );
1073
+ const decrypted = await decrypt(entity, next, decision.tenantId, decision.organizationId, { em: this.em });
1035
1074
  next = { ...next, ...decrypted };
1036
1075
  } catch (err) {
1037
1076
  logger.error("Error decrypting entity payload", { err });
@@ -1040,8 +1079,8 @@ class HybridQueryEngine {
1040
1079
  if (encSvc) {
1041
1080
  try {
1042
1081
  const cfScope = {
1043
- tenantId: next?.tenant_id ?? next?.tenantId ?? opts.tenantId ?? null,
1044
- organizationId: next?.organization_id ?? next?.organizationId ?? fallbackOrgId ?? null
1082
+ tenantId: decision.tenantId,
1083
+ organizationId: decision.organizationId
1045
1084
  };
1046
1085
  const resolveKinds = await resolveCustomFieldKinds();
1047
1086
  next = await decryptIndexDocCustomFields(
@@ -1056,6 +1095,10 @@ class HybridQueryEngine {
1056
1095
  }
1057
1096
  return next;
1058
1097
  };
1098
+ const reportDecryptRefusals = () => {
1099
+ if (refusalTally.refused === 0) return;
1100
+ logger.warn(DECRYPT_REFUSAL_LOG_MESSAGE, refusalTally.toLogContext(String(entity)));
1101
+ };
1059
1102
  let items;
1060
1103
  let encryptedSortRowCapWarning;
1061
1104
  if (requiresPlaintextSort) {
@@ -1136,6 +1179,10 @@ class HybridQueryEngine {
1136
1179
  );
1137
1180
  items = await mapWithConcurrency(itemsRaw, DECRYPT_CONCURRENCY, decryptRow);
1138
1181
  }
1182
+ reportDecryptRefusals();
1183
+ if (injectsTenantIdForDecrypt) {
1184
+ for (const row of items) delete row.tenant_id;
1185
+ }
1139
1186
  if (debugEnabled) this.debug("query:complete", { entity, total, items: items.length });
1140
1187
  const typedItems = items;
1141
1188
  let result = { items: typedItems, page, pageSize, total };