mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 10:28:22 +00:00
Merge branch 'main' into feature/google-signin-signup-integration
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
backend/node_modules
|
||||||
|
frontend/node_modules
|
||||||
@@ -3,3 +3,5 @@
|
|||||||
. "$(dirname -- "$0")/_/husky.sh"
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
npx lint-staged
|
npx lint-staged
|
||||||
|
|
||||||
|
infisical scan git-changes --staged -v
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Generated
+1
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
});
|
||||||
@@ -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({
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: []
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
@@ -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';
|
||||||
|
|
||||||
|
|||||||
@@ -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();
|
||||||
@@ -29,6 +29,16 @@ import {
|
|||||||
} from "../utils/errors";
|
} from "../utils/errors";
|
||||||
import { validateUserClientForOrganization } from "../helpers/user";
|
import { validateUserClientForOrganization } from "../helpers/user";
|
||||||
import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
|
import { validateServiceAccountClientForOrganization } from "../helpers/serviceAccount";
|
||||||
|
import {
|
||||||
|
EELicenseService
|
||||||
|
} from '../ee/services';
|
||||||
|
import {
|
||||||
|
getLicenseServerUrl
|
||||||
|
} from '../config';
|
||||||
|
import {
|
||||||
|
licenseServerKeyRequest,
|
||||||
|
licenseKeyRequest
|
||||||
|
} from '../config/request';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate accepted clients for organization with id [organizationId]
|
* Validate accepted clients for organization with id [organizationId]
|
||||||
@@ -228,30 +238,35 @@ const updateSubscriptionOrgQuantity = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (organization && organization.customerId) {
|
if (organization && organization.customerId) {
|
||||||
const quantity = await MembershipOrg.countDocuments({
|
if (EELicenseService.instanceType === 'cloud') {
|
||||||
organization: organizationId,
|
// instance of Infisical is a cloud instance
|
||||||
status: ACCEPTED,
|
const quantity = await MembershipOrg.countDocuments({
|
||||||
});
|
organization: new Types.ObjectId(organizationId),
|
||||||
|
status: ACCEPTED,
|
||||||
|
});
|
||||||
|
|
||||||
const stripe = new Stripe(await getStripeSecretKey(), {
|
await licenseServerKeyRequest.patch(
|
||||||
apiVersion: "2022-08-01",
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${organization.customerId}/cloud-plan`,
|
||||||
});
|
|
||||||
|
|
||||||
const subscription = (
|
|
||||||
await stripe.subscriptions.list({
|
|
||||||
customer: organization.customerId,
|
|
||||||
})
|
|
||||||
).data[0];
|
|
||||||
|
|
||||||
stripeSubscription = await stripe.subscriptions.update(subscription.id, {
|
|
||||||
items: [
|
|
||||||
{
|
{
|
||||||
id: subscription.items.data[0].id,
|
quantity
|
||||||
price: subscription.items.data[0].price.id,
|
}
|
||||||
quantity,
|
);
|
||||||
},
|
}
|
||||||
],
|
|
||||||
});
|
if (EELicenseService.instanceType === 'enterprise-self-hosted') {
|
||||||
|
// instance of Infisical is an enterprise self-hosted instance
|
||||||
|
|
||||||
|
const usedSeats = await MembershipOrg.countDocuments({
|
||||||
|
status: ACCEPTED
|
||||||
|
});
|
||||||
|
|
||||||
|
await licenseKeyRequest.patch(
|
||||||
|
`${await getLicenseServerUrl()}/api/license/v1/license`,
|
||||||
|
{
|
||||||
|
usedSeats
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return stripeSubscription;
|
return stripeSubscription;
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const validateClientForWorkspace = async ({
|
|||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
return ({ membership });
|
return ({ membership, workspace });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
if (authData.authMode === AUTH_MODE_SERVICE_ACCOUNT && authData.authPayload instanceof ServiceAccount) {
|
||||||
@@ -123,7 +123,7 @@ const validateClientForWorkspace = async ({
|
|||||||
requiredPermissions
|
requiredPermissions
|
||||||
});
|
});
|
||||||
|
|
||||||
return ({ membership });
|
return ({ membership, workspace });
|
||||||
}
|
}
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
|
|||||||
+189
-177
@@ -1,198 +1,210 @@
|
|||||||
import dotenv from 'dotenv';
|
import dotenv from "dotenv";
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
import express from 'express';
|
import express from "express";
|
||||||
import helmet from 'helmet';
|
import helmet from "helmet";
|
||||||
import cors from 'cors';
|
import cors from "cors";
|
||||||
import * as Sentry from '@sentry/node';
|
import * as Sentry from "@sentry/node";
|
||||||
import { DatabaseService } from './services';
|
import { DatabaseService } from "./services";
|
||||||
import { setUpHealthEndpoint } from './services/health';
|
import { EELicenseService } from "./ee/services";
|
||||||
import { initSmtp } from './services/smtp';
|
import { setUpHealthEndpoint } from "./services/health";
|
||||||
import { TelemetryService } from './services';
|
import { initSmtp } from "./services/smtp";
|
||||||
import { setTransporter } from './helpers/nodemailer';
|
import { TelemetryService } from "./services";
|
||||||
import { createTestUserForDevelopment } from './utils/addDevelopmentUser';
|
import { setTransporter } from "./helpers/nodemailer";
|
||||||
|
import { createTestUserForDevelopment } from "./utils/addDevelopmentUser";
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { patchRouterParam } = require('./utils/patchAsyncRoutes');
|
const { patchRouterParam } = require("./utils/patchAsyncRoutes");
|
||||||
|
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from "cookie-parser";
|
||||||
import swaggerUi = require('swagger-ui-express');
|
import swaggerUi = require("swagger-ui-express");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const swaggerFile = require('../spec.json');
|
const swaggerFile = require("../spec.json");
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const requestIp = require('request-ip');
|
const requestIp = require("request-ip");
|
||||||
import { apiLimiter } from './helpers/rateLimiter';
|
import { apiLimiter } from "./helpers/rateLimiter";
|
||||||
import {
|
import {
|
||||||
workspace as eeWorkspaceRouter,
|
workspace as eeWorkspaceRouter,
|
||||||
secret as eeSecretRouter,
|
secret as eeSecretRouter,
|
||||||
secretSnapshot as eeSecretSnapshotRouter,
|
secretSnapshot as eeSecretSnapshotRouter,
|
||||||
action as eeActionRouter
|
action as eeActionRouter,
|
||||||
} from './ee/routes/v1';
|
organizations as eeOrganizationsRouter,
|
||||||
|
cloudProducts as eeCloudProductsRouter,
|
||||||
|
} from "./ee/routes/v1";
|
||||||
import {
|
import {
|
||||||
signup as v1SignupRouter,
|
signup as v1SignupRouter,
|
||||||
auth as v1AuthRouter,
|
auth as v1AuthRouter,
|
||||||
bot as v1BotRouter,
|
bot as v1BotRouter,
|
||||||
organization as v1OrganizationRouter,
|
organization as v1OrganizationRouter,
|
||||||
workspace as v1WorkspaceRouter,
|
workspace as v1WorkspaceRouter,
|
||||||
membershipOrg as v1MembershipOrgRouter,
|
membershipOrg as v1MembershipOrgRouter,
|
||||||
membership as v1MembershipRouter,
|
membership as v1MembershipRouter,
|
||||||
key as v1KeyRouter,
|
key as v1KeyRouter,
|
||||||
inviteOrg as v1InviteOrgRouter,
|
inviteOrg as v1InviteOrgRouter,
|
||||||
oauth as v1OAuth,
|
oauth as v1OAuth,
|
||||||
user as v1UserRouter,
|
user as v1UserRouter,
|
||||||
userAction as v1UserActionRouter,
|
userAction as v1UserActionRouter,
|
||||||
secret as v1SecretRouter,
|
secret as v1SecretRouter,
|
||||||
serviceToken as v1ServiceTokenRouter,
|
serviceToken as v1ServiceTokenRouter,
|
||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
stripe as v1StripeRouter,
|
stripe as v1StripeRouter,
|
||||||
integration as v1IntegrationRouter,
|
integration as v1IntegrationRouter,
|
||||||
integrationAuth as v1IntegrationAuthRouter,
|
integrationAuth as v1IntegrationAuthRouter,
|
||||||
secretsFolder as v1SecretsFolder
|
secretsFolder as v1SecretsFolder,
|
||||||
} from './routes/v1';
|
} from "./routes/v1";
|
||||||
import {
|
import {
|
||||||
signup as v2SignupRouter,
|
signup as v2SignupRouter,
|
||||||
auth as v2AuthRouter,
|
auth as v2AuthRouter,
|
||||||
users as v2UsersRouter,
|
users as v2UsersRouter,
|
||||||
organizations as v2OrganizationsRouter,
|
organizations as v2OrganizationsRouter,
|
||||||
workspace as v2WorkspaceRouter,
|
workspace as v2WorkspaceRouter,
|
||||||
secret as v2SecretRouter, // begin to phase out
|
secret as v2SecretRouter, // begin to phase out
|
||||||
secrets as v2SecretsRouter,
|
secrets as v2SecretsRouter,
|
||||||
serviceTokenData as v2ServiceTokenDataRouter,
|
serviceTokenData as v2ServiceTokenDataRouter,
|
||||||
serviceAccounts as v2ServiceAccountsRouter,
|
serviceAccounts as v2ServiceAccountsRouter,
|
||||||
apiKeyData as v2APIKeyDataRouter,
|
apiKeyData as v2APIKeyDataRouter,
|
||||||
environment as v2EnvironmentRouter,
|
environment as v2EnvironmentRouter,
|
||||||
tags as v2TagsRouter,
|
tags as v2TagsRouter,
|
||||||
} from './routes/v2';
|
} from "./routes/v2";
|
||||||
import {
|
import {
|
||||||
auth as v3AuthRouter,
|
auth as v3AuthRouter,
|
||||||
secrets as v3SecretsRouter,
|
secrets as v3SecretsRouter,
|
||||||
signup as v3SignupRouter,
|
signup as v3SignupRouter,
|
||||||
workspaces as v3WorkspacesRouter,
|
workspaces as v3WorkspacesRouter,
|
||||||
} from './routes/v3';
|
} from "./routes/v3";
|
||||||
import { healthCheck } from './routes/status';
|
import { healthCheck } from "./routes/status";
|
||||||
import { getLogger } from './utils/logger';
|
import { getLogger } from "./utils/logger";
|
||||||
import { RouteNotFoundError } from './utils/errors';
|
import { RouteNotFoundError } from "./utils/errors";
|
||||||
import { requestErrorHandler } from './middleware/requestErrorHandler';
|
import { requestErrorHandler } from "./middleware/requestErrorHandler";
|
||||||
import {
|
import {
|
||||||
getMongoURL,
|
getMongoURL,
|
||||||
getNodeEnv,
|
getNodeEnv,
|
||||||
getPort,
|
getPort,
|
||||||
getSentryDSN,
|
getSentryDSN,
|
||||||
getSiteURL,
|
getSiteURL,
|
||||||
} from './config';
|
} from "./config";
|
||||||
import { initializePassport } from './utils/auth';
|
import { initializePassport } from "./utils/auth";
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
TelemetryService.logTelemetryMessage();
|
TelemetryService.logTelemetryMessage();
|
||||||
setTransporter(await initSmtp());
|
setTransporter(await initSmtp());
|
||||||
|
|
||||||
const mongoURL = await getMongoURL();
|
await EELicenseService.initGlobalFeatureSet();
|
||||||
await DatabaseService.initDatabase(mongoURL);
|
|
||||||
if ((await getNodeEnv()) !== 'test') {
|
|
||||||
Sentry.init({
|
|
||||||
dsn: await getSentryDSN(),
|
|
||||||
tracesSampleRate: 1.0,
|
|
||||||
debug: await getNodeEnv() === 'production' ? false : true,
|
|
||||||
environment: await getNodeEnv()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
patchRouterParam();
|
await DatabaseService.initDatabase(await getMongoURL());
|
||||||
const app = express();
|
if ((await getNodeEnv()) !== "test") {
|
||||||
|
Sentry.init({
|
||||||
await initializePassport();
|
dsn: await getSentryDSN(),
|
||||||
|
tracesSampleRate: 1.0,
|
||||||
app.enable('trust proxy');
|
debug: (await getNodeEnv()) === "production" ? false : true,
|
||||||
app.use(express.json());
|
environment: await getNodeEnv(),
|
||||||
app.use(cookieParser());
|
|
||||||
app.use(
|
|
||||||
cors({
|
|
||||||
credentials: true,
|
|
||||||
origin: await getSiteURL()
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
app.use(requestIp.mw());
|
|
||||||
|
|
||||||
if ((await getNodeEnv()) === 'production') {
|
|
||||||
// enable app-wide rate-limiting + helmet security
|
|
||||||
// in production
|
|
||||||
app.disable('x-powered-by');
|
|
||||||
app.use(apiLimiter);
|
|
||||||
app.use(helmet());
|
|
||||||
}
|
|
||||||
|
|
||||||
// (EE) routes
|
|
||||||
app.use('/api/v1/secret', eeSecretRouter);
|
|
||||||
app.use('/api/v1/secret-snapshot', eeSecretSnapshotRouter);
|
|
||||||
app.use('/api/v1/workspace', eeWorkspaceRouter);
|
|
||||||
app.use('/api/v1/action', eeActionRouter);
|
|
||||||
|
|
||||||
// v1 routes (default)
|
|
||||||
app.use('/api/v1/signup', v1SignupRouter);
|
|
||||||
app.use('/api/v1/auth', v1AuthRouter);
|
|
||||||
app.use('/api/v1/oauth', v1OAuth);
|
|
||||||
app.use('/api/v1/bot', v1BotRouter);
|
|
||||||
app.use('/api/v1/user', v1UserRouter);
|
|
||||||
app.use('/api/v1/user-action', v1UserActionRouter);
|
|
||||||
app.use('/api/v1/organization', v1OrganizationRouter);
|
|
||||||
app.use('/api/v1/workspace', v1WorkspaceRouter);
|
|
||||||
app.use('/api/v1/membership-org', v1MembershipOrgRouter);
|
|
||||||
app.use('/api/v1/membership', v1MembershipRouter);
|
|
||||||
app.use('/api/v1/key', v1KeyRouter);
|
|
||||||
app.use('/api/v1/invite-org', v1InviteOrgRouter);
|
|
||||||
app.use('/api/v1/secret', v1SecretRouter);
|
|
||||||
app.use('/api/v1/service-token', v1ServiceTokenRouter); // deprecated
|
|
||||||
app.use('/api/v1/password', v1PasswordRouter);
|
|
||||||
app.use('/api/v1/stripe', v1StripeRouter);
|
|
||||||
app.use('/api/v1/integration', v1IntegrationRouter);
|
|
||||||
app.use('/api/v1/integration-auth', v1IntegrationAuthRouter);
|
|
||||||
app.use('/api/v1/folder', v1SecretsFolder)
|
|
||||||
|
|
||||||
// v2 routes (improvements)
|
|
||||||
app.use('/api/v2/signup', v2SignupRouter);
|
|
||||||
app.use('/api/v2/auth', v2AuthRouter);
|
|
||||||
app.use('/api/v2/users', v2UsersRouter);
|
|
||||||
app.use('/api/v2/organizations', v2OrganizationsRouter);
|
|
||||||
app.use('/api/v2/workspace', v2EnvironmentRouter);
|
|
||||||
app.use('/api/v2/workspace', v2TagsRouter);
|
|
||||||
app.use('/api/v2/workspace', v2WorkspaceRouter);
|
|
||||||
app.use('/api/v2/secret', v2SecretRouter); // deprecated
|
|
||||||
app.use('/api/v2/secrets', v2SecretsRouter);
|
|
||||||
app.use('/api/v2/service-token', v2ServiceTokenDataRouter); // TODO: turn into plural route
|
|
||||||
app.use('/api/v2/service-accounts', v2ServiceAccountsRouter); // new
|
|
||||||
app.use('/api/v2/api-key', v2APIKeyDataRouter);
|
|
||||||
|
|
||||||
// v3 routes (experimental)
|
|
||||||
app.use('/api/v3/auth', v3AuthRouter);
|
|
||||||
app.use('/api/v3/secrets', v3SecretsRouter);
|
|
||||||
app.use('/api/v3/workspaces', v3WorkspacesRouter);
|
|
||||||
app.use('/api/v3/signup', v3SignupRouter);
|
|
||||||
|
|
||||||
// api docs
|
|
||||||
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerFile))
|
|
||||||
|
|
||||||
// Server status
|
|
||||||
app.use('/api', healthCheck)
|
|
||||||
|
|
||||||
//* Handle unrouted requests and respond with proper error message as well as status code
|
|
||||||
app.use((req, res, next) => {
|
|
||||||
if (res.headersSent) return next();
|
|
||||||
next(RouteNotFoundError({ message: `The requested source '(${req.method})${req.url}' was not found` }))
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use(requestErrorHandler)
|
|
||||||
|
|
||||||
const server = app.listen(await getPort(), async () => {
|
|
||||||
(await getLogger("backend-main")).info(`Server started listening at port ${await getPort()}`)
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await createTestUserForDevelopment();
|
patchRouterParam();
|
||||||
setUpHealthEndpoint(server);
|
const app = express();
|
||||||
|
|
||||||
server.on('close', async () => {
|
await initializePassport();
|
||||||
await DatabaseService.closeDatabase();
|
|
||||||
|
app.enable("trust proxy");
|
||||||
|
app.use(express.json());
|
||||||
|
app.use(cookieParser());
|
||||||
|
app.use(
|
||||||
|
cors({
|
||||||
|
credentials: true,
|
||||||
|
origin: await getSiteURL(),
|
||||||
})
|
})
|
||||||
|
);
|
||||||
|
|
||||||
return server;
|
app.use(requestIp.mw());
|
||||||
}
|
|
||||||
|
if ((await getNodeEnv()) === "production") {
|
||||||
|
// enable app-wide rate-limiting + helmet security
|
||||||
|
// in production
|
||||||
|
app.disable("x-powered-by");
|
||||||
|
app.use(apiLimiter);
|
||||||
|
app.use(helmet());
|
||||||
|
}
|
||||||
|
|
||||||
|
// (EE) routes
|
||||||
|
app.use("/api/v1/secret", eeSecretRouter);
|
||||||
|
app.use("/api/v1/secret-snapshot", eeSecretSnapshotRouter);
|
||||||
|
app.use("/api/v1/workspace", eeWorkspaceRouter);
|
||||||
|
app.use("/api/v1/action", eeActionRouter);
|
||||||
|
app.use("/api/v1/organizations", eeOrganizationsRouter);
|
||||||
|
app.use("/api/v1/cloud-products", eeCloudProductsRouter);
|
||||||
|
|
||||||
|
// v1 routes (default)
|
||||||
|
app.use("/api/v1/signup", v1SignupRouter);
|
||||||
|
app.use("/api/v1/auth", v1AuthRouter);
|
||||||
|
app.use("/api/v1/oauth", v1OAuth);
|
||||||
|
app.use("/api/v1/bot", v1BotRouter);
|
||||||
|
app.use("/api/v1/user", v1UserRouter);
|
||||||
|
app.use("/api/v1/user-action", v1UserActionRouter);
|
||||||
|
app.use("/api/v1/organization", v1OrganizationRouter);
|
||||||
|
app.use("/api/v1/workspace", v1WorkspaceRouter);
|
||||||
|
app.use("/api/v1/membership-org", v1MembershipOrgRouter);
|
||||||
|
app.use("/api/v1/membership", v1MembershipRouter);
|
||||||
|
app.use("/api/v1/key", v1KeyRouter);
|
||||||
|
app.use("/api/v1/invite-org", v1InviteOrgRouter);
|
||||||
|
app.use("/api/v1/secret", v1SecretRouter);
|
||||||
|
app.use("/api/v1/service-token", v1ServiceTokenRouter); // deprecated
|
||||||
|
app.use("/api/v1/password", v1PasswordRouter);
|
||||||
|
app.use("/api/v1/stripe", v1StripeRouter);
|
||||||
|
app.use("/api/v1/integration", v1IntegrationRouter);
|
||||||
|
app.use("/api/v1/integration-auth", v1IntegrationAuthRouter);
|
||||||
|
app.use("/api/v1/folder", v1SecretsFolder);
|
||||||
|
|
||||||
|
// v2 routes (improvements)
|
||||||
|
app.use("/api/v2/signup", v2SignupRouter);
|
||||||
|
app.use("/api/v2/auth", v2AuthRouter);
|
||||||
|
app.use("/api/v2/users", v2UsersRouter);
|
||||||
|
app.use("/api/v2/organizations", v2OrganizationsRouter);
|
||||||
|
app.use("/api/v2/workspace", v2EnvironmentRouter);
|
||||||
|
app.use("/api/v2/workspace", v2TagsRouter);
|
||||||
|
app.use("/api/v2/workspace", v2WorkspaceRouter);
|
||||||
|
app.use("/api/v2/secret", v2SecretRouter); // deprecated
|
||||||
|
app.use("/api/v2/secrets", v2SecretsRouter);
|
||||||
|
app.use("/api/v2/service-token", v2ServiceTokenDataRouter); // TODO: turn into plural route
|
||||||
|
app.use("/api/v2/service-accounts", v2ServiceAccountsRouter); // new
|
||||||
|
app.use("/api/v2/api-key", v2APIKeyDataRouter);
|
||||||
|
|
||||||
|
// v3 routes (experimental)
|
||||||
|
app.use("/api/v3/auth", v3AuthRouter);
|
||||||
|
app.use("/api/v3/secrets", v3SecretsRouter);
|
||||||
|
app.use("/api/v3/workspaces", v3WorkspacesRouter);
|
||||||
|
app.use("/api/v3/signup", v3SignupRouter);
|
||||||
|
|
||||||
|
// api docs
|
||||||
|
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
|
||||||
|
|
||||||
|
// Server status
|
||||||
|
app.use("/api", healthCheck);
|
||||||
|
|
||||||
|
//* Handle unrouted requests and respond with proper error message as well as status code
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
if (res.headersSent) return next();
|
||||||
|
next(
|
||||||
|
RouteNotFoundError({
|
||||||
|
message: `The requested source '(${req.method})${req.url}' was not found`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(requestErrorHandler);
|
||||||
|
|
||||||
|
const server = app.listen(await getPort(), async () => {
|
||||||
|
(await getLogger("backend-main")).info(
|
||||||
|
`Server started listening at port ${await getPort()}`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await createTestUserForDevelopment();
|
||||||
|
setUpHealthEndpoint(server);
|
||||||
|
|
||||||
|
server.on("close", async () => {
|
||||||
|
await DatabaseService.closeDatabase();
|
||||||
|
});
|
||||||
|
|
||||||
|
return server;
|
||||||
|
};
|
||||||
|
|
||||||
export default main();
|
export default main();
|
||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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: {
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user