diff --git a/backend/package-lock.json b/backend/package-lock.json index c743979e6..0bbbed613 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -17,6 +17,7 @@ "@octokit/rest": "^19.0.5", "@sentry/node": "^7.77.0", "@sentry/tracing": "^7.48.0", + "@serdnam/pino-cloudwatch-transport": "^1.0.4", "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", "@ucast/mongo2js": "^1.3.4", @@ -50,6 +51,7 @@ "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", + "ora": "^5.4.1", "passport": "^0.6.0", "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", @@ -72,6 +74,8 @@ "devDependencies": { "@jest/globals": "^29.3.1", "@posthog/plugin-scaffold": "^1.3.4", + "@swc/core": "^1.3.99", + "@swc/helpers": "^0.5.3", "@types/bcrypt": "^5.0.0", "@types/bcryptjs": "^2.4.2", "@types/bull": "^4.10.0", @@ -102,6 +106,7 @@ "nodemon": "^2.0.19", "npm": "^8.19.3", "pino-pretty": "^10.2.3", + "regenerator-runtime": "^0.14.0", "smee-client": "^1.2.3", "supertest": "^6.3.3", "swagger-autogen": "^2.23.5", @@ -222,6 +227,933 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/@aws-sdk/client-cloudwatch-logs": { + "version": "3.454.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.454.0.tgz", + "integrity": "sha512-anXMEIZvDvqsFAURYmNHaJU8SH85Rqkahkk0TsDiTLc6/J4Qh8xvcem358qTiXzRpPJmZe4m20XKqL0fXsJgIw==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.454.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/credential-provider-node": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sso": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz", + "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/client-sts": { + "version": "3.454.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.454.0.tgz", + "integrity": "sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/credential-provider-node": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-sdk-sts": "3.451.0", + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-env": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz", + "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz", + "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.451.0", + "@aws-sdk/credential-provider-process": "3.451.0", + "@aws-sdk/credential-provider-sso": "3.451.0", + "@aws-sdk/credential-provider-web-identity": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-node": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz", + "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.451.0", + "@aws-sdk/credential-provider-ini": "3.451.0", + "@aws-sdk/credential-provider-process": "3.451.0", + "@aws-sdk/credential-provider-sso": "3.451.0", + "@aws-sdk/credential-provider-web-identity": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-process": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz", + "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz", + "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==", + "dependencies": { + "@aws-sdk/client-sso": "3.451.0", + "@aws-sdk/token-providers": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz", + "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz", + "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-logger": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz", + "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz", + "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-sdk-sts": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz", + "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==", + "dependencies": { + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-signing": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz", + "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/signature-v4": "^2.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz", + "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/token-providers": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz", + "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==", + "dependencies": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/types": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz", + "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-endpoints": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz", + "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/util-endpoints": "^1.0.4", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz", + "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz", + "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==", + "dependencies": { + "@aws-sdk/types": "3.451.0", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/abort-controller": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz", + "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/config-resolver": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz", + "integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/credential-provider-imds": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz", + "integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/property-provider": "^2.0.14", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/eventstream-codec": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz", + "integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-hex-encoding": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/fetch-http-handler": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz", + "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==", + "dependencies": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/hash-node": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz", + "integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/invalid-dependency": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz", + "integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-content-length": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz", + "integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==", + "dependencies": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-endpoint": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz", + "integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==", + "dependencies": { + "@smithy/middleware-serde": "^2.0.13", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-retry": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz", + "integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/protocol-http": "^3.0.9", + "@smithy/service-error-classification": "^2.0.6", + "@smithy/types": "^2.5.0", + "@smithy/util-middleware": "^2.0.6", + "@smithy/util-retry": "^2.0.6", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-serde": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz", + "integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/middleware-stack": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz", + "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "dependencies": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/node-http-handler": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz", + "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==", + "dependencies": { + "@smithy/abort-controller": "^2.0.13", + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/protocol-http": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz", + "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/querystring-builder": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz", + "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/querystring-parser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz", + "integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/service-error-classification": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz", + "integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==", + "dependencies": { + "@smithy/types": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/signature-v4": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz", + "integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==", + "dependencies": { + "@smithy/eventstream-codec": "^2.0.13", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "@smithy/util-uri-escape": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/smithy-client": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz", + "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==", + "dependencies": { + "@smithy/middleware-stack": "^2.0.7", + "@smithy/types": "^2.5.0", + "@smithy/util-stream": "^2.0.20", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/url-parser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz", + "integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==", + "dependencies": { + "@smithy/querystring-parser": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-base64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", + "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-body-length-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", + "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", + "dependencies": { + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-body-length-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", + "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-config-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", + "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-defaults-mode-browser": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz", + "integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==", + "dependencies": { + "@smithy/property-provider": "^2.0.14", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-defaults-mode-node": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz", + "integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==", + "dependencies": { + "@smithy/config-resolver": "^2.0.18", + "@smithy/credential-provider-imds": "^2.1.1", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/property-provider": "^2.0.14", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz", + "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-retry": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz", + "integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==", + "dependencies": { + "@smithy/service-error-classification": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-stream": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz", + "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==", + "dependencies": { + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-cloudwatch-logs/node_modules/@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/client-cognito-identity": { "version": "3.388.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.388.0.tgz", @@ -1420,6 +2352,206 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/core": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz", + "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==", + "dependencies": { + "@smithy/smithy-client": "^2.1.15", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/abort-controller": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz", + "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/fetch-http-handler": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz", + "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==", + "dependencies": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/middleware-stack": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz", + "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/node-http-handler": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz", + "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==", + "dependencies": { + "@smithy/abort-controller": "^2.0.13", + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz", + "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/querystring-builder": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz", + "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/smithy-client": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz", + "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==", + "dependencies": { + "@smithy/middleware-stack": "^2.0.7", + "@smithy/types": "^2.5.0", + "@smithy/util-stream": "^2.0.20", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-base64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", + "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "dependencies": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-stream": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz", + "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==", + "dependencies": { + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", + "dependencies": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { "version": "3.388.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.388.0.tgz", @@ -2663,6 +3795,93 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz", + "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "dependencies": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-config-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", + "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver/node_modules/@smithy/util-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz", + "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/token-providers": { "version": "3.370.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.370.0.tgz", @@ -3360,6 +4579,11 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/@babel/template": { "version": "7.22.15", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", @@ -5145,6 +6369,28 @@ "node": ">=8" } }, + "node_modules/@serdnam/pino-cloudwatch-transport": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@serdnam/pino-cloudwatch-transport/-/pino-cloudwatch-transport-1.0.4.tgz", + "integrity": "sha512-0wtILlFlO/qTFANM1oEMZLKa9REo+mluHN0VTDaOMh15H9Puc+qU4z4jAoZqggFz9Fw9EGG4c+UHpMduZ1EzeQ==", + "dependencies": { + "@aws-sdk/client-cloudwatch-logs": "^3.52.0", + "p-throttle": "^5.0.0", + "pino-abstract-transport": "^0.5.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@serdnam/pino-cloudwatch-transport/node_modules/pino-abstract-transport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", + "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", + "dependencies": { + "duplexify": "^4.1.2", + "split2": "^4.0.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -5570,6 +6816,68 @@ "node": ">= 10.0.0" } }, + "node_modules/@smithy/util-endpoints": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz", + "integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==", + "dependencies": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "dependencies": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "dependencies": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-endpoints/node_modules/@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@smithy/util-hex-encoding": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.2.tgz", @@ -5645,6 +6953,208 @@ "node": ">=14.0.0" } }, + "node_modules/@swc/core": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.99.tgz", + "integrity": "sha512-8O996RfuPC4ieb4zbYMfbyCU9k4gSOpyCNnr7qBQ+o7IEmh8JCV6B8wwu+fT/Om/6Lp34KJe1IpJ/24axKS6TQ==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.3.99", + "@swc/core-darwin-x64": "1.3.99", + "@swc/core-linux-arm64-gnu": "1.3.99", + "@swc/core-linux-arm64-musl": "1.3.99", + "@swc/core-linux-x64-gnu": "1.3.99", + "@swc/core-linux-x64-musl": "1.3.99", + "@swc/core-win32-arm64-msvc": "1.3.99", + "@swc/core-win32-ia32-msvc": "1.3.99", + "@swc/core-win32-x64-msvc": "1.3.99" + }, + "peerDependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.99.tgz", + "integrity": "sha512-Qj7Jct68q3ZKeuJrjPx7k8SxzWN6PqLh+VFxzA+KwLDpQDPzOlKRZwkIMzuFjLhITO4RHgSnXoDk/Syz0ZeN+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.99.tgz", + "integrity": "sha512-wR7m9QVJjgiBu1PSOHy7s66uJPa45Kf9bZExXUL+JAa9OQxt5y+XVzr+n+F045VXQOwdGWplgPnWjgbUUHEVyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.99.tgz", + "integrity": "sha512-gcGv1l5t0DScEONmw5OhdVmEI/o49HCe9Ik38zzH0NtDkc+PDYaCcXU5rvfZP2qJFaAAr8cua8iJcOunOSLmnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.99.tgz", + "integrity": "sha512-XL1/eUsTO8BiKsWq9i3iWh7H99iPO61+9HYiWVKhSavknfj4Plbn+XyajDpxsauln5o8t+BRGitymtnAWJM4UQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.99.tgz", + "integrity": "sha512-fGrXYE6DbTfGNIGQmBefYxSk3rp/1lgbD0nVg4rl4mfFRQPi7CgGhrrqSuqZ/ezXInUIgoCyvYGWFSwjLXt/Qg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.99.tgz", + "integrity": "sha512-kvgZp/mqf3IJ806gUOL6gN6VU15+DfzM1Zv4Udn8GqgXiUAvbQehrtruid4Snn5pZTLj4PEpSCBbxgxK1jbssA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.99.tgz", + "integrity": "sha512-yt8RtZ4W/QgFF+JUemOUQAkVW58cCST7mbfKFZ1v16w3pl3NcWd9OrtppFIXpbjU1rrUX2zp2R7HZZzZ2Zk/aQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.99.tgz", + "integrity": "sha512-62p5fWnOJR/rlbmbUIpQEVRconICy5KDScWVuJg1v3GPLBrmacjphyHiJC1mp6dYvvoEWCk/77c/jcQwlXrDXw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.99.tgz", + "integrity": "sha512-PdppWhkoS45VGdMBxvClVgF1hVjqamtvYd82Gab1i4IV45OSym2KinoDCKE1b6j3LwBLOn2J9fvChGSgGfDCHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "node_modules/@swc/helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz", + "integrity": "sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==", + "dev": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -6574,7 +8084,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -6816,9 +8325,9 @@ } }, "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -7010,6 +8519,39 @@ "node": ">=8" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -7274,7 +8816,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7380,6 +8921,28 @@ "node": ">=6" } }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -7430,7 +8993,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -7441,8 +9003,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-support": { "version": "1.1.3", @@ -7703,6 +9264,25 @@ "node": ">=0.10.0" } }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7826,6 +9406,17 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -8950,7 +10541,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { "node": ">=8" } @@ -9400,6 +10990,14 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -9468,6 +11066,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -10410,6 +12019,21 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -10600,7 +12224,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, "engines": { "node": ">=6" } @@ -13835,7 +15458,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -13863,6 +15485,28 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -13901,6 +15545,17 @@ "node": ">=6" } }, + "node_modules/p-throttle": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.1.0.tgz", + "integrity": "sha512-+N+s2g01w1Zch4D0K3OpnPDqLOKmLcQ4BvIFq3JC0K29R28vUOjWpO+OJZBNt8X9i3pFCksZJZ0YXkUGjaFE6g==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -15365,9 +17020,10 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "dev": true }, "node_modules/require-directory": { "version": "2.1.1", @@ -15441,6 +17097,18 @@ "node": ">=10" } }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -15917,6 +17585,11 @@ "npm": ">=6" } }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -16088,7 +17761,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "dependencies": { "has-flag": "^4.0.0" }, @@ -16705,6 +18377,14 @@ "makeerror": "1.0.12" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -17089,6 +18769,771 @@ } } }, + "@aws-sdk/client-cloudwatch-logs": { + "version": "3.454.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudwatch-logs/-/client-cloudwatch-logs-3.454.0.tgz", + "integrity": "sha512-anXMEIZvDvqsFAURYmNHaJU8SH85Rqkahkk0TsDiTLc6/J4Qh8xvcem358qTiXzRpPJmZe4m20XKqL0fXsJgIw==", + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/client-sts": "3.454.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/credential-provider-node": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + }, + "dependencies": { + "@aws-sdk/client-sso": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.451.0.tgz", + "integrity": "sha512-KkYSke3Pdv3MfVH/5fT528+MKjMyPKlcLcd4zQb0x6/7Bl7EHrPh1JZYjzPLHelb+UY5X0qN8+cb8iSu1eiwIQ==", + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/client-sts": { + "version": "3.454.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.454.0.tgz", + "integrity": "sha512-0fDvr8WeB6IYO8BUCzcivWmahgGl/zDbaYfakzGnt4mrl5ztYaXE875WI6b7+oFcKMRvN+KLvwu5TtyFuNY+GQ==", + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/core": "3.451.0", + "@aws-sdk/credential-provider-node": "3.451.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-sdk-sts": "3.451.0", + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/protocol-http": "^3.0.9", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "fast-xml-parser": "4.2.5", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/credential-provider-env": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.451.0.tgz", + "integrity": "sha512-9dAav7DcRgaF7xCJEQR5ER9ErXxnu/tdnVJ+UPmb1NPeIZdESv1A3lxFDEq1Fs8c4/lzAj9BpshGyJVIZwZDKg==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/credential-provider-ini": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.451.0.tgz", + "integrity": "sha512-TySt64Ci5/ZbqFw1F9Z0FIGvYx5JSC9e6gqDnizIYd8eMnn8wFRUscRrD7pIHKfrhvVKN5h0GdYovmMO/FMCBw==", + "requires": { + "@aws-sdk/credential-provider-env": "3.451.0", + "@aws-sdk/credential-provider-process": "3.451.0", + "@aws-sdk/credential-provider-sso": "3.451.0", + "@aws-sdk/credential-provider-web-identity": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/credential-provider-node": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.451.0.tgz", + "integrity": "sha512-AEwM1WPyxUdKrKyUsKyFqqRFGU70e4qlDyrtBxJnSU9NRLZI8tfEZ67bN7fHSxBUBODgDXpMSlSvJiBLh5/3pw==", + "requires": { + "@aws-sdk/credential-provider-env": "3.451.0", + "@aws-sdk/credential-provider-ini": "3.451.0", + "@aws-sdk/credential-provider-process": "3.451.0", + "@aws-sdk/credential-provider-sso": "3.451.0", + "@aws-sdk/credential-provider-web-identity": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/credential-provider-imds": "^2.0.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/credential-provider-process": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.451.0.tgz", + "integrity": "sha512-HQywSdKeD5PErcLLnZfSyCJO+6T+ZyzF+Lm/QgscSC+CbSUSIPi//s15qhBRVely/3KBV6AywxwNH+5eYgt4lQ==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/credential-provider-sso": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.451.0.tgz", + "integrity": "sha512-Usm/N51+unOt8ID4HnQzxIjUJDrkAQ1vyTOC0gSEEJ7h64NSSPGD5yhN7il5WcErtRd3EEtT1a8/GTC5TdBctg==", + "requires": { + "@aws-sdk/client-sso": "3.451.0", + "@aws-sdk/token-providers": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/credential-provider-web-identity": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.451.0.tgz", + "integrity": "sha512-Xtg3Qw65EfDjWNG7o2xD6sEmumPfsy3WDGjk2phEzVg8s7hcZGxf5wYwe6UY7RJvlEKrU0rFA+AMn6Hfj5oOzg==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/middleware-host-header": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.451.0.tgz", + "integrity": "sha512-j8a5jAfhWmsK99i2k8oR8zzQgXrsJtgrLxc3js6U+525mcZytoiDndkWTmD5fjJ1byU1U2E5TaPq+QJeDip05Q==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/middleware-logger": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.451.0.tgz", + "integrity": "sha512-0kHrYEyVeB2QBfP6TfbI240aRtatLZtcErJbhpiNUb+CQPgEL3crIjgVE8yYiJumZ7f0jyjo8HLPkwD1/2APaw==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/middleware-recursion-detection": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.451.0.tgz", + "integrity": "sha512-J6jL6gJ7orjHGM70KDRcCP7so/J2SnkN4vZ9YRLTeeZY6zvBuHDjX8GCIgSqPn/nXFXckZO8XSnA7u6+3TAT0w==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/middleware-sdk-sts": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.451.0.tgz", + "integrity": "sha512-UJ6UfVUEgp0KIztxpAeelPXI5MLj9wUtUCqYeIMP7C1ZhoEMNm3G39VLkGN43dNhBf1LqjsV9jkKMZbVfYXuwg==", + "requires": { + "@aws-sdk/middleware-signing": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/middleware-signing": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.451.0.tgz", + "integrity": "sha512-s5ZlcIoLNg1Huj4Qp06iKniE8nJt/Pj1B/fjhWc6cCPCM7XJYUCejCnRh6C5ZJoBEYodjuwZBejPc1Wh3j+znA==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/signature-v4": "^2.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/middleware-user-agent": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.451.0.tgz", + "integrity": "sha512-8NM/0JiKLNvT9wtAQVl1DFW0cEO7OvZyLSUBLNLTHqyvOZxKaZ8YFk7d8PL6l76LeUKRxq4NMxfZQlUIRe0eSA==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/token-providers": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.451.0.tgz", + "integrity": "sha512-ij1L5iUbn6CwxVOT1PG4NFjsrsKN9c4N1YEM0lkl6DwmaNOscjLKGSNyj9M118vSWsOs1ZDbTwtj++h0O/BWrQ==", + "requires": { + "@aws-crypto/sha256-browser": "3.0.0", + "@aws-crypto/sha256-js": "3.0.0", + "@aws-sdk/middleware-host-header": "3.451.0", + "@aws-sdk/middleware-logger": "3.451.0", + "@aws-sdk/middleware-recursion-detection": "3.451.0", + "@aws-sdk/middleware-user-agent": "3.451.0", + "@aws-sdk/region-config-resolver": "3.451.0", + "@aws-sdk/types": "3.451.0", + "@aws-sdk/util-endpoints": "3.451.0", + "@aws-sdk/util-user-agent-browser": "3.451.0", + "@aws-sdk/util-user-agent-node": "3.451.0", + "@smithy/config-resolver": "^2.0.18", + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/hash-node": "^2.0.15", + "@smithy/invalid-dependency": "^2.0.13", + "@smithy/middleware-content-length": "^2.0.15", + "@smithy/middleware-endpoint": "^2.2.0", + "@smithy/middleware-retry": "^2.0.20", + "@smithy/middleware-serde": "^2.0.13", + "@smithy/middleware-stack": "^2.0.7", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/property-provider": "^2.0.0", + "@smithy/protocol-http": "^3.0.9", + "@smithy/shared-ini-file-loader": "^2.0.6", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-body-length-browser": "^2.0.0", + "@smithy/util-body-length-node": "^2.1.0", + "@smithy/util-defaults-mode-browser": "^2.0.19", + "@smithy/util-defaults-mode-node": "^2.0.25", + "@smithy/util-endpoints": "^1.0.4", + "@smithy/util-retry": "^2.0.6", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/types": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.451.0.tgz", + "integrity": "sha512-rhK+qeYwCIs+laJfWCcrYEjay2FR/9VABZJ2NRM89jV/fKqGVQR52E5DQqrI+oEIL5JHMhhnr4N4fyECMS35lw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/util-endpoints": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.451.0.tgz", + "integrity": "sha512-giqLGBTnRIcKkDqwU7+GQhKbtJ5Ku35cjGQIfMyOga6pwTBUbaK0xW1Sdd8sBQ1GhApscnChzI9o/R9x0368vw==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/util-endpoints": "^1.0.4", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/util-user-agent-browser": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.451.0.tgz", + "integrity": "sha512-Ws5mG3J0TQifH7OTcMrCTexo7HeSAc3cBgjfhS/ofzPUzVCtsyg0G7I6T7wl7vJJETix2Kst2cpOsxygPgPD9w==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/types": "^2.5.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "@aws-sdk/util-user-agent-node": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.451.0.tgz", + "integrity": "sha512-TBzm6P+ql4mkGFAjPlO1CI+w3yUT+NulaiALjl/jNX/nnUp6HsJsVxJf4nVFQTG5KRV0iqMypcs7I3KIhH+LmA==", + "requires": { + "@aws-sdk/types": "3.451.0", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/abort-controller": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz", + "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/config-resolver": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.18.tgz", + "integrity": "sha512-761sJSgNbvsqcsKW6/WZbrZr4H+0Vp/QKKqwyrxCPwD8BsiPEXNHyYnqNgaeK9xRWYswjon0Uxbpe3DWQo0j/g==", + "requires": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + } + }, + "@smithy/credential-provider-imds": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.1.tgz", + "integrity": "sha512-gw5G3FjWC6sNz8zpOJgPpH5HGKrpoVFQpToNAwLwJVyI/LJ2jDJRjSKEsM6XI25aRpYjMSE/Qptxx305gN1vHw==", + "requires": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/property-provider": "^2.0.14", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "tslib": "^2.5.0" + } + }, + "@smithy/eventstream-codec": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.13.tgz", + "integrity": "sha512-CExbelIYp+DxAHG8RIs0l9QL7ElqhG4ym9BNoSpkPa4ptBQfzJdep3LbOSVJIE2VUdBAeObdeL6EDB3Jo85n3g==", + "requires": { + "@aws-crypto/crc32": "3.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-hex-encoding": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/fetch-http-handler": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz", + "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==", + "requires": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "@smithy/hash-node": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.15.tgz", + "integrity": "sha512-t/qjEJZu/G46A22PAk1k/IiJZT4ncRkG5GOCNWN9HPPy5rCcSZUbh7gwp7CGKgJJ7ATMMg+0Td7i9o1lQTwOfQ==", + "requires": { + "@smithy/types": "^2.5.0", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/invalid-dependency": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.13.tgz", + "integrity": "sha512-XsGYhVhvEikX1Yz0kyIoLssJf2Rs6E0U2w2YuKdT4jSra5A/g8V2oLROC1s56NldbgnpesTYB2z55KCHHbKyjw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-content-length": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.15.tgz", + "integrity": "sha512-xH4kRBw01gJgWiU+/mNTrnyFXeozpZHw39gLb3JKGsFDVmSrJZ8/tRqu27tU/ki1gKkxr2wApu+dEYjI3QwV1Q==", + "requires": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-endpoint": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.0.tgz", + "integrity": "sha512-tddRmaig5URk2106PVMiNX6mc5BnKIKajHHDxb7K0J5MLdcuQluHMGnjkv18iY9s9O0tF+gAcPd/pDXA5L9DZw==", + "requires": { + "@smithy/middleware-serde": "^2.0.13", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "@smithy/url-parser": "^2.0.13", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-retry": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.20.tgz", + "integrity": "sha512-X2yrF/SHDk2WDd8LflRNS955rlzQ9daz9UWSp15wW8KtzoTXg3bhHM78HbK1cjr48/FWERSJKh9AvRUUGlIawg==", + "requires": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/protocol-http": "^3.0.9", + "@smithy/service-error-classification": "^2.0.6", + "@smithy/types": "^2.5.0", + "@smithy/util-middleware": "^2.0.6", + "@smithy/util-retry": "^2.0.6", + "tslib": "^2.5.0", + "uuid": "^8.3.2" + } + }, + "@smithy/middleware-serde": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.13.tgz", + "integrity": "sha512-tBGbeXw+XsE6pPr4UaXOh+UIcXARZeiA8bKJWxk2IjJcD1icVLhBSUQH9myCIZLNNzJIH36SDjUX8Wqk4xJCJg==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-stack": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz", + "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "requires": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/node-http-handler": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz", + "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==", + "requires": { + "@smithy/abort-controller": "^2.0.13", + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/protocol-http": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz", + "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/querystring-builder": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz", + "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==", + "requires": { + "@smithy/types": "^2.5.0", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/querystring-parser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.13.tgz", + "integrity": "sha512-TEiT6o8CPZVxJ44Rly/rrsATTQsE+b/nyBVzsYn2sa75xAaZcurNxsFd8z1haoUysONiyex24JMHoJY6iCfLdA==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/service-error-classification": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.6.tgz", + "integrity": "sha512-fCQ36frtYra2fqY2/DV8+3/z2d0VB/1D1hXbjRcM5wkxTToxq6xHbIY/NGGY6v4carskMyG8FHACxgxturJ9Pg==", + "requires": { + "@smithy/types": "^2.5.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/signature-v4": { + "version": "2.0.15", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.15.tgz", + "integrity": "sha512-SRTEJSEhQYVlBKIIdZ9SZpqW+KFqxqcNnEcBX+8xkDdWx+DItme9VcCDkdN32yTIrICC+irUufnUdV7mmHPjoA==", + "requires": { + "@smithy/eventstream-codec": "^2.0.13", + "@smithy/is-array-buffer": "^2.0.0", + "@smithy/types": "^2.5.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "@smithy/util-uri-escape": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/smithy-client": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz", + "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==", + "requires": { + "@smithy/middleware-stack": "^2.0.7", + "@smithy/types": "^2.5.0", + "@smithy/util-stream": "^2.0.20", + "tslib": "^2.5.0" + } + }, + "@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/url-parser": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.13.tgz", + "integrity": "sha512-okWx2P/d9jcTsZWTVNnRMpFOE7fMkzloSFyM53fA7nLKJQObxM2T4JlZ5KitKKuXq7pxon9J6SF2kCwtdflIrA==", + "requires": { + "@smithy/querystring-parser": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-base64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", + "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", + "requires": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-body-length-browser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", + "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-body-length-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", + "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "requires": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-config-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", + "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-defaults-mode-browser": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.19.tgz", + "integrity": "sha512-VHP8xdFR7/orpiABJwgoTB0t8Zhhwpf93gXhNfUBiwAE9O0rvsv7LwpQYjgvbOUDDO8JfIYQB2GYJNkqqGWsXw==", + "requires": { + "@smithy/property-provider": "^2.0.14", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "bowser": "^2.11.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-defaults-mode-node": { + "version": "2.0.25", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.25.tgz", + "integrity": "sha512-jkmep6/JyWmn2ADw9VULDeGbugR4N/FJCKOt+gYyVswmN1BJOfzF2umaYxQ1HhQDvna3kzm1Dbo1qIfBW4iuHA==", + "requires": { + "@smithy/config-resolver": "^2.0.18", + "@smithy/credential-provider-imds": "^2.1.1", + "@smithy/node-config-provider": "^2.1.5", + "@smithy/property-provider": "^2.0.14", + "@smithy/smithy-client": "^2.1.15", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz", + "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-retry": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.6.tgz", + "integrity": "sha512-PSO41FofOBmyhPQJwBQJ6mVlaD7Sp9Uff9aBbnfBJ9eqXOE/obrqQjn0PNdkfdvViiPXl49BINfnGcFtSP4kYw==", + "requires": { + "@smithy/service-error-classification": "^2.0.6", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-stream": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz", + "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==", + "requires": { + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", + "requires": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + } + } + } + }, "@aws-sdk/client-cognito-identity": { "version": "3.388.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.388.0.tgz", @@ -18113,6 +20558,163 @@ "tslib": "^2.5.0" } }, + "@aws-sdk/core": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.451.0.tgz", + "integrity": "sha512-SamWW2zHEf1ZKe3j1w0Piauryl8BQIlej0TBS18A4ACzhjhWXhCs13bO1S88LvPR5mBFXok3XOT6zPOnKDFktw==", + "requires": { + "@smithy/smithy-client": "^2.1.15", + "tslib": "^2.5.0" + }, + "dependencies": { + "@smithy/abort-controller": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.13.tgz", + "integrity": "sha512-eeOPD+GF9BzF/Mjy3PICLePx4l0f3rG/nQegQHRLTloN5p1lSJJNZsyn+FzDnW8P2AduragZqJdtKNCxXozB1Q==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/fetch-http-handler": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.6.tgz", + "integrity": "sha512-PStY3XO1Ksjwn3wMKye5U6m6zxXpXrXZYqLy/IeCbh3nM9QB3Jgw/B0PUSLUWKdXg4U8qgEu300e3ZoBvZLsDg==", + "requires": { + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "tslib": "^2.5.0" + } + }, + "@smithy/is-array-buffer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", + "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/middleware-stack": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.7.tgz", + "integrity": "sha512-L1KLAAWkXbGx1t2jjCI/mDJ2dDNq+rp4/ifr/HcC6FHngxho5O7A5bQLpKHGlkfATH6fUnOEx0VICEVFA4sUzw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/node-http-handler": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.9.tgz", + "integrity": "sha512-+K0q3SlNcocmo9OZj+fz67gY4lwhOCvIJxVbo/xH+hfWObvaxrMTx7JEzzXcluK0thnnLz++K3Qe7Z/8MDUreA==", + "requires": { + "@smithy/abort-controller": "^2.0.13", + "@smithy/protocol-http": "^3.0.9", + "@smithy/querystring-builder": "^2.0.13", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/protocol-http": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.9.tgz", + "integrity": "sha512-U1wl+FhYu4/BC+rjwh1lg2gcJChQhytiNQSggREgQ9G2FzmoK9sACBZvx7thyWMvRyHQTE22mO2d5UM8gMKDBg==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/querystring-builder": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.13.tgz", + "integrity": "sha512-JhXKwp3JtsFUe96XLHy/nUPEbaXqn6r7xE4sNaH8bxEyytE5q1fwt0ew/Ke6+vIC7gP87HCHgQpJHg1X1jN2Fw==", + "requires": { + "@smithy/types": "^2.5.0", + "@smithy/util-uri-escape": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/smithy-client": { + "version": "2.1.15", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.15.tgz", + "integrity": "sha512-rngZcQu7Jvs9UbHihK1EI67RMPuzkc3CJmu4MBgB7D7yBnMGuFR86tq5rqHfL2gAkNnMelBN/8kzQVvZjNKefQ==", + "requires": { + "@smithy/middleware-stack": "^2.0.7", + "@smithy/types": "^2.5.0", + "@smithy/util-stream": "^2.0.20", + "tslib": "^2.5.0" + } + }, + "@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-base64": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", + "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", + "requires": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-buffer-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", + "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", + "requires": { + "@smithy/is-array-buffer": "^2.0.0", + "tslib": "^2.5.0" + } + }, + "@smithy/util-hex-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", + "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-stream": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.20.tgz", + "integrity": "sha512-tT8VASuD8jJu0yjHEMTCPt1o5E3FVzgdsxK6FQLAjXKqVv5V8InCnc0EOsYrijgspbfDqdAJg7r0o2sySfcHVg==", + "requires": { + "@smithy/fetch-http-handler": "^2.2.6", + "@smithy/node-http-handler": "^2.1.9", + "@smithy/types": "^2.5.0", + "@smithy/util-base64": "^2.0.1", + "@smithy/util-buffer-from": "^2.0.0", + "@smithy/util-hex-encoding": "^2.0.0", + "@smithy/util-utf8": "^2.0.2", + "tslib": "^2.5.0" + } + }, + "@smithy/util-uri-escape": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", + "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-utf8": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", + "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", + "requires": { + "@smithy/util-buffer-from": "^2.0.0", + "tslib": "^2.5.0" + } + } + } + }, "@aws-sdk/credential-provider-cognito-identity": { "version": "3.388.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.388.0.tgz", @@ -19148,6 +21750,74 @@ "tslib": "^2.5.0" } }, + "@aws-sdk/region-config-resolver": { + "version": "3.451.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.451.0.tgz", + "integrity": "sha512-3iMf4OwzrFb4tAAmoROXaiORUk2FvSejnHIw/XHvf/jjR4EqGGF95NZP/n/MeFZMizJWVssrwS412GmoEyoqhg==", + "requires": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "@smithy/util-config-provider": "^2.0.0", + "@smithy/util-middleware": "^2.0.6", + "tslib": "^2.5.0" + }, + "dependencies": { + "@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "requires": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-config-provider": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", + "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", + "requires": { + "tslib": "^2.5.0" + } + }, + "@smithy/util-middleware": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.6.tgz", + "integrity": "sha512-7W4uuwBvSLgKoLC1x4LfeArCVcbuHdtVaC4g30kKsD1erfICyQ45+tFhhs/dZNeQg+w392fhunCm/+oCcb6BSA==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + } + } + }, "@aws-sdk/token-providers": { "version": "3.370.0", "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.370.0.tgz", @@ -19668,6 +22338,13 @@ "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", "requires": { "regenerator-runtime": "^0.13.11" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + } } }, "@babel/template": { @@ -21033,6 +23710,27 @@ "tslib": "^2.4.1 || ^1.9.3" } }, + "@serdnam/pino-cloudwatch-transport": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@serdnam/pino-cloudwatch-transport/-/pino-cloudwatch-transport-1.0.4.tgz", + "integrity": "sha512-0wtILlFlO/qTFANM1oEMZLKa9REo+mluHN0VTDaOMh15H9Puc+qU4z4jAoZqggFz9Fw9EGG4c+UHpMduZ1EzeQ==", + "requires": { + "@aws-sdk/client-cloudwatch-logs": "^3.52.0", + "p-throttle": "^5.0.0", + "pino-abstract-transport": "^0.5.0" + }, + "dependencies": { + "pino-abstract-transport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", + "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", + "requires": { + "duplexify": "^4.1.2", + "split2": "^4.0.0" + } + } + } + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -21377,6 +24075,55 @@ "tslib": "^2.5.0" } }, + "@smithy/util-endpoints": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.4.tgz", + "integrity": "sha512-FPry8j1xye5yzrdnf4xKUXVnkQErxdN7bUIaqC0OFoGsv2NfD9b2UUMuZSSt+pr9a8XWAqj0HoyVNUfPiZ/PvQ==", + "requires": { + "@smithy/node-config-provider": "^2.1.5", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + }, + "dependencies": { + "@smithy/node-config-provider": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.5.tgz", + "integrity": "sha512-3Omb5/h4tOCuKRx4p4pkYTvEYRCYoKk52bOYbKUyz/G/8gERbagsN8jFm4FjQubkrcIqQEghTpQaUw6uk+0edw==", + "requires": { + "@smithy/property-provider": "^2.0.14", + "@smithy/shared-ini-file-loader": "^2.2.4", + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/property-provider": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.14.tgz", + "integrity": "sha512-k3D2qp9o6imTrLaXRj6GdLYEJr1sXqS99nLhzq8fYmJjSVOeMg/G+1KVAAc7Oxpu71rlZ2f8SSZxcSxkevuR0A==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/shared-ini-file-loader": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.4.tgz", + "integrity": "sha512-9dRknGgvYlRIsoTcmMJXuoR/3ekhGwhRq4un3ns2/byre4Ql5hyUN4iS0x8eITohjU90YOnUCsbRwZRvCkbRfw==", + "requires": { + "@smithy/types": "^2.5.0", + "tslib": "^2.5.0" + } + }, + "@smithy/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.5.0.tgz", + "integrity": "sha512-/a31lYofrMBkJb3BuPlYJTMKDj0hUmKUP6JFZQu6YVuQVoAjubiY0A52U9S0Uysd33n/djexCUSNJ+G9bf3/aA==", + "requires": { + "tslib": "^2.5.0" + } + } + } + }, "@smithy/util-hex-encoding": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-1.0.2.tgz", @@ -21434,6 +24181,109 @@ "tslib": "^2.5.0" } }, + "@swc/core": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.99.tgz", + "integrity": "sha512-8O996RfuPC4ieb4zbYMfbyCU9k4gSOpyCNnr7qBQ+o7IEmh8JCV6B8wwu+fT/Om/6Lp34KJe1IpJ/24axKS6TQ==", + "dev": true, + "requires": { + "@swc/core-darwin-arm64": "1.3.99", + "@swc/core-darwin-x64": "1.3.99", + "@swc/core-linux-arm64-gnu": "1.3.99", + "@swc/core-linux-arm64-musl": "1.3.99", + "@swc/core-linux-x64-gnu": "1.3.99", + "@swc/core-linux-x64-musl": "1.3.99", + "@swc/core-win32-arm64-msvc": "1.3.99", + "@swc/core-win32-ia32-msvc": "1.3.99", + "@swc/core-win32-x64-msvc": "1.3.99", + "@swc/counter": "^0.1.1", + "@swc/types": "^0.1.5" + } + }, + "@swc/core-darwin-arm64": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.99.tgz", + "integrity": "sha512-Qj7Jct68q3ZKeuJrjPx7k8SxzWN6PqLh+VFxzA+KwLDpQDPzOlKRZwkIMzuFjLhITO4RHgSnXoDk/Syz0ZeN+Q==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.99.tgz", + "integrity": "sha512-wR7m9QVJjgiBu1PSOHy7s66uJPa45Kf9bZExXUL+JAa9OQxt5y+XVzr+n+F045VXQOwdGWplgPnWjgbUUHEVyw==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.99.tgz", + "integrity": "sha512-gcGv1l5t0DScEONmw5OhdVmEI/o49HCe9Ik38zzH0NtDkc+PDYaCcXU5rvfZP2qJFaAAr8cua8iJcOunOSLmnA==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.99.tgz", + "integrity": "sha512-XL1/eUsTO8BiKsWq9i3iWh7H99iPO61+9HYiWVKhSavknfj4Plbn+XyajDpxsauln5o8t+BRGitymtnAWJM4UQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.99.tgz", + "integrity": "sha512-fGrXYE6DbTfGNIGQmBefYxSk3rp/1lgbD0nVg4rl4mfFRQPi7CgGhrrqSuqZ/ezXInUIgoCyvYGWFSwjLXt/Qg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.99.tgz", + "integrity": "sha512-kvgZp/mqf3IJ806gUOL6gN6VU15+DfzM1Zv4Udn8GqgXiUAvbQehrtruid4Snn5pZTLj4PEpSCBbxgxK1jbssA==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.99.tgz", + "integrity": "sha512-yt8RtZ4W/QgFF+JUemOUQAkVW58cCST7mbfKFZ1v16w3pl3NcWd9OrtppFIXpbjU1rrUX2zp2R7HZZzZ2Zk/aQ==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.99.tgz", + "integrity": "sha512-62p5fWnOJR/rlbmbUIpQEVRconICy5KDScWVuJg1v3GPLBrmacjphyHiJC1mp6dYvvoEWCk/77c/jcQwlXrDXw==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.3.99", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.99.tgz", + "integrity": "sha512-PdppWhkoS45VGdMBxvClVgF1hVjqamtvYd82Gab1i4IV45OSym2KinoDCKE1b6j3LwBLOn2J9fvChGSgGfDCHQ==", + "dev": true, + "optional": true + }, + "@swc/counter": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.2.tgz", + "integrity": "sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==", + "dev": true + }, + "@swc/helpers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.3.tgz", + "integrity": "sha512-FaruWX6KdudYloq1AHD/4nU+UsMTdNE8CKyrseXWEcgjDAbvkwJg2QGPAnfIJLIWsjZOSPLOAykK6fuYp4vp4A==", + "dev": true, + "requires": { + "tslib": "^2.4.0" + } + }, + "@swc/types": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz", + "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", + "dev": true + }, "@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -22216,7 +25066,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "requires": { "color-convert": "^2.0.1" } @@ -22401,9 +25250,9 @@ } }, "axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -22549,6 +25398,27 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + }, + "dependencies": { + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + } + } + }, "body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -22741,7 +25611,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -22811,6 +25680,19 @@ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==" }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==" + }, "cliui": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", @@ -22848,7 +25730,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "requires": { "color-name": "~1.1.4" } @@ -22856,8 +25737,7 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "color-support": { "version": "1.1.3", @@ -23057,6 +25937,21 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + } + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -23143,6 +26038,17 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" }, + "duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, "ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -23984,8 +26890,7 @@ "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "has-proto": { "version": "1.0.1", @@ -24308,6 +27213,11 @@ "is-extglob": "^2.1.1" } }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, "is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -24349,6 +27259,11 @@ "which-typed-array": "^1.1.11" } }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -25091,6 +28006,15 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, "long": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", @@ -25236,8 +28160,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimatch": { "version": "3.1.2", @@ -27519,7 +30442,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, "requires": { "mimic-fn": "^2.1.0" } @@ -27538,6 +30460,22 @@ "type-check": "^0.4.0" } }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -27561,6 +30499,11 @@ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz", "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" }, + "p-throttle": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-5.1.0.tgz", + "integrity": "sha512-+N+s2g01w1Zch4D0K3OpnPDqLOKmLcQ4BvIFq3JC0K29R28vUOjWpO+OJZBNt8X9i3pFCksZJZ0YXkUGjaFE6g==" + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -28697,9 +31640,10 @@ } }, "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==", + "dev": true }, "require-directory": { "version": "2.1.1", @@ -28751,6 +31695,15 @@ "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", "dev": true }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -29119,6 +32072,11 @@ "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==" }, + "stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" + }, "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -29249,7 +32207,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "requires": { "has-flag": "^4.0.0" } @@ -29698,6 +32655,14 @@ "makeerror": "1.0.12" } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "requires": { + "defaults": "^1.0.3" + } + }, "webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index 9768762e8..933094768 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,6 +8,7 @@ "@octokit/rest": "^19.0.5", "@sentry/node": "^7.77.0", "@sentry/tracing": "^7.48.0", + "@serdnam/pino-cloudwatch-transport": "^1.0.4", "@types/crypto-js": "^4.1.1", "@types/libsodium-wrappers": "^0.7.10", "@ucast/mongo2js": "^1.3.4", @@ -41,13 +42,14 @@ "nanoid": "^3.3.6", "node-cache": "^5.1.2", "nodemailer": "^6.8.0", + "ora": "^5.4.1", "passport": "^0.6.0", "passport-github": "^1.1.0", "passport-gitlab2": "^5.0.0", "passport-google-oauth20": "^2.0.0", + "pg": "^8.11.3", "pino": "^8.16.1", "pino-http": "^8.5.1", - "pg": "^8.11.3", "posthog-node": "^2.6.0", "probot": "^12.3.1", "query-string": "^7.1.3", @@ -70,7 +72,7 @@ "main": "src/index.js", "scripts": { "start": "node build/index.js", - "dev": "nodemon index.js | pino-pretty --colorize", + "dev": "nodemon index.js", "swagger-autogen": "node ./swagger/index.ts", "build": "rimraf ./build && tsc && cp -R ./src/templates ./build && cp -R ./src/data ./build", "lint": "eslint . --ext .ts", @@ -95,6 +97,8 @@ "devDependencies": { "@jest/globals": "^29.3.1", "@posthog/plugin-scaffold": "^1.3.4", + "@swc/core": "^1.3.99", + "@swc/helpers": "^0.5.3", "@types/bcrypt": "^5.0.0", "@types/bcryptjs": "^2.4.2", "@types/bull": "^4.10.0", @@ -125,6 +129,7 @@ "nodemon": "^2.0.19", "npm": "^8.19.3", "pino-pretty": "^10.2.3", + "regenerator-runtime": "^0.14.0", "smee-client": "^1.2.3", "supertest": "^6.3.3", "swagger-autogen": "^2.23.5", diff --git a/backend/src/bootstrap.ts b/backend/src/bootstrap.ts new file mode 100644 index 000000000..a1ea148c2 --- /dev/null +++ b/backend/src/bootstrap.ts @@ -0,0 +1,43 @@ +import ora from "ora"; +import nodemailer from "nodemailer"; +import { getSmtpHost, getSmtpPort } from "./config"; +import { logger } from "./utils/logging"; +import mongoose from "mongoose"; +import { redisClient } from "./services/RedisService"; + +type BootstrapOpt = { + transporter: nodemailer.Transporter; +}; + +export const bootstrap = async ({ transporter }: BootstrapOpt) => { + const spinner = ora().start(); + spinner.info("Checking configurations..."); + spinner.info("Testing smtp connection"); + + await transporter + .verify() + .then(async () => { + spinner.succeed("SMTP successfully connected"); + }) + .catch(async (err) => { + spinner.fail(`SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`); + logger.error(err); + }); + + spinner.info("Testing mongodb connection"); + if (mongoose.connection.readyState !== mongoose.ConnectionStates.connected) { + spinner.fail("Mongo DB - Failed to connect"); + } else { + spinner.succeed("Mongodb successfully connected"); + } + + spinner.info("Testing redis connection"); + const redisPing = await redisClient?.ping(); + if (!redisPing) { + spinner.fail("Redis - Failed to connect"); + } else { + spinner.succeed("Redis successfully connected"); + } + + spinner.stop(); +}; diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index c5cb52663..a535c3f54 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -3,99 +3,171 @@ import { GITLAB_URL } from "../variables"; import InfisicalClient from "infisical-node"; export const client = new InfisicalClient({ - token: process.env.INFISICAL_TOKEN!, + token: process.env.INFISICAL_TOKEN! }); export const getPort = async () => (await client.getSecret("PORT")).secretValue || 4000; export const getEncryptionKey = async () => { const secretValue = (await client.getSecret("ENCRYPTION_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} +}; export const getRootEncryptionKey = async () => { const secretValue = (await client.getSecret("ROOT_ENCRYPTION_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} -export const getInviteOnlySignup = async () => (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true" -export const getSaltRounds = async () => parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; -export const getAuthSecret = async () => (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? (await client.getSecret("AUTH_SECRET")).secretValue; -export const getJwtAuthLifetime = async () => (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; -export const getJwtMfaLifetime = async () => (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; -export const getJwtRefreshLifetime = async () => (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; -export const getJwtServiceSecret = async () => (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1) -export const getJwtSignupLifetime = async () => (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; -export const getJwtProviderAuthLifetime = async () => (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; +}; +export const getInviteOnlySignup = async () => + (await client.getSecret("INVITE_ONLY_SIGNUP")).secretValue === "true"; +export const getSaltRounds = async () => + parseInt((await client.getSecret("SALT_ROUNDS")).secretValue) || 10; +export const getAuthSecret = async () => + (await client.getSecret("JWT_AUTH_SECRET")).secretValue ?? + (await client.getSecret("AUTH_SECRET")).secretValue; +export const getJwtAuthLifetime = async () => + (await client.getSecret("JWT_AUTH_LIFETIME")).secretValue || "10d"; +export const getJwtMfaLifetime = async () => + (await client.getSecret("JWT_MFA_LIFETIME")).secretValue || "5m"; +export const getJwtRefreshLifetime = async () => + (await client.getSecret("JWT_REFRESH_LIFETIME")).secretValue || "90d"; +export const getJwtServiceSecret = async () => + (await client.getSecret("JWT_SERVICE_SECRET")).secretValue; // TODO: deprecate (related to ST V1) +export const getJwtSignupLifetime = async () => + (await client.getSecret("JWT_SIGNUP_LIFETIME")).secretValue || "15m"; +export const getJwtProviderAuthLifetime = async () => + (await client.getSecret("JWT_PROVIDER_AUTH_LIFETIME")).secretValue || "15m"; export const getMongoURL = async () => (await client.getSecret("MONGO_URL")).secretValue; -export const getNodeEnv = async () => (await client.getSecret("NODE_ENV")).secretValue || "production"; -export const getVerboseErrorOutput = async () => (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; +export const getNodeEnv = async () => + (await client.getSecret("NODE_ENV")).secretValue || "production"; +export const getVerboseErrorOutput = async () => + (await client.getSecret("VERBOSE_ERROR_OUTPUT")).secretValue === "true" && true; export const getLokiHost = async () => (await client.getSecret("LOKI_HOST")).secretValue; export const getClientIdAzure = async () => (await client.getSecret("CLIENT_ID_AZURE")).secretValue; -export const getClientIdHeroku = async () => (await client.getSecret("CLIENT_ID_HEROKU")).secretValue; -export const getClientIdVercel = async () => (await client.getSecret("CLIENT_ID_VERCEL")).secretValue; -export const getClientIdNetlify = async () => (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; -export const getClientIdGitHub = async () => (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; -export const getClientIdGitLab = async () => (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; -export const getClientIdBitBucket = async () => (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; -export const getClientIdGCPSecretManager = async () => (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue; -export const getClientSecretAzure = async () => (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; -export const getClientSecretHeroku = async () => (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; -export const getClientSecretVercel = async () => (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue; -export const getClientSecretNetlify = async () => (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; -export const getClientSecretGitHub = async () => (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; -export const getClientSecretGitLab = async () => (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; -export const getClientSecretBitBucket = async () => (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; -export const getClientSecretGCPSecretManager = async () => (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue; -export const getClientSlugVercel = async () => (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; +export const getClientIdHeroku = async () => + (await client.getSecret("CLIENT_ID_HEROKU")).secretValue; +export const getClientIdVercel = async () => + (await client.getSecret("CLIENT_ID_VERCEL")).secretValue; +export const getClientIdNetlify = async () => + (await client.getSecret("CLIENT_ID_NETLIFY")).secretValue; +export const getClientIdGitHub = async () => + (await client.getSecret("CLIENT_ID_GITHUB")).secretValue; +export const getClientIdGitLab = async () => + (await client.getSecret("CLIENT_ID_GITLAB")).secretValue; +export const getClientIdBitBucket = async () => + (await client.getSecret("CLIENT_ID_BITBUCKET")).secretValue; +export const getClientIdGCPSecretManager = async () => + (await client.getSecret("CLIENT_ID_GCP_SECRET_MANAGER")).secretValue; +export const getClientSecretAzure = async () => + (await client.getSecret("CLIENT_SECRET_AZURE")).secretValue; +export const getClientSecretHeroku = async () => + (await client.getSecret("CLIENT_SECRET_HEROKU")).secretValue; +export const getClientSecretVercel = async () => + (await client.getSecret("CLIENT_SECRET_VERCEL")).secretValue; +export const getClientSecretNetlify = async () => + (await client.getSecret("CLIENT_SECRET_NETLIFY")).secretValue; +export const getClientSecretGitHub = async () => + (await client.getSecret("CLIENT_SECRET_GITHUB")).secretValue; +export const getClientSecretGitLab = async () => + (await client.getSecret("CLIENT_SECRET_GITLAB")).secretValue; +export const getClientSecretBitBucket = async () => + (await client.getSecret("CLIENT_SECRET_BITBUCKET")).secretValue; +export const getClientSecretGCPSecretManager = async () => + (await client.getSecret("CLIENT_SECRET_GCP_SECRET_MANAGER")).secretValue; +export const getClientSlugVercel = async () => + (await client.getSecret("CLIENT_SLUG_VERCEL")).secretValue; -export const getClientIdGoogleLogin = async () => (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; -export const getClientSecretGoogleLogin = async () => (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; -export const getClientIdGitHubLogin = async () => (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; -export const getClientSecretGitHubLogin = async () => (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; -export const getClientIdGitLabLogin = async () => (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue; -export const getClientSecretGitLabLogin = async () => (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue; -export const getUrlGitLabLogin = async () => (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL; +export const getClientIdGoogleLogin = async () => + (await client.getSecret("CLIENT_ID_GOOGLE_LOGIN")).secretValue; +export const getClientSecretGoogleLogin = async () => + (await client.getSecret("CLIENT_SECRET_GOOGLE_LOGIN")).secretValue; +export const getClientIdGitHubLogin = async () => + (await client.getSecret("CLIENT_ID_GITHUB_LOGIN")).secretValue; +export const getClientSecretGitHubLogin = async () => + (await client.getSecret("CLIENT_SECRET_GITHUB_LOGIN")).secretValue; +export const getClientIdGitLabLogin = async () => + (await client.getSecret("CLIENT_ID_GITLAB_LOGIN")).secretValue; +export const getClientSecretGitLabLogin = async () => + (await client.getSecret("CLIENT_SECRET_GITLAB_LOGIN")).secretValue; +export const getUrlGitLabLogin = async () => + (await client.getSecret("URL_GITLAB_LOGIN")).secretValue || GITLAB_URL; -export const getPostHogHost = async () => (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; -export const getPostHogProjectApiKey = async () => (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; +export const getAwsCloudWatchLog = async () => { + const logGroupName = + (await client.getSecret("AWS_CLOUDWATCH_LOG_GROUP_NAME")).secretValue || "infisical-log-stream"; + const region = (await client.getSecret("AWS_CLOUDWATCH_LOG_REGION")).secretValue; + const accessKeyId = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_ID")).secretValue; + const accessKeySecret = (await client.getSecret("AWS_CLOUDWATCH_LOG_ACCESS_KEY_SECRET")) + .secretValue; + const interval = parseInt( + (await client.getSecret("AWS_CLOUDWATCH_LOG_INTERVAL")).secretValue || 1000, + 10 + ); + if (!region || !accessKeyId || !accessKeySecret) return; + return { logGroupName, region, accessKeySecret, accessKeyId, interval }; +}; + +export const getPostHogHost = async () => + (await client.getSecret("POSTHOG_HOST")).secretValue || "https://app.posthog.com"; +export const getPostHogProjectApiKey = async () => + (await client.getSecret("POSTHOG_PROJECT_API_KEY")).secretValue || + "phc_nSin8j5q2zdhpFDI1ETmFNUIuTG4DwKVyIigrY10XiE"; export const getSentryDSN = async () => (await client.getSecret("SENTRY_DSN")).secretValue; export const getSiteURL = async () => (await client.getSecret("SITE_URL")).secretValue; export const getSmtpHost = async () => (await client.getSecret("SMTP_HOST")).secretValue; -export const getSmtpSecure = async () => (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false; -export const getSmtpPort = async () => parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587; +export const getSmtpSecure = async () => + (await client.getSecret("SMTP_SECURE")).secretValue === "true" || false; +export const getSmtpPort = async () => + parseInt((await client.getSecret("SMTP_PORT")).secretValue) || 587; export const getSmtpUsername = async () => (await client.getSecret("SMTP_USERNAME")).secretValue; export const getSmtpPassword = async () => (await client.getSecret("SMTP_PASSWORD")).secretValue; -export const getSmtpFromAddress = async () => (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue; -export const getSmtpFromName = async () => (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical"; +export const getSmtpFromAddress = async () => + (await client.getSecret("SMTP_FROM_ADDRESS")).secretValue; +export const getSmtpFromName = async () => + (await client.getSecret("SMTP_FROM_NAME")).secretValue || "Infisical"; -export const getSecretScanningWebhookProxy = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue; -export const getSecretScanningWebhookSecret = async () => (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue; -export const getSecretScanningGitAppId = async () => (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue; -export const getSecretScanningPrivateKey = async () => (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue; +export const getSecretScanningWebhookProxy = async () => + (await client.getSecret("SECRET_SCANNING_WEBHOOK_PROXY")).secretValue; +export const getSecretScanningWebhookSecret = async () => + (await client.getSecret("SECRET_SCANNING_WEBHOOK_SECRET")).secretValue; +export const getSecretScanningGitAppId = async () => + (await client.getSecret("SECRET_SCANNING_GIT_APP_ID")).secretValue; +export const getSecretScanningPrivateKey = async () => + (await client.getSecret("SECRET_SCANNING_PRIVATE_KEY")).secretValue; export const getRedisUrl = async () => (await client.getSecret("REDIS_URL")).secretValue; +export const getIsInfisicalCloud = async () => + (await client.getSecret("INFISICAL_CLOUD")).secretValue === "true"; export const getLicenseKey = async () => { const secretValue = (await client.getSecret("LICENSE_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} +}; export const getLicenseServerKey = async () => { const secretValue = (await client.getSecret("LICENSE_SERVER_KEY")).secretValue; return secretValue === "" ? undefined : secretValue; -} -export const getLicenseServerUrl = async () => (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com"; +}; +export const getLicenseServerUrl = async () => + (await client.getSecret("LICENSE_SERVER_URL")).secretValue || "https://portal.infisical.com"; -export const getTelemetryEnabled = async () => (await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true; +export const getTelemetryEnabled = async () => + (await client.getSecret("TELEMETRY_ENABLED")).secretValue !== "false" && true; export const getLoopsApiKey = async () => (await client.getSecret("LOOPS_API_KEY")).secretValue; -export const getSmtpConfigured = async () => (await client.getSecret("SMTP_HOST")).secretValue == "" || (await client.getSecret("SMTP_HOST")).secretValue == undefined ? false : true +export const getSmtpConfigured = async () => + (await client.getSecret("SMTP_HOST")).secretValue == "" || + (await client.getSecret("SMTP_HOST")).secretValue == undefined + ? false + : true; export const getHttpsEnabled = async () => { if ((await getNodeEnv()) != "production") { // no https for anything other than prod - return false + return false; } - if ((await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || (await client.getSecret("HTTPS_ENABLED")).secretValue == "") { + if ( + (await client.getSecret("HTTPS_ENABLED")).secretValue == undefined || + (await client.getSecret("HTTPS_ENABLED")).secretValue == "" + ) { // default when no value present - return true + return true; } - return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true -} + return (await client.getSecret("HTTPS_ENABLED")).secretValue === "true" && true; +}; diff --git a/backend/src/config/serverConfig.ts b/backend/src/config/serverConfig.ts new file mode 100644 index 000000000..7f1867d15 --- /dev/null +++ b/backend/src/config/serverConfig.ts @@ -0,0 +1,24 @@ +import { IServerConfig, ServerConfig } from "../models/serverConfig"; + +let serverConfig: IServerConfig; + +export const serverConfigInit = async () => { + const cfg = await ServerConfig.findOne({}); + if (!cfg) { + const cfg = new ServerConfig(); + await cfg.save(); + serverConfig = cfg; + } else { + serverConfig = cfg; + } + return serverConfig; +}; + +export const getServerConfig = () => serverConfig; + +export const updateServerConfig = async (data: Partial) => { + const cfg = await ServerConfig.findByIdAndUpdate(serverConfig._id, data, { new: true }); + if (!cfg) throw new Error("Failed to update server config"); + serverConfig = cfg; + return serverConfig; +}; diff --git a/backend/src/controllers/v1/adminController.ts b/backend/src/controllers/v1/adminController.ts new file mode 100644 index 000000000..004a33a68 --- /dev/null +++ b/backend/src/controllers/v1/adminController.ts @@ -0,0 +1,100 @@ +import { Request, Response } from "express"; +import { getHttpsEnabled } from "../../config"; +import { getServerConfig, updateServerConfig as setServerConfig } from "../../config/serverConfig"; +import { initializeDefaultOrg, issueAuthTokens } from "../../helpers"; +import { validateRequest } from "../../helpers/validation"; +import { User } from "../../models"; +import { TelemetryService } from "../../services"; +import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; +import * as reqValidator from "../../validation/admin"; + +export const getServerConfigInfo = (_req: Request, res: Response) => { + const config = getServerConfig(); + return res.send({ config }); +}; + +export const updateServerConfig = async (req: Request, res: Response) => { + const { + body: { allowSignUp } + } = await validateRequest(reqValidator.UpdateServerConfigV1, req); + const config = await setServerConfig({ allowSignUp }); + return res.send({ config }); +}; + +export const adminSignUp = async (req: Request, res: Response) => { + const cfg = getServerConfig(); + if (cfg.initialized) throw UnauthorizedRequestError({ message: "Admin has been created" }); + const { + body: { + email, + publicKey, + salt, + lastName, + verifier, + firstName, + protectedKey, + protectedKeyIV, + protectedKeyTag, + encryptedPrivateKey, + encryptedPrivateKeyIV, + encryptedPrivateKeyTag + } + } = await validateRequest(reqValidator.SignupV1, req); + let user = await User.findOne({ email }); + if (user) throw BadRequestError({ message: "User already exist" }); + user = new User({ + email, + firstName, + lastName, + encryptionVersion: 2, + protectedKey, + protectedKeyIV, + protectedKeyTag, + publicKey, + encryptedPrivateKey, + iv: encryptedPrivateKeyIV, + tag: encryptedPrivateKeyTag, + salt, + verifier, + superAdmin: true + }); + await user.save(); + await initializeDefaultOrg({ organizationName: "Admin Org", user }); + + await setServerConfig({ initialized: true }); + + // issue tokens + const tokens = await issueAuthTokens({ + userId: user._id, + ip: req.realIP, + userAgent: req.headers["user-agent"] ?? "" + }); + + const token = tokens.token; + + const postHogClient = await TelemetryService.getPostHogClient(); + if (postHogClient) { + postHogClient.capture({ + event: "admin initialization", + properties: { + email: user.email, + lastName, + firstName + } + }); + } + + // store (refresh) token in httpOnly cookie + res.cookie("jid", tokens.refreshToken, { + httpOnly: true, + path: "/", + sameSite: "strict", + secure: await getHttpsEnabled() + }); + + return res.status(200).send({ + message: "Successfully set up admin account", + user, + token + }); +}; diff --git a/backend/src/controllers/v1/botController.ts b/backend/src/controllers/v1/botController.ts index 65251e83a..3a2ff606a 100644 --- a/backend/src/controllers/v1/botController.ts +++ b/backend/src/controllers/v1/botController.ts @@ -7,7 +7,7 @@ import * as reqValidator from "../../validation/bot"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { BadRequestError } from "../../utils/errors"; @@ -28,7 +28,11 @@ export const getBotByWorkspaceId = async (req: Request, res: Response) => { const { params: { workspaceId } } = await validateRequest(reqValidator.GetBotByWorkspaceIdV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -70,7 +74,11 @@ export const setBotActiveState = async (req: Request, res: Response) => { } const userId = req.user._id; - const { permission } = await getUserProjectPermissions(userId, bot.workspace.toString()); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: bot.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations diff --git a/backend/src/controllers/v1/index.ts b/backend/src/controllers/v1/index.ts index b32debeca..fe171ae86 100644 --- a/backend/src/controllers/v1/index.ts +++ b/backend/src/controllers/v1/index.ts @@ -16,6 +16,8 @@ import * as workspaceController from "./workspaceController"; import * as secretScanningController from "./secretScanningController"; import * as webhookController from "./webhookController"; import * as secretImpsController from "./secretImpsController"; +import * as adminController from "./adminController"; + export { authController, botController, @@ -34,5 +36,6 @@ export { workspaceController, secretScanningController, webhookController, - secretImpsController + secretImpsController, + adminController }; diff --git a/backend/src/controllers/v1/integrationAuthController.ts b/backend/src/controllers/v1/integrationAuthController.ts index ec166df48..d04ead63f 100644 --- a/backend/src/controllers/v1/integrationAuthController.ts +++ b/backend/src/controllers/v1/integrationAuthController.ts @@ -25,7 +25,7 @@ import * as reqValidator from "../../validation/integrationAuth"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { getIntegrationAuthAccessHelper } from "../../helpers"; @@ -40,15 +40,15 @@ export const getIntegrationAuth = async (req: Request, res: Response) => { const integrationAuth = await IntegrationAuth.findById(integrationAuthId); - if (!integrationAuth) - return res.status(400).send({ - message: "Failed to find integration authorization" - }); + if (!integrationAuth) return res.status(400).send({ + message: "Failed to find integration authorization" + }); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -79,7 +79,11 @@ export const oAuthExchange = async (req: Request, res: Response) => { } = await validateRequest(reqValidator.OauthExchangeV1, req); if (!INTEGRATION_SET.has(integration)) throw new Error("Failed to validate integration"); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Integrations @@ -131,7 +135,11 @@ export const saveIntegrationToken = async (req: Request, res: Response) => { body: { workspaceId, integration, url, accessId, namespace, accessToken, refreshToken } } = await validateRequest(reqValidator.SaveIntegrationAccessTokenV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Integrations @@ -224,11 +232,12 @@ export const getIntegrationAuthApps = async (req: Request, res: Response) => { const { integrationAuth, accessToken, accessId } = await getIntegrationAuthAccessHelper({ integrationAuthId: new Types.ObjectId(integrationAuthId) }); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -263,10 +272,11 @@ export const getIntegrationAuthTeams = async (req: Request, res: Response) => { integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -299,10 +309,11 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -360,10 +371,11 @@ export const getIntegrationAuthChecklyGroups = async (req: Request, res: Respons integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -413,10 +425,11 @@ export const getIntegrationAuthQoveryOrgs = async (req: Request, res: Response) integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -465,10 +478,11 @@ export const getIntegrationAuthQoveryProjects = async (req: Request, res: Respon integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -526,10 +540,11 @@ export const getIntegrationAuthQoveryEnvironments = async (req: Request, res: Re integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -587,10 +602,11 @@ export const getIntegrationAuthQoveryApps = async (req: Request, res: Response) integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -648,10 +664,11 @@ export const getIntegrationAuthQoveryContainers = async (req: Request, res: Resp integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -709,10 +726,11 @@ export const getIntegrationAuthQoveryJobs = async (req: Request, res: Response) integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -771,10 +789,11 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -864,10 +883,11 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -988,10 +1008,11 @@ export const getIntegrationAuthBitBucketWorkspaces = async (req: Request, res: R integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -1044,10 +1065,11 @@ export const getIntegrationAuthNorthflankSecretGroups = async (req: Request, res integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -1132,10 +1154,11 @@ export const getIntegrationAuthTeamCityBuildConfigs = async (req: Request, res: integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -1201,10 +1224,11 @@ export const deleteIntegrationAuth = async (req: Request, res: Response) => { integrationAuthId: new Types.ObjectId(integrationAuthId) }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations diff --git a/backend/src/controllers/v1/integrationController.ts b/backend/src/controllers/v1/integrationController.ts index e709d9d0c..d426be0a1 100644 --- a/backend/src/controllers/v1/integrationController.ts +++ b/backend/src/controllers/v1/integrationController.ts @@ -13,7 +13,7 @@ import * as reqValidator from "../../validation/integration"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; @@ -51,11 +51,12 @@ export const createIntegration = async (req: Request, res: Response) => { ); if (!integrationAuth) throw BadRequestError({ message: "Integration auth not found" }); - - const { permission } = await getUserProjectPermissions( - req.user._id, - integrationAuth.workspace._id.toString() - ); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integrationAuth.workspace._id + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Integrations @@ -164,10 +165,11 @@ export const updateIntegration = async (req: Request, res: Response) => { const integration = await Integration.findById(integrationId); if (!integration) throw BadRequestError({ message: "Integration not found" }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integration.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integration.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations @@ -234,10 +236,11 @@ export const deleteIntegration = async (req: Request, res: Response) => { const integration = await Integration.findById(integrationId); if (!integration) throw BadRequestError({ message: "Integration not found" }); - const { permission } = await getUserProjectPermissions( - req.user._id, - integration.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: integration.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Integrations @@ -285,7 +288,11 @@ export const manualSync = async (req: Request, res: Response) => { body: { workspaceId, environment } } = await validateRequest(reqValidator.ManualSyncV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Integrations diff --git a/backend/src/controllers/v1/keyController.ts b/backend/src/controllers/v1/keyController.ts index 242cb8f82..956487814 100644 --- a/backend/src/controllers/v1/keyController.ts +++ b/backend/src/controllers/v1/keyController.ts @@ -9,7 +9,7 @@ import * as reqValidator from "../../validation/key"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; @@ -26,7 +26,11 @@ export const uploadKey = async (req: Request, res: Response) => { body: { key } } = await validateRequest(reqValidator.UploadKeyV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Member diff --git a/backend/src/controllers/v1/membershipController.ts b/backend/src/controllers/v1/membershipController.ts index 928d5b50b..cb11c4b74 100644 --- a/backend/src/controllers/v1/membershipController.ts +++ b/backend/src/controllers/v1/membershipController.ts @@ -12,7 +12,7 @@ import * as reqValidator from "../../validation/membership"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { BadRequestError } from "../../utils/errors"; @@ -63,11 +63,12 @@ export const deleteMembership = async (req: Request, res: Response) => { if (!membershipToDelete) { throw new Error("Failed to delete workspace membership that doesn't exist"); } - - const { permission } = await getUserProjectPermissions( - req.user._id, - membershipToDelete.workspace.toString() - ); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: membershipToDelete.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Member @@ -118,10 +119,11 @@ export const changeMembershipRole = async (req: Request, res: Response) => { throw new Error("Failed to find membership to change role"); } - const { permission } = await getUserProjectPermissions( - req.user._id, - membershipToChangeRole.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: membershipToChangeRole.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Member @@ -191,7 +193,12 @@ export const inviteUserToWorkspace = async (req: Request, res: Response) => { params: { workspaceId }, body: { email } } = await validateRequest(InviteUserToWorkspaceV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Member diff --git a/backend/src/controllers/v1/secretImpsController.ts b/backend/src/controllers/v1/secretImpsController.ts index 80adfc69a..14c99bb03 100644 --- a/backend/src/controllers/v1/secretImpsController.ts +++ b/backend/src/controllers/v1/secretImpsController.ts @@ -1,4 +1,6 @@ + import { Request, Response } from "express"; +import { Types } from "mongoose"; import { isValidScope } from "../../helpers"; import { Folder, IServiceTokenData, SecretImport, ServiceTokenData } from "../../models"; import { getAllImportedSecrets } from "../../services/SecretImportService"; @@ -15,7 +17,7 @@ import * as reqValidator from "../../validation/secretImports"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError, subject } from "@casl/ability"; @@ -105,7 +107,11 @@ export const createSecretImp = async (req: Request, res: Response) => { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } } else { - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) @@ -313,10 +319,11 @@ export const updateSecretImport = async (req: Request, res: Response) => { } } else { // non token entry check - const { permission } = await getUserProjectPermissions( - req.user._id, - importSecDoc.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: importSecDoc.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { @@ -442,10 +449,11 @@ export const deleteSecretImport = async (req: Request, res: Response) => { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } } else { - const { permission } = await getUserProjectPermissions( - req.user._id, - importSecDoc.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: importSecDoc.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { @@ -550,7 +558,11 @@ export const getSecretImports = async (req: Request, res: Response) => { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } } else { - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { @@ -604,7 +616,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } } else { - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { @@ -657,10 +673,11 @@ export const getAllSecretsFromImport = async (req: Request, res: Response) => { permissionCheckFn = (env: string, secPath: string) => isValidScope(req.authData.authPayload as IServiceTokenData, env, secPath); } else { - const { permission } = await getUserProjectPermissions( - req.user._id, - importSecDoc.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: importSecDoc.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { diff --git a/backend/src/controllers/v1/secretsFolderController.ts b/backend/src/controllers/v1/secretsFolderController.ts index af9ee5277..52627b2e3 100644 --- a/backend/src/controllers/v1/secretsFolderController.ts +++ b/backend/src/controllers/v1/secretsFolderController.ts @@ -17,7 +17,7 @@ import { import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; import * as reqValidator from "../../validation/folders"; @@ -125,7 +125,11 @@ export const createFolder = async (req: Request, res: Response) => { } } else { // user check - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) @@ -332,7 +336,11 @@ export const updateFolderById = async (req: Request, res: Response) => { throw UnauthorizedRequestError({ message: "Folder Permission Denied" }); } } else { - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) @@ -502,7 +510,11 @@ export const deleteFolder = async (req: Request, res: Response) => { } } else { // check that user is a member of the workspace - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, subject(ProjectPermissionSub.Secrets, { environment, secretPath: directory }) @@ -649,7 +661,10 @@ export const getFolders = async (req: Request, res: Response) => { } } else { // check that user is a member of the workspace - await getUserProjectPermissions(req.user._id, workspaceId); + await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); } const folders = await Folder.findOne({ workspace: workspaceId, environment }); diff --git a/backend/src/controllers/v1/signupController.ts b/backend/src/controllers/v1/signupController.ts index 4dceb2783..6422808ab 100644 --- a/backend/src/controllers/v1/signupController.ts +++ b/backend/src/controllers/v1/signupController.ts @@ -2,10 +2,8 @@ import { Request, Response } from "express"; import { AuthMethod, User } from "../../models"; import { checkEmailVerification, sendEmailVerification } from "../../helpers/signup"; import { createToken } from "../../helpers/auth"; -import { BadRequestError } from "../../utils/errors"; import { getAuthSecret, - getInviteOnlySignup, getJwtSignupLifetime, getSmtpConfigured } from "../../config"; @@ -68,16 +66,6 @@ export const verifyEmailSignup = async (req: Request, res: Response) => { }); } - if (await getInviteOnlySignup()) { - // Only one user can create an account without being invited. The rest need to be invited in order to make an account - const userCount = await User.countDocuments({}); - if (userCount != 0) { - throw BadRequestError({ - message: "New user sign ups are not allowed at this time. You must be invited to sign up." - }); - } - } - // verify email if (await getSmtpConfigured()) { await checkEmailVerification({ diff --git a/backend/src/controllers/v1/webhookController.ts b/backend/src/controllers/v1/webhookController.ts index 09175ebfe..852a3aeed 100644 --- a/backend/src/controllers/v1/webhookController.ts +++ b/backend/src/controllers/v1/webhookController.ts @@ -16,7 +16,7 @@ import * as reqValidator from "../../validation/webhooks"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; @@ -26,7 +26,11 @@ export const createWebhook = async (req: Request, res: Response) => { body: { webhookUrl, webhookSecretKey, environment, workspaceId, secretPath } } = await validateRequest(reqValidator.CreateWebhookV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Webhooks @@ -98,11 +102,11 @@ export const updateWebhook = async (req: Request, res: Response) => { if (!webhook) { throw BadRequestError({ message: "Webhook not found!!" }); } - - const { permission } = await getUserProjectPermissions( - req.user._id, - webhook.workspace.toString() - ); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: webhook.workspace + }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Webhooks @@ -146,10 +150,11 @@ export const deleteWebhook = async (req: Request, res: Response) => { throw ResourceNotFoundError({ message: "Webhook not found!!" }); } - const { permission } = await getUserProjectPermissions( - req.user._id, - webhook.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: webhook.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Webhooks @@ -193,10 +198,11 @@ export const testWebhook = async (req: Request, res: Response) => { throw BadRequestError({ message: "Webhook not found!!" }); } - const { permission } = await getUserProjectPermissions( - req.user._id, - webhook.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: webhook.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks @@ -236,8 +242,12 @@ export const listWebhooks = async (req: Request, res: Response) => { const { query: { environment, workspaceId, secretPath } } = await validateRequest(reqValidator.ListWebhooksV1, req); - - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Webhooks diff --git a/backend/src/controllers/v1/workspaceController.ts b/backend/src/controllers/v1/workspaceController.ts index 1b3a9fd3f..9d3ae5b80 100644 --- a/backend/src/controllers/v1/workspaceController.ts +++ b/backend/src/controllers/v1/workspaceController.ts @@ -25,7 +25,7 @@ import * as reqValidator from "../../validation"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; /** @@ -39,7 +39,11 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspacePublicKeysV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Member @@ -72,7 +76,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceMembershipsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Member @@ -195,7 +203,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.DeleteWorkspaceV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Workspace @@ -223,7 +235,11 @@ export const changeWorkspaceName = async (req: Request, res: Response) => { body: { name } } = await validateRequest(reqValidator.ChangeWorkspaceNameV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Workspace @@ -257,7 +273,12 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => { const { params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceIntegrationsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -283,7 +304,11 @@ export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: R params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceIntegrationAuthorizationsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Integrations @@ -309,7 +334,11 @@ export const getWorkspaceServiceTokens = async (req: Request, res: Response) => params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceServiceTokensV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.ServiceTokens diff --git a/backend/src/controllers/v2/environmentController.ts b/backend/src/controllers/v2/environmentController.ts index 130e2e225..3b412638b 100644 --- a/backend/src/controllers/v2/environmentController.ts +++ b/backend/src/controllers/v2/environmentController.ts @@ -12,14 +12,12 @@ import { import { EventType, SecretVersion } from "../../ee/models"; import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { BadRequestError, WorkspaceNotFoundError } from "../../utils/errors"; -import _ from "lodash"; -import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables"; import { validateRequest } from "../../helpers/validation"; import * as reqValidator from "../../validation/environments"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { SecretImport } from "../../models"; @@ -114,7 +112,11 @@ export const createWorkspaceEnvironment = async (req: Request, res: Response) => body: { environmentName, environmentSlug } } = await validateRequest(reqValidator.CreateWorkspaceEnvironmentV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Environments @@ -191,7 +193,11 @@ export const reorderWorkspaceEnvironments = async (req: Request, res: Response) body: { environmentName, environmentSlug, otherEnvironmentSlug, otherEnvironmentName } } = await validateRequest(reqValidator.ReorderWorkspaceEnvironmentsV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Environments @@ -322,7 +328,11 @@ export const renameWorkspaceEnvironment = async (req: Request, res: Response) => body: { environmentName, environmentSlug, oldEnvironmentSlug } } = await validateRequest(reqValidator.UpdateWorkspaceEnvironmentV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Environments @@ -511,7 +521,11 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => body: { environmentSlug } } = await validateRequest(reqValidator.DeleteWorkspaceEnvironmentV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Environments @@ -587,98 +601,4 @@ export const deleteWorkspaceEnvironment = async (req: Request, res: Response) => workspace: workspaceId, environment: environmentSlug }); -}; - -// TODO(akhilmhdh) after rbac this can be completely removed -export const getAllAccessibleEnvironmentsOfWorkspace = async (req: Request, res: Response) => { - /* - #swagger.summary = 'Get all accessible environments of a workspace' - #swagger.description = 'Fetch all environments that the user has access to in a specified workspace' - - #swagger.security = [{ - "apiKeyAuth": [] - }] - - #swagger.parameters['workspaceId'] = { - "description": "ID of the workspace", - "required": true, - "type": "string", - "in": "path" - } - - #swagger.responses[200] = { - content: { - "application/json": { - "schema": { - "type": "object", - "properties": { - "accessibleEnvironments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "example": "Development" - }, - "slug": { - "type": "string", - "example": "development" - }, - "isWriteDenied": { - "type": "boolean", - "example": false - }, - "isReadDenied": { - "type": "boolean", - "example": false - } - } - } - } - }, - "description": "List of environments the user has access to in the specified workspace" - } - } - } - } - */ - const { - params: { workspaceId } - } = await validateRequest(reqValidator.GetAllAccessibileEnvironmentsOfWorkspaceV2, req); - - const { membership: workspacesUserIsMemberOf } = await getUserProjectPermissions( - req.user._id, - workspaceId - ); - - const accessibleEnvironments: any = []; - const deniedPermission = workspacesUserIsMemberOf.deniedPermissions; - - const relatedWorkspace = await Workspace.findById(workspaceId); - if (!relatedWorkspace) { - throw BadRequestError(); - } - relatedWorkspace.environments.forEach((environment) => { - const isReadBlocked = _.some(deniedPermission, { - environmentSlug: environment.slug, - ability: PERMISSION_READ_SECRETS - }); - const isWriteBlocked = _.some(deniedPermission, { - environmentSlug: environment.slug, - ability: PERMISSION_WRITE_SECRETS - }); - if (isReadBlocked && isWriteBlocked) { - return; - } else { - accessibleEnvironments.push({ - name: environment.name, - slug: environment.slug, - isWriteDenied: isWriteBlocked, - isReadDenied: isReadBlocked - }); - } - }); - - res.json({ accessibleEnvironments }); -}; +}; \ No newline at end of file diff --git a/backend/src/controllers/v2/membershipController.ts b/backend/src/controllers/v2/membershipController.ts index 537134963..d6d25dad5 100644 --- a/backend/src/controllers/v2/membershipController.ts +++ b/backend/src/controllers/v2/membershipController.ts @@ -8,7 +8,7 @@ import { EEAuditLogService } from "../../ee/services"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { sendMail } from "../../helpers"; import { validateRequest } from "../../helpers/validation"; @@ -27,7 +27,11 @@ export const addUserToWorkspace = async (req: Request, res: Response) => { if (!workspace) throw new Error("Failed to find workspace"); // check permission - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Member diff --git a/backend/src/controllers/v2/organizationsController.ts b/backend/src/controllers/v2/organizationsController.ts index 9d08cce3d..5a14b5838 100644 --- a/backend/src/controllers/v2/organizationsController.ts +++ b/backend/src/controllers/v2/organizationsController.ts @@ -1,24 +1,16 @@ import { Request, Response } from "express"; import { Types } from "mongoose"; -import { - Membership, - MembershipOrg, - Workspace -} from "../../models"; +import { Membership, MembershipOrg, Workspace } from "../../models"; import { Role } from "../../ee/models"; import { deleteMembershipOrg } from "../../helpers/membershipOrg"; -import { +import { createOrganization as create, deleteOrganization, updateSubscriptionOrgQuantity } from "../../helpers/organization"; import { addMembershipsOrg } from "../../helpers/membershipOrg"; import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; -import { - ACCEPTED, - ADMIN, - CUSTOM -} from "../../variables"; +import { ACCEPTED, ADMIN, CUSTOM } from "../../variables"; import * as reqValidator from "../../validation/organization"; import { validateRequest } from "../../helpers/validation"; import { @@ -155,7 +147,7 @@ export const updateOrganizationMembership = async (req: Request, res: Response) OrgPermissionSubjects.Member ); - const isCustomRole = !["admin", "member", "owner"].includes(role); + const isCustomRole = !["admin", "member"].includes(role); if (isCustomRole) { const orgRole = await Role.findOne({ slug: role, isOrgRole: true }); if (!orgRole) throw BadRequestError({ message: "Role not found" }); @@ -336,7 +328,7 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) => * @param res * @returns */ - export const createOrganization = async (req: Request, res: Response) => { +export const createOrganization = async (req: Request, res: Response) => { const { body: { name } } = await validateRequest(reqValidator.CreateOrgv2, req); @@ -361,27 +353,27 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) => /** * Delete organization with id [organizationId] - * @param req - * @param res + * @param req + * @param res */ export const deleteOrganizationById = async (req: Request, res: Response) => { const { params: { organizationId } } = await validateRequest(reqValidator.DeleteOrgv2, req); - + const membershipOrg = await MembershipOrg.findOne({ user: req.user._id, organization: new Types.ObjectId(organizationId), role: ADMIN }); - + if (!membershipOrg) throw UnauthorizedRequestError(); - + const organization = await deleteOrganization({ organizationId: new Types.ObjectId(organizationId) }); - + return res.status(200).send({ organization }); -} \ No newline at end of file +}; diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index b0362f6a4..362221bf4 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -39,7 +39,7 @@ import { import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError, subject } from "@casl/ability"; @@ -159,7 +159,11 @@ export const batchSecrets = async (req: Request, res: Response) => { } // not using service token using auth if (!(req.authData.authPayload instanceof ServiceTokenData)) { - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + if (createSecrets.length) ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, diff --git a/backend/src/controllers/v2/serviceTokenDataController.ts b/backend/src/controllers/v2/serviceTokenDataController.ts index b8f1b8f43..b49639d3f 100644 --- a/backend/src/controllers/v2/serviceTokenDataController.ts +++ b/backend/src/controllers/v2/serviceTokenDataController.ts @@ -11,7 +11,7 @@ import * as reqValidator from "../../validation/serviceTokenData"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { Types } from "mongoose"; @@ -75,7 +75,12 @@ export const createServiceTokenData = async (req: Request, res: Response) => { const { body: { workspaceId, permissions, tag, encryptedKey, scopes, name, expiresIn, iv } } = await validateRequest(reqValidator.CreateServiceTokenV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens @@ -151,10 +156,11 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => { let serviceTokenData = await ServiceTokenData.findById(serviceTokenDataId); if (!serviceTokenData) throw BadRequestError({ message: "Service token not found" }); - const { permission } = await getUserProjectPermissions( - req.user._id, - serviceTokenData.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: serviceTokenData.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.ServiceTokens diff --git a/backend/src/controllers/v2/tagController.ts b/backend/src/controllers/v2/tagController.ts index 668903222..c803b0e18 100644 --- a/backend/src/controllers/v2/tagController.ts +++ b/backend/src/controllers/v2/tagController.ts @@ -7,7 +7,7 @@ import { validateRequest } from "../../helpers/validation"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import * as reqValidator from "../../validation/tags"; @@ -17,7 +17,11 @@ export const createWorkspaceTag = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.CreateWorkspaceTagsV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.Tags @@ -45,10 +49,11 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => { throw BadRequestError(); } - const { permission } = await getUserProjectPermissions( - req.user._id, - tagFromDB.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: tagFromDB.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Tags @@ -66,7 +71,12 @@ export const getWorkspaceTags = async (req: Request, res: Response) => { const { params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceTagsV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Tags diff --git a/backend/src/controllers/v2/workspaceController.ts b/backend/src/controllers/v2/workspaceController.ts index 040c68f31..8c5e70952 100644 --- a/backend/src/controllers/v2/workspaceController.ts +++ b/backend/src/controllers/v2/workspaceController.ts @@ -16,7 +16,7 @@ import * as reqValidator from "../../validation"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; @@ -272,7 +272,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceMembershipsV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.Member @@ -352,7 +356,11 @@ export const updateWorkspaceMembership = async (req: Request, res: Response) => body: { role } } = await validateRequest(reqValidator.UpdateWorkspaceMembershipsV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Member @@ -420,7 +428,11 @@ export const deleteWorkspaceMembership = async (req: Request, res: Response) => params: { workspaceId, membershipId } } = await validateRequest(reqValidator.DeleteWorkspaceMembershipsV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.Member @@ -452,7 +464,11 @@ export const toggleAutoCapitalization = async (req: Request, res: Response) => { body: { autoCapitalization } } = await validateRequest(reqValidator.ToggleAutoCapitalizationV2, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.Settings diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index 1cc3f1953..1c691088a 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -3,11 +3,16 @@ import { Types } from "mongoose"; import { EventService, SecretService } from "../../services"; import { eventPushSecrets } from "../../events"; import { BotService } from "../../services"; -import { containsGlobPatterns, isValidScopeV3, repackageSecretToRaw } from "../../helpers/secrets"; +import { containsGlobPatterns, repackageSecretToRaw } from "../../helpers/secrets"; import { encryptSymmetric128BitHexKeyUTF8 } from "../../utils/crypto"; import { getAllImportedSecrets } from "../../services/SecretImportService"; -import { Folder, IMembership, IServiceTokenData, IServiceTokenDataV3 } from "../../models"; -import { Permission } from "../../models/serviceTokenDataV3"; +import { + Folder, + IServiceTokenData, + Membership, + ServiceTokenData, + User +} from "../../models"; import { getFolderByPath } from "../../services/FolderService"; import { BadRequestError } from "../../utils/errors"; import { validateRequest } from "../../helpers/validation"; @@ -15,13 +20,10 @@ import * as reqValidator from "../../validation/secrets"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { ForbiddenError, subject } from "@casl/ability"; -import { - validateServiceTokenDataClientForWorkspace, - validateServiceTokenDataV3ClientForWorkspace -} from "../../validation"; +import { validateServiceTokenDataClientForWorkspace } from "../../validation"; import { PERMISSION_READ_SECRETS, PERMISSION_WRITE_SECRETS } from "../../variables"; import { ActorType } from "../../ee/models"; import { UnauthorizedRequestError } from "../../utils/errors"; @@ -31,7 +33,7 @@ import { getSecretPolicyOfBoard } from "../../ee/services/SecretApprovalService"; import { CommitType } from "../../ee/models/secretApprovalRequest"; -import { IRole } from "../../ee/models/role"; +import { logger } from "../../utils/logging"; const checkSecretsPermission = async ({ authData, @@ -47,36 +49,31 @@ const checkSecretsPermission = async ({ secretAction: ProjectPermissionActions; // CRUD }): Promise<{ authVerifier: (env: string, secPath: string) => boolean; - membership?: Omit & { customRole: IRole }; }> => { let STV2RequiredPermissions = []; - let STV3RequiredPermissions: Permission[] = []; switch (secretAction) { case ProjectPermissionActions.Create: STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS]; - STV3RequiredPermissions = [Permission.WRITE]; break; case ProjectPermissionActions.Read: STV2RequiredPermissions = [PERMISSION_READ_SECRETS]; - STV3RequiredPermissions = [Permission.READ]; break; case ProjectPermissionActions.Edit: STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS]; - STV3RequiredPermissions = [Permission.WRITE]; break; case ProjectPermissionActions.Delete: STV2RequiredPermissions = [PERMISSION_WRITE_SECRETS]; - STV3RequiredPermissions = [Permission.WRITE]; break; } switch (authData.actor.type) { case ActorType.USER: { - const { permission, membership } = await getUserProjectPermissions( - authData.actor.metadata.userId, - workspaceId - ); + const { permission } = await getAuthDataProjectPermissions({ + authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( secretAction, subject(ProjectPermissionSub.Secrets, { environment, secretPath }) @@ -89,8 +86,7 @@ const checkSecretsPermission = async ({ environment: env, secretPath: secPath }) - ), - membership + ) }; } case ActorType.SERVICE: { @@ -104,22 +100,24 @@ const checkSecretsPermission = async ({ return { authVerifier: () => true }; } case ActorType.SERVICE_V3: { - await validateServiceTokenDataV3ClientForWorkspace({ + const { permission } = await getAuthDataProjectPermissions({ authData, - serviceTokenData: authData.authPayload as IServiceTokenDataV3, - workspaceId: new Types.ObjectId(workspaceId), - environment, - secretPath, - requiredPermissions: STV3RequiredPermissions + workspaceId: new Types.ObjectId(workspaceId) }); + + ForbiddenError.from(permission).throwUnlessCan( + secretAction, + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + ); return { authVerifier: (env: string, secPath: string) => - isValidScopeV3({ - authPayload: authData.authPayload as IServiceTokenDataV3, - environment: env, - secretPath: secPath, - requiredPermissions: STV3RequiredPermissions - }) + permission.can( + secretAction, + subject(ProjectPermissionSub.Secrets, { + environment: env, + secretPath: secPath + }) + ) }; } default: { @@ -199,17 +197,22 @@ export const getSecretsRaw = async (req: Request, res: Response) => { query: { include_imports: includeImports } } = validatedData; - // if the service token has single scope, it will get all secrets for that scope by default - const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData; - if ( - serviceTokenDetails && - serviceTokenDetails.scopes.length == 1 && - !containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath) - ) { - const scope = serviceTokenDetails.scopes[0]; - secretPath = scope.secretPath; - environment = scope.environment; - workspaceId = serviceTokenDetails.workspace.toString(); + logger.info(`getSecretsRaw: fetch raw secrets [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [includeImports=${includeImports}]`) + + if (req.authData.authPayload instanceof ServiceTokenData) { + + // if the service token has single scope, it will get all secrets for that scope by default + const serviceTokenDetails: IServiceTokenData = req?.serviceTokenData; + if ( + serviceTokenDetails && + serviceTokenDetails.scopes.length == 1 && + !containsGlobPatterns(serviceTokenDetails.scopes[0].secretPath) + ) { + const scope = serviceTokenDetails.scopes[0]; + secretPath = scope.secretPath; + environment = scope.environment; + workspaceId = serviceTokenDetails.workspace.toString(); + } } if (!environment || !workspaceId) @@ -353,6 +356,8 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => { params: { secretName } } = await validateRequest(reqValidator.GetSecretByNameRawV3, req); + logger.info(`getSecretByNameRaw: fetch raw secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [include_imports=${include_imports}]`) + await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -476,6 +481,8 @@ export const createSecretRaw = async (req: Request, res: Response) => { } } = await validateRequest(reqValidator.CreateSecretRawV3, req); + logger.info(`createSecretRaw: create a secret raw by name and value [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [skipMultilineEncoding=${skipMultilineEncoding}]`) + await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -621,6 +628,8 @@ export const updateSecretByNameRaw = async (req: Request, res: Response) => { body: { workspaceId, environment, secretValue, secretPath, type, skipMultilineEncoding } } = await validateRequest(reqValidator.UpdateSecretByNameRawV3, req); + logger.info(`updateSecretByNameRaw: update raw secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}] [skipMultilineEncoding=${skipMultilineEncoding}]`) + await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -743,6 +752,8 @@ export const deleteSecretByNameRaw = async (req: Request, res: Response) => { body: { environment, secretPath, type, workspaceId } } = await validateRequest(reqValidator.DeleteSecretByNameRawV3, req); + logger.info(`deleteSecretByNameRaw: delete a secret by name [environment=${environment}] [workspaceId=${workspaceId}] [secretPath=${secretPath}] [type=${type}]`) + await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -796,6 +807,8 @@ export const getSecrets = async (req: Request, res: Response) => { query: { secretPath } } = validatedData; + logger.info(`getSecrets: fetch encrypted secrets [environment=${environment}] [workspaceId=${workspaceId}] [includeImports=${includeImports}]`) + const { authVerifier: permissionCheckFn } = await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -850,6 +863,8 @@ export const getSecretByName = async (req: Request, res: Response) => { params: { secretName } } = await validateRequest(reqValidator.GetSecretByNameV3, req); + logger.info(`getSecretByName: get a single secret by name [environment=${environment}] [workspaceId=${workspaceId}] [include_imports=${include_imports}] [type=${type}]`) + await checkSecretsPermission({ authData: req.authData, workspaceId, @@ -900,7 +915,9 @@ export const createSecret = async (req: Request, res: Response) => { params: { secretName } } = await validateRequest(reqValidator.CreateSecretV3, req); - const { membership } = await checkSecretsPermission({ + logger.info(`createSecret: create an encrypted secret [environment=${environment}] [workspaceId=${workspaceId}] [skipMultilineEncoding=${skipMultilineEncoding}] [type=${type}]`) + + await checkSecretsPermission({ authData: req.authData, workspaceId, environment, @@ -908,35 +925,42 @@ export const createSecret = async (req: Request, res: Response) => { secretAction: ProjectPermissionActions.Create }); - if (membership && type !== "personal") { - const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); - if (secretApprovalPolicy) { - const secretApprovalRequest = await generateSecretApprovalRequest({ - workspaceId, - environment, - secretPath, - policy: secretApprovalPolicy, - commiterMembershipId: membership._id.toString(), - authData: req.authData, - data: { - [CommitType.CREATE]: [ - { - secretName, - secretValueCiphertext, - secretValueIV, - secretValueTag, - secretCommentIV, - secretCommentTag, - secretCommentCiphertext, - skipMultilineEncoding, - secretKeyTag, - secretKeyCiphertext, - secretKeyIV - } - ] - } - }); - return res.send({ approval: secretApprovalRequest }); + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (membership && type !== "personal") { + const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); + if (secretApprovalPolicy) { + const secretApprovalRequest = await generateSecretApprovalRequest({ + workspaceId, + environment, + secretPath, + policy: secretApprovalPolicy, + commiterMembershipId: membership._id.toString(), + authData: req.authData, + data: { + [CommitType.CREATE]: [ + { + secretName, + secretValueCiphertext, + secretValueIV, + secretValueTag, + secretCommentIV, + secretCommentTag, + secretCommentCiphertext, + skipMultilineEncoding, + secretKeyTag, + secretKeyCiphertext, + secretKeyIV + } + ] + } + }); + return res.send({ approval: secretApprovalRequest }); + } } } @@ -1005,11 +1029,13 @@ export const updateSecretByName = async (req: Request, res: Response) => { params: { secretName } } = await validateRequest(reqValidator.UpdateSecretByNameV3, req); + logger.info(`updateSecretByName: update a encrypted secret by name [environment=${environment}] [workspaceId=${workspaceId}] [skipMultilineEncoding=${skipMultilineEncoding}] [type=${type}]`) + if (newSecretName && (!secretKeyIV || !secretKeyTag || !secretKeyCiphertext)) { throw BadRequestError({ message: "Missing encrypted key" }); } - const { membership } = await checkSecretsPermission({ + await checkSecretsPermission({ authData: req.authData, workspaceId, environment, @@ -1017,37 +1043,44 @@ export const updateSecretByName = async (req: Request, res: Response) => { secretAction: ProjectPermissionActions.Edit }); - if (membership && type !== "personal") { - const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); - if (secretApprovalPolicy) { - const secretApprovalRequest = await generateSecretApprovalRequest({ - workspaceId, - environment, - secretPath, - policy: secretApprovalPolicy, - commiterMembershipId: membership._id.toString(), - authData: req.authData, - data: { - [CommitType.UPDATE]: [ - { - secretName, - newSecretName, - secretValueCiphertext, - secretValueIV, - secretValueTag, - tags, - secretCommentIV, - secretCommentTag, - secretCommentCiphertext, - skipMultilineEncoding, - secretKeyTag, - secretKeyCiphertext, - secretKeyIV - } - ] - } - }); - return res.send({ approval: secretApprovalRequest }); + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (membership && type !== "personal") { + const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); + if (secretApprovalPolicy) { + const secretApprovalRequest = await generateSecretApprovalRequest({ + workspaceId, + environment, + secretPath, + policy: secretApprovalPolicy, + commiterMembershipId: membership._id.toString(), + authData: req.authData, + data: { + [CommitType.UPDATE]: [ + { + secretName, + newSecretName, + secretValueCiphertext, + secretValueIV, + secretValueTag, + tags, + secretCommentIV, + secretCommentTag, + secretCommentCiphertext, + skipMultilineEncoding, + secretKeyTag, + secretKeyCiphertext, + secretKeyIV + } + ] + } + }); + return res.send({ approval: secretApprovalRequest }); + } } } @@ -1097,7 +1130,9 @@ export const deleteSecretByName = async (req: Request, res: Response) => { params: { secretName } } = await validateRequest(reqValidator.DeleteSecretByNameV3, req); - const { membership } = await checkSecretsPermission({ + logger.info(`deleteSecretByName: delete a encrypted secret by name [environment=${environment}] [workspaceId=${workspaceId}] [type=${type}]`) + + await checkSecretsPermission({ authData: req.authData, workspaceId, environment, @@ -1105,25 +1140,32 @@ export const deleteSecretByName = async (req: Request, res: Response) => { secretAction: ProjectPermissionActions.Delete }); - if (membership && type !== "personal") { - const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); - if (secretApprovalPolicy) { - const secretApprovalRequest = await generateSecretApprovalRequest({ - workspaceId, - environment, - secretPath, - authData: req.authData, - policy: secretApprovalPolicy, - commiterMembershipId: membership._id.toString(), - data: { - [CommitType.DELETE]: [ - { - secretName - } - ] - } - }); - return res.send({ approval: secretApprovalRequest }); + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (membership && type !== "personal") { + const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); + if (secretApprovalPolicy) { + const secretApprovalRequest = await generateSecretApprovalRequest({ + workspaceId, + environment, + secretPath, + authData: req.authData, + policy: secretApprovalPolicy, + commiterMembershipId: membership._id.toString(), + data: { + [CommitType.DELETE]: [ + { + secretName + } + ] + } + }); + return res.send({ approval: secretApprovalRequest }); + } } } @@ -1155,7 +1197,9 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => { body: { secrets, secretPath, environment, workspaceId } } = await validateRequest(reqValidator.CreateSecretByNameBatchV3, req); - const { membership } = await checkSecretsPermission({ + logger.info(`createSecretByNameBatch: create a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`) + + await checkSecretsPermission({ authData: req.authData, workspaceId, environment, @@ -1163,21 +1207,28 @@ export const createSecretByNameBatch = async (req: Request, res: Response) => { secretAction: ProjectPermissionActions.Create }); - if (membership) { - const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); - if (secretApprovalPolicy) { - const secretApprovalRequest = await generateSecretApprovalRequest({ - workspaceId, - environment, - secretPath, - authData: req.authData, - policy: secretApprovalPolicy, - commiterMembershipId: membership._id.toString(), - data: { - [CommitType.CREATE]: secrets.filter(({ type }) => type === "shared") - } - }); - return res.send({ approval: secretApprovalRequest }); + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (membership) { + const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); + if (secretApprovalPolicy) { + const secretApprovalRequest = await generateSecretApprovalRequest({ + workspaceId, + environment, + secretPath, + authData: req.authData, + policy: secretApprovalPolicy, + commiterMembershipId: membership._id.toString(), + data: { + [CommitType.CREATE]: secrets.filter(({ type }) => type === "shared") + } + }); + return res.send({ approval: secretApprovalRequest }); + } } } @@ -1207,7 +1258,9 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => { body: { secrets, secretPath, environment, workspaceId } } = await validateRequest(reqValidator.UpdateSecretByNameBatchV3, req); - const { membership } = await checkSecretsPermission({ + logger.info(`updateSecretByNameBatch: update a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`) + + await checkSecretsPermission({ authData: req.authData, workspaceId, environment, @@ -1215,21 +1268,28 @@ export const updateSecretByNameBatch = async (req: Request, res: Response) => { secretAction: ProjectPermissionActions.Edit }); - if (membership) { - const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); - if (secretApprovalPolicy) { - const secretApprovalRequest = await generateSecretApprovalRequest({ - workspaceId, - environment, - secretPath, - policy: secretApprovalPolicy, - commiterMembershipId: membership._id.toString(), - data: { - [CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared") - }, - authData: req.authData - }); - return res.send({ approval: secretApprovalRequest }); + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (membership) { + const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); + if (secretApprovalPolicy) { + const secretApprovalRequest = await generateSecretApprovalRequest({ + workspaceId, + environment, + secretPath, + policy: secretApprovalPolicy, + commiterMembershipId: membership._id.toString(), + data: { + [CommitType.UPDATE]: secrets.filter(({ type }) => type === "shared") + }, + authData: req.authData + }); + return res.send({ approval: secretApprovalRequest }); + } } } @@ -1259,7 +1319,9 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => { body: { secrets, secretPath, environment, workspaceId } } = await validateRequest(reqValidator.DeleteSecretByNameBatchV3, req); - const { membership } = await checkSecretsPermission({ + logger.info(`deleteSecretByNameBatch: delete a list of secrets by their names [environment=${environment}] [workspaceId=${workspaceId}] [secretsLength=${secrets?.length}]`) + + await checkSecretsPermission({ authData: req.authData, workspaceId, environment, @@ -1267,21 +1329,28 @@ export const deleteSecretByNameBatch = async (req: Request, res: Response) => { secretAction: ProjectPermissionActions.Delete }); - if (membership) { - const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); - if (secretApprovalPolicy) { - const secretApprovalRequest = await generateSecretApprovalRequest({ - workspaceId, - environment, - secretPath, - policy: secretApprovalPolicy, - commiterMembershipId: membership._id.toString(), - data: { - [CommitType.DELETE]: secrets.filter(({ type }) => type === "shared") - }, - authData: req.authData - }); - return res.send({ approval: secretApprovalRequest }); + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (membership) { + const secretApprovalPolicy = await getSecretPolicyOfBoard(workspaceId, environment, secretPath); + if (secretApprovalPolicy) { + const secretApprovalRequest = await generateSecretApprovalRequest({ + workspaceId, + environment, + secretPath, + policy: secretApprovalPolicy, + commiterMembershipId: membership._id.toString(), + data: { + [CommitType.DELETE]: secrets.filter(({ type }) => type === "shared") + }, + authData: req.authData + }); + return res.send({ approval: secretApprovalRequest }); + } } } diff --git a/backend/src/controllers/v3/workspacesController.ts b/backend/src/controllers/v3/workspacesController.ts index f1edca1f7..4842ed3be 100644 --- a/backend/src/controllers/v3/workspacesController.ts +++ b/backend/src/controllers/v3/workspacesController.ts @@ -1,9 +1,9 @@ import { Request, Response } from "express"; import { Types } from "mongoose"; import { validateRequest } from "../../helpers/validation"; -import { Secret, ServiceTokenDataV3 } from "../../models"; +import { Membership, Secret, ServiceTokenDataV3, User } from "../../models"; import { SecretService } from "../../services"; -import { getUserProjectPermissions } from "../../ee/services/ProjectRoleService"; +import { getAuthDataProjectPermissions } from "../../ee/services/ProjectRoleService"; import { UnauthorizedRequestError } from "../../utils/errors"; import * as reqValidator from "../../validation/workspace"; @@ -19,9 +19,22 @@ export const getWorkspaceBlindIndexStatus = async (req: Request, res: Response) params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceBlinkIndexStatusV3, req); - const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); - if (membership.role !== "admin") - throw UnauthorizedRequestError({ message: "User must be an admin" }); + await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (!membership) throw UnauthorizedRequestError(); + + if (membership.role !== "admin") + throw UnauthorizedRequestError({ message: "User must be an admin" }); + } const secretsWithoutBlindIndex = await Secret.countDocuments({ workspace: new Types.ObjectId(workspaceId), @@ -41,9 +54,22 @@ export const getWorkspaceSecrets = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.GetWorkspaceSecretsV3, req); - const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); - if (membership.role !== "admin") - throw UnauthorizedRequestError({ message: "User must be an admin" }); + await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (!membership) throw UnauthorizedRequestError(); + + if (membership.role !== "admin") + throw UnauthorizedRequestError({ message: "User must be an admin" }); + } const secrets = await Secret.find({ workspace: new Types.ObjectId(workspaceId) @@ -65,9 +91,22 @@ export const nameWorkspaceSecrets = async (req: Request, res: Response) => { body: { secretsToUpdate } } = await validateRequest(reqValidator.NameWorkspaceSecretsV3, req); - const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); - if (membership.role !== "admin") - throw UnauthorizedRequestError({ message: "User must be an admin" }); + await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + + if (req.authData.authPayload instanceof User) { + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (!membership) throw UnauthorizedRequestError(); + + if (membership.role !== "admin") + throw UnauthorizedRequestError({ message: "User must be an admin" }); + } // get secret blind index salt const salt = await SecretService.getSecretBlindIndexSalt({ @@ -109,7 +148,7 @@ export const getWorkspaceServiceTokenData = async (req: Request, res: Response) const serviceTokenData = await ServiceTokenDataV3.find({ workspace: new Types.ObjectId(workspaceId) - }); + }).populate("customRole"); return res.status(200).send({ serviceTokenData diff --git a/backend/src/ee/controllers/v1/roleController.ts b/backend/src/ee/controllers/v1/roleController.ts index edc31273a..cef14e79f 100644 --- a/backend/src/ee/controllers/v1/roleController.ts +++ b/backend/src/ee/controllers/v1/roleController.ts @@ -1,4 +1,6 @@ import { Request, Response } from "express"; +import { Types } from "mongoose"; +import { Membership, User } from "../../../models"; import { CreateRoleSchema, DeleteRoleSchema, @@ -11,7 +13,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, adminProjectPermissions, - getUserProjectPermissions, + getAuthDataProjectPermissions, memberProjectPermissions, viewerProjectPermission } from "../../services/ProjectRoleService"; @@ -39,7 +41,10 @@ export const createRole = async (req: Request, res: Response) => { throw BadRequestError({ message: "user doesn't have the permission." }); } } else { - const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); if (permission.cannot(ProjectPermissionActions.Create, ProjectPermissionSub.Role)) { throw BadRequestError({ message: "User doesn't have the permission." }); } @@ -82,7 +87,11 @@ export const updateRole = async (req: Request, res: Response) => { throw BadRequestError({ message: "User doesn't have the org permission." }); } } else { - const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + if (permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Role)) { throw BadRequestError({ message: "User doesn't have the workspace permission." }); } @@ -134,7 +143,11 @@ export const deleteRole = async (req: Request, res: Response) => { throw BadRequestError({ message: "User doesn't have the org permission." }); } } else { - const { permission } = await getUserProjectPermissions(req.user.id, role.workspace.toString()); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: role.workspace + }); + if (permission.cannot(ProjectPermissionActions.Delete, ProjectPermissionSub.Role)) { throw BadRequestError({ message: "User doesn't have the workspace permission." }); } @@ -162,7 +175,11 @@ export const getRoles = async (req: Request, res: Response) => { throw BadRequestError({ message: "User doesn't have the org permission." }); } } else { - const { permission } = await getUserProjectPermissions(req.user.id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + if (permission.cannot(ProjectPermissionActions.Read, ProjectPermissionSub.Role)) { throw BadRequestError({ message: "User doesn't have the workspace permission." }); } @@ -227,7 +244,19 @@ export const getUserWorkspacePermissions = async (req: Request, res: Response) = const { params: { workspaceId } } = await validateRequest(GetUserProjectPermission, req); - const { permission, membership } = await getUserProjectPermissions(req.user._id, workspaceId); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + + let membership; + if (req.authData.authPayload instanceof User) { + membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }) + } res.status(200).json({ data: { diff --git a/backend/src/ee/controllers/v1/secretApprovalPolicyController.ts b/backend/src/ee/controllers/v1/secretApprovalPolicyController.ts index eb2f48718..11f1ed557 100644 --- a/backend/src/ee/controllers/v1/secretApprovalPolicyController.ts +++ b/backend/src/ee/controllers/v1/secretApprovalPolicyController.ts @@ -1,10 +1,11 @@ +import { Types } from "mongoose"; import { ForbiddenError, subject } from "@casl/ability"; import { Request, Response } from "express"; import { nanoid } from "nanoid"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import { validateRequest } from "../../../helpers/validation"; import { SecretApprovalPolicy } from "../../models/secretApprovalPolicy"; @@ -19,7 +20,11 @@ export const createSecretApprovalPolicy = async (req: Request, res: Response) => body: { approvals, secretPath, approvers, environment, workspaceId, name } } = await validateRequest(reqValidator.CreateSecretApprovalRule, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretApproval @@ -49,10 +54,11 @@ export const updateSecretApprovalPolicy = async (req: Request, res: Response) => const secretApproval = await SecretApprovalPolicy.findById(id); if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND; - const { permission } = await getUserProjectPermissions( - req.user._id, - secretApproval.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: secretApproval.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.SecretApproval @@ -78,10 +84,11 @@ export const deleteSecretApprovalPolicy = async (req: Request, res: Response) => const secretApproval = await SecretApprovalPolicy.findById(id); if (!secretApproval) throw ERR_SECRET_APPROVAL_NOT_FOUND; - const { permission } = await getUserProjectPermissions( - req.user._id, - secretApproval.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: secretApproval.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretApproval @@ -99,7 +106,11 @@ export const getSecretApprovalPolicy = async (req: Request, res: Response) => { query: { workspaceId } } = await validateRequest(reqValidator.GetSecretApprovalRuleList, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretApproval @@ -117,7 +128,11 @@ export const getSecretApprovalPolicyOfBoard = async (req: Request, res: Response query: { workspaceId, environment, secretPath } } = await validateRequest(reqValidator.GetSecretApprovalPolicyOfABoard, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, subject(ProjectPermissionSub.Secrets, { secretPath, environment }) diff --git a/backend/src/ee/controllers/v1/secretApprovalRequestsController.ts b/backend/src/ee/controllers/v1/secretApprovalRequestsController.ts index be2e59202..915df5d7c 100644 --- a/backend/src/ee/controllers/v1/secretApprovalRequestsController.ts +++ b/backend/src/ee/controllers/v1/secretApprovalRequestsController.ts @@ -1,7 +1,6 @@ import { Request, Response } from "express"; -import { getUserProjectPermissions } from "../../services/ProjectRoleService"; import { validateRequest } from "../../../helpers/validation"; -import { Folder } from "../../../models"; +import { Folder, Membership, User } from "../../../models"; import { ApprovalStatus, SecretApprovalRequest } from "../../models/secretApprovalRequest"; import * as reqValidator from "../../validation/secretApprovalRequest"; import { getFolderWithPathFromId } from "../../../services/FolderService"; @@ -17,7 +16,15 @@ export const getSecretApprovalRequestCount = async (req: Request, res: Response) query: { workspaceId } } = await validateRequest(reqValidator.getSecretApprovalRequestCount, req); - const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); + if (!(req.authData.authPayload instanceof User)) return; + + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (!membership) throw UnauthorizedRequestError(); + const approvalRequestCount = await SecretApprovalRequest.aggregate([ { $match: { @@ -65,7 +72,14 @@ export const getSecretApprovalRequests = async (req: Request, res: Response) => query: { status, committer, workspaceId, environment, limit, offset } } = await validateRequest(reqValidator.getSecretApprovalRequests, req); - const { membership } = await getUserProjectPermissions(req.user._id, workspaceId); + if (!(req.authData.authPayload instanceof User)) return; + + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: new Types.ObjectId(workspaceId) + }); + + if (!membership) throw UnauthorizedRequestError(); const query = { workspace: new Types.ObjectId(workspaceId), @@ -148,10 +162,15 @@ export const getSecretApprovalRequestDetails = async (req: Request, res: Respons if (!secretApprovalRequest) throw BadRequestError({ message: "Secret approval request not found" }); - const { membership } = await getUserProjectPermissions( - req.user._id, - secretApprovalRequest.workspace.toString() - ); + if (!(req.authData.authPayload instanceof User)) return; + + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: secretApprovalRequest.workspace + }); + + if (!membership) throw UnauthorizedRequestError(); + // allow to fetch only if its admin or is the committer or approver if ( membership.role !== "admin" && @@ -190,10 +209,15 @@ export const updateSecretApprovalReviewStatus = async (req: Request, res: Respon if (!secretApprovalRequest) throw BadRequestError({ message: "Secret approval request not found" }); - const { membership } = await getUserProjectPermissions( - req.user._id, - secretApprovalRequest.workspace.toString() - ); + if (!(req.authData.authPayload instanceof User)) return; + + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: secretApprovalRequest.workspace + }); + + if (!membership) throw UnauthorizedRequestError(); + if ( membership.role !== "admin" && secretApprovalRequest.committer !== membership.id && @@ -227,10 +251,15 @@ export const mergeSecretApprovalRequest = async (req: Request, res: Response) => if (!secretApprovalRequest) throw BadRequestError({ message: "Secret approval request not found" }); - const { membership } = await getUserProjectPermissions( - req.user._id, - secretApprovalRequest.workspace.toString() - ); + if (!(req.authData.authPayload instanceof User)) return; + + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: secretApprovalRequest.workspace + }); + + if (!membership) throw UnauthorizedRequestError(); + if ( membership.role !== "admin" && secretApprovalRequest.committer !== membership.id && @@ -272,10 +301,14 @@ export const updateSecretApprovalRequestStatus = async (req: Request, res: Respo if (!secretApprovalRequest) throw BadRequestError({ message: "Secret approval request not found" }); - const { membership } = await getUserProjectPermissions( - req.user._id, - secretApprovalRequest.workspace.toString() - ); + if (!(req.authData.authPayload instanceof User)) return; + + const membership = await Membership.findOne({ + user: req.authData.authPayload._id, + workspace: secretApprovalRequest.workspace + }); + + if (!membership) throw UnauthorizedRequestError(); if ( membership.role !== "admin" && diff --git a/backend/src/ee/controllers/v1/secretController.ts b/backend/src/ee/controllers/v1/secretController.ts index 42aa07f3d..ec6018929 100644 --- a/backend/src/ee/controllers/v1/secretController.ts +++ b/backend/src/ee/controllers/v1/secretController.ts @@ -5,7 +5,7 @@ import { Folder, Secret } from "../../../models"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import { BadRequestError } from "../../../utils/errors"; import * as reqValidator from "../../../validation"; @@ -74,7 +74,11 @@ export const getSecretVersions = async (req: Request, res: Response) => { throw BadRequestError({ message: "Failed to find secret" }); } - const { permission } = await getUserProjectPermissions(req.user._id, secret.workspace.toString()); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: secret.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback @@ -157,10 +161,12 @@ export const rollbackSecretVersion = async (req: Request, res: Response) => { if (!toBeUpdatedSec) { throw BadRequestError({ message: "Failed to find secret" }); } - const { permission } = await getUserProjectPermissions( - req.user._id, - toBeUpdatedSec.workspace.toString() - ); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: toBeUpdatedSec.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback diff --git a/backend/src/ee/controllers/v1/secretRotationController.ts b/backend/src/ee/controllers/v1/secretRotationController.ts index a8a2fd2dd..f2f80ffe6 100644 --- a/backend/src/ee/controllers/v1/secretRotationController.ts +++ b/backend/src/ee/controllers/v1/secretRotationController.ts @@ -1,11 +1,12 @@ import { Request, Response } from "express"; +import { Types } from "mongoose"; import { validateRequest } from "../../../helpers/validation"; import * as reqValidator from "../../validation/secretRotation"; import * as secretRotationService from "../../secretRotation/service"; import { - getUserProjectPermissions, ProjectPermissionActions, - ProjectPermissionSub + ProjectPermissionSub, + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; @@ -23,7 +24,11 @@ export const createSecretRotation = async (req: Request, res: Response) => { } } = await validateRequest(reqValidator.createSecretRotationV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation @@ -49,7 +54,12 @@ export const restartSecretRotations = async (req: Request, res: Response) => { } = await validateRequest(reqValidator.restartSecretRotationV1, req); const doc = await secretRotationService.getSecretRotationById({ id }); - const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString()); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: doc.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.SecretRotation @@ -65,7 +75,12 @@ export const deleteSecretRotations = async (req: Request, res: Response) => { } = await validateRequest(reqValidator.removeSecretRotationV1, req); const doc = await secretRotationService.getSecretRotationById({ id }); - const { permission } = await getUserProjectPermissions(req.user._id, doc.workspace.toString()); + + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: doc.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.SecretRotation @@ -80,7 +95,11 @@ export const getSecretRotations = async (req: Request, res: Response) => { query: { workspaceId } } = await validateRequest(reqValidator.getSecretRotationV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation diff --git a/backend/src/ee/controllers/v1/secretRotationProviderController.ts b/backend/src/ee/controllers/v1/secretRotationProviderController.ts index 7d62ed5f5..5e66c40b2 100644 --- a/backend/src/ee/controllers/v1/secretRotationProviderController.ts +++ b/backend/src/ee/controllers/v1/secretRotationProviderController.ts @@ -1,11 +1,12 @@ import { Request, Response } from "express"; +import { Types } from "mongoose"; import { validateRequest } from "../../../helpers/validation"; import * as reqValidator from "../../validation/secretRotationProvider"; import * as secretRotationProviderService from "../../secretRotation/service"; import { - getUserProjectPermissions, ProjectPermissionActions, - ProjectPermissionSub + ProjectPermissionSub, + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; @@ -14,7 +15,11 @@ export const getProviderTemplates = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(reqValidator.getSecretRotationProvidersV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretRotation diff --git a/backend/src/ee/controllers/v1/secretSnapshotController.ts b/backend/src/ee/controllers/v1/secretSnapshotController.ts index e39555929..34a1a6ef2 100644 --- a/backend/src/ee/controllers/v1/secretSnapshotController.ts +++ b/backend/src/ee/controllers/v1/secretSnapshotController.ts @@ -4,7 +4,7 @@ import { validateRequest } from "../../../helpers/validation"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import * as reqValidator from "../../../validation/secretSnapshot"; import { ISecretVersion, SecretSnapshot, TFolderRootVersionSchema } from "../../models"; @@ -33,10 +33,11 @@ export const getSecretSnapshot = async (req: Request, res: Response) => { if (!secretSnapshot) throw new Error("Failed to find secret snapshot"); - const { permission } = await getUserProjectPermissions( - req.user._id, - secretSnapshot.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: secretSnapshot.workspace + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback diff --git a/backend/src/ee/controllers/v1/workspaceController.ts b/backend/src/ee/controllers/v1/workspaceController.ts index b26882d74..7d5caae9b 100644 --- a/backend/src/ee/controllers/v1/workspaceController.ts +++ b/backend/src/ee/controllers/v1/workspaceController.ts @@ -27,7 +27,6 @@ import { } from "../../models"; import { EESecretService } from "../../services"; import { getLatestSecretVersionIds } from "../../helpers/secretVersion"; -// import Folder, { TFolderSchema } from "../../../models/folder"; import { getFolderByPath, searchByFolderId } from "../../../services/FolderService"; import { EEAuditLogService, EELicenseService } from "../../services"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; @@ -46,7 +45,7 @@ import { import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { BadRequestError } from "../../../utils/errors"; @@ -107,7 +106,11 @@ export const getWorkspaceSecretSnapshots = async (req: Request, res: Response) = query: { environment, directory, offset, limit } } = await validateRequest(GetWorkspaceSecretSnapshotsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback @@ -148,7 +151,11 @@ export const getWorkspaceSecretSnapshotsCount = async (req: Request, res: Respon query: { environment, directory } } = await validateRequest(GetWorkspaceSecretSnapshotsCountV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.SecretRollback @@ -238,7 +245,11 @@ export const rollbackWorkspaceSecretSnapshot = async (req: Request, res: Respons body: { directory, environment, version } } = await validateRequest(RollbackWorkspaceSecretSnapshotV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.SecretRollback @@ -572,7 +583,11 @@ export const getWorkspaceAuditLogs = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(GetWorkspaceAuditLogsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs @@ -632,7 +647,11 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res params: { workspaceId } } = await validateRequest(GetWorkspaceAuditLogActorFilterOptsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs @@ -700,7 +719,11 @@ export const getWorkspaceTrustedIps = async (req: Request, res: Response) => { params: { workspaceId } } = await validateRequest(GetWorkspaceTrustedIpsV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.IpAllowList @@ -726,7 +749,11 @@ export const addWorkspaceTrustedIp = async (req: Request, res: Response) => { body: { comment, isActive, ipAddress: ip } } = await validateRequest(AddWorkspaceTrustedIpV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.IpAllowList @@ -792,7 +819,11 @@ export const updateWorkspaceTrustedIp = async (req: Request, res: Response) => { body: { ipAddress: ip, comment } } = await validateRequest(UpdateWorkspaceTrustedIpV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, ProjectPermissionSub.IpAllowList @@ -884,7 +915,11 @@ export const deleteWorkspaceTrustedIp = async (req: Request, res: Response) => { params: { workspaceId, trustedIpId } } = await validateRequest(DeleteWorkspaceTrustedIpV1, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, ProjectPermissionSub.IpAllowList diff --git a/backend/src/ee/controllers/v3/serviceTokenDataController.ts b/backend/src/ee/controllers/v3/serviceTokenDataController.ts index 4590f3ab8..3e97c9afe 100644 --- a/backend/src/ee/controllers/v3/serviceTokenDataController.ts +++ b/backend/src/ee/controllers/v3/serviceTokenDataController.ts @@ -8,13 +8,11 @@ import { ServiceTokenDataV3Key, Workspace } from "../../../models"; -import { - IServiceTokenV3Scope, - IServiceTokenV3TrustedIp -} from "../../../models/serviceTokenDataV3"; +import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3"; import { ActorType, - EventType + EventType, + Role } from "../../models"; import { validateRequest } from "../../../helpers/validation"; import * as reqValidator from "../../../validation/serviceTokenDataV3"; @@ -22,14 +20,14 @@ import { createToken } from "../../../helpers/auth"; import { ProjectPermissionActions, ProjectPermissionSub, - getUserProjectPermissions + getAuthDataProjectPermissions } from "../../services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; import { BadRequestError, ResourceNotFoundError, UnauthorizedRequestError } from "../../../utils/errors"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; import { EEAuditLogService, EELicenseService } from "../../services"; import { getAuthSecret } from "../../../config"; -import { AuthTokenType } from "../../../variables"; +import { ADMIN, AuthTokenType, CUSTOM, MEMBER, VIEWER } from "../../../variables"; /** * Return project key for service token V3 @@ -163,7 +161,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => { name, workspaceId, publicKey, - scopes, + role, trustedIps, expiresIn, accessTokenTTL, @@ -172,7 +170,11 @@ export const createServiceTokenData = async (req: Request, res: Response) => { nonce, // for ServiceTokenDataV3Key } } = await validateRequest(reqValidator.CreateServiceTokenV3, req); - const { permission } = await getUserProjectPermissions(req.user._id, workspaceId); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: new Types.ObjectId(workspaceId) + }); + ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Create, ProjectPermissionSub.ServiceTokens @@ -181,6 +183,19 @@ export const createServiceTokenData = async (req: Request, res: Response) => { const workspace = await Workspace.findById(workspaceId); if (!workspace) throw BadRequestError({ message: "Workspace not found" }); + const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role); + + let customRole; + if (isCustomRole) { + customRole = await Role.findOne({ + slug: role, + isOrgRole: false, + workspace: workspace._id + }); + + if (!customRole) throw BadRequestError({ message: "Role not found" }); + } + const plan = await EELicenseService.getPlan(workspace.organization); // validate trusted ips @@ -219,7 +234,8 @@ export const createServiceTokenData = async (req: Request, res: Response) => { accessTokenUsageCount: 0, tokenVersion: 1, trustedIps: reformattedTrustedIps, - scopes, + role: isCustomRole ? CUSTOM : role, + customRole, isActive, expiresAt, accessTokenTTL, @@ -250,7 +266,7 @@ export const createServiceTokenData = async (req: Request, res: Response) => { metadata: { name, isActive, - scopes: scopes as Array, + role, trustedIps: reformattedTrustedIps as Array, expiresAt } @@ -278,7 +294,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { body: { name, isActive, - scopes, + role, trustedIps, expiresIn, accessTokenTTL, @@ -291,10 +307,10 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { message: "Service token not found" }); - const { permission } = await getUserProjectPermissions( - req.user._id, - serviceTokenData.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: serviceTokenData.workspace + }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Edit, @@ -304,6 +320,20 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { const workspace = await Workspace.findById(serviceTokenData.workspace); if (!workspace) throw BadRequestError({ message: "Workspace not found" }); + let customRole; + if (role) { + const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role); + if (isCustomRole) { + customRole = await Role.findOne({ + slug: role, + isOrgRole: false, + workspace: workspace._id + }); + + if (!customRole) throw BadRequestError({ message: "Role not found" }); + } + } + const plan = await EELicenseService.getPlan(workspace.organization); // validate trusted ips @@ -335,7 +365,15 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { { name, isActive, - scopes, + role: customRole ? CUSTOM : role, + ...(customRole ? { + customRole + } : {}), + ...(role && !customRole ? { // non-custom role + $unset: { + customRole: 1 + } + } : {}), trustedIps: reformattedTrustedIps, expiresAt, accessTokenTTL, @@ -357,7 +395,7 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { metadata: { name: serviceTokenData.name, isActive, - scopes: scopes as Array, + role, trustedIps: reformattedTrustedIps as Array, expiresAt } @@ -388,10 +426,10 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => { message: "Service token not found" }); - const { permission } = await getUserProjectPermissions( - req.user._id, - serviceTokenData.workspace.toString() - ); + const { permission } = await getAuthDataProjectPermissions({ + authData: req.authData, + workspaceId: serviceTokenData.workspace + }); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Delete, @@ -415,7 +453,7 @@ export const deleteServiceTokenData = async (req: Request, res: Response) => { metadata: { name: serviceTokenData.name, isActive: serviceTokenData.isActive, - scopes: serviceTokenData.scopes as Array, + role: serviceTokenData.role, trustedIps: serviceTokenData.trustedIps as Array, expiresAt: serviceTokenData.expiresAt } diff --git a/backend/src/ee/models/auditLog/enums.ts b/backend/src/ee/models/auditLog/enums.ts index 6f9971ef7..48147db08 100644 --- a/backend/src/ee/models/auditLog/enums.ts +++ b/backend/src/ee/models/auditLog/enums.ts @@ -2,7 +2,7 @@ export enum ActorType { USER = "user", SERVICE = "service", SERVICE_V3 = "service-v3", - Machine = "machine" + // Machine = "machine" } export enum UserAgentType { diff --git a/backend/src/ee/models/auditLog/types.ts b/backend/src/ee/models/auditLog/types.ts index 51f94fdb0..bae4e2f96 100644 --- a/backend/src/ee/models/auditLog/types.ts +++ b/backend/src/ee/models/auditLog/types.ts @@ -1,5 +1,5 @@ import { ActorType, EventType } from "./enums"; -import { IServiceTokenV3Scope, IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3"; +import { IServiceTokenV3TrustedIp } from "../../../models/serviceTokenDataV3"; interface UserActorMetadata { userId: string; @@ -26,11 +26,11 @@ export interface ServiceActorV3 { metadata: ServiceActorMetadata; } -export interface MachineActor { - type: ActorType.Machine; -} +// export interface MachineActor { +// type: ActorType.Machine; +// } -export type Actor = UserActor | ServiceActor | ServiceActorV3 | MachineActor; +export type Actor = UserActor | ServiceActor | ServiceActorV3; interface GetSecretsEvent { type: EventType.GET_SECRETS; @@ -225,7 +225,7 @@ interface CreateServiceTokenV3Event { metadata: { name: string; isActive: boolean; - scopes: Array; + role: string; trustedIps: Array; expiresAt?: Date; }; @@ -236,7 +236,7 @@ interface UpdateServiceTokenV3Event { metadata: { name?: string; isActive?: boolean; - scopes?: Array; + role?: string; trustedIps?: Array; expiresAt?: Date; }; @@ -247,7 +247,7 @@ interface DeleteServiceTokenV3Event { metadata: { name: string; isActive: boolean; - scopes: Array; + role: string; expiresAt?: Date; trustedIps: Array; }; diff --git a/backend/src/ee/services/ProjectRoleService.ts b/backend/src/ee/services/ProjectRoleService.ts index ac71121de..4e882d00f 100644 --- a/backend/src/ee/services/ProjectRoleService.ts +++ b/backend/src/ee/services/ProjectRoleService.ts @@ -1,3 +1,4 @@ +import { Types } from "mongoose"; import { AbilityBuilder, ForcedSubject, @@ -6,11 +7,14 @@ import { buildMongoQueryMatcher, createMongoAbility } from "@casl/ability"; -import { Membership } from "../../models"; -import { IRole } from "../models/role"; -import { BadRequestError, UnauthorizedRequestError } from "../../utils/errors"; +import { UnauthorizedRequestError } from "../../utils/errors"; import { FieldCondition, FieldInstruction, JsInterpreter } from "@ucast/mongo2js"; import picomatch from "picomatch"; +import { AuthData } from "../../interfaces/middleware"; +import { ActorType, IRole } from "../models"; +import { Membership, ServiceTokenData, ServiceTokenDataV3 } from "../../models"; +import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables"; +import { checkIPAgainstBlocklist } from "../../utils/ip"; const $glob: FieldInstruction = { type: "field", @@ -239,31 +243,89 @@ const buildViewerPermission = () => { export const viewerProjectPermission = buildViewerPermission(); -export const getUserProjectPermissions = async (userId: string, workspaceId: string) => { - // TODO(akhilmhdh): speed this up by pulling from cache later - const membership = await Membership.findOne({ - user: userId, - workspace: workspaceId - }) - .populate<{ - customRole: IRole & { permissions: RawRuleOf>[] }; - }>("customRole") - .exec(); +/** + * Return permissions for user/service pertaining to workspace with id [workspaceId] + * + * Note: should not rely on this function for ST V2 authorization logic + * b/c ST V2 does not support role-based access control + */ +export const getAuthDataProjectPermissions = async ({ + authData, + workspaceId +}: { + authData: AuthData; + workspaceId: Types.ObjectId; +}) => { + let role: "admin" | "member" | "viewer" | "custom"; + let customRole; + + switch (authData.actor.type) { + case ActorType.USER: { + const membership = await Membership.findOne({ + user: authData.authPayload._id, + workspace: workspaceId + }) + .populate<{ + customRole: IRole & { permissions: RawRuleOf>[] }; + }>("customRole") + .exec(); + + if (!membership || (membership.role === "custom" && !membership.customRole)) { + throw UnauthorizedRequestError(); + } + + role = membership.role; + customRole = membership.customRole; + break; + } + case ActorType.SERVICE: { + const serviceTokenData = await ServiceTokenData.findById(authData.authPayload._id); + if (!serviceTokenData || !serviceTokenData.workspace.equals(workspaceId)) throw UnauthorizedRequestError(); + role = "viewer"; + break; + } + case ActorType.SERVICE_V3: { + const serviceTokenData = await ServiceTokenDataV3 + .findById(authData.authPayload._id) + .populate<{ + customRole: IRole & { permissions: RawRuleOf>[] }; + }>("customRole") + .exec(); + + if (!serviceTokenData || (serviceTokenData.role === "custom" && !serviceTokenData.customRole)) { + throw UnauthorizedRequestError(); + } - if (!membership || (membership.role === "custom" && !membership.customRole)) { - throw UnauthorizedRequestError({ message: "User doesn't belong to organization" }); + checkIPAgainstBlocklist({ + ipAddress: authData.ipAddress, + trustedIps: serviceTokenData.trustedIps + }); + + role = serviceTokenData.role; + customRole = serviceTokenData.customRole; + break; + } + default: + throw UnauthorizedRequestError(); } - if (membership.role === "admin") return { permission: adminProjectPermissions, membership }; - if (membership.role === "member") return { permission: memberProjectPermissions, membership }; - if (membership.role === "viewer") return { permission: viewerProjectPermission, membership }; - - if (membership.role === "custom") { - const permission = createMongoAbility(membership.customRole.permissions, { - conditionsMatcher - }); - return { permission, membership }; + switch (role) { + case ADMIN: + return { permission: adminProjectPermissions }; + case MEMBER: + return { permission: memberProjectPermissions }; + case VIEWER: + return { permission: viewerProjectPermission }; + case CUSTOM: { + if (!customRole) throw UnauthorizedRequestError(); + return { + permission: createMongoAbility( + customRole.permissions, + { conditionsMatcher } + ) + }; + } + default: + throw UnauthorizedRequestError(); } - - throw BadRequestError({ message: "User role not found" }); -}; +} diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 6bd2db1cd..a3eb541bf 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -13,13 +13,11 @@ import { Folder, ISecret, IServiceTokenData, - IServiceTokenDataV3, Secret, SecretBlindIndexData, ServiceTokenData, TFolderRootSchema } from "../models"; -import { Permission } from "../models/serviceTokenDataV3"; import { EventType, SecretVersion } from "../ee/models"; import { BadRequestError, @@ -51,42 +49,6 @@ import picomatch from "picomatch"; import path from "path"; import { getAnImportedSecret } from "../services/SecretImportService"; -/** - * Validate scope for service token v3 - * @param authPayload - * @param environment - * @param secretPath - * @returns - */ -export const isValidScopeV3 = ({ - authPayload, - environment, - secretPath, - requiredPermissions -}: { - authPayload: IServiceTokenDataV3; - environment: string; - secretPath: string; - requiredPermissions: Permission[]; -}) => { - const { scopes } = authPayload; - - const validScope = scopes.find( - (scope) => - picomatch.isMatch(secretPath, scope.secretPath, { strictSlashes: false }) && - scope.environment === environment - ); - - if ( - validScope && - !requiredPermissions.every((permission) => validScope.permissions.includes(permission)) - ) { - return false; - } - - return Boolean(validScope); -}; - /** * Validate scope for service token v2 * @param authPayload diff --git a/backend/src/index.ts b/backend/src/index.ts index df3abb0e3..d1379c04f 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -5,7 +5,7 @@ import express from "express"; require("express-async-errors"); import helmet from "helmet"; import cors from "cors"; -import { logger } from "./utils/logging"; +import { initLogger, logger } from "./utils/logging"; import httpLogger from "pino-http"; import { DatabaseService } from "./services"; import { EELicenseService, GithubSecretScanningService } from "./ee/services"; @@ -35,6 +35,7 @@ import { import { apiKeyData as v3apiKeyDataRouter } from "./ee/routes/v3"; import { serviceTokenData as v3ServiceTokenDataRouter } from "./ee/routes/v3"; import { + admin as v1AdminRouter, auth as v1AuthRouter, bot as v1BotRouter, integrationAuth as v1IntegrationAuthRouter, @@ -81,6 +82,7 @@ import { healthCheck } from "./routes/status"; import { RouteNotFoundError } from "./utils/errors"; import { requestErrorHandler } from "./middleware/requestErrorHandler"; import { + getMongoURL, getNodeEnv, getPort, getSecretScanningGitAppId, @@ -94,12 +96,20 @@ import { syncSecretsToThirdPartyServices } from "./queues/integrations/syncSecre import { githubPushEventSecretScan } from "./queues/secret-scanning/githubScanPushEvent"; const SmeeClient = require("smee-client"); // eslint-disable-line import path from "path"; +import { serverConfigInit } from "./config/serverConfig"; +import { initRedis } from "./services/RedisService"; let handler: null | any = null; const main = async () => { + await initLogger(); + const port = await getPort(); + // initializing the database connection + redis + await initRedis() + await DatabaseService.initDatabase(await getMongoURL()); + const serverCfg = await serverConfigInit(); await setup(); await EELicenseService.initGlobalFeatureSet(); @@ -203,6 +213,7 @@ const main = async () => { // v1 routes app.use("/api/v1/signup", v1SignupRouter); app.use("/api/v1/auth", v1AuthRouter); + app.use("/api/v1/admin", v1AdminRouter); app.use("/api/v1/bot", v1BotRouter); app.use("/api/v1/user", v1UserRouter); app.use("/api/v1/user-action", v1UserActionRouter); @@ -271,7 +282,22 @@ const main = async () => { app.use(requestErrorHandler); const server = app.listen(port, async () => { - logger.info(`Server started listening at port ${port}`); + if (!serverCfg.initialized) { + logger.info(`Welcome to Infisical + +Create your Infisical administrator account at: +http://localhost:${port}/admin/signup +`); + } else { + logger.info(`Welcome back! + +To access Infisical Administrator Panel open +http://localhost:${port}/admin + +To access Infisical server +http://localhost:${port} +`); + } }); // await createTestUserForDevelopment(); diff --git a/backend/src/middleware/index.ts b/backend/src/middleware/index.ts index ded40da81..347519b6e 100644 --- a/backend/src/middleware/index.ts +++ b/backend/src/middleware/index.ts @@ -7,17 +7,21 @@ import requireSecretAuth from "./requireSecretAuth"; import requireSecretsAuth from "./requireSecretsAuth"; import requireBlindIndicesEnabled from "./requireBlindIndicesEnabled"; import requireE2EEOff from "./requireE2EEOff"; +import { requireSuperAdminAccess } from "./requireSuperAdminAccess"; import validateRequest from "./validateRequest"; +import { disableSignUpByServerCfg } from "./serverAdmin"; export { - requireAuth, - requireMfaAuth, - requireSignupAuth, - requireWorkspaceAuth, - requireServiceTokenAuth, - requireSecretAuth, - requireSecretsAuth, - requireBlindIndicesEnabled, - requireE2EEOff, - validateRequest, + requireAuth, + requireMfaAuth, + requireSignupAuth, + requireWorkspaceAuth, + requireServiceTokenAuth, + requireSecretAuth, + requireSecretsAuth, + requireBlindIndicesEnabled, + requireE2EEOff, + validateRequest, + requireSuperAdminAccess, + disableSignUpByServerCfg }; diff --git a/backend/src/middleware/requestErrorHandler.ts b/backend/src/middleware/requestErrorHandler.ts index 3bcea0d98..725fc4955 100644 --- a/backend/src/middleware/requestErrorHandler.ts +++ b/backend/src/middleware/requestErrorHandler.ts @@ -15,7 +15,7 @@ export const requestErrorHandler: ErrorRequestHandler = async ( if (res.headersSent) return next(); let error: RequestError; - + switch (true) { case err instanceof TokenExpiredError: error = UnauthorizedRequestError({ stack: err.stack, message: "Token expired" }); @@ -31,7 +31,7 @@ export const requestErrorHandler: ErrorRequestHandler = async ( break; } - logger[mapToPinoLogLevel(error.level)](error); + logger[mapToPinoLogLevel(error.level)]({ msg: error }); if (req.user) { Sentry.setUser({ email: (req.user as any).email }); diff --git a/backend/src/middleware/requireSuperAdminAccess.ts b/backend/src/middleware/requireSuperAdminAccess.ts new file mode 100644 index 000000000..4445433ff --- /dev/null +++ b/backend/src/middleware/requireSuperAdminAccess.ts @@ -0,0 +1,8 @@ +import { NextFunction, Request, Response } from "express"; +import { UnauthorizedRequestError } from "../utils/errors"; + +export const requireSuperAdminAccess = (req: Request, _res: Response, next: NextFunction) => { + const isSuperAdmin = req.user.superAdmin; + if (!isSuperAdmin) throw UnauthorizedRequestError({ message: "Requires superadmin access" }); + return next(); +}; diff --git a/backend/src/middleware/serverAdmin.ts b/backend/src/middleware/serverAdmin.ts new file mode 100644 index 000000000..d57dbf714 --- /dev/null +++ b/backend/src/middleware/serverAdmin.ts @@ -0,0 +1,9 @@ +import { NextFunction, Request, Response } from "express"; +import { getServerConfig } from "../config/serverConfig"; +import { BadRequestError } from "../utils/errors"; + +export const disableSignUpByServerCfg = (_req: Request, _res: Response, next: NextFunction) => { + const cfg = getServerConfig(); + if (!cfg.allowSignUp) throw BadRequestError({ message: "Signup are disabled" }); + return next(); +}; diff --git a/backend/src/models/serverConfig.ts b/backend/src/models/serverConfig.ts new file mode 100644 index 000000000..13e469bd5 --- /dev/null +++ b/backend/src/models/serverConfig.ts @@ -0,0 +1,25 @@ +import { Schema, Types, model } from "mongoose"; + +export interface IServerConfig { + _id: Types.ObjectId; + initialized: boolean; + allowSignUp: boolean; +} + +const serverConfigSchema = new Schema( + { + initialized: { + type: Boolean, + default: false + }, + allowSignUp: { + type: Boolean, + default: true + } + }, + { + timestamps: true + } +); + +export const ServerConfig = model("ServerConfig", serverConfigSchema); diff --git a/backend/src/models/serviceTokenDataV3.ts b/backend/src/models/serviceTokenDataV3.ts index 198b60507..fb39613d6 100644 --- a/backend/src/models/serviceTokenDataV3.ts +++ b/backend/src/models/serviceTokenDataV3.ts @@ -1,16 +1,6 @@ import { Document, Schema, Types, model } from "mongoose"; import { IPType } from "../ee/models"; - -export enum Permission { - READ = "read", - WRITE = "write" -} - -export interface IServiceTokenV3Scope { - environment: string; - secretPath: string; - permissions: Permission[]; -} +import { ADMIN, CUSTOM, MEMBER, VIEWER } from "../variables"; export interface IServiceTokenV3TrustedIp { ipAddress: string; @@ -33,7 +23,8 @@ export interface IServiceTokenDataV3 extends Document { isRefreshTokenRotationEnabled: boolean; expiresAt?: Date; accessTokenTTL: number; - scopes: Array; + role: "admin" | "member" | "viewer" | "custom"; + customRole: Types.ObjectId; trustedIps: Array; } @@ -100,28 +91,15 @@ const serviceTokenDataV3Schema = new Schema( default: 7200, required: true }, - scopes: { - type: [ - { - environment: { - type: String, - required: true - }, - secretPath: { - type: String, - default: "/", - required: true - }, - permissions: { - type: [String], - enum: [Permission.READ, Permission.WRITE], - default: [Permission.READ], - required: true - } - } - ], + role: { + type: String, + enum: [ADMIN, MEMBER, VIEWER, CUSTOM], required: true }, + customRole: { + type: Schema.Types.ObjectId, + ref: "Role" + }, trustedIps: { type: [ { diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index 911776a63..a3d73b8a2 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -1,125 +1,141 @@ import { Document, Schema, Types, model } from "mongoose"; export enum AuthMethod { - EMAIL = "email", - GOOGLE = "google", - GITHUB = "github", - GITLAB = "gitlab", - OKTA_SAML = "okta-saml", - AZURE_SAML = "azure-saml", - JUMPCLOUD_SAML = "jumpcloud-saml", + EMAIL = "email", + GOOGLE = "google", + GITHUB = "github", + GITLAB = "gitlab", + OKTA_SAML = "okta-saml", + AZURE_SAML = "azure-saml", + JUMPCLOUD_SAML = "jumpcloud-saml" } export interface IUser extends Document { - _id: Types.ObjectId; - authProvider?: AuthMethod; - authMethods: AuthMethod[]; - email: string; - firstName?: string; - lastName?: string; - encryptionVersion: number; - protectedKey: string; - protectedKeyIV: string; - protectedKeyTag: string; - publicKey?: string; - encryptedPrivateKey?: string; - iv?: string; - tag?: string; - salt?: string; - verifier?: string; - isMfaEnabled: boolean; - mfaMethods: boolean; - devices: { - ip: string; - userAgent: string; - }[]; + _id: Types.ObjectId; + authProvider?: AuthMethod; + authMethods: AuthMethod[]; + email: string; + superAdmin?: boolean; + firstName?: string; + lastName?: string; + encryptionVersion: number; + protectedKey: string; + protectedKeyIV: string; + protectedKeyTag: string; + publicKey?: string; + encryptedPrivateKey?: string; + iv?: string; + tag?: string; + salt?: string; + verifier?: string; + isMfaEnabled: boolean; + mfaMethods: boolean; + devices: { + ip: string; + userAgent: string; + }[]; } const userSchema = new Schema( - { - authProvider: { // TODO field: deprecate - type: String, - enum: AuthMethod, - }, - authMethods: { - type: [{ - type: String, - enum: AuthMethod, - }], - default: [AuthMethod.EMAIL], - required: true - }, - email: { - type: String, - required: true, - unique: true, - }, - firstName: { - type: String, - }, - lastName: { - type: String, - }, - encryptionVersion: { - type: Number, - select: false, - default: 1, // to resolve backward-compatibility issues - }, - protectedKey: { // introduced as part of encryption version 2 - type: String, - select: false, - }, - protectedKeyIV: { // introduced as part of encryption version 2 - type: String, - select: false, - }, - protectedKeyTag: { // introduced as part of encryption version 2 - type: String, - select: false, - }, - publicKey: { - type: String, - select: false, - }, - encryptedPrivateKey: { - type: String, - select: false, - }, - iv: { // iv of [encryptedPrivateKey] - type: String, - select: false, - }, - tag: { // tag of [encryptedPrivateKey] - type: String, - select: false, - }, - salt: { - type: String, - select: false, - }, - verifier: { - type: String, - select: false, - }, - isMfaEnabled: { - type: Boolean, - default: false, - }, - mfaMethods: [{ - type: String, - }], - devices: { - type: [{ - ip: String, - userAgent: String, - }], - default: [], - select: false, - }, - }, - { - timestamps: true, - } + { + authProvider: { + // TODO field: deprecate + type: String, + enum: AuthMethod + }, + authMethods: { + type: [ + { + type: String, + enum: AuthMethod + } + ], + default: [AuthMethod.EMAIL], + required: true + }, + email: { + type: String, + required: true, + unique: true + }, + firstName: { + type: String + }, + lastName: { + type: String + }, + encryptionVersion: { + type: Number, + select: false, + default: 1 // to resolve backward-compatibility issues + }, + protectedKey: { + // introduced as part of encryption version 2 + type: String, + select: false + }, + protectedKeyIV: { + // introduced as part of encryption version 2 + type: String, + select: false + }, + protectedKeyTag: { + // introduced as part of encryption version 2 + type: String, + select: false + }, + publicKey: { + type: String, + select: false + }, + encryptedPrivateKey: { + type: String, + select: false + }, + superAdmin: { + type: Boolean + }, + iv: { + // iv of [encryptedPrivateKey] + type: String, + select: false + }, + tag: { + // tag of [encryptedPrivateKey] + type: String, + select: false + }, + salt: { + type: String, + select: false + }, + verifier: { + type: String, + select: false + }, + isMfaEnabled: { + type: Boolean, + default: false + }, + mfaMethods: [ + { + type: String + } + ], + devices: { + type: [ + { + ip: String, + userAgent: String + } + ], + default: [], + select: false + } + }, + { + timestamps: true + } ); -export const User = model("User", userSchema); \ No newline at end of file +export const User = model("User", userSchema); diff --git a/backend/src/routes/v1/admin.ts b/backend/src/routes/v1/admin.ts new file mode 100644 index 000000000..5e7989ce0 --- /dev/null +++ b/backend/src/routes/v1/admin.ts @@ -0,0 +1,20 @@ +import express from "express"; +import { adminController } from "../../controllers/v1"; +const router = express.Router(); +import { requireAuth, requireSuperAdminAccess } from "../../middleware"; +import { AuthMode } from "../../variables"; + +router.get("/config", adminController.getServerConfigInfo); + +router.post("/signup", adminController.adminSignUp); + +router.patch( + "/config", + requireAuth({ + acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY] + }), + requireSuperAdminAccess, + adminController.updateServerConfig +); + +export default router; diff --git a/backend/src/routes/v1/index.ts b/backend/src/routes/v1/index.ts index 2d7bf27ef..c19ead569 100644 --- a/backend/src/routes/v1/index.ts +++ b/backend/src/routes/v1/index.ts @@ -18,6 +18,7 @@ import integrationAuth from "./integrationAuth"; import secretsFolder from "./secretsFolder"; import webhooks from "./webhook"; import secretImps from "./secretImps"; +import admin from "./admin"; export { signup, @@ -39,5 +40,6 @@ export { secretsFolder, webhooks, secretImps, - sso + sso, + admin }; diff --git a/backend/src/routes/v1/signup.ts b/backend/src/routes/v1/signup.ts index f3b5d3adf..e7b92f643 100644 --- a/backend/src/routes/v1/signup.ts +++ b/backend/src/routes/v1/signup.ts @@ -2,18 +2,21 @@ import express from "express"; const router = express.Router(); import { signupController } from "../../controllers/v1"; import { authLimiter } from "../../helpers/rateLimiter"; +import { disableSignUpByServerCfg } from "../../middleware"; // TODO: consider moving to users/v3/signup router.post( // TODO endpoint: consider moving to v3/users/signup/mail "/email/signup", + disableSignUpByServerCfg, authLimiter, signupController.beginEmailSignup ); router.post( "/email/verify", // TODO endpoint: consider moving to v3/users/signup/verify + disableSignUpByServerCfg, authLimiter, signupController.verifyEmailSignup ); diff --git a/backend/src/routes/v2/environment.ts b/backend/src/routes/v2/environment.ts index 9682a04a2..49e1786b3 100644 --- a/backend/src/routes/v2/environment.ts +++ b/backend/src/routes/v2/environment.ts @@ -36,12 +36,4 @@ router.delete( environmentController.deleteWorkspaceEnvironment ); -router.get( - "/:workspaceId/environments", - requireAuth({ - acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY] - }), - environmentController.getAllAccessibleEnvironmentsOfWorkspace -); - export default router; diff --git a/backend/src/routes/v2/signup.ts b/backend/src/routes/v2/signup.ts index eb376f56c..8a404cfc2 100644 --- a/backend/src/routes/v2/signup.ts +++ b/backend/src/routes/v2/signup.ts @@ -1,49 +1,51 @@ import express from "express"; const router = express.Router(); import { body } from "express-validator"; -import { requireSignupAuth, validateRequest } from "../../middleware"; +import { disableSignUpByServerCfg, requireSignupAuth, validateRequest } from "../../middleware"; import { signupController } from "../../controllers/v2"; import { authLimiter } from "../../helpers/rateLimiter"; router.post( - "/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup) - authLimiter, - requireSignupAuth, - body("email").exists().isString().trim().notEmpty().isEmail(), - body("firstName").exists().isString().trim().notEmpty(), - body("lastName").exists().isString().trim().notEmpty(), - body("protectedKey").exists().isString().trim().notEmpty(), - body("protectedKeyIV").exists().isString().trim().notEmpty(), - body("protectedKeyTag").exists().isString().trim().notEmpty(), - body("publicKey").exists().isString().trim().notEmpty(), - body("encryptedPrivateKey").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), - body("salt").exists().isString().trim().notEmpty(), - body("verifier").exists().isString().trim().notEmpty(), - body("organizationName").exists().isString().trim().notEmpty(), - validateRequest, - signupController.completeAccountSignup + "/complete-account/signup", // TODO endpoint: deprecate (moved to v3/signup/complete/account-signup), + disableSignUpByServerCfg, + authLimiter, + requireSignupAuth, + body("email").exists().isString().trim().notEmpty().isEmail(), + body("firstName").exists().isString().trim().notEmpty(), + body("lastName").exists().isString().trim().notEmpty(), + body("protectedKey").exists().isString().trim().notEmpty(), + body("protectedKeyIV").exists().isString().trim().notEmpty(), + body("protectedKeyTag").exists().isString().trim().notEmpty(), + body("publicKey").exists().isString().trim().notEmpty(), + body("encryptedPrivateKey").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), + body("salt").exists().isString().trim().notEmpty(), + body("verifier").exists().isString().trim().notEmpty(), + body("organizationName").exists().isString().trim().notEmpty(), + validateRequest, + signupController.completeAccountSignup ); router.post( - "/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite - authLimiter, - requireSignupAuth, - body("email").exists().isString().trim().notEmpty().isEmail(), - body("firstName").exists().isString().trim().notEmpty(), - body("lastName").exists().isString().trim().notEmpty(), - body("protectedKey").exists().isString().trim().notEmpty(), - body("protectedKeyIV").exists().isString().trim().notEmpty(), - body("protectedKeyTag").exists().isString().trim().notEmpty(), - body("publicKey").exists().trim().notEmpty(), - body("encryptedPrivateKey").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), - body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), - body("salt").exists().isString().trim().notEmpty(), - body("verifier").exists().isString().trim().notEmpty(), - validateRequest, - signupController.completeAccountInvite + "/complete-account/invite", // TODO: consider moving to v3/users/new/complete-account/invite + disableSignUpByServerCfg, + authLimiter, + requireSignupAuth, + body("email").exists().isString().trim().notEmpty().isEmail(), + body("firstName").exists().isString().trim().notEmpty(), + body("lastName").exists().isString().trim().notEmpty(), + body("protectedKey").exists().isString().trim().notEmpty(), + body("protectedKeyIV").exists().isString().trim().notEmpty(), + body("protectedKeyTag").exists().isString().trim().notEmpty(), + body("publicKey").exists().trim().notEmpty(), + body("encryptedPrivateKey").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyIV").exists().isString().trim().notEmpty(), + body("encryptedPrivateKeyTag").exists().isString().trim().notEmpty(), + body("salt").exists().isString().trim().notEmpty(), + body("verifier").exists().isString().trim().notEmpty(), + validateRequest, + signupController.completeAccountInvite ); -export default router; \ No newline at end of file +export default router; diff --git a/backend/src/routes/v3/signup.ts b/backend/src/routes/v3/signup.ts index 241f5d04c..e2b13a0a2 100644 --- a/backend/src/routes/v3/signup.ts +++ b/backend/src/routes/v3/signup.ts @@ -2,10 +2,11 @@ import express from "express"; const router = express.Router(); import { signupController } from "../../controllers/v3"; import { authLimiter } from "../../helpers/rateLimiter"; -import { validateRequest } from "../../middleware"; +import { disableSignUpByServerCfg, validateRequest } from "../../middleware"; router.post( "/complete-account/signup", // TODO: consider moving endpoint to v3/users/new/complete-account/signup + disableSignUpByServerCfg, authLimiter, validateRequest, signupController.completeAccountSignup diff --git a/backend/src/services/RedisService.ts b/backend/src/services/RedisService.ts index 218234a1a..e439ffb40 100644 --- a/backend/src/services/RedisService.ts +++ b/backend/src/services/RedisService.ts @@ -3,11 +3,14 @@ import { logger } from "../utils/logging"; let redisClient: TRedis | null; -if (process.env.REDIS_URL) { - redisClient = new Redis(process.env.REDIS_URL as string); -} else { - logger.warn("Redis URL not set, skipping Redis initialization."); - redisClient = null; +export const initRedis = async () => { + if (process.env.REDIS_URL) { + redisClient = new Redis(process.env.REDIS_URL as string); + } else { + logger.warn("Redis URL not set, skipping Redis initialization."); + redisClient = null; + } } + export { redisClient }; diff --git a/backend/src/services/smtp.ts b/backend/src/services/smtp.ts index cf688f801..027b81295 100644 --- a/backend/src/services/smtp.ts +++ b/backend/src/services/smtp.ts @@ -8,30 +8,28 @@ import { SMTP_HOST_ZOHOMAIL } from "../variables"; import SMTPConnection from "nodemailer/lib/smtp-connection"; -import * as Sentry from "@sentry/node"; import { getSmtpHost, getSmtpPassword, getSmtpPort, getSmtpSecure, - getSmtpUsername, + getSmtpUsername } from "../config"; -import { logger } from "../utils/logging"; export const initSmtp = async () => { const mailOpts: SMTPConnection.Options = { host: await getSmtpHost(), - port: await getSmtpPort(), + port: await getSmtpPort() }; if ((await getSmtpUsername()) && (await getSmtpPassword())) { mailOpts.auth = { user: await getSmtpUsername(), - pass: await getSmtpPassword(), + pass: await getSmtpPassword() }; } - if ((await getSmtpSecure()) ? (await getSmtpSecure()) : false) { + if ((await getSmtpSecure()) ? await getSmtpSecure() : false) { switch (await getSmtpHost()) { case SMTP_HOST_SENDGRID: mailOpts.requireTLS = true; @@ -39,38 +37,38 @@ export const initSmtp = async () => { case SMTP_HOST_MAILGUN: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_SOCKETLABS: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_ZOHOMAIL: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_GMAIL: mailOpts.requireTLS = true; mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; break; case SMTP_HOST_OFFICE365: mailOpts.requireTLS = true; mailOpts.tls = { ciphers: "TLSv1.2" - } + }; break; default: if ((await getSmtpHost()).includes("amazonaws.com")) { mailOpts.tls = { - ciphers: "TLSv1.2", - } + ciphers: "TLSv1.2" + }; } else { mailOpts.secure = true; } @@ -79,20 +77,6 @@ export const initSmtp = async () => { } const transporter = nodemailer.createTransport(mailOpts); - transporter - .verify() - .then(async () => { - Sentry.setUser(null); - Sentry.captureMessage("SMTP - Successfully connected"); - logger.info("SMTP - Successfully connected"); - }) - .catch(async (err) => { - Sentry.setUser(null); - Sentry.captureException( - `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()} \n\t${err}` - ); - logger.error(err, `SMTP - Failed to connect to ${await getSmtpHost()}:${await getSmtpPort()}`); - }); return transporter; }; diff --git a/backend/src/utils/authn/passport/helpers.ts b/backend/src/utils/authn/passport/helpers.ts index e8a21c37d..b6e672caa 100644 --- a/backend/src/utils/authn/passport/helpers.ts +++ b/backend/src/utils/authn/passport/helpers.ts @@ -1,62 +1,65 @@ -import { - AuthMethod, - User -} from "../../../models"; +import { AuthMethod, User } from "../../../models"; import { createToken } from "../../../helpers/auth"; import { AuthTokenType } from "../../../variables"; -import { getAuthSecret, getJwtProviderAuthLifetime} from "../../../config"; +import { getAuthSecret, getJwtProviderAuthLifetime } from "../../../config"; +import { getServerConfig } from "../../../config/serverConfig"; interface SSOUserTokenFlowParams { - email: string; - firstName: string; - lastName: string; - authMethod: AuthMethod; - callbackPort?: string; + email: string; + firstName: string; + lastName: string; + authMethod: AuthMethod; + callbackPort?: string; } export const handleSSOUserTokenFlow = async ({ - email, - firstName, - lastName, - authMethod, - callbackPort + email, + firstName, + lastName, + authMethod, + callbackPort }: SSOUserTokenFlowParams) => { - let user = await User.findOne({ - email - }).select("+publicKey"); - - if (!user) { - user = await new User({ - email, - authMethods: [authMethod], - firstName, - lastName - }).save(); - } + let user = await User.findOne({ + email + }).select("+publicKey"); - let isLinkingRequired = false; - if (!user.authMethods.includes(authMethod)) { + const serverCfg = getServerConfig(); + if (!user && !serverCfg.allowSignUp) throw new Error("User signup disabled"); + + if (!user) { + user = await new User({ + email, + authMethods: [authMethod], + firstName, + lastName + }).save(); + } + + let isLinkingRequired = false; + if (!user.authMethods.includes(authMethod)) { isLinkingRequired = true; - } + } - const isUserCompleted = !!user.publicKey; - const providerAuthToken = createToken({ + const isUserCompleted = !!user.publicKey; + const providerAuthToken = createToken({ payload: { - authTokenType: AuthTokenType.PROVIDER_TOKEN, - userId: user._id.toString(), - email: user.email, - firstName: user.firstName, - lastName: user.lastName, - authMethod, - isUserCompleted, - isLinkingRequired, - ...(callbackPort ? { + authTokenType: AuthTokenType.PROVIDER_TOKEN, + userId: user._id.toString(), + email: user.email, + firstName: user.firstName, + lastName: user.lastName, + authMethod, + isUserCompleted, + isLinkingRequired, + ...(callbackPort + ? { callbackPort - } : {}) + } + : {}) }, expiresIn: await getJwtProviderAuthLifetime(), - secret: await getAuthSecret(), - }); - - return { isUserCompleted, providerAuthToken }; -} \ No newline at end of file + secret: await getAuthSecret() + }); + + return { isUserCompleted, providerAuthToken }; +}; diff --git a/backend/src/utils/logging/index.ts b/backend/src/utils/logging/index.ts index ef89344c7..5d5654efc 100644 --- a/backend/src/utils/logging/index.ts +++ b/backend/src/utils/logging/index.ts @@ -1 +1 @@ -export { logger } from "./logger"; \ No newline at end of file +export { logger, initLogger } from "./logger"; diff --git a/backend/src/utils/logging/logger.ts b/backend/src/utils/logging/logger.ts index ecfe24b69..7562a8b5b 100644 --- a/backend/src/utils/logging/logger.ts +++ b/backend/src/utils/logging/logger.ts @@ -1,15 +1,69 @@ -import pino from "pino"; +import pino, { Logger } from "pino"; +import { getAwsCloudWatchLog, getNodeEnv } from "../../config"; -export const logger = pino({ - level: process.env.PINO_LOG_LEVEL || "trace", - timestamp: pino.stdTimeFunctions.isoTime, - formatters: { - bindings: (bindings) => { - return { +export let logger: Logger; + +// https://github.com/pinojs/pino/blob/master/lib/levels.js#L13-L20 +const logLevelToSeverityLookup: Record = { + '10': 'TRACE', + '20': 'DEBUG', + '30': 'INFO', + '40': 'WARNING', + '50': 'ERROR', + '60': 'CRITICAL' +} + +export const initLogger = async () => { + const awsCloudWatchLogCfg = await getAwsCloudWatchLog(); + const nodeEnv = await getNodeEnv(); + const isProduction = nodeEnv === "production"; + const targets: pino.TransportMultiOptions["targets"][number][] = [ + isProduction + ? { level: "info", target: "pino/file", options: {} } + : { + level: "info", + target: "pino-pretty", // must be installed separately + options: { + colorize: true + } + } + ]; + + if (awsCloudWatchLogCfg) { + targets.push({ + target: "@serdnam/pino-cloudwatch-transport", + level: "info", + options: { + logGroupName: awsCloudWatchLogCfg.logGroupName, + logStreamName: awsCloudWatchLogCfg.logGroupName, + awsRegion: awsCloudWatchLogCfg.region, + awsAccessKeyId: awsCloudWatchLogCfg.accessKeyId, + awsSecretAccessKey: awsCloudWatchLogCfg.accessKeySecret, + interval: awsCloudWatchLogCfg.interval + } + }); + } + + const transport = pino.transport({ + targets + }); + + logger = pino( + { + mixin(_context, level) { + return { 'severity': logLevelToSeverityLookup[level] || logLevelToSeverityLookup['30'] } + }, + level: process.env.PINO_LOG_LEVEL || "info", + formatters: { + bindings: (bindings) => { + return { pid: bindings.pid, hostname: bindings.hostname // node_version: process.version - }; + }; + } + } }, - } -}); \ No newline at end of file + transport + ); +}; diff --git a/backend/src/utils/setup/backfillData.ts b/backend/src/utils/setup/backfillData.ts index 319c2605b..21b2a95df 100644 --- a/backend/src/utils/setup/backfillData.ts +++ b/backend/src/utils/setup/backfillData.ts @@ -2,12 +2,12 @@ import crypto from "crypto"; import { Types } from "mongoose"; import { encryptSymmetric128BitHexKeyUTF8 } from "../crypto"; import { EESecretService } from "../../ee/services"; -import { redisClient } from "../../services/RedisService" -import { - IPType, - ISecretVersion, - Role, - SecretSnapshot, +import { redisClient } from "../../services/RedisService"; +import { + IPType, + ISecretVersion, + Role, + SecretSnapshot, SecretVersion, TrustedIP } from "../../ee/models"; @@ -30,7 +30,7 @@ import { Workspace } from "../../models"; import { generateKeyPair } from "../../utils/crypto"; -import { client, getEncryptionKey, getRootEncryptionKey } from "../../config"; +import { client, getEncryptionKey, getIsInfisicalCloud, getRootEncryptionKey } from "../../config"; import { ADMIN, ALGORITHM_AES_256_GCM, @@ -47,6 +47,7 @@ import { memberProjectPermissions } from "../../ee/services/ProjectRoleService"; import { logger } from "../logging"; +import { getServerConfig, updateServerConfig } from "../../config/serverConfig"; /** * Backfill secrets to ensure that they're all versioned and have @@ -693,7 +694,7 @@ export const backfillUserAuthMethods = async () => { export const backfillPermission = async () => { const lockKey = "backfill_permission_lock"; - const timeout = 900000; // 15 min lock timeout in milliseconds + const timeout = 900000; // 15 min lock timeout in milliseconds const lock = await redisClient?.set(lockKey, 1, "PX", timeout, "NX"); if (lock) { @@ -705,13 +706,16 @@ export const backfillPermission = async () => { $exists: true, $ne: [] }, - role: MEMBER, + role: MEMBER }) .populate<{ workspace: IWorkspace }>("workspace") .lean(); // group memberships that need the same permission set - const roleMap = new Map(); + const roleMap = new Map< + string, + { membershipIds: string[]; permissions: any[]; organizationId: string; workspaceId: string } + >(); for (const membership of memberships) { // get permissions of members except secret permission @@ -729,11 +733,11 @@ export const backfillPermission = async () => { }); // environments that are not listed in deniedPermissions should be set to allowed for both read & and write - membership.workspace.environments.forEach(env => { + membership.workspace.environments.forEach((env) => { if (!secretAccessRule?.[env.slug]) { secretAccessRule[env.slug] = { read: true, write: true }; } - }) + }); const secretPermissions: any = []; Object.entries(secretAccessRule).forEach(([envSlug, { read, write }]) => { @@ -769,21 +773,27 @@ export const backfillPermission = async () => { const value = roleMap.get(key); if (value) { value.membershipIds.push(membership._id.toString()); - value.organizationId = membership.workspace.organization.toString() - value.workspaceId = membership.workspace._id.toString() + value.organizationId = membership.workspace.organization.toString(); + value.workspaceId = membership.workspace._id.toString(); } else { - roleMap.set(key, { membershipIds: [membership._id.toString()], permissions: [...customPermissions, ...secretPermissions], organizationId: membership.workspace.organization.toString(), workspaceId: membership.workspace._id.toString() }); + roleMap.set(key, { + membershipIds: [membership._id.toString()], + permissions: [...customPermissions, ...secretPermissions], + organizationId: membership.workspace.organization.toString(), + workspaceId: membership.workspace._id.toString() + }); } } for (const [key, value] of roleMap.entries()) { - const { membershipIds, permissions, workspaceId, organizationId } = value - const membership_identity = crypto.randomBytes(3).toString("hex") + const { membershipIds, permissions, workspaceId, organizationId } = value; + const membership_identity = crypto.randomBytes(3).toString("hex"); const role = new Role({ name: `Limited [${membership_identity.toUpperCase()}]`, organization: organizationId, workspace: workspaceId, - description: "This role was auto generated by Infisical in effort to migrate your project members to our new permission system", + description: + "This role was auto generated by Infisical in effort to migrate your project members to our new permission system", isOrgRole: false, slug: `custom-role-${membership_identity}`, permissions: permissions @@ -792,7 +802,8 @@ export const backfillPermission = async () => { await role.save(); for (const id of membershipIds) { - await Membership.findByIdAndUpdate(id, { // document db doesn't support update many so we must loop + await Membership.findByIdAndUpdate(id, { + // document db doesn't support update many so we must loop $set: { role: CUSTOM, customRole: role @@ -815,11 +826,9 @@ export const backfillPermission = async () => { ); logger.info("Backfill: Finished converting owner role to member"); - } catch (error) { logger.error(error, "An error occurred when running script [backfillPermission]"); } - } else { logger.info("Could not acquire lock for script [backfillPermission], skipping"); } @@ -838,4 +847,33 @@ export const migrateRoleFromOwnerToAdmin = async () => { ); logger.info("Backfill: Finished converting owner role to member"); -} \ No newline at end of file +}; + +export const migrationAssignSuperadmin = async () => { + const users = await User.find({}).sort({ createdAt: 1 }).limit(2); + const serverCfg = getServerConfig(); + if (serverCfg.initialized) return; + + if (await getIsInfisicalCloud()) { + await updateServerConfig({ initialized: true }); + logger.info("Backfill: Infisical Cloud(initialized)"); + return; + } + + if (users.length) { + let superAdminUserId = ""; + const firstAccount = users?.[0]; + if (firstAccount.email === "test@localhost.local" && users.length === 2) { + superAdminUserId = users?.[1]?._id.toString(); + } else { + superAdminUserId = firstAccount._id.toString(); + } + + if (superAdminUserId) { + const user = await User.findByIdAndUpdate(superAdminUserId, { superAdmin: true }); + await updateServerConfig({ initialized: true }); + logger.info(`Migrated ${user?.email} to superuser`); + } + logger.info("Backfill: Migrated first infisical user to super admin"); + } +}; diff --git a/backend/src/utils/setup/index.ts b/backend/src/utils/setup/index.ts index b9fe39992..e6bdd88a0 100644 --- a/backend/src/utils/setup/index.ts +++ b/backend/src/utils/setup/index.ts @@ -1,9 +1,8 @@ import * as Sentry from "@sentry/node"; -import { DatabaseService, TelemetryService } from "../../services"; +import { TelemetryService } from "../../services"; import { setTransporter } from "../../helpers/nodemailer"; import { EELicenseService } from "../../ee/services"; import { initSmtp } from "../../services/smtp"; -import { createTestUserForDevelopment } from "../addDevelopmentUser"; // eslint-disable-next-line @typescript-eslint/no-var-requires import { validateEncryptionKeysConfig } from "./validateConfig"; import { @@ -18,14 +17,15 @@ import { backfillServiceTokenMultiScope, backfillTrustedIps, backfillUserAuthMethods, - migrateRoleFromOwnerToAdmin + migrateRoleFromOwnerToAdmin, + migrationAssignSuperadmin } from "./backfillData"; import { reencryptBotOrgKeys, reencryptBotPrivateKeys, reencryptSecretBlindIndexDataSalts } from "./reencryptData"; -import { getMongoURL, getNodeEnv, getRedisUrl, getSentryDSN } from "../../config"; +import { getNodeEnv, getRedisUrl, getSentryDSN } from "../../config"; import { initializeGitHubStrategy, initializeGitLabStrategy, @@ -33,6 +33,7 @@ import { initializeSamlStrategy } from "../authn/passport"; import { logger } from "../logging"; +import { bootstrap } from "../../bootstrap"; /** * Prepare Infisical upon startup. This includes tasks like: @@ -55,14 +56,15 @@ export const setup = async () => { await TelemetryService.logTelemetryMessage(); // initializing SMTP configuration - setTransporter(await initSmtp()); + const transporter = await initSmtp(); + setTransporter(transporter); // initializing global feature set await EELicenseService.initGlobalFeatureSet(); // initializing auth strategies await initializeGoogleStrategy(); - await initializeGitHubStrategy() + await initializeGitHubStrategy(); await initializeGitLabStrategy(); await initializeSamlStrategy(); @@ -71,8 +73,7 @@ export const setup = async () => { // await reencryptBotPrivateKeys(); // await reencryptSecretBlindIndexDataSalts(); - // initializing the database connection - await DatabaseService.initDatabase(await getMongoURL()); + await bootstrap({ transporter }); /** * NOTE: the order in this setup function is critical. @@ -92,7 +93,8 @@ export const setup = async () => { await backfillTrustedIps(); await backfillUserAuthMethods(); // await backfillPermission(); - await migrateRoleFromOwnerToAdmin() + await migrateRoleFromOwnerToAdmin(); + await migrationAssignSuperadmin(); // re-encrypt any data previously encrypted under server hex 128-bit ENCRYPTION_KEY // to base64 256-bit ROOT_ENCRYPTION_KEY @@ -108,5 +110,7 @@ export const setup = async () => { environment: await getNodeEnv() }); - await createTestUserForDevelopment(); + // akhilmhdh: removed dev account as we have now admin account onboarding flow + // That will be user's first account going forward + // await createTestUserForDevelopment(); }; diff --git a/backend/src/validation/admin.ts b/backend/src/validation/admin.ts new file mode 100644 index 000000000..aee6c88cc --- /dev/null +++ b/backend/src/validation/admin.ts @@ -0,0 +1,24 @@ +import { z } from "zod"; + +export const UpdateServerConfigV1 = z.object({ + body: z.object({ + allowSignUp: z.boolean().optional() + }) +}); + +export const SignupV1 = z.object({ + body: z.object({ + email: z.string().email().trim(), + firstName: z.string().trim(), + lastName: z.string().trim().optional(), + protectedKey: z.string().trim(), + protectedKeyIV: z.string().trim(), + protectedKeyTag: z.string().trim(), + publicKey: z.string().trim(), + encryptedPrivateKey: z.string().trim(), + encryptedPrivateKeyIV: z.string().trim(), + encryptedPrivateKeyTag: z.string().trim(), + salt: z.string().trim(), + verifier: z.string().trim() + }) +}); diff --git a/backend/src/validation/serviceTokenDataV3.ts b/backend/src/validation/serviceTokenDataV3.ts index 4be60c645..e4b2603b3 100644 --- a/backend/src/validation/serviceTokenDataV3.ts +++ b/backend/src/validation/serviceTokenDataV3.ts @@ -1,64 +1,5 @@ -import { Types } from "mongoose"; -import { IServiceTokenDataV3 } from "../models"; -import { Permission } from "../models/serviceTokenDataV3"; import { z } from "zod"; -import { UnauthorizedRequestError } from "../utils/errors"; -import { isValidScopeV3 } from "../helpers"; -import { AuthData } from "../interfaces/middleware"; -import { checkIPAgainstBlocklist } from "../utils/ip"; - -/** - * Validate that service token (client) can access workspace - * with id [workspaceId] and its environment [environment] with required permissions - * [requiredPermissions] - * @param {Object} obj - * @param {ServiceTokenData} obj.serviceTokenData - service token client - * @param {Types.ObjectId} obj.workspaceId - id of workspace to validate against - * @param {String} environment - (optional) environment in workspace to validate against - * @param {String[]} acceptedPermissions - accepted permissions as part of the endpoint - */ - export const validateServiceTokenDataV3ClientForWorkspace = async ({ - authData, - serviceTokenData, - workspaceId, - environment, - secretPath = "/", - requiredPermissions -}: { - authData: AuthData; - serviceTokenData: IServiceTokenDataV3; - workspaceId: Types.ObjectId; - environment?: string; - secretPath?: string; - requiredPermissions: Permission[]; -}) => { - - // validate ST V3 IP address - checkIPAgainstBlocklist({ - ipAddress: authData.ipAddress, - trustedIps: serviceTokenData.trustedIps - }); - - if (!serviceTokenData.workspace.equals(workspaceId)) { - // case: invalid workspaceId passed - throw UnauthorizedRequestError({ - message: "Failed service token authorization for the given workspace" - }); - } - - if (environment) { - const isValid = isValidScopeV3({ - authPayload: serviceTokenData, - environment, - secretPath, - requiredPermissions - }); - - if (!isValid) throw UnauthorizedRequestError({ - message: "Failed service token authorization for the given workspace" - }); - } -}; +import { MEMBER } from "../variables"; export const RefreshTokenV3 = z.object({ body: z.object({ @@ -71,20 +12,14 @@ export const CreateServiceTokenV3 = z.object({ name: z.string().trim(), workspaceId: z.string().trim(), publicKey: z.string().trim(), - scopes: z - .object({ - permissions: z.enum(["read", "write"]).array(), - environment: z.string().trim(), - secretPath: z.string().trim() - }) - .array() - .min(1), - trustedIps: z + role: z.string().trim().min(1).default(MEMBER), + trustedIps: z // TODO: provide default .object({ ipAddress: z.string().trim(), }) .array() - .min(1), + .min(1) + .default([{ ipAddress: "0.0.0.0/0" }]), expiresIn: z.number().optional(), accessTokenTTL: z.number().int().min(1), encryptedKey: z.string().trim(), @@ -100,15 +35,7 @@ export const UpdateServiceTokenV3 = z.object({ body: z.object({ name: z.string().trim().optional(), isActive: z.boolean().optional(), - scopes: z - .object({ - permissions: z.enum(["read", "write"]).array(), - environment: z.string().trim(), - secretPath: z.string().trim() - }) - .array() - .min(1) - .optional(), + role: z.string().trim().min(1).optional(), trustedIps: z .object({ ipAddress: z.string().trim() diff --git a/backend/tsconfig.json b/backend/tsconfig.json index d2f7a1b89..a908b72e9 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -15,6 +15,9 @@ "skipLibCheck": true, "typeRoots": ["./src/types", "./node_modules/@types"] }, + "ts-node": { + "swc": true + }, "include": ["src/**/*"], "exclude": ["node_modules"] } diff --git a/docker-compose.yml b/docker-compose.yml index 57e5b5b63..c159a2175 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: redis: image: redis container_name: infisical-dev-redis + env_file: .env environment: - ALLOW_EMPTY_PASSWORD=yes ports: diff --git a/frontend/public/locales/en/translations.json b/frontend/public/locales/en/translations.json index 9900176e1..8b0487292 100644 --- a/frontend/public/locales/en/translations.json +++ b/frontend/public/locales/en/translations.json @@ -336,5 +336,9 @@ "step5-invite-team": "Invite your team", "step5-subtitle": "Infisical is meant to be used with your teammates. Invite them to test it out.", "step5-skip": "Skip" + }, + "admin": { + "signup-title": "Admin Sign Up", + "dashboard": "Admin Dashboard" } } diff --git a/frontend/src/components/signup/DonwloadBackupPDFStep.tsx b/frontend/src/components/signup/DonwloadBackupPDFStep.tsx index 9b170cf1d..d928c1473 100644 --- a/frontend/src/components/signup/DonwloadBackupPDFStep.tsx +++ b/frontend/src/components/signup/DonwloadBackupPDFStep.tsx @@ -2,7 +2,11 @@ import { useTranslation } from "react-i18next"; import { faWarning } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import issueBackupKey from "../utilities/cryptography/issueBackupKey"; +import { useToggle } from "@app/hooks"; +import { generateUserBackupKey } from "@app/lib/crypto"; + +import { useNotificationContext } from "../context/Notifications/NotificationProvider"; +import { generateBackupPDFAsync } from "../utilities/generateBackupPDF"; import { Button } from "../v2"; interface DownloadBackupPDFStepProps { @@ -28,35 +32,56 @@ export default function DonwloadBackupPDFStep({ name }: DownloadBackupPDFStepProps): JSX.Element { const { t } = useTranslation(); + const { createNotification } = useNotificationContext(); + const [isLoading, setIsLoading] = useToggle(); + + const handleBackupKeyGenerate = async () => { + try { + setIsLoading.on(); + const generatedKey = await generateUserBackupKey(email, password); + await generateBackupPDFAsync({ + generatedKey, + personalEmail: email, + personalName: name + }); + incrementStep(); + } catch (err) { + console.log(err); + createNotification({ + type: "error", + text: "Faield to generate backup key" + }); + } finally { + setIsLoading.off(); + } + }; return (

- {t("signup.step4-message")} + + {t("signup.step4-message")}

- {t("signup.step4-description1")} {t("signup.step4-description3")} + + {t("signup.step4-description1")} {t("signup.step4-description3")} +
+ > + Download PDF +
diff --git a/frontend/src/components/utilities/attemptLogin.ts b/frontend/src/components/utilities/attemptLogin.ts index c9cedbf68..195cf9b9a 100644 --- a/frontend/src/components/utilities/attemptLogin.ts +++ b/frontend/src/components/utilities/attemptLogin.ts @@ -8,9 +8,6 @@ import Telemetry from "./telemetry/Telemetry"; import { saveTokenToLocalStorage } from "./saveTokenToLocalStorage"; import SecurityClient from "./SecurityClient"; -// eslint-disable-next-line new-cap -const client = new jsrp.client(); - interface IsLoginSuccessful { mfaEnabled: boolean; success: boolean; @@ -22,118 +19,100 @@ interface IsLoginSuccessful { * @param {string} email - email of user to log in * @param {string} password - password of user to log in */ -const attemptLogin = async ( - { - email, - password, - providerAuthToken, - }: { - email: string; - password: string; - providerAuthToken?: string; - } -): Promise => { +const attemptLogin = async ({ + email, + password, + providerAuthToken +}: { + email: string; + password: string; + providerAuthToken?: string; +}): Promise => { const telemetry = new Telemetry().getInstance(); - return new Promise((resolve, reject) => { - client.init( - { - username: email, - password - }, - async () => { - try { - const clientPublicKey = client.getPublicKey(); - - const { serverPublicKey, salt } = await login1({ - email, - clientPublicKey, - providerAuthToken, - }); - - client.setSalt(salt); - client.setServerPublicKey(serverPublicKey); - const clientProof = client.getProof(); // called M1 - - const { - mfaEnabled, - encryptionVersion, - protectedKey, - protectedKeyIV, - protectedKeyTag, - token, - publicKey, - encryptedPrivateKey, - iv, - tag - } = await login2( - { - email, - clientProof, - providerAuthToken, - } - ); - - if (mfaEnabled) { - // case: MFA is enabled - - // set temporary (MFA) JWT token - SecurityClient.setMfaToken(token); - - resolve({ - mfaEnabled, - success: true - }); - } else if ( - !mfaEnabled && - encryptionVersion && - encryptedPrivateKey && - iv && - tag && - token - ) { - // case: MFA is not enabled - - // unset provider auth token in case it was used - SecurityClient.setProviderAuthToken(""); - // set JWT token - SecurityClient.setToken(token); - - const privateKey = await KeyService.decryptPrivateKey({ - encryptionVersion, - encryptedPrivateKey, - iv, - tag, - password, - salt, - protectedKey, - protectedKeyIV, - protectedKeyTag - }); - - saveTokenToLocalStorage({ - publicKey, - encryptedPrivateKey, - iv, - tag, - privateKey - }); - - if (email) { - telemetry.identify(email, email); - telemetry.capture("User Logged In"); - } - - resolve({ - mfaEnabled: false, - success: true - }); - } - } catch (err) { - reject(err); - } - } - ); + // eslint-disable-next-line new-cap + const client = new jsrp.client(); + await new Promise((resolve) => { + client.init({ username: email, password }, () => resolve(null)); }); + const clientPublicKey = client.getPublicKey(); + + const { serverPublicKey, salt } = await login1({ + email, + clientPublicKey, + providerAuthToken + }); + + client.setSalt(salt); + client.setServerPublicKey(serverPublicKey); + const clientProof = client.getProof(); // called M1 + + const { + mfaEnabled, + encryptionVersion, + protectedKey, + protectedKeyIV, + protectedKeyTag, + token, + publicKey, + encryptedPrivateKey, + iv, + tag + } = await login2({ + email, + clientProof, + providerAuthToken + }); + + if (mfaEnabled) { + // case: MFA is enabled + + // set temporary (MFA) JWT token + SecurityClient.setMfaToken(token); + + return { + mfaEnabled, + success: true + }; + } + if (!mfaEnabled && encryptionVersion && encryptedPrivateKey && iv && tag && token) { + // case: MFA is not enabled + + // unset provider auth token in case it was used + SecurityClient.setProviderAuthToken(""); + // set JWT token + SecurityClient.setToken(token); + + const privateKey = await KeyService.decryptPrivateKey({ + encryptionVersion, + encryptedPrivateKey, + iv, + tag, + password, + salt, + protectedKey, + protectedKeyIV, + protectedKeyTag + }); + + saveTokenToLocalStorage({ + publicKey, + encryptedPrivateKey, + iv, + tag, + privateKey + }); + + if (email) { + telemetry.identify(email, email); + telemetry.capture("User Logged In"); + } + + return { + mfaEnabled: false, + success: true + }; + } + return { success: false, mfaEnabled: false }; }; export default attemptLogin; diff --git a/frontend/src/components/utilities/checks/OnboardingCheck.ts b/frontend/src/components/utilities/checks/OnboardingCheck.ts index 01d7a2d55..b3abc9b85 100644 --- a/frontend/src/components/utilities/checks/OnboardingCheck.ts +++ b/frontend/src/components/utilities/checks/OnboardingCheck.ts @@ -1,6 +1,7 @@ -import { fetchOrgUsers,fetchUserAction } from "@app/hooks/api/users/queries"; +import { fetchOrgUsers, fetchUserAction } from "@app/hooks/api/users/queries"; interface OnboardingCheckProps { + orgId: string; setTotalOnboardingActionsDone?: (value: number) => void; setHasUserClickedSlack?: (value: boolean) => void; setHasUserClickedIntro?: (value: boolean) => void; @@ -12,6 +13,7 @@ interface OnboardingCheckProps { * This function checks which onboarding steps a user has already finished. */ const onboardingCheck = async ({ + orgId, setTotalOnboardingActionsDone, setHasUserClickedSlack, setHasUserClickedIntro, @@ -19,9 +21,7 @@ const onboardingCheck = async ({ setUsersInOrg }: OnboardingCheckProps) => { let countActions = 0; - const userActionSlack = await fetchUserAction( - "slack_cta_clicked" - ); + const userActionSlack = await fetchUserAction("slack_cta_clicked"); if (userActionSlack) { countActions += 1; @@ -41,9 +41,8 @@ const onboardingCheck = async ({ } if (setHasUserClickedIntro) setHasUserClickedIntro(!!userActionIntro); - const orgId = localStorage.getItem("orgData.id"); const orgUsers = await fetchOrgUsers(orgId || ""); - + if (orgUsers.length > 1) { countActions += 1; } diff --git a/frontend/src/components/utilities/generateBackupPDF.ts b/frontend/src/components/utilities/generateBackupPDF.ts index 3dace44da..bf3b430c6 100644 --- a/frontend/src/components/utilities/generateBackupPDF.ts +++ b/frontend/src/components/utilities/generateBackupPDF.ts @@ -16,7 +16,6 @@ const yyyy = today.getFullYear(); const todayFormatted = `${mm}/${dd}/${yyyy}`; - function createPdfHeader(doc: jsPDF, personalName: string) { doc.setFillColor(255, 255, 255); doc.rect(0, 0, 600, 900, "F"); @@ -92,5 +91,15 @@ function generateBackupPDF({ personalName, personalEmail, generatedKey }: PDFPro doc.save("Infisical Emergency Kit.pdf"); } -export default generateBackupPDF; +/** + * This function generate a pdf with a secret key for a user. + */ +export function generateBackupPDFAsync({ personalName, personalEmail, generatedKey }: PDFProps) { + // eslint-disable-next-line new-cap + const doc = new jsPDF("p", "pt", "a4", true); + createPdfHeader(doc, personalName); + createPdfContent(doc, personalEmail, generatedKey); + return doc.save("Infisical Emergency Kit.pdf", { returnPromise: true }); +} +export default generateBackupPDF; diff --git a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx index f43ee923c..c4759ae01 100644 --- a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx +++ b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx @@ -3,13 +3,15 @@ import { useEffect, useState } from "react"; import { AnimatePresence, motion } from "framer-motion"; +import { twMerge } from "tailwind-merge"; type Props = { text?: string | string[]; frequency?: number; + className?: string; }; -export const ContentLoader = ({ text, frequency = 2000 }: Props) => { +export const ContentLoader = ({ text, frequency = 2000, className }: Props) => { const [pos, setPos] = useState(0); const isTextArray = Array.isArray(text); useEffect(() => { @@ -23,7 +25,12 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => { }, []); return ( -
+
loading animation {text && isTextArray && ( diff --git a/frontend/src/components/v2/Switch/Switch.tsx b/frontend/src/components/v2/Switch/Switch.tsx index c66da2204..a54657fc6 100644 --- a/frontend/src/components/v2/Switch/Switch.tsx +++ b/frontend/src/components/v2/Switch/Switch.tsx @@ -3,7 +3,7 @@ import * as SwitchPrimitive from "@radix-ui/react-switch"; import { twMerge } from "tailwind-merge"; export type SwitchProps = Omit & { - children: ReactNode; + children?: ReactNode; id: string; isChecked?: boolean; isRequired?: boolean; diff --git a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx index fb1a21d40..1c1d732e1 100644 --- a/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx +++ b/frontend/src/components/v2/UpgradePlanModal/UpgradePlanModal.tsx @@ -1,7 +1,5 @@ import { useOrganization, useSubscription } from "@app/context"; -import { - useGetOrgTrialUrl -} from "@app/hooks/api"; +import { useGetOrgTrialUrl } from "@app/hooks/api"; import { Button } from "../Button"; import { Modal, ModalContent } from "../Modal"; @@ -16,38 +14,36 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele const { subscription } = useSubscription(); const { currentOrg } = useOrganization(); const { mutateAsync, isLoading } = useGetOrgTrialUrl(); - const link = (subscription && subscription.slug !== null) - ? `/org/${currentOrg?._id}/billing` - : "https://infisical.com/scheduledemo"; - + const link = + subscription && subscription.slug !== null + ? `/org/${currentOrg?._id}/billing` + : "https://infisical.com/scheduledemo"; + const handleUpgradeBtnClick = async () => { try { if (!subscription || !currentOrg) return; - + if (!subscription.has_used_trial) { // direct user to start pro trial - + const url = await mutateAsync({ orgId: currentOrg._id, success_url: window.location.href }); - + window.location.href = url; } else { // direct user to upgrade their plan window.location.href = link; } - } catch (err) { console.error(err); } - } - + }; + return ( - +

{text}

Upgrade and get access to this, as well as to other powerful enhancements. @@ -59,10 +55,10 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele onClick={handleUpgradeBtnClick} className="mr-4" > - {(subscription && !subscription.has_used_trial) ? "Start Pro Free Trial" : "Upgrade Plan"} + {subscription && !subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"} -

- ) -} \ No newline at end of file + ); +}; diff --git a/frontend/src/const.ts b/frontend/src/const.ts index 267d80ccd..67340780d 100644 --- a/frontend/src/const.ts +++ b/frontend/src/const.ts @@ -21,7 +21,8 @@ export const publicPaths = [ "/saml-sso", "/login/provider/success", // TODO: change "/login/provider/error", // TODO: change - "/login/sso" + "/login/sso", + "/admin/signup" ]; export const languageMap = { @@ -50,7 +51,8 @@ const plansProd: Mapping = { export const plans = plansProd || plansDev; -export const leaveConfirmDefaultMessage = "Your changes will be lost if you leave the page. Are you sure you want to continue?"; +export const leaveConfirmDefaultMessage = + "Your changes will be lost if you leave the page. Are you sure you want to continue?"; export const secretTagsColors = [ { @@ -115,5 +117,5 @@ export const secretTagsColors = [ rgba: "rgb(255,0,0, 0.8)", name: "Red", selected: false - }, -] \ No newline at end of file + } +]; diff --git a/frontend/src/context/AuthContext/AuthContext.tsx b/frontend/src/context/AuthContext/AuthContext.tsx index 7515e459c..3c615485b 100644 --- a/frontend/src/context/AuthContext/AuthContext.tsx +++ b/frontend/src/context/AuthContext/AuthContext.tsx @@ -18,7 +18,7 @@ type Props = { // Provide a context for whole app to notify user is authorized or not export const AuthProvider = ({ children }: Props): JSX.Element => { const { isLoading } = useGetAuthToken(); - const { pathname, push } = useRouter(); + const { pathname, push, asPath } = useRouter(); const [isReady, setIsReady] = useToggle(false); useEffect(() => { @@ -26,7 +26,7 @@ export const AuthProvider = ({ children }: Props): JSX.Element => { if (!isLoading) { // not a public path and not authenticated kick to login page if (!publicPaths.includes(pathname) && !isLoggedIn()) { - push("/login").then(() => { + push({ pathname: "/login", query: { redirect: asPath } }).then(() => { setIsReady.on(); }); } else { @@ -40,7 +40,12 @@ export const AuthProvider = ({ children }: Props): JSX.Element => { if (isLoading || !isReady) { return (
- infisical loading indicator + infisical loading indicator
); } diff --git a/frontend/src/context/ServerConfigContext/ServerConfigContext.tsx b/frontend/src/context/ServerConfigContext/ServerConfigContext.tsx new file mode 100644 index 000000000..fba1600cc --- /dev/null +++ b/frontend/src/context/ServerConfigContext/ServerConfigContext.tsx @@ -0,0 +1,53 @@ +import { createContext, ReactNode, useContext, useEffect, useMemo } from "react"; +import { useRouter } from "next/router"; + +import { ContentLoader } from "@app/components/v2/ContentLoader"; +import { useGetServerConfig } from "@app/hooks/api"; +import { TServerConfig } from "@app/hooks/api/admin/types"; + +type TServerConfigContext = { + config: TServerConfig; +}; + +const ServerConfigContext = createContext(null); + +type Props = { + children: ReactNode; +}; + +export const ServerConfigProvider = ({ children }: Props): JSX.Element => { + const router = useRouter(); + const { data, isLoading } = useGetServerConfig(); + + // memorize the workspace details for the context + const value = useMemo(() => { + return { + config: data! + }; + }, [data]); + + useEffect(() => { + if (!isLoading && data && !data.initialized) { + router.push("/admin/signup"); + } + }, [isLoading, data]); + + if (isLoading || (!data?.initialized && router.pathname !== "/admin/signup")) { + return ( +
+ +
+ ); + } + + return {children}; +}; + +export const useServerConfig = () => { + const ctx = useContext(ServerConfigContext); + if (!ctx) { + throw new Error("useServerConfig has to be used within "); + } + + return ctx; +}; diff --git a/frontend/src/context/ServerConfigContext/index.tsx b/frontend/src/context/ServerConfigContext/index.tsx new file mode 100644 index 000000000..aa7e7a4d0 --- /dev/null +++ b/frontend/src/context/ServerConfigContext/index.tsx @@ -0,0 +1 @@ +export { ServerConfigProvider,useServerConfig } from "./ServerConfigContext"; diff --git a/frontend/src/context/index.tsx b/frontend/src/context/index.tsx index 35c39f533..11c729080 100644 --- a/frontend/src/context/index.tsx +++ b/frontend/src/context/index.tsx @@ -14,6 +14,7 @@ export { ProjectPermissionSub, useProjectPermission } from "./ProjectPermissionContext"; +export { ServerConfigProvider,useServerConfig } from "./ServerConfigContext"; export { SubscriptionProvider, useSubscription } from "./SubscriptionContext"; export { UserProvider, useUser } from "./UserContext"; export { useWorkspace, WorkspaceProvider } from "./WorkspaceContext"; diff --git a/frontend/src/hooks/api/admin/index.ts b/frontend/src/hooks/api/admin/index.ts new file mode 100644 index 000000000..e1c4301d4 --- /dev/null +++ b/frontend/src/hooks/api/admin/index.ts @@ -0,0 +1,2 @@ +export { useCreateAdminUser, useUpdateServerConfig } from "./mutation"; +export { useGetServerConfig } from "./queries"; diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts new file mode 100644 index 000000000..ea53ff1db --- /dev/null +++ b/frontend/src/hooks/api/admin/mutation.ts @@ -0,0 +1,39 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { User } from "../users/types"; +import { adminQueryKeys } from "./queries"; +import { TCreateAdminUserDTO, TServerConfig } from "./types"; + +export const useCreateAdminUser = () => { + const queryClient = useQueryClient(); + + return useMutation<{ user: User; token: string }, {}, TCreateAdminUserDTO>({ + mutationFn: async (opt) => { + const { data } = await apiRequest.post("/api/v1/admin/signup", opt); + return data; + }, + onSuccess: () => { + queryClient.invalidateQueries(adminQueryKeys.serverConfig()); + } + }); +}; + +export const useUpdateServerConfig = () => { + const queryClient = useQueryClient(); + + return useMutation>({ + mutationFn: async (opt) => { + const { data } = await apiRequest.patch<{ config: TServerConfig }>( + "/api/v1/admin/config", + opt + ); + return data.config; + }, + onSuccess: (data) => { + queryClient.setQueryData(adminQueryKeys.serverConfig(), data); + queryClient.invalidateQueries(adminQueryKeys.serverConfig()); + } + }); +}; diff --git a/frontend/src/hooks/api/admin/queries.ts b/frontend/src/hooks/api/admin/queries.ts new file mode 100644 index 000000000..f64c8bfa9 --- /dev/null +++ b/frontend/src/hooks/api/admin/queries.ts @@ -0,0 +1,34 @@ +import { useQuery, UseQueryOptions } from "@tanstack/react-query"; + +import { apiRequest } from "@app/config/request"; + +import { TServerConfig } from "./types"; + +export const adminQueryKeys = { + serverConfig: () => ["server-config"] as const +}; + +const fetchServerConfig = async () => { + const { data } = await apiRequest.get<{ config: TServerConfig }>("/api/v1/admin/config"); + return data.config; +}; + +export const useGetServerConfig = ({ + options = {} +}: { + options?: Omit< + UseQueryOptions< + TServerConfig, + unknown, + TServerConfig, + ReturnType + >, + "queryKey" | "queryFn" + >; +} = {}) => + useQuery({ + queryKey: adminQueryKeys.serverConfig(), + queryFn: fetchServerConfig, + ...options, + enabled: options?.enabled ?? true + }); diff --git a/frontend/src/hooks/api/admin/types.ts b/frontend/src/hooks/api/admin/types.ts new file mode 100644 index 000000000..72453ad44 --- /dev/null +++ b/frontend/src/hooks/api/admin/types.ts @@ -0,0 +1,19 @@ +export type TServerConfig = { + initialized: boolean; + allowSignUp: boolean; +}; + +export type TCreateAdminUserDTO = { + email: string; + firstName: string; + lastName?: string; + protectedKey: string; + protectedKeyTag: string; + protectedKeyIV: string; + encryptedPrivateKey: string; + encryptedPrivateKeyIV: string; + encryptedPrivateKeyTag: string; + publicKey: string; + verifier: string; + salt: string; +}; diff --git a/frontend/src/hooks/api/auditLogs/types.tsx b/frontend/src/hooks/api/auditLogs/types.tsx index f28f9004c..8680c9bed 100644 --- a/frontend/src/hooks/api/auditLogs/types.tsx +++ b/frontend/src/hooks/api/auditLogs/types.tsx @@ -4,17 +4,6 @@ import { UserAgentType } from "./enums"; -enum Permission { - READ = "read", - READ_WRITE = "readWrite" -} - -interface Scope { - environment: string; - secretPath: string; - permission: Permission; -} - interface UserActorMetadata { userId: string; email: string; @@ -211,7 +200,7 @@ interface CreateServiceTokenV3Event { metadata: { name: string; isActive: boolean; - scopes: Array; + role: string; expiresAt?: Date; } } @@ -221,7 +210,7 @@ interface UpdateServiceTokenV3Event { metadata: { name?: string; isActive?: boolean; - scopes?: Array; + role?: string; expiresAt?: Date; } } @@ -231,7 +220,7 @@ interface DeleteServiceTokenV3Event { metadata: { name: string; isActive: boolean; - scopes: Array; + role?: string; expiresAt?: Date; } } diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index ea5bb658d..c5e719218 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -1,4 +1,5 @@ export * from "./apiKeys"; +export * from "./admin" export * from "./auditLogs"; export * from "./auth"; export * from "./bots"; diff --git a/frontend/src/hooks/api/serviceTokens/queries.tsx b/frontend/src/hooks/api/serviceTokens/queries.tsx index 44786ba0d..d0bf2ac42 100644 --- a/frontend/src/hooks/api/serviceTokens/queries.tsx +++ b/frontend/src/hooks/api/serviceTokens/queries.tsx @@ -85,8 +85,8 @@ export const useUpdateServiceTokenV3 = () => { mutationFn: async ({ serviceTokenDataId, name, + role, isActive, - scopes, trustedIps, expiresIn, accessTokenTTL, @@ -94,8 +94,8 @@ export const useUpdateServiceTokenV3 = () => { }) => { const { data: { serviceTokenData } } = await apiRequest.patch(`/api/v3/service-token/${serviceTokenDataId}`, { name, + role, isActive, - scopes, trustedIps, expiresIn, accessTokenTTL, diff --git a/frontend/src/hooks/api/serviceTokens/types.ts b/frontend/src/hooks/api/serviceTokens/types.ts index 18e59c68e..210ff7cd8 100644 --- a/frontend/src/hooks/api/serviceTokens/types.ts +++ b/frontend/src/hooks/api/serviceTokens/types.ts @@ -1,5 +1,3 @@ -import { Permission } from "./enums"; - export type ServiceTokenScope = { environment: string; secretPath: string; @@ -38,12 +36,6 @@ export type DeleteServiceTokenRes = { serviceTokenData: ServiceToken }; // --- v3 -export type ServiceTokenV3Scope = { - permissions: Permission[]; - environment: string; - secretPath: string; -}; - export type ServiceTokenV3TrustedIp = { _id: string; ipAddress: string; @@ -54,13 +46,17 @@ export type ServiceTokenV3TrustedIp = { export type ServiceTokenDataV3 = { _id: string; name: string; + role: string; + customRole?: { + name: string; + slug: string; + }; workspace: string; isActive: boolean; refreshTokenLastUsed?: string; accessTokenLastUsed?: string; refreshTokenUsageCount: number; accessTokenUsageCount: number; - scopes: ServiceTokenV3Scope[]; trustedIps: ServiceTokenV3TrustedIp[]; expiresAt?: string; accessTokenTTL: number; @@ -71,9 +67,9 @@ export type ServiceTokenDataV3 = { export type CreateServiceTokenDataV3DTO = { name: string; + role?: string; workspaceId: string; publicKey: string; - scopes: ServiceTokenV3Scope[]; trustedIps: { ipAddress: string; }[]; @@ -93,7 +89,7 @@ export type UpdateServiceTokenDataV3DTO = { serviceTokenDataId: string; isActive?: boolean; name?: string; - scopes?: ServiceTokenV3Scope[]; + role?: string; trustedIps?: { ipAddress: string; }[]; diff --git a/frontend/src/hooks/api/users/types.ts b/frontend/src/hooks/api/users/types.ts index c6b790592..3b57e8953 100644 --- a/frontend/src/hooks/api/users/types.ts +++ b/frontend/src/hooks/api/users/types.ts @@ -14,6 +14,7 @@ export type User = { createdAt: Date; updatedAt: Date; email: string; + superAdmin: boolean; firstName?: string; lastName?: string; authProvider?: AuthMethod; diff --git a/frontend/src/hooks/api/workspace/index.tsx b/frontend/src/hooks/api/workspace/index.tsx index f7b749156..cef88fede 100644 --- a/frontend/src/hooks/api/workspace/index.tsx +++ b/frontend/src/hooks/api/workspace/index.tsx @@ -7,7 +7,6 @@ export { useDeleteWsEnvironment, useGetUserWorkspaceMemberships, useGetUserWorkspaces, - useGetUserWsEnvironments, useGetWorkspaceAuthorizations, useGetWorkspaceById, useGetWorkspaceIndexStatus, diff --git a/frontend/src/hooks/api/workspace/queries.tsx b/frontend/src/hooks/api/workspace/queries.tsx index 5b185b97a..ede931de8 100644 --- a/frontend/src/hooks/api/workspace/queries.tsx +++ b/frontend/src/hooks/api/workspace/queries.tsx @@ -12,14 +12,12 @@ import { CreateWorkspaceDTO, DeleteEnvironmentDTO, DeleteWorkspaceDTO, - GetWsEnvironmentDTO, NameWorkspaceSecretsDTO, RenameWorkspaceDTO, ReorderEnvironmentsDTO, ToggleAutoCapitalizationDTO, UpdateEnvironmentDTO, - Workspace, - WorkspaceEnv + Workspace } from "./types"; export const workspaceKeys = { @@ -31,7 +29,6 @@ export const workspaceKeys = { getWorkspaceAuthorization: (workspaceId: string) => [{ workspaceId }, "workspace-authorizations"], getWorkspaceIntegrations: (workspaceId: string) => [{ workspaceId }, "workspace-integrations"], getAllUserWorkspace: ["workspaces"] as const, - getUserWsEnvironments: (workspaceId: string) => ["workspace-env", { workspaceId }] as const, getWorkspaceAuditLogs: (workspaceId: string) => [{ workspaceId }] as const, getWorkspaceUsers: (workspaceId: string) => [{ workspaceId }] as const, getWorkspaceServiceTokenDataV3: (workspaceId: string) => @@ -103,21 +100,6 @@ const fetchUserWorkspaceMemberships = async (orgId: string) => { return data; }; -const fetchUserWsEnvironments = async (workspaceId: string) => { - const { data } = await apiRequest.get<{ accessibleEnvironments: WorkspaceEnv[] }>( - `/api/v2/workspace/${workspaceId}/environments` - ); - return data.accessibleEnvironments; -}; - -export const useGetUserWsEnvironments = ({ workspaceId, onSuccess }: GetWsEnvironmentDTO) => - useQuery({ - enabled: Boolean(workspaceId), - onSuccess, - queryKey: workspaceKeys.getUserWsEnvironments(workspaceId), - queryFn: () => fetchUserWsEnvironments(workspaceId) - }); - // to get all userids in an org with the workspace they are part of export const useGetUserWorkspaceMemberships = (orgId: string) => useQuery({ diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index f6728988a..bebf5119f 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -30,11 +30,6 @@ export type CreateWorkspaceDTO = { organizationId: string; }; -export type GetWsEnvironmentDTO = { - workspaceId: string; - onSuccess?: (data: WorkspaceEnv[]) => void; -}; - export type RenameWorkspaceDTO = { workspaceID: string; newWorkspaceName: string }; export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean }; diff --git a/frontend/src/layouts/AdminLayout/AdminLayout.tsx b/frontend/src/layouts/AdminLayout/AdminLayout.tsx new file mode 100644 index 000000000..ad156d748 --- /dev/null +++ b/frontend/src/layouts/AdminLayout/AdminLayout.tsx @@ -0,0 +1,306 @@ +/* eslint-disable no-nested-ternary */ +/* eslint-disable no-unexpected-multiline */ +/* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable vars-on-top */ +/* eslint-disable no-var */ +/* eslint-disable func-names */ +// @ts-nocheck + +import { useTranslation } from "react-i18next"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; +import { + faArrowLeft, + faArrowUpRightFromSquare, + faBook, + faEnvelope, + faInfinity, + faInfo, + faMobile, + faPlus, + faQuestion +} from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu"; + +import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@app/components/v2"; +import { useOrganization, useSubscription, useUser } from "@app/context"; +import { + useGetOrgTrialUrl, + useGetUserAction, + useLogoutUser, + useRegisterUserAction +} from "@app/hooks/api"; + +interface LayoutProps { + children: React.ReactNode; +} + +const supportOptions = [ + [ + , + "Support Forum", + "https://infisical.com/slack" + ], + [ + , + "Read Docs", + "https://infisical.com/docs/documentation/getting-started/introduction" + ], + [ + , + "GitHub Issues", + "https://github.com/Infisical/infisical/issues" + ], + [ + , + "Email Support", + "mailto:support@infisical.com" + ] +]; + +export const AdminLayout = ({ children }: LayoutProps) => { + const router = useRouter(); + const { mutateAsync } = useGetOrgTrialUrl(); + + // eslint-disable-next-line prefer-const + const { currentOrg } = useOrganization(); + + const { user } = useUser(); + const { subscription } = useSubscription(); + const { data: updateClosed } = useGetUserAction("september_update_closed"); + const infisicalPlatformVersion = process.env.NEXT_PUBLIC_INFISICAL_PLATFORM_VERSION; + + const { t } = useTranslation(); + + const registerUserAction = useRegisterUserAction(); + + const closeUpdate = async () => { + await registerUserAction.mutateAsync("september_update_closed"); + }; + + const logout = useLogoutUser(); + const logOutUser = async () => { + try { + console.log("Logging out..."); + await logout.mutateAsync(); + router.push("/login"); + } catch (error) { + console.error(error); + } + }; + + return ( + <> +
+
+ +
+ {children} +
+
+
+
+ +

+ {` ${t("common.no-mobile")} `} +

+
+ + ); +}; diff --git a/frontend/src/layouts/AdminLayout/index.tsx b/frontend/src/layouts/AdminLayout/index.tsx new file mode 100644 index 000000000..1a136d62f --- /dev/null +++ b/frontend/src/layouts/AdminLayout/index.tsx @@ -0,0 +1 @@ +export { AdminLayout } from "./AdminLayout"; diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 0757196c9..b70cc442e 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -399,6 +399,13 @@ export const AppLayout = ({ children }: LayoutProps) => { /> + {user?.superAdmin && ( + + + Admin Panel + + + )}
{!isLoading && loginError && } - {!serverDetails?.inviteOnlySignup ? ( + {config.allowSignUp ? (
@@ -236,7 +236,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: ) : (
)} -
+
Forgot password? Recover your account diff --git a/frontend/src/views/Project/MembersPage/MembersPage.tsx b/frontend/src/views/Project/MembersPage/MembersPage.tsx index 4c83e087f..a9b10b607 100644 --- a/frontend/src/views/Project/MembersPage/MembersPage.tsx +++ b/frontend/src/views/Project/MembersPage/MembersPage.tsx @@ -1,42 +1,32 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ -import { useTranslation } from "react-i18next"; import { motion } from "framer-motion"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; -import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context"; +import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { withProjectPermission } from "@app/hoc"; -import { useGetRoles } from "@app/hooks/api"; -import { TRole } from "@app/hooks/api/roles/types"; import { MemberListTab } from "./components/MemberListTab"; import { ProjectRoleListTab } from "./components/ProjectRoleListTab"; +import { ServiceTokenTab } from "./components/ServiceTokenTab"; enum TabSections { Member = "members", - Roles = "roles" + Roles = "roles", + ServiceTokens = "service-tokens" } export const MembersPage = withProjectPermission( () => { - const { t } = useTranslation(); - const { currentWorkspace } = useWorkspace(); - const workspaceId = currentWorkspace?._id || ""; - const orgId = currentWorkspace?.organization || ""; - - const { data: roles, isLoading: isRolesLoading } = useGetRoles({ - orgId, - workspaceId - }); - return (

- {t("settings.members.title")} + Access Control

Members + Service Tokens Roles @@ -47,14 +37,14 @@ export const MembersPage = withProjectPermission( animate={{ opacity: 1, translateX: 0 }} exit={{ opacity: 0, translateX: 30 }} > - []} isRolesLoading={isRolesLoading} /> + + + + - []} - isRolesLoading={isRolesLoading} - /> +
diff --git a/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberListTab.tsx b/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberListTab.tsx index f35b578e1..758970e2d 100644 --- a/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberListTab.tsx +++ b/frontend/src/views/Project/MembersPage/components/MemberListTab/MemberListTab.tsx @@ -46,17 +46,11 @@ import { useAddUserToWs, useDeleteUserFromWorkspace, useGetOrgUsers, + useGetRoles, useGetUserWsKey, useGetWorkspaceUsers, useUpdateUserWorkspaceRole, - useUploadWsKey -} from "@app/hooks/api"; -import { TRole } from "@app/hooks/api/roles/types"; - -type Props = { - roles?: TRole[]; - isRolesLoading?: boolean; -}; + useUploadWsKey} from "@app/hooks/api"; const addMemberFormSchema = z.object({ orgMembershipId: z.string().trim() @@ -64,7 +58,7 @@ const addMemberFormSchema = z.object({ type TAddMemberForm = z.infer; -export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => { +export const MemberListTab = () => { const { createNotification } = useNotificationContext(); const { t } = useTranslation(); @@ -76,6 +70,11 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => { const orgId = currentOrg?._id || ""; const workspaceId = currentWorkspace?._id || ""; + const { data: roles, isLoading: isRolesLoading } = useGetRoles({ + orgId, + workspaceId + }); + const { data: wsKey } = useGetUserWsKey(workspaceId); const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId); const { data: orgUsers } = useGetOrgUsers(orgId); @@ -162,7 +161,7 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => { const findRoleFromId = useCallback( (roleId: string) => { - return roles.find(({ _id: id }) => id === roleId); + return (roles || []).find(({ _id: id }) => id === roleId); }, [roles] ); @@ -307,7 +306,7 @@ export const MemberListTab = ({ roles = [], isRolesLoading }: Props) => { onRoleChange(membershipId, selectedRole) } > - {roles + {(roles || []) .filter(({ slug }) => slug === "owner" ? isIamOwner || role === "owner" : true ) diff --git a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/ProjectRoleListTab.tsx b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/ProjectRoleListTab.tsx index b433ed7e4..d3bd3b39c 100644 --- a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/ProjectRoleListTab.tsx +++ b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/ProjectRoleListTab.tsx @@ -8,13 +8,8 @@ import { TRole } from "@app/hooks/api/roles/types"; import { ProjectRoleList } from "./components/ProjectRoleList"; import { ProjectRoleModifySection } from "./components/ProjectRoleModifySection"; -type Props = { - roles?: TRole[]; - isRolesLoading?: boolean; -}; - export const ProjectRoleListTab = withProjectPermission( - ({ roles = [], isRolesLoading }: Props) => { + () => { const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["editRole"] as const); return popUp.editRole.isOpen ? ( @@ -38,11 +33,7 @@ export const ProjectRoleListTab = withProjectPermission( animate={{ opacity: 1, translateX: 0 }} exit={{ opacity: 0, translateX: -30 }} > - handlePopUpOpen("editRole", role)} - /> + handlePopUpOpen("editRole", role)} /> ); }, diff --git a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx index ce0b42385..cca666ba8 100644 --- a/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx +++ b/frontend/src/views/Project/MembersPage/components/ProjectRoleListTab/components/ProjectRoleList/ProjectRoleList.tsx @@ -25,24 +25,26 @@ import { useWorkspace } from "@app/context"; import { usePopUp } from "@app/hooks"; -import { useDeleteRole } from "@app/hooks/api"; +import { useDeleteRole, useGetRoles } from "@app/hooks/api"; import { TRole } from "@app/hooks/api/roles/types"; type Props = { - isRolesLoading?: boolean; - roles?: TRole[]; onSelectRole: (role?: TRole) => void; }; -export const ProjectRoleList = ({ isRolesLoading, roles = [], onSelectRole }: Props) => { +export const ProjectRoleList = ({ onSelectRole }: Props) => { const [searchRoles, setSearchRoles] = useState(""); + const { createNotification } = useNotificationContext(); + const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const); const { currentOrg } = useOrganization(); const { currentWorkspace } = useWorkspace(); const orgId = currentOrg?._id || ""; const workspaceId = currentWorkspace?._id || ""; - - const { createNotification } = useNotificationContext(); - const { popUp, handlePopUpOpen, handlePopUpClose } = usePopUp(["deleteRole"] as const); + + const { data: roles, isLoading: isRolesLoading } = useGetRoles({ + orgId, + workspaceId + }); const { mutateAsync: deleteRole } = useDeleteRole(); @@ -62,6 +64,8 @@ export const ProjectRoleList = ({ isRolesLoading, roles = [], onSelectRole }: Pr } }; + // roles={roles as TRole[]} + return (
@@ -97,7 +101,7 @@ export const ProjectRoleList = ({ isRolesLoading, roles = [], onSelectRole }: Pr {isRolesLoading && } - {roles?.map((role) => { + {(roles as TRole[])?.map((role) => { const { _id: id, name, slug } = role; const isNonMutatable = ["admin", "member", "viewer"].includes(slug); diff --git a/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/ServiceTokenTab.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/ServiceTokenTab.tsx new file mode 100644 index 000000000..86ae8bfe5 --- /dev/null +++ b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/ServiceTokenTab.tsx @@ -0,0 +1,21 @@ +import { motion } from "framer-motion"; + +import { + ServiceTokenSection, + // ServiceTokenV3Section +} from "./components"; + +export const ServiceTokenTab = () => { + return ( + + {/* */} + + + ); +} diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/AddServiceTokenModal.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx similarity index 100% rename from frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/AddServiceTokenModal.tsx rename to frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/AddServiceTokenModal.tsx diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx similarity index 100% rename from frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenSection.tsx rename to frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenSection.tsx diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenTable.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenTable.tsx similarity index 100% rename from frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/ServiceTokenTable.tsx rename to frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/ServiceTokenTable.tsx diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/index.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/index.tsx similarity index 100% rename from frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenSection/index.tsx rename to frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenSection/index.tsx diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx similarity index 70% rename from frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx rename to frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx index 3d76b2435..cfe5937d7 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx +++ b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx @@ -29,22 +29,17 @@ import { Tabs, UpgradePlanModal} from "@app/components/v2"; import { + useOrganization, useSubscription, - useWorkspace + useWorkspace } from "@app/context"; import { useToggle } from "@app/hooks"; import { useCreateServiceTokenV3, + useGetRoles, useGetUserWsKey, - useUpdateServiceTokenV3 -} from "@app/hooks/api"; -import { - Permission -} from "@app/hooks/api/serviceTokens/enums"; -import { - ServiceTokenV3Scope, - ServiceTokenV3TrustedIp -} from "@app/hooks/api/serviceTokens/types"; + useUpdateServiceTokenV3} from "@app/hooks/api"; +import { ServiceTokenV3TrustedIp } from "@app/hooks/api/serviceTokens/types"; import { UsePopUpState } from "@app/hooks/usePopUp"; enum TabSections { @@ -61,13 +56,6 @@ const expirations = [ { label: "12 months", value: "31104000" } ]; -const permissionsMap: { - [key: string]: Permission[] -} = { - "read": [Permission.READ], - "readWrite": [Permission.READ, Permission.WRITE], -} - const schema = yup.object({ name: yup.string().required("ST V3 name is required"), expiresIn: yup.string(), @@ -82,24 +70,7 @@ const schema = yup.object({ return !Number.isNaN(num) && num > 0 && String(num) === value; }) .required("Access Token TTL is required"), - scopes: yup - .array( - yup.object({ - permission: yup.string().oneOf(Object.keys(permissionsMap), "Invalid permission").required().label("Permission"), - environment: yup.string().max(50).required().label("Environment"), - secretPath: yup - .string() - .required() - .default("/") - .label("Secret Path") - .transform((val) => - typeof val === "string" && val.at(-1) === "/" && val.length > 1 ? val.slice(0, -1) : val - ) - }) - ) - .min(1) - .required() - .label("Scope"), + role: yup.string().required("ST V3 role is required"), trustedIps: yup .array( yup.object({ @@ -129,9 +100,18 @@ export const AddServiceTokenV3Modal = ({ const [isServiceTokenJSONCopied, setIsServiceTokenJSONCopied] = useToggle(false); const { subscription } = useSubscription(); + const { currentOrg } = useOrganization(); const { currentWorkspace } = useWorkspace(); - const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? ""); + const orgId = currentOrg?._id || ""; + const workspaceId = currentWorkspace?._id || ""; + + const { data: roles } = useGetRoles({ + orgId, + workspaceId + }); + + const { data: latestFileKey } = useGetUserWsKey(workspaceId); const { mutateAsync: createMutateAsync } = useCreateServiceTokenV3(); const { mutateAsync: updateMutateAsync } = useUpdateServiceTokenV3(); const { createNotification } = useNotificationContext(); @@ -145,11 +125,6 @@ export const AddServiceTokenV3Modal = ({ defaultValues: { name: "", accessTokenTTL: "7200", - scopes: [{ - permission: "read", - environment: currentWorkspace?.environments?.[0]?.slug, - secretPath: "/", - }], trustedIps: [{ ipAddress: "0.0.0.0/0" }] @@ -175,31 +150,22 @@ export const AddServiceTokenV3Modal = ({ const serviceTokenData = popUp?.serviceTokenV3?.data as { serviceTokenDataId: string; name: string; - scopes: ServiceTokenV3Scope[]; + role: string; + customRole: { + name: string; + slug: string; + }; trustedIps: ServiceTokenV3TrustedIp[]; accessTokenTTL: number; isRefreshTokenRotationEnabled: boolean; }; + if (!roles?.length) return; + if (serviceTokenData) { reset({ name: serviceTokenData.name, - scopes: serviceTokenData.scopes.map(({ - environment, - secretPath, - permissions - }: ServiceTokenV3Scope) => { - let permission = "read"; - if (permissions.includes(Permission.WRITE)) { - permission = "readWrite"; - } - - return ({ - environment, - secretPath, - permission - }) - }), + role: serviceTokenData?.customRole?.slug ?? serviceTokenData.role, trustedIps: serviceTokenData.trustedIps.map(({ ipAddress, prefix @@ -215,26 +181,21 @@ export const AddServiceTokenV3Modal = ({ reset({ name: "", accessTokenTTL: "7200", - scopes: [{ - permission: "read", - environment: currentWorkspace?.environments?.[0]?.slug, - secretPath: "/", - }], + role: roles[0].slug, trustedIps: [{ ipAddress: "0.0.0.0/0" }] }); } - }, [popUp?.serviceTokenV3?.data]); + }, [popUp?.serviceTokenV3?.data, roles]); - const { fields: tokenScopes, append, remove } = useFieldArray({ control, name: "scopes" }); const { fields: tokenTrustedIps, append: appendTrustedIp, remove: removeTrustedIp } = useFieldArray({ control, name: "trustedIps" }); const onFormSubmit = async ({ name, expiresIn, accessTokenTTL, - scopes, + role, trustedIps, isRefreshTokenRotationEnabled }: FormData) => { @@ -242,25 +203,16 @@ export const AddServiceTokenV3Modal = ({ const serviceTokenData = popUp?.serviceTokenV3?.data as { serviceTokenDataId: string; name: string; - scopes: any; + role: string; }; - // convert read/readWrite permission => ["read", "write"] format - const reformattedScopes = scopes.map((scope) => { - return ({ - environment: scope.environment, - secretPath: scope.secretPath, - permissions: permissionsMap[scope.permission] - }); - }); - if (serviceTokenData) { // update await updateMutateAsync({ serviceTokenDataId: serviceTokenData.serviceTokenDataId, name, - scopes: reformattedScopes, + role, trustedIps, expiresIn: expiresIn === "" ? undefined : Number(expiresIn), accessTokenTTL: Number(accessTokenTTL), @@ -270,7 +222,7 @@ export const AddServiceTokenV3Modal = ({ handlePopUpToggle("serviceTokenV3", false); } else { // create - if (!currentWorkspace?._id) return; + if (!workspaceId) return; if (!latestFileKey) return; const pair = nacl.box.keyPair(); @@ -294,9 +246,9 @@ export const AddServiceTokenV3Modal = ({ const { refreshToken } = await createMutateAsync({ name, - workspaceId: currentWorkspace._id, + role, + workspaceId, publicKey, - scopes: reformattedScopes, trustedIps, expiresIn: expiresIn === "" ? undefined : Number(expiresIn), accessTokenTTL: Number(accessTokenTTL), @@ -385,102 +337,32 @@ export const AddServiceTokenV3Modal = ({ )} /> - {tokenScopes.map(({ id }, index) => ( -
- ( - - - - )} - /> - ( - - onChange(e)} + className="w-full" + > + {(roles || []).map(({ name, slug }) => ( + {name} - ))} - - - )} - /> - ( - - - - )} - /> - remove(index)} - size="lg" - colorSchema="danger" - variant="plain" - ariaLabel="update" - className="p-3" - > - - -
- ))} -
- -
+ ))} + + + )} + /> Name Status - Scopes + Role Trusted IPs Access Token TTL Created At @@ -98,7 +101,8 @@ export const ServiceTokenV3Table = ({ _id, name, isActive, - scopes, + role, + customRole, trustedIps, createdAt, expiresAt, @@ -128,26 +132,7 @@ export const ServiceTokenV3Table = ({ )} - - {scopes.map((scope) => { - let permissionText = "read" - if ( - scope.permissions.includes(Permission.WRITE) && - scope.permissions.includes(Permission.READ) - ) { - permissionText = "readWrite"; - } - - return ( -

- - {permissionText} - - {` @${scope.environment} - ${scope.secretPath}`} -

- ); - })} - + {customRole?.slug ?? role} {trustedIps.map(({ _id: trustedIpId, @@ -175,7 +160,8 @@ export const ServiceTokenV3Table = ({ handlePopUpOpen("serviceTokenV3", { serviceTokenDataId: _id, name, - scopes, + role, + customRole, trustedIps, accessTokenTTL, isRefreshTokenRotationEnabled diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/index.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenV3Section/index.tsx similarity index 100% rename from frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/index.tsx rename to frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/ServiceTokenV3Section/index.tsx diff --git a/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/index.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/index.tsx new file mode 100644 index 000000000..18a8762cd --- /dev/null +++ b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/components/index.tsx @@ -0,0 +1,2 @@ +export { ServiceTokenSection } from "./ServiceTokenSection"; +export { ServiceTokenV3Section } from "./ServiceTokenV3Section"; \ No newline at end of file diff --git a/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/index.tsx b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/index.tsx new file mode 100644 index 000000000..762c29bde --- /dev/null +++ b/frontend/src/views/Project/MembersPage/components/ServiceTokenTab/index.tsx @@ -0,0 +1 @@ +export { ServiceTokenTab } from "./ServiceTokenTab"; \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx index 8aa06594b..e5d62fe65 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx @@ -3,12 +3,10 @@ import { useTranslation } from "react-i18next"; import { Tab } from "@headlessui/react"; import { ProjectGeneralTab } from "./components/ProjectGeneralTab"; -import { ProjectServiceTokensTab } from "./components/ProjectServiceTokensTab"; import { WebhooksTab } from "./components/WebhooksTab"; const tabs = [ { name: "General", key: "tab-project-general" }, - { name: "Service Tokens", key: "tab-project-service-tokens" }, { name: "Webhooks", key: "tab-project-webhooks" } ]; @@ -41,9 +39,6 @@ export const ProjectSettingsPage = () => { - - - diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectServiceTokensTab/ProjectServiceTokensTab.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectServiceTokensTab/ProjectServiceTokensTab.tsx deleted file mode 100644 index d4f8e79e7..000000000 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectServiceTokensTab/ProjectServiceTokensTab.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { ServiceTokenSection } from "../ServiceTokenSection"; -// import { ServiceTokenV3Section } from "../ServiceTokenV3Section"; - -export const ProjectServiceTokensTab = () => { - return ( - <> - {/* */} - - - ); -} \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectServiceTokensTab/index.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectServiceTokensTab/index.tsx deleted file mode 100644 index 0f1f58e25..000000000 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectServiceTokensTab/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { ProjectServiceTokensTab } from "./ProjectServiceTokensTab"; \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx index 757dfc6b2..4a5bc4982 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx @@ -3,5 +3,4 @@ export { DeleteProjectSection } from "./DeleteProjectSection"; export { E2EESection } from "./E2EESection"; export { EnvironmentSection } from "./EnvironmentSection"; export { ProjectNameChangeSection } from "./ProjectNameChangeSection"; -export { SecretTagsSection } from "./SecretTagsSection"; -export { ServiceTokenSection } from "./ServiceTokenSection"; +export { SecretTagsSection } from "./SecretTagsSection"; \ No newline at end of file diff --git a/frontend/src/views/admin/DashboardPage/DashboardPage.tsx b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx new file mode 100644 index 000000000..6c7e18740 --- /dev/null +++ b/frontend/src/views/admin/DashboardPage/DashboardPage.tsx @@ -0,0 +1,64 @@ +import { useEffect } from "react"; +import { useRouter } from "next/router"; + +import { ContentLoader, Switch, Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { useOrganization, useServerConfig, useUser } from "@app/context"; +import { useUpdateServerConfig } from "@app/hooks/api"; + +enum TabSections { + Settings = "settings" +} + +export const AdminDashboardPage = () => { + const router = useRouter(); + const data = useServerConfig(); + const { config } = data; + const { user, isLoading: isUserLoading } = useUser(); + const { orgs } = useOrganization(); + const { mutate: updateServerConfig } = useUpdateServerConfig(); + + const isNotAllowed = !user?.superAdmin; + + useEffect(() => { + if (isNotAllowed && !isUserLoading) { + if (orgs?.length) { + localStorage.setItem("orgData.id", orgs?.[0]?._id); + router.push(`/org/${orgs?.[0]?._id}/overview`); + } + } + }, [isNotAllowed, isUserLoading]); + + return ( +
+
+
+

Admin Dashboard

+

Manage your Infisical

+
+
+ {isUserLoading || isNotAllowed ? ( + + ) : ( +
+ + +
+ General +
+
+ +
+ updateServerConfig({ allowSignUp: isChecked })} + /> +
Enable signup or invite
+
+
+
+
+ )} +
+ ); +}; diff --git a/frontend/src/views/admin/DashboardPage/index.tsx b/frontend/src/views/admin/DashboardPage/index.tsx new file mode 100644 index 000000000..1d5f062e6 --- /dev/null +++ b/frontend/src/views/admin/DashboardPage/index.tsx @@ -0,0 +1 @@ +export { AdminDashboardPage } from "./DashboardPage"; diff --git a/frontend/src/views/admin/SignUpPage/SignUpPage.tsx b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx new file mode 100644 index 000000000..d3c83fc17 --- /dev/null +++ b/frontend/src/views/admin/SignUpPage/SignUpPage.tsx @@ -0,0 +1,223 @@ +import { useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { useRouter } from "next/router"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { AnimatePresence, motion } from "framer-motion"; +import { z } from "zod"; + +import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider"; +import { generateBackupPDFAsync } from "@app/components/utilities/generateBackupPDF"; +// TODO(akhilmhdh): rewrite this into module functions in lib +import { saveTokenToLocalStorage } from "@app/components/utilities/saveTokenToLocalStorage"; +import SecurityClient from "@app/components/utilities/SecurityClient"; +import { Button, ContentLoader, FormControl, Input } from "@app/components/v2"; +import { useServerConfig } from "@app/context"; +import { useCreateAdminUser } from "@app/hooks/api"; +import { generateUserBackupKey, generateUserPassKey } from "@app/lib/crypto"; +import { isLoggedIn } from "@app/reactQuery"; + +import { DownloadBackupKeys } from "./components/DownloadBackupKeys"; + +const formSchema = z + .object({ + email: z.string().email().trim(), + firstName: z.string().trim(), + lastName: z.string().trim().optional(), + password: z.string().trim().min(14).max(100), + confirmPassword: z.string().trim() + }) + .refine((data) => data.password === data.confirmPassword, { + message: "Password don't match", + path: ["confirmPassword"] + }); + +type TFormSchema = z.infer; + +enum SignupSteps { + DetailsForm = "details-form", + BackupKey = "backup-key" +} + +export const SignUpPage = () => { + const router = useRouter(); + const { + control, + handleSubmit, + getValues, + formState: { isSubmitting } + } = useForm({ + resolver: zodResolver(formSchema) + }); + const { createNotification } = useNotificationContext(); + const [step, setStep] = useState(SignupSteps.DetailsForm); + + const { config } = useServerConfig(); + + useEffect(() => { + if (config?.initialized) { + if (isLoggedIn()) { + router.push("/admin"); + } else { + router.push("/login"); + } + } + }, []); + + const { mutateAsync: createAdminUser } = useCreateAdminUser(); + + const handleFormSubmit = async ({ email, password, firstName, lastName }: TFormSchema) => { + // avoid multi submission + if (isSubmitting) return; + try { + const { privateKey, ...userPass } = await generateUserPassKey(email, password); + const res = await createAdminUser({ + email, + firstName, + lastName, + ...userPass + }); + SecurityClient.setToken(res.token); + saveTokenToLocalStorage({ + publicKey: userPass.publicKey, + encryptedPrivateKey: userPass.encryptedPrivateKey, + iv: userPass.encryptedPrivateKeyIV, + tag: userPass.encryptedPrivateKeyTag, + privateKey + }); + setStep(SignupSteps.BackupKey); + } catch (err) { + console.log(err); + createNotification({ + type: "error", + text: "Faield to create admin" + }); + } + }; + + const handleBackupKeyGenerate = async () => { + try { + const { email, password, firstName, lastName } = getValues(); + const generatedKey = await generateUserBackupKey(email, password); + await generateBackupPDFAsync({ + generatedKey, + personalEmail: email, + personalName: `${firstName} ${lastName}` + }); + router.push("/admin"); + } catch (err) { + console.log(err); + createNotification({ + type: "error", + text: "Faield to generate backup" + }); + } + }; + + if (config?.initialized && step === SignupSteps.DetailsForm) + return ; + + return ( +
+ + {step === SignupSteps.DetailsForm && ( + +
+ Infisical logo +
Welcome to Infisical
+
Create your first Admin Account
+
+
+
+
+ ( + + + + )} + /> + ( + + + + )} + /> +
+ ( + + + + )} + /> + ( + + + + )} + /> + ( + + + + )} + /> +
+ +
+
+ )} + {step === SignupSteps.BackupKey && ( + + + + )} +
+
+ ); +}; diff --git a/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx b/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx new file mode 100644 index 000000000..d431aae44 --- /dev/null +++ b/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/DownloadBackupKeys.tsx @@ -0,0 +1,53 @@ +import { useTranslation } from "react-i18next"; +import { faWarning } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import { Button } from "@app/components/v2"; +import { useToggle } from "@app/hooks"; + +type Props = { + onGenerate: () => Promise; +}; + +export const DownloadBackupKeys = ({ onGenerate }: Props): JSX.Element => { + const { t } = useTranslation(); + const [isLoading, setIsLoading] = useToggle(); + + return ( +
+

+ + {t("signup.step4-message")} +

+
+
+ + {t("signup.step4-description1")} {t("signup.step4-description3")} + +
+
+
+ +
+
+
+
+ ); +}; diff --git a/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/index.tsx b/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/index.tsx new file mode 100644 index 000000000..bbbd9aad9 --- /dev/null +++ b/frontend/src/views/admin/SignUpPage/components/DownloadBackupKeys/index.tsx @@ -0,0 +1 @@ +export { DownloadBackupKeys } from "./DownloadBackupKeys"; diff --git a/frontend/src/views/admin/SignUpPage/index.tsx b/frontend/src/views/admin/SignUpPage/index.tsx new file mode 100644 index 000000000..aed0b2d78 --- /dev/null +++ b/frontend/src/views/admin/SignUpPage/index.tsx @@ -0,0 +1 @@ +export { SignUpPage } from "./SignUpPage";