From 0111276b04bb348e4fb13b6885555c06dfd47682 Mon Sep 17 00:00:00 2001 From: Rikkard29 Date: Fri, 11 Aug 2023 20:21:54 +0200 Subject: [PATCH] Add higher HTTP requests per minute limit request form --- .env.example | 13 +- client/{ => generate-api-key}/index.html | 2 +- client/{ => generate-api-key}/script.js | 0 .../{ => generate-api-key}/show-api-key.html | 0 client/{ => generate-api-key}/style.css | 0 .../higher-request-limit/checkmark-icon.svg | 20 + client/higher-request-limit/index.html | 194 +++++++ .../higher-request-limit/submit-success.html | 46 ++ package.json | 8 +- pnpm-lock.yaml | 515 ++++++++++++++++-- src/app.module.spec.ts | 38 -- src/app.module.ts | 28 +- src/auth/auth.controller.ts | 20 +- src/auth/auth.guard.ts | 12 +- src/auth/auth.module.ts | 2 + src/auth/auth.service.ts | 94 +++- src/auth/dto/HigherRequestLimitDto.ts | 7 + .../utils/generateConfirmationEmailHtml.ts | 243 +++++++++ .../generateNewHigherLimitRequestHtml.ts | 246 +++++++++ src/auth/utils/utils.ts | 49 ++ src/users/user.entity.ts | 10 + src/users/users.module.ts | 12 + src/users/users.service.ts | 22 + src/utils.ts | 23 + test/app.e2e-spec.ts | 3 +- 25 files changed, 1492 insertions(+), 115 deletions(-) rename client/{ => generate-api-key}/index.html (96%) rename client/{ => generate-api-key}/script.js (100%) rename client/{ => generate-api-key}/show-api-key.html (100%) rename client/{ => generate-api-key}/style.css (100%) create mode 100644 client/higher-request-limit/checkmark-icon.svg create mode 100644 client/higher-request-limit/index.html create mode 100644 client/higher-request-limit/submit-success.html delete mode 100644 src/app.module.spec.ts create mode 100644 src/auth/dto/HigherRequestLimitDto.ts create mode 100644 src/auth/utils/generateConfirmationEmailHtml.ts create mode 100644 src/auth/utils/generateNewHigherLimitRequestHtml.ts create mode 100644 src/auth/utils/utils.ts create mode 100644 src/users/user.entity.ts create mode 100644 src/users/users.module.ts create mode 100644 src/users/users.service.ts diff --git a/.env.example b/.env.example index 506b175..ed9dccf 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,15 @@ RATE_LIMIT_TTL_SEC=60 RATE_LIMIT_REQ_COUNT_PUBLIC=20 RATE_LIMIT_REQ_COUNT_AUTH=100 -JWT_SECRET_KEY= \ No newline at end of file +JWT_SECRET_KEY= +RECAPTCHA_SECRET_KEY= +DB_HOST=localhost +DB_PORT=5432 +DB_USER= +DB_PASS= +DB_NAME= +EMAIL_ADDRESS_SENDER= +EMAIL_CLIENT_ID= +EMAIL_CLIENT_SECRET= +EMAIL_REDIRECT_URI= +EMAIL_REFRESH_TOKEN= \ No newline at end of file diff --git a/client/index.html b/client/generate-api-key/index.html similarity index 96% rename from client/index.html rename to client/generate-api-key/index.html index 04caf46..edb6d3d 100644 --- a/client/index.html +++ b/client/generate-api-key/index.html @@ -14,7 +14,7 @@
- +

Your gateway to enhanced cross-chain experiences!

diff --git a/client/script.js b/client/generate-api-key/script.js similarity index 100% rename from client/script.js rename to client/generate-api-key/script.js diff --git a/client/show-api-key.html b/client/generate-api-key/show-api-key.html similarity index 100% rename from client/show-api-key.html rename to client/generate-api-key/show-api-key.html diff --git a/client/style.css b/client/generate-api-key/style.css similarity index 100% rename from client/style.css rename to client/generate-api-key/style.css diff --git a/client/higher-request-limit/checkmark-icon.svg b/client/higher-request-limit/checkmark-icon.svg new file mode 100644 index 0000000..373ecbd --- /dev/null +++ b/client/higher-request-limit/checkmark-icon.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + diff --git a/client/higher-request-limit/index.html b/client/higher-request-limit/index.html new file mode 100644 index 0000000..f7cead0 --- /dev/null +++ b/client/higher-request-limit/index.html @@ -0,0 +1,194 @@ + + + + Higher Request Limit Form + + + + + + +
+
+ +

+ Your gateway to enhanced cross-chain experiences! +

+
+
+

Request Higher Request Limit

+

+ If your use case requires a higher requests per minute limit than the + standard 100, you can submit a request for consideration. Requests for + higher limits are manually reviewed by our team to ensure fair usage + and optimal service quality. Please provide the following details in + your request: +

+
    +
  • Email Address for Notification
  • +
  • Your API Key
  • +
  • Reason for Higher Limit (How you plan to use the API)
  • +
  • Requested Requests Per Minute Limit
  • +
+

+ After your request is submitted, our team will review it to assess its + compatibility with your intended usage. You will be notified via the + provided email address once the review process is complete. +

+
+ + + + + + + + + +
+ + +
+
+
+ + + diff --git a/client/higher-request-limit/submit-success.html b/client/higher-request-limit/submit-success.html new file mode 100644 index 0000000..e15cae9 --- /dev/null +++ b/client/higher-request-limit/submit-success.html @@ -0,0 +1,46 @@ + + + + Form Submission Successful + + + + + +
+ Checkmark Icon +

Form Submission Successful

+

+ Your request has been submitted successfully. Our team will review it + and notify you soon. +

+
+ + diff --git a/package.json b/package.json index 8efb0bd..f597ac2 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@nestjs/platform-express": "^10.0.0", "@nestjs/serve-static": "^4.0.0", "@nestjs/throttler": "4.2.0", + "@nestjs/typeorm": "^10.0.0", "@paraspell/sdk": "^2.0.5", "@polkadot/api": "^10.9.1", "@polkadot/api-base": "^10.9.1", @@ -38,8 +39,12 @@ "axios": "^1.4.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "googleapis": "^124.0.0", + "nodemailer": "^6.9.4", + "pg": "^8.11.2", "reflect-metadata": "^0.1.13", - "rxjs": "^7.8.1" + "rxjs": "^7.8.1", + "typeorm": "^0.3.17" }, "devDependencies": { "@nestjs/cli": "^10.0.0", @@ -48,6 +53,7 @@ "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", + "@types/nodemailer": "^6.4.9", "@types/supertest": "^2.0.12", "@typescript-eslint/eslint-plugin": "^5.59.11", "@typescript-eslint/parser": "^5.59.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d1e2f37..0cef043 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,6 +25,9 @@ dependencies: '@nestjs/throttler': specifier: 4.2.0 version: 4.2.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.1.13) + '@nestjs/typeorm': + specifier: ^10.0.0 + version: 10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17) '@paraspell/sdk': specifier: ^2.0.5 version: 2.0.5(@polkadot/api-base@10.9.1)(@polkadot/api@10.9.1)(@polkadot/apps-config@0.124.1)(@polkadot/types@10.9.1) @@ -49,12 +52,24 @@ dependencies: class-validator: specifier: ^0.14.0 version: 0.14.0 + googleapis: + specifier: ^124.0.0 + version: 124.0.0 + nodemailer: + specifier: ^6.9.4 + version: 6.9.4 + pg: + specifier: ^8.11.2 + version: 8.11.2 reflect-metadata: specifier: ^0.1.13 version: 0.1.13 rxjs: specifier: ^7.8.1 version: 7.8.1 + typeorm: + specifier: ^0.3.17 + version: 0.3.17(pg@8.11.2)(ts-node@10.9.1) devDependencies: '@nestjs/cli': @@ -75,6 +90,9 @@ devDependencies: '@types/node': specifier: ^20.3.1 version: 20.3.1 + '@types/nodemailer': + specifier: ^6.4.9 + version: 6.4.9 '@types/supertest': specifier: ^2.0.12 version: 2.0.12 @@ -546,7 +564,6 @@ packages: engines: {node: '>=12'} dependencies: '@jridgewell/trace-mapping': 0.3.9 - dev: true /@darwinia/types-known@2.8.10: resolution: {integrity: sha512-bdNzf1gOw0is3PvcQJXkjf5jp8j0pT0wDxamkwLdajMymWgi0cReReDlhmGNx4Qvq6gy3TVJ9aok0Nsrr4sjKg==} @@ -1214,7 +1231,6 @@ packages: /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} - dev: true /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} @@ -1234,7 +1250,6 @@ packages: /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: true /@jridgewell/trace-mapping@0.3.18: resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} @@ -1248,7 +1263,6 @@ packages: dependencies: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - dev: true /@kiltprotocol/type-definitions@0.30.0: resolution: {integrity: sha512-1UpPDjX8PFqTFm3lRRfYUPEY9M8KrbpRinf4q4K843lY5GdTxQaevrVdK9/WCHKywLyDa4tSrlUv9KQjrTP4bg==} @@ -1504,6 +1518,23 @@ packages: reflect-metadata: 0.1.13 dev: false + /@nestjs/typeorm@10.0.0(@nestjs/common@10.0.0)(@nestjs/core@10.0.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)(typeorm@0.3.17): + resolution: {integrity: sha512-WQU4HCDTz4UavsFzvGUKDHqi0MO5K47yFoPXdmh+Z/hCNO7SHCMmV9jLiLukM8n5nKUqJ3jDqiljkWBcZPdCtA==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + reflect-metadata: ^0.1.13 + rxjs: ^7.2.0 + typeorm: ^0.3.0 + dependencies: + '@nestjs/common': 10.0.0(class-transformer@0.5.1)(class-validator@0.14.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + '@nestjs/core': 10.0.0(@nestjs/common@10.0.0)(@nestjs/platform-express@10.0.0)(reflect-metadata@0.1.13)(rxjs@7.8.1) + reflect-metadata: 0.1.13 + rxjs: 7.8.1 + typeorm: 0.3.17(pg@8.11.2)(ts-node@10.9.1) + uuid: 9.0.0 + dev: false + /@noble/curves@1.1.0: resolution: {integrity: sha512-091oBExgENk/kGj3AZmtBDMpxQPDtxQABR2B9lb1JbVTs6ytdzZNwvhxQ4MWasRNEzlbEH8jCWFCwhF/Obj5AA==} dependencies: @@ -3028,6 +3059,10 @@ packages: '@open-web3/orml-type-definitions': 0.9.4-26 dev: false + /@sqltools/formatter@1.2.5: + resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} + dev: false + /@subsocial/definitions@0.7.14: resolution: {integrity: sha512-dor5S6/tbY09n40e/dh7qFcqF9slMihOMDTXWBM5hTe8nS/Pf5Zp4/r9WiZxxYLoY2v5MlSqyJxjiSCjTxxjUw==} dependencies: @@ -3106,19 +3141,15 @@ packages: /@tsconfig/node10@1.0.9: resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} - dev: true /@tsconfig/node12@1.0.11: resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} - dev: true /@tsconfig/node14@1.0.3: resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} - dev: true /@tsconfig/node16@1.0.4: resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - dev: true /@types/babel__core@7.20.1: resolution: {integrity: sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==} @@ -3275,6 +3306,12 @@ packages: /@types/node@20.3.1: resolution: {integrity: sha512-EhcH/wvidPy1WeML3TtYFGR83UzjxeWRen9V402T8aUGYsCHOmfoisV3ZSg03gAFIbLq8TnWOJ0f4cALtnSEUg==} + /@types/nodemailer@6.4.9: + resolution: {integrity: sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==} + dependencies: + '@types/node': 20.3.1 + dev: true + /@types/parse-json@4.0.0: resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} dev: true @@ -3658,18 +3695,25 @@ packages: /acorn-walk@8.2.0: resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} engines: {node: '>=0.4.0'} - dev: true /acorn@8.9.0: resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /aes-js@3.0.0: resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} dev: false + /agent-base@7.1.0: + resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /ajv-formats@2.1.1(ajv@8.12.0): resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3722,7 +3766,6 @@ packages: /ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - dev: true /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} @@ -3742,6 +3785,10 @@ packages: engines: {node: '>=10'} dev: true + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: false + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -3750,12 +3797,16 @@ packages: picomatch: 2.3.1 dev: true + /app-root-path@3.1.0: + resolution: {integrity: sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==} + engines: {node: '>= 6.0.0'} + dev: false + /append-field@1.0.0: resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} /arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} - dev: true /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -3870,7 +3921,6 @@ packages: /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - dev: true /base-x@3.0.9: resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==} @@ -3885,6 +3935,10 @@ packages: resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} dev: false + /bignumber.js@9.1.1: + resolution: {integrity: sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==} + dev: false + /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} @@ -3959,7 +4013,6 @@ packages: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} dependencies: balanced-match: 1.0.2 - dev: true /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} @@ -4003,6 +4056,11 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + /buffer-writer@2.0.0: + resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==} + engines: {node: '>=4'} + dev: false + /buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} dependencies: @@ -4141,6 +4199,19 @@ packages: restore-cursor: 3.1.0 dev: true + /cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.0 + dev: false + /cli-spinners@2.9.0: resolution: {integrity: sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==} engines: {node: '>=6'} @@ -4160,6 +4231,14 @@ packages: engines: {node: '>= 10'} dev: true + /cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + dev: false + /cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -4167,7 +4246,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true /clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} @@ -4310,7 +4388,6 @@ packages: /create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - dev: true /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} @@ -4341,6 +4418,13 @@ packages: engines: {node: '>= 12'} dev: false + /date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + dependencies: + '@babel/runtime': 7.22.5 + dev: false + /debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -4413,7 +4497,6 @@ packages: /diff@4.0.2: resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} engines: {node: '>=0.3.1'} - dev: true /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -4477,7 +4560,6 @@ packages: /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - dev: true /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} @@ -4535,7 +4617,6 @@ packages: /escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} - dev: true /escape-html@1.0.3: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} @@ -4839,6 +4920,10 @@ packages: type: 2.7.2 dev: false + /extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + dev: false + /external-editor@3.1.0: resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} engines: {node: '>=4'} @@ -5054,7 +5139,6 @@ packages: /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - dev: true /fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -5067,6 +5151,30 @@ packages: /function-bind@1.1.1: resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + /gaxios@6.0.4: + resolution: {integrity: sha512-mwKfHJn7f3pLRfahdEPNyvygXRwjwgsgDPaIIoBRIDkgP4SFyezkYGWQ2aLCfrAnzimSrP+mAg1aSUj5gidXvw==} + engines: {node: '>=14'} + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.1 + is-stream: 2.0.1 + node-fetch: 2.6.11 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /gcp-metadata@6.0.0: + resolution: {integrity: sha512-Ozxyi23/1Ar51wjUT2RDklK+3HxqDr8TLBNK8rBBFQ7T85iIGnXnVusauj06QyqCXRFZig8LZC+TUddWbndlpQ==} + engines: {node: '>=14'} + dependencies: + gaxios: 6.0.4 + json-bigint: 1.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} @@ -5075,7 +5183,6 @@ packages: /get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - dev: true /get-intrinsic@1.2.1: resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==} @@ -5131,6 +5238,17 @@ packages: path-is-absolute: 1.0.1 dev: true + /glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + dev: false + /glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} @@ -5165,6 +5283,48 @@ packages: slash: 3.0.0 dev: true + /google-auth-library@9.0.0: + resolution: {integrity: sha512-IQGjgQoVUAfOk6khqTVMLvWx26R+yPw9uLyb1MNyMQpdKiKt0Fd9sp4NWoINjyGHR8S3iw12hMTYK7O8J07c6Q==} + engines: {node: '>=14'} + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 6.0.4 + gcp-metadata: 6.0.0 + gtoken: 7.0.1 + jws: 4.0.0 + lru-cache: 6.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /googleapis-common@7.0.0: + resolution: {integrity: sha512-58iSybJPQZ8XZNMpjrklICefuOuyJ0lMxfKmBqmaC0/xGT4SiOs4BE60LAOOGtBURy1n8fHa2X2YUNFEWWbXyQ==} + engines: {node: '>=14.0.0'} + dependencies: + extend: 3.0.2 + gaxios: 6.0.4 + google-auth-library: 9.0.0 + qs: 6.11.2 + url-template: 2.0.8 + uuid: 9.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + + /googleapis@124.0.0: + resolution: {integrity: sha512-kNIN8tu33K1pbvKD8m1TQTDcdH+GF7wOm0QFF+2+etBwLM36/z8tUVKFsTVzE25B0aIcbTdxrGBTRZztRF/K8Q==} + engines: {node: '>=14.0.0'} + dependencies: + google-auth-library: 9.0.0 + googleapis-common: 7.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} dev: true @@ -5177,6 +5337,17 @@ packages: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /gtoken@7.0.1: + resolution: {integrity: sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==} + engines: {node: '>=14.0.0'} + dependencies: + gaxios: 6.0.4 + jws: 4.0.0 + transitivePeerDependencies: + - encoding + - supports-color + dev: false + /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} @@ -5226,6 +5397,10 @@ packages: engines: {node: '>=8'} dev: true + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + /hmac-drbg@1.0.1: resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==} dependencies: @@ -5248,6 +5423,16 @@ packages: statuses: 2.0.1 toidentifier: 1.0.1 + /https-proxy-agent@7.0.1: + resolution: {integrity: sha512-Eun8zV0kcYS1g19r78osiQLEFIRspRUDd9tIfBCTBPBeMieF/EsJNL8VI3xOIdYRDEkjQnqOYPsZ2DsWsVsFwQ==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.0 + debug: 4.3.4 + transitivePeerDependencies: + - supports-color + dev: false + /human-signals@1.1.1: resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} engines: {node: '>=8.12.0'} @@ -5299,7 +5484,6 @@ packages: dependencies: once: 1.4.0 wrappy: 1.0.2 - dev: true /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -5389,7 +5573,6 @@ packages: /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - dev: true /is-generator-fn@2.1.0: resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} @@ -5421,7 +5604,6 @@ packages: /is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - dev: true /is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} @@ -5937,6 +6119,12 @@ packages: hasBin: true dev: true + /json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + dependencies: + bignumber.js: 9.1.1 + dev: false + /json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} dev: true @@ -5993,6 +6181,14 @@ packages: safe-buffer: 5.2.1 dev: false + /jwa@2.0.0: + resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + dev: false + /jws@3.2.2: resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==} dependencies: @@ -6000,6 +6196,13 @@ packages: safe-buffer: 5.2.1 dev: false + /jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + dependencies: + jwa: 2.0.0 + safe-buffer: 5.2.1 + dev: false + /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -6104,7 +6307,6 @@ packages: /make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - dev: true /makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} @@ -6203,6 +6405,13 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@5.1.6: + resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + engines: {node: '>=10'} + dependencies: + brace-expansion: 2.0.1 + dev: false + /minimatch@8.0.4: resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==} engines: {node: '>=16 || 14 >=14.17'} @@ -6229,6 +6438,12 @@ packages: dependencies: minimist: 1.2.8 + /mkdirp@2.1.6: + resolution: {integrity: sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==} + engines: {node: '>=10'} + hasBin: true + dev: false + /mock-socket@9.2.1: resolution: {integrity: sha512-aw9F9T9G2zpGipLLhSNh6ZpgUyUl4frcVmRN08uE1NWPWg43Wx6+sGPDbQ7E5iFZZDJW5b5bypMeAEHqTbIFag==} engines: {node: '>= 8'} @@ -6270,6 +6485,14 @@ packages: resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==} dev: true + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: false + /natural-compare-lite@1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -6350,6 +6573,11 @@ packages: resolution: {integrity: sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==} dev: true + /nodemailer@6.9.4: + resolution: {integrity: sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==} + engines: {node: '>=6.0.0'} + dev: false + /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} @@ -6379,7 +6607,6 @@ packages: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: wrappy: 1.0.2 - dev: true /onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} @@ -6461,6 +6688,10 @@ packages: engines: {node: '>=6'} dev: true + /packet-reader@1.0.0: + resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==} + dev: false + /pako@2.1.0: resolution: {integrity: sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==} dev: false @@ -6482,6 +6713,20 @@ packages: lines-and-columns: 1.2.4 dev: true + /parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + dependencies: + parse5: 6.0.1 + dev: false + + /parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + dev: false + + /parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + dev: false + /parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} engines: {node: '>= 0.8'} @@ -6528,6 +6773,70 @@ packages: engines: {node: '>=8'} dev: true + /pg-cloudflare@1.1.1: + resolution: {integrity: sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q==} + requiresBuild: true + dev: false + optional: true + + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} + dev: false + + /pg-int8@1.0.1: + resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} + engines: {node: '>=4.0.0'} + dev: false + + /pg-pool@3.6.1(pg@8.11.2): + resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} + peerDependencies: + pg: '>=8.0' + dependencies: + pg: 8.11.2 + dev: false + + /pg-protocol@1.6.0: + resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} + dev: false + + /pg-types@2.2.0: + resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} + engines: {node: '>=4'} + dependencies: + pg-int8: 1.0.1 + postgres-array: 2.0.0 + postgres-bytea: 1.0.0 + postgres-date: 1.0.7 + postgres-interval: 1.2.0 + dev: false + + /pg@8.11.2: + resolution: {integrity: sha512-l4rmVeV8qTIrrPrIR3kZQqBgSN93331s9i6wiUiLOSk0Q7PmUxZD/m1rQI622l3NfqBby9Ar5PABfS/SulfieQ==} + engines: {node: '>= 8.0.0'} + peerDependencies: + pg-native: '>=3.0.1' + peerDependenciesMeta: + pg-native: + optional: true + dependencies: + buffer-writer: 2.0.0 + packet-reader: 1.0.0 + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.11.2) + pg-protocol: 1.6.0 + pg-types: 2.2.0 + pgpass: 1.0.5 + optionalDependencies: + pg-cloudflare: 1.1.1 + dev: false + + /pgpass@1.0.5: + resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} + dependencies: + split2: 4.2.0 + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -6565,6 +6874,28 @@ packages: - '@polkadot/util-crypto' dev: false + /postgres-array@2.0.0: + resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} + engines: {node: '>=4'} + dev: false + + /postgres-bytea@1.0.0: + resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-date@1.0.7: + resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} + engines: {node: '>=0.10.0'} + dev: false + + /postgres-interval@1.2.0: + resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} + engines: {node: '>=0.10.0'} + dependencies: + xtend: 4.0.2 + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -6646,7 +6977,6 @@ packages: engines: {node: '>=0.6'} dependencies: side-channel: 1.0.4 - dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -6732,7 +7062,6 @@ packages: /require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - dev: true /require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} @@ -6991,6 +7320,11 @@ packages: engines: {node: '>= 8'} dev: true + /split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + dev: false + /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -7025,7 +7359,6 @@ packages: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - dev: true /string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} @@ -7042,7 +7375,6 @@ packages: engines: {node: '>=8'} dependencies: ansi-regex: 5.0.1 - dev: true /strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} @@ -7199,6 +7531,19 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: false + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: false + /through@2.3.8: resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} dev: true @@ -7316,7 +7661,6 @@ packages: typescript: 5.1.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 - dev: true /tsconfig-paths-webpack-plugin@4.0.1: resolution: {integrity: sha512-m5//KzLoKmqu2MVix+dgLKq70MnFi8YL8sdzQZ6DblmCdfuq/y3OqvJd5vMndg2KEVCOeNz8Es4WVZhYInteLw==} @@ -7405,6 +7749,85 @@ packages: /typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + /typeorm@0.3.17(pg@8.11.2)(ts-node@10.9.1): + resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==} + engines: {node: '>= 12.9.0'} + hasBin: true + peerDependencies: + '@google-cloud/spanner': ^5.18.0 + '@sap/hana-client': ^2.12.25 + better-sqlite3: ^7.1.2 || ^8.0.0 + hdb-pool: ^0.1.6 + ioredis: ^5.0.4 + mongodb: ^5.2.0 + mssql: ^9.1.1 + mysql2: ^2.2.5 || ^3.0.1 + oracledb: ^5.1.0 + pg: ^8.5.1 + pg-native: ^3.0.0 + pg-query-stream: ^4.0.0 + redis: ^3.1.1 || ^4.0.0 + sql.js: ^1.4.0 + sqlite3: ^5.0.3 + ts-node: ^10.7.0 + typeorm-aurora-data-api-driver: ^2.0.0 + peerDependenciesMeta: + '@google-cloud/spanner': + optional: true + '@sap/hana-client': + optional: true + better-sqlite3: + optional: true + hdb-pool: + optional: true + ioredis: + optional: true + mongodb: + optional: true + mssql: + optional: true + mysql2: + optional: true + oracledb: + optional: true + pg: + optional: true + pg-native: + optional: true + pg-query-stream: + optional: true + redis: + optional: true + sql.js: + optional: true + sqlite3: + optional: true + ts-node: + optional: true + typeorm-aurora-data-api-driver: + optional: true + dependencies: + '@sqltools/formatter': 1.2.5 + app-root-path: 3.1.0 + buffer: 6.0.3 + chalk: 4.1.2 + cli-highlight: 2.1.11 + date-fns: 2.30.0 + debug: 4.3.4 + dotenv: 16.1.4 + glob: 8.1.0 + mkdirp: 2.1.6 + pg: 8.11.2 + reflect-metadata: 0.1.13 + sha.js: 2.4.11 + ts-node: 10.9.1(@types/node@20.3.1)(typescript@5.1.3) + tslib: 2.6.0 + uuid: 9.0.0 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + dev: false + /typescript@4.9.5: resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} engines: {node: '>=4.2.0'} @@ -7415,7 +7838,6 @@ packages: resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} engines: {node: '>=14.17'} hasBin: true - dev: true /uid@2.0.2: resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==} @@ -7449,6 +7871,10 @@ packages: punycode: 2.3.0 dev: true + /url-template@2.0.8: + resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} + dev: false + /utf-8-validate@5.0.10: resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==} engines: {node: '>=6.14.2'} @@ -7476,7 +7902,6 @@ packages: /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - dev: true /v8-to-istanbul@9.1.0: resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==} @@ -7660,11 +8085,9 @@ packages: ansi-styles: 4.3.0 string-width: 4.2.3 strip-ansi: 6.0.1 - dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - dev: true /write-file-atomic@4.0.2: resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} @@ -7713,7 +8136,6 @@ packages: /y18n@5.0.8: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} - dev: true /yaeti@0.0.6: resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==} @@ -7732,10 +8154,27 @@ packages: engines: {node: '>= 6'} dev: true + /yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: false + /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - dev: true + + /yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + dependencies: + cliui: 7.0.4 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + dev: false /yargs@17.7.2: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} @@ -7748,12 +8187,10 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true /yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} - dev: true /yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} diff --git a/src/app.module.spec.ts b/src/app.module.spec.ts deleted file mode 100644 index ef0591a..0000000 --- a/src/app.module.spec.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Test, TestingModule } from '@nestjs/testing'; -import { AppModule } from './app.module'; -import { AppController } from './app.controller'; -import { XTransferModule } from './x-transfer/x-transfer.module'; -import { AssetsModule } from './assets/assets.module'; -import { ChannelsModule } from './channels/channels.module'; -import { PalletsModule } from './pallets/pallets.module'; - -describe('AppModule', () => { - let appModule: TestingModule; - - beforeAll(async () => { - appModule = await Test.createTestingModule({ - imports: [AppModule], - }).compile(); - }); - - it('should be defined', () => { - expect(appModule).toBeDefined(); - }); - - it('should have the correct controllers', () => { - const controllers = appModule.get(AppController); - expect(controllers).toBeDefined(); - }); - - it('should have the correct module imports', () => { - const xTransferModule = appModule.select(XTransferModule); - const assetsModule = appModule.select(AssetsModule); - const channelsModule = appModule.select(ChannelsModule); - const palletsModule = appModule.select(PalletsModule); - - expect(xTransferModule).toBeDefined(); - expect(assetsModule).toBeDefined(); - expect(channelsModule).toBeDefined(); - expect(palletsModule).toBeDefined(); - }); -}); diff --git a/src/app.module.ts b/src/app.module.ts index 2adeb1e..d892389 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -11,6 +11,9 @@ import { AuthGuard } from './auth/auth.guard'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from './users/user.entity'; +import { UsersService } from './users/users.service'; @Module({ imports: [ @@ -20,12 +23,31 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; PalletsModule, AuthModule, ConfigModule.forRoot({ isGlobal: true }), - ThrottlerModule.forRootAsync({ + TypeOrmModule.forRootAsync({ inject: [ConfigService], useFactory: (config: ConfigService) => ({ - ttl: config.get('RATE_LIMIT_TTL_SEC'), + type: 'postgres', + host: config.get('DB_HOST'), + port: config.get('DB_PORT'), + username: config.get('DB_USER'), + password: config.get('DB_PASS'), + database: config.get('DB_NAME'), + entities: [User], + synchronize: true, + }), + }), + ThrottlerModule.forRootAsync({ + inject: [ConfigService, UsersService], + useFactory: (config: ConfigService) => ({ + ttl: + process.env.NODE_ENV === 'test' + ? 0 + : config.get('RATE_LIMIT_TTL_SEC'), limit: (context) => { const request = context.switchToHttp().getRequest(); + if (request.user && request.user.requestLimit) { + return request.user.requestLimit; + } return request.user ? config.get('RATE_LIMIT_REQ_COUNT_AUTH') : config.get('RATE_LIMIT_REQ_COUNT_PUBLIC'); @@ -34,7 +56,7 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; }), ServeStaticModule.forRoot({ rootPath: join(__dirname, '..', 'client'), - renderPath: '/generate-api-key', + serveRoot: '/app', }), ], controllers: [AppController], diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 910ef3a..29c7206 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -1,5 +1,13 @@ -import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; +import { + Body, + Controller, + HttpCode, + HttpStatus, + Post, + Res, +} from '@nestjs/common'; import { AuthService } from './auth.service'; +import { HigherRequestLimitDto } from './dto/HigherRequestLimitDto'; @Controller('auth') export class AuthController { @@ -10,4 +18,14 @@ export class AuthController { generateApiKey(@Body('recaptchaResponse') recaptcha: string) { return this.authService.generateApiKey(recaptcha); } + + @HttpCode(HttpStatus.OK) + @Post('higher-request-limit-form') + async submitHigherRequestLimitForm( + @Body() higherRequestLimitDto: HigherRequestLimitDto, + @Res() res, + ) { + await this.authService.submitHigherRequestLimitForm(higherRequestLimitDto); + return res.redirect('/app/higher-request-limit/submit-success.html'); + } } diff --git a/src/auth/auth.guard.ts b/src/auth/auth.guard.ts index 9240eff..c97f653 100644 --- a/src/auth/auth.guard.ts +++ b/src/auth/auth.guard.ts @@ -5,10 +5,14 @@ import { ForbiddenException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; +import { UsersService } from '../users/users.service'; @Injectable() export class AuthGuard implements CanActivate { - constructor(private jwtService: JwtService) {} + constructor( + private jwtService: JwtService, + private usersService: UsersService, + ) {} async canActivate(context: ExecutionContext): Promise { const request = context.switchToHttp().getRequest(); @@ -19,8 +23,10 @@ export class AuthGuard implements CanActivate { } try { - const decoded = this.jwtService.verify(apiKey); - request.user = decoded; + const { userId } = this.jwtService.verify(apiKey); + if (!userId) throw new ForbiddenException('Invalid API key.'); + const dbUser = await this.usersService.findOne(userId); + request.user = dbUser; return true; } catch (error) { throw new ForbiddenException( diff --git a/src/auth/auth.module.ts b/src/auth/auth.module.ts index b2233a3..2971941 100644 --- a/src/auth/auth.module.ts +++ b/src/auth/auth.module.ts @@ -3,9 +3,11 @@ import { AuthController } from './auth.controller'; import { AuthService } from './auth.service'; import { JwtModule } from '@nestjs/jwt'; import { ConfigService } from '@nestjs/config'; +import { UsersModule } from '../users/users.module'; @Module({ imports: [ + UsersModule, JwtModule.registerAsync({ global: true, inject: [ConfigService], diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 3c4b597..f9e04ae 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -1,36 +1,63 @@ -import { - ForbiddenException, - Injectable, - InternalServerErrorException, -} from '@nestjs/common'; +import { ForbiddenException, Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; -import axios from 'axios'; +import { HigherRequestLimitDto } from './dto/HigherRequestLimitDto'; +import { validateRecaptcha } from '../utils'; +import { sendEmail } from './utils/utils'; +import { generateConfirmationEmailHtml } from './utils/generateConfirmationEmailHtml'; +import { generateNewHigherLimitRequestHtml } from './utils/generateNewHigherLimitRequestHtml'; +import { ConfigService } from '@nestjs/config'; +import { UsersService } from '../users/users.service'; + +const sendEmails = async ( + { email, reason, requestedLimit }: HigherRequestLimitDto, + userId: string, + configService: ConfigService, +) => { + const title = 'Request Submission Confirmation'; + await sendEmail( + title, + generateConfirmationEmailHtml(reason, requestedLimit, title), + email, + configService, + ); + const emailsVal = configService.get('EMAIL_ADDRESS_RECIPIENT_ARR'); + if (emailsVal) { + const emails = emailsVal.split(','); + const title = 'New higher limit request'; + await Promise.all( + emails.map((email) => + sendEmail( + title, + generateNewHigherLimitRequestHtml( + email, + userId, + reason, + requestedLimit, + ), + email, + configService, + ), + ), + ); + } +}; @Injectable() export class AuthService { - constructor(private jwtService: JwtService) {} + constructor( + private jwtService: JwtService, + private usersService: UsersService, + private configService: ConfigService, + ) {} async generateApiKey(recaptcha: string) { - const recaptchaSecretKey = '6LfL0oYnAAAAABX3l2hJrzxhoQZJQGfZKuUcZyTt'; - - const data = { - secret: recaptchaSecretKey, - response: recaptcha, - }; - - const response = await axios - .post('https://www.google.com/recaptcha/api/siteverify', null, { - params: data, - }) - .catch((error) => { - throw new InternalServerErrorException( - 'Error verifying reCAPTCHA: ' + error, - ); - }); - - const verificationResult = response.data; - if (verificationResult.success) { - const payload = {}; + const verificationResult = await validateRecaptcha( + recaptcha, + this.configService.get('RECAPTCHA_SECRET_KEY'), + ); + if (verificationResult) { + const { id } = await this.usersService.create(); + const payload = { userId: id }; return { api_key: await this.jwtService.signAsync(payload), }; @@ -38,4 +65,17 @@ export class AuthService { throw new ForbiddenException('Recaptcha verification failed'); } } + + async submitHigherRequestLimitForm(dto: HigherRequestLimitDto) { + const verificationResult = await validateRecaptcha( + dto['g-recaptcha-response'], + this.configService.get('RECAPTCHA_SECRET_KEY'), + ); + if (verificationResult) { + const { userId } = this.jwtService.verify(dto.api_key); + await sendEmails(dto, userId, this.configService); + } else { + throw new ForbiddenException('Recaptcha verification failed'); + } + } } diff --git a/src/auth/dto/HigherRequestLimitDto.ts b/src/auth/dto/HigherRequestLimitDto.ts new file mode 100644 index 0000000..e41ef99 --- /dev/null +++ b/src/auth/dto/HigherRequestLimitDto.ts @@ -0,0 +1,7 @@ +export class HigherRequestLimitDto { + email: string; + api_key: string; + reason: string; + requestedLimit: string; + 'g-recaptcha-response': string; +} diff --git a/src/auth/utils/generateConfirmationEmailHtml.ts b/src/auth/utils/generateConfirmationEmailHtml.ts new file mode 100644 index 0000000..a0c4647 --- /dev/null +++ b/src/auth/utils/generateConfirmationEmailHtml.ts @@ -0,0 +1,243 @@ +export const generateConfirmationEmailHtml = ( + title: string, + reason: string, + requestedLimit: string, +) => ` + + + + + + + Email Confirmation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + Logo + +
+ +
+ + + + + +
+

${title}

+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+

Your request has been submitted successfully. We will review it shortly and notify you about the status. Thank you for using our service!

+
+

Reason for Higher Limit: ${reason}

+

Requested Requests Per Minute: ${requestedLimit}

+
+

With kind regards, +
Team LightSpell✨

+
+ +
+ + + + + + + + + +
+

You received this email because we received a request for increasing a number of HTTP requests per minute for your API key.

+
+ +
+ + + + + `; diff --git a/src/auth/utils/generateNewHigherLimitRequestHtml.ts b/src/auth/utils/generateNewHigherLimitRequestHtml.ts new file mode 100644 index 0000000..165edc3 --- /dev/null +++ b/src/auth/utils/generateNewHigherLimitRequestHtml.ts @@ -0,0 +1,246 @@ +export const generateNewHigherLimitRequestHtml = ( + userEmail: string, + userId: string, + reason: string, + requestedLimit: string, +) => ` + + + + + + + Email Confirmation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + Logo + +
+ +
+ + + + + +
+

Request Submission Confirmation

+
+ +
+ + + + + + + + + + + + + + + + + + + + + +
+

New higher limit request for submitted:

+
+

User Email: ${userEmail}

+

User ID: ${userId}

+

Reason for Higher Limit: ${reason}

+

Requested Requests Per Minute: ${requestedLimit}

+
+

With kind regards, +
Team LightSpell✨

+
+ +
+ + + + + + + + + +
+

You received this email because you are subscribed to higher http request limit requests.

+
+ +
+ + + + + `; diff --git a/src/auth/utils/utils.ts b/src/auth/utils/utils.ts new file mode 100644 index 0000000..c9b9cc5 --- /dev/null +++ b/src/auth/utils/utils.ts @@ -0,0 +1,49 @@ +import * as nodemailer from 'nodemailer'; +import { google } from 'googleapis'; +import { ConfigService } from '@nestjs/config'; +const OAuth2 = google.auth.OAuth2; + +export const sendEmail = async ( + subject: string, + htmlContent: string, + recipient: string, + configService: ConfigService, +) => { + const email = configService.get('EMAIL_ADDRESS_SENDER'); + const clientId = configService.get('EMAIL_CLIENT_ID'); + const clientSecret = configService.get('EMAIL_CLIENT_SECRET'); + const redirectUri = configService.get('EMAIL_REDIRECT_URI'); + const refreshToken = configService.get('EMAIL_REFRESH_TOKEN'); + + const myOAuth2Client = new OAuth2(clientId, clientSecret, redirectUri); + myOAuth2Client.setCredentials({ + refresh_token: refreshToken, + }); + const myAccessToken = await myOAuth2Client.getAccessToken(); + + const transporter = nodemailer.createTransport({ + service: 'Gmail', + auth: { + type: 'OAuth2', + user: email, + clientId, + clientSecret, + refreshToken, + accessToken: myAccessToken.token, + }, + }); + + transporter.sendMail( + { + from: email, + to: recipient, + subject, + html: htmlContent, + }, + (err) => { + if (err) { + console.error('Error sending email:', err); + } + }, + ); +}; diff --git a/src/users/user.entity.ts b/src/users/user.entity.ts new file mode 100644 index 0000000..8f73615 --- /dev/null +++ b/src/users/user.entity.ts @@ -0,0 +1,10 @@ +import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; + +@Entity() +export class User { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ nullable: true }) + requestLimit: number; +} diff --git a/src/users/users.module.ts b/src/users/users.module.ts new file mode 100644 index 0000000..2e96da4 --- /dev/null +++ b/src/users/users.module.ts @@ -0,0 +1,12 @@ +import { Global, Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { User } from './user.entity'; +import { UsersService } from './users.service'; + +@Global() +@Module({ + imports: [TypeOrmModule.forFeature([User])], + providers: [UsersService], + exports: [UsersService, TypeOrmModule], +}) +export class UsersModule {} diff --git a/src/users/users.service.ts b/src/users/users.service.ts new file mode 100644 index 0000000..4081db3 --- /dev/null +++ b/src/users/users.service.ts @@ -0,0 +1,22 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { User } from './user.entity'; + +@Injectable() +export class UsersService { + constructor( + @InjectRepository(User) + private usersRepository: Repository, + ) {} + + create() { + return this.usersRepository.save({}); + } + + findOne(userId: string) { + return this.usersRepository.findOneBy({ + id: userId, + }); + } +} diff --git a/src/utils.ts b/src/utils.ts index b74b69b..3d4e64d 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -9,6 +9,7 @@ import { getRelayChainSymbol, } from '@paraspell/sdk'; import { ApiPromise, WsProvider } from '@polkadot/api'; +import axios from 'axios'; export const isNumeric = (num: any) => !isNaN(num); @@ -50,3 +51,25 @@ export const determineWsUrl = (fromNode?: TNode, destinationNode?: TNode) => { ? findWsUrlByNode(fromNode) : getNodeRelayChainWsUrl(destinationNode); }; + +export const validateRecaptcha = async ( + recaptcha: string, + recaptchaSecretKey: string, +): Promise => { + const data = { + secret: recaptchaSecretKey, + response: recaptcha, + }; + + const response = await axios + .post('https://www.google.com/recaptcha/api/siteverify', null, { + params: data, + }) + .catch((error) => { + throw new InternalServerErrorException( + 'Error verifying reCAPTCHA: ' + error, + ); + }); + + return response.data.success; +}; diff --git a/test/app.e2e-spec.ts b/test/app.e2e-spec.ts index e8f2aac..d9b696f 100644 --- a/test/app.e2e-spec.ts +++ b/test/app.e2e-spec.ts @@ -18,6 +18,7 @@ import { } from '@paraspell/sdk'; import { ApiPromise } from '@polkadot/api'; import { createApiInstance, determineWsUrl } from '../src/utils'; +import { ThrottlerModule } from '@nestjs/throttler'; describe('XCM API (e2e)', () => { let app: INestApplication; @@ -25,7 +26,7 @@ describe('XCM API (e2e)', () => { const mockSymbol = 'DOT'; const unknownNode = 'UnknownNode'; - beforeEach(async () => { + beforeAll(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], }).compile();