Merge remote-tracking branch 'refs/remotes/origin/main' into circleci-integration-branch

This commit is contained in:
Aashish-Upadhyay-101
2023-02-11 17:55:59 +05:45
137 changed files with 5954 additions and 499 deletions
+3259 -49
View File
File diff suppressed because it is too large Load Diff
+23 -49
View File
@@ -1,11 +1,18 @@
{ {
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.267.0",
"@godaddy/terminus": "^4.11.2", "@godaddy/terminus": "^4.11.2",
"@sentry/node": "^7.21.1", "@octokit/rest": "^19.0.5",
"@sentry/tracing": "^7.21.1", "@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1", "@types/crypto-js": "^4.1.1",
"axios": "^1.2.0", "@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"aws-sdk": "^2.1311.0",
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2", "bigint-conversion": "^2.2.2",
"builder-pattern": "^2.2.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"cors": "^2.8.5", "cors": "^2.8.5",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
@@ -15,17 +22,26 @@
"express-validator": "^6.14.2", "express-validator": "^6.14.2",
"handlebars": "^4.7.7", "handlebars": "^4.7.7",
"helmet": "^5.1.1", "helmet": "^5.1.1",
"jsonwebtoken": "^8.5.1", "js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4", "jsrp": "^0.2.4",
"mongoose": "^6.7.3", "libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0", "nodemailer": "^6.8.0",
"posthog-node": "^2.1.0", "posthog-node": "^2.2.2",
"query-string": "^7.1.3", "query-string": "^7.1.3",
"request-ip": "^3.3.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"stripe": "^10.7.0", "stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.0",
"tweetnacl": "^1.0.3", "tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1", "tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3" "typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
}, },
"name": "infisical-api", "name": "infisical-api",
"version": "1.0.0", "version": "1.0.0",
@@ -102,47 +118,5 @@
"suiteNameTemplate": "{filepath}", "suiteNameTemplate": "{filepath}",
"classNameTemplate": "{classname}", "classNameTemplate": "{classname}",
"titleTemplate": "{title}" "titleTemplate": "{title}"
},
"dependencies": {
"@godaddy/terminus": "^4.11.2",
"@octokit/rest": "^19.0.5",
"@sentry/node": "^7.14.0",
"@sentry/tracing": "^7.19.0",
"@types/crypto-js": "^4.1.1",
"@types/libsodium-wrappers": "^0.7.10",
"await-to-js": "^3.0.0",
"axios": "^1.1.3",
"bcrypt": "^5.1.0",
"bigint-conversion": "^2.2.2",
"builder-pattern": "^2.2.0",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"crypto-js": "^4.1.1",
"dotenv": "^16.0.1",
"express": "^4.18.1",
"express-rate-limit": "^6.7.0",
"express-validator": "^6.14.2",
"handlebars": "^4.7.7",
"helmet": "^5.1.1",
"js-yaml": "^4.1.0",
"jsonwebtoken": "^9.0.0",
"jsrp": "^0.2.4",
"libsodium-wrappers": "^0.7.10",
"lodash": "^4.17.21",
"mongoose": "^6.7.2",
"nodemailer": "^6.8.0",
"posthog-node": "^2.2.2",
"query-string": "^7.1.3",
"request-ip": "^3.3.0",
"rimraf": "^3.0.2",
"stripe": "^10.7.0",
"swagger-autogen": "^2.22.0",
"swagger-ui-express": "^4.6.0",
"tweetnacl": "^1.0.3",
"tweetnacl-util": "^0.15.1",
"typescript": "^4.9.3",
"utility-types": "^3.10.0",
"winston": "^3.8.2",
"winston-loki": "^6.0.6"
} }
} }
@@ -81,26 +81,31 @@ export const oAuthExchange = async (
}; };
/** /**
* Save integration access token as part of integration [integration] for workspace with id [workspaceId] * Save integration access token and (optionally) access id as part of integration
* @param req * [integration] for workspace with id [workspaceId]
* @param res * @param req
* @param res
*/ */
export const saveIntegrationAccessToken = async ( export const saveIntegrationAccessToken = async (
req: Request, req: Request,
res: Response res: Response
) => { ) => {
// TODO: refactor // TODO: refactor
let integrationAuth; // TODO: check if access token is valid for each integration
try {
const { let integrationAuth;
workspaceId, try {
accessToken, const {
integration, workspaceId,
}: { accessId,
workspaceId: string; accessToken,
accessToken: string; integration
integration: string; }: {
} = req.body; workspaceId: string;
accessId: string | null;
accessToken: string;
integration: string;
} = req.body;
const bot = await Bot.findOne({ const bot = await Bot.findOne({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
@@ -120,9 +125,10 @@ export const saveIntegrationAccessToken = async (
upsert: true upsert: true
}); });
// encrypt and save integration access token // encrypt and save integration access details
integrationAuth = await IntegrationService.setIntegrationAuthAccess({ integrationAuth = await IntegrationService.setIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString(), integrationAuthId: integrationAuth._id.toString(),
accessId,
accessToken, accessToken,
accessExpiresAt: undefined accessExpiresAt: undefined
}); });
@@ -26,7 +26,9 @@ export const createIntegration = async (req: Request, res: Response) => {
isActive, isActive,
sourceEnvironment, sourceEnvironment,
targetEnvironment, targetEnvironment,
owner owner,
path,
region
} = req.body; } = req.body;
// TODO: validate [sourceEnvironment] and [targetEnvironment] // TODO: validate [sourceEnvironment] and [targetEnvironment]
@@ -40,6 +42,8 @@ export const createIntegration = async (req: Request, res: Response) => {
appId, appId,
targetEnvironment, targetEnvironment,
owner, owner,
path,
region,
integration: req.integrationAuth.integration, integration: req.integrationAuth.integration,
integrationAuth: new Types.ObjectId(integrationAuthId) integrationAuth: new Types.ObjectId(integrationAuthId)
}).save(); }).save();
@@ -452,7 +452,7 @@ export const updateSecrets = async (req: Request, res: Response) => {
secretValueTag, secretValueTag,
tags, tags,
...(( ...((
secretCommentCiphertext && secretCommentCiphertext !== undefined &&
secretCommentIV && secretCommentIV &&
secretCommentTag secretCommentTag
) ? { ) ? {
+7 -1
View File
@@ -2,7 +2,7 @@ import { Request, Response } from 'express';
import * as Sentry from '@sentry/node'; import * as Sentry from '@sentry/node';
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import { import {
Membership, Membership, Secret,
} from '../../models'; } from '../../models';
import Tag, { ITag } from '../../models/tag'; import Tag, { ITag } from '../../models/tag';
import { Builder } from "builder-pattern" import { Builder } from "builder-pattern"
@@ -54,6 +54,12 @@ export const deleteWorkspaceTag = async (req: Request, res: Response) => {
const result = await Tag.findByIdAndDelete(tagId); const result = await Tag.findByIdAndDelete(tagId);
// remove the tag from secrets
await Secret.updateMany(
{ tags: { $in: [tagId] } },
{ $pull: { tags: tagId } }
);
res.json(result); res.json(result);
} }
+39 -12
View File
@@ -94,6 +94,7 @@ const handleOAuthExchangeHelper = async ({
// set integration auth access token // set integration auth access token
await setIntegrationAuthAccessHelper({ await setIntegrationAuthAccessHelper({
integrationAuthId: integrationAuth._id.toString(), integrationAuthId: integrationAuth._id.toString(),
accessId: null,
accessToken: res.accessToken, accessToken: res.accessToken,
accessExpiresAt: res.accessExpiresAt accessExpiresAt: res.accessExpiresAt
}); });
@@ -138,7 +139,7 @@ const syncIntegrationsHelper = async ({
if (!integrationAuth) throw new Error('Failed to find integration auth'); if (!integrationAuth) throw new Error('Failed to find integration auth');
// get integration auth access token // get integration auth access token
const accessToken = await getIntegrationAuthAccessHelper({ const access = await getIntegrationAuthAccessHelper({
integrationAuthId: integration.integrationAuth.toString() integrationAuthId: integration.integrationAuth.toString()
}); });
@@ -147,7 +148,8 @@ const syncIntegrationsHelper = async ({
integration, integration,
integrationAuth, integrationAuth,
secrets, secrets,
accessToken accessId: access.accessId,
accessToken: access.accessToken
}); });
} }
} catch (err) { } catch (err) {
@@ -203,12 +205,12 @@ const syncIntegrationsHelper = async ({
* @returns {String} accessToken - decrypted access token * @returns {String} accessToken - decrypted access token
*/ */
const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => { const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrationAuthId: string }) => {
let accessId;
let accessToken; let accessToken;
try { try {
const integrationAuth = await IntegrationAuth const integrationAuth = await IntegrationAuth
.findById(integrationAuthId) .findById(integrationAuthId)
.select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext'); .select('workspace integration +accessCiphertext +accessIV +accessTag +accessExpiresAt + refreshCiphertext +accessIdCiphertext +accessIdIV +accessIdTag');
if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'}); if (!integrationAuth) throw UnauthorizedRequestError({message: 'Failed to locate Integration Authentication credentials'});
@@ -232,6 +234,15 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
}); });
} }
} }
if (integrationAuth?.accessIdCiphertext && integrationAuth?.accessIdIV && integrationAuth?.accessIdTag) {
accessId = await BotService.decryptSymmetric({
workspaceId: integrationAuth.workspace.toString(),
ciphertext: integrationAuth.accessIdCiphertext as string,
iv: integrationAuth.accessIdIV as string,
tag: integrationAuth.accessIdTag as string
});
}
} catch (err) { } catch (err) {
Sentry.setUser(null); Sentry.setUser(null);
@@ -242,7 +253,10 @@ const getIntegrationAuthAccessHelper = async ({ integrationAuthId }: { integrati
throw new Error('Failed to get integration access token'); throw new Error('Failed to get integration access token');
} }
return accessToken; return ({
accessId,
accessToken
});
} }
/** /**
@@ -292,9 +306,9 @@ const setIntegrationAuthRefreshHelper = async ({
} }
/** /**
* Encrypt access token [accessToken] using the bot's copy * Encrypt access token [accessToken] and (optionally) access id [accessId]
* of the workspace key for workspace belonging to integration auth * using the bot's copy of the workspace key for workspace belonging to
* with id [integrationAuthId] and store it along with [accessExpiresAt] * integration auth with id [integrationAuthId] and store it along with [accessExpiresAt]
* @param {Object} obj * @param {Object} obj
* @param {String} obj.integrationAuthId - id of integration auth * @param {String} obj.integrationAuthId - id of integration auth
* @param {String} obj.accessToken - access token * @param {String} obj.accessToken - access token
@@ -302,10 +316,12 @@ const setIntegrationAuthRefreshHelper = async ({
*/ */
const setIntegrationAuthAccessHelper = async ({ const setIntegrationAuthAccessHelper = async ({
integrationAuthId, integrationAuthId,
accessId,
accessToken, accessToken,
accessExpiresAt accessExpiresAt
}: { }: {
integrationAuthId: string; integrationAuthId: string;
accessId: string | null;
accessToken: string; accessToken: string;
accessExpiresAt: Date | undefined; accessExpiresAt: Date | undefined;
}) => { }) => {
@@ -315,17 +331,28 @@ const setIntegrationAuthAccessHelper = async ({
if (!integrationAuth) throw new Error('Failed to find integration auth'); if (!integrationAuth) throw new Error('Failed to find integration auth');
const obj = await BotService.encryptSymmetric({ const encryptedAccessTokenObj = await BotService.encryptSymmetric({
workspaceId: integrationAuth.workspace.toString(), workspaceId: integrationAuth.workspace.toString(),
plaintext: accessToken plaintext: accessToken
}); });
let encryptedAccessIdObj;
if (accessId) {
encryptedAccessIdObj = await BotService.encryptSymmetric({
workspaceId: integrationAuth.workspace.toString(),
plaintext: accessId
});
}
integrationAuth = await IntegrationAuth.findOneAndUpdate({ integrationAuth = await IntegrationAuth.findOneAndUpdate({
_id: integrationAuthId _id: integrationAuthId
}, { }, {
accessCiphertext: obj.ciphertext, accessIdCiphertext: encryptedAccessIdObj?.ciphertext ?? undefined,
accessIV: obj.iv, accessIdIV: encryptedAccessIdObj?.iv ?? undefined,
accessTag: obj.tag, accessIdTag: encryptedAccessIdObj?.tag ?? undefined,
accessCiphertext: encryptedAccessTokenObj.ciphertext,
accessIV: encryptedAccessTokenObj.iv,
accessTag: encryptedAccessTokenObj.tag,
accessExpiresAt accessExpiresAt
}, { }, {
new: true new: true
+13 -14
View File
@@ -4,6 +4,8 @@ import { Octokit } from "@octokit/rest";
import { IIntegrationAuth } from "../models"; import { IIntegrationAuth } from "../models";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
@@ -44,10 +46,14 @@ const getApps = async ({
try { try {
switch (integrationAuth.integration) { switch (integrationAuth.integration) {
case INTEGRATION_AZURE_KEY_VAULT: case INTEGRATION_AZURE_KEY_VAULT:
apps = await getAppsAzureKeyVault({ apps = [];
accessToken break;
}); case INTEGRATION_AWS_PARAMETER_STORE:
break; apps = [];
break;
case INTEGRATION_AWS_SECRET_MANAGER:
apps = [];
break;
case INTEGRATION_HEROKU: case INTEGRATION_HEROKU:
apps = await getAppsHeroku({ apps = await getAppsHeroku({
accessToken, accessToken,
@@ -94,15 +100,6 @@ const getApps = async ({
return apps; return apps;
}; };
const getAppsAzureKeyVault = async ({
accessToken
}: {
accessToken: string;
}) => {
// TODO
return [];
}
/** /**
* Return list of apps for Heroku integration * Return list of apps for Heroku integration
* @param {Object} obj * @param {Object} obj
@@ -154,6 +151,7 @@ const getAppsVercel = async ({
await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, { await axios.get(`${INTEGRATION_VERCEL_API_URL}/v9/projects`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
'Accept-Encoding': 'application/json'
}, },
...(integrationAuth?.teamId ...(integrationAuth?.teamId
? { ? {
@@ -191,7 +189,8 @@ const getAppsNetlify = async ({ accessToken }: { accessToken: string }) => {
await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, { await axios.get(`${INTEGRATION_NETLIFY_API_URL}/api/v1/sites`, {
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`,
}, 'Accept-Encoding': 'application/json'
}
}) })
).data; ).data;
+1 -1
View File
@@ -141,7 +141,7 @@ const exchangeCodeAzure = async ({
new URLSearchParams({ new URLSearchParams({
grant_type: 'authorization_code', grant_type: 'authorization_code',
code: code, code: code,
scope: 'https://vault.azure.net/.default openid offline_access', // TODO: do we need all these permissions? scope: 'https://vault.azure.net/.default openid offline_access',
client_id: CLIENT_ID_AZURE, client_id: CLIENT_ID_AZURE,
client_secret: CLIENT_SECRET_AZURE, client_secret: CLIENT_SECRET_AZURE,
redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback` redirect_uri: `${SITE_URL}/integrations/azure-key-vault/oauth2/callback`
+235 -33
View File
@@ -1,10 +1,21 @@
import axios from "axios"; import axios from "axios";
import * as Sentry from "@sentry/node"; import * as Sentry from "@sentry/node";
import _ from 'lodash';
import AWS from 'aws-sdk';
import {
SecretsManagerClient,
UpdateSecretCommand,
CreateSecretCommand,
GetSecretValueCommand,
ResourceNotFoundException
} from '@aws-sdk/client-secrets-manager';
import { Octokit } from "@octokit/rest"; import { Octokit } from "@octokit/rest";
import sodium from "libsodium-wrappers"; import sodium from "libsodium-wrappers";
import { IIntegration, IIntegrationAuth } from "../models"; import { IIntegration, IIntegrationAuth } from "../models";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
@@ -27,17 +38,20 @@ import { access, appendFile } from "fs";
* @param {IIntegration} obj.integration - integration details * @param {IIntegration} obj.integration - integration details
* @param {IIntegrationAuth} obj.integrationAuth - integration auth details * @param {IIntegrationAuth} obj.integrationAuth - integration auth details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values) * @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessId - access id for integration
* @param {String} obj.accessToken - access token for integration * @param {String} obj.accessToken - access token for integration
*/ */
const syncSecrets = async ({ const syncSecrets = async ({
integration, integration,
integrationAuth, integrationAuth,
secrets, secrets,
accessId,
accessToken, accessToken,
}: { }: {
integration: IIntegration; integration: IIntegration;
integrationAuth: IIntegrationAuth; integrationAuth: IIntegrationAuth;
secrets: any; secrets: any;
accessId: string | null;
accessToken: string; accessToken: string;
}) => { }) => {
try { try {
@@ -49,6 +63,22 @@ const syncSecrets = async ({
accessToken accessToken
}); });
break; break;
case INTEGRATION_AWS_PARAMETER_STORE:
await syncSecretsAWSParameterStore({
integration,
secrets,
accessId,
accessToken
});
break;
case INTEGRATION_AWS_SECRET_MANAGER:
await syncSecretsAWSSecretManager({
integration,
secrets,
accessId,
accessToken
});
break;
case INTEGRATION_HEROKU: case INTEGRATION_HEROKU:
await syncSecretsHeroku({ await syncSecretsHeroku({
integration, integration,
@@ -252,6 +282,188 @@ const syncSecretsAzureKeyVault = async ({
} }
}; };
/**
* Sync/push [secrets] to AWS parameter store
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessId - access id for AWS parameter store integration
* @param {String} obj.accessToken - access token for AWS parameter store integration
*/
const syncSecretsAWSParameterStore = async ({
integration,
secrets,
accessId,
accessToken
}: {
integration: IIntegration;
secrets: any;
accessId: string | null;
accessToken: string;
}) => {
try {
if (!accessId) return;
AWS.config.update({
region: integration.region,
accessKeyId: accessId,
secretAccessKey: accessToken
});
const ssm = new AWS.SSM({
apiVersion: '2014-11-06',
region: integration.region
});
const params = {
Path: integration.path,
Recursive: true,
WithDecryption: true
};
const parameterList = (await ssm.getParametersByPath(params).promise()).Parameters
let awsParameterStoreSecretsObj: {
[key: string]: any // TODO: fix type
} = {};
if (parameterList) {
awsParameterStoreSecretsObj = parameterList.reduce((obj: any, secret: any) => ({
...obj,
[secret.Name.split("/").pop()]: secret
}), {});
}
// Identify secrets to create
Object.keys(secrets).map(async (key) => {
if (!(key in awsParameterStoreSecretsObj)) {
// case: secret does not exist in AWS parameter store
// -> create secret
await ssm.putParameter({
Name: `${integration.path}${key}`,
Type: 'SecureString',
Value: secrets[key],
Overwrite: true
}).promise();
} else {
// case: secret exists in AWS parameter store
if (awsParameterStoreSecretsObj[key].Value !== secrets[key]) {
// case: secret value doesn't match one in AWS parameter store
// -> update secret
await ssm.putParameter({
Name: `${integration.path}${key}`,
Type: 'SecureString',
Value: secrets[key],
Overwrite: true
}).promise();
}
}
});
// Identify secrets to delete
Object.keys(awsParameterStoreSecretsObj).map(async (key) => {
if (!(key in secrets)) {
// case:
// -> delete secret
await ssm.deleteParameter({
Name: awsParameterStoreSecretsObj[key].Name
}).promise();
}
});
AWS.config.update({
region: undefined,
accessKeyId: undefined,
secretAccessKey: undefined
});
} catch (err) {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to AWS Parameter Store');
}
}
/**
* Sync/push [secrets] to AWS secret manager
* @param {Object} obj
* @param {IIntegration} obj.integration - integration details
* @param {Object} obj.secrets - secrets to push to integration (object where keys are secret keys and values are secret values)
* @param {String} obj.accessId - access id for AWS secret manager integration
* @param {String} obj.accessToken - access token for AWS secret manager integration
*/
const syncSecretsAWSSecretManager = async ({
integration,
secrets,
accessId,
accessToken
}: {
integration: IIntegration;
secrets: any;
accessId: string | null;
accessToken: string;
}) => {
let secretsManager;
try {
if (!accessId) return;
AWS.config.update({
region: integration.region,
accessKeyId: accessId,
secretAccessKey: accessToken
});
secretsManager = new SecretsManagerClient({
region: integration.region,
credentials: {
accessKeyId: accessId,
secretAccessKey: accessToken
}
});
const awsSecretManagerSecret = await secretsManager.send(
new GetSecretValueCommand({
SecretId: integration.app
})
);
let awsSecretManagerSecretObj: { [key: string]: any } = {};
if (awsSecretManagerSecret?.SecretString) {
awsSecretManagerSecretObj = JSON.parse(awsSecretManagerSecret.SecretString);
}
if (!_.isEqual(awsSecretManagerSecretObj, secrets)) {
await secretsManager.send(new UpdateSecretCommand({
SecretId: integration.app,
SecretString: JSON.stringify(secrets)
}));
}
AWS.config.update({
region: undefined,
accessKeyId: undefined,
secretAccessKey: undefined
});
} catch (err) {
if (err instanceof ResourceNotFoundException && secretsManager) {
await secretsManager.send(new CreateSecretCommand({
Name: integration.app,
SecretString: JSON.stringify(secrets)
}));
} else {
Sentry.setUser(null);
Sentry.captureException(err);
throw new Error('Failed to sync secrets to AWS Secret Manager');
}
AWS.config.update({
region: undefined,
accessKeyId: undefined,
secretAccessKey: undefined
});
}
}
/** /**
* Sync/push [secrets] to Heroku app named [integration.app] * Sync/push [secrets] to Heroku app named [integration.app]
* @param {Object} obj * @param {Object} obj
@@ -349,40 +561,30 @@ const syncSecretsVercel = async ({
{ {
params, params,
headers: { headers: {
Authorization: `Bearer ${accessToken}`, Authorization: `Bearer ${accessToken}`
}, }
} }
) ))
).data.envs .data
.filter((secret: VercelSecret) => .envs
secret.target.includes(integration.targetEnvironment) .filter((secret: VercelSecret) => secret.target.includes(integration.targetEnvironment))
) .map(async (secret: VercelSecret) => (await axios.get(
.map( `${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`,
async (secret: VercelSecret) => {
( params,
await axios.get( headers: {
`${INTEGRATION_VERCEL_API_URL}/v9/projects/${integration.app}/env/${secret.id}`, Authorization: `Bearer ${accessToken}`
{ }
params, }
headers: { )).data)
Authorization: `Bearer ${accessToken}`, )).reduce((obj: any, secret: any) => ({
}, ...obj,
} [secret.key]: secret
) }), {});
).data
)
)
).reduce(
(obj: any, secret: any) => ({
...obj,
[secret.key]: secret,
}),
{}
);
const updateSecrets: VercelSecret[] = []; const updateSecrets: VercelSecret[] = [];
const deleteSecrets: VercelSecret[] = []; const deleteSecrets: VercelSecret[] = [];
const newSecrets: VercelSecret[] = []; const newSecrets: VercelSecret[] = [];
// Identify secrets to create // Identify secrets to create
Object.keys(secrets).map((key) => { Object.keys(secrets).map((key) => {
@@ -45,9 +45,10 @@ const requireIntegrationAuthorizationAuth = ({
req.integrationAuth = integrationAuth; req.integrationAuth = integrationAuth;
if (attachAccessToken) { if (attachAccessToken) {
req.accessToken = await IntegrationService.getIntegrationAuthAccess({ const access = await IntegrationService.getIntegrationAuthAccess({
integrationAuthId: integrationAuth._id.toString() integrationAuthId: integrationAuth._id.toString()
}); });
req.accessToken = access.accessToken;
} }
return next(); return next();
+36 -8
View File
@@ -1,6 +1,8 @@
import { Schema, model, Types } from "mongoose"; import { Schema, model, Types } from "mongoose";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
@@ -19,15 +21,19 @@ export interface IIntegration {
owner: string; owner: string;
targetEnvironment: string; targetEnvironment: string;
appId: string; appId: string;
path: string;
region: string;
integration: integration:
| "heroku" | 'azure-key-vault'
| "vercel" | 'aws-parameter-store'
| "netlify" | 'aws-secret-manager'
| "github" | 'heroku'
| "render" | 'vercel'
| "flyio" | 'netlify'
| "azure-key-vault" | 'github'
| "circleci"; | 'render'
| 'flyio'
| 'circleci';
integrationAuth: Types.ObjectId; integrationAuth: Types.ObjectId;
} }
@@ -68,10 +74,32 @@ const integrationSchema = new Schema<IIntegration>(
type: String, type: String,
default: null, default: null,
}, },
path: {
// aws-parameter-store-specific path
type: String,
default: null
},
region: {
// aws-parameter-store-specific path
type: String,
default: null
},
path: {
// aws-parameter-store-specific path
type: String,
default: null
},
region: {
// aws-parameter-store-specific path
type: String,
default: null
},
integration: { integration: {
type: String, type: String,
enum: [ enum: [
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
+36 -9
View File
@@ -1,30 +1,29 @@
import { Schema, model, Types } from "mongoose"; import { Schema, model, Types } from "mongoose";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI, INTEGRATION_CIRCLECI,
} from "../variables"; } from "../variables";
export interface IIntegrationAuth { export interface IIntegrationAuth {
_id: Types.ObjectId; _id: Types.ObjectId;
workspace: Types.ObjectId; workspace: Types.ObjectId;
integration: integration: 'heroku' | 'vercel' | 'netlify' | 'github' | 'render' | 'flyio' | 'azure-key-vault' | 'circleci' | 'aws-parameter-store' | 'aws-secret-manager';
| "heroku"
| "vercel"
| "netlify"
| "github"
| "render"
| "flyio"
| "azure-key-vault"
| "circleci";
teamId: string; teamId: string;
accountId: string; accountId: string;
refreshCiphertext?: string; refreshCiphertext?: string;
refreshIV?: string; refreshIV?: string;
refreshTag?: string; refreshTag?: string;
accessIdCiphertext?: string; // new
accessIdIV?: string; // new
accessIdTag?: string; // new
accessCiphertext?: string; accessCiphertext?: string;
accessIV?: string; accessIV?: string;
accessTag?: string; accessTag?: string;
@@ -42,10 +41,14 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
type: String, type: String,
enum: [ enum: [
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
INTEGRATION_GITHUB, INTEGRATION_GITHUB,
INTEGRATION_RENDER,
INTEGRATION_FLYIO,
INTEGRATION_CIRCLECI, INTEGRATION_CIRCLECI,
], ],
required: true, required: true,
@@ -70,6 +73,30 @@ const integrationAuthSchema = new Schema<IIntegrationAuth>(
type: String, type: String,
select: false, select: false,
}, },
accessIdCiphertext: {
type: String,
select: false
},
accessIdIV: {
type: String,
select: false
},
accessIdTag: {
type: String,
select: false
},
accessIdCiphertext: {
type: String,
select: false
},
accessIdIV: {
type: String,
select: false
},
accessIdTag: {
type: String,
select: false
},
accessCiphertext: { accessCiphertext: {
type: String, type: String,
select: false, select: false,
+3
View File
@@ -109,6 +109,9 @@ const secretSchema = new Schema<ISecret>(
} }
); );
secretSchema.index({ tags: 1 }, { background: true })
const Secret = model<ISecret>('Secret', secretSchema); const Secret = model<ISecret>('Secret', secretSchema);
export default Secret; export default Secret;
+3 -1
View File
@@ -20,12 +20,14 @@ router.post( // new: add new integration for integration auth
location: 'body' location: 'body'
}), }),
body('integrationAuthId').exists().isString().trim(), body('integrationAuthId').exists().isString().trim(),
body('app').isString().trim(), body('app').trim(),
body('isActive').exists().isBoolean(), body('isActive').exists().isBoolean(),
body('appId').trim(), body('appId').trim(),
body('sourceEnvironment').trim(), body('sourceEnvironment').trim(),
body('targetEnvironment').trim(), body('targetEnvironment').trim(),
body('owner').trim(), body('owner').trim(),
body('path').trim(),
body('region').trim(),
validateRequest, validateRequest,
integrationController.createIntegration integrationController.createIntegration
); );
+1
View File
@@ -57,6 +57,7 @@ router.post(
location: 'body' location: 'body'
}), }),
body('workspaceId').exists().trim().notEmpty(), body('workspaceId').exists().trim().notEmpty(),
body('accessId').trim(),
body('accessToken').exists().trim().notEmpty(), body('accessToken').exists().trim().notEmpty(),
body('integration').exists().trim().notEmpty(), body('integration').exists().trim().notEmpty(),
validateRequest, validateRequest,
+6 -2
View File
@@ -112,26 +112,30 @@ class IntegrationService {
} }
/** /**
* Encrypt access token [accessToken] using the bot's copy * Encrypt access token [accessToken] and (optionally) access id using the
* of the workspace key for workspace belonging to integration auth * bot's copy of the workspace key for workspace belonging to integration auth
* with id [integrationAuthId] * with id [integrationAuthId]
* @param {Object} obj * @param {Object} obj
* @param {String} obj.integrationAuthId - id of integration auth * @param {String} obj.integrationAuthId - id of integration auth
* @param {String} obj.accessId - access id
* @param {String} obj.accessToken - access token * @param {String} obj.accessToken - access token
* @param {Date} obj.accessExpiresAt - expiration date of access token * @param {Date} obj.accessExpiresAt - expiration date of access token
* @returns {IntegrationAuth} - updated integration auth * @returns {IntegrationAuth} - updated integration auth
*/ */
static async setIntegrationAuthAccess({ static async setIntegrationAuthAccess({
integrationAuthId, integrationAuthId,
accessId,
accessToken, accessToken,
accessExpiresAt accessExpiresAt
}: { }: {
integrationAuthId: string; integrationAuthId: string;
accessId: string | null;
accessToken: string; accessToken: string;
accessExpiresAt: Date | undefined; accessExpiresAt: Date | undefined;
}) { }) {
return await setIntegrationAuthAccessHelper({ return await setIntegrationAuthAccessHelper({
integrationAuthId, integrationAuthId,
accessId,
accessToken, accessToken,
accessExpiresAt accessExpiresAt
}); });
@@ -6,10 +6,9 @@
<title>Email Verification</title> <title>Email Verification</title>
</head> </head>
<body> <body>
<h2>Infisical</h2>
<h2>Confirm your email address</h2> <h2>Confirm your email address</h2>
<p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p> <p>Your confirmation code is below — enter it in the browser window where you've started signing up for Infisical.</p>
<h2>{{code}}</h2> <h1>{{code}}</h1>
<p>Questions about setting up Infisical? Email us at [email protected]</p> <p>Questions about setting up Infisical? Email us at [email protected]</p>
</body> </body>
</html> </html>
@@ -7,12 +7,10 @@
<title>Organization Invitation</title> <title>Organization Invitation</title>
</head> </head>
<body> <body>
<h2>Infisical</h2> <h2>Join your organization on Infisical</h2>
<h2>Join your team on Infisical</h2> <p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p>
<p>{{inviterFirstName}}({{inviterEmail}}) has invited you to their Infisical organization — {{organizationName}}</p>
<a href="{{callback_url}}?token={{token}}&to={{email}}">Join now</a> <a href="{{callback_url}}?token={{token}}&to={{email}}">Join now</a>
<h3>What is Infisical?</h3> <h3>What is Infisical?</h3>
<p>Infisical is a simple end-to-end encrypted solution that enables teams to sync and manage their environment <p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
variables.</p>
</body> </body>
</html> </html>
@@ -6,7 +6,6 @@
<title>Account Recovery</title> <title>Account Recovery</title>
</head> </head>
<body> <body>
<h2>Infisical</h2>
<h2>Reset your password</h2> <h2>Reset your password</h2>
<p>Someone requested a password reset.</p> <p>Someone requested a password reset.</p>
<a href="{{callback_url}}?token={{token}}&to={{email}}">Reset password</a> <a href="{{callback_url}}?token={{token}}&to={{email}}">Reset password</a>
@@ -6,11 +6,10 @@
<title>Project Invitation</title> <title>Project Invitation</title>
</head> </head>
<body> <body>
<h2>Infisical</h2>
<h2>Join your team on Infisical</h2> <h2>Join your team on Infisical</h2>
<p>{{inviterFirstName}}({{inviterEmail}}) has invited you to their Infisical workspace — {{workspaceName}}</p> <p>{{inviterFirstName}} ({{inviterEmail}}) has invited you to their Infisical project — {{workspaceName}}</p>
<a href="{{callback_url}}">Join now</a> <a href="{{callback_url}}">Join now</a>
<h3>What is Infisical?</h3> <h3>What is Infisical?</h3>
<p>Infisical is a simple end-to-end encrypted solution that enables teams to sync and manage their environment variables.</p> <p>Infisical is an easy-to-use end-to-end encrypted tool that enables developers to sync and manage their secrets and configs.</p>
</body> </body>
</html> </html>
+4
View File
@@ -7,6 +7,8 @@ import {
} from "./environment"; } from "./environment";
import { import {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
@@ -57,6 +59,8 @@ export {
ENV_PROD, ENV_PROD,
ENV_SET, ENV_SET,
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
+105 -101
View File
@@ -11,6 +11,8 @@ import {
// integrations // integrations
const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault'; const INTEGRATION_AZURE_KEY_VAULT = 'azure-key-vault';
const INTEGRATION_AWS_PARAMETER_STORE = 'aws-parameter-store';
const INTEGRATION_AWS_SECRET_MANAGER = 'aws-secret-manager';
const INTEGRATION_HEROKU = "heroku"; const INTEGRATION_HEROKU = "heroku";
const INTEGRATION_VERCEL = "vercel"; const INTEGRATION_VERCEL = "vercel";
const INTEGRATION_NETLIFY = "netlify"; const INTEGRATION_NETLIFY = "netlify";
@@ -50,6 +52,79 @@ const INTEGRATION_FLYIO_API_URL = "https://api.fly.io/graphql";
const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api"; const INTEGRATION_CIRCLECI_API_URL = "https://circleci.com/api";
const INTEGRATION_OPTIONS = [ const INTEGRATION_OPTIONS = [
{
name: 'Heroku',
slug: 'heroku',
image: 'Heroku.png',
isAvailable: true,
type: 'oauth',
clientId: CLIENT_ID_HEROKU,
docsLink: ''
},
{
name: 'Vercel',
slug: 'vercel',
image: 'Vercel.png',
isAvailable: true,
type: 'oauth',
clientId: '',
clientSlug: CLIENT_SLUG_VERCEL,
docsLink: ''
},
{
name: 'Netlify',
slug: 'netlify',
image: 'Netlify.png',
isAvailable: true,
type: 'oauth',
clientId: CLIENT_ID_NETLIFY,
docsLink: ''
},
{
name: 'GitHub',
slug: 'github',
image: 'GitHub.png',
isAvailable: true,
type: 'oauth',
clientId: CLIENT_ID_GITHUB,
docsLink: ''
},
{
name: 'Render',
slug: 'render',
image: 'Render.png',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'Fly.io',
slug: 'flyio',
image: 'Flyio.svg',
isAvailable: true,
type: 'pat',
clientId: '',
docsLink: ''
},
{
name: 'AWS Parameter Store',
slug: 'aws-parameter-store',
image: 'Amazon Web Services.png',
isAvailable: true,
type: 'custom',
clientId: '',
docsLink: ''
},
{
name: 'AWS Secret Manager',
slug: 'aws-secret-manager',
image: 'Amazon Web Services.png',
isAvailable: true,
type: 'custom',
clientId: '',
docsLink: ''
},
{ {
name: 'Azure Key Vault', name: 'Azure Key Vault',
slug: 'azure-key-vault', slug: 'azure-key-vault',
@@ -60,110 +135,39 @@ const INTEGRATION_OPTIONS = [
tenantId: TENANT_ID_AZURE, tenantId: TENANT_ID_AZURE,
docsLink: '' docsLink: ''
}, },
{ {
name: "Heroku", name: 'Google Cloud Platform',
slug: "heroku", slug: 'gcp',
image: "Heroku.png", image: 'Google Cloud Platform.png',
isAvailable: true, isAvailable: false,
type: "oauth", type: '',
clientId: CLIENT_ID_HEROKU, clientId: '',
docsLink: "", docsLink: ''
}, },
{ {
name: "Vercel", name: 'Travis CI',
slug: "vercel", slug: 'travisci',
image: "Vercel.png", image: 'Travis CI.png',
isAvailable: true, isAvailable: false,
type: "oauth", type: '',
clientId: "", clientId: '',
clientSlug: CLIENT_SLUG_VERCEL, docsLink: ''
docsLink: "", },
}, {
{ name: 'Circle CI',
name: "Netlify", slug: 'circleci',
slug: "netlify", image: 'Circle CI.png',
image: "Netlify.png", isAvailable: true,
isAvailable: true, type: 'pat',
type: "oauth", clientId: '',
clientId: CLIENT_ID_NETLIFY, docsLink: ''
docsLink: "", }
}, ]
{
name: "GitHub",
slug: "github",
image: "GitHub.png",
isAvailable: true,
type: "oauth",
clientId: CLIENT_ID_GITHUB,
docsLink: "",
},
{
name: "Render",
slug: "render",
image: "Render.png",
isAvailable: true,
type: "pat",
clientId: "",
docsLink: "",
},
{
name: "Fly.io",
slug: "flyio",
image: "Flyio.svg",
isAvailable: true,
type: "pat",
clientId: "",
docsLink: "",
},
{
name: "Google Cloud Platform",
slug: "gcp",
image: "Google Cloud Platform.png",
isAvailable: false,
type: "",
clientId: "",
docsLink: "",
},
{
name: "Amazon Web Services",
slug: "aws",
image: "Amazon Web Services.png",
isAvailable: false,
type: "",
clientId: "",
docsLink: "",
},
{
name: "Microsoft Azure",
slug: "azure",
image: "Microsoft Azure.png",
isAvailable: false,
type: "",
clientId: "",
docsLink: "",
},
{
name: "Travis CI",
slug: "travisci",
image: "Travis CI.png",
isAvailable: false,
type: "",
clientId: "",
docsLink: "",
},
{
name: "Circle CI",
slug: "circleci",
image: "Circle CI.png",
isAvailable: true,
type: "pat",
clientId: "",
docsLink: "",
},
];
export { export {
INTEGRATION_AZURE_KEY_VAULT, INTEGRATION_AZURE_KEY_VAULT,
INTEGRATION_AWS_PARAMETER_STORE,
INTEGRATION_AWS_SECRET_MANAGER,
INTEGRATION_HEROKU, INTEGRATION_HEROKU,
INTEGRATION_VERCEL, INTEGRATION_VERCEL,
INTEGRATION_NETLIFY, INTEGRATION_NETLIFY,
+21
View File
@@ -166,3 +166,24 @@ func CallIsAuthenticated(httpClient *resty.Client) bool {
return true return true
} }
func CallGetAccessibleEnvironments(httpClient *resty.Client, request GetAccessibleEnvironmentsRequest) (GetAccessibleEnvironmentsResponse, error) {
var accessibleEnvironmentsResponse GetAccessibleEnvironmentsResponse
response, err := httpClient.
R().
SetResult(&accessibleEnvironmentsResponse).
SetHeader("User-Agent", USER_AGENT).
Get(fmt.Sprintf("%v/v2/workspace/%s/environments", config.INFISICAL_URL, request.WorkspaceId))
log.Debugln(fmt.Errorf("CallGetAccessibleEnvironments: Unsuccessful response: [response=%v]", response))
if err != nil {
return GetAccessibleEnvironmentsResponse{}, err
}
if response.IsError() {
return GetAccessibleEnvironmentsResponse{}, fmt.Errorf("CallGetAccessibleEnvironments: Unsuccessful response: [response=%v]", response)
}
return accessibleEnvironmentsResponse, nil
}
+12
View File
@@ -250,3 +250,15 @@ type GetServiceTokenDetailsResponse struct {
UpdatedAt time.Time `json:"updatedAt"` UpdatedAt time.Time `json:"updatedAt"`
V int `json:"__v"` V int `json:"__v"`
} }
type GetAccessibleEnvironmentsRequest struct {
WorkspaceId string `json:"workspaceId"`
}
type GetAccessibleEnvironmentsResponse struct {
AccessibleEnvironments []struct {
Name string `json:"name"`
Slug string `json:"slug"`
IsWriteDenied bool `json:"isWriteDenied"`
} `json:"accessibleEnvironments"`
}
+45
View File
@@ -0,0 +1,45 @@
/*
Copyright (c) 2023 Infisical Inc.
*/
package cmd
import (
"os"
"github.com/Infisical/infisical-merge/packages/util"
"github.com/spf13/cobra"
)
var resetCmd = &cobra.Command{
Use: "reset",
Short: "Used delete all Infisical related data on your machine",
DisableFlagsInUseLine: true,
Example: "infisical reset",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
toggleDebug(cmd, args)
},
Run: func(cmd *cobra.Command, args []string) {
// delete config
_, pathToDir, err := util.GetFullConfigFilePath()
if err != nil {
util.HandleError(err)
}
os.RemoveAll(pathToDir)
// delete keyring
keyringInstance, err := util.GetKeyRing()
if err != nil {
util.HandleError(err)
}
keyringInstance.Remove(util.KEYRING_SERVICE_NAME)
util.PrintSuccessMessage("Reset successful")
},
}
func init() {
rootCmd.AddCommand(resetCmd)
}
-4
View File
@@ -64,10 +64,6 @@ var runCmd = &cobra.Command{
util.HandleError(err, "Unable to parse flag") util.HandleError(err, "Unable to parse flag")
} }
// if !util.IsSecretEnvironmentValid(envName) {
// util.PrintMessageAndExit("Invalid environment name passed. Environment names can only be prod, dev, test or staging")
// }
secretOverriding, err := cmd.Flags().GetBool("secret-overriding") secretOverriding, err := cmd.Flags().GetBool("secret-overriding")
if err != nil { if err != nil {
util.HandleError(err, "Unable to parse flag") util.HandleError(err, "Unable to parse flag")
+1 -1
View File
@@ -512,7 +512,7 @@ func generateExampleEnv(cmd *cobra.Command, args []string) {
if len(listOfTagNames) == 0 { if len(listOfTagNames) == 0 {
fmt.Printf("\n%s \n", strings.Join(listOfKeyValue, "\n \n")) fmt.Printf("\n%s \n", strings.Join(listOfKeyValue, "\n \n"))
} else { } else {
fmt.Printf("\n\n\n%s\n \n%s \n", heading, strings.Join(listOfKeyValue, "\n \n")) fmt.Printf("\n\n\n%s \n%s \n", heading, strings.Join(listOfKeyValue, "\n \n"))
} }
} }
} }
+4
View File
@@ -27,6 +27,10 @@ func PrintWarning(message string) {
color.New(color.FgYellow).Fprintf(os.Stderr, "Warning: %v \n", message) color.New(color.FgYellow).Fprintf(os.Stderr, "Warning: %v \n", message)
} }
func PrintSuccessMessage(message string) {
color.New(color.FgGreen).Println(message)
}
func PrintMessageAndExit(messages ...string) { func PrintMessageAndExit(messages ...string) {
if len(messages) > 0 { if len(messages) > 0 {
for _, message := range messages { for _, message := range messages {
+33
View File
@@ -130,6 +130,12 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
return nil, err return nil, err
} }
// Verify environment
err = ValidateEnvironmentName(params.Environment, workspaceFile.WorkspaceId, loggedInUserDetails.UserCredentials)
if err != nil {
return nil, fmt.Errorf("unable to validate environment name because [err=%s]", err)
}
secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment) secretsToReturn, errorToReturn = GetPlainTextSecretsViaJTW(loggedInUserDetails.UserCredentials.JTWToken, loggedInUserDetails.UserCredentials.PrivateKey, workspaceFile.WorkspaceId, params.Environment)
log.Debugf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn) log.Debugf("GetAllEnvironmentVariables: Trying to fetch secrets JTW token [err=%s]", errorToReturn)
@@ -156,6 +162,33 @@ func GetAllEnvironmentVariables(params models.GetAllSecretsParameters) ([]models
return secretsToReturn, errorToReturn return secretsToReturn, errorToReturn
} }
func ValidateEnvironmentName(environmentName string, workspaceId string, userLoggedInDetails models.UserCredentials) error {
httpClient := resty.New()
httpClient.SetAuthToken(userLoggedInDetails.JTWToken).
SetHeader("Accept", "application/json")
response, err := api.CallGetAccessibleEnvironments(httpClient, api.GetAccessibleEnvironmentsRequest{WorkspaceId: workspaceId})
if err != nil {
return err
}
listOfEnvSlugs := []string{}
mapOfEnvSlugs := make(map[string]interface{})
for _, environment := range response.AccessibleEnvironments {
listOfEnvSlugs = append(listOfEnvSlugs, environment.Slug)
mapOfEnvSlugs[environment.Slug] = environment
}
_, exists := mapOfEnvSlugs[environmentName]
if !exists {
HandleError(fmt.Errorf("the environment [%s] does not exist in project with [id=%s]. Only [%s] are available", environmentName, workspaceId, strings.Join(listOfEnvSlugs, ",")))
}
return nil
}
func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string { func getExpandedEnvVariable(secrets []models.SingleEnvironmentVariable, variableWeAreLookingFor string, hashMapOfCompleteVariables map[string]string, hashMapOfSelfRefs map[string]string) string {
if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found { if value, found := hashMapOfCompleteVariables[variableWeAreLookingFor]; found {
return value return value
+11
View File
@@ -0,0 +1,11 @@
---
title: "infisical reset"
description: "Reset Infisical"
---
```bash
infisical reset
```
## Description
This command provides a way to clear all Infisical-generated configuration data, effectively resetting the software to its default settings. This can be an effective way to address any persistent issues that arise while using the CLI.
+22 -1
View File
@@ -87,4 +87,25 @@ $ infisical secrets set STRIPE_API_KEY=sjdgwkeudyjwe DOMAIN=example.com HASH=jeb
Default value: `dev` Default value: `dev`
</Accordion> </Accordion>
</Accordion> </Accordion>
<Accordion title="infisical secrets generate-example-env">
This command allows you to generate an example .env file from your secrets and with their associated comments and tags. This is useful when you would like to let
others who work on the project but do not use Infisical become aware of the required environment variables and their intended values.
To place default values in your example .env file, you can simply include the syntax `DEFAULT:<value>` within your secret's comment in Infisical. This will result in the specified value being extracted and utilized as the default.
```bash
$ infisical secrets generate-example-env
## Example
$ infisical secrets generate-example-env > .example-env
```
### Flags
<Accordion title="--env">
Used to select the environment name on which actions should be taken on
Default value: `dev`
</Accordion>
</Accordion>
Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 323 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 KiB

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 399 KiB

After

Width:  |  Height:  |  Size: 373 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 402 KiB

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 404 KiB

After

Width:  |  Height:  |  Size: 380 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 351 KiB

After

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 398 KiB

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 KiB

After

Width:  |  Height:  |  Size: 421 KiB

@@ -0,0 +1,75 @@
---
title: "AWS Parameter Store"
description: "How to automatically sync secrets from Infisical to your AWS Parameter Store."
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Set up AWS and have/create an IAM user
## Grant the IAM user permissions to access AWS Parameter Store
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Parameter Store.
![integration IAM 1](../../images/integrations-aws-iam-1.png)
![integration IAM 2](../../images/integrations-aws-parameter-store-iam-2.png)
![integrations IAM 3](../../images/integrations-aws-parameter-store-iam-3.png)
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Parameter Store for the IAM user that you can use:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSSMAccess",
"Effect": "Allow",
"Action": [
"ssm:PutParameter",
"ssm:DeleteParameter",
"ssm:GetParametersByPath",
"ssm:DeleteParameters"
],
"Resource": "*"
}
]
}
```
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for AWS Parameter store
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
![access key 1](../../images/integrations-aws-access-key-1.png)
![access key 2](../../images/integrations-aws-access-key-2.png)
![access key 3](../../images/integrations-aws-access-key-3.png)
Press on the AWS Parameter Store tile and input your AWS access key ID and secret access key from the previous step.
![integration auth](../../images/integrations-aws-parameter-store-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which AWS Parameter Store region and indicate the path for your secrets. Then, press create integration to start syncing secrets to AWS Parameter Store.
![integration create](../../images/integrations-aws-parameter-store-create.png)
<Tip>
Infisical requires you to add a path for your secrets to be stored in AWS
Parameter Store and recommends setting the path structure to
`/[project_name]/[environment]/` according to best practices. This enables a
secret like `TEST` to be stored as `/[project_name]/[environment]/TEST` in AWS
Parameter Store.
</Tip>
@@ -0,0 +1,73 @@
---
title: "AWS Secret Manager"
description: "How to automatically sync secrets from Infisical to your AWS Secret Manager."
---
Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com)
- Set up AWS and have/create an IAM user
## Grant the IAM user permissions to access AWS Secret Manager
Navigate to your IAM user permissions and add a permission policy to grant access to AWS Secret Manager.
![integration IAM 1](../../images/integrations-aws-iam-1.png)
![integration IAM 2](../../images/integrations-aws-secret-manager-iam-2.png)
![integrations IAM 3](../../images/integrations-aws-secret-manager-iam-3.png)
For better security, here's a custom policy containing the minimum permissions required by Infisical to sync secrets to AWS Secret Manager for the IAM user that you can use:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowSecretsManagerAccess",
"Effect": "Allow",
"Action": [
"secretsmanager:GetSecretValue",
"secretsmanager:CreateSecret",
"secretsmanager:UpdateSecret"
],
"Resource": "*"
}
]
}
```
## Navigate to your project's integrations tab
![integrations](../../images/integrations.png)
## Authorize Infisical for AWS Secret Manager
Obtain a AWS access key ID and secret access key for your IAM user in IAM > Users > User > Security credentials > Access keys
![access key 1](../../images/integrations-aws-access-key-1.png)
![access key 2](../../images/integrations-aws-access-key-2.png)
![access key 3](../../images/integrations-aws-access-key-3.png)
Press on the AWS Secret Manager tile and input your AWS access key ID and secret access key from the previous step.
![integration auth](../../images/integrations-aws-secret-manager-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration
Select which Infisical environment secrets you want to sync to which AWS Secret Manager region and under which secret name. Then, press create integration to start syncing secrets to AWS Secret Manager.
![integration create](../../images/integrations-aws-secret-manager-create.png)
<Info>
Infisical currently syncs environment variables to AWS Secret Manager as
key-value pairs under one secret. We're actively exploring ways to help users
group environment variable key-pairs under multiple secrets for greater
control.
</Info>
+2 -1
View File
@@ -31,6 +31,7 @@ Press on the Fly.io tile and input your Fly.io access token to grant Infisical a
## Start integration ## Start integration
Select which Infisical environment secrets you want to sync to which Fly.io app and press start integration to start syncing secrets to Fly.io. Select which Infisical environment secrets you want to sync to which Fly.io app and press create integration to start syncing secrets to Fly.io.
![integrations fly](../../images/integrations-flyio-create.png)
![integrations fly](../../images/integrations-flyio.png) ![integrations fly](../../images/integrations-flyio.png)
+6 -4
View File
@@ -18,13 +18,15 @@ Press on the Heroku tile and grant Infisical access to your Heroku account.
![integrations heroku authorization](../../images/integrations-heroku-auth.png) ![integrations heroku authorization](../../images/integrations-heroku-auth.png)
<Info> <Info>
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables. If this is your project's first cloud integration, then you'll have to grant
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform. Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info> </Info>
## Start integration ## Start integration
Select which Infisical environment secrets you want to sync to which Heroku app and press start integration to start syncing secrets to Heroku. Select which Infisical environment secrets you want to sync to which Heroku app and press create integration to start syncing secrets to Heroku.
![integrations heroku](../../images/integrations-heroku-create.png)
![integrations heroku](../../images/integrations-heroku.png) ![integrations heroku](../../images/integrations-heroku.png)
+10 -5
View File
@@ -4,7 +4,9 @@ description: "How to automatically sync secrets from Infisical into your Netlify
--- ---
<Warning> <Warning>
Infisical integrates with Netlify's new environment variable experience. If your site uses Netlify's old environment variable experience, you'll have to upgrade it to the new one to use this integration. Infisical integrates with Netlify's new environment variable experience. If
your site uses Netlify's old environment variable experience, you'll have to
upgrade it to the new one to use this integration.
</Warning> </Warning>
Prerequisites: Prerequisites:
@@ -22,12 +24,15 @@ Press on the Netlify tile and grant Infisical access to your Netlify account.
![integrations netlify authorization](../../images/integrations-netlify-auth.png) ![integrations netlify authorization](../../images/integrations-netlify-auth.png)
<Info> <Info>
If this is your project's first cloud integration, then you'll have to grant Infisical access to your project's environment variables. If this is your project's first cloud integration, then you'll have to grant
Although this step breaks E2EE, it's necessary for Infisical to sync the environment variables to the cloud platform. Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info> </Info>
## Start integration ## Start integration
Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press start integration to start syncing secrets to Netlify. Select which Infisical environment secrets you want to sync to which Netlify app and context. Lastly, press create integration to start syncing secrets to Netlify.
![integrations netlify](../../images/integrations-netlify.png) ![integrations netlify](../../images/integrations-netlify-create.png)
![integrations netlify](../../images/integrations-netlify.png)
+3 -2
View File
@@ -31,6 +31,7 @@ Press on the Render tile and input your Render API Key to grant Infisical access
## Start integration ## Start integration
Select which Infisical environment secrets you want to sync to which Render service and press start integration to start syncing secrets to Render. Select which Infisical environment secrets you want to sync to which Render service and press create integration to start syncing secrets to Render.
![integrations heroku](../../images/integrations-render.png) ![integrations render](../../images/integrations-render-create.png)
![integrations render](../../images/integrations-render.png)
+9 -1
View File
@@ -17,8 +17,16 @@ Press on the Vercel tile and grant Infisical access to your Vercel account.
![integrations vercel authorization](../../images/integrations-vercel-auth.png) ![integrations vercel authorization](../../images/integrations-vercel-auth.png)
<Info>
If this is your project's first cloud integration, then you'll have to grant
Infisical access to your project's environment variables. Although this step
breaks E2EE, it's necessary for Infisical to sync the environment variables to
the cloud platform.
</Info>
## Start integration ## Start integration
Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press start integration to start syncing secrets to Vercel. Select which Infisical environment secrets you want to sync to which Vercel app and environment. Lastly, press create integration to start syncing secrets to Vercel.
![integrations vercel](../../images/integrations-vercel-create.png)
![integrations vercel](../../images/integrations-vercel.png) ![integrations vercel](../../images/integrations-vercel.png)
+37 -35
View File
@@ -7,38 +7,40 @@ Integrations allow environment variables to be synced from Infisical into your l
Missing an integration? Throw in a [request](https://github.com/Infisical/infisical/issues). Missing an integration? Throw in a [request](https://github.com/Infisical/infisical/issues).
| Integration | Type | Status | | Integration | Type | Status |
| -------------------------------------------------------- | --------- | ----------- | | -------------------------------------------------------------- | --------- | ----------- |
| [Docker](/integrations/platforms/docker) | Platform | Available | | [Docker](/integrations/platforms/docker) | Platform | Available |
| [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available | | [Docker-Compose](/integrations/platforms/docker-compose) | Platform | Available |
| [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available | | [Kubernetes](/integrations/platforms/kubernetes) | Platform | Available |
| [PM2](/integrations/platforms/pm2) | Platform | Available | | [PM2](/integrations/platforms/pm2) | Platform | Available |
| [Heroku](/integrations/cloud/heroku) | Cloud | Available | | [Heroku](/integrations/cloud/heroku) | Cloud | Available |
| [Vercel](/integrations/cloud/vercel) | Cloud | Available | | [Vercel](/integrations/cloud/vercel) | Cloud | Available |
| [Netlify](/integrations/cloud/netlify) | Cloud | Available | | [Netlify](/integrations/cloud/netlify) | Cloud | Available |
| [Render](/integrations/cloud/render) | Cloud | Available | | [Render](/integrations/cloud/render) | Cloud | Available |
| [Fly.io](/integrations/cloud/flyio) | Cloud | Available | | [Fly.io](/integrations/cloud/flyio) | Cloud | Available |
| [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available | | [AWS Parameter Store](/integrations/cloud/aws-parameter-store) | Cloud | Available |
| [React](/integrations/frameworks/react) | Framework | Available | | [AWS Secret Manager](/integrations/cloud/aws-secret-manager) | Cloud | Available |
| [Vue](/integrations/frameworks/vue) | Framework | Available | | [GitHub Actions](/integrations/cicd/githubactions) | CI/CD | Available |
| [Express](/integrations/frameworks/express) | Framework | Available | | [React](/integrations/frameworks/react) | Framework | Available |
| [Next.js](/integrations/frameworks/nextjs) | Framework | Available | | [Vue](/integrations/frameworks/vue) | Framework | Available |
| [NestJS](/integrations/frameworks/nestjs) | Framework | Available | | [Express](/integrations/frameworks/express) | Framework | Available |
| [Nuxt](/integrations/frameworks/nuxt) | Framework | Available | | [Next.js](/integrations/frameworks/nextjs) | Framework | Available |
| [Gatsby](/integrations/frameworks/gatsby) | Framework | Available | | [NestJS](/integrations/frameworks/nestjs) | Framework | Available |
| [Remix](/integrations/frameworks/remix) | Framework | Available | | [Nuxt](/integrations/frameworks/nuxt) | Framework | Available |
| [Vite](/integrations/frameworks/vite) | Framework | Available | | [Gatsby](/integrations/frameworks/gatsby) | Framework | Available |
| [Fiber](/integrations/frameworks/fiber) | Framework | Available | | [Remix](/integrations/frameworks/remix) | Framework | Available |
| [Django](/integrations/frameworks/django) | Framework | Available | | [Vite](/integrations/frameworks/vite) | Framework | Available |
| [Flask](/integrations/frameworks/flask) | Framework | Available | | [Fiber](/integrations/frameworks/fiber) | Framework | Available |
| [Laravel](/integrations/frameworks/laravel) | Framework | Available | | [Django](/integrations/frameworks/django) | Framework | Available |
| [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available | | [Flask](/integrations/frameworks/flask) | Framework | Available |
| AWS | Cloud | Coming soon | | [Laravel](/integrations/frameworks/laravel) | Framework | Available |
| GCP | Cloud | Coming soon | | [Ruby on Rails](/integrations/frameworks/rails) | Framework | Available |
| Azure | Cloud | Coming soon | | AWS | Cloud | Coming soon |
| DigitalOcean | Cloud | Coming soon | | GCP | Cloud | Coming soon |
| [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available | | Azure | Cloud | Coming soon |
| [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon | | DigitalOcean | Cloud | Coming soon |
| TravisCI | CI/CD | Coming soon | | [GitLab Pipeline](/integrations/cicd/gitlab) | CI/CD | Available |
| GitHub Actions | CI/CD | Coming soon | | [CircleCI](/integrations/cicd/circleci) | CI/CD | Coming soon |
| Jenkins | CI/CD | Coming soon | | TravisCI | CI/CD | Coming soon |
| GitHub Actions | CI/CD | Coming soon |
| Jenkins | CI/CD | Coming soon |
+5 -2
View File
@@ -114,7 +114,8 @@
"cli/commands/run", "cli/commands/run",
"cli/commands/secrets", "cli/commands/secrets",
"cli/commands/export", "cli/commands/export",
"cli/commands/vault" "cli/commands/vault",
"cli/commands/reset"
] ]
}, },
"cli/faq" "cli/faq"
@@ -220,7 +221,9 @@
"integrations/cloud/vercel", "integrations/cloud/vercel",
"integrations/cloud/netlify", "integrations/cloud/netlify",
"integrations/cloud/render", "integrations/cloud/render",
"integrations/cloud/flyio" "integrations/cloud/flyio",
"integrations/cloud/aws-parameter-store",
"integrations/cloud/aws-secret-manager"
] ]
}, },
{ {
@@ -4,6 +4,8 @@ interface Mapping {
const integrationSlugNameMapping: Mapping = { const integrationSlugNameMapping: Mapping = {
'azure-key-vault': 'Azure Key Vault', 'azure-key-vault': 'Azure Key Vault',
'aws-parameter-store': 'AWS Parameter Store',
'aws-secret-manager': 'AWS Secret Manager',
'heroku': 'Heroku', 'heroku': 'Heroku',
'vercel': 'Vercel', 'vercel': 'Vercel',
'netlify': 'Netlify', 'netlify': 'Netlify',
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"support": { "support": {
"slack": "[NEW] Join Slack Forum", "slack": "Join Slack Forum",
"docs": "Read Docs", "docs": "Read Docs",
"issue": "Open a Github Issue", "issue": "Open a Github Issue",
"email": "Send us an Email" "email": "Send us an Email"
+4 -4
View File
@@ -246,9 +246,9 @@ const Layout = ({ children }: LayoutProps) => {
return ( return (
<> <>
<div className="flex h-screen w-full flex-col overflow-x-hidden"> <div className="h-screen w-full flex-col overflow-x-hidden hidden md:flex">
<NavBarDashboard /> <NavBarDashboard />
<div className="flex flex-grow flex-col overflow-y-hidden md:flex-row"> <div className="flex flex-grow flex-col overflow-y-hidden md:flex-row dark">
<aside className="w-full border-r border-mineshaft-500 bg-bunker-600 md:w-60"> <aside className="w-full border-r border-mineshaft-500 bg-bunker-600 md:w-60">
<nav className="items-between flex h-full flex-col justify-between"> <nav className="items-between flex h-full flex-col justify-between">
{/* <div className="py-6"></div> */} {/* <div className="py-6"></div> */}
@@ -368,10 +368,10 @@ const Layout = ({ children }: LayoutProps) => {
error={error} error={error}
loading={loading} loading={loading}
/> />
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800">{children}</main> <main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">{children}</main>
</div> </div>
</div> </div>
<div className="flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 md:hidden"> <div className="flex h-screen w-screen flex-col items-center justify-center bg-bunker-800 z-[200] md:hidden">
<FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" /> <FontAwesomeIcon icon={faMobile} className="mb-8 text-7xl text-gray-300" />
<p className="max-w-sm px-6 text-center text-lg text-gray-200"> <p className="max-w-sm px-6 text-center text-lg text-gray-200">
{` ${t('common:no-mobile')} `} {` ${t('common:no-mobile')} `}
@@ -34,7 +34,7 @@ const BottonRightPopup = ({
}: PopupProps): JSX.Element => { }: PopupProps): JSX.Element => {
return ( return (
<div <div
className="z-50 drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6" className="z-[100] drop-shadow-xl border-gray-600/50 border flex flex-col items-start bg-bunker max-w-xl text-gray-200 pt-3 pb-4 rounded-md absolute bottom-0 right-0 mr-6 mb-6"
role="alert" role="alert"
> >
<div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6"> <div className="flex flex-row items-center justify-between w-full border-b border-gray-600/70 pb-3 px-6">
@@ -18,7 +18,7 @@ const CommentField = ({
<div className="relative mt-4 px-4 pt-6"> <div className="relative mt-4 px-4 pt-6">
<p className="text-sm text-bunker-300 pl-0.5">{t('dashboard:sidebar.comments')}</p> <p className="text-sm text-bunker-300 pl-0.5">{t('dashboard:sidebar.comments')}</p>
<textarea <textarea
className="placeholder:text-bunker-400 h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70" className="placeholder:text-bunker-400 dark:[color-scheme:dark] h-32 w-full bg-bunker-800 px-2 py-1.5 rounded-md border border-mineshaft-500 text-sm text-bunker-300 outline-none focus:ring-2 ring-primary-800 ring-opacity-70"
value={comment} value={comment}
onChange={(e) => modifyComment(e.target.value, position)} onChange={(e) => modifyComment(e.target.value, position)}
placeholder="Leave any comments here..." placeholder="Leave any comments here..."
@@ -125,7 +125,7 @@ const DashboardInputField = ({
const error = startsWithNumber || isDuplicate; const error = startsWithNumber || isDuplicate;
return ( return (
<div className={`relative flex-col w-full h-10 ${ <div title={value} className={`relative flex-col w-full h-10 ${
isSideBarOpen && 'bg-mineshaft-700 duration-200' isSideBarOpen && 'bg-mineshaft-700 duration-200'
}`}> }`}>
<div <div
@@ -137,7 +137,7 @@ const DashboardInputField = ({
onChange={(e) => onChangeHandler(e.target.value, position)} onChange={(e) => onChangeHandler(e.target.value, position)}
type={type} type={type}
value={value} value={value}
className='z-10 peer font-mono ph-no-capture bg-transparent py-2.5 caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200' className='z-10 peer ph-no-capture bg-transparent py-2.5 caret-bunker-200 text-sm px-2 w-full min-w-16 outline-none text-bunker-300 focus:text-bunker-100 placeholder:text-bunker-400 placeholder:focus:text-transparent placeholder duration-200'
spellCheck="false" spellCheck="false"
placeholder='–' placeholder='–'
/> />
@@ -209,7 +209,7 @@ const DashboardInputField = ({
{value?.split('').map(() => ( {value?.split('').map(() => (
<FontAwesomeIcon <FontAwesomeIcon
key={guidGenerator()} key={guidGenerator()}
className="text-xxs mx-0.5" className="text-xxs mr-0.5"
icon={faCircle} icon={faCircle}
/> />
))} ))}
+10 -4
View File
@@ -34,7 +34,10 @@ const colors = [
'bg-[#cb1c8d]/40', 'bg-[#cb1c8d]/40',
'bg-[#badc58]/40', 'bg-[#badc58]/40',
'bg-[#ff5400]/40', 'bg-[#ff5400]/40',
'bg-[#00bbf9]/40' 'bg-[#3AB0FF]/40',
'bg-[#6F1AB6]/40',
'bg-[#C40B13]/40',
'bg-[#332FD0]/40'
] ]
@@ -43,7 +46,10 @@ const colorsText = [
'text-[#f2c6e3]/70', 'text-[#f2c6e3]/70',
'text-[#eef6d5]/70', 'text-[#eef6d5]/70',
'text-[#ffddcc]/70', 'text-[#ffddcc]/70',
'text-[#f0fffd]/70' 'text-[#f0fffd]/70',
'text-[#FFE5F1]/70',
'text-[#FFDEDE]/70',
'text-[#DFF6FF]/70'
] ]
/** /**
@@ -95,9 +101,9 @@ const KeyPair = ({
}`} }`}
> >
<div className="relative flex flex-row justify-between w-full mr-auto max-h-14 items-center"> <div className="relative flex flex-row justify-between w-full mr-auto max-h-14 items-center">
<div className="w-2/12 border-r border-mineshaft-600 flex flex-row items-center"> <div className="w-1/5 border-r border-mineshaft-600 flex flex-row items-center">
<div className='text-bunker-400 text-xs flex items-center justify-center w-14 h-10 cursor-default'>{keyPair.pos + 1}</div> <div className='text-bunker-400 text-xs flex items-center justify-center w-14 h-10 cursor-default'>{keyPair.pos + 1}</div>
<div className="flex items-center max-h-16"> <div className="flex items-center max-h-16 w-full">
<DashboardInputField <DashboardInputField
isCapitalized = {isCapitalized} isCapitalized = {isCapitalized}
onChangeHandler={modifyKey} onChangeHandler={modifyKey}
@@ -18,6 +18,8 @@ interface Integration {
isActive: boolean; isActive: boolean;
app: string | null; app: string | null;
appId: string | null; appId: string | null;
path: string | null;
region: string | null;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
environment: string; environment: string;
@@ -75,6 +77,8 @@ const IntegrationTile = ({
if (integration?.app) { if (integration?.app) {
setIntegrationApp(integration.app); setIntegrationApp(integration.app);
} else if (integration?.path && integration?.region) {
setIntegrationApp(`${integration.path} (${integration.region})`);
} else if (tempApps.length > 0) { } else if (tempApps.length > 0) {
setIntegrationApp(tempApps[0].name) setIntegrationApp(tempApps[0].name)
} else { } else {
@@ -14,6 +14,8 @@ interface Integration {
isActive: boolean; isActive: boolean;
app: string | null; app: string | null;
appId: string | null; appId: string | null;
path: string | null;
region: string | null;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
environment: string; environment: string;
@@ -93,7 +93,7 @@ export default function Navbar() {
}; };
return ( return (
<div className="flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-50"> <div className="flex flex-row justify-between w-full bg-bunker text-white border-b border-mineshaft-500 z-[61]">
<div className="m-auto flex justify-start items-center mx-4"> <div className="m-auto flex justify-start items-center mx-4">
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<div className="flex justify-center py-4"> <div className="flex justify-center py-4">
@@ -166,8 +166,8 @@ export default function Navbar() {
leaveFrom="transform opacity-100 scale-100" leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95" leaveTo="transform opacity-0 scale-95"
> >
<Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-20 ring-opacity-5 focus:outline-none"> <Menu.Items className="absolute right-0 mt-0.5 w-64 origin-top-right divide-y divide-gray-700 rounded-md bg-bunker border border-mineshaft-700 shadow-lg ring-1 ring-black z-[65] ring-opacity-5 focus:outline-none">
<div className="px-1 py-1 "> <div className="px-1 py-1 z-[100]">
<div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide"> <div className="text-gray-400 self-start ml-2 mt-2 text-xs font-semibold tracking-wide">
{t('nav:user.signed-in-as')} {t('nav:user.signed-in-as')}
</div> </div>
@@ -1,10 +1,7 @@
import { useEffect, useState } from 'react';
import { useRouter } from 'next/router';
import { faAngleRight } from '@fortawesome/free-solid-svg-icons'; import { faAngleRight } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { useWorkspace } from '@app/context'; import { useOrganization, useWorkspace } from '@app/context';
import getOrganization from '@app/pages/api/organization/GetOrg';
/** /**
* This is the component at the top of almost every page. * This is the component at the top of almost every page.
@@ -22,28 +19,15 @@ export default function NavHeader({
pageName: string; pageName: string;
isProjectRelated?: boolean; isProjectRelated?: boolean;
}): JSX.Element { }): JSX.Element {
const [orgName, setOrgName] = useState('');
const router = useRouter();
const projectId = String(router.query.id);
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { currentOrg } = useOrganization();
useEffect(() => {
(async () => {
const orgId = localStorage.getItem('orgData.id');
const org = await getOrganization({
orgId: orgId || ''
});
setOrgName(org.name);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [projectId]);
return ( return (
<div className="ml-6 flex flex-row items-center pt-8"> <div className="ml-6 flex flex-row items-center pt-8">
<div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100"> <div className="mr-2 flex h-6 w-6 items-center justify-center rounded-md bg-primary-900 text-mineshaft-100">
{orgName?.charAt(0)} {currentOrg?.name?.charAt(0)}
</div> </div>
<div className="text-sm font-semibold text-primary">{orgName}</div> <div className="text-sm font-semibold text-primary">{currentOrg?.name}</div>
{isProjectRelated && ( {isProjectRelated && (
<> <>
<FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" /> <FontAwesomeIcon icon={faAngleRight} className="ml-3 mr-3 text-sm text-gray-400" />
@@ -4,7 +4,7 @@ const POSTHOG_HOST =
process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com"; process.env.NEXT_PUBLIC_POSTHOG_HOST! || "https://app.posthog.com";
const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!; const STRIPE_PRODUCT_PRO = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_PRO!;
const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!; const STRIPE_PRODUCT_STARTER = process.env.NEXT_PUBLIC_STRIPE_PRODUCT_STARTER!;
const {SITE_URL} = process.env const SITE_URL = "https://app.infisical.com";
export { export {
ENV, ENV,
@@ -12,4 +12,5 @@ export {
POSTHOG_HOST, POSTHOG_HOST,
SITE_URL, SITE_URL,
STRIPE_PRODUCT_PRO, STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER}; STRIPE_PRODUCT_STARTER
};
+1 -1
View File
@@ -24,7 +24,7 @@ const buttonVariants = cva(
{ {
variants: { variants: {
colorSchema: { colorSchema: {
primary: ['bg-primary', 'text-black', 'border-primary hover:bg-opacity-80'], primary: ['bg-primary', 'text-black', 'border-primary bg-opacity-80 hover:bg-opacity-100'],
secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'], secondary: ['bg-mineshaft', 'text-gray-300', 'border-mineshaft hover:bg-opacity-80'],
danger: ['bg-red', 'text-white', 'border-red hover:bg-opacity-90'] danger: ['bg-red', 'text-white', 'border-red hover:bg-opacity-90']
}, },
+2 -2
View File
@@ -8,9 +8,9 @@ export type CardTitleProps = {
}; };
export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => ( export const CardTitle = ({ children, className, subTitle }: CardTitleProps) => (
<div className={twMerge('px-6 py-4 font-sans text-xl font-medium', className)}> <div className={twMerge('px-6 py-4 mb-5 font-sans text-lg font-normal border-b border-mineshaft-600', className)}>
{children} {children}
{subTitle && <p className="py-1 text-sm font-normal text-gray-400">{subTitle}</p>} {subTitle && <p className="pt-0.5 text-sm font-normal text-gray-400">{subTitle}</p>}
</div> </div>
); );
@@ -28,8 +28,9 @@ export const Checkbox = ({
<div className="flex items-center font-inter text-bunker-300"> <div className="flex items-center font-inter text-bunker-300">
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
className={twMerge( className={twMerge(
'flex items-center justify-center w-5 h-5 mr-3 transition-all rounded shadow hover:bg-bunker-200 bg-bunker-300', 'flex items-center justify-center w-4 h-4 mr-3 transition-all rounded shadow border border-mineshaft-400 hover:bg-mineshaft-500 bg-mineshaft-600',
isDisabled && 'bg-bunker-400 hover:bg-bunker-400', isDisabled && 'bg-bunker-400 hover:bg-bunker-400',
isChecked && 'bg-primary hover:bg-primary',
className className
)} )}
required={isRequired} required={isRequired}
@@ -24,7 +24,7 @@ export const HoverObject = ({
</a> </a>
</HoverCard.Trigger> </HoverCard.Trigger>
<HoverCard.Portal> <HoverCard.Portal>
<HoverCard.Content className="HoverCardContent z-[50]" sideOffset={5}> <HoverCard.Content className="HoverCardContent z-[300]" sideOffset={5}>
<div className='bg-bunker-700 border border-mineshaft-600 p-2 rounded-md drop-shadow-xl text-bunker-300'> <div className='bg-bunker-700 border border-mineshaft-600 p-2 rounded-md drop-shadow-xl text-bunker-300'>
<div style={{ display: 'flex', flexDirection: 'column', gap: 15 }}> <div style={{ display: 'flex', flexDirection: 'column', gap: 15 }}>
<div> <div>
+8 -6
View File
@@ -31,18 +31,20 @@ export const MenuItem = <T extends ElementType = 'button'>({
as: Item = 'button', as: Item = 'button',
description, description,
// wrapping in forward ref with generic component causes the loss of ts definitions on props // wrapping in forward ref with generic component causes the loss of ts definitions on props
inputRef inputRef,
...props
}: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => ( }: MenuItemProps<T> & ComponentPropsWithRef<T>): JSX.Element => (
<li <li
className={twMerge( className={twMerge(
'px-2 py-3 font-inter flex flex-col text-sm text-white transition-all rounded cursor-pointer hover:bg-gray-700', 'px-1 py-2.5 mt-0.5 font-inter flex flex-col text-sm text-bunker-100 transition-all rounded cursor-pointer hover:bg-mineshaft-600 duration-50',
isSelected && 'text-primary', isSelected && 'bg-mineshaft-500',
isDisabled && 'text-gray-500 hover:bg-transparent cursor-not-allowed', isDisabled && 'hover:bg-transparent cursor-not-allowed',
className className
)} )}
> >
<Item type="button" role="menuitem" class="flex items-center" ref={inputRef}> <Item type="button" role="menuitem" className="flex items-center relative" ref={inputRef} {...props}>
{icon && <span className="mr-2">{icon}</span>} <div className={`${isSelected ? "visisble" : "invisible"} absolute w-[0.25rem] rounded-md h-8 bg-primary`}/>
{icon && <span className="mr-3 ml-4 w-5">{icon}</span>}
<span className="flex-grow text-left">{children}</span> <span className="flex-grow text-left">{children}</span>
</Item> </Item>
{description && <span className="mt-2 text-xs">{description}</span>} {description && <span className="mt-2 text-xs">{description}</span>}
+2 -2
View File
@@ -25,7 +25,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
<Card <Card
isRounded isRounded
className={twMerge( className={twMerge(
'fixed top-1/2 left-1/2 max-w-lg -translate-y-2/4 -translate-x-2/4 animate-popIn drop-shadow-2xl z-[90]', 'fixed top-1/2 left-1/2 max-w-lg border border-mineshaft-600 -translate-y-2/4 -translate-x-2/4 animate-popIn drop-shadow-2xl z-[90]',
className className
)} )}
> >
@@ -36,7 +36,7 @@ export const ModalContent = forwardRef<HTMLDivElement, ModalContentProps>(
<IconButton <IconButton
variant="plain" variant="plain"
ariaLabel="close" ariaLabel="close"
className="absolute top-3 right-3 rounded text-white hover:bg-gray-600" className="absolute top-4 right-6 rounded text-bunker-400 hover:text-bunker-50"
> >
<FontAwesomeIcon icon={faTimes} size="lg" className="cursor-pointer" /> <FontAwesomeIcon icon={faTimes} size="lg" className="cursor-pointer" />
</IconButton> </IconButton>
+8 -5
View File
@@ -12,13 +12,14 @@ type Props = {
className?: string; className?: string;
dropdownContainerClassName?: string; dropdownContainerClassName?: string;
isLoading?: boolean; isLoading?: boolean;
position?: 'item-aligned' | 'popper';
}; };
export type SelectProps = SelectPrimitive.SelectProps & Props; export type SelectProps = SelectPrimitive.SelectProps & Props;
export const Select = forwardRef<HTMLButtonElement, SelectProps>( export const Select = forwardRef<HTMLButtonElement, SelectProps>(
( (
{ children, placeholder, className, isLoading, dropdownContainerClassName, ...props }, { children, placeholder, className, isLoading, dropdownContainerClassName, position, ...props },
ref ref
): JSX.Element => { ): JSX.Element => {
return ( return (
@@ -44,11 +45,13 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(
'relative left-4 top-1 overflow-hidden rounded-md bg-bunker-800 font-inter text-bunker-100 shadow-md z-[100]', 'relative left-4 top-1 overflow-hidden rounded-md bg-bunker-800 font-inter text-bunker-100 shadow-md z-[100]',
dropdownContainerClassName dropdownContainerClassName
)} )}
position={position}
style={{ width: 'var(--radix-select-trigger-width)' }}
> >
<SelectPrimitive.ScrollUpButton> <SelectPrimitive.ScrollUpButton>
<FontAwesomeIcon icon={faChevronUp} size="sm" /> <FontAwesomeIcon icon={faChevronUp} size="sm" />
</SelectPrimitive.ScrollUpButton> </SelectPrimitive.ScrollUpButton>
<SelectPrimitive.Viewport className="p-1.5"> <SelectPrimitive.Viewport className="p-1">
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center"> <div className="flex items-center justify-center">
<Spinner size="xs" /> <Spinner size="xs" />
@@ -82,7 +85,7 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
{...props} {...props}
className={twMerge( className={twMerge(
`relative flex cursor-pointer `relative flex cursor-pointer
select-none items-center rounded-md py-2 pl-10 pr-4 text-sm select-none items-center rounded-md py-2 pl-10 pr-4 mb-0.5 text-sm
outline-none transition-all hover:bg-mineshaft-500`, outline-none transition-all hover:bg-mineshaft-500`,
isSelected && 'bg-primary', isSelected && 'bg-primary',
isDisabled && 'cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-gray-600', isDisabled && 'cursor-not-allowed text-gray-600 hover:bg-transparent hover:text-gray-600',
@@ -90,8 +93,8 @@ export const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(
)} )}
ref={forwardedRef} ref={forwardedRef}
> >
<SelectPrimitive.ItemIndicator className="absolute left-3.5"> <SelectPrimitive.ItemIndicator className="absolute left-3.5 text-primary">
<FontAwesomeIcon icon={faCheck} size="sm" /> <FontAwesomeIcon icon={faCheck} />
</SelectPrimitive.ItemIndicator> </SelectPrimitive.ItemIndicator>
<SelectPrimitive.ItemText className="">{children}</SelectPrimitive.ItemText> <SelectPrimitive.ItemText className="">{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item> </SelectPrimitive.Item>
@@ -0,0 +1,46 @@
import { createContext, ReactNode, useContext, useMemo } from 'react';
import { useGetOrganization } from '@app/hooks/api';
import { Organization } from '@app/hooks/api/types';
import { useWorkspace } from '../WorkspaceContext';
type TOrgContext = {
orgs?: Organization[];
currentOrg?: Organization;
isLoading: boolean;
};
const OrgContext = createContext<TOrgContext | null>(null);
type Props = {
children: ReactNode;
};
export const OrgProvider = ({ children }: Props): JSX.Element => {
const { currentWorkspace } = useWorkspace();
const { data: userOrgs, isLoading } = useGetOrganization();
const currentWsOrgID = currentWorkspace?.organization;
// memorize the workspace details for the context
const value = useMemo<TOrgContext>(
() => ({
orgs: userOrgs,
currentOrg: (userOrgs || []).find(({ _id }) => _id === currentWsOrgID),
isLoading
}),
[currentWsOrgID, userOrgs, isLoading]
);
return <OrgContext.Provider value={value}>{children}</OrgContext.Provider>;
};
export const useOrganization = () => {
const ctx = useContext(OrgContext);
if (!ctx) {
throw new Error('useOrganization to be used within <OrgContext.Provider>');
}
return ctx;
};
@@ -0,0 +1 @@
export { OrgProvider, useOrganization } from './OrganizationContext';
@@ -0,0 +1,38 @@
import { createContext, ReactNode, useContext, useMemo } from 'react';
import { useGetUser } from '@app/hooks/api';
import { User } from '@app/hooks/api/types';
type TUserContext = {
user: User;
isLoading: boolean;
};
const UserContext = createContext<TUserContext | null>(null);
type Props = {
children: ReactNode;
};
export const UserProvider = ({ children }: Props): JSX.Element => {
const { data, isLoading } = useGetUser();
// memorize the workspace details for the context
const value = useMemo<TUserContext>(() => {
return {
user: data!,
isLoading
};
}, [data, isLoading]);
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};
export const useUser = () => {
const ctx = useContext(UserContext);
if (!ctx) {
throw new Error('useUser has to be used within <UserContext.Provider>');
}
return ctx;
};
@@ -0,0 +1 @@
export { UserProvider, useUser } from './UserContext';
@@ -23,9 +23,10 @@ export const WorkspaceProvider = ({ children }: Props): JSX.Element => {
// memorize the workspace details for the context // memorize the workspace details for the context
const value = useMemo<TWorkspaceContext>(() => { const value = useMemo<TWorkspaceContext>(() => {
const wsId = workspaceId || localStorage.getItem('projectData.id');
return { return {
workspaces: ws || [], workspaces: ws || [],
currentWorkspace: (ws || []).find(({ _id: id }) => id === workspaceId), currentWorkspace: (ws || []).find(({ _id: id }) => id === wsId),
isLoading isLoading
}; };
}, [ws, workspaceId, isLoading]); }, [ws, workspaceId, isLoading]);
+3
View File
@@ -1,2 +1,5 @@
export { AuthProvider } from './AuthContext';
export { OrgProvider, useOrganization } from './OrganizationContext';
export { SubscriptionProvider, useSubscription } from './SubscriptionContext'; export { SubscriptionProvider, useSubscription } from './SubscriptionContext';
export { UserProvider, useUser } from './UserContext';
export { useWorkspace, WorkspaceProvider } from './WorkspaceContext'; export { useWorkspace, WorkspaceProvider } from './WorkspaceContext';
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import Image from 'next/image'; import Image from 'next/image';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next'; import { useTranslation } from 'next-i18next';
import { faX } from '@fortawesome/free-solid-svg-icons'; import { faXmark } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import getActionData from '@app/ee/api/secrets/GetActionData'; import getActionData from '@app/ee/api/secrets/GetActionData';
@@ -154,7 +154,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
tabIndex={0} tabIndex={0}
onClick={() => toggleSidebar('')} onClick={() => toggleSidebar('')}
> >
<FontAwesomeIcon icon={faX} className="w-4 h-4 text-bunker-300 cursor-pointer" /> <FontAwesomeIcon icon={faXmark} className="w-4 h-4 text-bunker-300 cursor-pointer" />
</div> </div>
</div> </div>
<div className="flex flex-col px-4"> <div className="flex flex-col px-4">
@@ -163,7 +163,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
actionMetaData?.name === 'deleteSecrets') && actionMetaData?.name === 'deleteSecrets') &&
actionData?.map((item, id) => ( actionData?.map((item, id) => (
<div key={`secret.${id + 1}`}> <div key={`secret.${id + 1}`}>
<div className="text-xs text-bunker-200 mt-4 pl-1"> <div className="text-xs text-bunker-200 mt-4 pl-1 ph-no-capture">
{item.newSecretVersion.key} {item.newSecretVersion.key}
</div> </div>
<DashboardInputField <DashboardInputField
@@ -183,7 +183,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
{item.newSecretVersion.key} {item.newSecretVersion.key}
</div> </div>
<div className="text-bunker-100 font-mono rounded-md overflow-hidden"> <div className="text-bunker-100 font-mono rounded-md overflow-hidden">
<div className="bg-red/30 px-2"> <div className="bg-red/30 px-2 ph-no-capture">
-{' '} -{' '}
{patienceDiff( {patienceDiff(
item.oldSecretVersion.value.split(''), item.oldSecretVersion.value.split(''),
@@ -201,7 +201,7 @@ const ActivitySideBar = ({ toggleSidebar, currentAction }: SideBarProps) => {
) )
)} )}
</div> </div>
<div className="bg-green-500/30 px-2"> <div className="bg-green-500/30 px-2 ph-no-capture">
+{' '} +{' '}
{patienceDiff( {patienceDiff(
item.oldSecretVersion.value.split(''), item.oldSecretVersion.value.split(''),
@@ -113,7 +113,7 @@ const SecretVersionList = ({ secretId }: { secretId: string }) => {
})} })}
</div> </div>
<div className=""> <div className="">
<p className="break-words"> <p className="break-words ph-no-capture">
<span className="py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5"> <span className="py-0.5 px-1 rounded-md bg-primary-200/10 mr-1.5">
Value: Value:
</span> </span>
+2
View File
@@ -1,6 +1,8 @@
export * from './auth'; export * from './auth';
export * from './keys'; export * from './keys';
export * from './organization';
export * from './serviceTokens'; export * from './serviceTokens';
export * from './subscriptions'; export * from './subscriptions';
export * from './tags'; export * from './tags';
export * from './users';
export * from './workspace'; export * from './workspace';
+1 -1
View File
@@ -1 +1 @@
export { useGetUserWsKey } from './queries'; export { useGetUserWsKey, useUploadWsKey } from './queries';
+9 -2
View File
@@ -1,8 +1,8 @@
import { useQuery } from '@tanstack/react-query'; import { useMutation, useQuery } from '@tanstack/react-query';
import { apiRequest } from '@app/config/request'; import { apiRequest } from '@app/config/request';
import { UserWsKeyPair } from './types'; import { UploadWsKeyDTO, UserWsKeyPair } from './types';
const encKeyKeys = { const encKeyKeys = {
getUserWorkspaceKey: (workspaceID: string) => ['worksapce-key-pair', { workspaceID }] as const getUserWorkspaceKey: (workspaceID: string) => ['worksapce-key-pair', { workspaceID }] as const
@@ -22,3 +22,10 @@ export const useGetUserWsKey = (workspaceID: string) =>
queryFn: () => fetchUserWsKey(workspaceID), queryFn: () => fetchUserWsKey(workspaceID),
enabled: Boolean(workspaceID) enabled: Boolean(workspaceID)
}); });
// mutations
export const useUploadWsKey = () =>
useMutation<{}, {}, UploadWsKeyDTO>({
mutationFn: ({ encryptedKey, nonce, userId, workspaceId }) =>
apiRequest.post(`/api/v1/key/${workspaceId}`, { key: { userId, encryptedKey, nonce } })
});
+7
View File
@@ -20,3 +20,10 @@ export type Sender = {
lastName: string; lastName: string;
publicKey: string; publicKey: string;
}; };
export type UploadWsKeyDTO = {
userId: string;
encryptedKey: string;
nonce: string;
workspaceId: string;
};
@@ -0,0 +1 @@
export { useGetOrganization } from './queries';

Some files were not shown because too many files have changed in this diff Show More