@remit/backend 0.0.144 → 0.0.145

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 (91) hide show
  1. package/dist/src/api.d.ts +0 -2
  2. package/dist/src/api.js +9 -80
  3. package/dist/src/auth.d.ts +13 -19
  4. package/dist/src/auth.js +51 -43
  5. package/dist/src/config/api-config.d.ts +20 -0
  6. package/dist/src/config/api-config.js +1 -0
  7. package/dist/src/config/from-env.d.ts +2 -0
  8. package/dist/src/config/from-env.js +65 -0
  9. package/dist/src/config/msoauth-secrets-manager.d.ts +2 -0
  10. package/dist/src/config/msoauth-secrets-manager.js +28 -0
  11. package/dist/src/config/msoauth.d.ts +18 -29
  12. package/dist/src/config/msoauth.js +4 -28
  13. package/dist/src/derive/contentSignature.d.ts +3 -4
  14. package/dist/src/derive/contentSignature.js +4 -13
  15. package/dist/src/derive/contentUrl.d.ts +0 -13
  16. package/dist/src/derive/contentUrl.js +0 -19
  17. package/dist/src/handlers/account-oauth.d.ts +4 -3
  18. package/dist/src/handlers/account-oauth.js +197 -250
  19. package/dist/src/handlers/account.d.ts +4 -3
  20. package/dist/src/handlers/account.js +26 -39
  21. package/dist/src/handlers/address.d.ts +2 -2
  22. package/dist/src/handlers/address.js +11 -19
  23. package/dist/src/handlers/calendar-event.js +18 -26
  24. package/dist/src/handlers/calendar-feed.d.ts +2 -2
  25. package/dist/src/handlers/calendar-feed.js +24 -32
  26. package/dist/src/handlers/calendar-suggestion.js +18 -26
  27. package/dist/src/handlers/calendar.d.ts +1 -1
  28. package/dist/src/handlers/calendar.js +15 -22
  29. package/dist/src/handlers/config.d.ts +5 -5
  30. package/dist/src/handlers/config.js +23 -35
  31. package/dist/src/handlers/filter.d.ts +2 -2
  32. package/dist/src/handlers/filter.js +17 -32
  33. package/dist/src/handlers/folder-role.js +3 -6
  34. package/dist/src/handlers/index.d.ts +88 -2
  35. package/dist/src/handlers/index.js +6 -4
  36. package/dist/src/handlers/label.js +15 -22
  37. package/dist/src/handlers/mailbox.d.ts +3 -3
  38. package/dist/src/handlers/mailbox.js +18 -26
  39. package/dist/src/handlers/me.d.ts +1 -1
  40. package/dist/src/handlers/me.js +26 -47
  41. package/dist/src/handlers/message.d.ts +3 -5
  42. package/dist/src/handlers/message.js +34 -46
  43. package/dist/src/handlers/organize.d.ts +2 -2
  44. package/dist/src/handlers/organize.js +11 -20
  45. package/dist/src/handlers/outbox-attachment.d.ts +5 -3
  46. package/dist/src/handlers/outbox-attachment.js +11 -15
  47. package/dist/src/handlers/outbox.d.ts +3 -3
  48. package/dist/src/handlers/outbox.js +27 -35
  49. package/dist/src/handlers/sync.d.ts +5 -5
  50. package/dist/src/handlers/sync.js +13 -22
  51. package/dist/src/handlers/system-update.js +14 -27
  52. package/dist/src/handlers/thread.js +9 -14
  53. package/dist/src/handlers/unified-threads.js +3 -6
  54. package/dist/src/index.d.ts +4 -1
  55. package/dist/src/index.js +18 -2
  56. package/dist/src/jwt-auth.d.ts +9 -12
  57. package/dist/src/jwt-auth.js +19 -71
  58. package/dist/src/lambda.d.ts +13 -0
  59. package/dist/src/lambda.js +33 -0
  60. package/dist/src/request.d.ts +8 -2
  61. package/dist/src/request.js +16 -6
  62. package/dist/src/response.d.ts +20 -3
  63. package/dist/src/response.js +15 -22
  64. package/dist/src/serve.d.ts +31 -0
  65. package/dist/src/serve.js +117 -0
  66. package/dist/src/service/account-deletion.d.ts +2 -4
  67. package/dist/src/service/account-deletion.js +3 -9
  68. package/dist/src/service/compose-sqlite.d.ts +2 -1
  69. package/dist/src/service/compose-sqlite.js +5 -3
  70. package/dist/src/service/create-remit-client.d.ts +25 -11
  71. package/dist/src/service/create-remit-client.js +96 -115
  72. package/dist/src/service/data-client.js +3 -5
  73. package/dist/src/service/export-identity.d.ts +2 -1
  74. package/dist/src/service/export-identity.js +4 -6
  75. package/dist/src/service/fire-and-forget.js +1 -2
  76. package/dist/src/service/organize.d.ts +7 -10
  77. package/dist/src/service/organize.js +10 -27
  78. package/dist/src/service/queues.d.ts +12 -0
  79. package/dist/src/service/queues.js +1 -0
  80. package/dist/src/service/shared-deps.d.ts +6 -0
  81. package/dist/src/service/shared-deps.js +24 -0
  82. package/dist/src/service/trigger-sync.d.ts +11 -5
  83. package/dist/src/service/trigger-sync.js +10 -18
  84. package/dist/src/types.d.ts +24 -1
  85. package/package.json +20 -8
  86. package/dist/src/data-backend.d.ts +0 -12
  87. package/dist/src/data-backend.js +0 -12
  88. package/dist/src/request-context.d.ts +0 -9
  89. package/dist/src/request-context.js +0 -22
  90. package/dist/src/service/sqs.d.ts +0 -1
  91. package/dist/src/service/sqs.js +0 -3
@@ -4,12 +4,10 @@ import { ContentDisposition, MediaType, MessageCategory, SenderTrust, } from "@r
4
4
  import { logger } from "@remit/logger";
5
5
  import { guardConnectionCursor, isCursorRebuildNeeded, isMessageBodySyncBroken, MailboxCursorPausedError, MoveNotSettledError, materializeBodyParts, NoJunkMailboxError, ownedMessages, resolveFolderPlacement, settledFolder, } from "@remit/mailbox-service";
6
6
  import { isStorageNotFoundError as isStorageNotFoundErrorFromService, parseStorageUri, } from "@remit/storage-service";
7
- import { getAccountConfigIdFromEvent, getSubFromEvent } from "../auth.js";
8
7
  import { deriveAutoMoved } from "../derive/autoMoved.js";
9
8
  import { getContentSigner, } from "../derive/contentSignature.js";
10
- import { buildContentUrl, getContentDeliveryDomain, } from "../derive/contentUrl.js";
9
+ import { buildContentUrl } from "../derive/contentUrl.js";
11
10
  import { deriveSenderTrust } from "../derive/senderTrust.js";
12
- import { getClient } from "../service/data-client.js";
13
11
  import { unwrap } from "../service/refusal.js";
14
12
  export const isStorageNotFoundError = (error) => isStorageNotFoundErrorFromService(error);
15
13
  /**
@@ -144,11 +142,10 @@ export const settleSpamReportBulk = async (messageIds, run) => {
144
142
  };
145
143
  };
146
144
  export const MessageOperations = {
147
- MessageOperations_describeMessage: async (context, ...args) => {
148
- const event = args[0];
149
- const accountConfigId = getAccountConfigIdFromEvent(event);
145
+ MessageOperations_describeMessage: async (context, ctx) => {
146
+ const accountConfigId = ctx.identity.accountConfigId;
150
147
  const { messageId } = context.request.params;
151
- const client = await getClient();
148
+ const client = ctx.client;
152
149
  const ownedAccountId = unwrap(await ownedMessages(client, [messageId], accountConfigId, "read"));
153
150
  const description = await client.message.describe(messageId);
154
151
  const message = description.message[0];
@@ -335,11 +332,11 @@ export const MessageOperations = {
335
332
  logger,
336
333
  });
337
334
  const bodyParts = buildBodyPartResponses(bodyPartRows, {
338
- contentDeliveryDomain: getContentDeliveryDomain(),
335
+ contentDeliveryDomain: ctx.config.contentDeliveryDomain,
339
336
  accountConfigId,
340
337
  accountId,
341
338
  messageId,
342
- sign: getContentSigner(),
339
+ sign: getContentSigner(ctx.config),
343
340
  });
344
341
  const references = description.messageReference.map((ref) => ({
345
342
  messageIdValue: ref.messageIdValue,
@@ -354,11 +351,10 @@ export const MessageOperations = {
354
351
  references,
355
352
  };
356
353
  },
357
- MessageOperations_getRawMessage: async (context, ...args) => {
358
- const event = args[0];
359
- const accountConfigId = getAccountConfigIdFromEvent(event);
354
+ MessageOperations_getRawMessage: async (context, ctx) => {
355
+ const accountConfigId = ctx.identity.accountConfigId;
360
356
  const { messageId } = context.request.params;
361
- const client = await getClient();
357
+ const client = ctx.client;
362
358
  // Cross-tenant ownership guard. Returning raw RFC822 bytes is sensitive,
363
359
  // so resolve the owning account and prove the caller owns it BEFORE any
364
360
  // storage read or backfill. `read` mode throws NotFoundError (→ 404) on a
@@ -421,12 +417,11 @@ export const MessageOperations = {
421
417
  const body = await client.storage.retrieve(stored.bodyStorageKey);
422
418
  return { raw: decodeRawEml(body) };
423
419
  },
424
- MessageOperations_updateMessageFlags: async (context, ...args) => {
425
- const event = args[0];
426
- const accountConfigId = getAccountConfigIdFromEvent(event);
420
+ MessageOperations_updateMessageFlags: async (context, ctx) => {
421
+ const accountConfigId = ctx.identity.accountConfigId;
427
422
  const { messageId } = context.request.params;
428
423
  const { isRead, isStarred, starColor } = context.request.requestBody;
429
- const client = await getClient();
424
+ const client = ctx.client;
430
425
  const accountId = unwrap(await ownedMessages(client, [messageId], accountConfigId, "act"));
431
426
  // FlagQueueService handles: MessageFlag + ThreadMessage updates + SQS event
432
427
  const result = await client.flagQueue.updateFlags(accountConfigId, messageId, accountId, {
@@ -442,15 +437,14 @@ export const MessageOperations = {
442
437
  },
443
438
  };
444
439
  export const MessageBulkOperations = {
445
- MessageBulkOperations_updateFlags: async (context, ...args) => {
446
- const event = args[0];
447
- const accountConfigId = getAccountConfigIdFromEvent(event);
440
+ MessageBulkOperations_updateFlags: async (context, ctx) => {
441
+ const accountConfigId = ctx.identity.accountConfigId;
448
442
  const { messageIds, isRead, isStarred, starColor } = context.request
449
443
  .requestBody;
450
444
  if (messageIds.length === 0) {
451
445
  return { successCount: 0, failureCount: 0 };
452
446
  }
453
- const client = await getClient();
447
+ const client = ctx.client;
454
448
  const accountId = unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
455
449
  // FlagQueueService handles: MessageFlag + ThreadMessage updates + SQS events
456
450
  // Process each message individually (same pattern as moveMessages)
@@ -466,14 +460,13 @@ export const MessageBulkOperations = {
466
460
  failureCount: 0,
467
461
  };
468
462
  },
469
- MessageBulkOperations_deleteMessages: async (context, ...args) => {
470
- const event = args[0];
471
- const accountConfigId = getAccountConfigIdFromEvent(event);
463
+ MessageBulkOperations_deleteMessages: async (context, ctx) => {
464
+ const accountConfigId = ctx.identity.accountConfigId;
472
465
  const { messageIds, permanent } = context.request.requestBody;
473
466
  if (messageIds.length === 0) {
474
467
  return { successCount: 0, failureCount: 0 };
475
468
  }
476
- const client = await getClient();
469
+ const client = ctx.client;
477
470
  const accountId = unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
478
471
  // MessageMoveService handles: Message + ThreadMessage updates + SQS events
479
472
  const { refusedMessageIds } = await client.messageMove.deleteMessages(accountConfigId, messageIds, accountId, {
@@ -489,15 +482,14 @@ export const MessageBulkOperations = {
489
482
  failureCount: refusedMessageIds.length,
490
483
  };
491
484
  },
492
- MessageBulkOperations_moveMessages: async (context, ...args) => {
493
- const event = args[0];
494
- const accountConfigId = getAccountConfigIdFromEvent(event);
485
+ MessageBulkOperations_moveMessages: async (context, ctx) => {
486
+ const accountConfigId = ctx.identity.accountConfigId;
495
487
  const { messageIds, destinationMailboxId } = context.request
496
488
  .requestBody;
497
489
  if (messageIds.length === 0) {
498
490
  return { successCount: 0, failureCount: 0 };
499
491
  }
500
- const client = await getClient();
492
+ const client = ctx.client;
501
493
  const accountId = unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
502
494
  // Destination must belong to the same (caller-owned) account, and must be
503
495
  // a folder the mail server has settled: the row records the destination
@@ -515,15 +507,14 @@ export const MessageBulkOperations = {
515
507
  failureCount: 0,
516
508
  };
517
509
  },
518
- MessageBulkOperations_copyMessages: async (context, ...args) => {
519
- const event = args[0];
520
- const accountConfigId = getAccountConfigIdFromEvent(event);
510
+ MessageBulkOperations_copyMessages: async (context, ctx) => {
511
+ const accountConfigId = ctx.identity.accountConfigId;
521
512
  const { messageIds, destinationMailboxId } = context.request
522
513
  .requestBody;
523
514
  if (messageIds.length === 0) {
524
515
  return { successCount: 0, failureCount: 0 };
525
516
  }
526
- const client = await getClient();
517
+ const client = ctx.client;
527
518
  const accountId = unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
528
519
  // Destination must belong to the same (caller-owned) account, and must be
529
520
  // a folder the mail server has settled: the row records the destination
@@ -541,14 +532,13 @@ export const MessageBulkOperations = {
541
532
  failureCount: 0,
542
533
  };
543
534
  },
544
- MessageBulkOperations_updateMessageLabels: async (context, ...args) => {
545
- const event = args[0];
546
- const accountConfigId = getAccountConfigIdFromEvent(event);
535
+ MessageBulkOperations_updateMessageLabels: async (context, ctx) => {
536
+ const accountConfigId = ctx.identity.accountConfigId;
547
537
  const { messageIds, labelId, action } = context.request.requestBody;
548
538
  if (messageIds.length === 0) {
549
539
  return { successCount: 0, failureCount: 0 };
550
540
  }
551
- const client = await getClient();
541
+ const client = ctx.client;
552
542
  unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
553
543
  // Scoped by accountConfigId (RFC 030) — a foreign labelId 404s here rather
554
544
  // than silently applying to messages under someone else's label.
@@ -574,17 +564,16 @@ export const MessageBulkOperations = {
574
564
  failureCount: 0,
575
565
  };
576
566
  },
577
- MessageBulkOperations_reportSpam: async (context, ...args) => {
578
- const event = args[0];
579
- const accountConfigId = getAccountConfigIdFromEvent(event);
567
+ MessageBulkOperations_reportSpam: async (context, ctx) => {
568
+ const accountConfigId = ctx.identity.accountConfigId;
580
569
  const { messageIds: requestedIds } = context.request.requestBody;
581
570
  const messageIds = [...new Set(requestedIds)];
582
571
  if (messageIds.length === 0) {
583
572
  return { successCount: 0, failureCount: 0 };
584
573
  }
585
- const client = await getClient();
574
+ const client = ctx.client;
586
575
  const accountId = unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
587
- const setBy = getSubFromEvent(event) ?? accountConfigId;
576
+ const setBy = ctx.identity.sub;
588
577
  return settleSpamReportBulk(messageIds, (messageId) => client.spamReport.reportSpam({
589
578
  accountConfigId,
590
579
  accountId,
@@ -592,15 +581,14 @@ export const MessageBulkOperations = {
592
581
  setBy,
593
582
  }));
594
583
  },
595
- MessageBulkOperations_notSpam: async (context, ...args) => {
596
- const event = args[0];
597
- const accountConfigId = getAccountConfigIdFromEvent(event);
584
+ MessageBulkOperations_notSpam: async (context, ctx) => {
585
+ const accountConfigId = ctx.identity.accountConfigId;
598
586
  const { messageIds: requestedIds } = context.request.requestBody;
599
587
  const messageIds = [...new Set(requestedIds)];
600
588
  if (messageIds.length === 0) {
601
589
  return { successCount: 0, failureCount: 0 };
602
590
  }
603
- const client = await getClient();
591
+ const client = ctx.client;
604
592
  const accountId = unwrap(await ownedMessages(client, messageIds, accountConfigId, "act"));
605
593
  return settleSpamReportBulk(messageIds, (messageId) => client.spamReport.notSpam({ accountConfigId, accountId, messageId }));
606
594
  },
@@ -6,5 +6,5 @@ import type { OperationHandler, OrganizeJobDetailOperationIds, OrganizeOperation
6
6
  * matcher share.
7
7
  */
8
8
  export declare const predicateFromInput: (input: OrganizeInput) => OrganizePredicate;
9
- export declare const OrganizeOperations: Record<OrganizeOperationIds, OperationHandler<OrganizeOperationIds>>;
10
- export declare const OrganizeJobDetailOperations: Record<OrganizeJobDetailOperationIds, OperationHandler<OrganizeJobDetailOperationIds>>;
9
+ export declare const OrganizeOperations: Record<OrganizeOperationIds, OperationHandler<OrganizeOperationIds, "accountFanout">>;
10
+ export declare const OrganizeJobDetailOperations: Record<OrganizeJobDetailOperationIds, OperationHandler<OrganizeJobDetailOperationIds, "accountFanout">>;
@@ -1,9 +1,6 @@
1
1
  import { BadRequestError, NotFoundError } from "@remit/data-ports/errors";
2
2
  import { logger } from "@remit/logger";
3
3
  import { ownedAccount, queueOrganizeJob, } from "@remit/mailbox-service";
4
- import { env } from "expect-env";
5
- import { getAccountConfigIdFromEvent, getSubFromEvent } from "../auth.js";
6
- import { getClient } from "../service/data-client.js";
7
4
  import { buildOrganizeMatchDeps, matchOrganize, ORGANIZE_MATCH_LIMIT, organizePredicateRejection, } from "../service/organize.js";
8
5
  import { unwrap } from "../service/refusal.js";
9
6
  /**
@@ -55,20 +52,16 @@ const requestedJob = async (client, owner, input) => {
55
52
  return { ...owner, predicate };
56
53
  };
57
54
  export const OrganizeOperations = {
58
- OrganizeOperations_createOrganizeJob: async (context, ...args) => {
59
- const event = args[0];
60
- const accountConfigId = getAccountConfigIdFromEvent(event);
55
+ OrganizeOperations_createOrganizeJob: async (context, ctx) => {
56
+ const accountConfigId = ctx.identity.accountConfigId;
61
57
  const { accountId } = context.request.params;
62
58
  const input = context.request.requestBody;
63
- const userId = getSubFromEvent(event);
64
- if (!userId) {
65
- throw new Error("Missing Cognito `sub`: cannot attribute an organize job to a user");
66
- }
67
- const client = await getClient();
59
+ const userId = ctx.identity.sub;
60
+ const client = ctx.client;
68
61
  await assertAccount(client, accountId, accountConfigId, "act");
69
62
  const job = await queueOrganizeJob({
70
63
  organizeJobRequest: client.organizeJobRequest,
71
- sqsQueueUrl: env.SQS_QUEUE_URL_ACCOUNT_FANOUT,
64
+ queue: client.queues.accountFanout,
72
65
  logger,
73
66
  }, await requestedJob(client, { accountConfigId, userId }, input));
74
67
  return {
@@ -77,12 +70,11 @@ export const OrganizeOperations = {
77
70
  state: job.state,
78
71
  };
79
72
  },
80
- OrganizeOperations_previewOrganize: async (context, ...args) => {
81
- const event = args[0];
82
- const accountConfigId = getAccountConfigIdFromEvent(event);
73
+ OrganizeOperations_previewOrganize: async (context, ctx) => {
74
+ const accountConfigId = ctx.identity.accountConfigId;
83
75
  const { accountId } = context.request.params;
84
76
  const input = context.request.requestBody;
85
- const client = await getClient();
77
+ const client = ctx.client;
86
78
  await assertAccount(client, accountId, accountConfigId, "read");
87
79
  if (input.filterId) {
88
80
  throw new BadRequestError("Preview takes a rule, not a saved filter. Run the filter to apply it.");
@@ -97,11 +89,10 @@ export const OrganizeOperations = {
97
89
  },
98
90
  };
99
91
  export const OrganizeJobDetailOperations = {
100
- OrganizeJobDetailOperations_getOrganizeJob: async (context, ...args) => {
101
- const event = args[0];
102
- const accountConfigId = getAccountConfigIdFromEvent(event);
92
+ OrganizeJobDetailOperations_getOrganizeJob: async (context, ctx) => {
93
+ const accountConfigId = ctx.identity.accountConfigId;
103
94
  const { accountId, organizeJobId } = context.request.params;
104
- const client = await getClient();
95
+ const client = ctx.client;
105
96
  await assertAccount(client, accountId, accountConfigId, "read");
106
97
  const job = await client.organizeJobRequest.get(organizeJobId);
107
98
  if (job.accountConfigId !== accountConfigId) {
@@ -1,7 +1,9 @@
1
1
  import type { MintOutboxAttachmentResponse, OutboxAttachmentRejection, OutboxAttachmentResponse } from "@remit/api-openapi-types";
2
2
  import type { OutboxAttachmentItem } from "@remit/data-ports";
3
3
  import type { Context } from "openapi-backend";
4
- export declare const toOutboxAttachmentResponse: (item: OutboxAttachmentItem) => OutboxAttachmentResponse;
4
+ import type { ApiConfig } from "../config/api-config.js";
5
+ import type { HandlerContext } from "../types.js";
6
+ export declare const toOutboxAttachmentResponse: (config: ApiConfig) => (item: OutboxAttachmentItem) => OutboxAttachmentResponse;
5
7
  interface RejectionResponse {
6
8
  statusCode: number;
7
9
  body: OutboxAttachmentRejection;
@@ -12,12 +14,12 @@ interface RejectionResponse {
12
14
  * lets the upload live on its own fleet and keeps this operation under any
13
15
  * payload ceiling the REST tier has.
14
16
  */
15
- export declare const mintOutboxAttachment: (context: Context, ...args: unknown[]) => Promise<MintOutboxAttachmentResponse | RejectionResponse>;
17
+ export declare const mintOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<MintOutboxAttachmentResponse | RejectionResponse>;
16
18
  /**
17
19
  * Confirm the upload landed. The size in the response is read back from
18
20
  * storage — on a deployment where the browser PUT straight to block storage
19
21
  * this call is the first the API hears of the bytes, and the client's word for
20
22
  * how many there are is not evidence.
21
23
  */
22
- export declare const completeOutboxAttachment: (context: Context, ...args: unknown[]) => Promise<OutboxAttachmentResponse | RejectionResponse>;
24
+ export declare const completeOutboxAttachment: (context: Context, ctx: HandlerContext) => Promise<OutboxAttachmentResponse | RejectionResponse>;
23
25
  export {};
@@ -1,9 +1,7 @@
1
1
  import { OutboxAttachmentRejectionReason } from "@remit/domain-enums";
2
2
  import { outboxAttachmentContentId } from "@remit/mailbox-service/outbox-attachment-content";
3
- import { getAccountConfigIdFromEvent } from "../auth.js";
4
3
  import { getContentSigner } from "../derive/contentSignature.js";
5
- import { buildStorageContentUrl, getContentDeliveryDomain, } from "../derive/contentUrl.js";
6
- import { getClient } from "../service/data-client.js";
4
+ import { buildStorageContentUrl } from "../derive/contentUrl.js";
7
5
  // Every error body the API emits carries a `code` (issue #371). A refusal here
8
6
  // is one kind, so the code is one value and `reason` says which refusal it was.
9
7
  const ATTACHMENT_REJECTED_CODE = "attachment_rejected";
@@ -12,7 +10,7 @@ const TOO_LARGE = new Set([
12
10
  OutboxAttachmentRejectionReason.MessageTooLarge,
13
11
  OutboxAttachmentRejectionReason.TooManyAttachments,
14
12
  ]);
15
- export const toOutboxAttachmentResponse = (item) => ({
13
+ export const toOutboxAttachmentResponse = (config) => (item) => ({
16
14
  outboxAttachmentId: item.outboxAttachmentId,
17
15
  outboxMessageId: item.outboxMessageId,
18
16
  filename: item.filename,
@@ -21,9 +19,9 @@ export const toOutboxAttachmentResponse = (item) => ({
21
19
  state: item.state,
22
20
  contentId: outboxAttachmentContentId(item.outboxAttachmentId),
23
21
  contentUrl: buildStorageContentUrl({
24
- domain: getContentDeliveryDomain(),
22
+ domain: config.contentDeliveryDomain,
25
23
  storageKey: item.storageKey,
26
- sign: getContentSigner(),
24
+ sign: getContentSigner(config),
27
25
  }),
28
26
  });
29
27
  const refuse = (detail) => ({
@@ -42,12 +40,11 @@ const refuse = (detail) => ({
42
40
  * lets the upload live on its own fleet and keeps this operation under any
43
41
  * payload ceiling the REST tier has.
44
42
  */
45
- export const mintOutboxAttachment = async (context, ...args) => {
46
- const event = args[0];
47
- const accountConfigId = getAccountConfigIdFromEvent(event);
43
+ export const mintOutboxAttachment = async (context, ctx) => {
44
+ const accountConfigId = ctx.identity.accountConfigId;
48
45
  const { outboxMessageId } = context.request.params;
49
46
  const input = context.request.requestBody;
50
- const client = await getClient();
47
+ const client = ctx.client;
51
48
  const result = await client.outboxAttachment.mint({
52
49
  accountConfigId,
53
50
  outboxMessageId,
@@ -65,11 +62,10 @@ export const mintOutboxAttachment = async (context, ...args) => {
65
62
  * this call is the first the API hears of the bytes, and the client's word for
66
63
  * how many there are is not evidence.
67
64
  */
68
- export const completeOutboxAttachment = async (context, ...args) => {
69
- const event = args[0];
70
- const accountConfigId = getAccountConfigIdFromEvent(event);
65
+ export const completeOutboxAttachment = async (context, ctx) => {
66
+ const accountConfigId = ctx.identity.accountConfigId;
71
67
  const { outboxMessageId, outboxAttachmentId } = context.request.params;
72
- const client = await getClient();
68
+ const client = ctx.client;
73
69
  const result = await client.outboxAttachment.complete({
74
70
  accountConfigId,
75
71
  outboxMessageId,
@@ -77,5 +73,5 @@ export const completeOutboxAttachment = async (context, ...args) => {
77
73
  });
78
74
  if (result.outcome === "Rejected")
79
75
  return refuse(result.rejection);
80
- return toOutboxAttachmentResponse(result.attachment);
76
+ return toOutboxAttachmentResponse(ctx.config)(result.attachment);
81
77
  };
@@ -1,4 +1,4 @@
1
1
  import type { OperationHandler, OutboxAttachmentOperationIds, OutboxDetailOperationIds, OutboxOperationIds } from "../types.js";
2
- export declare const OutboxOperations: Record<OutboxOperationIds, OperationHandler<OutboxOperationIds>>;
3
- export declare const OutboxDetailOperations: Record<OutboxDetailOperationIds, OperationHandler<OutboxDetailOperationIds>>;
4
- export declare const OutboxAttachmentOperations: Record<OutboxAttachmentOperationIds, OperationHandler<OutboxAttachmentOperationIds>>;
2
+ export declare const OutboxOperations: Record<OutboxOperationIds, OperationHandler<OutboxOperationIds, "smtp">>;
3
+ export declare const OutboxDetailOperations: Record<OutboxDetailOperationIds, OperationHandler<OutboxDetailOperationIds, "smtp">>;
4
+ export declare const OutboxAttachmentOperations: Record<OutboxAttachmentOperationIds, OperationHandler<OutboxAttachmentOperationIds, "smtp">>;
@@ -1,9 +1,7 @@
1
1
  import { ForbiddenError } from "@remit/data-ports/errors";
2
- import { getAccountConfigIdFromEvent } from "../auth.js";
3
- import { getClient } from "../service/data-client.js";
4
2
  import { completeOutboxAttachment, mintOutboxAttachment, toOutboxAttachmentResponse, } from "./outbox-attachment.js";
5
- const toOutboxMessageResponse = (item, attachments = []) => ({
6
- attachments: attachments.map(toOutboxAttachmentResponse),
3
+ const toOutboxMessageResponse = (config, item, attachments = []) => ({
4
+ attachments: attachments.map(toOutboxAttachmentResponse(config)),
7
5
  outboxMessageId: item.outboxMessageId,
8
6
  accountId: item.accountId,
9
7
  fromAddress: item.fromAddress,
@@ -23,11 +21,10 @@ const toOutboxMessageResponse = (item, attachments = []) => ({
23
21
  updatedAt: item.updatedAt,
24
22
  });
25
23
  export const OutboxOperations = {
26
- OutboxOperations_createOutboxMessage: async (_context, ...args) => {
27
- const event = args[0];
28
- const accountConfigId = getAccountConfigIdFromEvent(event);
29
- const input = JSON.parse(event.body ?? "{}");
30
- const client = await getClient();
24
+ OutboxOperations_createOutboxMessage: async (context, ctx) => {
25
+ const accountConfigId = ctx.identity.accountConfigId;
26
+ const input = context.request.requestBody;
27
+ const client = ctx.client;
31
28
  const account = await client.account.get(input.accountId);
32
29
  if (account.accountConfigId !== accountConfigId) {
33
30
  throw new ForbiddenError(`Account ${input.accountId} not in account config`);
@@ -47,7 +44,7 @@ export const OutboxOperations = {
47
44
  inReplyTo: input.inReplyTo,
48
45
  references: input.references,
49
46
  });
50
- return toOutboxMessageResponse(outbox);
47
+ return toOutboxMessageResponse(ctx.config, outbox);
51
48
  }
52
49
  const outbox = await client.outboxQueue.createDraft({
53
50
  accountId: input.accountId,
@@ -62,41 +59,38 @@ export const OutboxOperations = {
62
59
  inReplyTo: input.inReplyTo,
63
60
  references: input.references,
64
61
  });
65
- return toOutboxMessageResponse(outbox);
62
+ return toOutboxMessageResponse(ctx.config, outbox);
66
63
  },
67
- OutboxOperations_listOutboxMessages: async (context, ...args) => {
68
- const event = args[0];
69
- const accountConfigId = getAccountConfigIdFromEvent(event);
64
+ OutboxOperations_listOutboxMessages: async (context, ctx) => {
65
+ const accountConfigId = ctx.identity.accountConfigId;
70
66
  const { continuationToken } = context.request.query;
71
- const client = await getClient();
67
+ const client = ctx.client;
72
68
  const accounts = await client.account.listAllByAccountConfig(accountConfigId);
73
69
  if (accounts.length === 0) {
74
70
  return { items: [], continuationToken: null };
75
71
  }
76
72
  const result = await client.outboxMessage.listByAccounts(accounts.map((account) => account.accountId), { continuationToken });
77
73
  return {
78
- items: result.items.map((item) => toOutboxMessageResponse(item)),
74
+ items: result.items.map((item) => toOutboxMessageResponse(ctx.config, item)),
79
75
  continuationToken: result.continuationToken,
80
76
  };
81
77
  },
82
78
  };
83
79
  export const OutboxDetailOperations = {
84
- OutboxDetailOperations_getOutboxMessage: async (context, ...args) => {
85
- const event = args[0];
86
- const accountConfigId = getAccountConfigIdFromEvent(event);
80
+ OutboxDetailOperations_getOutboxMessage: async (context, ctx) => {
81
+ const accountConfigId = ctx.identity.accountConfigId;
87
82
  const { outboxMessageId } = context.request.params;
88
- const client = await getClient();
83
+ const client = ctx.client;
89
84
  // Default mode "read": the scoped get refuses a foreign message with
90
85
  // NotFound (404, no existence leak) — no separate check needed.
91
86
  const outbox = await client.outboxMessage.get(accountConfigId, outboxMessageId);
92
- return toOutboxMessageResponse(outbox, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
87
+ return toOutboxMessageResponse(ctx.config, outbox, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
93
88
  },
94
- OutboxDetailOperations_updateOutboxMessage: async (context, ...args) => {
95
- const event = args[0];
96
- const accountConfigId = getAccountConfigIdFromEvent(event);
89
+ OutboxDetailOperations_updateOutboxMessage: async (context, ctx) => {
90
+ const accountConfigId = ctx.identity.accountConfigId;
97
91
  const { outboxMessageId } = context.request.params;
98
92
  const input = context.request.requestBody;
99
- const client = await getClient();
93
+ const client = ctx.client;
100
94
  // updateDraft's own scoped get uses mode "act": a foreign message is
101
95
  // denied with Forbidden (403), not feigned as NotFound.
102
96
  const updated = await client.outboxQueue.updateDraft(accountConfigId, outboxMessageId, {
@@ -115,27 +109,25 @@ export const OutboxDetailOperations = {
115
109
  if (input.attachmentIds !== undefined) {
116
110
  await client.outboxAttachment.retainOnly(accountConfigId, updated.accountId, outboxMessageId, input.attachmentIds);
117
111
  }
118
- return toOutboxMessageResponse(updated, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
112
+ return toOutboxMessageResponse(ctx.config, updated, await client.outboxAttachment.listFor(accountConfigId, outboxMessageId));
119
113
  },
120
- OutboxDetailOperations_deleteOutboxMessage: async (context, ...args) => {
121
- const event = args[0];
122
- const accountConfigId = getAccountConfigIdFromEvent(event);
114
+ OutboxDetailOperations_deleteOutboxMessage: async (context, ctx) => {
115
+ const accountConfigId = ctx.identity.accountConfigId;
123
116
  const { outboxMessageId } = context.request.params;
124
- const client = await getClient();
117
+ const client = ctx.client;
125
118
  // deleteDraft's own scoped get uses mode "act": a foreign message is
126
119
  // denied with Forbidden (403), not feigned as NotFound.
127
120
  await client.outboxQueue.deleteDraft(accountConfigId, outboxMessageId);
128
121
  return { statusCode: 204 };
129
122
  },
130
- OutboxDetailOperations_sendOutboxMessage: async (context, ...args) => {
131
- const event = args[0];
132
- const accountConfigId = getAccountConfigIdFromEvent(event);
123
+ OutboxDetailOperations_sendOutboxMessage: async (context, ctx) => {
124
+ const accountConfigId = ctx.identity.accountConfigId;
133
125
  const { outboxMessageId } = context.request.params;
134
- const client = await getClient();
126
+ const client = ctx.client;
135
127
  // send's own scoped get uses mode "act": a foreign message is denied
136
128
  // with Forbidden (403), not feigned as NotFound.
137
129
  const sent = await client.outboxQueue.send(accountConfigId, outboxMessageId);
138
- return toOutboxMessageResponse(sent);
130
+ return toOutboxMessageResponse(ctx.config, sent);
139
131
  },
140
132
  OutboxDetailOperations_mintOutboxAttachment: mintOutboxAttachment,
141
133
  };
@@ -1,9 +1,9 @@
1
- import type { SQSClient } from "@aws-sdk/client-sqs";
1
+ import type { FifoQueueProducer } from "@remit/data-ports";
2
2
  import { type FireAndForgetLogger } from "../service/fire-and-forget.js";
3
+ import { type SyncMailboxesEvent } from "../service/trigger-sync.js";
3
4
  import type { OperationHandler, SyncOperationIds } from "../types.js";
4
5
  interface SyncTriggerDeps {
5
- sqsClient: SQSClient;
6
- queueUrl: string;
6
+ queue: FifoQueueProducer<SyncMailboxesEvent>;
7
7
  logger: FireAndForgetLogger & {
8
8
  info: (fields: Record<string, unknown>, message: string) => void;
9
9
  };
@@ -23,6 +23,6 @@ interface SyncTriggerDeps {
23
23
  * loudly with the alertable structured fields, and resolves to void. A genuine
24
24
  * account-lookup / ownership failure still propagates and 500s as normal.
25
25
  */
26
- export declare const triggerSyncSafe: (accountId: string, accountConfigId: string, deps?: SyncTriggerDeps) => Promise<void>;
27
- export declare const SyncOperations: Record<SyncOperationIds, OperationHandler<SyncOperationIds>>;
26
+ export declare const triggerSyncSafe: (accountId: string, accountConfigId: string, deps: SyncTriggerDeps) => Promise<void>;
27
+ export declare const SyncOperations: Record<SyncOperationIds, OperationHandler<SyncOperationIds, "sync">>;
28
28
  export {};
@@ -1,18 +1,9 @@
1
1
  import { logger } from "@remit/logger";
2
2
  import { ownedAccount } from "@remit/mailbox-service";
3
- import { env } from "expect-env";
4
- import { getAccountConfigIdFromEvent } from "../auth.js";
5
- import { getClient } from "../service/data-client.js";
6
3
  import { fireAndForget, } from "../service/fire-and-forget.js";
7
4
  import { unwrap } from "../service/refusal.js";
8
- import { sqsClient } from "../service/sqs.js";
9
- import { triggerAccountSync } from "../service/trigger-sync.js";
5
+ import { triggerAccountSync, } from "../service/trigger-sync.js";
10
6
  import { toMailboxSyncProgress } from "./sync-progress.js";
11
- const defaultSyncTriggerDeps = () => ({
12
- sqsClient,
13
- queueUrl: env.SQS_QUEUE_URL,
14
- logger,
15
- });
16
7
  /**
17
8
  * Fire-and-forget enqueue for the POST /sync kick.
18
9
  *
@@ -28,11 +19,10 @@ const defaultSyncTriggerDeps = () => ({
28
19
  * loudly with the alertable structured fields, and resolves to void. A genuine
29
20
  * account-lookup / ownership failure still propagates and 500s as normal.
30
21
  */
31
- export const triggerSyncSafe = async (accountId, accountConfigId, deps = defaultSyncTriggerDeps()) => {
22
+ export const triggerSyncSafe = async (accountId, accountConfigId, deps) => {
32
23
  await fireAndForget(async () => {
33
24
  const { eventId } = await triggerAccountSync({
34
- sqsClient: deps.sqsClient,
35
- queueUrl: deps.queueUrl,
25
+ queue: deps.queue,
36
26
  accountId,
37
27
  // POST /sync asks for a sync of this account by name — the refresh
38
28
  // control, pull-to-refresh, and the client's automatic poll all land
@@ -49,23 +39,24 @@ export const triggerSyncSafe = async (accountId, accountConfigId, deps = default
49
39
  });
50
40
  };
51
41
  export const SyncOperations = {
52
- SyncOperations_triggerSync: async (context, ...args) => {
53
- const event = args[0];
54
- const accountConfigId = getAccountConfigIdFromEvent(event);
42
+ SyncOperations_triggerSync: async (context, ctx) => {
43
+ const accountConfigId = ctx.identity.accountConfigId;
55
44
  const { accountId } = context.request.params;
56
- const account = await (await getClient()).account.get(accountId);
45
+ const account = await ctx.client.account.get(accountId);
57
46
  unwrap(ownedAccount(account, accountConfigId, "act"));
58
- void triggerSyncSafe(account.accountId, accountConfigId);
47
+ void triggerSyncSafe(account.accountId, accountConfigId, {
48
+ queue: ctx.client.queues.sync,
49
+ logger,
50
+ });
59
51
  return {
60
52
  triggered: true,
61
53
  message: `Sync triggered for account ${accountId}`,
62
54
  };
63
55
  },
64
- SyncOperations_getSyncStatus: async (context, ...args) => {
65
- const event = args[0];
66
- const accountConfigId = getAccountConfigIdFromEvent(event);
56
+ SyncOperations_getSyncStatus: async (context, ctx) => {
57
+ const accountConfigId = ctx.identity.accountConfigId;
67
58
  const { accountId } = context.request.params;
68
- const client = await getClient();
59
+ const client = ctx.client;
69
60
  const account = await client.account.get(accountId);
70
61
  unwrap(ownedAccount(account, accountConfigId, "read"));
71
62
  // Collect all mailboxes (read-only — does not write or contend with sync lock)