mirror of
https://github.com/Infisical/infisical.git
synced 2025-03-29 22:02:57 +00:00
Resolve merge conflicts
This commit is contained in:
@ -25,7 +25,7 @@
|
||||
<img src="https://img.shields.io/github/commit-activity/m/infisical/infisical" alt="git commit activity" />
|
||||
</a>
|
||||
<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 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" />
|
||||
|
9
backend/jest.config.ts
Normal file
9
backend/jest.config.ts
Normal 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']
|
||||
};
|
111
backend/package-lock.json
generated
111
backend/package-lock.json
generated
@ -12,7 +12,7 @@
|
||||
"@aws-sdk/client-secrets-manager": "^3.267.0",
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/node": "^7.39.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
@ -40,13 +40,13 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.2.2",
|
||||
"posthog-node": "^2.5.4",
|
||||
"query-string": "^7.1.3",
|
||||
"request-ip": "^3.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"stripe": "^10.7.0",
|
||||
"swagger-autogen": "^2.22.0",
|
||||
"swagger-ui-express": "^4.6.0",
|
||||
"swagger-ui-express": "^4.6.2",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"typescript": "^4.9.3",
|
||||
@ -2800,13 +2800,13 @@
|
||||
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
|
||||
},
|
||||
"node_modules/@sentry/node": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.38.0.tgz",
|
||||
"integrity": "sha512-jNIN6NZvgzn/oms8RQzffjX8Z0LQDTN6N28nnhzqGCvnfmS1QtTt0FlU+pTuFXZNNSjfGy4XMXMYvLlbvhm2bg==",
|
||||
"version": "7.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.39.0.tgz",
|
||||
"integrity": "sha512-oe1OBxgs6t/FizjxkSPtuvJv5wJMO+mLENZkiE0PpBD56JyZrWK48kYIt2ccWAfk6Vh235/oIpmqET150xB4lQ==",
|
||||
"dependencies": {
|
||||
"@sentry/core": "7.38.0",
|
||||
"@sentry/types": "7.38.0",
|
||||
"@sentry/utils": "7.38.0",
|
||||
"@sentry/core": "7.39.0",
|
||||
"@sentry/types": "7.39.0",
|
||||
"@sentry/utils": "7.39.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
@ -2816,6 +2816,39 @@
|
||||
"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": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
@ -10500,9 +10533,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/posthog-node": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.3.tgz",
|
||||
"integrity": "sha512-kDmBjQHguPrh/rUTKmB0+Hj7C3fq2t+/fcfQkDBGz0f0fEF2WxV5yyxRmd2IF/hFmHxMrGLDkEVjKr78B+judg==",
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.4.tgz",
|
||||
"integrity": "sha512-CdywlVh0CZU05/3MrBc0qY/zsLdU2X9XSz/yL1qMRhbyZhD8lrnuGlI69G2cpzZtli6S/nu64wcmULz/mFFA5w==",
|
||||
"dependencies": {
|
||||
"axios": "^0.27.0"
|
||||
},
|
||||
@ -11483,9 +11516,9 @@
|
||||
"integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w=="
|
||||
},
|
||||
"node_modules/swagger-ui-express": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.1.tgz",
|
||||
"integrity": "sha512-Pss7YNFKNdq66XKNjRe4IRXKKYNx/LvOSml9TdrZ8/78UpxUHIp9JoXpXWA5Z4L+SCmX63DZ9IPlQ8nnRuncvA==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz",
|
||||
"integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==",
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": ">=4.11.0"
|
||||
},
|
||||
@ -14361,19 +14394,43 @@
|
||||
}
|
||||
},
|
||||
"@sentry/node": {
|
||||
"version": "7.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.38.0.tgz",
|
||||
"integrity": "sha512-jNIN6NZvgzn/oms8RQzffjX8Z0LQDTN6N28nnhzqGCvnfmS1QtTt0FlU+pTuFXZNNSjfGy4XMXMYvLlbvhm2bg==",
|
||||
"version": "7.39.0",
|
||||
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-7.39.0.tgz",
|
||||
"integrity": "sha512-oe1OBxgs6t/FizjxkSPtuvJv5wJMO+mLENZkiE0PpBD56JyZrWK48kYIt2ccWAfk6Vh235/oIpmqET150xB4lQ==",
|
||||
"requires": {
|
||||
"@sentry/core": "7.38.0",
|
||||
"@sentry/types": "7.38.0",
|
||||
"@sentry/utils": "7.38.0",
|
||||
"@sentry/core": "7.39.0",
|
||||
"@sentry/types": "7.39.0",
|
||||
"@sentry/utils": "7.39.0",
|
||||
"cookie": "^0.4.1",
|
||||
"https-proxy-agent": "^5.0.0",
|
||||
"lru_map": "^0.3.3",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"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": {
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
|
||||
@ -20049,9 +20106,9 @@
|
||||
}
|
||||
},
|
||||
"posthog-node": {
|
||||
"version": "2.5.3",
|
||||
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.3.tgz",
|
||||
"integrity": "sha512-kDmBjQHguPrh/rUTKmB0+Hj7C3fq2t+/fcfQkDBGz0f0fEF2WxV5yyxRmd2IF/hFmHxMrGLDkEVjKr78B+judg==",
|
||||
"version": "2.5.4",
|
||||
"resolved": "https://registry.npmjs.org/posthog-node/-/posthog-node-2.5.4.tgz",
|
||||
"integrity": "sha512-CdywlVh0CZU05/3MrBc0qY/zsLdU2X9XSz/yL1qMRhbyZhD8lrnuGlI69G2cpzZtli6S/nu64wcmULz/mFFA5w==",
|
||||
"requires": {
|
||||
"axios": "^0.27.0"
|
||||
},
|
||||
@ -20779,9 +20836,9 @@
|
||||
"integrity": "sha512-4J4XekQG0ol4/TyUzMfksrWsMTbw/7JYlT+SFaX7H0xamd1OeuVlUSb/Cbq4qdDx1lc+uLZQW7u2mlImcE8c+w=="
|
||||
},
|
||||
"swagger-ui-express": {
|
||||
"version": "4.6.1",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.1.tgz",
|
||||
"integrity": "sha512-Pss7YNFKNdq66XKNjRe4IRXKKYNx/LvOSml9TdrZ8/78UpxUHIp9JoXpXWA5Z4L+SCmX63DZ9IPlQ8nnRuncvA==",
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.6.2.tgz",
|
||||
"integrity": "sha512-MHIOaq9JrTTB3ygUJD+08PbjM5Tt/q7x80yz9VTFIatw8j5uIWKcr90S0h5NLMzFEDC6+eVprtoeA5MDZXCUKQ==",
|
||||
"requires": {
|
||||
"swagger-ui-dist": ">=4.11.0"
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
"@aws-sdk/client-secrets-manager": "^3.267.0",
|
||||
"@godaddy/terminus": "^4.11.2",
|
||||
"@octokit/rest": "^19.0.5",
|
||||
"@sentry/node": "^7.14.0",
|
||||
"@sentry/node": "^7.39.0",
|
||||
"@sentry/tracing": "^7.19.0",
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/libsodium-wrappers": "^0.7.10",
|
||||
@ -31,13 +31,13 @@
|
||||
"lodash": "^4.17.21",
|
||||
"mongoose": "^6.7.2",
|
||||
"nodemailer": "^6.8.0",
|
||||
"posthog-node": "^2.2.2",
|
||||
"posthog-node": "^2.5.4",
|
||||
"query-string": "^7.1.3",
|
||||
"request-ip": "^3.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"stripe": "^10.7.0",
|
||||
"swagger-autogen": "^2.22.0",
|
||||
"swagger-ui-express": "^4.6.0",
|
||||
"swagger-ui-express": "^4.6.2",
|
||||
"tweetnacl": "^1.0.3",
|
||||
"tweetnacl-util": "^0.15.1",
|
||||
"typescript": "^4.9.3",
|
||||
@ -101,17 +101,6 @@
|
||||
"ts-jest": "^29.0.3",
|
||||
"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": {
|
||||
"outputDirectory": "reports",
|
||||
"outputName": "jest-junit.xml",
|
||||
|
@ -5,12 +5,12 @@ export const getEncryptionKey = () => infisical.get('ENCRYPTION_KEY')!;
|
||||
export const getSaltRounds = () => parseInt(infisical.get('SALT_ROUNDS')!) || 10;
|
||||
export const getJwtAuthLifetime = () => infisical.get('JWT_AUTH_LIFETIME')! || '10d';
|
||||
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 getJwtRefreshLifetime = () => infisical.get('JWT_REFRESH_LIFETIME')! || '90d';
|
||||
export const getJwtRefreshSecret = () => infisical.get('JWT_REFRESH_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 getMongoURL = () => infisical.get('MONGO_URL')!;
|
||||
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 getSiteURL = () => infisical.get('SITE_URL')!;
|
||||
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 getSmtpUsername = () => infisical.get('SMTP_USERNAME')!;
|
||||
export const getSmtpPassword = () => infisical.get('SMTP_PASSWORD')!;
|
||||
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 getStripeProductPro = () => infisical.get('STRIPE_PRODUCT_PRO')!;
|
||||
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 getStripeWebhookSecret = () => infisical.get('STRIPE_WEBHOOK_SECRET')!;
|
||||
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 { TokenService } from '../../services';
|
||||
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
|
||||
@ -99,9 +99,11 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
||||
* @returns
|
||||
*/
|
||||
export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
let invitee, inviteeMembershipOrg;
|
||||
let invitee, inviteeMembershipOrg, completeInviteLink;
|
||||
try {
|
||||
const { organizationId, inviteeEmail } = req.body;
|
||||
const host = req.headers.host;
|
||||
const siteUrl = `${req.protocol}://${host}`;
|
||||
|
||||
// validate membership
|
||||
const membershipOrg = await MembershipOrg.findOne({
|
||||
@ -181,6 +183,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
callback_url: getSiteURL() + '/signupinvite'
|
||||
}
|
||||
});
|
||||
|
||||
if (!getSmtpConfigured()) {
|
||||
completeInviteLink = `${siteUrl + '/signupinvite'}?token=${token}&to=${inviteeEmail}`
|
||||
}
|
||||
}
|
||||
|
||||
await updateSubscriptionOrgQuantity({ organizationId });
|
||||
@ -193,7 +199,8 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
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)
|
||||
throw new Error('Failed to find any invitations for email');
|
||||
|
||||
|
||||
await TokenService.validateToken({
|
||||
type: TOKEN_EMAIL_ORG_INVITATION,
|
||||
email,
|
||||
|
@ -7,7 +7,7 @@ import {
|
||||
} from '../../helpers/signup';
|
||||
import { createToken } from '../../helpers/auth';
|
||||
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
|
||||
@ -21,7 +21,7 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
|
||||
try {
|
||||
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
|
||||
const userCount = await User.countDocuments({})
|
||||
if (userCount != 0) {
|
||||
@ -66,7 +66,7 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
const { email, code } = req.body;
|
||||
|
||||
// initialize user account
|
||||
user = await User.findOne({ email });
|
||||
user = await User.findOne({ email }).select('+publicKey');
|
||||
if (user && user?.publicKey) {
|
||||
// case: user has already completed account
|
||||
return res.status(403).send({
|
||||
@ -75,10 +75,12 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// verify email
|
||||
await checkEmailVerification({
|
||||
email,
|
||||
code
|
||||
});
|
||||
if (getSmtpConfigured()) {
|
||||
await checkEmailVerification({
|
||||
email,
|
||||
code
|
||||
});
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
user = await new User({
|
||||
|
@ -87,7 +87,7 @@ export const login1 = async (req: Request, res: Response) => {
|
||||
*/
|
||||
export const login2 = async (req: Request, res: Response) => {
|
||||
try {
|
||||
|
||||
|
||||
if (!req.headers['user-agent']) throw InternalServerError({ message: 'User-Agent header is required' });
|
||||
|
||||
const { email, clientProof } = req.body;
|
||||
@ -127,12 +127,12 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
expiresIn: getJwtMfaLifetime(),
|
||||
secret: getJwtMfaSecret()
|
||||
});
|
||||
|
||||
|
||||
const code = await TokenService.createToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email
|
||||
});
|
||||
|
||||
|
||||
// send MFA code [code] to [email]
|
||||
await sendMail({
|
||||
template: 'emailMfa.handlebars',
|
||||
@ -142,13 +142,13 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
code
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
mfaEnabled: true,
|
||||
token
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.ip,
|
||||
@ -181,7 +181,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
iv?: string;
|
||||
tag?: string;
|
||||
}
|
||||
|
||||
|
||||
const response: ResponseData = {
|
||||
mfaEnabled: false,
|
||||
encryptionVersion: user.encryptionVersion,
|
||||
@ -191,7 +191,7 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
iv: user.iv,
|
||||
tag: user.tag
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
user?.protectedKey &&
|
||||
user?.protectedKeyIV &&
|
||||
@ -206,14 +206,14 @@ export const login2 = async (req: Request, res: Response) => {
|
||||
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(response);
|
||||
}
|
||||
|
||||
@ -244,7 +244,7 @@ export const sendMfaToken = async (req: Request, res: Response) => {
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email
|
||||
});
|
||||
|
||||
|
||||
// send MFA code [code] to [email]
|
||||
await sendMail({
|
||||
template: 'emailMfa.handlebars',
|
||||
@ -259,9 +259,9 @@ export const sendMfaToken = async (req: Request, res: Response) => {
|
||||
Sentry.captureException(err);
|
||||
return res.status(400).send({
|
||||
message: 'Failed to send MFA code'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return res.status(200).send({
|
||||
message: 'Successfully sent new MFA code'
|
||||
});
|
||||
@ -274,75 +274,87 @@ export const sendMfaToken = async (req: Request, res: Response) => {
|
||||
* @param res
|
||||
*/
|
||||
export const verifyMfaToken = async (req: Request, res: Response) => {
|
||||
const { email, mfaToken } = req.body;
|
||||
const { email, mfaToken } = req.body;
|
||||
|
||||
await TokenService.validateToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email,
|
||||
token: mfaToken
|
||||
});
|
||||
await TokenService.validateToken({
|
||||
type: TOKEN_EMAIL_MFA,
|
||||
email,
|
||||
token: mfaToken
|
||||
});
|
||||
|
||||
const user = await User.findOne({
|
||||
email
|
||||
}).select('+salt +verifier +encryptionVersion +protectedKey +protectedKeyIV +protectedKeyTag +publicKey +encryptedPrivateKey +iv +tag');
|
||||
const user = await User.findOne({
|
||||
email
|
||||
}).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({
|
||||
user,
|
||||
ip: req.ip,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
await checkUserDevice({
|
||||
user,
|
||||
ip: req.ip,
|
||||
userAgent: req.headers['user-agent'] ?? ''
|
||||
});
|
||||
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||
// issue tokens
|
||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
||||
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie('jid', tokens.refreshToken, {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
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;
|
||||
}
|
||||
// store (refresh) token in httpOnly cookie
|
||||
res.cookie('jid', tokens.refreshToken, {
|
||||
httpOnly: true,
|
||||
path: '/',
|
||||
sameSite: 'strict',
|
||||
secure: getNodeEnv() === 'production' ? true : false
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
interface VerifyMfaTokenRes {
|
||||
encryptionVersion: number;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
token: string;
|
||||
publicKey: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
tag: string;
|
||||
}
|
||||
|
||||
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
|
||||
});
|
||||
interface VerifyMfaTokenRes {
|
||||
encryptionVersion: number;
|
||||
protectedKey?: string;
|
||||
protectedKeyIV?: string;
|
||||
protectedKeyTag?: string;
|
||||
token: string;
|
||||
publicKey: string;
|
||||
encryptedPrivateKey: string;
|
||||
iv: string;
|
||||
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);
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import handlebars from 'handlebars';
|
||||
import nodemailer from 'nodemailer';
|
||||
import { getSmtpFromName, getSmtpFromAddress } from '../config';
|
||||
import { getSmtpFromName, getSmtpFromAddress, getSmtpConfigured } from '../config';
|
||||
|
||||
let smtpTransporter: nodemailer.Transporter;
|
||||
|
||||
@ -25,23 +25,25 @@ const sendMail = async ({
|
||||
recipients: string[];
|
||||
substitutions: any;
|
||||
}) => {
|
||||
try {
|
||||
const html = fs.readFileSync(
|
||||
path.resolve(__dirname, '../templates/' + template),
|
||||
'utf8'
|
||||
);
|
||||
const temp = handlebars.compile(html);
|
||||
const htmlToSend = temp(substitutions);
|
||||
if (getSmtpConfigured()) {
|
||||
try {
|
||||
const html = fs.readFileSync(
|
||||
path.resolve(__dirname, '../templates/' + template),
|
||||
'utf8'
|
||||
);
|
||||
const temp = handlebars.compile(html);
|
||||
const htmlToSend = temp(substitutions);
|
||||
|
||||
await smtpTransporter.sendMail({
|
||||
from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`,
|
||||
to: recipients.join(', '),
|
||||
subject: subjectLine,
|
||||
html: htmlToSend
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
await smtpTransporter.sendMail({
|
||||
from: `"${getSmtpFromName()}" <${getSmtpFromAddress()}>`,
|
||||
to: recipients.join(', '),
|
||||
subject: subjectLine,
|
||||
html: htmlToSend
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.setUser(null);
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -23,29 +23,29 @@ const swaggerFile = require('../spec.json');
|
||||
const requestIp = require('request-ip');
|
||||
import { apiLimiter } from './helpers/rateLimiter';
|
||||
import {
|
||||
workspace as eeWorkspaceRouter,
|
||||
secret as eeSecretRouter,
|
||||
secretSnapshot as eeSecretSnapshotRouter,
|
||||
action as eeActionRouter
|
||||
workspace as eeWorkspaceRouter,
|
||||
secret as eeSecretRouter,
|
||||
secretSnapshot as eeSecretSnapshotRouter,
|
||||
action as eeActionRouter
|
||||
} from './ee/routes/v1';
|
||||
import {
|
||||
signup as v1SignupRouter,
|
||||
auth as v1AuthRouter,
|
||||
bot as v1BotRouter,
|
||||
organization as v1OrganizationRouter,
|
||||
workspace as v1WorkspaceRouter,
|
||||
membershipOrg as v1MembershipOrgRouter,
|
||||
membership as v1MembershipRouter,
|
||||
key as v1KeyRouter,
|
||||
inviteOrg as v1InviteOrgRouter,
|
||||
user as v1UserRouter,
|
||||
userAction as v1UserActionRouter,
|
||||
secret as v1SecretRouter,
|
||||
serviceToken as v1ServiceTokenRouter,
|
||||
password as v1PasswordRouter,
|
||||
stripe as v1StripeRouter,
|
||||
integration as v1IntegrationRouter,
|
||||
integrationAuth as v1IntegrationAuthRouter
|
||||
signup as v1SignupRouter,
|
||||
auth as v1AuthRouter,
|
||||
bot as v1BotRouter,
|
||||
organization as v1OrganizationRouter,
|
||||
workspace as v1WorkspaceRouter,
|
||||
membershipOrg as v1MembershipOrgRouter,
|
||||
membership as v1MembershipRouter,
|
||||
key as v1KeyRouter,
|
||||
inviteOrg as v1InviteOrgRouter,
|
||||
user as v1UserRouter,
|
||||
userAction as v1UserActionRouter,
|
||||
secret as v1SecretRouter,
|
||||
serviceToken as v1ServiceTokenRouter,
|
||||
password as v1PasswordRouter,
|
||||
stripe as v1StripeRouter,
|
||||
integration as v1IntegrationRouter,
|
||||
integrationAuth as v1IntegrationAuthRouter
|
||||
} from './routes/v1';
|
||||
import {
|
||||
signup as v2SignupRouter,
|
||||
@ -66,7 +66,7 @@ import { getLogger } from './utils/logger';
|
||||
import { RouteNotFoundError } from './utils/errors';
|
||||
import { requestErrorHandler } from './middleware/requestErrorHandler';
|
||||
import {
|
||||
getMongoURL,
|
||||
getMongoURL,
|
||||
getNodeEnv,
|
||||
getPort,
|
||||
getSentryDSN,
|
||||
@ -74,10 +74,12 @@ import {
|
||||
} from './config';
|
||||
|
||||
const main = async () => {
|
||||
await infisical.connect({
|
||||
token: process.env.INFISICAL_TOKEN!
|
||||
});
|
||||
|
||||
if (process.env.INFISICAL_TOKEN != "" || process.env.INFISICAL_TOKEN != undefined) {
|
||||
await infisical.connect({
|
||||
token: process.env.INFISICAL_TOKEN!
|
||||
});
|
||||
}
|
||||
|
||||
logTelemetryMessage();
|
||||
setTransporter(initSmtp());
|
||||
|
||||
@ -160,7 +162,7 @@ const main = async () => {
|
||||
|
||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||
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` }))
|
||||
})
|
||||
|
||||
@ -172,7 +174,7 @@ const main = async () => {
|
||||
|
||||
createTestUserForDevelopment();
|
||||
setUpHealthEndpoint(server);
|
||||
|
||||
|
||||
server.on('close', async () => {
|
||||
await DatabaseService.closeDatabase();
|
||||
})
|
||||
|
@ -1,4 +1,5 @@
|
||||
import express, { Request, Response } from 'express';
|
||||
import { getSmtpConfigured } from '../../config';
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
@ -8,6 +9,7 @@ router.get(
|
||||
res.status(200).json({
|
||||
date: new Date(),
|
||||
message: 'Ok',
|
||||
emailConfigured: getSmtpConfigured()
|
||||
})
|
||||
}
|
||||
);
|
||||
|
5
backend/tests/example.test.ts
Normal file
5
backend/tests/example.test.ts
Normal 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');
|
||||
expect(res.status).toEqual(200);
|
||||
});
|
||||
});
|
||||
});
|
@ -4,7 +4,10 @@ go 1.19
|
||||
|
||||
require (
|
||||
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/reflow v0.3.0
|
||||
github.com/muesli/roff v0.1.0
|
||||
github.com/spf13/cobra v1.6.1
|
||||
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/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // 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/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/mtibben/percent v0.2.1 // indirect
|
||||
|
@ -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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
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/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
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/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/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/go.mod h1:5XFpbC8jY5UUv89YQciiXNlbi+iJgt29VDC5xbzrLL4=
|
||||
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-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe7Sg=
|
||||
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/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
|
||||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
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/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
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/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-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
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/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
|
@ -263,10 +263,10 @@ var secretsSetCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
// Print secret operations
|
||||
headers := []string{"SECRET NAME", "SECRET VALUE", "STATUS"}
|
||||
rows := [][]string{}
|
||||
headers := [...]string{"SECRET NAME", "SECRET VALUE", "STATUS"}
|
||||
rows := [][3]string{}
|
||||
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)
|
||||
|
@ -3,12 +3,12 @@ package visualize
|
||||
import "github.com/Infisical/infisical-merge/packages/models"
|
||||
|
||||
func PrintAllSecretDetails(secrets []models.SingleEnvironmentVariable) {
|
||||
rows := [][]string{}
|
||||
rows := [][3]string{}
|
||||
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)
|
||||
}
|
||||
|
@ -2,8 +2,14 @@ package visualize
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"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 {
|
||||
@ -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
|
||||
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.SetOutputMirror(os.Stdout)
|
||||
t.SetStyle(table.StyleLight)
|
||||
@ -35,7 +68,11 @@ func Table(headers []string, rows [][]string) {
|
||||
t.AppendHeader(tableHeaders)
|
||||
for _, row := range rows {
|
||||
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)
|
||||
}
|
||||
t.AppendRow(tableRow)
|
||||
@ -43,3 +80,28 @@ func Table(headers []string, rows [][]string) {
|
||||
|
||||
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
|
||||
}
|
||||
|
176
cloudformation/ec2-deployment/infisical-ec2-deployment.template
Normal file
176
cloudformation/ec2-deployment/infisical-ec2-deployment.template
Normal file
@ -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: []
|
@ -4,7 +4,7 @@ description: "Frequently Asked Questions about Infisical"
|
||||
---
|
||||
|
||||
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">
|
||||
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?">
|
||||
Yes. This is simply a configuration file and contains no sensitive data.
|
||||
</Accordion>
|
||||
</Accordion>
|
||||
|
@ -30,6 +30,8 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical
|
||||
>
|
||||
Learn how to configure and deploy Infisical.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
<Card
|
||||
href="/integrations/overview"
|
||||
title="Integrations"
|
||||
@ -38,16 +40,3 @@ Start syncing environment variables with [Infisical Cloud](https://app.infisical
|
||||
>
|
||||
Explore integrations for Docker, AWS, Heroku, etc.
|
||||
</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>
|
||||
|
BIN
docs/images/deploy-aws-button.png
Normal file
BIN
docs/images/deploy-aws-button.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -39,10 +39,10 @@ Prerequisites:
|
||||
Starting your service with the Infisical CLI pulls your secrets from Infisical and injects them into your service.
|
||||
|
||||
```dockerfile
|
||||
CMD ["infisical", "run", "---", "[your service start command]"]
|
||||
CMD ["infisical", "run", "--", "[your service start command]"]
|
||||
|
||||
# example with single single command
|
||||
CMD ["infisical", "run", "---", "npm", "run", "start"]
|
||||
CMD ["infisical", "run", "--", "npm", "run", "start"]
|
||||
|
||||
# example with multiple commands
|
||||
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
|
||||
|
||||
```bash
|
||||
docker run --env INFISICAL_TOKEN=[token]...
|
||||
docker run --env INFISICAL_TOKEN=[token] [DOCKER-IMAGE]...
|
||||
```
|
||||
|
||||
<Info>
|
||||
|
@ -44,11 +44,6 @@
|
||||
"icon": "shield-halved",
|
||||
"url": "security"
|
||||
},
|
||||
{
|
||||
"name": "Self-hosting",
|
||||
"icon": "server",
|
||||
"url": "self-hosting"
|
||||
},
|
||||
{
|
||||
"name": "SDKs",
|
||||
"icon": "puzzle-piece",
|
||||
@ -101,6 +96,14 @@
|
||||
"getting-started/dashboard/token"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Self-hosting",
|
||||
"pages": [
|
||||
"self-hosting/overview",
|
||||
"self-hosting/configuration/envars",
|
||||
"self-hosting/configuration/email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Command line",
|
||||
"pages": [
|
||||
@ -171,12 +174,6 @@
|
||||
"integrations/platforms/pm2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Self-hosting",
|
||||
"pages": [
|
||||
"self-hosting/overview"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Deployment options",
|
||||
"pages": [
|
||||
@ -184,13 +181,6 @@
|
||||
"self-hosting/deployments/kubernetes"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Configuration",
|
||||
"pages": [
|
||||
"self-hosting/configuration/envars",
|
||||
"self-hosting/configuration/email"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Overview",
|
||||
"pages": [
|
||||
|
@ -1,5 +1,5 @@
|
||||
---
|
||||
title: "Email"
|
||||
title: "Configure email service"
|
||||
description: "How to configure your email when self-hosting Infisical."
|
||||
---
|
||||
|
||||
|
@ -1,43 +1,151 @@
|
||||
---
|
||||
title: "Environment Variables"
|
||||
description: "How to configure your environment variables when self-hosting Infisical."
|
||||
title: "All environment variables"
|
||||
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 |
|
||||
| ----------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `ENCRYPTION_KEY` | ❗️ Strong hex encryption key | `None` |
|
||||
| `JWT_SIGNUP_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_REFRESH_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_AUTH_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_MFA_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `JWT_SERVICE_SECRET` | ❗️ JWT token secret | `None` |
|
||||
| `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` |
|
||||
| `JWT_AUTH_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `10d` |
|
||||
| `JWT_MFA_LIFETIME` | JWT token lifetime expressed in seconds or a string describing a time span (e.g. 60, "2 days", "10h", "7d") | `5m` |
|
||||
| `MONGO_URL` | ❗️ MongoDB instance connection string either to container instance or MongoDB Cloud | `None` |
|
||||
| `MONGO_USERNAME` | MongoDB username if using container | `None` |
|
||||
| `MONGO_PASSWORD` | MongoDB password if using container | `None` |
|
||||
| `SITE_URL` | ❗️ Site URL - should be an absolute URL including the protocol (e.g. `https://app.infisical.com`) | `None` |
|
||||
| `SMTP_HOST` | ❗️ Hostname to connect to for establishing SMTP connections | `None` |
|
||||
| `SMTP_USERNAME` | ❗️ Credential to connect to host (e.g. `team@infisical.com`) | `None` |
|
||||
| `SMTP_PASSWORD` | ❗️ Credential to connect to host | `None` |
|
||||
| `SMTP_PORT` | Port to connect to for establishing SMTP connections | `587` |
|
||||
| `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. `team@infisical.com`) | `None` |
|
||||
| `SMTP_FROM_NAME` | Name label to be used in From field (e.g. `Team`) | `Infisical` |
|
||||
| `TELEMETRY_ENABLED` | `true` or `false`. [More](../overview). | `true` |
|
||||
| `LICENSE_KEY` | License key if using Infisical Enterprise Edition | `true` |
|
||||
| `CLIENT_ID_HEROKU` | OAuth2 client ID for Heroku integration | `None` |
|
||||
| `CLIENT_ID_VERCEL` | OAuth2 client ID for Vercel integration | `None` |
|
||||
| `CLIENT_ID_NETLIFY` | OAuth2 client ID for Netlify integration | `None` |
|
||||
| `CLIENT_ID_GITHUB` | OAuth2 client ID for GitHub integration | `None` |
|
||||
| `CLIENT_SECRET_HEROKU` | OAuth2 client secret for Heroku integration | `None` |
|
||||
| `CLIENT_SECRET_VERCEL` | OAuth2 client secret for Vercel integration | `None` |
|
||||
| `CLIENT_SECRET_NETLIFY` | OAuth2 client secret for Netlify integration | `None` |
|
||||
| `CLIENT_SECRET_GITHUB` | OAuth2 client secret for GitHub integration | `None` |
|
||||
| `CLIENT_SLUG_VERCEL` | OAuth2 slug for Netlify integration | `None` |
|
||||
| `SENTRY_DSN` | DSN for error-monitoring with Sentry | `None` |
|
||||
| `INVITE_ONLY_SIGNUP` | If true, users can only sign up if they are invited | `false` |
|
||||
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.
|
||||
|
||||
<Tabs>
|
||||
<Tab title="Required">
|
||||
<ParamField query="ENCRYPTION_KEY" type="string" default="none" required>
|
||||
Must be a random 32 character length hex string
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="JWT_SIGNUP_SECRET" type="string" default="none" required>
|
||||
Must be a random 32 character length hex string
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="JWT_REFRESH_SECRET" type="string" default="none" required>
|
||||
Must be a random 32 character length hex string
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="JWT_AUTH_SECRET" type="string" default="none" required>
|
||||
Must be a random 32 character length hex string
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="JWT_MFA_SECRET" type="string" default="none" required>
|
||||
Must be a random 32 character length hex string
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="JWT_SERVICE_SECRET" type="string" default="none" required>
|
||||
Must be a random 32 character length hex string
|
||||
</ParamField>
|
||||
|
||||
<ParamField query="MONGO_URL" type="string" default="none" required>
|
||||
*TLS based connection string is not yet supported
|
||||
</ParamField>
|
||||
</Tab>
|
||||
<Tab title="Email service">
|
||||
<Info>When email service is not configured, Infisical will have limited functionality</Info>
|
||||
|
||||
<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. team@infisical.com)
|
||||
</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>
|
||||
|
@ -1,36 +1,198 @@
|
||||
---
|
||||
title: "Overview"
|
||||
description: "Infisical is an open-source end-to-end encrypted secrets manager that developers can set up within 15 minutes."
|
||||
title: "Deployment options"
|
||||
description: "Explore deployment options for self hosting Infisical"
|
||||
---
|
||||
|
||||
<Info>
|
||||
Self-host vs. Infisical Cloud
|
||||
To meet various compliance requirements, you may want to self-host Infisical instead of using [Infisical Cloud](https://app.infisical.com/).
|
||||
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}>
|
||||
<Card title="Any Linux" icon="square-1" color="#ea5a0c" href="/self-hosting/deployments/linux">
|
||||
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>
|
||||
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.
|
||||
Refer to the available [environment variables](/self-hosting/configuration/envars) to learn more
|
||||
|
||||
## 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 { 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 getWorkspaces from '@app/pages/api/workspace/getWorkspaces';
|
||||
|
||||
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.
|
||||
@ -14,6 +17,10 @@ export default function TeamInviteStep(): JSX.Element {
|
||||
const [emails, setEmails] = useState('');
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const {data: serverDetails } = useFetchServerStatus()
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
|
||||
'setUpEmail'
|
||||
] as const);
|
||||
|
||||
// Redirect user to the getting started page
|
||||
const redirectToHome = async () => {
|
||||
@ -62,10 +69,20 @@ export default function TeamInviteStep(): JSX.Element {
|
||||
</div>
|
||||
<Button
|
||||
text={t('signup:step5-send-invites') ?? ''}
|
||||
onButtonPressed={() => inviteUsers({ emails })}
|
||||
onButtonPressed={() => {
|
||||
if(serverDetails?.emailConfigured){
|
||||
inviteUsers({ emails })
|
||||
}else{
|
||||
handlePopUpOpen('setUpEmail');
|
||||
}
|
||||
}}
|
||||
size="lg"
|
||||
/>
|
||||
</div>
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -4,13 +4,11 @@ const POSTHOG_HOST =
|
||||
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
|
||||
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
|
||||
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
|
||||
const SITE_URL = "https://app.infisical.com";
|
||||
|
||||
export {
|
||||
ENV,
|
||||
POSTHOG_API_KEY,
|
||||
POSTHOG_HOST,
|
||||
SITE_URL,
|
||||
STRIPE_PRODUCT_PRO,
|
||||
STRIPE_PRODUCT_STARTER
|
||||
};
|
@ -1,7 +1,5 @@
|
||||
import { jsPDF } from 'jspdf';
|
||||
|
||||
import { SITE_URL } from './config';
|
||||
|
||||
interface PDFProps {
|
||||
personalName: string;
|
||||
personalEmail: string;
|
||||
@ -19,7 +17,7 @@ const yyyy = today.getFullYear();
|
||||
const todayFormatted = `${mm}/${dd}/${yyyy}`;
|
||||
|
||||
|
||||
function createPdfHeader(doc: jsPDF, personalName : string) {
|
||||
function createPdfHeader(doc: jsPDF, personalName: string) {
|
||||
doc.setFillColor(255, 255, 255);
|
||||
doc.rect(0, 0, 600, 900, 'F');
|
||||
doc.setTextColor(23, 23, 23);
|
||||
@ -30,6 +28,10 @@ function createPdfHeader(doc: jsPDF, personalName : 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.text(
|
||||
'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.setTextColor(23, 23, 23);
|
||||
doc.setFontSize(14);
|
||||
doc.text(`${SITE_URL}/login`, 180, 420);
|
||||
doc.text(`${siteURL}/login`, 180, 420);
|
||||
doc.text(personalEmail, 180, 465);
|
||||
doc.text(generatedKey, 180, 510);
|
||||
doc.text('Need help? Contact us at support@infisical.com', 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';
|
@ -3,6 +3,7 @@ export * from './Card';
|
||||
export * from './Checkbox';
|
||||
export * from './DeleteActionModal';
|
||||
export * from './Dropdown';
|
||||
export * from './EmailServiceSetupModal'
|
||||
export * from './EmptyState';
|
||||
export * from './FormControl';
|
||||
export * from './IconButton';
|
||||
|
1
frontend/src/hooks/api/serverDetails/index.ts
Normal file
1
frontend/src/hooks/api/serverDetails/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { useFetchServerStatus } from './queries'
|
19
frontend/src/hooks/api/serverDetails/queries.tsx
Normal file
19
frontend/src/hooks/api/serverDetails/queries.tsx
Normal file
@ -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 });
|
||||
}
|
5
frontend/src/hooks/api/serverDetails/types.ts
Normal file
5
frontend/src/hooks/api/serverDetails/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type ServerStatus = {
|
||||
date: string;
|
||||
message: string;
|
||||
emailConfigured: boolean;
|
||||
};
|
@ -83,8 +83,14 @@ export const useAddUserToWs = () => {
|
||||
|
||||
export const useAddUserToOrg = () => {
|
||||
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),
|
||||
onSuccess: (_, { organizationId }) => {
|
||||
queryClient.invalidateQueries(userKeys.getOrgUsers(organizationId));
|
||||
|
@ -13,6 +13,7 @@ import TeamInviteStep from '@app/components/signup/TeamInviteStep';
|
||||
import UserInfoStep from '@app/components/signup/UserInfoStep';
|
||||
import SecurityClient from '@app/components/utilities/SecurityClient';
|
||||
import { getTranslatedStaticProps } from '@app/components/utilities/withTranslateProps';
|
||||
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
|
||||
|
||||
import checkEmailVerificationCode from './api/auth/CheckEmailVerificationCode';
|
||||
import getWorkspaces from './api/workspace/getWorkspaces';
|
||||
@ -25,10 +26,12 @@ export default function SignUp() {
|
||||
const [password, setPassword] = useState('');
|
||||
const [firstName, setFirstName] = useState('');
|
||||
const [lastName, setLastName] = useState('');
|
||||
const [code, setCode] = useState('');
|
||||
const [code, setCode] = useState('123456');
|
||||
const [codeError, setCodeError] = useState(false);
|
||||
const [step, setStep] = useState(1);
|
||||
const router = useRouter();
|
||||
const {data: serverDetails } = useFetchServerStatus()
|
||||
|
||||
|
||||
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 (
|
||||
<div className="bg-bunker-800 h-screen flex flex-col items-center justify-center">
|
||||
<Head>
|
||||
@ -111,9 +127,7 @@ export default function SignUp() {
|
||||
password={password}
|
||||
name={`${firstName} ${lastName}`}
|
||||
/>
|
||||
) : (
|
||||
<TeamInviteStep />
|
||||
)}
|
||||
) : (serverDetails?.emailConfigured ? <TeamInviteStep /> : "")}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -6,12 +6,19 @@ import Link from 'next/link';
|
||||
import Button from '@app/components/basic/buttons/Button';
|
||||
import InputField from '@app/components/basic/InputField';
|
||||
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';
|
||||
|
||||
export default function VerifyEmail() {
|
||||
const [email, setEmail] = useState('');
|
||||
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.
|
||||
@ -63,7 +70,13 @@ export default function VerifyEmail() {
|
||||
</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="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>
|
||||
@ -80,6 +93,11 @@ export default function VerifyEmail() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { plans } from 'public/data/frequentConstants';
|
||||
|
||||
@ -55,6 +56,8 @@ export const OrgSettingsPage = () => {
|
||||
const addIncidentContact = useAddIncidentContact();
|
||||
const removeIncidentContact = useDeleteIncidentContact();
|
||||
|
||||
const [completeInviteLink, setcompleteInviteLink] = useState<string|undefined>("")
|
||||
|
||||
const isMoreUsersNotAllowed =
|
||||
(orgUsers || []).length >= 5 &&
|
||||
subscriptionPlan === plans.starter &&
|
||||
@ -100,11 +103,16 @@ export const OrgSettingsPage = () => {
|
||||
if (!currentOrg?._id) return;
|
||||
|
||||
try {
|
||||
await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email });
|
||||
createNotification({
|
||||
text: 'Successfully invited user to the organization.',
|
||||
type: 'success'
|
||||
});
|
||||
const {data} = await addUserToOrg.mutateAsync({ organizationId: currentOrg?._id, inviteeEmail: email });
|
||||
setcompleteInviteLink(data?.completeInviteLink)
|
||||
|
||||
// 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) {
|
||||
console.error(error);
|
||||
createNotification({
|
||||
@ -249,6 +257,8 @@ export const OrgSettingsPage = () => {
|
||||
onRemoveMember={onRemoveUserOrgMembership}
|
||||
onRoleChange={onUpdateOrgUserRole}
|
||||
onGrantAccess={onGrantUserAccess}
|
||||
completeInviteLink={completeInviteLink}
|
||||
setCompleteInviteLink={setcompleteInviteLink}
|
||||
/>
|
||||
</div>
|
||||
<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 {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmailServiceSetupModal,
|
||||
EmptyState,
|
||||
FormControl,
|
||||
IconButton,
|
||||
@ -29,6 +30,7 @@ import {
|
||||
Tr
|
||||
} from '@app/components/v2';
|
||||
import { usePopUp } from '@app/hooks';
|
||||
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
|
||||
import { IncidentContact } from '@app/hooks/api/types';
|
||||
|
||||
type Props = {
|
||||
@ -51,9 +53,11 @@ export const OrgIncidentContactsTable = ({
|
||||
isLoading
|
||||
}: Props) => {
|
||||
const [searchContact, setSearchContact] = useState('');
|
||||
const {data: serverDetails } = useFetchServerStatus()
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
'addContact',
|
||||
'removeContact'
|
||||
'removeContact',
|
||||
'setUpEmail'
|
||||
] as const);
|
||||
|
||||
const {
|
||||
@ -93,7 +97,13 @@ export const OrgIncidentContactsTable = ({
|
||||
<div>
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
onClick={() => handlePopUpOpen('addContact')}
|
||||
onClick={() => {
|
||||
if (serverDetails?.emailConfigured){
|
||||
handlePopUpOpen('addContact');
|
||||
} else {
|
||||
handlePopUpOpen('setUpEmail');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Add Contact
|
||||
</Button>
|
||||
@ -181,6 +191,10 @@ export const OrgIncidentContactsTable = ({
|
||||
onChange={(isOpen) => handlePopUpToggle('removeContact', isOpen)}
|
||||
onDeleteApproved={onRemoveIncidentContact}
|
||||
/>
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
|
||||
/>
|
||||
</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 { 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 { yupResolver } from '@hookform/resolvers/yup';
|
||||
import * as yup from 'yup';
|
||||
@ -9,7 +9,7 @@ import * as yup from 'yup';
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
EmailServiceSetupModal, EmptyState,
|
||||
FormControl,
|
||||
IconButton,
|
||||
Input,
|
||||
@ -28,6 +28,7 @@ import {
|
||||
Tr,
|
||||
UpgradePlanModal} from '@app/components/v2';
|
||||
import { usePopUp } from '@app/hooks';
|
||||
import { useFetchServerStatus } from '@app/hooks/api/serverDetails';
|
||||
import { OrgUser, Workspace } from '@app/hooks/api/types';
|
||||
|
||||
type Props = {
|
||||
@ -42,6 +43,8 @@ type Props = {
|
||||
onGrantAccess: (userId: string, publicKey: string) => Promise<void>;
|
||||
// the current user id to block remove org button
|
||||
userId: string;
|
||||
completeInviteLink: string | undefined,
|
||||
setCompleteInviteLink: Dispatch<SetStateAction<string | undefined>>
|
||||
};
|
||||
|
||||
const addMemberFormSchema = yup.object({
|
||||
@ -60,14 +63,18 @@ export const OrgMembersTable = ({
|
||||
onGrantAccess,
|
||||
onRoleChange,
|
||||
userId,
|
||||
isLoading
|
||||
isLoading,
|
||||
completeInviteLink,
|
||||
setCompleteInviteLink
|
||||
}: Props) => {
|
||||
const router = useRouter();
|
||||
const [searchMemberFilter, setSearchMemberFilter] = useState('');
|
||||
const {data: serverDetails } = useFetchServerStatus()
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
'addMember',
|
||||
'removeMember',
|
||||
'upgradePlan'
|
||||
'upgradePlan',
|
||||
'setUpEmail'
|
||||
] as const);
|
||||
|
||||
const {
|
||||
@ -79,8 +86,11 @@ export const OrgMembersTable = ({
|
||||
|
||||
const onAddMember = async ({ email }: TAddMemberForm) => {
|
||||
await onInviteMember(email);
|
||||
handlePopUpClose('addMember');
|
||||
reset();
|
||||
if (serverDetails?.emailConfigured){
|
||||
handlePopUpClose('addMember');
|
||||
}
|
||||
|
||||
reset();
|
||||
};
|
||||
|
||||
const onRemoveOrgMemberApproved = async () => {
|
||||
@ -106,6 +116,11 @@ export const OrgMembersTable = ({
|
||||
[members, searchMemberFilter]
|
||||
);
|
||||
|
||||
const copyTokenToClipboard = () => {
|
||||
navigator.clipboard.writeText(completeInviteLink as string);
|
||||
// setIsTokenCopied.on();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="mb-4 flex">
|
||||
@ -171,7 +186,7 @@ export const OrgMembersTable = ({
|
||||
<SelectItem value="member">member</SelectItem>
|
||||
</Select>
|
||||
)}
|
||||
{(status === 'invited' || status === 'verified') && (
|
||||
{((status === 'invited' || status === 'verified') && serverDetails?.emailConfigured) && (
|
||||
<Button className='w-40' colorSchema="secondary" onClick={() => onInviteMember(email)}>
|
||||
Resend Invite
|
||||
</Button>
|
||||
@ -232,20 +247,23 @@ export const OrgMembersTable = ({
|
||||
isOpen={popUp?.addMember?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle('addMember', isOpen);
|
||||
reset();
|
||||
setCompleteInviteLink(undefined)
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title={`Invite others to ${orgName}`}
|
||||
subTitle={
|
||||
<>
|
||||
An invite is specific to an email address and expires after 1 day.
|
||||
<br />
|
||||
For security reasons, you will need to separately add members to projects.
|
||||
</>
|
||||
<div>
|
||||
{!completeInviteLink && <div>
|
||||
An invite is specific to an email address and expires after 1 day.
|
||||
<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
|
||||
control={control}
|
||||
defaultValue=""
|
||||
@ -274,7 +292,22 @@ export const OrgMembersTable = ({
|
||||
Cancel
|
||||
</Button>
|
||||
</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>
|
||||
</Modal>
|
||||
<DeleteActionModal
|
||||
@ -289,6 +322,10 @@ export const OrgMembersTable = ({
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('upgradePlan', isOpen)}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
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 { User } from '../../../../hooks/api/types';
|
||||
@ -11,6 +13,11 @@ export const SecuritySection = () => {
|
||||
const [isMfaEnabled, setIsMfaEnabled] = useState(false);
|
||||
const { data: user } = useGetUser();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen } = usePopUp([
|
||||
'setUpEmail'
|
||||
] as const);
|
||||
|
||||
const {data: serverDetails } = useFetchServerStatus()
|
||||
|
||||
useEffect(() => {
|
||||
if (user && typeof user.isMfaEnabled !== 'undefined') {
|
||||
@ -42,6 +49,7 @@ export const SecuritySection = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
<p className="mb-4 mt-2 text-xl font-semibold">
|
||||
@ -52,12 +60,21 @@ export const SecuritySection = () => {
|
||||
id="isTwoFAEnabled"
|
||||
isChecked={isMfaEnabled}
|
||||
onCheckedChange={(state) => {
|
||||
toggleMfa(state as boolean);
|
||||
if (serverDetails?.emailConfigured){
|
||||
toggleMfa(state as boolean);
|
||||
} else {
|
||||
handlePopUpOpen('setUpEmail');
|
||||
}
|
||||
}}
|
||||
>
|
||||
Enable 2-factor authentication via your personal email.
|
||||
</Checkbox>
|
||||
</div>
|
||||
</form>
|
||||
<EmailServiceSetupModal
|
||||
isOpen={popUp.setUpEmail?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle('setUpEmail', isOpen)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
Reference in New Issue
Block a user