diff --git a/package.json b/package.json index 19500151..fcbb5bb2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "@aws-sdk/client-ses": "^3.929.0", "@nestjs/axios": "^4.0.0", "@nestjs/common": "^11.0.11", "@nestjs/config": "^4.0.1", @@ -30,6 +31,7 @@ "reflect-metadata": "^0.2.2", "rrule": "^2.8.1", "rxjs": "^7.8.2", + "ses": "^1.14.0", "stripe": "^18.1.1", "twilio": "^5.7.0", "winston": "^3.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c45a7f56..5e60688e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@aws-sdk/client-ses': + specifier: ^3.929.0 + version: 3.929.0 '@nestjs/axios': specifier: ^4.0.0 version: 4.0.1(@nestjs/common@11.1.6(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(axios@1.12.2)(rxjs@7.8.2) @@ -98,6 +101,9 @@ importers: rxjs: specifier: ^7.8.2 version: 7.8.2 + ses: + specifier: ^1.14.0 + version: 1.14.0 stripe: specifier: ^18.1.1 version: 18.5.0(@types/node@22.18.6) @@ -216,6 +222,119 @@ packages: resolution: {integrity: sha512-kNOJ+3vekJJCQKWihNmxBkarJzNW09kP5a9E1SRNiQVNOUEeSwcRR0qYotM65nx821gNzjjhJXnAZ8OazWldrg==} engines: {node: ^18.19.1 || ^20.11.1 || >=22.0.0, npm: ^6.11.0 || ^7.5.6 || >=8.0.0, yarn: '>= 1.13.0'} + '@aws-crypto/sha256-browser@5.2.0': + resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} + + '@aws-crypto/sha256-js@5.2.0': + resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==} + engines: {node: '>=16.0.0'} + + '@aws-crypto/supports-web-crypto@5.2.0': + resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==} + + '@aws-crypto/util@5.2.0': + resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} + + '@aws-sdk/client-ses@3.929.0': + resolution: {integrity: sha512-CD0Y+aPV+NLy7lBM6yEs1PiwA7gRaokVU430mTrAA8YeHe9WT3HMlYVM9zrj6rKjzmvgl/OyZMyuzf+VKrxwvA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/client-sso@3.929.0': + resolution: {integrity: sha512-CE1T7PvN2MDRCw96BTUz2Zcnb6Lae3Dl4w3TPB5auBv2sAiIPbQegFUwT2C8teMDGCNXyndzoTvAd4wmO9AcpA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/core@3.928.0': + resolution: {integrity: sha512-e28J2uKjy2uub4u41dNnmzAu0AN3FGB+LRcLN2Qnwl9Oq3kIcByl5sM8ZD+vWpNG+SFUrUasBCq8cMnHxwXZ4w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-env@3.928.0': + resolution: {integrity: sha512-tB8F9Ti0/NFyFVQX8UQtgRik88evtHpyT6WfXOB4bAY6lEnEHA0ubJZmk9y+aUeoE+OsGLx70dC3JUsiiCPJkQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-http@3.928.0': + resolution: {integrity: sha512-67ynC/8UW9Y8Gn1ZZtC3OgcQDGWrJelHmkbgpmmxYUrzVhp+NINtz3wiTzrrBFhPH/8Uy6BxvhMfXhn0ptcMEQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-ini@3.929.0': + resolution: {integrity: sha512-XIzWsJUYeS/DjggHFB53sGGjXdlN/BA6x+Y/JvLbpdkGD2yLISU34/cDPbK/O8BAQCRTCQ69VPa/1AdNgZZRQw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-node@3.929.0': + resolution: {integrity: sha512-GhNZEacpa7fh8GNggshm5S93UK25bCV5aDK8c2vfe7Y3OxBiL89Ox5GUKCu0xIOqiBdfYkI9wvWCFsQRRn7Bjw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-process@3.928.0': + resolution: {integrity: sha512-XL0juran8yhqwn0mreV+NJeHJOkcRBaExsvVn9fXWW37A4gLh4esSJxM2KbSNh0t+/Bk3ehBI5sL9xad+yRDuw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-sso@3.929.0': + resolution: {integrity: sha512-aADe6cLo4+9MUOe0GnC5kUn8IduEKnTxqBlsciZOplU0/0+Rdp9rRh/e9ZBskeIXZ33eO2HG+KDAf1lvtPT7dA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/credential-provider-web-identity@3.929.0': + resolution: {integrity: sha512-L18JtW28xUZVTRHblgqZ8QTVGQfxpMLIuVYgQXrVWiY9Iz9EF4XrfZo3ywCAgqfgLi5pgg3fCxx/pe7uiMOs2w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-host-header@3.922.0': + resolution: {integrity: sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-logger@3.922.0': + resolution: {integrity: sha512-AkvYO6b80FBm5/kk2E636zNNcNgjztNNUxpqVx+huyGn9ZqGTzS4kLqW2hO6CBe5APzVtPCtiQsXL24nzuOlAg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-recursion-detection@3.922.0': + resolution: {integrity: sha512-TtSCEDonV/9R0VhVlCpxZbp/9sxQvTTRKzIf8LxW3uXpby6Wl8IxEciBJlxmSkoqxh542WRcko7NYODlvL/gDA==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/middleware-user-agent@3.928.0': + resolution: {integrity: sha512-ESvcfLx5PtpdUM3ptCwb80toBTd3y5I4w5jaeOPHihiZr7jkRLE/nsaCKzlqscPs6UQ8xI0maav04JUiTskcHw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/nested-clients@3.929.0': + resolution: {integrity: sha512-emR4LTSupxPed1ni0zVxz5msezz/gA1YYXooiW567+NyhvLgSzDvNjK7GPU1waLCj1LrRFe7NkXX1pwa5sPrpw==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/region-config-resolver@3.925.0': + resolution: {integrity: sha512-FOthcdF9oDb1pfQBRCfWPZhJZT5wqpvdAS5aJzB1WDZ+6EuaAhLzLH/fW1slDunIqq1PSQGG3uSnVglVVOvPHQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/token-providers@3.929.0': + resolution: {integrity: sha512-78kph1R6TVJ53VXDKUmt64HMqWjTECLymJ7kLguz2QJiWh2ZdLvpyYGvaueEwwhisHYBh2qef1tGIf/PpEb8SQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/types@3.922.0': + resolution: {integrity: sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-endpoints@3.922.0': + resolution: {integrity: sha512-4ZdQCSuNMY8HMlR1YN4MRDdXuKd+uQTeKIr5/pIM+g3TjInZoj8imvXudjcrFGA63UF3t92YVTkBq88mg58RXQ==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-locate-window@3.893.0': + resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} + engines: {node: '>=18.0.0'} + + '@aws-sdk/util-user-agent-browser@3.922.0': + resolution: {integrity: sha512-qOJAERZ3Plj1st7M4Q5henl5FRpE30uLm6L9edZqZXGR6c7ry9jzexWamWVpQ4H4xVAVmiO9dIEBAfbq4mduOA==} + + '@aws-sdk/util-user-agent-node@3.928.0': + resolution: {integrity: sha512-s0jP67nQLLWVWfBtqTkZUkSWK5e6OI+rs+wFya2h9VLyWBFir17XSDI891s8HZKIVCEl8eBrup+hhywm4nsIAA==} + engines: {node: '>=18.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + + '@aws-sdk/xml-builder@3.921.0': + resolution: {integrity: sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q==} + engines: {node: '>=18.0.0'} + + '@aws/lambda-invoke-store@0.1.1': + resolution: {integrity: sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==} + engines: {node: '>=18.0.0'} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -407,6 +526,15 @@ packages: class-validator: '*' eslint: '>=9.18.0' + '@endo/cache-map@1.1.0': + resolution: {integrity: sha512-owFGshs/97PDw9oguZqU/px8Lv1d0KjAUtDUiPwKHNXRVUE/jyettEbRoTbNJR1OaI8biMn6bHr9kVJsOh6dXw==} + + '@endo/env-options@1.1.11': + resolution: {integrity: sha512-p9OnAPsdqoX4YJsE98e3NBVhIr2iW9gNZxHhAI2/Ul5TdRfoOViItzHzTqrgUVopw6XxA1u1uS6CykLMDUxarA==} + + '@endo/immutable-arraybuffer@1.1.2': + resolution: {integrity: sha512-u+NaYB2aqEugQ3u7w3c5QNkPogf8q/xGgsPaqdY6pUiGWtYiTiFspKFcha6+oeZhWXWQ23rf0KrUq0kfuzqYyQ==} + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -895,6 +1023,182 @@ packages: '@sinonjs/fake-timers@10.3.0': resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@smithy/abort-controller@4.2.5': + resolution: {integrity: sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==} + engines: {node: '>=18.0.0'} + + '@smithy/config-resolver@4.4.3': + resolution: {integrity: sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==} + engines: {node: '>=18.0.0'} + + '@smithy/core@3.18.0': + resolution: {integrity: sha512-vGSDXOJFZgOPTatSI1ly7Gwyy/d/R9zh2TO3y0JZ0uut5qQ88p9IaWaZYIWSSqtdekNM4CGok/JppxbAff4KcQ==} + engines: {node: '>=18.0.0'} + + '@smithy/credential-provider-imds@4.2.5': + resolution: {integrity: sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==} + engines: {node: '>=18.0.0'} + + '@smithy/fetch-http-handler@5.3.6': + resolution: {integrity: sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==} + engines: {node: '>=18.0.0'} + + '@smithy/hash-node@4.2.5': + resolution: {integrity: sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==} + engines: {node: '>=18.0.0'} + + '@smithy/invalid-dependency@4.2.5': + resolution: {integrity: sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==} + engines: {node: '>=18.0.0'} + + '@smithy/is-array-buffer@2.2.0': + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + + '@smithy/is-array-buffer@4.2.0': + resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-content-length@4.2.5': + resolution: {integrity: sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-endpoint@4.3.7': + resolution: {integrity: sha512-i8Mi8OuY6Yi82Foe3iu7/yhBj1HBRoOQwBSsUNYglJTNSFaWYTNM2NauBBs/7pq2sqkLRqeUXA3Ogi2utzpUlQ==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-retry@4.4.7': + resolution: {integrity: sha512-E7Vc6WHCHlzDRTx1W0jZ6J1L6ziEV0PIWcUdmfL4y+c8r7WYr6I+LkQudaD8Nfb7C5c4P3SQ972OmXHtv6m/OA==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-serde@4.2.5': + resolution: {integrity: sha512-La1ldWTJTZ5NqQyPqnCNeH9B+zjFhrNoQIL1jTh4zuqXRlmXhxYHhMtI1/92OlnoAtp6JoN7kzuwhWoXrBwPqg==} + engines: {node: '>=18.0.0'} + + '@smithy/middleware-stack@4.2.5': + resolution: {integrity: sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==} + engines: {node: '>=18.0.0'} + + '@smithy/node-config-provider@4.3.5': + resolution: {integrity: sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==} + engines: {node: '>=18.0.0'} + + '@smithy/node-http-handler@4.4.5': + resolution: {integrity: sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==} + engines: {node: '>=18.0.0'} + + '@smithy/property-provider@4.2.5': + resolution: {integrity: sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==} + engines: {node: '>=18.0.0'} + + '@smithy/protocol-http@5.3.5': + resolution: {integrity: sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-builder@4.2.5': + resolution: {integrity: sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==} + engines: {node: '>=18.0.0'} + + '@smithy/querystring-parser@4.2.5': + resolution: {integrity: sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/service-error-classification@4.2.5': + resolution: {integrity: sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==} + engines: {node: '>=18.0.0'} + + '@smithy/shared-ini-file-loader@4.4.0': + resolution: {integrity: sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==} + engines: {node: '>=18.0.0'} + + '@smithy/signature-v4@5.3.5': + resolution: {integrity: sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==} + engines: {node: '>=18.0.0'} + + '@smithy/smithy-client@4.9.3': + resolution: {integrity: sha512-8tlueuTgV5n7inQCkhyptrB3jo2AO80uGrps/XTYZivv5MFQKKBj3CIWIGMI2fRY5LEduIiazOhAWdFknY1O9w==} + engines: {node: '>=18.0.0'} + + '@smithy/types@4.9.0': + resolution: {integrity: sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==} + engines: {node: '>=18.0.0'} + + '@smithy/url-parser@4.2.5': + resolution: {integrity: sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-base64@4.3.0': + resolution: {integrity: sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-browser@4.2.0': + resolution: {integrity: sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-body-length-node@4.2.1': + resolution: {integrity: sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-buffer-from@2.2.0': + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + + '@smithy/util-buffer-from@4.2.0': + resolution: {integrity: sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==} + engines: {node: '>=18.0.0'} + + '@smithy/util-config-provider@4.2.0': + resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-browser@4.3.6': + resolution: {integrity: sha512-kbpuXbEf2YQ9zEE6eeVnUCQWO0e1BjMnKrXL8rfXgiWA0m8/E0leU4oSNzxP04WfCmW8vjEqaDeXWxwE4tpOjQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-defaults-mode-node@4.2.9': + resolution: {integrity: sha512-dgyribrVWN5qE5usYJ0m5M93mVM3L3TyBPZWe1Xl6uZlH2gzfQx3dz+ZCdW93lWqdedJRkOecnvbnoEEXRZ5VQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-endpoints@3.2.5': + resolution: {integrity: sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==} + engines: {node: '>=18.0.0'} + + '@smithy/util-hex-encoding@4.2.0': + resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-middleware@4.2.5': + resolution: {integrity: sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-retry@4.2.5': + resolution: {integrity: sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==} + engines: {node: '>=18.0.0'} + + '@smithy/util-stream@4.5.6': + resolution: {integrity: sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==} + engines: {node: '>=18.0.0'} + + '@smithy/util-uri-escape@4.2.0': + resolution: {integrity: sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==} + engines: {node: '>=18.0.0'} + + '@smithy/util-utf8@2.3.0': + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + + '@smithy/util-utf8@4.2.0': + resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} + engines: {node: '>=18.0.0'} + + '@smithy/util-waiter@4.2.5': + resolution: {integrity: sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==} + engines: {node: '>=18.0.0'} + + '@smithy/uuid@1.1.0': + resolution: {integrity: sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==} + engines: {node: '>=18.0.0'} + '@tokenizer/inflate@0.2.7': resolution: {integrity: sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==} engines: {node: '>=18'} @@ -1362,6 +1666,9 @@ packages: resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} engines: {node: '>=18'} + bowser@2.12.1: + resolution: {integrity: sha512-z4rE2Gxh7tvshQ4hluIT7XcFrgLIQaw9X3A+kTTRdovCz5PMukm/0QC/BKSYPj3omF5Qfypn9O/c5kgpmvYUCw==} + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1915,6 +2222,10 @@ packages: fast-uri@3.1.0: resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + fast-xml-parser@5.2.5: + resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==} + hasBin: true + fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -3109,6 +3420,9 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + ses@1.14.0: + resolution: {integrity: sha512-T07hNgOfVRTLZGwSS50RnhqrG3foWP+rM+Q5Du4KUQyMLFI3A8YA4RKl0jjZzhihC1ZvDGrWi/JMn4vqbgr/Jg==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -3249,6 +3563,9 @@ packages: '@types/node': optional: true + strnum@2.1.1: + resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==} + strtok3@10.3.4: resolution: {integrity: sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==} engines: {node: '>=18'} @@ -3668,6 +3985,360 @@ snapshots: transitivePeerDependencies: - chokidar + '@aws-crypto/sha256-browser@5.2.0': + dependencies: + '@aws-crypto/sha256-js': 5.2.0 + '@aws-crypto/supports-web-crypto': 5.2.0 + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-locate-window': 3.893.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-crypto/sha256-js@5.2.0': + dependencies: + '@aws-crypto/util': 5.2.0 + '@aws-sdk/types': 3.922.0 + tslib: 2.8.1 + + '@aws-crypto/supports-web-crypto@5.2.0': + dependencies: + tslib: 2.8.1 + + '@aws-crypto/util@5.2.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.8.1 + + '@aws-sdk/client-ses@3.929.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.928.0 + '@aws-sdk/credential-provider-node': 3.929.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.928.0 + '@aws-sdk/region-config-resolver': 3.925.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.928.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.7 + '@smithy/middleware-retry': 4.4.7 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.6 + '@smithy/util-defaults-mode-node': 4.2.9 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + '@smithy/util-waiter': 4.2.5 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/client-sso@3.929.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.928.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.928.0 + '@aws-sdk/region-config-resolver': 3.925.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.928.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.7 + '@smithy/middleware-retry': 4.4.7 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.6 + '@smithy/util-defaults-mode-node': 4.2.9 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/core@3.928.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@aws-sdk/xml-builder': 3.921.0 + '@smithy/core': 3.18.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/signature-v4': 5.3.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-env@3.928.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-http@3.928.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/types': 3.922.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/property-provider': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-ini@3.929.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/credential-provider-env': 3.928.0 + '@aws-sdk/credential-provider-http': 3.928.0 + '@aws-sdk/credential-provider-process': 3.928.0 + '@aws-sdk/credential-provider-sso': 3.929.0 + '@aws-sdk/credential-provider-web-identity': 3.929.0 + '@aws-sdk/nested-clients': 3.929.0 + '@aws-sdk/types': 3.922.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-node@3.929.0': + dependencies: + '@aws-sdk/credential-provider-env': 3.928.0 + '@aws-sdk/credential-provider-http': 3.928.0 + '@aws-sdk/credential-provider-ini': 3.929.0 + '@aws-sdk/credential-provider-process': 3.928.0 + '@aws-sdk/credential-provider-sso': 3.929.0 + '@aws-sdk/credential-provider-web-identity': 3.929.0 + '@aws-sdk/types': 3.922.0 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-process@3.928.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/credential-provider-sso@3.929.0': + dependencies: + '@aws-sdk/client-sso': 3.929.0 + '@aws-sdk/core': 3.928.0 + '@aws-sdk/token-providers': 3.929.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/credential-provider-web-identity@3.929.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/nested-clients': 3.929.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/middleware-host-header@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-logger@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-recursion-detection@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@aws/lambda-invoke-store': 0.1.1 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/middleware-user-agent@3.928.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@smithy/core': 3.18.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/nested-clients@3.929.0': + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/core': 3.928.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.928.0 + '@aws-sdk/region-config-resolver': 3.925.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.928.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/core': 3.18.0 + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/hash-node': 4.2.5 + '@smithy/invalid-dependency': 4.2.5 + '@smithy/middleware-content-length': 4.2.5 + '@smithy/middleware-endpoint': 4.3.7 + '@smithy/middleware-retry': 4.4.7 + '@smithy/middleware-serde': 4.2.5 + '@smithy/middleware-stack': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/node-http-handler': 4.4.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-body-length-node': 4.2.1 + '@smithy/util-defaults-mode-browser': 4.3.6 + '@smithy/util-defaults-mode-node': 4.2.9 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/region-config-resolver@3.925.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@smithy/config-resolver': 4.4.3 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/token-providers@3.929.0': + dependencies: + '@aws-sdk/core': 3.928.0 + '@aws-sdk/nested-clients': 3.929.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + transitivePeerDependencies: + - aws-crt + + '@aws-sdk/types@3.922.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/util-endpoints@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-endpoints': 3.2.5 + tslib: 2.8.1 + + '@aws-sdk/util-locate-window@3.893.0': + dependencies: + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-browser@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.9.0 + bowser: 2.12.1 + tslib: 2.8.1 + + '@aws-sdk/util-user-agent-node@3.928.0': + dependencies: + '@aws-sdk/middleware-user-agent': 3.928.0 + '@aws-sdk/types': 3.922.0 + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@aws-sdk/xml-builder@3.921.0': + dependencies: + '@smithy/types': 4.9.0 + fast-xml-parser: 5.2.5 + tslib: 2.8.1 + + '@aws/lambda-invoke-store@0.1.1': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.27.1 @@ -3893,6 +4564,12 @@ snapshots: - supports-color - typescript + '@endo/cache-map@1.1.0': {} + + '@endo/env-options@1.1.11': {} + + '@endo/immutable-arraybuffer@1.1.2': {} + '@eslint-community/eslint-utils@4.9.0(eslint@9.36.0)': dependencies: eslint: 9.36.0 @@ -4492,6 +5169,286 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 + '@smithy/abort-controller@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/config-resolver@4.4.3': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-config-provider': 4.2.0 + '@smithy/util-endpoints': 3.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/core@3.18.0': + dependencies: + '@smithy/middleware-serde': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-body-length-browser': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-stream': 4.5.6 + '@smithy/util-utf8': 4.2.0 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/credential-provider-imds@4.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + tslib: 2.8.1 + + '@smithy/fetch-http-handler@5.3.6': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + tslib: 2.8.1 + + '@smithy/hash-node@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/invalid-dependency@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/is-array-buffer@2.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/is-array-buffer@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/middleware-content-length@4.2.5': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-endpoint@4.3.7': + dependencies: + '@smithy/core': 3.18.0 + '@smithy/middleware-serde': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + '@smithy/url-parser': 4.2.5 + '@smithy/util-middleware': 4.2.5 + tslib: 2.8.1 + + '@smithy/middleware-retry@4.4.7': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/service-error-classification': 4.2.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-retry': 4.2.5 + '@smithy/uuid': 1.1.0 + tslib: 2.8.1 + + '@smithy/middleware-serde@4.2.5': + dependencies: + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/middleware-stack@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-config-provider@4.3.5': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/shared-ini-file-loader': 4.4.0 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/node-http-handler@4.4.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/querystring-builder': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/property-provider@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/protocol-http@5.3.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/querystring-builder@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + '@smithy/util-uri-escape': 4.2.0 + tslib: 2.8.1 + + '@smithy/querystring-parser@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/service-error-classification@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + + '@smithy/shared-ini-file-loader@4.4.0': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/signature-v4@5.3.5': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-middleware': 4.2.5 + '@smithy/util-uri-escape': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/smithy-client@4.9.3': + dependencies: + '@smithy/core': 3.18.0 + '@smithy/middleware-endpoint': 4.3.7 + '@smithy/middleware-stack': 4.2.5 + '@smithy/protocol-http': 5.3.5 + '@smithy/types': 4.9.0 + '@smithy/util-stream': 4.5.6 + tslib: 2.8.1 + + '@smithy/types@4.9.0': + dependencies: + tslib: 2.8.1 + + '@smithy/url-parser@4.2.5': + dependencies: + '@smithy/querystring-parser': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-base64@4.3.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-body-length-browser@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-body-length-node@4.2.1': + dependencies: + tslib: 2.8.1 + + '@smithy/util-buffer-from@2.2.0': + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-buffer-from@4.2.0': + dependencies: + '@smithy/is-array-buffer': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-config-provider@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-defaults-mode-browser@4.3.6': + dependencies: + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-defaults-mode-node@4.2.9': + dependencies: + '@smithy/config-resolver': 4.4.3 + '@smithy/credential-provider-imds': 4.2.5 + '@smithy/node-config-provider': 4.3.5 + '@smithy/property-provider': 4.2.5 + '@smithy/smithy-client': 4.9.3 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-endpoints@3.2.5': + dependencies: + '@smithy/node-config-provider': 4.3.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-hex-encoding@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-middleware@4.2.5': + dependencies: + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-retry@4.2.5': + dependencies: + '@smithy/service-error-classification': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/util-stream@4.5.6': + dependencies: + '@smithy/fetch-http-handler': 5.3.6 + '@smithy/node-http-handler': 4.4.5 + '@smithy/types': 4.9.0 + '@smithy/util-base64': 4.3.0 + '@smithy/util-buffer-from': 4.2.0 + '@smithy/util-hex-encoding': 4.2.0 + '@smithy/util-utf8': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-uri-escape@4.2.0': + dependencies: + tslib: 2.8.1 + + '@smithy/util-utf8@2.3.0': + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.8.1 + + '@smithy/util-utf8@4.2.0': + dependencies: + '@smithy/util-buffer-from': 4.2.0 + tslib: 2.8.1 + + '@smithy/util-waiter@4.2.5': + dependencies: + '@smithy/abort-controller': 4.2.5 + '@smithy/types': 4.9.0 + tslib: 2.8.1 + + '@smithy/uuid@1.1.0': + dependencies: + tslib: 2.8.1 + '@tokenizer/inflate@0.2.7': dependencies: debug: 4.4.3 @@ -5075,6 +6032,8 @@ snapshots: transitivePeerDependencies: - supports-color + bowser@2.12.1: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -5605,6 +6564,10 @@ snapshots: fast-uri@3.1.0: {} + fast-xml-parser@5.2.5: + dependencies: + strnum: 2.1.1 + fastq@1.19.1: dependencies: reusify: 1.1.0 @@ -6970,6 +7933,12 @@ snapshots: transitivePeerDependencies: - supports-color + ses@1.14.0: + dependencies: + '@endo/cache-map': 1.1.0 + '@endo/env-options': 1.1.11 + '@endo/immutable-arraybuffer': 1.1.2 + setprototypeof@1.2.0: {} shebang-command@2.0.0: @@ -7105,6 +8074,8 @@ snapshots: optionalDependencies: '@types/node': 22.18.6 + strnum@2.1.1: {} + strtok3@10.3.4: dependencies: '@tokenizer/token': 0.3.0 diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index ee8ac1e0..3ba51b7b 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -18,6 +18,7 @@ import { AuthService } from '@/modules/auth/auth.service'; import { LoginDto } from '@/modules/auth/dto/login.dto'; import { CreateUserDto } from '@/modules/auth/dto/signup.dto'; import { UserResponseDto } from '@/modules/auth/dto/user-response.dto'; +import { ResetPasswordDto } from '@/modules/auth/dto/reset-password.dto'; import { UserStatus } from '@/modules/user/enum/userStatus.enum'; import { generateCSRFToken } from '@/utils/csrf.util'; @@ -213,6 +214,18 @@ export class AuthController { ); } + @ApiOperation({ + summary: 'Forgot Password', + description: 'Send a password reset link to the user\'s email', + }) + @ApiResponse({ status: 200, description: 'If that email is registered, a reset link has been sent.' }) + @Post('forgot-password') + @SkipCSRF() + async forgotPassword(@Body('email') email: string): Promise<{ message: string }> { + await this.authService.forgotPassword(email); + return { message: 'If that email is registered, a reset link has been sent.' }; + } + @ApiOperation({ summary: 'User Logout', description: 'Logout and clear authentication cookies', @@ -312,4 +325,17 @@ export class AuthController { }; return { user: safeUser }; } + + @ApiOperation({ + summary: 'Reset Password', + description: 'Reset password using a valid reset token', + }) + @ApiResponse({ status: 200, description: 'Password reset successful' }) + @ApiResponse({ status: 400, description: 'Invalid token or password' }) + @Post('reset-password') + @SkipCSRF() + async resetPassword(@Body() dto: ResetPasswordDto): Promise<{ message: string }> { + await this.authService.resetPassword(dto); + return { message: 'Password reset successful' }; + } } diff --git a/src/modules/auth/auth.module.ts b/src/modules/auth/auth.module.ts index 145f4c15..ce1729d8 100644 --- a/src/modules/auth/auth.module.ts +++ b/src/modules/auth/auth.module.ts @@ -10,9 +10,9 @@ import { AuthService } from '@/modules/auth/auth.service'; import { GoogleStrategy } from '@/modules/auth/strategies/google.strategy'; import { JwtStrategy } from '@/modules/auth/strategies/jwt.strategy'; import { DatabaseModule } from '@/modules/database/database.module'; +import { SesModule } from '@/modules/ses/ses.module'; import { User, userSchema } from '@/modules/user/schema/user.schema'; import { UserModule } from '@/modules/user/user.module'; - @Module({ imports: [ PassportModule, @@ -27,6 +27,7 @@ import { UserModule } from '@/modules/user/user.module'; MongooseModule.forFeature([{ name: User.name, schema: userSchema }]), DatabaseModule, UserModule, + SesModule, ], controllers: [AuthController], providers: [AuthService, JwtStrategy, GoogleStrategy], diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index f5b4f1c7..51b6869c 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,25 +1,39 @@ +import process from 'process'; import { + BadRequestException, ConflictException, Injectable, + NotFoundException, UnauthorizedException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { InjectModel } from '@nestjs/mongoose'; import * as bcrypt from 'bcryptjs'; +import * as crypto from 'crypto'; +import * as fs from 'fs'; import { Model } from 'mongoose'; +import * as path from 'path'; import { EUserRole } from '@/common/constants/user.constant'; import { SALT_ROUNDS } from '@/modules/auth/auth.config'; import { LoginDto } from '@/modules/auth/dto/login.dto'; +import { ResetPasswordDto } from '@/modules/auth/dto/reset-password.dto'; import { CreateUserDto } from '@/modules/auth/dto/signup.dto'; +import { SesService } from '@/modules/ses/ses.service'; import { User, UserDocument } from '@/modules/user/schema/user.schema'; import { generateCSRFToken } from '@/utils/csrf.util'; @Injectable() export class AuthService { + private emailTemplate: string; + constructor( @InjectModel(User.name) private readonly userModel: Model, private readonly jwtService: JwtService, - ) {} + private readonly sesService: SesService, + ) { + const templatePath = path.join(process.cwd(), 'templates', 'email.html'); + this.emailTemplate = fs.readFileSync(templatePath, 'utf-8'); + } async validateUser(email: string, password: string): Promise { const user = await this.userModel @@ -96,11 +110,66 @@ export class AuthService { async checkUserExists(email: string): Promise { const user = await this.userModel.findOne({ email }); - return !!user; + return user !== null; } async getUserById(userId: string): Promise { const user = await this.userModel.findById(userId).exec(); - return user ? (user.toObject() as User) : null; + if (user !== null) { + return user.toObject() as User; + } + return null; + } + + async forgotPassword(email: string): Promise { + const user = await this.userModel.findOne({ email }); + if (user === null) return; + + // Generate a secure random token + const token = crypto.randomBytes(32).toString('hex'); + user.resetPasswordToken = token; + user.resetPasswordExpires = new Date(Date.now() + 15 * 60 * 1000); // 15 minutes + + await user.save(); + + const resetLink = `http://localhost:3000/reset-password?token=${token}`; + const userName = user.firstName || user.email; + + // Replace variables in the template + const html = this.emailTemplate + .replace(/\$\{userName\}/g, userName) + .replace(/\$\{resetLink\}/g, resetLink); + + await this.sesService.sendEmail({ + to: user.email, + subject: 'Reset your Dispatch AI password', + html, + }); + } + + async resetPassword(dto: ResetPasswordDto): Promise { + const { token, password, confirmPassword } = dto; + + if (!token || !password || !confirmPassword) { + throw new BadRequestException('Missing required fields'); + } + if (password !== confirmPassword) { + throw new BadRequestException('Passwords do not match'); + } + if (password.length < 6) { + throw new BadRequestException('Password must be at least 6 characters'); + } + const user = await this.userModel.findOne({ + resetPasswordToken: token, + resetPasswordExpires: { $gt: new Date() }, + }); + + if (user === null) { + throw new NotFoundException('Invalid or expired reset token'); + } + user.password = await bcrypt.hash(password, SALT_ROUNDS); + user.resetPasswordToken = undefined; + user.resetPasswordExpires = undefined; + await user.save(); } } diff --git a/src/modules/auth/dto/reset-password.dto.ts b/src/modules/auth/dto/reset-password.dto.ts new file mode 100644 index 00000000..efd4faf7 --- /dev/null +++ b/src/modules/auth/dto/reset-password.dto.ts @@ -0,0 +1,18 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsString, MinLength } from 'class-validator'; + +export class ResetPasswordDto { + @ApiProperty() + @IsString() + token!: string; + + @ApiProperty() + @IsString() + @MinLength(6) + password!: string; + + @ApiProperty() + @IsString() + @MinLength(6) + confirmPassword!: string; +} \ No newline at end of file diff --git a/src/modules/ses/ses.module.ts b/src/modules/ses/ses.module.ts new file mode 100644 index 00000000..5193e5c5 --- /dev/null +++ b/src/modules/ses/ses.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; + +import { SesService } from '@/modules/ses/ses.service'; + +@Module({ + providers: [SesService], + exports: [SesService], +}) +export class SesModule {} diff --git a/src/modules/ses/ses.service.ts b/src/modules/ses/ses.service.ts new file mode 100644 index 00000000..ddc6a02c --- /dev/null +++ b/src/modules/ses/ses.service.ts @@ -0,0 +1,52 @@ +import { SendEmailCommand, SESClient } from '@aws-sdk/client-ses'; +import { Injectable, Logger } from '@nestjs/common'; +import process from 'process'; + +@Injectable() +export class SesService { + private readonly sesClient: SESClient; + private readonly logger = new Logger(SesService.name); + + constructor() { + this.sesClient = new SESClient({ + region: process.env.AWS_REGION ?? 'us-east-1', + credentials: { + accessKeyId: process.env.AWS_ACCESS_KEY_ID ?? '', + secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY ?? '', + }, + }); + } + + async sendEmail({ + to, + subject, + html, + from, + }: { + to: string; + subject: string; + html: string; + from?: string; + }): Promise { + const sender = + from ?? process.env.SES_FROM_EMAIL ?? 'no-reply@dispatchai.com'; + const params = { + Destination: { ToAddresses: [to] }, + Message: { + Body: { Html: { Charset: 'UTF-8', Data: html } }, + Subject: { Charset: 'UTF-8', Data: subject }, + }, + Source: sender, + }; + + try { + await this.sesClient.send(new SendEmailCommand(params)); + this.logger.log(`Email sent to ${to}`); + } catch (error) { + this.logger.error( + `Failed to send email to ${to}: ${error instanceof Error ? error.message : String(error)}`, + ); + throw error; + } + } +} diff --git a/src/modules/user/schema/user.schema.ts b/src/modules/user/schema/user.schema.ts index d851d40e..62fa18ea 100644 --- a/src/modules/user/schema/user.schema.ts +++ b/src/modules/user/schema/user.schema.ts @@ -12,7 +12,7 @@ Your team is not available to take the call right now. I can take a message for you, or help you book an appointment with your team. What can I do for you today? -你也可以和我说普通话。`; +Thank you!`; @Schema({ timestamps: true }) export class User extends Document { @@ -53,6 +53,12 @@ export class User extends Document { @Prop() statusReason!: string; + @Prop({ required: false }) + resetPasswordToken?: string; + + @Prop({ required: false }) + resetPasswordExpires?: Date; + @Prop() position!: string; diff --git a/templates/email.html b/templates/email.html new file mode 100644 index 00000000..f9bc4a96 --- /dev/null +++ b/templates/email.html @@ -0,0 +1,64 @@ + + + + + + + + + +
+ + + + + + + + + + +
+ DispatchAI Logo +
+

Reset your + password

+
+

Hi ${escapeHtml(userName)},

+

+ We've received a request to reset your password.
+ If you didn't make the request, just ignore this message. Otherwise, you can reset your + password below. +

+ +

Thank you for using Dispatch AI!

+

The Dispatch AI Team

+
+
+ + + + + +
+ Dispatch AI   |   Smart Call Handling AI Agent +   |   getdispatch.ai + + +
+
+
+ bottom line +
+ + \ No newline at end of file