Resolve merge conflicts

This commit is contained in:
Tuan Dang
2023-03-28 14:43:56 +07:00
43 changed files with 1088 additions and 325 deletions
+1 -1
View File
@@ -25,7 +25,7 @@
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" /> <img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
</a> </a>
<a href="https://cloudsmith.io/~infisical/repos/"> <a href="https://cloudsmith.io/~infisical/repos/">
<img src="https://img.shields.io/badge/Downloads-45.7k-orange" alt="Cloudsmith downloads" /> <img src="https://img.shields.io/badge/Downloads-55.7k-orange" alt="Cloudsmith downloads" />
</a> </a>
<a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g"> <a href="https://join.slack.com/t/infisical-users/shared_invite/zt-1kdbk07ro-RtoyEt_9E~fyzGo_xQYP6g">
<img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" /> <img src="https://img.shields.io/badge/chat-on%20Slack-blueviolet" alt="Slack community channel" />
+9
View File
@@ -0,0 +1,9 @@
export default {
preset: 'ts-jest',
testEnvironment: 'node',
collectCoverageFrom: ['src/*.{js,ts}', '!**/node_modules/**'],
modulePaths: ['<rootDir>/src'],
testMatch: ['<rootDir>/tests/**/*.test.ts'],
setupFiles: ['<rootDir>/test-resources/env-vars.js'],
setupFilesAfterEnv: ['<rootDir>/tests/setupTests.ts']
};
+84 -27
View File
@@ -12,7 +12,7 @@
"@aws-sdk/client-secrets-manager": "^3.267.0", "@aws-sdk/client-secrets-manager": "^3.267.0",
"@godaddy/terminus": "^4.11.2", "@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0", "@sentry/node": "^7.39.0",
"@sentry/tracing": "^7.19.0", "@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
@@ -40,13 +40,13 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.7.2", "mongoose": "^6.7.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.2.2", "posthog-node": "^2.5.4",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"request-ip": "^3.3.0", "request-ip": "^3.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"swagger-autogen": "^2.22.0", "swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.0", "swagger-ui-express": "^4.6.2",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3", "typescript": "^4.9.3",
@@ -2800,13 +2800,13 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
}, },
"node_modules/@sentry/node": { "node_modules/@sentry/node": {
"version": "7.38.0", "version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.38.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.39.0.tgz",
"integrity": "sha512-jNIN6NZvgzn/oms8RQzffjX8Z0LQDTN6N28nnhzqGCvnfmS1QtTt0FlU+pTuFXZNNSjfGy4XMXMYvLlbvhm2bg==", "integrity": "sha512-oe1OBxgs6t/FizjxkSPtuvJv5wJMO+mLENZkiE0PpBD56JyZrWK48kYIt2ccWAfk6Vh235/oIpmqET150xB4lQ==",
"dependencies": { "dependencies": {
"@sentry/core": "7.38.0", "@sentry/core": "7.39.0",
"@sentry/types": "7.38.0", "@sentry/types": "7.39.0",
"@sentry/utils": "7.38.0", "@sentry/utils": "7.39.0",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3", "lru_map": "^0.3.3",
@@ -2816,6 +2816,39 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/@sentry/node/node_modules/@sentry/core": {
"version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.39.0.tgz",
"integrity": "sha512-45WJIcWWCQnZ8zhHtcrkJjQ4YydmzMWY4pmRuBG7Qp+zrCT6ISoyODcjY+SCHFdvXkiYFi8+bFZa1qG3YQnnYw==",
"dependencies": {
"@sentry/types": "7.39.0",
"@sentry/utils": "7.39.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/@sentry/types": {
"version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.39.0.tgz",
"integrity": "sha512-5Y83Y8O3dT5zT2jTKEIPMcpn5lUm05KRMaCXuw0sRsv4r9TbBUKeqiSU1LjowT8rB/XNy8m7DHav8+NmogPaJw==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/@sentry/utils": {
"version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.39.0.tgz",
"integrity": "sha512-/ZxlPgm1mGgmuMckCTc9iyqDuFTEYNEoMB53IjVFz8ann+37OiWB7Py/QV1rEEsv3xKrGbA8thhRhV9E1sjTlQ==",
"dependencies": {
"@sentry/types": "7.39.0",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/node/node_modules/tslib": { "node_modules/@sentry/node/node_modules/tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -10500,9 +10533,9 @@
} }
}, },
"node_modules/posthog-node": { "node_modules/posthog-node": {
"version": "2.5.3", "version": "2.5.4",
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.3.tgz", "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.4.tgz",
"integrity": "sha512-kDmBjQHguPrh/rUTKmB0+Hj7C3fq2t+/fcfQkDBGz0f0fEF2WxV5yyxRmd2IF/hFmHxMrGLDkEVjKr78B+judg==", "integrity": "sha512-CdywlVh0CZU05/3MrBc0qY/zsLdU2X9XSz/yL1qMRhbyZhD8lrnuGlI69G2cpzZtli6S/nu64wcmULz/mFFA5w==",
"dependencies": { "dependencies": {
"axios": "^0.27.0" "axios": "^0.27.0"
}, },
@@ -11483,9 +11516,9 @@
"integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w==" "integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w=="
}, },
"node_modules/swagger-ui-express": { "node_modules/swagger-ui-express": {
"version": "4.6.1", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.1.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz",
"integrity": "sha512-Pss7YNFKNdq66XKNjRe4IRXKKYNx/LvOSml9TdrZ8/78UpxUHIp9JoXpXWA5Z4L+SCmX63DZ9IPlQ8nnRuncvA==", "integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==",
"dependencies": { "dependencies": {
"swagger-ui-dist": ">=4.11.0" "swagger-ui-dist": ">=4.11.0"
}, },
@@ -14361,19 +14394,43 @@
} }
}, },
"@sentry/node": { "@sentry/node": {
"version": "7.38.0", "version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.38.0.tgz", "resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.39.0.tgz",
"integrity": "sha512-jNIN6NZvgzn/oms8RQzffjX8Z0LQDTN6N28nnhzqGCvnfmS1QtTt0FlU+pTuFXZNNSjfGy4XMXMYvLlbvhm2bg==", "integrity": "sha512-oe1OBxgs6t/FizjxkSPtuvJv5wJMO+mLENZkiE0PpBD56JyZrWK48kYIt2ccWAfk6Vh235/oIpmqET150xB4lQ==",
"requires": { "requires": {
"@sentry/core": "7.38.0", "@sentry/core": "7.39.0",
"@sentry/types": "7.38.0", "@sentry/types": "7.39.0",
"@sentry/utils": "7.38.0", "@sentry/utils": "7.39.0",
"cookie": "^0.4.1", "cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3", "lru_map": "^0.3.3",
"tslib": "^1.9.3" "tslib": "^1.9.3"
}, },
"dependencies": { "dependencies": {
"@sentry/core": {
"version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.39.0.tgz",
"integrity": "sha512-45WJIcWWCQnZ8zhHtcrkJjQ4YydmzMWY4pmRuBG7Qp+zrCT6ISoyODcjY+SCHFdvXkiYFi8+bFZa1qG3YQnnYw==",
"requires": {
"@sentry/types": "7.39.0",
"@sentry/utils": "7.39.0",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.39.0.tgz",
"integrity": "sha512-5Y83Y8O3dT5zT2jTKEIPMcpn5lUm05KRMaCXuw0sRsv4r9TbBUKeqiSU1LjowT8rB/XNy8m7DHav8+NmogPaJw=="
},
"@sentry/utils": {
"version": "7.39.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.39.0.tgz",
"integrity": "sha512-/ZxlPgm1mGgmuMckCTc9iyqDuFTEYNEoMB53IjVFz8ann+37OiWB7Py/QV1rEEsv3xKrGbA8thhRhV9E1sjTlQ==",
"requires": {
"@sentry/types": "7.39.0",
"tslib": "^1.9.3"
}
},
"tslib": { "tslib": {
"version": "1.14.1", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
@@ -20049,9 +20106,9 @@
} }
}, },
"posthog-node": { "posthog-node": {
"version": "2.5.3", "version": "2.5.4",
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.3.tgz", "resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.4.tgz",
"integrity": "sha512-kDmBjQHguPrh/rUTKmB0+Hj7C3fq2t+/fcfQkDBGz0f0fEF2WxV5yyxRmd2IF/hFmHxMrGLDkEVjKr78B+judg==", "integrity": "sha512-CdywlVh0CZU05/3MrBc0qY/zsLdU2X9XSz/yL1qMRhbyZhD8lrnuGlI69G2cpzZtli6S/nu64wcmULz/mFFA5w==",
"requires": { "requires": {
"axios": "^0.27.0" "axios": "^0.27.0"
}, },
@@ -20779,9 +20836,9 @@
"integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w==" "integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w=="
}, },
"swagger-ui-express": { "swagger-ui-express": {
"version": "4.6.1", "version": "4.6.2",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.1.tgz", "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz",
"integrity": "sha512-Pss7YNFKNdq66XKNjRe4IRXKKYNx/LvOSml9TdrZ8/78UpxUHIp9JoXpXWA5Z4L+SCmX63DZ9IPlQ8nnRuncvA==", "integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==",
"requires": { "requires": {
"swagger-ui-dist": ">=4.11.0" "swagger-ui-dist": ">=4.11.0"
} }
+3 -14
View File
@@ -3,7 +3,7 @@
"@aws-sdk/client-secrets-manager": "^3.267.0", "@aws-sdk/client-secrets-manager": "^3.267.0",
"@godaddy/terminus": "^4.11.2", "@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5", "@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0", "@sentry/node": "^7.39.0",
"@sentry/tracing": "^7.19.0", "@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10", "@types/libsodium-wrappers": "^0.7.10",
@@ -31,13 +31,13 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.7.2", "mongoose": "^6.7.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.2.2", "posthog-node": "^2.5.4",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"request-ip": "^3.3.0", "request-ip": "^3.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"swagger-autogen": "^2.22.0", "swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.0", "swagger-ui-express": "^4.6.2",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3", "typescript": "^4.9.3",
@@ -101,17 +101,6 @@
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"ts-node": "^10.9.1" "ts-node": "^10.9.1"
}, },
"jest": {
"preset": "ts-jest",
"testEnvironment": "node",
"collectCoverageFrom": [
"src/*.{js,ts}",
"!**/node_modules/**"
],
"setupFiles": [
"<rootDir>/test-resources/env-vars.js"
]
},
"jest-junit": { "jest-junit": {
"outputDirectory": "reports", "outputDirectory": "reports",
"outputName": "jest-junit.xml", "outputName": "jest-junit.xml",
+6 -5
View File
@@ -5,12 +5,12 @@ export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!;
export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10; export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10;
export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d'; export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d';
export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!; export const getJwtAuthSecret = () => infisical.get('JWT_AUTH_SECRET')!;
export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')!; export const getJwtMfaLifetime = () => infisical.get('JWT_MFA_LIFETIME')! || '5m';
export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m'; export const getJwtMfaSecret = () => infisical.get('JWT_MFA_LIFETIME')! || '5m';
export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d'; export const getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d';
export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!; export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_SECRET')!;
export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!; export const getJwtServiceSecret = () => infisical.get('JWT_SERVICE_SECRET')!;
export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')!; export const getJwtSignupLifetime = () => infisical.get('JWT_SIGNUP_LIFETIME')! || '15m';
export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!; export const getJwtSignupSecret = () => infisical.get('JWT_SIGNUP_SECRET')!;
export const getMongoURL = () => infisical.get('MONGO_URL')!; export const getMongoURL = () => infisical.get('MONGO_URL')!;
export const getNodeEnv = () => infisical.get('NODE_ENV')!; export const getNodeEnv = () => infisical.get('NODE_ENV')!;
@@ -34,12 +34,12 @@ export const getPostHogProjectApiKey = () => infisical.get('POSTHOG_PROJECT_API_
export const getSentryDSN = () => infisical.get('SENTRY_DSN')!; export const getSentryDSN = () => infisical.get('SENTRY_DSN')!;
export const getSiteURL = () => infisical.get('SITE_URL')!; export const getSiteURL = () => infisical.get('SITE_URL')!;
export const getSmtpHost = () => infisical.get('SMTP_HOST')!; export const getSmtpHost = () => infisical.get('SMTP_HOST')!;
export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false; export const getSmtpSecure = () => infisical.get('SMTP_SECURE')! === 'true' || false;
export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587; export const getSmtpPort = () => parseInt(infisical.get('SMTP_PORT')!) || 587;
export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!; export const getSmtpUsername = () => infisical.get('SMTP_USERNAME')!;
export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!; export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!;
export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!; export const getSmtpFromAddress = () => infisical.get('SMTP_FROM_ADDRESS')!;
export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical'; export const getSmtpFromName = () => infisical.get('SMTP_FROM_NAME')! || 'Infisical';
export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!; export const getStripeProductStarter = () => infisical.get('STRIPE_PRODUCT_STARTER')!;
export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!; export const getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!;
export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!; export const getStripeProductTeam = () => infisical.get('STRIPE_PRODUCT_TEAM')!;
@@ -47,4 +47,5 @@ export const getStripePublishableKey = () => infisical.get('STRIPE_PUBLISHABLE_K
export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!; export const getStripeSecretKey = () => infisical.get('STRIPE_SECRET_KEY')!;
export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!; export const getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!;
export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true; export const getTelemetryEnabled = () => infisical.get('TELEMETRY_ENABLED')! !== 'false' && true;
export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!; export const getLoopsApiKey = () => infisical.get('LOOPS_API_KEY')!;
export const getSmtpConfigured = () => infisical.get('SMTP_HOST') == '' || infisical.get('SMTP_HOST') == undefined ? false : true
@@ -7,7 +7,7 @@ import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
import { sendMail } from '../../helpers/nodemailer'; import { sendMail } from '../../helpers/nodemailer';
import { TokenService } from '../../services'; import { TokenService } from '../../services';
import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables'; import { OWNER, ADMIN, MEMBER, ACCEPTED, INVITED, TOKEN_EMAIL_ORG_INVITATION } from '../../variables';
import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; import { getSiteURL, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
/** /**
* Delete organization membership with id [membershipOrgId] from organization * Delete organization membership with id [membershipOrgId] from organization
@@ -99,9 +99,11 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
* @returns * @returns
*/ */
export const inviteUserToOrganization = async (req: Request, res: Response) => { export const inviteUserToOrganization = async (req: Request, res: Response) => {
let invitee, inviteeMembershipOrg; let invitee, inviteeMembershipOrg, completeInviteLink;
try { try {
const { organizationId, inviteeEmail } = req.body; const { organizationId, inviteeEmail } = req.body;
const host = req.headers.host;
const siteUrl = `${req.protocol}://${host}`;
// validate membership // validate membership
const membershipOrg = await MembershipOrg.findOne({ const membershipOrg = await MembershipOrg.findOne({
@@ -181,6 +183,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
callback_url: getSiteURL() + '/signupinvite' callback_url: getSiteURL() + '/signupinvite'
} }
}); });
if (!getSmtpConfigured()) {
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
}
} }
await updateSubscriptionOrgQuantity({ organizationId }); await updateSubscriptionOrgQuantity({ organizationId });
@@ -193,7 +199,8 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
} }
return res.status(200).send({ return res.status(200).send({
message: `Sent an invite link to ${req.body.inviteeEmail}` message: `Sent an invite link to ${req.body.inviteeEmail}`,
completeInviteLink
}); });
}; };
@@ -218,7 +225,7 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
if (!membershipOrg) if (!membershipOrg)
throw new Error('Failed to find any invitations for email'); throw new Error('Failed to find any invitations for email');
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_ORG_INVITATION, type: TOKEN_EMAIL_ORG_INVITATION,
email, email,
@@ -7,7 +7,7 @@ import {
} from '../../helpers/signup'; } from '../../helpers/signup';
import { createToken } from '../../helpers/auth'; import { createToken } from '../../helpers/auth';
import { BadRequestError } from '../../utils/errors'; import { BadRequestError } from '../../utils/errors';
import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret } from '../../config'; import { getInviteOnlySignup, getJwtSignupLifetime, getJwtSignupSecret, getSmtpConfigured } from '../../config';
/** /**
* Signup step 1: Initialize account for user under email [email] and send a verification code * Signup step 1: Initialize account for user under email [email] and send a verification code
@@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
try { try {
email = req.body.email; email = req.body.email;
if (getInviteOnlySignup() || false) { if (getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account // 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({}) const userCount = await User.countDocuments({})
if (userCount != 0) { if (userCount != 0) {
@@ -66,7 +66,7 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
const { email, code } = req.body; const { email, code } = req.body;
// initialize user account // initialize user account
user = await User.findOne({ email }); user = await User.findOne({ email }).select('+publicKey');
if (user && user?.publicKey) { if (user && user?.publicKey) {
// case: user has already completed account // case: user has already completed account
return res.status(403).send({ return res.status(403).send({
@@ -75,10 +75,12 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
} }
// verify email // verify email
await checkEmailVerification({ if (getSmtpConfigured()) {
email, await checkEmailVerification({
code email,
}); code
});
}
if (!user) { if (!user) {
user = await new User({ user = await new User({
+87 -75
View File
@@ -87,7 +87,7 @@ export const login1 = async (req: Request, res: Response) => {
*/ */
export const login2 = async (req: Request, res: Response) => { export const login2 = async (req: Request, res: Response) => {
try { try {
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' }); if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
const { email, clientProof } = req.body; const { email, clientProof } = req.body;
@@ -127,12 +127,12 @@ export const login2 = async (req: Request, res: Response) => {
expiresIn: getJwtMfaLifetime(), expiresIn: getJwtMfaLifetime(),
secret: getJwtMfaSecret() secret: getJwtMfaSecret()
}); });
const code = await TokenService.createToken({ const code = await TokenService.createToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: 'emailMfa.handlebars',
@@ -142,13 +142,13 @@ export const login2 = async (req: Request, res: Response) => {
code code
} }
}); });
return res.status(200).send({ return res.status(200).send({
mfaEnabled: true, mfaEnabled: true,
token token
}); });
} }
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.ip,
@@ -181,7 +181,7 @@ export const login2 = async (req: Request, res: Response) => {
iv?: string; iv?: string;
tag?: string; tag?: string;
} }
const response: ResponseData = { const response: ResponseData = {
mfaEnabled: false, mfaEnabled: false,
encryptionVersion: user.encryptionVersion, encryptionVersion: user.encryptionVersion,
@@ -191,7 +191,7 @@ export const login2 = async (req: Request, res: Response) => {
iv: user.iv, iv: user.iv,
tag: user.tag tag: user.tag
} }
if ( if (
user?.protectedKey && user?.protectedKey &&
user?.protectedKeyIV && user?.protectedKeyIV &&
@@ -206,14 +206,14 @@ export const login2 = async (req: Request, res: Response) => {
name: ACTION_LOGIN, name: ACTION_LOGIN,
userId: user._id userId: user._id
}); });
loginAction && await EELogService.createLog({ loginAction && await EELogService.createLog({
userId: user._id, userId: user._id,
actions: [loginAction], actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']), channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip ipAddress: req.ip
}); });
return res.status(200).send(response); return res.status(200).send(response);
} }
@@ -244,7 +244,7 @@ export const sendMfaToken = async (req: Request, res: Response) => {
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email email
}); });
// send MFA code [code] to [email] // send MFA code [code] to [email]
await sendMail({ await sendMail({
template: 'emailMfa.handlebars', template: 'emailMfa.handlebars',
@@ -259,9 +259,9 @@ export const sendMfaToken = async (req: Request, res: Response) => {
Sentry.captureException(err); Sentry.captureException(err);
return res.status(400).send({ return res.status(400).send({
message: 'Failed to send MFA code' message: 'Failed to send MFA code'
}); });
} }
return res.status(200).send({ return res.status(200).send({
message: 'Successfully sent new MFA code' message: 'Successfully sent new MFA code'
}); });
@@ -274,75 +274,87 @@ export const sendMfaToken = async (req: Request, res: Response) => {
* @param res * @param res
*/ */
export const verifyMfaToken = async (req: Request, res: Response) => { export const verifyMfaToken = async (req: Request, res: Response) => {
const { email, mfaToken } = req.body; const { email, mfaToken } = req.body;
await TokenService.validateToken({ await TokenService.validateToken({
type: TOKEN_EMAIL_MFA, type: TOKEN_EMAIL_MFA,
email, email,
token: mfaToken token: mfaToken
}); });
const user = await User.findOne({ const user = await User.findOne({
email email
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag'); }).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
if (!user) throw new Error('Failed to find user'); if (!user) throw new Error('Failed to find user');
await checkUserDevice({ await checkUserDevice({
user, user,
ip: req.ip, ip: req.ip,
userAgent: req.headers['user-agent'] ?? '' userAgent: req.headers['user-agent'] ?? ''
}); });
// issue tokens // issue tokens
const tokens = await issueAuthTokens({ userId: user._id.toString() }); const tokens = await issueAuthTokens({ userId: user._id.toString() });
// store (refresh) token in httpOnly cookie // store (refresh) token in httpOnly cookie
res.cookie('jid', tokens.refreshToken, { res.cookie('jid', tokens.refreshToken, {
httpOnly: true, httpOnly: true,
path: '/', path: '/',
sameSite: 'strict', sameSite: 'strict',
secure: getNodeEnv() === 'production' ? true : false secure: getNodeEnv() === 'production' ? true : false
}); });
interface VerifyMfaTokenRes {
encryptionVersion: number;
protectedKey?: string;
protectedKeyIV?: string;
protectedKeyTag?: string;
token: string;
publicKey: string;
encryptedPrivateKey: string;
iv: string;
tag: string;
}
const resObj: VerifyMfaTokenRes = { interface VerifyMfaTokenRes {
encryptionVersion: user.encryptionVersion, encryptionVersion: number;
token: tokens.token, protectedKey?: string;
publicKey: user.publicKey as string, protectedKeyIV?: string;
encryptedPrivateKey: user.encryptedPrivateKey as string, protectedKeyTag?: string;
iv: user.iv as string, token: string;
tag: user.tag as string publicKey: string;
} encryptedPrivateKey: string;
iv: string;
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) { tag: string;
resObj.protectedKey = user.protectedKey; }
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({ interface VerifyMfaTokenRes {
name: ACTION_LOGIN, encryptionVersion: number;
userId: user._id protectedKey?: string;
}); protectedKeyIV?: string;
protectedKeyTag?: string;
loginAction && await EELogService.createLog({ token: string;
userId: user._id, publicKey: string;
actions: [loginAction], encryptedPrivateKey: string;
channel: getChannelFromUserAgent(req.headers['user-agent']), iv: string;
ipAddress: req.ip tag: string;
}); }
return res.status(200).send(resObj); const resObj: VerifyMfaTokenRes = {
} encryptionVersion: user.encryptionVersion,
token: tokens.token,
publicKey: user.publicKey as string,
encryptedPrivateKey: user.encryptedPrivateKey as string,
iv: user.iv as string,
tag: user.tag as string
}
if (user?.protectedKey && user?.protectedKeyIV && user?.protectedKeyTag) {
resObj.protectedKey = user.protectedKey;
resObj.protectedKeyIV = user.protectedKeyIV;
resObj.protectedKeyTag = user.protectedKeyTag;
}
const loginAction = await EELogService.createAction({
name: ACTION_LOGIN,
userId: user._id
});
loginAction && await EELogService.createLog({
userId: user._id,
actions: [loginAction],
channel: getChannelFromUserAgent(req.headers['user-agent']),
ipAddress: req.ip
});
return res.status(200).send(resObj);
}
+19 -17
View File
@@ -3,7 +3,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import handlebars from 'handlebars'; import handlebars from 'handlebars';
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import { getSmtpFromName, getSmtpFromAddress } from '../config'; import { getSmtpFromName, getSmtpFromAddress, getSmtpConfigured } from '../config';
let smtpTransporter: nodemailer.Transporter; let smtpTransporter: nodemailer.Transporter;
@@ -25,23 +25,25 @@ const sendMail = async ({
recipients: string[]; recipients: string[];
substitutions: any; substitutions: any;
}) => { }) => {
try { if (getSmtpConfigured()) {
const html = fs.readFileSync( try {
path.resolve(__dirname, '../templates/' + template), const html = fs.readFileSync(
'utf8' path.resolve(__dirname, '../templates/' + template),
); 'utf8'
const temp = handlebars.compile(html); );
const htmlToSend = temp(substitutions); const temp = handlebars.compile(html);
const htmlToSend = temp(substitutions);
await smtpTransporter.sendMail({ await smtpTransporter.sendMail({
from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`, from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`,
to: recipients.join(', '), to: recipients.join(', '),
subject: subjectLine, subject: subjectLine,
html: htmlToSend html: htmlToSend
}); });
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
Sentry.captureException(err); Sentry.captureException(err);
}
} }
}; };
+30 -28
View File
@@ -23,29 +23,29 @@ const swaggerFile = require('../spec.json');
const requestIp = require('request-ip'); const requestIp = require('request-ip');
import { apiLimiter } from './helpers/rateLimiter'; import { apiLimiter } from './helpers/rateLimiter';
import { import {
workspace as eeWorkspaceRouter, workspace as eeWorkspaceRouter,
secret as eeSecretRouter, secret as eeSecretRouter,
secretSnapshot as eeSecretSnapshotRouter, secretSnapshot as eeSecretSnapshotRouter,
action as eeActionRouter action as eeActionRouter
} from './ee/routes/v1'; } from './ee/routes/v1';
import { import {
signup as v1SignupRouter, signup as v1SignupRouter,
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
organization as v1OrganizationRouter, organization as v1OrganizationRouter,
workspace as v1WorkspaceRouter, workspace as v1WorkspaceRouter,
membershipOrg as v1MembershipOrgRouter, membershipOrg as v1MembershipOrgRouter,
membership as v1MembershipRouter, membership as v1MembershipRouter,
key as v1KeyRouter, key as v1KeyRouter,
inviteOrg as v1InviteOrgRouter, inviteOrg as v1InviteOrgRouter,
user as v1UserRouter, user as v1UserRouter,
userAction as v1UserActionRouter, userAction as v1UserActionRouter,
secret as v1SecretRouter, secret as v1SecretRouter,
serviceToken as v1ServiceTokenRouter, serviceToken as v1ServiceTokenRouter,
password as v1PasswordRouter, password as v1PasswordRouter,
stripe as v1StripeRouter, stripe as v1StripeRouter,
integration as v1IntegrationRouter, integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter integrationAuth as v1IntegrationAuthRouter
} from './routes/v1'; } from './routes/v1';
import { import {
signup as v2SignupRouter, signup as v2SignupRouter,
@@ -66,7 +66,7 @@ import { getLogger } from './utils/logger';
import { RouteNotFoundError } from './utils/errors'; import { RouteNotFoundError } from './utils/errors';
import { requestErrorHandler } from './middleware/requestErrorHandler'; import { requestErrorHandler } from './middleware/requestErrorHandler';
import { import {
getMongoURL, getMongoURL,
getNodeEnv, getNodeEnv,
getPort, getPort,
getSentryDSN, getSentryDSN,
@@ -74,10 +74,12 @@ import {
} from './config'; } from './config';
const main = async () => { const main = async () => {
await infisical.connect({ if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) {
token: process.env.INFISICAL_TOKEN! await infisical.connect({
}); token: process.env.INFISICAL_TOKEN!
});
}
logTelemetryMessage(); logTelemetryMessage();
setTransporter(initSmtp()); setTransporter(initSmtp());
@@ -160,7 +162,7 @@ const main = async () => {
//* Handle unrouted requests and respond with proper error message as well as status code //* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => { app.use((req, res, next) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` })) next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
}) })
@@ -172,7 +174,7 @@ const main = async () => {
createTestUserForDevelopment(); createTestUserForDevelopment();
setUpHealthEndpoint(server); setUpHealthEndpoint(server);
server.on('close', async () => { server.on('close', async () => {
await DatabaseService.closeDatabase(); await DatabaseService.closeDatabase();
}) })
+2
View File
@@ -1,4 +1,5 @@
import express, { Request, Response } from 'express'; import express, { Request, Response } from 'express';
import { getSmtpConfigured } from '../../config';
const router = express.Router(); const router = express.Router();
@@ -8,6 +9,7 @@ router.get(
res.status(200).json({ res.status(200).json({
date: new Date(), date: new Date(),
message: 'Ok', message: 'Ok',
emailConfigured: getSmtpConfigured()
}) })
} }
); );
+5
View File
@@ -0,0 +1,5 @@
import { it, expect } from '@jest/globals';
it('should return true', () => {
expect(true).toBeTruthy();
});
@@ -18,4 +18,4 @@ describe('Healthcheck endpoint', () => {
const res = await request(server).get('/healthcheck'); const res = await request(server).get('/healthcheck');
expect(res.status).toEqual(200); expect(res.status).toEqual(200);
}); });
}); });
+3 -1
View File
@@ -4,7 +4,10 @@ go 1.19
require ( require (
github.com/99designs/keyring v1.2.2 github.com/99designs/keyring v1.2.2
github.com/mattn/go-isatty v0.0.14
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a
github.com/muesli/mango-cobra v1.2.0 github.com/muesli/mango-cobra v1.2.0
github.com/muesli/reflow v0.3.0
github.com/muesli/roff v0.1.0 github.com/muesli/roff v0.1.0
github.com/spf13/cobra v1.6.1 github.com/spf13/cobra v1.6.1
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
@@ -22,7 +25,6 @@ require (
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/mattn/go-colorable v0.1.9 // indirect github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/mtibben/percent v0.2.1 // indirect github.com/mtibben/percent v0.2.1 // indirect
+6 -1
View File
@@ -56,6 +56,7 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8= github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
@@ -63,12 +64,16 @@ github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs= github.com/mtibben/percent v0.2.1 h1:5gssi8Nqo8QU/r2pynCm+hBQHpkB/uNK7BJCFogWdzs=
github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns= github.com/mtibben/percent v0.2.1/go.mod h1:KG9uO+SZkUp+VkRHsCdYQV3XSZrrSpR3O9ibNBTZrns=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a h1:jlDOeO5TU0pYlbc/y6PFguab5IjANI0Knrpg3u/ton4=
github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI= github.com/muesli/mango v0.1.0 h1:DZQK45d2gGbql1arsYA4vfg4d7I9Hfx5rX/GCmzsAvI=
github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4= github.com/muesli/mango v0.1.0/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg= github.com/muesli/mango-cobra v1.2.0 h1:DQvjzAM0PMZr85Iv9LIMaYISpTOliMEg+uMFtNbYvWg=
github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA= github.com/muesli/mango-cobra v1.2.0/go.mod h1:vMJL54QytZAJhCT13LPVDfkvCUJ5/4jNUKF/8NC2UjA=
github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg= github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0= github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8= github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig= github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
@@ -78,6 +83,7 @@ github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
@@ -106,7 +112,6 @@ go.mongodb.org/mongo-driver v1.10.0/go.mod h1:wsihk0Kdgv8Kqu1Anit4sfK+22vSFbUrAV
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+3 -3
View File
@@ -263,10 +263,10 @@ var secretsSetCmd = &cobra.Command{
} }
// Print secret operations // Print secret operations
headers := []string{"SECRET NAME", "SECRET VALUE", "STATUS"} headers := [...]string{"SECRET NAME", "SECRET VALUE", "STATUS"}
rows := [][]string{} rows := [][3]string{}
for _, secretOperation := range secretOperations { for _, secretOperation := range secretOperations {
rows = append(rows, []string{secretOperation.SecretKey, secretOperation.SecretValue, secretOperation.SecretOperation}) rows = append(rows, [...]string{secretOperation.SecretKey, secretOperation.SecretValue, secretOperation.SecretOperation})
} }
visualize.Table(headers, rows) visualize.Table(headers, rows)
+3 -3
View File
@@ -3,12 +3,12 @@ package visualize
import "github.com/Infisical/infisical-merge/packages/models" import "github.com/Infisical/infisical-merge/packages/models"
func PrintAllSecretDetails(secrets []models.SingleEnvironmentVariable) { func PrintAllSecretDetails(secrets []models.SingleEnvironmentVariable) {
rows := [][]string{} rows := [][3]string{}
for _, secret := range secrets { for _, secret := range secrets {
rows = append(rows, []string{secret.Key, secret.Value, secret.Type}) rows = append(rows, [...]string{secret.Key, secret.Value, secret.Type})
} }
headers := []string{"SECRET NAME", "SECRET VALUE", "SECRET TYPE"} headers := [...]string{"SECRET NAME", "SECRET VALUE", "SECRET TYPE"}
Table(headers, rows) Table(headers, rows)
} }
+64 -2
View File
@@ -2,8 +2,14 @@ package visualize
import ( import (
"os" "os"
"strings"
"github.com/jedib0t/go-pretty/table" "github.com/jedib0t/go-pretty/table"
"github.com/mattn/go-isatty"
"github.com/muesli/ansi"
"github.com/muesli/reflow/truncate"
log "github.com/sirupsen/logrus"
"golang.org/x/term"
) )
type TableOptions struct { type TableOptions struct {
@@ -16,8 +22,35 @@ type TableOptions struct {
// } // }
// } // }
const (
// combined width of the table borders and padding
borderWidths = 10
// char to indicate that a string has been truncated
ellipsis = "…"
)
// Given headers and rows, this function will print out a table // Given headers and rows, this function will print out a table
func Table(headers []string, rows [][]string) { func Table(headers [3]string, rows [][3]string) {
// if we're not in a terminal or cygwin terminal, don't truncate the secret value
shouldTruncate := isatty.IsTerminal(os.Stdout.Fd())
// This will return an error if we're not in a terminal or
// if the terminal is a cygwin terminal like Git Bash.
width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil {
if shouldTruncate {
log.Errorf("error getting terminal size: %s", err)
} else {
log.Debug(err)
}
}
longestSecretName, longestSecretType := getLongestValues(append(rows, headers))
availableWidth := width - longestSecretName - longestSecretType - borderWidths
if availableWidth < 0 {
availableWidth = 0
}
t := table.NewWriter() t := table.NewWriter()
t.SetOutputMirror(os.Stdout) t.SetOutputMirror(os.Stdout)
t.SetStyle(table.StyleLight) t.SetStyle(table.StyleLight)
@@ -35,7 +68,11 @@ func Table(headers []string, rows [][]string) {
t.AppendHeader(tableHeaders) t.AppendHeader(tableHeaders)
for _, row := range rows { for _, row := range rows {
tableRow := table.Row{} tableRow := table.Row{}
for _, val := range row { for i, val := range row {
// only truncate the first column (secret value)
if i == 1 && stringWidth(val) > availableWidth && shouldTruncate {
val = truncate.StringWithTail(val, uint(availableWidth), ellipsis)
}
tableRow = append(tableRow, val) tableRow = append(tableRow, val)
} }
t.AppendRow(tableRow) t.AppendRow(tableRow)
@@ -43,3 +80,28 @@ func Table(headers []string, rows [][]string) {
t.Render() t.Render()
} }
// getLongestValues returns the length of the longest secret name and type from all rows (including the header).
func getLongestValues(rows [][3]string) (longestSecretName, longestSecretType int) {
for _, row := range rows {
if len(row[0]) > longestSecretName {
longestSecretName = stringWidth(row[0])
}
if len(row[2]) > longestSecretType {
longestSecretType = stringWidth(row[2])
}
}
return
}
// stringWidth returns the width of a string.
// ANSI escape sequences are ignored and double-width characters are handled correctly.
func stringWidth(str string) (width int) {
for _, l := range strings.Split(str, "\n") {
w := ansi.PrintableRuneWidth(l)
if w > width {
width = w
}
}
return width
}
@@ -0,0 +1,176 @@
AWSTemplateFormatVersion: 2010-09-09
Description: >-
CloudFormation template to deploy Infisical on a EC2 instance with a
DocumentDB instance
Parameters:
KeyPairName:
Description: The name of the EC2 Key Pair to enable SSH access to the instance
Type: "AWS::EC2::KeyPair::KeyName"
VpcId:
Description: The ID of the VPC in which to launch the instance
Type: "AWS::EC2::VPC::Id"
DocumentDBUsername:
Description: The username for the DocumentDB instance
Type: String
MinLength: 5
DocumentDBPassword:
Description: The password for the DocumentDB instance (minimum 8 characters)
Type: String
MinLength: 8
NoEcho: true
Resources:
DocumentDBCluster:
Type: "AWS::DocDB::DBCluster"
Properties:
EngineVersion: 4.0.0
StorageEncrypted: true
MasterUsername: !Ref DocumentDBUsername
MasterUserPassword: !Ref DocumentDBPassword
VpcSecurityGroupIds:
- !Ref DocumentDBClusterSecurityGroup
DBClusterParameterGroupName: !Ref DBClusterParameterGroup
Metadata:
"AWS::CloudFormation::Designer":
id: 73b974cf-eed3-4f7d-8657-6a6746bac169
DependsOn:
- DBClusterParameterGroup
DBClusterParameterGroup:
Type: "AWS::DocDB::DBClusterParameterGroup"
Properties:
Description: "description"
Family: "docdb4.0"
Parameters:
tls: "disabled"
ttl_monitor: "disabled"
Tags:
- Key: "String"
Value: "String"
DocumentDBInstance:
Type: "AWS::DocDB::DBInstance"
Properties:
DBInstanceClass: db.t4g.medium
DBClusterIdentifier: !Ref DocumentDBCluster
Metadata:
"AWS::CloudFormation::Designer":
id: f04cee38-175e-4432-9ad7-62ca28bbf935
DocumentDBClusterSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow inbound traffic for DocumentDB cluster
VpcId: !Ref VpcId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 27017
ToPort: 27017
SourceSecurityGroupId: !Ref InstanceSecurityGroup
EC2Instance:
Type: "AWS::EC2::Instance"
Properties:
ImageId: ami-0557a15b87f6559cf
InstanceType: t2.medium
KeyName: !Ref KeyPairName
UserData:
Fn::Base64: !Sub |
#!/bin/bash
cd /home/ubuntu
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
git clone https://github.com/Infisical/infisical.git
cd infisical
DOCUMENT_DB_CONNECTION_URL="mongodb://${DocumentDBUsername}:${DocumentDBPassword}@${DocumentDBCluster.Endpoint}:${DocumentDBCluster.Port}/infisical?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false"
ENCRYPTION_KEY=$(openssl rand -hex 16)
JWT_SIGNUP_SECRET=$(openssl rand -hex 16)
JWT_REFRESH_SECRET=$(openssl rand -hex 16)
JWT_AUTH_SECRET=$(openssl rand -hex 16)
JWT_SERVICE_SECRET=$(openssl rand -hex 16)
touch .env
echo "ENCRYPTION_KEY=${!ENCRYPTION_KEY}" >> .env
echo "JWT_SIGNUP_SECRET=${!JWT_SIGNUP_SECRET}" >> .env
echo "JWT_REFRESH_SECRET=${!JWT_REFRESH_SECRET}" >> .env
echo "JWT_AUTH_SECRET=${!JWT_AUTH_SECRET}" >> .env
echo "JWT_SERVICE_SECRET=${!JWT_SERVICE_SECRET}" >> .env
echo "MONGO_URL=${!DOCUMENT_DB_CONNECTION_URL}" >> .env
docker-compose up -d
SecurityGroupIds:
- !Ref InstanceSecurityGroup
Tags:
- Key: Name
Value: infisical
Metadata:
"AWS::CloudFormation::Designer":
id: 2c0a771c-5002-4785-9848-0377e33cd0e9
DependsOn:
- DocumentDBInstance
InstanceSecurityGroup:
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: Allow SSH and HTTP traffic
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
VpcId: !Ref VpcId
Metadata:
"AWS::CloudFormation::Designer":
id: 1fd6856a-11e5-4369-84fa-d18d4011b3de
Outputs:
InstanceIP:
Value: !GetAtt EC2Instance.PublicIp
Metadata:
"AWS::CloudFormation::Designer":
1fd6856a-11e5-4369-84fa-d18d4011b3de:
size:
width: 60
height: 60
position:
x: 60
"y": 90
z: 1
embeds: []
2c0a771c-5002-4785-9848-0377e33cd0e9:
size:
width: 60
height: 60
position:
x: 180
"y": 90
z: 1
embeds: []
isassociatedwith:
- 1fd6856a-11e5-4369-84fa-d18d4011b3de
dependson:
- 2cabaada-fbdb-4945-bf95-a0406704dd5a
- f04cee38-175e-4432-9ad7-62ca28bbf935
73b974cf-eed3-4f7d-8657-6a6746bac169:
size:
width: 60
height: 60
position:
x: 390
"y": 210
z: 1
embeds: []
f04cee38-175e-4432-9ad7-62ca28bbf935:
size:
width: 60
height: 60
position:
x: 270
"y": 90
z: 1
embeds: []
+2 -2
View File
@@ -4,7 +4,7 @@ description: "Frequently Asked Questions about Infisical"
--- ---
Frequently asked questions about the CLI can be found on this page. Frequently asked questions about the CLI can be found on this page.
If you can't find the answer you're looking for, please create an issue on our GitHub repository or join our Slack channel for additional support. If you can't find the answer you are looking for, please create an issue on our GitHub repository or join our Slack channel for additional support.
<Accordion title="I'm getting a Keyring related error message when trying to login" defaultOpen="true"> <Accordion title="I'm getting a Keyring related error message when trying to login" defaultOpen="true">
By default, the CLI will choose the most suitable store available on your system. By default, the CLI will choose the most suitable store available on your system.
@@ -22,4 +22,4 @@ Yes. If you have previously retrieved secrets for a specific project and environ
<Accordion title="Can I upload the .infisical.json file that was generated?"> <Accordion title="Can I upload the .infisical.json file that was generated?">
Yes. This is simply a configuration file and contains no sensitive data. Yes. This is simply a configuration file and contains no sensitive data.
</Accordion> </Accordion>
+2 -13
View File
@@ -30,6 +30,8 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical
> >
Learn how to configure and deploy Infisical. Learn how to configure and deploy Infisical.
</Card> </Card>
</CardGroup>
<Card <Card
href="/integrations/overview" href="/integrations/overview"
title="Integrations" title="Integrations"
@@ -38,16 +40,3 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical
> >
Explore integrations for Docker, AWS, Heroku, etc. Explore integrations for Docker, AWS, Heroku, etc.
</Card> </Card>
</CardGroup>
<Card
title="Set up a 1x1 with an Infisical Engineer"
iconType="duotone"
color="#ca8b04"
href="https://calendly.com/maidull/30min"
>
Our team is happy to help you get started with Infisical. If you have any
questions or want to learn how you can leverage Infisical within your
infrastructure, **[set up a 1-on-1 with an Infisical
engineer](https://cal.com/maidul/15min)**.
</Card>
Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+3 -3
View File
@@ -39,10 +39,10 @@ Prerequisites:
Starting your service with the Infisical CLI pulls your secrets from Infisical and injects them into your service. Starting your service with the Infisical CLI pulls your secrets from Infisical and injects them into your service.
```dockerfile ```dockerfile
CMD ["infisical", "run", "---", "[your service start command]"] CMD ["infisical", "run", "--", "[your service start command]"]
# example with single single command # example with single single command
CMD ["infisical", "run", "---", "npm", "run", "start"] CMD ["infisical", "run", "--", "npm", "run", "start"]
# example with multiple commands # example with multiple commands
CMD ["infisical", "run", "--command", "npm run start && ..."] CMD ["infisical", "run", "--command", "npm run start && ..."]
@@ -55,7 +55,7 @@ Head to your project settings in Infisical Cloud to generate an [Infisical Token
## Feed Docker your Infisical Token ## Feed Docker your Infisical Token
```bash ```bash
docker run --env INFISICAL_TOKEN=[token]... docker run --env INFISICAL_TOKEN=[token] [DOCKER-IMAGE]...
``` ```
<Info> <Info>
+8 -18
View File
@@ -44,11 +44,6 @@
"icon": "shield-halved", "icon": "shield-halved",
"url": "security" "url": "security"
}, },
{
"name": "Self-hosting",
"icon": "server",
"url": "self-hosting"
},
{ {
"name": "SDKs", "name": "SDKs",
"icon": "puzzle-piece", "icon": "puzzle-piece",
@@ -101,6 +96,14 @@
"getting-started/dashboard/token" "getting-started/dashboard/token"
] ]
}, },
{
"group": "Self-hosting",
"pages": [
"self-hosting/overview",
"self-hosting/configuration/envars",
"self-hosting/configuration/email"
]
},
{ {
"group": "Command line", "group": "Command line",
"pages": [ "pages": [
@@ -171,12 +174,6 @@
"integrations/platforms/pm2" "integrations/platforms/pm2"
] ]
}, },
{
"group": "Self-hosting",
"pages": [
"self-hosting/overview"
]
},
{ {
"group": "Deployment options", "group": "Deployment options",
"pages": [ "pages": [
@@ -184,13 +181,6 @@
"self-hosting/deployments/kubernetes" "self-hosting/deployments/kubernetes"
] ]
}, },
{
"group": "Configuration",
"pages": [
"self-hosting/configuration/envars",
"self-hosting/configuration/email"
]
},
{ {
"group": "Overview", "group": "Overview",
"pages": [ "pages": [
+1 -1
View File
@@ -1,5 +1,5 @@
--- ---
title: "Email" title: "Configure email service"
description: "How to configure your email when self-hosting Infisical." description: "How to configure your email when self-hosting Infisical."
--- ---
+147 -39
View File
@@ -1,43 +1,151 @@
--- ---
title: "Environment Variables" title: "All environment variables"
description: "How to configure your environment variables when self-hosting Infisical." description: "Configure your environment variables when self-hosting Infisical."
--- ---
Configuring Infisical requires setting some environment variables. There is a file called [`.env.example`](https://github.com/Infisical/infisical/blob/main/.env.example) at the root directory of our main repo that you can use to create a `.env` file before you start the server. ## Backend environment variables
| Variable | Description | Default Value | Depending on your choosen self hosted deployment method, you may need to configured at least the required environment variable listed below.
| ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | Other environment variables are listed below to increase the functionality of your self hosted instance based on your use case.
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` | <Tabs>
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` | <Tab title="Required">
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` | <ParamField query="ENCRYPTION_KEY" type="string" default="none" required>
| `JWT_MFA_SECRET` | ❗️ JWT token secret | `None` | Must be a random 32 character length hex string
| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` | </ParamField>
| `JWT_SIGNUP_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `15m` |
| `JWT_REFRESH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `90d` | <ParamField query="JWT_SIGNUP_SECRET" type="string" default="none" required>
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` | Must be a random 32 character length hex string
| `JWT_MFA_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `5m` | </ParamField>
| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` |
| `MONGO_USERNAME` | MongoDB username if using container | `None` | <ParamField query="JWT_REFRESH_SECRET" type="string" default="none" required>
| `MONGO_PASSWORD` | MongoDB password if using container | `None` | Must be a random 32 character length hex string
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` | </ParamField>
| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` |
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `[email protected]`) | `None` | <ParamField query="JWT_AUTH_SECRET" type="string" default="none" required>
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` | Must be a random 32 character length hex string
| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` | </ParamField>
| `SMTP_SECURE` | If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported | `false` |
| `SMTP_FROM_ADDRESS` | ❗️ Email address to be used for sending emails (e.g. `[email protected]`) | `None` | <ParamField query="JWT_MFA_SECRET" type="string" default="none" required>
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` | Must be a random 32 character length hex string
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` | </ParamField>
| `LICENSE_KEY` | License key if using Infisical Enterprise Edition | `true` |
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` | <ParamField query="JWT_SERVICE_SECRET" type="string" default="none" required>
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` | Must be a random 32 character length hex string
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` | </ParamField>
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` | <ParamField query="MONGO_URL" type="string" default="none" required>
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` | *TLS based connection string is not yet supported
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` | </ParamField>
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` | </Tab>
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` | <Tab title="Email service">
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` | <Info>When email service is not configured, Infisical will have limited functionality</Info>
| `INVITE_ONLY_SIGNUP` | If true, users can only sign up if they are invited | `false` |
<ParamField query="SMTP_HOST" type="string" default="none" optional>
Hostname to connect to for establishing SMTP connections
</ParamField>
<ParamField query="SMTP_USERNAME" type="string" default="none" optional>
Credential to connect to host (e.g. [email protected])
</ParamField>
<ParamField query="SMTP_PASSWORD" type="string" default="587" optional>
Credential to connect to host
</ParamField>
<ParamField query="SMTP_PORT" type="string" default="587" optional>
Port to connect to for establishing SMTP connections
</ParamField>
<ParamField query="SMTP_SECURE" type="string" default="none" optional>
If true, use TLS when connecting to host. If false, TLS will be used if STARTTLS is supported
</ParamField>
<ParamField query="SMTP_FROM_ADDRESS" type="string" default="none" optional>
Email address to be used for sending emails
</ParamField>
<ParamField query="SMTP_FROM_NAME" type="string" default="none" optional>
Name label to be used in From field (e.g. Team)
</ParamField>
</Tab>
<Tab title="Integrations">
To sync secret to third party services, provide value for the related services
<ParamField query="CLIENT_ID_HEROKU" type="string" default="none" optional>
OAuth2 client ID for Heroku integration
</ParamField>
<ParamField query="CLIENT_SECRET_HEROKU" type="string" default="none" optional>
OAuth2 client secret for Heroku integration
</ParamField>
<ParamField query="CLIENT_ID_VERCEL" type="string" default="none" optional>
OAuth2 client ID for Vercel integration
</ParamField>
<ParamField query="CLIENT_SECRET_VERCEL" type="string" default="none" optional>
OAuth2 client secret for Vercel integration
</ParamField>
<ParamField query="CLIENT_ID_NETLIFY" type="string" default="none" optional>
OAuth2 client ID for Netlify integration
</ParamField>
<ParamField query="CLIENT_SECRET_NETLIFY" type="string" default="none" optional>
OAuth2 client secret for Netlify integration
</ParamField>
<ParamField query="CLIENT_ID_GITHUB" type="string" default="none" optional>
OAuth2 client ID for GitHub integration
</ParamField>
<ParamField query="CLIENT_SECRET_GITHUB" type="string" default="none" optional>
OAuth2 client secret for GitHub integration
</ParamField>
<ParamField query="CLIENT_SLUG_VERCEL" type="string" default="none" optional>
OAuth2 slug for Netlify integration
</ParamField>
</Tab>
<Tab title="Others">
#### JWT
<ParamField query="JWT_SIGNUP_LIFETIME" type="string" default="15m" optional>
JWT token lifetime expressed in seconds or a string describing a time span
</ParamField>
<ParamField query="JWT_REFRESH_LIFETIME" type="string" default="90d" optional>
JWT token lifetime expressed in seconds or a string describing a time span
</ParamField>
<ParamField query="JWT_AUTH_LIFETIME" type="string" default="10d" optional>
JWT token lifetime expressed in seconds or a string describing a time span
</ParamField>
<ParamField query="JWT_MFA_LIFETIME" type="string" default="5m" optional>
JWT token lifetime expressed in seconds or a string describing a time span
</ParamField>
<ParamField query="MONGO_USERNAME" type="string" default="none" optional></ParamField>
<ParamField query="MONGO_PASSWORD" type="string" default="none" optional></ParamField>
#### Error logging
Infisical uses Sentry to report error logs
<ParamField query="SENTRY_DSN" type="string" default="none" optional></ParamField>
#### Settings
<ParamField query="INVITE_ONLY_SIGNUP" type="string" default="false" optional>
Only allow users who are invited to sign up
</ParamField>
<ParamField query="SITE_URL" type="string" default="none" optional>
Site URL - should be an absolute URL including the protocol (e.g. https://app.infisical.com)
</ParamField>
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional></ParamField>
</Tab>
</Tabs>
## Frontend environment variables
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional></ParamField>
+184 -22
View File
@@ -1,36 +1,198 @@
--- ---
title: "Overview" title: "Deployment options"
description: "Infisical is an open-source end-to-end encrypted secrets manager that developers can set up within 15 minutes." description: "Explore deployment options for self hosting Infisical"
--- ---
<Info> To meet various compliance requirements, you may want to self-host Infisical instead of using [Infisical Cloud](https://app.infisical.com/).
Self-host vs. Infisical Cloud Self-hosted Infisical allows you to maintain your sensitive information within your own infrastructure and network, ensuring complete control over your data.
Self-hosting Infisical means managing the service yourself, taking care of upgrades, scaling, security, etc. <Tabs>
<Tab title="Quick deploy AWS">
<iframe width="560" height="315" src="https://www.youtube.com/embed/jR-gM7vIY2c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
This deployment option will use AWS Cloudformation to auto deploy an instance of Infisical on a single EC2 via Docker Compose.
If you're less technical and looking for a hands-free experience with minimal overhead then we recommend Infisical Cloud. **Resources that will be provisioned**
- 1 EC2 instance
- 1 DocumentDB cluster
- 1 DocumentDB instance
- Security groups
Infisical Cloud also comes with some extra features unavailable in the self-hosted edition. You can find more information about Infisical Cloud's offering on the pricing page. <a href="https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/create/review?templateURL=https://ec2-instance-cloudformation.s3.amazonaws.com/cloudformation.template&stackName=infisical">
<img width="200" src="../images/deploy-aws-button.png" />
</a>
</Info> </Tab>
<Tab title="Quick deploy Digital Ocean">
<Note>This deployment option is highly available</Note>
Coming soon
</Tab>
<Tab title="Helm Kubernetes">
<Note>This deployment option is highly available</Note>
**Prerequisites**
- You have understanding of [Kubernetes](https://kubernetes.io/)
- You have understanding of [Helm package manager](https://helm.sh/)
- You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster
## Deployment options
Infisical can be deployed on a Linux VM with docker-compose and Kubernetes. We're rolling out more specific deployment options for DigitalOcean, AWS, GCP, and Azure soon. #### 1. Fill our environment variables
<CardGroup cols={2}> Before you can deploy the Helm chart, you must fill out the required environment variables. To do so, please copy the below file to a `.yaml` file.
<Card title="Any Linux" icon="square-1" color="#ea5a0c" href="/self-hosting/deployments/linux"> Refer to the available [environment variables](/self-hosting/configuration/envars) to learn more
Deploy to any Linux with Docker
</Card>
<Card title="Kubernetes" icon="square-2" color="#0285c7" href="/self-hosting/deployments/kubernetes">
Deploy to your Kubernetes cluster
</Card>
</CardGroup>
## Telemetry <Accordion title="values.yaml">
[View all available Helm chart values parameters](https://github.com/Infisical/infisical/tree/main/helm-charts/infisical)
```yaml
frontend:
enabled: true
name: frontend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 2
image:
repository: infisical/frontend
tag: "latest"
pullPolicy: IfNotPresent
kubeSecretRef: ""
service:
annotations: {}
type: ClusterIP
nodePort: ""
Infisical collects telemetry data about general usage. frontendEnvironmentVariables:
SITE_URL: infisical.local
The data helps us understand how the product is doing and guide our product development to create the best possible platform; it also helps us demonstrate growth for investors as we support Infisical as open-source software. backend:
enabled: true
name: backend
podAnnotations: {}
deploymentAnnotations: {}
replicaCount: 2
image:
repository: infisical/backend
tag: "latest"
pullPolicy: IfNotPresent
kubeSecretRef: ""
service:
annotations: {}
type: ClusterIP
nodePort: ""
To opt out of telemetry, you can set `TELEMETRY_ENABLED=false` within the [environment variables](./configuration/envars). backendEnvironmentVariables:
ENCRYPTION_KEY: MUST_REPLACE
JWT_SIGNUP_SECRET: MUST_REPLACE
JWT_REFRESH_SECRET: MUST_REPLACE
JWT_AUTH_SECRET: MUST_REPLACE
JWT_SERVICE_SECRET: MUST_REPLACE
SMTP_HOST: MUST_REPLACE
SMTP_PORT: 587
SMTP_SECURE: false
SMTP_FROM_NAME: Infisical
SMTP_FROM_ADDRESS: MUST_REPLACE
SMTP_USERNAME: MUST_REPLACE
SMTP_PASSWORD: MUST_REPLACE
SITE_URL: infisical.local
## Mongo DB persistence
mongodb:
enabled: true
## By default the backend will be connected to a Mongo instance within the cluster
## However, it is recommended to add a managed document DB connection string for production-use (DBaaS)
## Learn about connection string type here https://www.mongodb.com/docs/manual/reference/connection-string/
## e.g. "mongodb://<user>:<pass>@<host>:<port>/<database-name>"
mongodbConnection:
externalMongoDBConnectionString: ""
ingress:
enabled: true
annotations:
kubernetes.io/ingress.class: "nginx"
# cert-manager.io/issuer: letsencrypt-nginx
hostName: infisical.local ## <- Replace with your own domain
frontend:
path: /
pathType: Prefix
backend:
path: /api
pathType: Prefix
tls: []
# - secretName: letsencrypt-nginx
# hosts:
# - infisical.local
mailhog:
enabled: false
```
</Accordion>
Once you have a local copy of the values file, fill our the required environment variables and save the file.
#### 2. Install Infisical Helm repository
```bash
helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/'
helm repo update
```
#### 3. Install the Helm chart
By default, the helm chart will be installed on your default namespace. If you wish to install the Chart on a different namespace, you may specify
that by adding the `--namespace <namespace-to-install-to>` to your `helm install` command.
```bash
## Installs to default namespace
helm install infisical-helm-charts/infisical --generate-name --values <path to the values.yaml you downloaded/created in step 2>
```
<Note>
If you have not filled out all of the required environment variables, you will see an error message prompting you to
do so.
</Note>
#### 4. Your Infisical installation is complete and should be running on the host name you specified in Ingress in `values.yaml`.
</Tab>
<Tab title="Bare Docker Compose">
1. Install Docker on your VM
```bash
# Example in ubuntu
apt-get update
apt-get upgrade
apt install docker-compose
```
2. Download the required files
```bash
# Download env file template
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
# Download docker compose template
wget -O docker-compose.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.yml
# Download nginx config
mkdir nginx && wget -O ./nginx/default.conf https://raw.githubusercontent.com/Infisical/infisical/main/nginx/default.dev.conf
```
3. Tweak the `.env` according to your preferences. Refer to the available [environment variables](/self-hosting/configuration/envars)
```bash
# update environment variables like mongo login
nano .env
```
4. Get the service up and running.
```bash
# Start up services in detached mode
docker-compose -f docker-compose.yml up -d
```
5. Your Infisical installation is complete and should be running on [http://localhost:80](http://localhost:80). Please note that the containers are not exposed to the internet and only bind to the localhost. It's up to you to configure a firewall, SSL certificates, and implement any additional security measures.
</Tab>
</Tabs>
@@ -2,10 +2,13 @@ import React, { useState } from 'react';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { usePopUp } from '@app/hooks/usePopUp';
import addUserToOrg from '@app/pages/api/organization/addUserToOrg'; import addUserToOrg from '@app/pages/api/organization/addUserToOrg';
import getWorkspaces from '@app/pages/api/workspace/getWorkspaces'; import getWorkspaces from '@app/pages/api/workspace/getWorkspaces';
import Button from '../basic/buttons/Button'; import Button from '../basic/buttons/Button';
import { EmailServiceSetupModal } from '../v2';
/** /**
* This is the last step of the signup flow. People can optionally invite their teammates here. * This is the last step of the signup flow. People can optionally invite their teammates here.
@@ -14,6 +17,10 @@ export default function TeamInviteStep(): JSX.Element {
const [emails, setEmails] = useState(''); const [emails, setEmails] = useState('');
const { t } = useTranslation(); const { t } = useTranslation();
const router = useRouter(); const router = useRouter();
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
// Redirect user to the getting started page // Redirect user to the getting started page
const redirectToHome = async () => { const redirectToHome = async () => {
@@ -62,10 +69,20 @@ export default function TeamInviteStep(): JSX.Element {
</div> </div>
<Button <Button
text={t('signup:step5-send-invites') ?? ''} text={t('signup:step5-send-invites') ?? ''}
onButtonPressed={() => inviteUsers({ emails })} onButtonPressed={() => {
if(serverDetails?.emailConfigured){
inviteUsers({ emails })
}else{
handlePopUpOpen('setUpEmail');
}
}}
size="lg" size="lg"
/> />
</div> </div>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
} }
@@ -4,13 +4,11 @@ const POSTHOG_HOST =
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com"; process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!; const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!; const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
const SITE_URL = "https://app.infisical.com";
export { export {
ENV, ENV,
POSTHOG_API_KEY, POSTHOG_API_KEY,
POSTHOG_HOST, POSTHOG_HOST,
SITE_URL,
STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER STRIPE_PRODUCT_STARTER
}; };
@@ -1,7 +1,5 @@
import { jsPDF } from 'jspdf'; import { jsPDF } from 'jspdf';
import { SITE_URL } from './config';
interface PDFProps { interface PDFProps {
personalName: string; personalName: string;
personalEmail: string; personalEmail: string;
@@ -19,7 +17,7 @@ const yyyy = today.getFullYear();
const todayFormatted = `${mm}/${dd}/${yyyy}`; const todayFormatted = `${mm}/${dd}/${yyyy}`;
function createPdfHeader(doc: jsPDF, personalName : string) { function createPdfHeader(doc: jsPDF, personalName: string) {
doc.setFillColor(255, 255, 255); doc.setFillColor(255, 255, 255);
doc.rect(0, 0, 600, 900, 'F'); doc.rect(0, 0, 600, 900, 'F');
doc.setTextColor(23, 23, 23); doc.setTextColor(23, 23, 23);
@@ -30,6 +28,10 @@ function createPdfHeader(doc: jsPDF, personalName : string) {
} }
function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: string) { function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: string) {
const { protocol, hostname, port } = window.location;
const portSuffix = port && port !== '80' ? `:${port}` : '';
const siteURL = `${protocol}//${hostname}${portSuffix}`;
doc.setFontSize(14); doc.setFontSize(14);
doc.text( doc.text(
'In case you get locked out of you Infisical account, you`ll need these account details', 'In case you get locked out of you Infisical account, you`ll need these account details',
@@ -73,7 +75,7 @@ function createPdfContent(doc: jsPDF, personalEmail: string, generatedKey: strin
doc.roundedRect(170, 488, 375, 35, 5, 5, 'F'); doc.roundedRect(170, 488, 375, 35, 5, 5, 'F');
doc.setTextColor(23, 23, 23); doc.setTextColor(23, 23, 23);
doc.setFontSize(14); doc.setFontSize(14);
doc.text(`${SITE_URL}/login`, 180, 420); doc.text(`${siteURL}/login`, 180, 420);
doc.text(personalEmail, 180, 465); doc.text(personalEmail, 180, 465);
doc.text(generatedKey, 180, 510); doc.text(generatedKey, 180, 510);
doc.text('Need help? Contact us at [email protected]', 32, 575); doc.text('Need help? Contact us at [email protected]', 32, 575);
@@ -0,0 +1,21 @@
import { Button } from '../Button';
import { Modal, ModalContent } from '../Modal';
type Props = {
isOpen?: boolean;
onOpenChange?: (isOpen: boolean) => void;
};
export const EmailServiceSetupModal = ({ isOpen, onOpenChange }: Props): JSX.Element => (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Email service not configured">
<p className="mb-4 text-bunker-300">
The administrators of this Infisical instance have not yet set up an email service provider required to perform this action
</p>
<a href="https://infisical.com/docs/self-hosting/configuration/email">
<Button className="mr-4">Learn more</Button>
</a>
</ModalContent>
</Modal>
);
@@ -0,0 +1 @@
export { EmailServiceSetupModal } from './EmailServiceSetupModal';
+1
View File
@@ -3,6 +3,7 @@ export * from './Card';
export * from './Checkbox'; export * from './Checkbox';
export * from './DeleteActionModal'; export * from './DeleteActionModal';
export * from './Dropdown'; export * from './Dropdown';
export * from './EmailServiceSetupModal'
export * from './EmptyState'; export * from './EmptyState';
export * from './FormControl'; export * from './FormControl';
export * from './IconButton'; export * from './IconButton';
@@ -0,0 +1 @@
export { useFetchServerStatus } from './queries'
@@ -0,0 +1,19 @@
import {useQuery } from '@tanstack/react-query';
import { apiRequest } from '@app/config/request';
import { ServerStatus } from './types';
// cache key
const serverStatusKeys = {
serverStatus: ['serverStatus'] as const
};
const fetchServerStatus = async () => {
const {data} = await apiRequest.get<ServerStatus>('/api/status');
return data;
};
export const useFetchServerStatus= () => {
return useQuery({ queryKey: serverStatusKeys.serverStatus, queryFn: fetchServerStatus });
}
@@ -0,0 +1,5 @@
export type ServerStatus = {
date: string;
message: string;
emailConfigured: boolean;
};
+7 -1
View File
@@ -83,8 +83,14 @@ export const useAddUserToWs = () => {
export const useAddUserToOrg = () => { export const useAddUserToOrg = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
type Response = {
data: {
message: string,
completeInviteLink: string | undefined
}
}
return useMutation<{}, {}, AddUserToOrgDTO>({ return useMutation<Response, {}, AddUserToOrgDTO>({
mutationFn: (dto) => apiRequest.post(`/api/v1/invite-org/signup`, dto), mutationFn: (dto) => apiRequest.post(`/api/v1/invite-org/signup`, dto),
onSuccess: (_, { organizationId }) => { onSuccess: (_, { organizationId }) => {
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId)); queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
+18 -4
View File
@@ -13,6 +13,7 @@ import TeamInviteStep from '@app/components/signup/TeamInviteStep';
import UserInfoStep from '@app/components/signup/UserInfoStep'; import UserInfoStep from '@app/components/signup/UserInfoStep';
import SecurityClient from '@app/components/utilities/SecurityClient'; import SecurityClient from '@app/components/utilities/SecurityClient';
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode'; import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode';
import getWorkspaces from './api/workspace/getWorkspaces'; import getWorkspaces from './api/workspace/getWorkspaces';
@@ -25,10 +26,12 @@ export default function SignUp() {
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [firstName, setFirstName] = useState(''); const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState(''); const [lastName, setLastName] = useState('');
const [code, setCode] = useState(''); const [code, setCode] = useState('123456');
const [codeError, setCodeError] = useState(false); const [codeError, setCodeError] = useState(false);
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const router = useRouter(); const router = useRouter();
const {data: serverDetails } = useFetchServerStatus()
const { t } = useTranslation(); const { t } = useTranslation();
@@ -68,6 +71,19 @@ export default function SignUp() {
} }
}; };
// when email service is not configured, skip step 2 and 5
useEffect(() => {
if (!serverDetails?.emailConfigured && step === 2){
incrementStep()
}
if (!serverDetails?.emailConfigured && step === 5){
getWorkspaces().then((userWorkspaces)=>{
router.push(`/dashboard/${userWorkspaces[0]._id}`);
});
}
}, [step]);
return ( return (
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center"> <div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
<Head> <Head>
@@ -111,9 +127,7 @@ export default function SignUp() {
password={password} password={password}
name={`${firstName} ${lastName}`} name={`${firstName} ${lastName}`}
/> />
) : ( ) : (serverDetails?.emailConfigured ? <TeamInviteStep /> : "")}
<TeamInviteStep />
)}
</form> </form>
</div> </div>
</div> </div>
+19 -1
View File
@@ -6,12 +6,19 @@ import Link from 'next/link';
import Button from '@app/components/basic/buttons/Button'; import Button from '@app/components/basic/buttons/Button';
import InputField from '@app/components/basic/InputField'; import InputField from '@app/components/basic/InputField';
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps'; import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
import { EmailServiceSetupModal } from '@app/components/v2';
import { usePopUp } from '@app/hooks';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset'; import SendEmailOnPasswordReset from './api/auth/SendEmailOnPasswordReset';
export default function VerifyEmail() { export default function VerifyEmail() {
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [step, setStep] = useState(1); const [step, setStep] = useState(1);
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
/** /**
* This function sends the verification email and forwards a user to the next step. * This function sends the verification email and forwards a user to the next step.
@@ -63,7 +70,13 @@ export default function VerifyEmail() {
</div> </div>
<div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm"> <div className="flex flex-col items-center justify-center w-full md:p-2 max-h-20 max-w-md mt-4 mx-auto text-sm">
<div className="text-l mt-6 m-8 px-8 py-3 text-lg"> <div className="text-l mt-6 m-8 px-8 py-3 text-lg">
<Button text="Continue" onButtonPressed={sendVerificationEmail} size="lg" /> <Button text="Continue" onButtonPressed={()=>{
if (serverDetails?.emailConfigured){
sendVerificationEmail()
} else {
handlePopUpOpen('setUpEmail');
}
}} size="lg" />
</div> </div>
</div> </div>
</div> </div>
@@ -80,6 +93,11 @@ export default function VerifyEmail() {
</div> </div>
</div> </div>
)} )}
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
} }
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
import { useState } from 'react';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { plans } from 'public/data/frequentConstants'; import { plans } from 'public/data/frequentConstants';
@@ -55,6 +56,8 @@ export const OrgSettingsPage = () => {
const addIncidentContact = useAddIncidentContact(); const addIncidentContact = useAddIncidentContact();
const removeIncidentContact = useDeleteIncidentContact(); const removeIncidentContact = useDeleteIncidentContact();
const [completeInviteLink, setcompleteInviteLink] = useState<string|undefined>("")
const isMoreUsersNotAllowed = const isMoreUsersNotAllowed =
(orgUsers || []).length >= 5 && (orgUsers || []).length >= 5 &&
subscriptionPlan === plans.starter && subscriptionPlan === plans.starter &&
@@ -100,11 +103,16 @@ export const OrgSettingsPage = () => {
if (!currentOrg?._id) return; if (!currentOrg?._id) return;
try { try {
await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email }); const {data} = await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email });
createNotification({ setcompleteInviteLink(data?.completeInviteLink)
text: 'Successfully invited user to the organization.',
type: 'success' // only show this notification when email is configured. A [completeInviteLink] will not be sent if smtp is configured
}); if (!data.completeInviteLink){
createNotification({
text: 'Successfully invited user to the organization.',
type: 'success'
});
}
} catch (error) { } catch (error) {
console.error(error); console.error(error);
createNotification({ createNotification({
@@ -249,6 +257,8 @@ export const OrgSettingsPage = () => {
onRemoveMember={onRemoveUserOrgMembership} onRemoveMember={onRemoveUserOrgMembership}
onRoleChange={onUpdateOrgUserRole} onRoleChange={onUpdateOrgUserRole}
onGrantAccess={onGrantUserAccess} onGrantAccess={onGrantUserAccess}
completeInviteLink={completeInviteLink}
setCompleteInviteLink={setcompleteInviteLink}
/> />
</div> </div>
<div className="mb-6 mt-2 w-full rounded-md bg-white/5 p-6"> <div className="mb-6 mt-2 w-full rounded-md bg-white/5 p-6">
@@ -13,6 +13,7 @@ import * as yup from 'yup';
import { import {
Button, Button,
DeleteActionModal, DeleteActionModal,
EmailServiceSetupModal,
EmptyState, EmptyState,
FormControl, FormControl,
IconButton, IconButton,
@@ -29,6 +30,7 @@ import {
Tr Tr
} from '@app/components/v2'; } from '@app/components/v2';
import { usePopUp } from '@app/hooks'; import { usePopUp } from '@app/hooks';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { IncidentContact } from '@app/hooks/api/types'; import { IncidentContact } from '@app/hooks/api/types';
type Props = { type Props = {
@@ -51,9 +53,11 @@ export const OrgIncidentContactsTable = ({
isLoading isLoading
}: Props) => { }: Props) => {
const [searchContact, setSearchContact] = useState(''); const [searchContact, setSearchContact] = useState('');
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'addContact', 'addContact',
'removeContact' 'removeContact',
'setUpEmail'
] as const); ] as const);
const { const {
@@ -93,7 +97,13 @@ export const OrgIncidentContactsTable = ({
<div> <div>
<Button <Button
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => handlePopUpOpen('addContact')} onClick={() => {
if (serverDetails?.emailConfigured){
handlePopUpOpen('addContact');
} else {
handlePopUpOpen('setUpEmail');
}
}}
> >
Add Contact Add Contact
</Button> </Button>
@@ -181,6 +191,10 @@ export const OrgIncidentContactsTable = ({
onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)} onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)}
onDeleteApproved={onRemoveIncidentContact} onDeleteApproved={onRemoveIncidentContact}
/> />
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
}; };
@@ -1,7 +1,7 @@
import { useMemo, useState } from 'react'; import { Dispatch, SetStateAction, useMemo, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { faMagnifyingGlass, faPlus, faTrash, faUsers } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faCopy, faMagnifyingGlass, faPlus, faTrash, faUsers } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup'; import * as yup from 'yup';
@@ -9,7 +9,7 @@ import * as yup from 'yup';
import { import {
Button, Button,
DeleteActionModal, DeleteActionModal,
EmptyState, EmailServiceSetupModal, EmptyState,
FormControl, FormControl,
IconButton, IconButton,
Input, Input,
@@ -28,6 +28,7 @@ import {
Tr, Tr,
UpgradePlanModal} from '@app/components/v2'; UpgradePlanModal} from '@app/components/v2';
import { usePopUp } from '@app/hooks'; import { usePopUp } from '@app/hooks';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { OrgUser, Workspace } from '@app/hooks/api/types'; import { OrgUser, Workspace } from '@app/hooks/api/types';
type Props = { type Props = {
@@ -42,6 +43,8 @@ type Props = {
onGrantAccess: (userId: string, publicKey: string) => Promise<void>; onGrantAccess: (userId: string, publicKey: string) => Promise<void>;
// the current user id to block remove org button // the current user id to block remove org button
userId: string; userId: string;
completeInviteLink: string | undefined,
setCompleteInviteLink: Dispatch<SetStateAction<string | undefined>>
}; };
const addMemberFormSchema = yup.object({ const addMemberFormSchema = yup.object({
@@ -60,14 +63,18 @@ export const OrgMembersTable = ({
onGrantAccess, onGrantAccess,
onRoleChange, onRoleChange,
userId, userId,
isLoading isLoading,
completeInviteLink,
setCompleteInviteLink
}: Props) => { }: Props) => {
const router = useRouter(); const router = useRouter();
const [searchMemberFilter, setSearchMemberFilter] = useState(''); const [searchMemberFilter, setSearchMemberFilter] = useState('');
const {data: serverDetails } = useFetchServerStatus()
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
'addMember', 'addMember',
'removeMember', 'removeMember',
'upgradePlan' 'upgradePlan',
'setUpEmail'
] as const); ] as const);
const { const {
@@ -79,8 +86,11 @@ export const OrgMembersTable = ({
const onAddMember = async ({ email }: TAddMemberForm) => { const onAddMember = async ({ email }: TAddMemberForm) => {
await onInviteMember(email); await onInviteMember(email);
handlePopUpClose('addMember'); if (serverDetails?.emailConfigured){
reset(); handlePopUpClose('addMember');
}
reset();
}; };
const onRemoveOrgMemberApproved = async () => { const onRemoveOrgMemberApproved = async () => {
@@ -106,6 +116,11 @@ export const OrgMembersTable = ({
[members, searchMemberFilter] [members, searchMemberFilter]
); );
const copyTokenToClipboard = () => {
navigator.clipboard.writeText(completeInviteLink as string);
// setIsTokenCopied.on();
};
return ( return (
<div className="w-full"> <div className="w-full">
<div className="mb-4 flex"> <div className="mb-4 flex">
@@ -171,7 +186,7 @@ export const OrgMembersTable = ({
<SelectItem value="member">member</SelectItem> <SelectItem value="member">member</SelectItem>
</Select> </Select>
)} )}
{(status === 'invited' || status === 'verified') && ( {((status === 'invited' || status === 'verified') && serverDetails?.emailConfigured) && (
<Button className='w-40' colorSchema="secondary" onClick={() => onInviteMember(email)}> <Button className='w-40' colorSchema="secondary" onClick={() => onInviteMember(email)}>
Resend Invite Resend Invite
</Button> </Button>
@@ -232,20 +247,23 @@ export const OrgMembersTable = ({
isOpen={popUp?.addMember?.isOpen} isOpen={popUp?.addMember?.isOpen}
onOpenChange={(isOpen) => { onOpenChange={(isOpen) => {
handlePopUpToggle('addMember', isOpen); handlePopUpToggle('addMember', isOpen);
reset(); setCompleteInviteLink(undefined)
}} }}
> >
<ModalContent <ModalContent
title={`Invite others to ${orgName}`} title={`Invite others to ${orgName}`}
subTitle={ subTitle={
<> <div>
An invite is specific to an email address and expires after 1 day. {!completeInviteLink && <div>
<br /> An invite is specific to an email address and expires after 1 day.
For security reasons, you will need to separately add members to projects. <br />
</> For security reasons, you will need to separately add members to projects.
</div>}
{completeInviteLink && "This Infisical instance does not have a email provider setup. Please share this invite link with the invitee manually"}
</div>
} }
> >
<form onSubmit={handleSubmit(onAddMember)}> {!completeInviteLink && <form onSubmit={handleSubmit(onAddMember)} >
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@@ -274,7 +292,22 @@ export const OrgMembersTable = ({
Cancel Cancel
</Button> </Button>
</div> </div>
</form> </form>}
{
completeInviteLink &&
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{completeInviteLink}</p>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={copyTokenToClipboard}
>
<FontAwesomeIcon icon={false ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">click to copy</span>
</IconButton>
</div>
}
</ModalContent> </ModalContent>
</Modal> </Modal>
<DeleteActionModal <DeleteActionModal
@@ -289,6 +322,10 @@ export const OrgMembersTable = ({
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)} onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
text="You can add custom environments if you switch to Infisical's Team plan." text="You can add custom environments if you switch to Infisical's Team plan."
/> />
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</div> </div>
); );
}; };
@@ -1,7 +1,9 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider'; import { useNotificationContext } from '@app/components/context/Notifications/NotificationProvider';
import { Checkbox } from '@app/components/v2'; import { Checkbox, EmailServiceSetupModal } from '@app/components/v2';
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
import { usePopUp } from '@app/hooks/usePopUp';
import { useGetUser } from '../../../../hooks/api'; import { useGetUser } from '../../../../hooks/api';
import { User } from '../../../../hooks/api/types'; import { User } from '../../../../hooks/api/types';
@@ -11,6 +13,11 @@ export const SecuritySection = () => {
const [isMfaEnabled, setIsMfaEnabled] = useState(false); const [isMfaEnabled, setIsMfaEnabled] = useState(false);
const { data: user } = useGetUser(); const { data: user } = useGetUser();
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
'setUpEmail'
] as const);
const {data: serverDetails } = useFetchServerStatus()
useEffect(() => { useEffect(() => {
if (user && typeof user.isMfaEnabled !== 'undefined') { if (user && typeof user.isMfaEnabled !== 'undefined') {
@@ -42,6 +49,7 @@ export const SecuritySection = () => {
} }
return ( return (
<>
<form> <form>
<div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2"> <div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
<p className="mb-4 mt-2 text-xl font-semibold"> <p className="mb-4 mt-2 text-xl font-semibold">
@@ -52,12 +60,21 @@ export const SecuritySection = () => {
id="isTwoFAEnabled" id="isTwoFAEnabled"
isChecked={isMfaEnabled} isChecked={isMfaEnabled}
onCheckedChange={(state) => { onCheckedChange={(state) => {
toggleMfa(state as boolean); if (serverDetails?.emailConfigured){
toggleMfa(state as boolean);
} else {
handlePopUpOpen('setUpEmail');
}
}} }}
> >
Enable 2-factor authentication via your personal email. Enable 2-factor authentication via your personal email.
</Checkbox> </Checkbox>
</div> </div>
</form> </form>
<EmailServiceSetupModal
isOpen={popUp.setUpEmail?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
/>
</>
); );
}; };