@remit/backend 0.0.134 → 0.0.135

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/backend",
3
- "version": "0.0.134",
3
+ "version": "0.0.135",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -4,7 +4,11 @@ import {
4
4
  createContentSigner,
5
5
  verifyContentSignature,
6
6
  } from "./contentSignature.js";
7
- import { buildContentUrl, getContentDeliveryDomain } from "./contentUrl.js";
7
+ import {
8
+ buildContentUrl,
9
+ buildStorageContentUrl,
10
+ getContentDeliveryDomain,
11
+ } from "./contentUrl.js";
8
12
 
9
13
  describe("buildContentUrl", () => {
10
14
  it("produces the /content/accounts/{cfg}/{acc}/messages/{msg}/parts/{part} layout the Lambda@Edge expects", () => {
@@ -64,6 +68,18 @@ describe("buildContentUrl", () => {
64
68
  );
65
69
  });
66
70
 
71
+ it("addresses an outbox attachment by its storage key", () => {
72
+ const url = buildStorageContentUrl({
73
+ domain: "https://cdn.test",
74
+ storageKey: "accounts/cfg/acc/outbox/ob-1/attachments/att-1",
75
+ });
76
+
77
+ assert.equal(
78
+ url,
79
+ "https://cdn.test/content/accounts/cfg/acc/outbox/ob-1/attachments/att-1",
80
+ );
81
+ });
82
+
67
83
  it("leaves the URL unsigned when no signer is provided (AWS/Lambda@Edge path)", () => {
68
84
  const url = buildContentUrl({
69
85
  domain: "https://abc123.cloudfront.net",
@@ -26,9 +26,16 @@ export interface BuildContentUrlInput {
26
26
  sign?: ContentSigner;
27
27
  }
28
28
 
29
- export const buildContentUrl = (input: BuildContentUrlInput): string => {
30
- const { domain, accountConfigId, accountId, messageId, partPath, sign } =
31
- input;
29
+ export interface BuildStorageContentUrlInput {
30
+ domain: string;
31
+ storageKey: string;
32
+ sign?: ContentSigner;
33
+ }
34
+
35
+ export const buildStorageContentUrl = (
36
+ input: BuildStorageContentUrlInput,
37
+ ): string => {
38
+ const { domain, storageKey, sign } = input;
32
39
  const normalizedDomain = domain.replace(/\/+$/, "");
33
40
  const base = normalizedDomain.startsWith("http")
34
41
  ? normalizedDomain
@@ -36,17 +43,26 @@ export const buildContentUrl = (input: BuildContentUrlInput): string => {
36
43
  // The signed message is the decoded, account-scoped storage path — the same
37
44
  // value the `/content` route recomputes from `req.path`. Keep it in lockstep
38
45
  // with the verifier's `req.path.replace(/^\/content\//, "")`.
39
- const relativePath = `accounts/${accountConfigId}/${accountId}/messages/${messageId}/parts/${partPath}`;
40
- const safePart = partPath
46
+ const safeKey = storageKey
41
47
  .split("/")
42
48
  .map((segment) => encodeURIComponent(segment))
43
49
  .join("/");
44
- const url = `${base}/content/accounts/${accountConfigId}/${accountId}/messages/${messageId}/parts/${safePart}`;
50
+ const url = `${base}/content/${safeKey}`;
45
51
  if (!sign) return url;
46
- const { exp, sig } = sign(relativePath);
52
+ const { exp, sig } = sign(storageKey);
47
53
  return `${url}?exp=${exp}&sig=${encodeURIComponent(sig)}`;
48
54
  };
49
55
 
56
+ export const buildContentUrl = (input: BuildContentUrlInput): string => {
57
+ const { domain, accountConfigId, accountId, messageId, partPath, sign } =
58
+ input;
59
+ return buildStorageContentUrl({
60
+ domain,
61
+ storageKey: `accounts/${accountConfigId}/${accountId}/messages/${messageId}/parts/${partPath}`,
62
+ sign,
63
+ });
64
+ };
65
+
50
66
  /**
51
67
  * Read the CloudFront distribution domain from the environment. The CDK
52
68
  * wires `CONTENT_DELIVERY_DOMAIN` onto the API Lambda from the
@@ -53,6 +53,8 @@ import {
53
53
  mintOutboxAttachment,
54
54
  } from "./outbox-attachment.js";
55
55
 
56
+ process.env.CONTENT_DELIVERY_DOMAIN = "https://content.test";
57
+
56
58
  const SUB = "cognito-sub-679";
57
59
  const ACCOUNT_CONFIG_ID = deriveAccountConfigId(SUB);
58
60
  const OTHER_ACCOUNT_CONFIG_ID = deriveAccountConfigId("cognito-sub-stranger");
@@ -4,14 +4,21 @@ import type {
4
4
  OutboxAttachmentRejection,
5
5
  OutboxAttachmentResponse,
6
6
  } from "@remit/api-openapi-types";
7
+ import type { OutboxAttachmentItem } from "@remit/data-ports";
7
8
  import { OutboxAttachmentRejectionReason } from "@remit/domain-enums";
8
9
  import type {
9
10
  OutboxAttachmentRejectionDetail,
10
11
  OutboxAttachmentRejectionReasonValue,
11
12
  } from "@remit/mailbox-service";
13
+ import { outboxAttachmentContentId } from "@remit/mailbox-service/outbox-attachment-content";
12
14
  import type { APIGatewayProxyEvent } from "aws-lambda";
13
15
  import type { Context } from "openapi-backend";
14
16
  import { getAccountConfigIdFromEvent } from "../auth.js";
17
+ import { getContentSigner } from "../derive/contentSignature.js";
18
+ import {
19
+ buildStorageContentUrl,
20
+ getContentDeliveryDomain,
21
+ } from "../derive/contentUrl.js";
15
22
  import { getClient } from "../service/data-client.js";
16
23
 
17
24
  // Every error body the API emits carries a `code` (issue #371). A refusal here
@@ -24,6 +31,23 @@ const TOO_LARGE: ReadonlySet<OutboxAttachmentRejectionReasonValue> = new Set([
24
31
  OutboxAttachmentRejectionReason.TooManyAttachments,
25
32
  ]);
26
33
 
34
+ export const toOutboxAttachmentResponse = (
35
+ item: OutboxAttachmentItem,
36
+ ): OutboxAttachmentResponse => ({
37
+ outboxAttachmentId: item.outboxAttachmentId,
38
+ outboxMessageId: item.outboxMessageId,
39
+ filename: item.filename,
40
+ contentType: item.contentType,
41
+ sizeBytes: item.sizeBytes,
42
+ state: item.state,
43
+ contentId: outboxAttachmentContentId(item.outboxAttachmentId),
44
+ contentUrl: buildStorageContentUrl({
45
+ domain: getContentDeliveryDomain(),
46
+ storageKey: item.storageKey,
47
+ sign: getContentSigner(),
48
+ }),
49
+ });
50
+
27
51
  interface RejectionResponse {
28
52
  statusCode: number;
29
53
  body: OutboxAttachmentRejection;
@@ -98,5 +122,5 @@ export const completeOutboxAttachment = async (
98
122
 
99
123
  if (result.outcome === "Rejected") return refuse(result.rejection);
100
124
 
101
- return result.attachment;
125
+ return toOutboxAttachmentResponse(result.attachment);
102
126
  };
@@ -50,6 +50,9 @@ import {
50
50
  } from "../service/data-client.js";
51
51
  import { OutboxDetailOperations, OutboxOperations } from "./outbox.js";
52
52
 
53
+ const CONTENT_DOMAIN = "https://content.test";
54
+ process.env.CONTENT_DELIVERY_DOMAIN = CONTENT_DOMAIN;
55
+
53
56
  const SUB = "cognito-sub-604";
54
57
  const ACCOUNT_CONFIG_ID = deriveAccountConfigId(SUB);
55
58
  const ACCOUNT_ID = "acc-604";
@@ -616,6 +619,31 @@ describe("attachmentIds on a draft update (#679)", () => {
616
619
  assert.equal(attachmentRows.size, 2);
617
620
  });
618
621
 
622
+ it("names each file by its content-id and where its bytes are read from", async () => {
623
+ installClient();
624
+ const { outboxMessageId, ids } = await seed();
625
+
626
+ const response = await patch(outboxMessageId, { subject: "still typing" });
627
+
628
+ const body = JSON.parse(response.body) as {
629
+ attachments: {
630
+ outboxAttachmentId: string;
631
+ contentId: string;
632
+ contentUrl: string;
633
+ }[];
634
+ };
635
+ const [first] = body.attachments.filter(
636
+ (item) => item.outboxAttachmentId === ids[0],
637
+ );
638
+ assert.equal(first.contentId, `${ids[0]}@remit`);
639
+ assert.ok(
640
+ first.contentUrl.startsWith(
641
+ `${CONTENT_DOMAIN}/content/${attachmentRows.get(ids[0])?.storageKey}`,
642
+ ),
643
+ first.contentUrl,
644
+ );
645
+ });
646
+
619
647
  it("present and empty removes every file", async () => {
620
648
  installClient();
621
649
  const { outboxMessageId } = await seed();
@@ -21,24 +21,14 @@ import type {
21
21
  import {
22
22
  completeOutboxAttachment,
23
23
  mintOutboxAttachment,
24
+ toOutboxAttachmentResponse,
24
25
  } from "./outbox-attachment.js";
25
26
 
26
- const toAttachmentResponse = (
27
- item: OutboxAttachmentItem,
28
- ): OutboxMessageResponse["attachments"][number] => ({
29
- outboxAttachmentId: item.outboxAttachmentId,
30
- outboxMessageId: item.outboxMessageId,
31
- filename: item.filename,
32
- contentType: item.contentType,
33
- sizeBytes: item.sizeBytes,
34
- state: item.state,
35
- });
36
-
37
27
  const toOutboxMessageResponse = (
38
28
  item: OutboxMessageItem,
39
29
  attachments: OutboxAttachmentItem[] = [],
40
30
  ): OutboxMessageResponse => ({
41
- attachments: attachments.map(toAttachmentResponse),
31
+ attachments: attachments.map(toOutboxAttachmentResponse),
42
32
  outboxMessageId: item.outboxMessageId,
43
33
  accountId: item.accountId,
44
34
  fromAddress: item.fromAddress,