quidproquo-actionprocessor-awslambda 0.1.9 → 0.1.11

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 (86) hide show
  1. package/lib/commonjs/getActionProcessor/core/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js +2 -2
  2. package/lib/commonjs/getActionProcessor/core/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js.map +1 -1
  3. package/lib/commonjs/getActionProcessor/core/userDirectory/getUserDirectoryRevokeRefreshTokenActionProcessor.d.ts +2 -0
  4. package/lib/commonjs/getActionProcessor/core/userDirectory/getUserDirectoryRevokeRefreshTokenActionProcessor.js +41 -0
  5. package/lib/commonjs/getActionProcessor/core/userDirectory/getUserDirectoryRevokeRefreshTokenActionProcessor.js.map +1 -0
  6. package/lib/commonjs/getActionProcessor/core/userDirectory/getUserDirectorySignOutUserActionProcessor.d.ts +2 -0
  7. package/lib/commonjs/getActionProcessor/core/userDirectory/getUserDirectorySignOutUserActionProcessor.js +37 -0
  8. package/lib/commonjs/getActionProcessor/core/userDirectory/getUserDirectorySignOutUserActionProcessor.js.map +1 -0
  9. package/lib/commonjs/getActionProcessor/core/userDirectory/index.js +3 -1
  10. package/lib/commonjs/getActionProcessor/core/userDirectory/index.js.map +1 -1
  11. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.d.ts +2 -0
  12. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js +43 -0
  13. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map +1 -0
  14. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.d.ts +2 -0
  15. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js +26 -0
  16. package/lib/commonjs/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map +1 -0
  17. package/lib/commonjs/getActionProcessor/webserver/email/index.d.ts +2 -0
  18. package/lib/commonjs/getActionProcessor/webserver/email/index.js +19 -0
  19. package/lib/commonjs/getActionProcessor/webserver/email/index.js.map +1 -0
  20. package/lib/commonjs/getActionProcessor/webserver/index.d.ts +1 -0
  21. package/lib/commonjs/getActionProcessor/webserver/index.js +3 -1
  22. package/lib/commonjs/getActionProcessor/webserver/index.js.map +1 -1
  23. package/lib/commonjs/logic/cognito/globalSignOut.d.ts +1 -0
  24. package/lib/commonjs/logic/cognito/globalSignOut.js +28 -0
  25. package/lib/commonjs/logic/cognito/globalSignOut.js.map +1 -0
  26. package/lib/commonjs/logic/cognito/index.d.ts +2 -0
  27. package/lib/commonjs/logic/cognito/index.js +2 -0
  28. package/lib/commonjs/logic/cognito/index.js.map +1 -1
  29. package/lib/commonjs/logic/cognito/refreshToken.js +10 -1
  30. package/lib/commonjs/logic/cognito/refreshToken.js.map +1 -1
  31. package/lib/commonjs/logic/cognito/revokeRefreshToken.d.ts +1 -0
  32. package/lib/commonjs/logic/cognito/revokeRefreshToken.js +33 -0
  33. package/lib/commonjs/logic/cognito/revokeRefreshToken.js.map +1 -0
  34. package/lib/commonjs/logic/s3/generatePresignedUploadUrl.d.ts +1 -1
  35. package/lib/commonjs/logic/s3/generatePresignedUploadUrl.js +4 -1
  36. package/lib/commonjs/logic/s3/generatePresignedUploadUrl.js.map +1 -1
  37. package/lib/commonjs/logic/sesV2/buildRawMimeMessage.d.ts +2 -0
  38. package/lib/commonjs/logic/sesV2/buildRawMimeMessage.js +79 -0
  39. package/lib/commonjs/logic/sesV2/buildRawMimeMessage.js.map +1 -0
  40. package/lib/commonjs/logic/sesV2/sendEmail.d.ts +2 -0
  41. package/lib/commonjs/logic/sesV2/sendEmail.js +48 -0
  42. package/lib/commonjs/logic/sesV2/sendEmail.js.map +1 -0
  43. package/lib/esm/getActionProcessor/core/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js +2 -2
  44. package/lib/esm/getActionProcessor/core/file/getFileGenerateTemporaryUploadSecureUrlActionProcessor.js.map +1 -1
  45. package/lib/esm/getActionProcessor/core/userDirectory/getUserDirectoryRevokeRefreshTokenActionProcessor.d.ts +2 -0
  46. package/lib/esm/getActionProcessor/core/userDirectory/getUserDirectoryRevokeRefreshTokenActionProcessor.js +26 -0
  47. package/lib/esm/getActionProcessor/core/userDirectory/getUserDirectoryRevokeRefreshTokenActionProcessor.js.map +1 -0
  48. package/lib/esm/getActionProcessor/core/userDirectory/getUserDirectorySignOutUserActionProcessor.d.ts +2 -0
  49. package/lib/esm/getActionProcessor/core/userDirectory/getUserDirectorySignOutUserActionProcessor.js +22 -0
  50. package/lib/esm/getActionProcessor/core/userDirectory/getUserDirectorySignOutUserActionProcessor.js.map +1 -0
  51. package/lib/esm/getActionProcessor/core/userDirectory/index.js +4 -0
  52. package/lib/esm/getActionProcessor/core/userDirectory/index.js.map +1 -1
  53. package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.d.ts +2 -0
  54. package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js +28 -0
  55. package/lib/esm/getActionProcessor/webserver/email/getEmailSendEmailActionProcessor.js.map +1 -0
  56. package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.d.ts +2 -0
  57. package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js +11 -0
  58. package/lib/esm/getActionProcessor/webserver/email/getEmailSetDeliveryStatusActionProcessor.js.map +1 -0
  59. package/lib/esm/getActionProcessor/webserver/email/index.d.ts +2 -0
  60. package/lib/esm/getActionProcessor/webserver/email/index.js +7 -0
  61. package/lib/esm/getActionProcessor/webserver/email/index.js.map +1 -0
  62. package/lib/esm/getActionProcessor/webserver/index.d.ts +1 -0
  63. package/lib/esm/getActionProcessor/webserver/index.js +3 -0
  64. package/lib/esm/getActionProcessor/webserver/index.js.map +1 -1
  65. package/lib/esm/logic/cognito/globalSignOut.d.ts +1 -0
  66. package/lib/esm/logic/cognito/globalSignOut.js +15 -0
  67. package/lib/esm/logic/cognito/globalSignOut.js.map +1 -0
  68. package/lib/esm/logic/cognito/index.d.ts +2 -0
  69. package/lib/esm/logic/cognito/index.js +2 -0
  70. package/lib/esm/logic/cognito/index.js.map +1 -1
  71. package/lib/esm/logic/cognito/refreshToken.js +10 -1
  72. package/lib/esm/logic/cognito/refreshToken.js.map +1 -1
  73. package/lib/esm/logic/cognito/revokeRefreshToken.d.ts +1 -0
  74. package/lib/esm/logic/cognito/revokeRefreshToken.js +20 -0
  75. package/lib/esm/logic/cognito/revokeRefreshToken.js.map +1 -0
  76. package/lib/esm/logic/s3/generatePresignedUploadUrl.d.ts +1 -1
  77. package/lib/esm/logic/s3/generatePresignedUploadUrl.js +4 -1
  78. package/lib/esm/logic/s3/generatePresignedUploadUrl.js.map +1 -1
  79. package/lib/esm/logic/sesV2/buildRawMimeMessage.d.ts +2 -0
  80. package/lib/esm/logic/sesV2/buildRawMimeMessage.js +74 -0
  81. package/lib/esm/logic/sesV2/buildRawMimeMessage.js.map +1 -0
  82. package/lib/esm/logic/sesV2/sendEmail.d.ts +2 -0
  83. package/lib/esm/logic/sesV2/sendEmail.js +37 -0
  84. package/lib/esm/logic/sesV2/sendEmail.js.map +1 -0
  85. package/lib/extension-layer/qpq-log-extension/index.js +22 -22
  86. package/package.json +25 -24
@@ -31,7 +31,7 @@ import { createAwsClient } from '../createAwsClient';
31
31
  // fields: presignedPost.fields
32
32
  // };
33
33
  // };
34
- export const generatePresignedUploadUrl = async (bucketName, objectKey, region, expirationMs, correlationId, contentType) => {
34
+ export const generatePresignedUploadUrl = async (bucketName, objectKey, region, expirationMs, correlationId, contentType, contentDisposition) => {
35
35
  // WHEN_REQUIRED disables the SDK v3 default request checksum (CRC32). Otherwise the
36
36
  // presigner bakes an empty-body checksum (x-amz-checksum-crc32=AAAAAA==) into the signed
37
37
  // URL, which never matches the real body the browser PUTs — S3 then rejects the upload and
@@ -44,6 +44,9 @@ export const generatePresignedUploadUrl = async (bucketName, objectKey, region,
44
44
  Bucket: bucketName,
45
45
  Key: objectKey,
46
46
  ContentType: contentType,
47
+ // Signed into the URL: the client's PUT must send the matching Content-Disposition header, and
48
+ // S3 stores it so later GETs serve it back (e.g. 'inline' to preview a PDF instead of downloading).
49
+ ContentDisposition: contentDisposition,
47
50
  Metadata: {
48
51
  ...(correlationId && { 'correlation-id': correlationId }),
49
52
  'upload-timestamp': Date.now().toString(),
@@ -1 +1 @@
1
- {"version":3,"file":"generatePresignedUploadUrl.js","sourceRoot":"","sources":["../../../../src/logic/s3/generatePresignedUploadUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,oEAAoE;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,wDAAwD;AACxD,wBAAwB;AACxB,uBAAuB;AACvB,oBAAoB;AACpB,0BAA0B;AAC1B,qCAAqC;AACrC,sCAAsC;AACtC,mEAAmE;AACnE,4DAA4D;AAE5D,kCAAkC;AAElC,8CAA8C;AAC9C,uBAAuB;AACvB,6DAA6D;AAC7D,MAAM;AAEN,iDAAiD;AACjD,wBAAwB;AACxB,kEAAkE;AAClE,MAAM;AAEN,gEAAgE;AAChE,0BAA0B;AAC1B,sBAAsB;AACtB,8BAA8B;AAC9B,gDAAgD;AAChD,QAAQ;AAER,aAAa;AACb,8BAA8B;AAC9B,mCAAmC;AACnC,OAAO;AACP,KAAK;AAEL,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,EAC7C,UAAkB,EAClB,SAAiB,EACjB,MAAc,EACd,YAAoB,EACpB,aAAsB,EACtB,WAAoB,EACH,EAAE;IACnB,oFAAoF;IACpF,yFAAyF;IACzF,2FAA2F;IAC3F,+FAA+F;IAC/F,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE;QACzC,MAAM;QACN,0BAA0B,EAAE,eAAe;KAC5C,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;QAC5C,MAAM,EAAE,UAAU;QAClB,GAAG,EAAE,SAAS;QACd,WAAW,EAAE,WAAW;QAExB,QAAQ,EAAE;YACR,GAAG,CAAC,aAAa,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAC;YACzD,kBAAkB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAC1C;KACF,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,gBAAgB,EAAE;QACzD,SAAS,EAAE,YAAY,GAAG,IAAI;KAC/B,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC,CAAC","sourcesContent":["import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\n// import { createPresignedPost } from '@aws-sdk/s3-presigned-post';\nimport { createAwsClient } from '../createAwsClient';\n\n// export const generatePresignedUploadFormUrl = async (\n// bucketName: string,\n// objectKey: string,\n// region: string,\n// expirationMs: number,\n// contentType: string | undefined,\n// maxSizeBytes?: number | undefined\n// ): Promise<{ url: string; fields: Record<string, string> }> => {\n// const s3Client = createAwsClient(S3Client, { region });\n\n// const conditions: any[] = [];\n\n// // Add content type condition if provided\n// if (contentType) {\n// conditions.push(['eq', '$Content-Type', contentType]);\n// }\n\n// // Add file size limit condition if provided\n// if (maxSizeBytes) {\n// conditions.push(['content-length-range', 0, maxSizeBytes]);\n// }\n\n// const presignedPost = await createPresignedPost(s3Client, {\n// Bucket: bucketName,\n// Key: objectKey,\n// Conditions: conditions,\n// Expires: Math.floor(expirationMs / 1000),\n// });\n\n// return {\n// url: presignedPost.url,\n// fields: presignedPost.fields\n// };\n// };\n\nexport const generatePresignedUploadUrl = async (\n bucketName: string,\n objectKey: string,\n region: string,\n expirationMs: number,\n correlationId?: string,\n contentType?: string,\n): Promise<string> => {\n // WHEN_REQUIRED disables the SDK v3 default request checksum (CRC32). Otherwise the\n // presigner bakes an empty-body checksum (x-amz-checksum-crc32=AAAAAA==) into the signed\n // URL, which never matches the real body the browser PUTs — S3 then rejects the upload and\n // the failure surfaces in the browser as a CORS error (the error response has no ACAO header).\n const s3Client = createAwsClient(S3Client, {\n region,\n requestChecksumCalculation: 'WHEN_REQUIRED',\n });\n\n const putObjectCommand = new PutObjectCommand({\n Bucket: bucketName,\n Key: objectKey,\n ContentType: contentType,\n\n Metadata: {\n ...(correlationId && { 'correlation-id': correlationId }),\n 'upload-timestamp': Date.now().toString(),\n },\n });\n\n const url = await getSignedUrl(s3Client, putObjectCommand, {\n expiresIn: expirationMs / 1000,\n });\n\n return url;\n};\n"]}
1
+ {"version":3,"file":"generatePresignedUploadUrl.js","sourceRoot":"","sources":["../../../../src/logic/s3/generatePresignedUploadUrl.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,oEAAoE;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,wDAAwD;AACxD,wBAAwB;AACxB,uBAAuB;AACvB,oBAAoB;AACpB,0BAA0B;AAC1B,qCAAqC;AACrC,sCAAsC;AACtC,mEAAmE;AACnE,4DAA4D;AAE5D,kCAAkC;AAElC,8CAA8C;AAC9C,uBAAuB;AACvB,6DAA6D;AAC7D,MAAM;AAEN,iDAAiD;AACjD,wBAAwB;AACxB,kEAAkE;AAClE,MAAM;AAEN,gEAAgE;AAChE,0BAA0B;AAC1B,sBAAsB;AACtB,8BAA8B;AAC9B,gDAAgD;AAChD,QAAQ;AAER,aAAa;AACb,8BAA8B;AAC9B,mCAAmC;AACnC,OAAO;AACP,KAAK;AAEL,MAAM,CAAC,MAAM,0BAA0B,GAAG,KAAK,EAC7C,UAAkB,EAClB,SAAiB,EACjB,MAAc,EACd,YAAoB,EACpB,aAAsB,EACtB,WAAoB,EACpB,kBAA2B,EACV,EAAE;IACnB,oFAAoF;IACpF,yFAAyF;IACzF,2FAA2F;IAC3F,+FAA+F;IAC/F,MAAM,QAAQ,GAAG,eAAe,CAAC,QAAQ,EAAE;QACzC,MAAM;QACN,0BAA0B,EAAE,eAAe;KAC5C,CAAC,CAAC;IAEH,MAAM,gBAAgB,GAAG,IAAI,gBAAgB,CAAC;QAC5C,MAAM,EAAE,UAAU;QAClB,GAAG,EAAE,SAAS;QACd,WAAW,EAAE,WAAW;QACxB,+FAA+F;QAC/F,oGAAoG;QACpG,kBAAkB,EAAE,kBAAkB;QAEtC,QAAQ,EAAE;YACR,GAAG,CAAC,aAAa,IAAI,EAAE,gBAAgB,EAAE,aAAa,EAAE,CAAC;YACzD,kBAAkB,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;SAC1C;KACF,CAAC,CAAC;IAEH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,QAAQ,EAAE,gBAAgB,EAAE;QACzD,SAAS,EAAE,YAAY,GAAG,IAAI;KAC/B,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC,CAAC","sourcesContent":["import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';\nimport { getSignedUrl } from '@aws-sdk/s3-request-presigner';\n\n// import { createPresignedPost } from '@aws-sdk/s3-presigned-post';\nimport { createAwsClient } from '../createAwsClient';\n\n// export const generatePresignedUploadFormUrl = async (\n// bucketName: string,\n// objectKey: string,\n// region: string,\n// expirationMs: number,\n// contentType: string | undefined,\n// maxSizeBytes?: number | undefined\n// ): Promise<{ url: string; fields: Record<string, string> }> => {\n// const s3Client = createAwsClient(S3Client, { region });\n\n// const conditions: any[] = [];\n\n// // Add content type condition if provided\n// if (contentType) {\n// conditions.push(['eq', '$Content-Type', contentType]);\n// }\n\n// // Add file size limit condition if provided\n// if (maxSizeBytes) {\n// conditions.push(['content-length-range', 0, maxSizeBytes]);\n// }\n\n// const presignedPost = await createPresignedPost(s3Client, {\n// Bucket: bucketName,\n// Key: objectKey,\n// Conditions: conditions,\n// Expires: Math.floor(expirationMs / 1000),\n// });\n\n// return {\n// url: presignedPost.url,\n// fields: presignedPost.fields\n// };\n// };\n\nexport const generatePresignedUploadUrl = async (\n bucketName: string,\n objectKey: string,\n region: string,\n expirationMs: number,\n correlationId?: string,\n contentType?: string,\n contentDisposition?: string,\n): Promise<string> => {\n // WHEN_REQUIRED disables the SDK v3 default request checksum (CRC32). Otherwise the\n // presigner bakes an empty-body checksum (x-amz-checksum-crc32=AAAAAA==) into the signed\n // URL, which never matches the real body the browser PUTs — S3 then rejects the upload and\n // the failure surfaces in the browser as a CORS error (the error response has no ACAO header).\n const s3Client = createAwsClient(S3Client, {\n region,\n requestChecksumCalculation: 'WHEN_REQUIRED',\n });\n\n const putObjectCommand = new PutObjectCommand({\n Bucket: bucketName,\n Key: objectKey,\n ContentType: contentType,\n // Signed into the URL: the client's PUT must send the matching Content-Disposition header, and\n // S3 stores it so later GETs serve it back (e.g. 'inline' to preview a PDF instead of downloading).\n ContentDisposition: contentDisposition,\n\n Metadata: {\n ...(correlationId && { 'correlation-id': correlationId }),\n 'upload-timestamp': Date.now().toString(),\n },\n });\n\n const url = await getSignedUrl(s3Client, putObjectCommand, {\n expiresIn: expirationMs / 1000,\n });\n\n return url;\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { EmailSendEmailActionPayload } from 'quidproquo-webserver';
2
+ export declare const buildRawMimeMessage: (payload: EmailSendEmailActionPayload, boundarySeed?: string) => string;
@@ -0,0 +1,74 @@
1
+ const CRLF = '\r\n';
2
+ // Header values must never contain CR/LF, or a caller-supplied string could inject extra headers
3
+ const sanitizeHeaderValue = (value) => value.replace(/[\r\n]+/g, ' ').trim();
4
+ // RFC 2047 encoded-word for header text that isn't printable ascii (e.g. subjects with emoji)
5
+ const encodeHeaderText = (value) => {
6
+ const sanitized = sanitizeHeaderValue(value);
7
+ if (/^[\x20-\x7e]*$/.test(sanitized)) {
8
+ return sanitized;
9
+ }
10
+ return `=?UTF-8?B?${Buffer.from(sanitized, 'utf8').toString('base64')}?=`;
11
+ };
12
+ // RFC 2045 requires base64 body lines of at most 76 characters
13
+ const wrapBase64 = (base64Data) => {
14
+ const lines = base64Data.match(/.{1,76}/g) || [];
15
+ return lines.join(CRLF);
16
+ };
17
+ const buildBodyPart = (contentType, content, boundary) => [
18
+ `--${boundary}`,
19
+ `Content-Type: ${contentType}; charset=UTF-8`,
20
+ 'Content-Transfer-Encoding: base64',
21
+ '',
22
+ wrapBase64(Buffer.from(content, 'utf8').toString('base64')),
23
+ ];
24
+ const buildAttachmentPart = (attachment, boundary) => {
25
+ const filename = encodeHeaderText(attachment.filename);
26
+ const contentDisposition = attachment.contentDisposition
27
+ ? sanitizeHeaderValue(attachment.contentDisposition)
28
+ : `attachment; filename="${filename}"`;
29
+ return [
30
+ `--${boundary}`,
31
+ `Content-Type: ${sanitizeHeaderValue(attachment.mimetype || 'application/octet-stream')}; name="${filename}"`,
32
+ 'Content-Transfer-Encoding: base64',
33
+ `Content-Disposition: ${contentDisposition}`,
34
+ '',
35
+ wrapBase64(attachment.base64Data),
36
+ ];
37
+ };
38
+ // Builds an RFC 5322 message: multipart/mixed wrapping a multipart/alternative body plus one
39
+ // part per attachment. Bcc recipients are deliberately never written into the headers; they
40
+ // travel only in the SES Destination so other recipients can't see them. boundarySeed exists
41
+ // so tests get deterministic output.
42
+ export const buildRawMimeMessage = (payload, boundarySeed = 'qpq') => {
43
+ const mixedBoundary = `----=_${boundarySeed}_mixed`;
44
+ const alternativeBoundary = `----=_${boundarySeed}_alternative`;
45
+ const headers = [
46
+ `From: ${encodeHeaderText(payload.from)}`,
47
+ `To: ${payload.to.map(encodeHeaderText).join(', ')}`,
48
+ ...(payload.cc?.length ? [`Cc: ${payload.cc.map(encodeHeaderText).join(', ')}`] : []),
49
+ ...(payload.replyTo?.length ? [`Reply-To: ${payload.replyTo.map(encodeHeaderText).join(', ')}`] : []),
50
+ `Subject: ${encodeHeaderText(payload.subject)}`,
51
+ 'MIME-Version: 1.0',
52
+ `Content-Type: multipart/mixed; boundary="${mixedBoundary}"`,
53
+ ];
54
+ const bodyParts = [
55
+ ...(payload.bodyText ? buildBodyPart('text/plain', payload.bodyText, alternativeBoundary) : []),
56
+ ...(payload.bodyHtml ? buildBodyPart('text/html', payload.bodyHtml, alternativeBoundary) : []),
57
+ ];
58
+ const attachmentParts = (payload.attachments || []).flatMap((attachment) => buildAttachmentPart(attachment, mixedBoundary));
59
+ const lines = [
60
+ ...headers,
61
+ '',
62
+ `--${mixedBoundary}`,
63
+ `Content-Type: multipart/alternative; boundary="${alternativeBoundary}"`,
64
+ '',
65
+ ...bodyParts,
66
+ `--${alternativeBoundary}--`,
67
+ '',
68
+ ...attachmentParts,
69
+ `--${mixedBoundary}--`,
70
+ '',
71
+ ];
72
+ return lines.join(CRLF);
73
+ };
74
+ //# sourceMappingURL=buildRawMimeMessage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildRawMimeMessage.js","sourceRoot":"","sources":["../../../../src/logic/sesV2/buildRawMimeMessage.ts"],"names":[],"mappings":"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","sourcesContent":["import { QPQBinaryData } from 'quidproquo-core';\nimport { EmailSendEmailActionPayload } from 'quidproquo-webserver';\n\nconst CRLF = '\\r\\n';\n\n// Header values must never contain CR/LF, or a caller-supplied string could inject extra headers\nconst sanitizeHeaderValue = (value: string): string => value.replace(/[\\r\\n]+/g, ' ').trim();\n\n// RFC 2047 encoded-word for header text that isn't printable ascii (e.g. subjects with emoji)\nconst encodeHeaderText = (value: string): string => {\n const sanitized = sanitizeHeaderValue(value);\n\n if (/^[\\x20-\\x7e]*$/.test(sanitized)) {\n return sanitized;\n }\n\n return `=?UTF-8?B?${Buffer.from(sanitized, 'utf8').toString('base64')}?=`;\n};\n\n// RFC 2045 requires base64 body lines of at most 76 characters\nconst wrapBase64 = (base64Data: string): string => {\n const lines = base64Data.match(/.{1,76}/g) || [];\n\n return lines.join(CRLF);\n};\n\nconst buildBodyPart = (contentType: string, content: string, boundary: string): string[] => [\n `--${boundary}`,\n `Content-Type: ${contentType}; charset=UTF-8`,\n 'Content-Transfer-Encoding: base64',\n '',\n wrapBase64(Buffer.from(content, 'utf8').toString('base64')),\n];\n\nconst buildAttachmentPart = (attachment: QPQBinaryData, boundary: string): string[] => {\n const filename = encodeHeaderText(attachment.filename);\n const contentDisposition = attachment.contentDisposition\n ? sanitizeHeaderValue(attachment.contentDisposition)\n : `attachment; filename=\"${filename}\"`;\n\n return [\n `--${boundary}`,\n `Content-Type: ${sanitizeHeaderValue(attachment.mimetype || 'application/octet-stream')}; name=\"${filename}\"`,\n 'Content-Transfer-Encoding: base64',\n `Content-Disposition: ${contentDisposition}`,\n '',\n wrapBase64(attachment.base64Data),\n ];\n};\n\n// Builds an RFC 5322 message: multipart/mixed wrapping a multipart/alternative body plus one\n// part per attachment. Bcc recipients are deliberately never written into the headers; they\n// travel only in the SES Destination so other recipients can't see them. boundarySeed exists\n// so tests get deterministic output.\nexport const buildRawMimeMessage = (payload: EmailSendEmailActionPayload, boundarySeed: string = 'qpq'): string => {\n const mixedBoundary = `----=_${boundarySeed}_mixed`;\n const alternativeBoundary = `----=_${boundarySeed}_alternative`;\n\n const headers = [\n `From: ${encodeHeaderText(payload.from)}`,\n `To: ${payload.to.map(encodeHeaderText).join(', ')}`,\n ...(payload.cc?.length ? [`Cc: ${payload.cc.map(encodeHeaderText).join(', ')}`] : []),\n ...(payload.replyTo?.length ? [`Reply-To: ${payload.replyTo.map(encodeHeaderText).join(', ')}`] : []),\n `Subject: ${encodeHeaderText(payload.subject)}`,\n 'MIME-Version: 1.0',\n `Content-Type: multipart/mixed; boundary=\"${mixedBoundary}\"`,\n ];\n\n const bodyParts = [\n ...(payload.bodyText ? buildBodyPart('text/plain', payload.bodyText, alternativeBoundary) : []),\n ...(payload.bodyHtml ? buildBodyPart('text/html', payload.bodyHtml, alternativeBoundary) : []),\n ];\n\n const attachmentParts = (payload.attachments || []).flatMap((attachment) => buildAttachmentPart(attachment, mixedBoundary));\n\n const lines = [\n ...headers,\n '',\n `--${mixedBoundary}`,\n `Content-Type: multipart/alternative; boundary=\"${alternativeBoundary}\"`,\n '',\n ...bodyParts,\n `--${alternativeBoundary}--`,\n '',\n ...attachmentParts,\n `--${mixedBoundary}--`,\n '',\n ];\n\n return lines.join(CRLF);\n};\n"]}
@@ -0,0 +1,2 @@
1
+ import { EmailSendEmailActionPayload } from 'quidproquo-webserver';
2
+ export declare const sendEmail: (payload: EmailSendEmailActionPayload, region: string) => Promise<string>;
@@ -0,0 +1,37 @@
1
+ import { SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';
2
+ import { createAwsClient } from '../createAwsClient';
3
+ import { buildRawMimeMessage } from './buildRawMimeMessage';
4
+ // SES Simple content can't carry attachments, so those emails go through the raw MIME path.
5
+ // Either way the recipients come from Destination below, which is what keeps bcc addresses
6
+ // out of the message the recipients see.
7
+ const getEmailContent = (payload) => {
8
+ if (payload.attachments?.length) {
9
+ return {
10
+ Raw: { Data: new TextEncoder().encode(buildRawMimeMessage(payload)) },
11
+ };
12
+ }
13
+ return {
14
+ Simple: {
15
+ Subject: { Data: payload.subject, Charset: 'UTF-8' },
16
+ Body: {
17
+ ...(payload.bodyText ? { Text: { Data: payload.bodyText, Charset: 'UTF-8' } } : {}),
18
+ ...(payload.bodyHtml ? { Html: { Data: payload.bodyHtml, Charset: 'UTF-8' } } : {}),
19
+ },
20
+ },
21
+ };
22
+ };
23
+ export const sendEmail = async (payload, region) => {
24
+ const sesClient = createAwsClient(SESv2Client, { region });
25
+ const response = await sesClient.send(new SendEmailCommand({
26
+ FromEmailAddress: payload.from,
27
+ Destination: {
28
+ ToAddresses: payload.to,
29
+ CcAddresses: payload.cc,
30
+ BccAddresses: payload.bcc,
31
+ },
32
+ ReplyToAddresses: payload.replyTo,
33
+ Content: getEmailContent(payload),
34
+ }));
35
+ return response.MessageId || '';
36
+ };
37
+ //# sourceMappingURL=sendEmail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sendEmail.js","sourceRoot":"","sources":["../../../../src/logic/sesV2/sendEmail.ts"],"names":[],"mappings":"AAEA,OAAO,EAAgB,gBAAgB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpF,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,4FAA4F;AAC5F,2FAA2F;AAC3F,yCAAyC;AACzC,MAAM,eAAe,GAAG,CAAC,OAAoC,EAAgB,EAAE;IAC7E,IAAI,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;QAChC,OAAO;YACL,GAAG,EAAE,EAAE,IAAI,EAAE,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,EAAE;SACtE,CAAC;IACJ,CAAC;IAED,OAAO;QACL,MAAM,EAAE;YACN,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE;YACpD,IAAI,EAAE;gBACJ,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBACnF,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACpF;SACF;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,OAAoC,EAAE,MAAc,EAAmB,EAAE;IACvG,MAAM,SAAS,GAAG,eAAe,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAE3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CACnC,IAAI,gBAAgB,CAAC;QACnB,gBAAgB,EAAE,OAAO,CAAC,IAAI;QAC9B,WAAW,EAAE;YACX,WAAW,EAAE,OAAO,CAAC,EAAE;YACvB,WAAW,EAAE,OAAO,CAAC,EAAE;YACvB,YAAY,EAAE,OAAO,CAAC,GAAG;SAC1B;QACD,gBAAgB,EAAE,OAAO,CAAC,OAAO;QACjC,OAAO,EAAE,eAAe,CAAC,OAAO,CAAC;KAClC,CAAC,CACH,CAAC;IAEF,OAAO,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC;AAClC,CAAC,CAAC","sourcesContent":["import { EmailSendEmailActionPayload } from 'quidproquo-webserver';\n\nimport { EmailContent, SendEmailCommand, SESv2Client } from '@aws-sdk/client-sesv2';\n\nimport { createAwsClient } from '../createAwsClient';\nimport { buildRawMimeMessage } from './buildRawMimeMessage';\n\n// SES Simple content can't carry attachments, so those emails go through the raw MIME path.\n// Either way the recipients come from Destination below, which is what keeps bcc addresses\n// out of the message the recipients see.\nconst getEmailContent = (payload: EmailSendEmailActionPayload): EmailContent => {\n if (payload.attachments?.length) {\n return {\n Raw: { Data: new TextEncoder().encode(buildRawMimeMessage(payload)) },\n };\n }\n\n return {\n Simple: {\n Subject: { Data: payload.subject, Charset: 'UTF-8' },\n Body: {\n ...(payload.bodyText ? { Text: { Data: payload.bodyText, Charset: 'UTF-8' } } : {}),\n ...(payload.bodyHtml ? { Html: { Data: payload.bodyHtml, Charset: 'UTF-8' } } : {}),\n },\n },\n };\n};\n\nexport const sendEmail = async (payload: EmailSendEmailActionPayload, region: string): Promise<string> => {\n const sesClient = createAwsClient(SESv2Client, { region });\n\n const response = await sesClient.send(\n new SendEmailCommand({\n FromEmailAddress: payload.from,\n Destination: {\n ToAddresses: payload.to,\n CcAddresses: payload.cc,\n BccAddresses: payload.bcc,\n },\n ReplyToAddresses: payload.replyTo,\n Content: getEmailContent(payload),\n }),\n );\n\n return response.MessageId || '';\n};\n"]}