Merge branch 'main' into feature/google-signin-signup-integration

This commit is contained in:
Sheen Capadngan
2023-05-24 18:39:18 +08:00
40 changed files with 1188 additions and 322 deletions
+2
View File
@@ -0,0 +1,2 @@
backend/node_modules
frontend/node_modules
+2
View File
@@ -3,3 +3,5 @@
. "$(dirname -- "$0")/_/husky.sh" . "$(dirname -- "$0")/_/husky.sh"
npx lint-staged npx lint-staged
infisical scan git-changes --staged -v
+2 -8
View File
@@ -98,16 +98,10 @@ To scan your full git history, run:
infisical scan --verbose infisical scan --verbose
``` ```
To scan your uncommitted git changes, run: Install pre commit hook to scan each commit before you push to your repository
``` ```
infisical scan git-changes --verbose infisical scan install --pre-commit-hook
```
You can also scan your uncommited but staged changes by running the command below. This command can also be used as a pre-commit hook to prevent secret leak.
```
infisical scan git-changes --staged --verbose
``` ```
Lean about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview) Lean about Infisical's code scanning feature [here](https://infisical.com/docs/cli/scanning-overview)
+1
View File
@@ -40,6 +40,7 @@
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.10.5", "mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
+1
View File
@@ -31,6 +31,7 @@
"libsodium-wrappers": "^0.7.10", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"mongoose": "^6.10.5", "mongoose": "^6.10.5",
"node-cache": "^5.1.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"passport": "^0.6.0", "passport": "^0.6.0",
"passport-google-oauth20": "^2.0.0", "passport-google-oauth20": "^2.0.0",
+8 -1
View File
@@ -5,7 +5,7 @@ const client = new InfisicalClient({
}); });
export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000; export const getPort = async () => (await client.getSecret('PORT')).secretValue || 4000;
export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue == undefined ? false : (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue; export const getInviteOnlySignup = async () => (await client.getSecret('INVITE_ONLY_SIGNUP')).secretValue === 'true'
export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue; export const getEncryptionKey = async () => (await client.getSecret('ENCRYPTION_KEY')).secretValue;
export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10; export const getSaltRounds = async () => parseInt((await client.getSecret('SALT_ROUNDS')).secretValue) || 10;
export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d'; export const getJwtAuthLifetime = async () => (await client.getSecret('JWT_AUTH_LIFETIME')).secretValue || '10d';
@@ -49,12 +49,19 @@ export const getSmtpUsername = async () => (await client.getSecret('SMTP_USERNAM
export const getSmtpPassword = async () => (await client.getSecret('SMTP_PASSWORD')).secretValue; export const getSmtpPassword = async () => (await client.getSecret('SMTP_PASSWORD')).secretValue;
export const getSmtpFromAddress = async () => (await client.getSecret('SMTP_FROM_ADDRESS')).secretValue; export const getSmtpFromAddress = async () => (await client.getSecret('SMTP_FROM_ADDRESS')).secretValue;
export const getSmtpFromName = async () => (await client.getSecret('SMTP_FROM_NAME')).secretValue || 'Infisical'; export const getSmtpFromName = async () => (await client.getSecret('SMTP_FROM_NAME')).secretValue || 'Infisical';
export const getLicenseKey = async () => (await client.getSecret('LICENSE_KEY')).secretValue;
export const getLicenseServerKey = async () => (await client.getSecret('LICENSE_SERVER_KEY')).secretValue;
export const getLicenseServerUrl = async () => (await client.getSecret('LICENSE_SERVER_URL')).secretValue || 'https://portal.infisical.com';
// TODO: deprecate from here
export const getStripeProductStarter = async () => (await client.getSecret('STRIPE_PRODUCT_STARTER')).secretValue; export const getStripeProductStarter = async () => (await client.getSecret('STRIPE_PRODUCT_STARTER')).secretValue;
export const getStripeProductPro = async () => (await client.getSecret('STRIPE_PRODUCT_PRO')).secretValue; export const getStripeProductPro = async () => (await client.getSecret('STRIPE_PRODUCT_PRO')).secretValue;
export const getStripeProductTeam = async () => (await client.getSecret('STRIPE_PRODUCT_TEAM')).secretValue; export const getStripeProductTeam = async () => (await client.getSecret('STRIPE_PRODUCT_TEAM')).secretValue;
export const getStripePublishableKey = async () => (await client.getSecret('STRIPE_PUBLISHABLE_KEY')).secretValue; export const getStripePublishableKey = async () => (await client.getSecret('STRIPE_PUBLISHABLE_KEY')).secretValue;
export const getStripeSecretKey = async () => (await client.getSecret('STRIPE_SECRET_KEY')).secretValue; export const getStripeSecretKey = async () => (await client.getSecret('STRIPE_SECRET_KEY')).secretValue;
export const getStripeWebhookSecret = async () => (await client.getSecret('STRIPE_WEBHOOK_SECRET')).secretValue; export const getStripeWebhookSecret = async () => (await client.getSecret('STRIPE_WEBHOOK_SECRET')).secretValue;
export const getTelemetryEnabled = async () => (await client.getSecret('TELEMETRY_ENABLED')).secretValue !== 'false' && true; export const getTelemetryEnabled = async () => (await client.getSecret('TELEMETRY_ENABLED')).secretValue !== 'false' && true;
export const getLoopsApiKey = async () => (await client.getSecret('LOOPS_API_KEY')).secretValue; export const getLoopsApiKey = async () => (await client.getSecret('LOOPS_API_KEY')).secretValue;
export const getSmtpConfigured = async () => (await client.getSecret('SMTP_HOST')).secretValue == '' || (await client.getSecret('SMTP_HOST')).secretValue == undefined ? false : true export const getSmtpConfigured = async () => (await client.getSecret('SMTP_HOST')).secretValue == '' || (await client.getSecret('SMTP_HOST')).secretValue == undefined ? false : true
+111 -3
View File
@@ -1,10 +1,24 @@
import axios from 'axios'; import axios from 'axios';
import axiosRetry from 'axios-retry'; import axiosRetry from 'axios-retry';
import {
getLicenseServerKeyAuthToken,
setLicenseServerKeyAuthToken,
getLicenseKeyAuthToken,
setLicenseKeyAuthToken
} from './storage';
import {
getLicenseKey,
getLicenseServerKey,
getLicenseServerUrl
} from './index';
const axiosInstance = axios.create(); // should have JWT to interact with the license server
export const licenseServerKeyRequest = axios.create();
export const licenseKeyRequest = axios.create();
export const standardRequest = axios.create();
// add retry functionality to the axios instance // add retry functionality to the axios instance
axiosRetry(axiosInstance, { axiosRetry(standardRequest, {
retries: 3, retries: 3,
retryDelay: axiosRetry.exponentialDelay, // exponential back-off delay between retries retryDelay: axiosRetry.exponentialDelay, // exponential back-off delay between retries
retryCondition: (error) => { retryCondition: (error) => {
@@ -13,4 +27,98 @@ axiosRetry(axiosInstance, {
}, },
}); });
export default axiosInstance; export const refreshLicenseServerKeyToken = async () => {
const licenseServerKey = await getLicenseServerKey();
const licenseServerUrl = await getLicenseServerUrl();
const { data: { token } } = await standardRequest.post(
`${licenseServerUrl}/api/auth/v1/license-server-login`, {},
{
headers: {
'X-API-KEY': licenseServerKey
}
}
);
setLicenseServerKeyAuthToken(token);
return token;
}
export const refreshLicenseKeyToken = async () => {
const licenseKey = await getLicenseKey();
const licenseServerUrl = await getLicenseServerUrl();
const { data: { token } } = await standardRequest.post(
`${licenseServerUrl}/api/auth/v1/license-login`, {},
{
headers: {
'X-API-KEY': licenseKey
}
}
);
setLicenseKeyAuthToken(token);
return token;
}
licenseServerKeyRequest.interceptors.request.use((config) => {
const token = getLicenseServerKeyAuthToken();
if (token && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (err) => {
return Promise.reject(err);
});
licenseServerKeyRequest.interceptors.response.use((response) => {
return response
}, async function (err) {
const originalRequest = err.config;
if (err.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
// refresh
const token = await refreshLicenseServerKeyToken();
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
return licenseServerKeyRequest(originalRequest);
}
return Promise.reject(err);
});
licenseKeyRequest.interceptors.request.use((config) => {
const token = getLicenseKeyAuthToken();
if (token && config.headers) {
// eslint-disable-next-line no-param-reassign
config.headers.Authorization = `Bearer ${token}`;
}
return config;
}, (err) => {
return Promise.reject(err);
});
licenseKeyRequest.interceptors.response.use((response) => {
return response
}, async function (err) {
const originalRequest = err.config;
if (err.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
// refresh
const token = await refreshLicenseKeyToken();
axios.defaults.headers.common['Authorization'] = 'Bearer ' + token;
return licenseKeyRequest(originalRequest);
}
return Promise.reject(err);
});
+30
View File
@@ -0,0 +1,30 @@
const MemoryLicenseServerKeyTokenStorage = () => {
let authToken: string;
return {
setToken: (token: string) => {
authToken = token;
},
getToken: () => authToken
};
};
const MemoryLicenseKeyTokenStorage = () => {
let authToken: string;
return {
setToken: (token: string) => {
authToken = token;
},
getToken: () => authToken
};
};
const licenseServerTokenStorage = MemoryLicenseServerKeyTokenStorage();
const licenseTokenStorage = MemoryLicenseKeyTokenStorage();
export const getLicenseServerKeyAuthToken = licenseServerTokenStorage.getToken;
export const setLicenseServerKeyAuthToken = licenseServerTokenStorage.setToken;
export const getLicenseKeyAuthToken = licenseTokenStorage.getToken;
export const setLicenseKeyAuthToken = licenseTokenStorage.setToken;
@@ -16,7 +16,7 @@ import {
INTEGRATION_VERCEL_API_URL, INTEGRATION_VERCEL_API_URL,
INTEGRATION_RAILWAY_API_URL INTEGRATION_RAILWAY_API_URL
} from '../../variables'; } from '../../variables';
import request from '../../config/request'; import { standardRequest } from '../../config/request';
/*** /***
* Return integration authorization with id [integrationAuthId] * Return integration authorization with id [integrationAuthId]
@@ -229,7 +229,7 @@ export const getIntegrationAuthVercelBranches = async (req: Request, res: Respon
let branches: string[] = []; let branches: string[] = [];
if (appId && appId !== '') { if (appId && appId !== '') {
const { data }: { data: VercelBranch[] } = await request.get( const { data }: { data: VercelBranch[] } = await standardRequest.get(
`${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`, `${INTEGRATION_VERCEL_API_URL}/v1/integrations/git-branches`,
{ {
params, params,
@@ -292,7 +292,7 @@ export const getIntegrationAuthRailwayEnvironments = async (req: Request, res: R
projectId: appId projectId: appId
} }
const { data: { data: { environments: { edges } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, { const { data: { data: { environments: { edges } } } } = await standardRequest.post(INTEGRATION_RAILWAY_API_URL, {
query, query,
variables, variables,
}, { }, {
@@ -372,7 +372,7 @@ export const getIntegrationAuthRailwayServices = async (req: Request, res: Respo
id: appId id: appId
} }
const { data: { data: { project: { services: { edges } } } } } = await request.post(INTEGRATION_RAILWAY_API_URL, { const { data: { data: { project: { services: { edges } } } } } = await standardRequest.post(INTEGRATION_RAILWAY_API_URL, {
query, query,
variables variables
}, { }, {
@@ -135,6 +135,7 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
} }
if (!inviteeMembershipOrg) { if (!inviteeMembershipOrg) {
await new MembershipOrg({ await new MembershipOrg({
user: invitee, user: invitee,
inviteEmail: inviteeEmail, inviteEmail: inviteeEmail,
@@ -247,6 +248,10 @@ export const verifyUserToOrganization = async (req: Request, res: Response) => {
membershipOrg.status = ACCEPTED; membershipOrg.status = ACCEPTED;
await membershipOrg.save(); await membershipOrg.save();
await updateSubscriptionOrgQuantity({
organizationId
});
return res.status(200).send({ return res.status(200).send({
message: 'Successfully verified email', message: 'Successfully verified email',
user, user,
@@ -21,14 +21,6 @@ export const beginEmailSignup = async (req: Request, res: Response) => {
try { try {
email = req.body.email; email = req.body.email;
if (await getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({})
if (userCount != 0) {
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
}
}
const user = await User.findOne({ email }).select('+publicKey'); const user = await User.findOne({ email }).select('+publicKey');
if (user && user?.publicKey) { if (user && user?.publicKey) {
// case: user has already completed account // case: user has already completed account
@@ -74,6 +66,14 @@ export const verifyEmailSignup = async (req: Request, res: Response) => {
}); });
} }
if (await getInviteOnlySignup()) {
// Only one user can create an account without being invited. The rest need to be invited in order to make an account
const userCount = await User.countDocuments({})
if (userCount != 0) {
throw BadRequestError({ message: "New user sign ups are not allowed at this time. You must be invited to sign up." })
}
}
// verify email // verify email
if (await getSmtpConfigured()) { if (await getSmtpConfigured()) {
await checkEmailVerification({ await checkEmailVerification({
+50 -13
View File
@@ -1,26 +1,24 @@
import to from 'await-to-js';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { ISecret, Secret } from '../../models'; import { ISecret, Secret, Workspace } from '../../models';
import { IAction, SecretVersion } from '../../ee/models'; import { IAction, SecretVersion } from '../../ee/models';
import { import {
SECRET_PERSONAL, SECRET_PERSONAL,
SECRET_SHARED,
ACTION_ADD_SECRETS, ACTION_ADD_SECRETS,
ACTION_READ_SECRETS, ACTION_READ_SECRETS,
ACTION_UPDATE_SECRETS, ACTION_UPDATE_SECRETS,
ACTION_DELETE_SECRETS ACTION_DELETE_SECRETS
} from '../../variables'; } from '../../variables';
import { UnauthorizedRequestError, ValidationError } from '../../utils/errors'; import { UnauthorizedRequestError, WorkspaceNotFoundError } from '../../utils/errors';
import { EventService } from '../../services'; import { EventService } from '../../services';
import { eventPushSecrets } from '../../events'; import { eventPushSecrets } from '../../events';
import { EESecretService, EELogService } from '../../ee/services'; import { EESecretService, EELogService, EELicenseService } from '../../ee/services';
import { TelemetryService, SecretService } from '../../services'; import { TelemetryService, SecretService } from '../../services';
import { getChannelFromUserAgent } from '../../utils/posthog'; import { getChannelFromUserAgent } from '../../utils/posthog';
import { PERMISSION_WRITE_SECRETS } from '../../variables'; import { PERMISSION_WRITE_SECRETS } from '../../variables';
import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions'; import { userHasNoAbility, userHasWorkspaceAccess, userHasWriteOnlyAbility } from '../../ee/helpers/checkMembershipPermissions';
import Tag from '../../models/tag'; import Tag from '../../models/tag';
import _, { eq } from 'lodash'; import _ from 'lodash';
import { import {
BatchSecretRequest, BatchSecretRequest,
BatchSecret BatchSecret
@@ -49,6 +47,12 @@ export const batchSecrets = async (req: Request, res: Response) => {
requests: BatchSecretRequest[]; requests: BatchSecretRequest[];
} = req.body; } = req.body;
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw WorkspaceNotFoundError();
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
const isPaid = orgPlan.tier < 1;
const createSecrets: BatchSecret[] = []; const createSecrets: BatchSecret[] = [];
const updateSecrets: BatchSecret[] = []; const updateSecrets: BatchSecret[] = [];
const deleteSecrets: Types.ObjectId[] = []; const deleteSecrets: Types.ObjectId[] = [];
@@ -139,7 +143,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
environment, environment,
workspaceId, workspaceId,
channel, channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
@@ -226,7 +231,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
environment, environment,
workspaceId, workspaceId,
channel, channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
@@ -261,7 +267,8 @@ export const batchSecrets = async (req: Request, res: Response) => {
environment, environment,
workspaceId, workspaceId,
channel: channel, channel: channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
@@ -376,6 +383,12 @@ export const createSecrets = async (req: Request, res: Response) => {
} }
} }
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw WorkspaceNotFoundError();
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
const isPaid = orgPlan.tier < 1;
let listOfSecretsToCreate; let listOfSecretsToCreate;
if (Array.isArray(req.body.secrets)) { if (Array.isArray(req.body.secrets)) {
// case: create multiple secrets // case: create multiple secrets
@@ -531,7 +544,8 @@ export const createSecrets = async (req: Request, res: Response) => {
environment, environment,
workspaceId, workspaceId,
channel: channel, channel: channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
@@ -595,6 +609,12 @@ export const getSecrets = async (req: Request, res: Response) => {
const normalizedPath = normalizePath(secretsPath as string) const normalizedPath = normalizePath(secretsPath as string)
const folders = await getFoldersInDirectory(workspaceId as string, environment as string, normalizedPath) const folders = await getFoldersInDirectory(workspaceId as string, environment as string, normalizedPath)
const workspace = await Workspace.findById(workspaceId);
if (!workspace) throw WorkspaceNotFoundError();
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
const isPaid = orgPlan.tier < 1;
// secrets to return // secrets to return
let secrets: ISecret[] = []; let secrets: ISecret[] = [];
@@ -727,7 +747,8 @@ export const getSecrets = async (req: Request, res: Response) => {
environment, environment,
workspaceId, workspaceId,
channel, channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
@@ -938,6 +959,12 @@ export const updateSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key) workspaceId: new Types.ObjectId(key)
}) })
const workspace = await Workspace.findById(key);
if (!workspace) throw WorkspaceNotFoundError();
const orgPlan = await EELicenseService.getOrganizationPlan(workspace.organization.toString());
const isPaid = orgPlan.tier < 1;
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -950,7 +977,8 @@ export const updateSecrets = async (req: Request, res: Response) => {
environment: workspaceSecretObj[key][0].environment, environment: workspaceSecretObj[key][0].environment,
workspaceId: key, workspaceId: key,
channel: channel, channel: channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
@@ -1072,6 +1100,14 @@ export const deleteSecrets = async (req: Request, res: Response) => {
workspaceId: new Types.ObjectId(key) workspaceId: new Types.ObjectId(key)
}); });
const organizationId = (
await Workspace.findOne({
_id: key
})
)?.organization?.toString();
const orgPlan = await EELicenseService.getOrganizationPlan(organizationId || '');
const isPaid = orgPlan.slug != 'starter';
const postHogClient = await TelemetryService.getPostHogClient(); const postHogClient = await TelemetryService.getPostHogClient();
if (postHogClient) { if (postHogClient) {
postHogClient.capture({ postHogClient.capture({
@@ -1084,7 +1120,8 @@ export const deleteSecrets = async (req: Request, res: Response) => {
environment: workspaceSecretObj[key][0].environment, environment: workspaceSecretObj[key][0].environment,
workspaceId: key, workspaceId: key,
channel: channel, channel: channel,
userAgent: req.headers?.['user-agent'] userAgent: req.headers?.['user-agent'],
isPaid
} }
}); });
} }
+27 -2
View File
@@ -7,8 +7,9 @@ import {
} from '../../helpers/signup'; } from '../../helpers/signup';
import { issueAuthTokens } from '../../helpers/auth'; import { issueAuthTokens } from '../../helpers/auth';
import { INVITED, ACCEPTED } from '../../variables'; import { INVITED, ACCEPTED } from '../../variables';
import request from '../../config/request'; import { standardRequest } from '../../config/request';
import { getLoopsApiKey, getHttpsEnabled } from '../../config'; import { getLoopsApiKey, getHttpsEnabled } from '../../config';
import { updateSubscriptionOrgQuantity } from '../../helpers/organization';
/** /**
* Complete setting up user by adding their personal and auth information as part of the * Complete setting up user by adding their personal and auth information as part of the
@@ -87,6 +88,19 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
user user
}); });
// update organization membership statuses that are
// invited to completed with user attached
const membershipsToUpdate = await MembershipOrg.find({
inviteEmail: email,
status: INVITED
});
membershipsToUpdate.forEach(async (membership) => {
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString()
});
});
// update organization membership statuses that are // update organization membership statuses that are
// invited to completed with user attached // invited to completed with user attached
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
@@ -109,7 +123,7 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
// sending a welcome email to new users // sending a welcome email to new users
if (await getLoopsApiKey()) { if (await getLoopsApiKey()) {
await request.post("https://app.loops.so/api/v1/events/send", { await standardRequest.post("https://app.loops.so/api/v1/events/send", {
"email": email, "email": email,
"eventName": "Sign Up", "eventName": "Sign Up",
"firstName": firstName, "firstName": firstName,
@@ -209,6 +223,17 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
// update organization membership statuses that are // update organization membership statuses that are
// invited to completed with user attached // invited to completed with user attached
const membershipsToUpdate = await MembershipOrg.find({
inviteEmail: email,
status: INVITED
});
membershipsToUpdate.forEach(async (membership) => {
await updateSubscriptionOrgQuantity({
organizationId: membership.organization.toString()
});
});
await MembershipOrg.updateMany( await MembershipOrg.updateMany(
{ {
inviteEmail: email, inviteEmail: email,
@@ -0,0 +1,34 @@
import * as Sentry from '@sentry/node';
import { Request, Response } from 'express';
import { EELicenseService } from '../../services';
import { getLicenseServerUrl } from '../../../config';
import { licenseServerKeyRequest } from '../../../config/request';
/**
* Return available cloud product information.
* Note: Nicely formatted to easily construct a table from
* @param req
* @param res
* @returns
*/
export const getCloudProducts = async (req: Request, res: Response) => {
try {
const billingCycle = req.query['billing-cycle'] as string;
if (EELicenseService.instanceType === 'cloud') {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
return res.status(200).send(data);
}
} catch (err) {
Sentry.setUser({ email: req.user.email });
Sentry.captureException(err);
}
return res.status(200).send({
head: [],
rows: []
});
}
+5 -1
View File
@@ -1,15 +1,19 @@
import * as stripeController from './stripeController'; import * as stripeController from './stripeController';
import * as secretController from './secretController'; import * as secretController from './secretController';
import * as secretSnapshotController from './secretSnapshotController'; import * as secretSnapshotController from './secretSnapshotController';
import * as organizationsController from './organizationsController';
import * as workspaceController from './workspaceController'; import * as workspaceController from './workspaceController';
import * as actionController from './actionController'; import * as actionController from './actionController';
import * as membershipController from './membershipController'; import * as membershipController from './membershipController';
import * as cloudProductsController from './cloudProductsController';
export { export {
stripeController, stripeController,
secretController, secretController,
secretSnapshotController, secretSnapshotController,
organizationsController,
workspaceController, workspaceController,
actionController, actionController,
membershipController membershipController,
cloudProductsController
} }
@@ -0,0 +1,84 @@
import { Request, Response } from 'express';
import { getLicenseServerUrl } from '../../../config';
import { licenseServerKeyRequest } from '../../../config/request';
import { EELicenseService } from '../../services';
/**
* Return the organization's current plan and allowed feature set
*/
export const getOrganizationPlan = async (req: Request, res: Response) => {
const plan = await EELicenseService.getOrganizationPlan(req.organization._id.toString());
// cache fetched plan for organization
EELicenseService.localFeatureSet.set(req.organization._id.toString(), plan);
return res.status(200).send({
plan
});
}
/**
* Update the organization plan to product with id [productId]
* @param req
* @param res
* @returns
*/
export const updateOrganizationPlan = async (req: Request, res: Response) => {
const {
productId
} = req.body;
const { data } = await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan`,
{
productId
}
);
return res.status(200).send(data);
}
/**
* Return the organization's payment methods on file
*/
export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`
);
return res.status(200).send({
pmtMethods
});
}
/**
* Return a Stripe session URL to add payment method for organization
*/
export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
const {
success_url,
cancel_url
} = req.body;
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
{
success_url,
cancel_url
}
);
return res.status(200).send({
url
});
}
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
const { pmtMethodId } = req.params;
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods/${pmtMethodId}`,
);
return res.status(200).send(data);
}
+20
View File
@@ -0,0 +1,20 @@
import express from 'express';
const router = express.Router();
import {
requireAuth,
validateRequest
} from '../../../middleware';
import { query } from 'express-validator';
import { cloudProductsController } from '../../controllers/v1';
router.get(
'/',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
query('billing-cycle').exists().isIn(['monthly', 'yearly']),
validateRequest,
cloudProductsController.getCloudProducts
);
export default router;
+5 -1
View File
@@ -1,11 +1,15 @@
import secret from './secret'; import secret from './secret';
import secretSnapshot from './secretSnapshot'; import secretSnapshot from './secretSnapshot';
import organizations from './organizations';
import workspace from './workspace'; import workspace from './workspace';
import action from './action'; import action from './action';
import cloudProducts from './cloudProducts';
export { export {
secret, secret,
secretSnapshot, secretSnapshot,
organizations,
workspace, workspace,
action action,
cloudProducts
} }
+87
View File
@@ -0,0 +1,87 @@
import express from 'express';
const router = express.Router();
import {
requireAuth,
requireOrganizationAuth,
validateRequest
} from '../../../middleware';
import { param, body } from 'express-validator';
import { organizationsController } from '../../controllers/v1';
import {
OWNER, ADMIN, MEMBER, ACCEPTED
} from '../../../variables';
router.get(
'/:organizationId/plan',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
param('organizationId').exists().trim(),
validateRequest,
organizationsController.getOrganizationPlan
);
router.patch(
'/:organizationId/plan',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
param('organizationId').exists().trim(),
body('productId').exists().isString(),
validateRequest,
organizationsController.updateOrganizationPlan
);
router.get(
'/:organizationId/billing-details/payment-methods',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
param('organizationId').exists().trim(),
validateRequest,
organizationsController.getOrganizationPmtMethods
);
router.post(
'/:organizationId/billing-details/payment-methods',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
param('organizationId').exists().trim(),
body('success_url').exists().isString(),
body('cancel_url').exists().isString(),
validateRequest,
organizationsController.addOrganizationPmtMethod
);
router.delete(
'/:organizationId/billing-details/payment-methods/:pmtMethodId',
requireAuth({
acceptedAuthModes: ['jwt', 'apiKey']
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
param('organizationId').exists().trim(),
validateRequest,
organizationsController.deleteOrganizationPmtMethod
);
export default router;
+1 -1
View File
@@ -7,7 +7,7 @@ import {
requireAuth, requireAuth,
validateRequest validateRequest
} from '../../../middleware'; } from '../../../middleware';
import { param, body } from 'express-validator'; import { param } from 'express-validator';
import { ADMIN, MEMBER } from '../../../variables'; import { ADMIN, MEMBER } from '../../../variables';
import { secretSnapshotController } from '../../controllers/v1'; import { secretSnapshotController } from '../../controllers/v1';
+116 -4
View File
@@ -1,12 +1,124 @@
import NodeCache from 'node-cache';
import * as Sentry from '@sentry/node';
import {
getLicenseKey,
getLicenseServerKey,
getLicenseServerUrl
} from '../../config';
import {
licenseKeyRequest,
licenseServerKeyRequest,
refreshLicenseServerKeyToken,
refreshLicenseKeyToken
} from '../../config/request';
import { Organization } from '../../models';
import { OrganizationNotFoundError } from '../../utils/errors';
interface FeatureSet {
_id: string | null;
slug: 'starter' | 'team' | 'pro' | 'enterprise' | null;
tier: number | null;
projectLimit: number | null;
memberLimit: number | null;
secretVersioning: boolean;
pitRecovery: boolean;
rbac: boolean;
customRateLimits: boolean;
customAlerts: boolean;
auditLogs: boolean;
}
/** /**
* Class to handle Enterprise Edition license actions * Class to handle license/plan configurations:
* - Infisical Cloud: Fetch and cache customer plans in [localFeatureSet]
* - Self-hosted regular: Use default global feature set
* - Self-hosted enterprise: Fetch and update global feature set
*/ */
class EELicenseService { class EELicenseService {
private readonly _isLicenseValid: boolean; private readonly _isLicenseValid: boolean; // TODO: deprecate
constructor(licenseKey: string) { public instanceType: 'self-hosted' | 'enterprise-self-hosted' | 'cloud' = 'self-hosted';
public globalFeatureSet: FeatureSet = {
_id: null,
slug: null,
tier: -1,
projectLimit: null,
memberLimit: null,
secretVersioning: true,
pitRecovery: true,
rbac: true,
customRateLimits: true,
customAlerts: true,
auditLogs: false
}
public localFeatureSet: NodeCache;
constructor() {
this._isLicenseValid = true; this._isLicenseValid = true;
this.localFeatureSet = new NodeCache({
stdTTL: 300
});
}
public async getOrganizationPlan(organizationId: string) {
try {
if (this.instanceType === 'cloud') {
const cachedPlan = this.localFeatureSet.get(organizationId);
if (cachedPlan) return cachedPlan;
const organization = await Organization.findById(organizationId);
if (!organization) throw OrganizationNotFoundError();
const { data: { currentPlan } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`
);
return currentPlan;
}
} catch (err) {
return this.globalFeatureSet;
}
return this.globalFeatureSet;
}
public async initGlobalFeatureSet() {
const licenseServerKey = await getLicenseServerKey();
const licenseKey = await getLicenseKey();
try {
if (licenseServerKey) {
// license server key is present -> validate it
const token = await refreshLicenseServerKeyToken()
if (token) {
this.instanceType = 'cloud';
}
return;
}
if (licenseKey) {
// license key is present -> validate it
const token = await refreshLicenseKeyToken();
if (token) {
const { data: { currentPlan } } = await licenseKeyRequest.get(
`${await getLicenseServerUrl()}/api/license/v1/plan`
);
this.globalFeatureSet = currentPlan;
this.instanceType = 'enterprise-self-hosted';
}
}
} catch (err) {
// case: self-hosted free
Sentry.setUser(null);
Sentry.captureException(err);
}
} }
public get isLicenseValid(): boolean { public get isLicenseValid(): boolean {
@@ -14,4 +126,4 @@ class EELicenseService {
} }
} }
export default new EELicenseService('N/A'); export default new EELicenseService();
+37 -22
View File
@@ -29,6 +29,16 @@ import {
} from "../utils/errors"; } from "../utils/errors";
import { validateUserClientForOrganization } from "../helpers/user"; import { validateUserClientForOrganization } from "../helpers/user";
import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount"; import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
import {
EELicenseService
} from '../ee/services';
import {
getLicenseServerUrl
} from '../config';
import {
licenseServerKeyRequest,
licenseKeyRequest
} from '../config/request';
/** /**
* Validate accepted clients for organization with id [organizationId] * Validate accepted clients for organization with id [organizationId]
@@ -228,30 +238,35 @@ const updateSubscriptionOrgQuantity = async ({
}); });
if (organization && organization.customerId) { if (organization && organization.customerId) {
const quantity = await MembershipOrg.countDocuments({ if (EELicenseService.instanceType === 'cloud') {
organization: organizationId, // instance of Infisical is a cloud instance
status: ACCEPTED, const quantity = await MembershipOrg.countDocuments({
}); organization: new Types.ObjectId(organizationId),
status: ACCEPTED,
});
const stripe = new Stripe(await getStripeSecretKey(), { await licenseServerKeyRequest.patch(
apiVersion: "2022-08-01", `${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`,
});
const subscription = (
await stripe.subscriptions.list({
customer: organization.customerId,
})
).data[0];
stripeSubscription = await stripe.subscriptions.update(subscription.id, {
items: [
{ {
id: subscription.items.data[0].id, quantity
price: subscription.items.data[0].price.id, }
quantity, );
}, }
],
}); if (EELicenseService.instanceType === 'enterprise-self-hosted') {
// instance of Infisical is an enterprise self-hosted instance
const usedSeats = await MembershipOrg.countDocuments({
status: ACCEPTED
});
await licenseKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license/v1/license`,
{
usedSeats
}
);
}
} }
return stripeSubscription; return stripeSubscription;
+2 -2
View File
@@ -89,7 +89,7 @@ const validateClientForWorkspace = async ({
requiredPermissions requiredPermissions
}); });
return ({ membership }); return ({ membership, workspace });
} }
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) { if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
@@ -123,7 +123,7 @@ const validateClientForWorkspace = async ({
requiredPermissions requiredPermissions
}); });
return ({ membership }); return ({ membership, workspace });
} }
throw UnauthorizedRequestError({ throw UnauthorizedRequestError({
+189 -177
View File
@@ -1,198 +1,210 @@
import dotenv from 'dotenv'; import dotenv from "dotenv";
dotenv.config(); dotenv.config();
import express from 'express'; import express from "express";
import helmet from 'helmet'; import helmet from "helmet";
import cors from 'cors'; import cors from "cors";
import * as Sentry from '@sentry/node'; import * as Sentry from "@sentry/node";
import { DatabaseService } from './services'; import { DatabaseService } from "./services";
import { setUpHealthEndpoint } from './services/health'; import { EELicenseService } from "./ee/services";
import { initSmtp } from './services/smtp'; import { setUpHealthEndpoint } from "./services/health";
import { TelemetryService } from './services'; import { initSmtp } from "./services/smtp";
import { setTransporter } from './helpers/nodemailer'; import { TelemetryService } from "./services";
import { createTestUserForDevelopment } from './utils/addDevelopmentUser'; import { setTransporter } from "./helpers/nodemailer";
import { createTestUserForDevelopment } from "./utils/addDevelopmentUser";
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const { patchRouterParam } = require('./utils/patchAsyncRoutes'); const { patchRouterParam } = require("./utils/patchAsyncRoutes");
import cookieParser from 'cookie-parser'; import cookieParser from "cookie-parser";
import swaggerUi = require('swagger-ui-express'); import swaggerUi = require("swagger-ui-express");
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const swaggerFile = require('../spec.json'); const swaggerFile = require("../spec.json");
// eslint-disable-next-line @typescript-eslint/no-var-requires // eslint-disable-next-line @typescript-eslint/no-var-requires
const requestIp = require('request-ip'); const requestIp = require("request-ip");
import { apiLimiter } from './helpers/rateLimiter'; import { apiLimiter } from "./helpers/rateLimiter";
import { import {
workspace as eeWorkspaceRouter, workspace as eeWorkspaceRouter,
secret as eeSecretRouter, secret as eeSecretRouter,
secretSnapshot as eeSecretSnapshotRouter, secretSnapshot as eeSecretSnapshotRouter,
action as eeActionRouter action as eeActionRouter,
} from './ee/routes/v1'; organizations as eeOrganizationsRouter,
cloudProducts as eeCloudProductsRouter,
} from "./ee/routes/v1";
import { import {
signup as v1SignupRouter, signup as v1SignupRouter,
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
organization as v1OrganizationRouter, organization as v1OrganizationRouter,
workspace as v1WorkspaceRouter, workspace as v1WorkspaceRouter,
membershipOrg as v1MembershipOrgRouter, membershipOrg as v1MembershipOrgRouter,
membership as v1MembershipRouter, membership as v1MembershipRouter,
key as v1KeyRouter, key as v1KeyRouter,
inviteOrg as v1InviteOrgRouter, inviteOrg as v1InviteOrgRouter,
oauth as v1OAuth, oauth as v1OAuth,
user as v1UserRouter, user as v1UserRouter,
userAction as v1UserActionRouter, userAction as v1UserActionRouter,
secret as v1SecretRouter, secret as v1SecretRouter,
serviceToken as v1ServiceTokenRouter, serviceToken as v1ServiceTokenRouter,
password as v1PasswordRouter, password as v1PasswordRouter,
stripe as v1StripeRouter, stripe as v1StripeRouter,
integration as v1IntegrationRouter, integration as v1IntegrationRouter,
integrationAuth as v1IntegrationAuthRouter, integrationAuth as v1IntegrationAuthRouter,
secretsFolder as v1SecretsFolder secretsFolder as v1SecretsFolder,
} from './routes/v1'; } from "./routes/v1";
import { import {
signup as v2SignupRouter, signup as v2SignupRouter,
auth as v2AuthRouter, auth as v2AuthRouter,
users as v2UsersRouter, users as v2UsersRouter,
organizations as v2OrganizationsRouter, organizations as v2OrganizationsRouter,
workspace as v2WorkspaceRouter, workspace as v2WorkspaceRouter,
secret as v2SecretRouter, // begin to phase out secret as v2SecretRouter, // begin to phase out
secrets as v2SecretsRouter, secrets as v2SecretsRouter,
serviceTokenData as v2ServiceTokenDataRouter, serviceTokenData as v2ServiceTokenDataRouter,
serviceAccounts as v2ServiceAccountsRouter, serviceAccounts as v2ServiceAccountsRouter,
apiKeyData as v2APIKeyDataRouter, apiKeyData as v2APIKeyDataRouter,
environment as v2EnvironmentRouter, environment as v2EnvironmentRouter,
tags as v2TagsRouter, tags as v2TagsRouter,
} from './routes/v2'; } from "./routes/v2";
import { import {
auth as v3AuthRouter, auth as v3AuthRouter,
secrets as v3SecretsRouter, secrets as v3SecretsRouter,
signup as v3SignupRouter, signup as v3SignupRouter,
workspaces as v3WorkspacesRouter, workspaces as v3WorkspacesRouter,
} from './routes/v3'; } from "./routes/v3";
import { healthCheck } from './routes/status'; import { healthCheck } from "./routes/status";
import { getLogger } from './utils/logger'; import { getLogger } from "./utils/logger";
import { RouteNotFoundError } from './utils/errors'; import { RouteNotFoundError } from "./utils/errors";
import { requestErrorHandler } from './middleware/requestErrorHandler'; import { requestErrorHandler } from "./middleware/requestErrorHandler";
import { import {
getMongoURL, getMongoURL,
getNodeEnv, getNodeEnv,
getPort, getPort,
getSentryDSN, getSentryDSN,
getSiteURL, getSiteURL,
} from './config'; } from "./config";
import { initializePassport } from './utils/auth'; import { initializePassport } from "./utils/auth";
const main = async () => { const main = async () => {
TelemetryService.logTelemetryMessage(); TelemetryService.logTelemetryMessage();
setTransporter(await initSmtp()); setTransporter(await initSmtp());
const mongoURL = await getMongoURL(); await EELicenseService.initGlobalFeatureSet();
await DatabaseService.initDatabase(mongoURL);
if ((await getNodeEnv()) !== 'test') {
Sentry.init({
dsn: await getSentryDSN(),
tracesSampleRate: 1.0,
debug: await getNodeEnv() === 'production' ? false : true,
environment: await getNodeEnv()
});
}
patchRouterParam(); await DatabaseService.initDatabase(await getMongoURL());
const app = express(); if ((await getNodeEnv()) !== "test") {
Sentry.init({
await initializePassport(); dsn: await getSentryDSN(),
tracesSampleRate: 1.0,
app.enable('trust proxy'); debug: (await getNodeEnv()) === "production" ? false : true,
app.use(express.json()); environment: await getNodeEnv(),
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: await getSiteURL()
})
);
app.use(requestIp.mw());
if ((await getNodeEnv()) === 'production') {
// enable app-wide rate-limiting + helmet security
// in production
app.disable('x-powered-by');
app.use(apiLimiter);
app.use(helmet());
}
// (EE) routes
app.use('/api/v1/secret', eeSecretRouter);
app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter);
app.use('/api/v1/workspace', eeWorkspaceRouter);
app.use('/api/v1/action', eeActionRouter);
// v1 routes (default)
app.use('/api/v1/signup', v1SignupRouter);
app.use('/api/v1/auth', v1AuthRouter);
app.use('/api/v1/oauth', v1OAuth);
app.use('/api/v1/bot', v1BotRouter);
app.use('/api/v1/user', v1UserRouter);
app.use('/api/v1/user-action', v1UserActionRouter);
app.use('/api/v1/organization', v1OrganizationRouter);
app.use('/api/v1/workspace', v1WorkspaceRouter);
app.use('/api/v1/membership-org', v1MembershipOrgRouter);
app.use('/api/v1/membership', v1MembershipRouter);
app.use('/api/v1/key', v1KeyRouter);
app.use('/api/v1/invite-org', v1InviteOrgRouter);
app.use('/api/v1/secret', v1SecretRouter);
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated
app.use('/api/v1/password', v1PasswordRouter);
app.use('/api/v1/stripe', v1StripeRouter);
app.use('/api/v1/integration', v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
app.use('/api/v1/folder', v1SecretsFolder)
// v2 routes (improvements)
app.use('/api/v2/signup', v2SignupRouter);
app.use('/api/v2/auth', v2AuthRouter);
app.use('/api/v2/users', v2UsersRouter);
app.use('/api/v2/organizations', v2OrganizationsRouter);
app.use('/api/v2/workspace', v2EnvironmentRouter);
app.use('/api/v2/workspace', v2TagsRouter);
app.use('/api/v2/workspace', v2WorkspaceRouter);
app.use('/api/v2/secret', v2SecretRouter); // deprecated
app.use('/api/v2/secrets', v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
app.use('/api/v2/api-key', v2APIKeyDataRouter);
// v3 routes (experimental)
app.use('/api/v3/auth', v3AuthRouter);
app.use('/api/v3/secrets', v3SecretsRouter);
app.use('/api/v3/workspaces', v3WorkspacesRouter);
app.use('/api/v3/signup', v3SignupRouter);
// api docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
// Server status
app.use('/api', healthCheck)
//* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => {
if (res.headersSent) return next();
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
})
app.use(requestErrorHandler)
const server = app.listen(await getPort(), async () => {
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
}); });
}
await createTestUserForDevelopment(); patchRouterParam();
setUpHealthEndpoint(server); const app = express();
server.on('close', async () => { await initializePassport();
await DatabaseService.closeDatabase();
app.enable("trust proxy");
app.use(express.json());
app.use(cookieParser());
app.use(
cors({
credentials: true,
origin: await getSiteURL(),
}) })
);
return server; app.use(requestIp.mw());
}
if ((await getNodeEnv()) === "production") {
// enable app-wide rate-limiting + helmet security
// in production
app.disable("x-powered-by");
app.use(apiLimiter);
app.use(helmet());
}
// (EE) routes
app.use("/api/v1/secret", eeSecretRouter);
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
app.use("/api/v1/workspace", eeWorkspaceRouter);
app.use("/api/v1/action", eeActionRouter);
app.use("/api/v1/organizations", eeOrganizationsRouter);
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
// v1 routes (default)
app.use("/api/v1/signup", v1SignupRouter);
app.use("/api/v1/auth", v1AuthRouter);
app.use("/api/v1/oauth", v1OAuth);
app.use("/api/v1/bot", v1BotRouter);
app.use("/api/v1/user", v1UserRouter);
app.use("/api/v1/user-action", v1UserActionRouter);
app.use("/api/v1/organization", v1OrganizationRouter);
app.use("/api/v1/workspace", v1WorkspaceRouter);
app.use("/api/v1/membership-org", v1MembershipOrgRouter);
app.use("/api/v1/membership", v1MembershipRouter);
app.use("/api/v1/key", v1KeyRouter);
app.use("/api/v1/invite-org", v1InviteOrgRouter);
app.use("/api/v1/secret", v1SecretRouter);
app.use("/api/v1/service-token", v1ServiceTokenRouter); // deprecated
app.use("/api/v1/password", v1PasswordRouter);
app.use("/api/v1/stripe", v1StripeRouter);
app.use("/api/v1/integration", v1IntegrationRouter);
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
app.use("/api/v1/folder", v1SecretsFolder);
// v2 routes (improvements)
app.use("/api/v2/signup", v2SignupRouter);
app.use("/api/v2/auth", v2AuthRouter);
app.use("/api/v2/users", v2UsersRouter);
app.use("/api/v2/organizations", v2OrganizationsRouter);
app.use("/api/v2/workspace", v2EnvironmentRouter);
app.use("/api/v2/workspace", v2TagsRouter);
app.use("/api/v2/workspace", v2WorkspaceRouter);
app.use("/api/v2/secret", v2SecretRouter); // deprecated
app.use("/api/v2/secrets", v2SecretsRouter);
app.use("/api/v2/service-token", v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
app.use("/api/v2/api-key", v2APIKeyDataRouter);
// v3 routes (experimental)
app.use("/api/v3/auth", v3AuthRouter);
app.use("/api/v3/secrets", v3SecretsRouter);
app.use("/api/v3/workspaces", v3WorkspacesRouter);
app.use("/api/v3/signup", v3SignupRouter);
// api docs
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
// Server status
app.use("/api", healthCheck);
//* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => {
if (res.headersSent) return next();
next(
RouteNotFoundError({
message: `The requested source '(${req.method})${req.url}' was not found`,
})
);
});
app.use(requestErrorHandler);
const server = app.listen(await getPort(), async () => {
(await getLogger("backend-main")).info(
`Server started listening at port ${await getPort()}`
);
});
await createTestUserForDevelopment();
setUpHealthEndpoint(server);
server.on("close", async () => {
await DatabaseService.closeDatabase();
});
return server;
};
export default main(); export default main();
+13 -13
View File
@@ -1,6 +1,6 @@
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import { IIntegrationAuth } from "../models"; import { IIntegrationAuth } from "../models";
import request from "../config/request"; import { standardRequest } from "../config/request";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE, INTEGRATION_AWS_PARAMETER_STORE,
@@ -134,7 +134,7 @@ const getApps = async ({
*/ */
const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => { const getAppsHeroku = async ({ accessToken }: { accessToken: string }) => {
const res = ( const res = (
await request.get(`${INTEGRATION_HEROKU_API_URL}/apps`, { await standardRequest.get(`${INTEGRATION_HEROKU_API_URL}/apps`, {
headers: { headers: {
Accept: "application/vnd.heroku+json; version=3", Accept: "application/vnd.heroku+json; version=3",
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
@@ -164,7 +164,7 @@ const getAppsVercel = async ({
accessToken: string; accessToken: string;
}) => { }) => {
const res = ( const res = (
await request.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, { await standardRequest.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
@@ -208,7 +208,7 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
filter: 'all' filter: 'all'
}); });
const { data } = await request.get( const { data } = await standardRequest.get(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`,
{ {
params, params,
@@ -310,7 +310,7 @@ const getAppsGithub = async ({ accessToken }: { accessToken: string }) => {
*/ */
const getAppsRender = async ({ accessToken }: { accessToken: string }) => { const getAppsRender = async ({ accessToken }: { accessToken: string }) => {
const res = ( const res = (
await request.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, { await standardRequest.get(`${INTEGRATION_RENDER_API_URL}/v1/services`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
Accept: "application/json", Accept: "application/json",
@@ -358,7 +358,7 @@ const getAppsRailway = async ({ accessToken }: { accessToken: string }) => {
projects: { edges }, projects: { edges },
}, },
}, },
} = await request.post( } = await standardRequest.post(
INTEGRATION_RAILWAY_API_URL, INTEGRATION_RAILWAY_API_URL,
{ {
query, query,
@@ -402,7 +402,7 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
`; `;
const res = ( const res = (
await request.post( await standardRequest.post(
INTEGRATION_FLYIO_API_URL, INTEGRATION_FLYIO_API_URL,
{ {
query, query,
@@ -436,7 +436,7 @@ const getAppsFlyio = async ({ accessToken }: { accessToken: string }) => {
*/ */
const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => { const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
const res = ( const res = (
await request.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, { await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v1.1/projects`, {
headers: { headers: {
"Circle-Token": accessToken, "Circle-Token": accessToken,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
@@ -455,7 +455,7 @@ const getAppsCircleCI = async ({ accessToken }: { accessToken: string }) => {
const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => { const getAppsTravisCI = async ({ accessToken }: { accessToken: string }) => {
const res = ( const res = (
await request.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, { await standardRequest.get(`${INTEGRATION_TRAVISCI_API_URL}/repos`, {
headers: { headers: {
Authorization: `token ${accessToken}`, Authorization: `token ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
@@ -502,7 +502,7 @@ const getAppsGitlab = async ({
per_page: String(perPage), per_page: String(perPage),
}); });
const { data } = await request.get( const { data } = await standardRequest.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`, `${INTEGRATION_GITLAB_API_URL}/v4/groups/${teamId}/projects`,
{ {
params, params,
@@ -530,7 +530,7 @@ const getAppsGitlab = async ({
// case: fetch projects for individual in GitLab // case: fetch projects for individual in GitLab
const { id } = ( const { id } = (
await request.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, { await standardRequest.get(`${INTEGRATION_GITLAB_API_URL}/v4/user`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
@@ -544,7 +544,7 @@ const getAppsGitlab = async ({
per_page: String(perPage), per_page: String(perPage),
}); });
const { data } = await request.get( const { data } = await standardRequest.get(
`${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`, `${INTEGRATION_GITLAB_API_URL}/v4/users/${id}/projects`,
{ {
params, params,
@@ -581,7 +581,7 @@ const getAppsGitlab = async ({
* @returns {String} apps.name - name of Supabase app * @returns {String} apps.name - name of Supabase app
*/ */
const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => { const getAppsSupabase = async ({ accessToken }: { accessToken: string }) => {
const { data } = await request.get( const { data } = await standardRequest.get(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects`, `${INTEGRATION_SUPABASE_API_URL}/v1/projects`,
{ {
headers: { headers: {
+9 -9
View File
@@ -1,4 +1,4 @@
import request from "../config/request"; import { standardRequest } from "../config/request";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
@@ -142,7 +142,7 @@ const exchangeCodeAzure = async ({ code }: { code: string }) => {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const res: ExchangeCodeAzureResponse = ( const res: ExchangeCodeAzureResponse = (
await request.post( await standardRequest.post(
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
@@ -178,7 +178,7 @@ const exchangeCodeHeroku = async ({ code }: { code: string }) => {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const res: ExchangeCodeHerokuResponse = ( const res: ExchangeCodeHerokuResponse = (
await request.post( await standardRequest.post(
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
@@ -209,7 +209,7 @@ const exchangeCodeHeroku = async ({ code }: { code: string }) => {
*/ */
const exchangeCodeVercel = async ({ code }: { code: string }) => { const exchangeCodeVercel = async ({ code }: { code: string }) => {
const res: ExchangeCodeVercelResponse = ( const res: ExchangeCodeVercelResponse = (
await request.post( await standardRequest.post(
INTEGRATION_VERCEL_TOKEN_URL, INTEGRATION_VERCEL_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
code: code, code: code,
@@ -240,7 +240,7 @@ const exchangeCodeVercel = async ({ code }: { code: string }) => {
*/ */
const exchangeCodeNetlify = async ({ code }: { code: string }) => { const exchangeCodeNetlify = async ({ code }: { code: string }) => {
const res: ExchangeCodeNetlifyResponse = ( const res: ExchangeCodeNetlifyResponse = (
await request.post( await standardRequest.post(
INTEGRATION_NETLIFY_TOKEN_URL, INTEGRATION_NETLIFY_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
@@ -252,14 +252,14 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
) )
).data; ).data;
const res2 = await request.get("https://api.netlify.com/api/v1/sites", { const res2 = await standardRequest.get("https://api.netlify.com/api/v1/sites", {
headers: { headers: {
Authorization: `Bearer ${res.access_token}`, Authorization: `Bearer ${res.access_token}`,
}, },
}); });
const res3 = ( const res3 = (
await request.get("https://api.netlify.com/api/v1/accounts", { await standardRequest.get("https://api.netlify.com/api/v1/accounts", {
headers: { headers: {
Authorization: `Bearer ${res.access_token}`, Authorization: `Bearer ${res.access_token}`,
}, },
@@ -287,7 +287,7 @@ const exchangeCodeNetlify = async ({ code }: { code: string }) => {
*/ */
const exchangeCodeGithub = async ({ code }: { code: string }) => { const exchangeCodeGithub = async ({ code }: { code: string }) => {
const res: ExchangeCodeGithubResponse = ( const res: ExchangeCodeGithubResponse = (
await request.get(INTEGRATION_GITHUB_TOKEN_URL, { await standardRequest.get(INTEGRATION_GITHUB_TOKEN_URL, {
params: { params: {
client_id: await getClientIdGitHub(), client_id: await getClientIdGitHub(),
client_secret: await getClientSecretGitHub(), client_secret: await getClientSecretGitHub(),
@@ -321,7 +321,7 @@ const exchangeCodeGithub = async ({ code }: { code: string }) => {
const exchangeCodeGitlab = async ({ code }: { code: string }) => { const exchangeCodeGitlab = async ({ code }: { code: string }) => {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const res: ExchangeCodeGitlabResponse = ( const res: ExchangeCodeGitlabResponse = (
await request.post( await standardRequest.post(
INTEGRATION_GITLAB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: "authorization_code", grant_type: "authorization_code",
+4 -4
View File
@@ -1,4 +1,4 @@
import request from "../config/request"; import { standardRequest } from "../config/request";
import { IIntegrationAuth } from "../models"; import { IIntegrationAuth } from "../models";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
@@ -121,7 +121,7 @@ const exchangeRefreshAzure = async ({
refreshToken: string; refreshToken: string;
}) => { }) => {
const accessExpiresAt = new Date(); const accessExpiresAt = new Date();
const { data }: { data: RefreshTokenAzureResponse } = await request.post( const { data }: { data: RefreshTokenAzureResponse } = await standardRequest.post(
INTEGRATION_AZURE_TOKEN_URL, INTEGRATION_AZURE_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
client_id: await getClientIdAzure(), client_id: await getClientIdAzure(),
@@ -158,7 +158,7 @@ const exchangeRefreshHeroku = async ({
data, data,
}: { }: {
data: RefreshTokenHerokuResponse; data: RefreshTokenHerokuResponse;
} = await request.post( } = await standardRequest.post(
INTEGRATION_HEROKU_TOKEN_URL, INTEGRATION_HEROKU_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: "refresh_token", grant_type: "refresh_token",
@@ -193,7 +193,7 @@ const exchangeRefreshGitLab = async ({
data, data,
}: { }: {
data: RefreshTokenGitLabResponse; data: RefreshTokenGitLabResponse;
} = await request.post( } = await standardRequest.post(
INTEGRATION_GITLAB_TOKEN_URL, INTEGRATION_GITLAB_TOKEN_URL,
new URLSearchParams({ new URLSearchParams({
grant_type: "refresh_token", grant_type: "refresh_token",
+38 -39
View File
@@ -37,8 +37,7 @@ import {
INTEGRATION_TRAVISCI_API_URL, INTEGRATION_TRAVISCI_API_URL,
INTEGRATION_SUPABASE_API_URL INTEGRATION_SUPABASE_API_URL
} from "../variables"; } from "../variables";
import request from '../config/request'; import { standardRequest} from '../config/request';
import axios from "axios";
/** /**
* Sync/push [secrets] to [app] in integration named [integration] * Sync/push [secrets] to [app] in integration named [integration]
@@ -215,7 +214,7 @@ const syncSecretsAzureKeyVault = async ({
let result: GetAzureKeyVaultSecret[] = []; let result: GetAzureKeyVaultSecret[] = [];
try { try {
while (url) { while (url) {
const res = await request.get(url, { const res = await standardRequest.get(url, {
headers: { headers: {
Authorization: `Bearer ${accessToken}` Authorization: `Bearer ${accessToken}`
} }
@@ -242,7 +241,7 @@ const syncSecretsAzureKeyVault = async ({
lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf('/'); lastSlashIndex = getAzureKeyVaultSecret.id.lastIndexOf('/');
} }
const azureKeyVaultSecret = await request.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, { const azureKeyVaultSecret = await standardRequest.get(`${getAzureKeyVaultSecret.id}?api-version=7.3`, {
headers: { headers: {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
} }
@@ -308,7 +307,7 @@ const syncSecretsAzureKeyVault = async ({
while (!isSecretSet && maxTries > 0) { while (!isSecretSet && maxTries > 0) {
// try to set secret // try to set secret
try { try {
await request.put( await standardRequest.put(
`${integration.app}/secrets/${key}?api-version=7.3`, `${integration.app}/secrets/${key}?api-version=7.3`,
{ {
value value
@@ -325,7 +324,7 @@ const syncSecretsAzureKeyVault = async ({
} catch (err) { } catch (err) {
const error: any = err; const error: any = err;
if (error?.response?.data?.error?.innererror?.code === 'ObjectIsDeletedButRecoverable') { if (error?.response?.data?.error?.innererror?.code === 'ObjectIsDeletedButRecoverable') {
await request.post( await standardRequest.post(
`${integration.app}/deletedsecrets/${key}/recover?api-version=7.3`, {}, `${integration.app}/deletedsecrets/${key}/recover?api-version=7.3`, {},
{ {
headers: { headers: {
@@ -355,7 +354,7 @@ const syncSecretsAzureKeyVault = async ({
for await (const deleteSecret of deleteSecrets) { for await (const deleteSecret of deleteSecrets) {
const { key } = deleteSecret; const { key } = deleteSecret;
await request.delete(`${integration.app}/secrets/${key}?api-version=7.3`, { await standardRequest.delete(`${integration.app}/secrets/${key}?api-version=7.3`, {
headers: { headers: {
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
} }
@@ -568,7 +567,7 @@ const syncSecretsHeroku = async ({
}) => { }) => {
try { try {
const herokuSecrets = ( const herokuSecrets = (
await request.get( await standardRequest.get(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
{ {
headers: { headers: {
@@ -586,7 +585,7 @@ const syncSecretsHeroku = async ({
} }
}); });
await request.patch( await standardRequest.patch(
`${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`, `${INTEGRATION_HEROKU_API_URL}/apps/${integration.app}/config-vars`,
secrets, secrets,
{ {
@@ -642,7 +641,7 @@ const syncSecretsVercel = async ({
: {}), : {}),
}; };
const vercelSecrets: VercelSecret[] = (await request.get( const vercelSecrets: VercelSecret[] = (await standardRequest.get(
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`, `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env`,
{ {
params, params,
@@ -675,7 +674,7 @@ const syncSecretsVercel = async ({
for await (const vercelSecret of vercelSecrets) { for await (const vercelSecret of vercelSecrets) {
if (vercelSecret.type === 'encrypted') { if (vercelSecret.type === 'encrypted') {
// case: secret is encrypted -> need to decrypt // case: secret is encrypted -> need to decrypt
const decryptedSecret = (await request.get( const decryptedSecret = (await standardRequest.get(
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`, `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${vercelSecret.id}`,
{ {
params, params,
@@ -747,7 +746,7 @@ const syncSecretsVercel = async ({
// Sync/push new secrets // Sync/push new secrets
if (newSecrets.length > 0) { if (newSecrets.length > 0) {
await request.post( await standardRequest.post(
`${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`, `${INTEGRATION_VERCEL_API_URL}/v10/projects/${integration.app}/env`,
newSecrets, newSecrets,
{ {
@@ -763,7 +762,7 @@ const syncSecretsVercel = async ({
for await (const secret of updateSecrets) { for await (const secret of updateSecrets) {
if (secret.type !== 'sensitive') { if (secret.type !== 'sensitive') {
const { id, ...updatedSecret } = secret; const { id, ...updatedSecret } = secret;
await request.patch( await standardRequest.patch(
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
updatedSecret, updatedSecret,
{ {
@@ -778,7 +777,7 @@ const syncSecretsVercel = async ({
} }
for await (const secret of deleteSecrets) { for await (const secret of deleteSecrets) {
await request.delete( await standardRequest.delete(
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
{ {
params, params,
@@ -837,7 +836,7 @@ const syncSecretsNetlify = async ({
}); });
const res = ( const res = (
await request.get( await standardRequest.get(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
{ {
params: getParams, params: getParams,
@@ -951,7 +950,7 @@ const syncSecretsNetlify = async ({
}); });
if (newSecrets.length > 0) { if (newSecrets.length > 0) {
await request.post( await standardRequest.post(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env`,
newSecrets, newSecrets,
{ {
@@ -966,7 +965,7 @@ const syncSecretsNetlify = async ({
if (updateSecrets.length > 0) { if (updateSecrets.length > 0) {
updateSecrets.forEach(async (secret: NetlifySecret) => { updateSecrets.forEach(async (secret: NetlifySecret) => {
await request.patch( await standardRequest.patch(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}`,
{ {
context: secret.values[0].context, context: secret.values[0].context,
@@ -985,7 +984,7 @@ const syncSecretsNetlify = async ({
if (deleteSecrets.length > 0) { if (deleteSecrets.length > 0) {
deleteSecrets.forEach(async (key: string) => { deleteSecrets.forEach(async (key: string) => {
await request.delete( await standardRequest.delete(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${key}`,
{ {
params: syncParams, params: syncParams,
@@ -1000,7 +999,7 @@ const syncSecretsNetlify = async ({
if (deleteSecretValues.length > 0) { if (deleteSecretValues.length > 0) {
deleteSecretValues.forEach(async (secret: NetlifySecret) => { deleteSecretValues.forEach(async (secret: NetlifySecret) => {
await request.delete( await standardRequest.delete(
`${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`, `${INTEGRATION_NETLIFY_API_URL}/api/v1/accounts/${integrationAuth.accountId}/env/${secret.key}/value/${secret.values[0].id}`,
{ {
params: syncParams, params: syncParams,
@@ -1151,7 +1150,7 @@ const syncSecretsRender = async ({
accessToken: string; accessToken: string;
}) => { }) => {
try { try {
await request.put( await standardRequest.put(
`${INTEGRATION_RENDER_API_URL}/v1/services/${integration.appId}/env-vars`, `${INTEGRATION_RENDER_API_URL}/v1/services/${integration.appId}/env-vars`,
Object.keys(secrets).map((key) => ({ Object.keys(secrets).map((key) => ({
key, key,
@@ -1203,7 +1202,7 @@ const syncSecretsRailway = async ({
variables: secrets variables: secrets
}; };
await request.post(INTEGRATION_RAILWAY_API_URL, { await standardRequest.post(INTEGRATION_RAILWAY_API_URL, {
query, query,
variables: { variables: {
input, input,
@@ -1261,7 +1260,7 @@ const syncSecretsFlyio = async ({
} }
`; `;
await request.post(INTEGRATION_FLYIO_API_URL, { await standardRequest.post(INTEGRATION_FLYIO_API_URL, {
query: SetSecrets, query: SetSecrets,
variables: { variables: {
input: { input: {
@@ -1296,7 +1295,7 @@ const syncSecretsFlyio = async ({
} }
}`; }`;
const getSecretsRes = (await request.post(INTEGRATION_FLYIO_API_URL, { const getSecretsRes = (await standardRequest.post(INTEGRATION_FLYIO_API_URL, {
query: GetSecrets, query: GetSecrets,
variables: { variables: {
appName: integration.app, appName: integration.app,
@@ -1332,7 +1331,7 @@ const syncSecretsFlyio = async ({
} }
}`; }`;
await request.post(INTEGRATION_FLYIO_API_URL, { await standardRequest.post(INTEGRATION_FLYIO_API_URL, {
query: DeleteSecrets, query: DeleteSecrets,
variables: { variables: {
input: { input: {
@@ -1373,7 +1372,7 @@ const syncSecretsCircleCI = async ({
}) => { }) => {
try { try {
const circleciOrganizationDetail = ( const circleciOrganizationDetail = (
await request.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, { await standardRequest.get(`${INTEGRATION_CIRCLECI_API_URL}/v2/me/collaborations`, {
headers: { headers: {
"Circle-Token": accessToken, "Circle-Token": accessToken,
"Accept-Encoding": "application/json", "Accept-Encoding": "application/json",
@@ -1386,7 +1385,7 @@ const syncSecretsCircleCI = async ({
// sync secrets to CircleCI // sync secrets to CircleCI
Object.keys(secrets).forEach( Object.keys(secrets).forEach(
async (key) => async (key) =>
await request.post( await standardRequest.post(
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`, `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
{ {
name: key, name: key,
@@ -1403,7 +1402,7 @@ const syncSecretsCircleCI = async ({
// get secrets from CircleCI // get secrets from CircleCI
const getSecretsRes = ( const getSecretsRes = (
await request.get( await standardRequest.get(
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`, `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar`,
{ {
headers: { headers: {
@@ -1417,7 +1416,7 @@ const syncSecretsCircleCI = async ({
// delete secrets from CircleCI // delete secrets from CircleCI
getSecretsRes.forEach(async (sec: any) => { getSecretsRes.forEach(async (sec: any) => {
if (!(sec.name in secrets)) { if (!(sec.name in secrets)) {
await request.delete( await standardRequest.delete(
`${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`, `${INTEGRATION_CIRCLECI_API_URL}/v2/project/${slug}/${integration.app}/envvar/${sec.name}`,
{ {
headers: { headers: {
@@ -1454,7 +1453,7 @@ const syncSecretsTravisCI = async ({
try { try {
// get secrets from travis-ci // get secrets from travis-ci
const getSecretsRes = ( const getSecretsRes = (
await request.get( await standardRequest.get(
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`, `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`,
{ {
headers: { headers: {
@@ -1476,7 +1475,7 @@ const syncSecretsTravisCI = async ({
if (!(key in getSecretsRes)) { if (!(key in getSecretsRes)) {
// case: secret does not exist in travis ci // case: secret does not exist in travis ci
// -> add secret // -> add secret
await request.post( await standardRequest.post(
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`, `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars?repository_id=${integration.appId}`,
{ {
env_var: { env_var: {
@@ -1495,7 +1494,7 @@ const syncSecretsTravisCI = async ({
} else { } else {
// case: secret exists in travis ci // case: secret exists in travis ci
// -> update/set secret // -> update/set secret
await request.patch( await standardRequest.patch(
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`, `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`,
{ {
env_var: { env_var: {
@@ -1517,7 +1516,7 @@ const syncSecretsTravisCI = async ({
for await (const key of Object.keys(getSecretsRes)) { for await (const key of Object.keys(getSecretsRes)) {
if (!(key in secrets)){ if (!(key in secrets)){
// delete secret // delete secret
await request.delete( await standardRequest.delete(
`${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`, `${INTEGRATION_TRAVISCI_API_URL}/settings/env_vars/${getSecretsRes[key].id}?repository_id=${getSecretsRes[key].repository_id}`,
{ {
headers: { headers: {
@@ -1562,7 +1561,7 @@ const syncSecretsGitLab = async ({
// get secrets from gitlab // get secrets from gitlab
const getSecretsRes: GitLabSecret[] = ( const getSecretsRes: GitLabSecret[] = (
await request.get( await standardRequest.get(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`, `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
{ {
headers: { headers: {
@@ -1580,7 +1579,7 @@ const syncSecretsGitLab = async ({
for await (const key of Object.keys(secrets)) { for await (const key of Object.keys(secrets)) {
const existingSecret = getSecretsRes.find((s: any) => s.key == key); const existingSecret = getSecretsRes.find((s: any) => s.key == key);
if (!existingSecret) { if (!existingSecret) {
await request.post( await standardRequest.post(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`, `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables`,
{ {
key: key, key: key,
@@ -1601,7 +1600,7 @@ const syncSecretsGitLab = async ({
} else { } else {
// update secret // update secret
if (secrets[key] !== existingSecret.value) { if (secrets[key] !== existingSecret.value) {
await request.put( await standardRequest.put(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`, `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${existingSecret.key}?filter[environment_scope]=${integration.targetEnvironment}`,
{ {
...existingSecret, ...existingSecret,
@@ -1622,7 +1621,7 @@ const syncSecretsGitLab = async ({
// delete secrets // delete secrets
for await (const sec of getSecretsRes) { for await (const sec of getSecretsRes) {
if (!(sec.key in secrets)) { if (!(sec.key in secrets)) {
await request.delete( await standardRequest.delete(
`${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`, `${INTEGRATION_GITLAB_API_URL}/v4/projects/${integration?.appId}/variables/${sec.key}?filter[environment_scope]=${integration.targetEnvironment}`,
{ {
headers: { headers: {
@@ -1657,7 +1656,7 @@ const syncSecretsSupabase = async ({
accessToken: string; accessToken: string;
}) => { }) => {
try { try {
const { data: getSecretsRes } = await request.get( const { data: getSecretsRes } = await standardRequest.get(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
{ {
headers: { headers: {
@@ -1677,7 +1676,7 @@ const syncSecretsSupabase = async ({
} }
); );
await request.post( await standardRequest.post(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
modifiedFormatForSecretInjection, modifiedFormatForSecretInjection,
{ {
@@ -1695,7 +1694,7 @@ const syncSecretsSupabase = async ({
} }
}); });
await request.delete( await standardRequest.delete(
`${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`, `${INTEGRATION_SUPABASE_API_URL}/v1/projects/${integration.appId}/secrets`,
{ {
headers: { headers: {
+2 -2
View File
@@ -5,7 +5,7 @@ import {
INTEGRATION_GITLAB, INTEGRATION_GITLAB,
INTEGRATION_GITLAB_API_URL INTEGRATION_GITLAB_API_URL
} from '../variables'; } from '../variables';
import request from '../config/request'; import { standardRequest } from '../config/request';
interface Team { interface Team {
name: string; name: string;
@@ -56,7 +56,7 @@ const getTeamsGitLab = async ({
accessToken: string; accessToken: string;
}) => { }) => {
let teams: Team[] = []; let teams: Team[] = [];
const res = (await request.get( const res = (await standardRequest.get(
`${INTEGRATION_GITLAB_API_URL}/v4/groups`, `${INTEGRATION_GITLAB_API_URL}/v4/groups`,
{ {
headers: { headers: {
@@ -1,8 +1,6 @@
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { validateMembership } from '../helpers/membership';
import { validateClientForWorkspace } from '../helpers/workspace'; import { validateClientForWorkspace } from '../helpers/workspace';
import { UnauthorizedRequestError } from '../utils/errors';
type req = 'params' | 'body' | 'query'; type req = 'params' | 'body' | 'query';
@@ -31,7 +29,7 @@ const requireWorkspaceAuth = ({
const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined; const environment = locationEnvironment ? req[locationEnvironment]?.environment : undefined;
// validate clients // validate clients
const { membership } = await validateClientForWorkspace({ const { membership, workspace } = await validateClientForWorkspace({
authData: req.authData, authData: req.authData,
workspaceId: new Types.ObjectId(workspaceId), workspaceId: new Types.ObjectId(workspaceId),
environment, environment,
@@ -44,6 +42,10 @@ const requireWorkspaceAuth = ({
req.membership = membership; req.membership = membership;
} }
if (workspace) {
req.workspace = workspace;
}
return next(); return next();
}; };
}; };
+6
View File
@@ -21,6 +21,7 @@ export interface IIntegration {
workspace: Types.ObjectId; workspace: Types.ObjectId;
environment: string; environment: string;
isActive: boolean; isActive: boolean;
url: string;
app: string; app: string;
appId: string; appId: string;
owner: string; owner: string;
@@ -63,6 +64,11 @@ const integrationSchema = new Schema<IIntegration>(
type: Boolean, type: Boolean,
required: true, required: true,
}, },
url: {
// for custom self-hosted integrations (e.g. self-hosted GitHub enterprise)
type: String,
default: null
},
app: { app: {
// name of app in provider // name of app in provider
type: String, type: String,
@@ -0,0 +1,20 @@
# MANAGED BY INFISICAL CLI (Do not modify): START
infisicalScanEnabled=$(git config --bool hooks.infisical-scan)
if [ "$infisicalScanEnabled" != "false" ]; then
infisical scan git-changes -v --staged
exitCode=$?
if [ $exitCode -eq 1 ]; then
echo "Commit blocked: Infisical scan has uncovered secrets in your git commit"
echo "To disable the Infisical scan precommit hook run the following command:"
echo ""
echo " git config hooks.infisical-scan false"
echo ""
exit 1
fi
else
echo 'Warning: infisical scan precommit disabled'
fi
# MANAGED BY INFISICAL CLI (Do not modify): END
@@ -0,0 +1,20 @@
#!/bin/sh
# MANAGED BY INFISICAL CLI (Do not modify): START
infisicalScanEnabled=$(git config --bool hooks.infisical-scan)
if [ "$infisicalScanEnabled" != "false" ]; then
infisical scan git-changes -v --staged
exitCode=$?
if [ $exitCode -eq 1 ]; then
echo "Commit blocked: Infisical scan has uncovered secrets in your git commit"
echo "To disable the Infisical scan precommit hook run the following command:"
echo ""
echo " git config hooks.infisical-scan false"
echo ""
exit 1
fi
else
echo 'Warning: infisical scan precommit disabled'
fi
# MANAGED BY INFISICAL CLI (Do not modify): END
+167
View File
@@ -23,7 +23,11 @@
package cmd package cmd
import ( import (
_ "embed"
"fmt"
"io/ioutil"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"time" "time"
@@ -32,6 +36,7 @@ import (
"github.com/Infisical/infisical-merge/detect" "github.com/Infisical/infisical-merge/detect"
"github.com/Infisical/infisical-merge/packages/util" "github.com/Infisical/infisical-merge/packages/util"
"github.com/Infisical/infisical-merge/report" "github.com/Infisical/infisical-merge/report"
"github.com/manifoldco/promptui"
"github.com/posthog/posthog-go" "github.com/posthog/posthog-go"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -45,6 +50,17 @@ order of precedence:
3. (--source/-s)/.infisical-scan.toml 3. (--source/-s)/.infisical-scan.toml
If none of the three options are used, then Infisical will use the default scan config` If none of the three options are used, then Infisical will use the default scan config`
//go:embed pre-commit-script/pre-commit.sh
var preCommitTemplate []byte
//go:embed pre-commit-script/pre-commit-without-bang.sh
var preCommitTemplateAppend []byte
const (
defaultHooksPath = ".git/hooks/"
preCommitFile = "pre-commit"
)
func init() { func init() {
// scan flag for only scan command // scan flag for only scan command
scanCmd.Flags().String("log-opts", "", "git log options") scanCmd.Flags().String("log-opts", "", "git log options")
@@ -77,6 +93,9 @@ func init() {
// add flags to main // add flags to main
scanCmd.AddCommand(scanGitChangesCmd) scanCmd.AddCommand(scanGitChangesCmd)
rootCmd.AddCommand(scanCmd) rootCmd.AddCommand(scanCmd)
installCmd.Flags().Bool("pre-commit-hook", false, "installs pre commit hook for Git repository")
scanCmd.AddCommand(installCmd)
} }
func initScanConfig(cmd *cobra.Command) { func initScanConfig(cmd *cobra.Command) {
@@ -132,6 +151,50 @@ func initScanConfig(cmd *cobra.Command) {
} }
} }
var installCmd = &cobra.Command{
Use: "install",
Short: "Install scanning scripts and tools. Use --help flag to see all options",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
installPrecommit := cmd.Flags().Changed("pre-commit-hook")
if installPrecommit {
hooksPath, err := getHooksPath()
if err != nil {
fmt.Printf("Error: %s\n", err)
return
}
if hooksPath != ".git/hooks" {
defaultHookOverride, err := overrideDefaultHooksPath(hooksPath)
if err != nil {
fmt.Printf("Error: %s\n", err)
}
if defaultHookOverride {
ConfigureGitHooksPath()
log.Info().Msgf("To switch back previous githooks manager run: git config core.hooksPath %s\n", hooksPath)
return
} else {
log.Warn().Msgf("To automatically configure this hook, you need to switch the path of the Hooks. Alternatively, you can manually configure this hook by setting your pre-commit script to run command [infisical scan git-changes -v --staged].\n")
return
}
}
err = createOrUpdatePreCommitFile(hooksPath)
if err != nil {
fmt.Printf("Error: %s\n", err)
return
}
log.Info().Msgf("Pre-commit hook successfully added. Infisical scan should now run on each commit you make\n")
Telemetry.CaptureEvent("cli-command:install --pre-commit-hook", posthog.NewProperties().Set("version", util.CLI_VERSION))
return
}
}}
var scanCmd = &cobra.Command{ var scanCmd = &cobra.Command{
Use: "scan", Use: "scan",
Short: "Scan for leaked secrets in git history, directories, and files", Short: "Scan for leaked secrets in git history, directories, and files",
@@ -417,3 +480,107 @@ func FormatDuration(d time.Duration) string {
} }
return d.Round(scale / 100).String() return d.Round(scale / 100).String()
} }
func overrideDefaultHooksPath(managedHook string) (bool, error) {
YES := "Yes"
NO := "No"
options := []string{YES, NO}
optionsPrompt := promptui.Select{
Label: fmt.Sprintf("Your hooks path is set to [%s] but needs to be [.git/hooks] for automatic configuration. Would you like to switch? ", managedHook),
Items: options,
Size: 2,
}
_, selectedOption, err := optionsPrompt.Run()
if err != nil {
return false, err
}
return selectedOption == YES, err
}
func ConfigureGitHooksPath() {
cmd := exec.Command("git", "config", "core.hooksPath", ".git/hooks")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
log.Fatal().Msgf("Failed to configure git hooks path: %v", err)
}
}
// GetGitRoot returns the root directory of the current Git repository.
func GetGitRoot() (string, error) {
cmd := exec.Command("git", "rev-parse", "--show-toplevel")
output, err := cmd.Output()
if err != nil {
return "", fmt.Errorf("failed to get git root directory: %w", err)
}
gitRoot := strings.TrimSpace(string(output)) // Remove any trailing newline
return gitRoot, nil
}
func getHooksPath() (string, error) {
out, err := exec.Command("git", "config", "core.hooksPath").Output()
if err != nil {
if len(out) == 0 {
out = []byte(".git/hooks") // set the default hook
} else {
log.Error().Msgf("Failed to get Git hooks path: %s\nOutput: %s\n", err, out)
}
}
hooksPath := strings.TrimSpace(string(out))
return hooksPath, nil
}
func createOrUpdatePreCommitFile(hooksPath string) error {
// File doesn't exist, create a new one
rootGitRepoPath, err := GetGitRoot()
if err != nil {
return err
}
filePath := fmt.Sprintf("%s/%s/%s", rootGitRepoPath, hooksPath, preCommitFile)
_, err = os.Stat(filePath)
if err == nil {
// File already exists, check if it contains the managed comments
content, err := ioutil.ReadFile(filePath)
if err != nil {
return fmt.Errorf("failed to read pre-commit file: %s", err)
}
if strings.Contains(string(content), "# MANAGED BY INFISICAL CLI (Do not modify): START") &&
strings.Contains(string(content), "# MANAGED BY INFISICAL CLI (Do not modify): END") {
return nil
}
// File already exists, append the template content
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_WRONLY, 0755)
if err != nil {
return fmt.Errorf("failed to open pre-commit file: %s", err)
}
defer file.Close()
_, err = file.Write(preCommitTemplateAppend)
if err != nil {
return fmt.Errorf("failed to append to pre-commit file: %s", err)
}
} else if os.IsNotExist(err) {
err = os.WriteFile(filePath, preCommitTemplate, 0755)
if err != nil {
return fmt.Errorf("failed to create pre-commit file: %s", err)
}
} else {
// Error occurred while checking file status
return fmt.Errorf("failed to check pre-commit file status: %s", err)
}
return nil
}
+14 -1
View File
@@ -4,6 +4,7 @@ import (
"github.com/Infisical/infisical-merge/packages/util" "github.com/Infisical/infisical-merge/packages/util"
"github.com/denisbrodbeck/machineid" "github.com/denisbrodbeck/machineid"
"github.com/posthog/posthog-go" "github.com/posthog/posthog-go"
"github.com/rs/zerolog/log"
) )
var POSTHOG_API_KEY_FOR_CLI string var POSTHOG_API_KEY_FOR_CLI string
@@ -13,11 +14,23 @@ type Telemetry struct {
posthogClient posthog.Client posthogClient posthog.Client
} }
type NoOpLogger struct{}
func (NoOpLogger) Logf(format string, args ...interface{}) {
log.Debug().Msgf(format, args...)
}
func (NoOpLogger) Errorf(format string, args ...interface{}) {
log.Debug().Msgf(format, args...)
}
func NewTelemetry(telemetryIsEnabled bool) *Telemetry { func NewTelemetry(telemetryIsEnabled bool) *Telemetry {
if POSTHOG_API_KEY_FOR_CLI != "" { if POSTHOG_API_KEY_FOR_CLI != "" {
client, _ := posthog.NewWithConfig( client, _ := posthog.NewWithConfig(
POSTHOG_API_KEY_FOR_CLI, POSTHOG_API_KEY_FOR_CLI,
posthog.Config{}, posthog.Config{
Logger: NoOpLogger{},
},
) )
return &Telemetry{isEnabled: telemetryIsEnabled, posthogClient: client} return &Telemetry{isEnabled: telemetryIsEnabled, posthogClient: client}
+2
View File
@@ -37,6 +37,8 @@ services:
- MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin - MONGO_URL=mongodb://root:example@mongo:27017/?authSource=admin
networks: networks:
- infisical-dev - infisical-dev
extra_hosts:
- "host.docker.internal:host-gateway"
frontend: frontend:
container_name: infisical-dev-frontend container_name: infisical-dev-frontend
+23
View File
@@ -0,0 +1,23 @@
---
title: "scan install"
description: "Add various scanning tools seamlessly into your development lifecycle"
---
```bash
infisical scan install --pre-commit-hook
```
## Description
The command `infisical scan install` is designed to incorporate various scanning tools seamlessly into your development lifecycle.
Initially, we are offering users the ability to install a pre-commit hook. This hook conducts an automatic scan for any exposed secrets in your commits before they are pushed.
### Flags
<Accordion title="--pre-commit-hook">
```bash
infisical scan install --pre-commit-hook
```
**Description**
Installs a git pre-commit hook that triggers Infisical to scan your staged changes for any exposed secrets prior to pushing.
</Accordion>
+24
View File
@@ -52,6 +52,30 @@ In addition to scanning for past leaks, this new addition also actively aids in
</Tab> </Tab>
</Tabs> </Tabs>
#
#
# Automatically scan changes before you commit
To lower the risk of committing hardcoded secrets to your code repository, we have designed a custom git pre-commit hook.
This hook scans the changes you're about to commit for any exposed secrets. If any hardcoded secrets are detected, it will block your commit.
### Install pre-commit hook
To install this git hook, go into your local git repository and run the following command.
```bash
infisical scan install --pre-commit-hook
```
To disable this hook after installing it, run the command `git config --bool hooks.infisical-scan false`
### Third party hooks management
If you prefer to manage your pre-commit hook outside of the .git/hooks directory, you can easily accomplish this by adding the following command to your pre-commit script
```bash
infisical scan git-changes --staged --verbose
```
# #
# #
# Creating a baseline # Creating a baseline
+2 -1
View File
@@ -160,7 +160,8 @@
"group": "infisical scan", "group": "infisical scan",
"pages": [ "pages": [
"cli/commands/scan", "cli/commands/scan",
"cli/commands/scan-git-changes" "cli/commands/scan-git-changes",
"cli/commands/scan-install"
] ]
} }
] ]
@@ -21,6 +21,11 @@ docker pull infisical/infisical:latest
The Infisical Docker image requires a .env file to manage environment variables. The Infisical Docker image requires a .env file to manage environment variables.
Create a new file called .env in your preferred location. Add the required environment variables listed below. View [all configurable environment variables](../configuration/envars) Create a new file called .env in your preferred location. Add the required environment variables listed below. View [all configurable environment variables](../configuration/envars)
<ParamField query="ENCRYPTION_KEY" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField>
<ParamField query="JWT_SIGNUP_SECRET" type="string" default="none" required> <ParamField query="JWT_SIGNUP_SECRET" type="string" default="none" required>
Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16` Must be a random 16 byte hex string. Can be generated with `openssl rand -hex 16`
</ParamField> </ParamField>