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();
+33 -18
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) {
if (EELicenseService.instanceType === 'cloud') {
// instance of Infisical is a cloud instance
const quantity = await MembershipOrg.countDocuments({ const quantity = await MembershipOrg.countDocuments({
organization: organizationId, organization: new Types.ObjectId(organizationId),
status: ACCEPTED, 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({
+97 -85
View File
@@ -1,31 +1,34 @@
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,
@@ -45,8 +48,8 @@ import {
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,
@@ -60,38 +63,39 @@ import {
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') { await DatabaseService.initDatabase(await getMongoURL());
if ((await getNodeEnv()) !== "test") {
Sentry.init({ Sentry.init({
dsn: await getSentryDSN(), dsn: await getSentryDSN(),
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
debug: await getNodeEnv() === 'production' ? false : true, debug: (await getNodeEnv()) === "production" ? false : true,
environment: await getNodeEnv() environment: await getNodeEnv(),
}); });
} }
@@ -100,99 +104,107 @@ const main = async () => {
await initializePassport(); await initializePassport();
app.enable('trust proxy'); app.enable("trust proxy");
app.use(express.json()); app.use(express.json());
app.use(cookieParser()); app.use(cookieParser());
app.use( app.use(
cors({ cors({
credentials: true, credentials: true,
origin: await getSiteURL() origin: await getSiteURL(),
}) })
); );
app.use(requestIp.mw()); app.use(requestIp.mw());
if ((await getNodeEnv()) === 'production') { if ((await getNodeEnv()) === "production") {
// enable app-wide rate-limiting + helmet security // enable app-wide rate-limiting + helmet security
// in production // in production
app.disable('x-powered-by'); app.disable("x-powered-by");
app.use(apiLimiter); app.use(apiLimiter);
app.use(helmet()); app.use(helmet());
} }
// (EE) routes // (EE) routes
app.use('/api/v1/secret', eeSecretRouter); app.use("/api/v1/secret", eeSecretRouter);
app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter); app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
app.use('/api/v1/workspace', eeWorkspaceRouter); app.use("/api/v1/workspace", eeWorkspaceRouter);
app.use('/api/v1/action', eeActionRouter); app.use("/api/v1/action", eeActionRouter);
app.use("/api/v1/organizations", eeOrganizationsRouter);
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
// v1 routes (default) // v1 routes (default)
app.use('/api/v1/signup', v1SignupRouter); app.use("/api/v1/signup", v1SignupRouter);
app.use('/api/v1/auth', v1AuthRouter); app.use("/api/v1/auth", v1AuthRouter);
app.use('/api/v1/oauth', v1OAuth); app.use("/api/v1/oauth", v1OAuth);
app.use('/api/v1/bot', v1BotRouter); app.use("/api/v1/bot", v1BotRouter);
app.use('/api/v1/user', v1UserRouter); app.use("/api/v1/user", v1UserRouter);
app.use('/api/v1/user-action', v1UserActionRouter); app.use("/api/v1/user-action", v1UserActionRouter);
app.use('/api/v1/organization', v1OrganizationRouter); app.use("/api/v1/organization", v1OrganizationRouter);
app.use('/api/v1/workspace', v1WorkspaceRouter); app.use("/api/v1/workspace", v1WorkspaceRouter);
app.use('/api/v1/membership-org', v1MembershipOrgRouter); app.use("/api/v1/membership-org", v1MembershipOrgRouter);
app.use('/api/v1/membership', v1MembershipRouter); app.use("/api/v1/membership", v1MembershipRouter);
app.use('/api/v1/key', v1KeyRouter); app.use("/api/v1/key", v1KeyRouter);
app.use('/api/v1/invite-org', v1InviteOrgRouter); app.use("/api/v1/invite-org", v1InviteOrgRouter);
app.use('/api/v1/secret', v1SecretRouter); app.use("/api/v1/secret", v1SecretRouter);
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated app.use("/api/v1/service-token", v1ServiceTokenRouter); // deprecated
app.use('/api/v1/password', v1PasswordRouter); app.use("/api/v1/password", v1PasswordRouter);
app.use('/api/v1/stripe', v1StripeRouter); app.use("/api/v1/stripe", v1StripeRouter);
app.use('/api/v1/integration', v1IntegrationRouter); app.use("/api/v1/integration", v1IntegrationRouter);
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter); app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
app.use('/api/v1/folder', v1SecretsFolder) app.use("/api/v1/folder", v1SecretsFolder);
// v2 routes (improvements) // v2 routes (improvements)
app.use('/api/v2/signup', v2SignupRouter); app.use("/api/v2/signup", v2SignupRouter);
app.use('/api/v2/auth', v2AuthRouter); app.use("/api/v2/auth", v2AuthRouter);
app.use('/api/v2/users', v2UsersRouter); app.use("/api/v2/users", v2UsersRouter);
app.use('/api/v2/organizations', v2OrganizationsRouter); app.use("/api/v2/organizations", v2OrganizationsRouter);
app.use('/api/v2/workspace', v2EnvironmentRouter); app.use("/api/v2/workspace", v2EnvironmentRouter);
app.use('/api/v2/workspace', v2TagsRouter); app.use("/api/v2/workspace", v2TagsRouter);
app.use('/api/v2/workspace', v2WorkspaceRouter); app.use("/api/v2/workspace", v2WorkspaceRouter);
app.use('/api/v2/secret', v2SecretRouter); // deprecated app.use("/api/v2/secret", v2SecretRouter); // deprecated
app.use('/api/v2/secrets', v2SecretsRouter); app.use("/api/v2/secrets", v2SecretsRouter);
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route app.use("/api/v2/service-token", v2ServiceTokenDataRouter); // TODO: turn into plural route
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
app.use('/api/v2/api-key', v2APIKeyDataRouter); app.use("/api/v2/api-key", v2APIKeyDataRouter);
// v3 routes (experimental) // v3 routes (experimental)
app.use('/api/v3/auth', v3AuthRouter); app.use("/api/v3/auth", v3AuthRouter);
app.use('/api/v3/secrets', v3SecretsRouter); app.use("/api/v3/secrets", v3SecretsRouter);
app.use('/api/v3/workspaces', v3WorkspacesRouter); app.use("/api/v3/workspaces", v3WorkspacesRouter);
app.use('/api/v3/signup', v3SignupRouter); app.use("/api/v3/signup", v3SignupRouter);
// api docs // api docs
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile)) app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
// Server status // Server status
app.use('/api', healthCheck) app.use("/api", healthCheck);
//* Handle unrouted requests and respond with proper error message as well as status code //* Handle unrouted requests and respond with proper error message as well as status code
app.use((req, res, next) => { app.use((req, res, next) => {
if (res.headersSent) return next(); if (res.headersSent) return next();
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` })) next(
RouteNotFoundError({
message: `The requested source '(${req.method})${req.url}' was not found`,
}) })
);
});
app.use(requestErrorHandler) app.use(requestErrorHandler);
const server = app.listen(await getPort(), async () => { const server = app.listen(await getPort(), async () => {
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`) (await getLogger("backend-main")).info(
`Server started listening at port ${await getPort()}`
);
}); });
await createTestUserForDevelopment(); await createTestUserForDevelopment();
setUpHealthEndpoint(server); setUpHealthEndpoint(server);
server.on('close', async () => { server.on("close", async () => {
await DatabaseService.closeDatabase(); await DatabaseService.closeDatabase();
}) });
return server; 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>