Compare commits

..

1 Commits

Author SHA1 Message Date
91634fbe76 Patch LDAP 2024-06-20 17:49:09 -07:00
39 changed files with 270 additions and 403 deletions

View File

@ -50,13 +50,6 @@ jobs:
environment: environment:
name: Gamma name: Gamma
steps: steps:
- uses: twingate/github-action@v1
with:
# The Twingate Service Key used to connect Twingate to the proper service
# Learn more about [Twingate Services](https://docs.twingate.com/docs/services)
#
# Required
service-key: ${{ secrets.TWINGATE_GAMMA_SERVICE_KEY }}
- name: Checkout code - name: Checkout code
uses: actions/checkout@v2 uses: actions/checkout@v2
- name: Setup Node.js environment - name: Setup Node.js environment
@ -81,21 +74,21 @@ jobs:
uses: pr-mpt/actions-commit-hash@v2 uses: pr-mpt/actions-commit-hash@v2
- name: Download task definition - name: Download task definition
run: | run: |
aws ecs describe-task-definition --task-definition infisical-core-gamma-stage --query taskDefinition > task-definition.json aws ecs describe-task-definition --task-definition infisical-core-platform --query taskDefinition > task-definition.json
- name: Render Amazon ECS task definition - name: Render Amazon ECS task definition
id: render-web-container id: render-web-container
uses: aws-actions/amazon-ecs-render-task-definition@v1 uses: aws-actions/amazon-ecs-render-task-definition@v1
with: with:
task-definition: task-definition.json task-definition: task-definition.json
container-name: infisical-core container-name: infisical-core-platform
image: infisical/staging_infisical:${{ steps.commit.outputs.short }} image: infisical/staging_infisical:${{ steps.commit.outputs.short }}
environment-variables: "LOG_LEVEL=info" environment-variables: "LOG_LEVEL=info"
- name: Deploy to Amazon ECS service - name: Deploy to Amazon ECS service
uses: aws-actions/amazon-ecs-deploy-task-definition@v1 uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with: with:
task-definition: ${{ steps.render-web-container.outputs.task-definition }} task-definition: ${{ steps.render-web-container.outputs.task-definition }}
service: infisical-core-gamma-stage service: infisical-core-platform
cluster: infisical-gamma-stage cluster: infisical-core-platform
wait-for-service-stability: true wait-for-service-stability: true
production-postgres-deployment: production-postgres-deployment:

View File

@ -23,6 +23,8 @@ import {
} from "@app/lib/crypto/encryption"; } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type"; import { AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { TokenType } from "@app/services/auth-token/auth-token-types";
import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { TGroupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal"; import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
import { TOrgDALFactory } from "@app/services/org/org-dal"; import { TOrgDALFactory } from "@app/services/org/org-dal";
@ -30,6 +32,7 @@ import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membe
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal"; import { TProjectBotDALFactory } from "@app/services/project-bot/project-bot-dal";
import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal"; import { TProjectKeyDALFactory } from "@app/services/project-key/project-key-dal";
import { SmtpTemplates, TSmtpService } from "@app/services/smtp/smtp-service";
import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TUserDALFactory } from "@app/services/user/user-dal"; import { TUserDALFactory } from "@app/services/user/user-dal";
import { normalizeUsername } from "@app/services/user/user-fns"; import { normalizeUsername } from "@app/services/user/user-fns";
@ -84,6 +87,8 @@ type TLdapConfigServiceFactoryDep = {
userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">; userAliasDAL: Pick<TUserAliasDALFactory, "create" | "findOne">;
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">; permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">; licenseService: Pick<TLicenseServiceFactory, "getPlan" | "updateSubscriptionOrgMemberCount">;
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser">;
smtpService: Pick<TSmtpService, "sendMail">;
}; };
export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>; export type TLdapConfigServiceFactory = ReturnType<typeof ldapConfigServiceFactory>;
@ -103,7 +108,9 @@ export const ldapConfigServiceFactory = ({
userDAL, userDAL,
userAliasDAL, userAliasDAL,
permissionService, permissionService,
licenseService licenseService,
tokenService,
smtpService
}: TLdapConfigServiceFactoryDep) => { }: TLdapConfigServiceFactoryDep) => {
const createLdapCfg = async ({ const createLdapCfg = async ({
actor, actor,
@ -494,7 +501,7 @@ export const ldapConfigServiceFactory = ({
if (!orgMembership) { if (!orgMembership) {
await orgMembershipDAL.create( await orgMembershipDAL.create(
{ {
userId: userAlias.userId, userId: newUser.id,
inviteEmail: email, inviteEmail: email,
orgId, orgId,
role: OrgMembershipRole.Member, role: OrgMembershipRole.Member,
@ -627,6 +634,22 @@ export const ldapConfigServiceFactory = ({
} }
); );
if (user.email && !user.isEmailVerified) {
const token = await tokenService.createTokenForUser({
type: TokenType.TOKEN_EMAIL_VERIFICATION,
userId: user.id
});
await smtpService.sendMail({
template: SmtpTemplates.EmailVerification,
subjectLine: "Infisical confirmation code",
recipients: [user.email],
substitutions: {
code: token
}
});
}
return { isUserCompleted, providerAuthToken }; return { isUserCompleted, providerAuthToken };
}; };

View File

@ -101,25 +101,9 @@ export const getUserPrivateKey = async (
password: string, password: string,
user: Pick< user: Pick<
TUserEncryptionKeys, TUserEncryptionKeys,
| "protectedKeyTag" "protectedKeyTag" | "protectedKey" | "protectedKeyIV" | "encryptedPrivateKey" | "iv" | "salt" | "tag"
| "protectedKey"
| "protectedKeyIV"
| "encryptedPrivateKey"
| "iv"
| "salt"
| "tag"
| "encryptionVersion"
> >
) => { ) => {
if (user.encryptionVersion === 1) {
return decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.encryptedPrivateKey,
iv: user.iv,
tag: user.tag,
key: password.slice(0, 32).padStart(32 + (password.slice(0, 32).length - new Blob([password]).size), "0")
});
}
if (user.encryptionVersion === 2 && user.protectedKey && user.protectedKeyIV && user.protectedKeyTag) {
const derivedKey = await argon2.hash(password, { const derivedKey = await argon2.hash(password, {
salt: Buffer.from(user.salt), salt: Buffer.from(user.salt),
memoryCost: 65536, memoryCost: 65536,
@ -131,9 +115,9 @@ export const getUserPrivateKey = async (
}); });
if (!derivedKey) throw new Error("Failed to derive key from password"); if (!derivedKey) throw new Error("Failed to derive key from password");
const key = decryptSymmetric128BitHexKeyUTF8({ const key = decryptSymmetric128BitHexKeyUTF8({
ciphertext: user.protectedKey, ciphertext: user.protectedKey as string,
iv: user.protectedKeyIV, iv: user.protectedKeyIV as string,
tag: user.protectedKeyTag, tag: user.protectedKeyTag as string,
key: derivedKey key: derivedKey
}); });
@ -144,8 +128,6 @@ export const getUserPrivateKey = async (
key: Buffer.from(key, "hex") key: Buffer.from(key, "hex")
}); });
return privateKey; return privateKey;
}
throw new Error(`GetUserPrivateKey: Encryption version not found`);
}; };
export const buildUserProjectKey = async (privateKey: string, publickey: string) => { export const buildUserProjectKey = async (privateKey: string, publickey: string) => {

View File

@ -392,7 +392,9 @@ export const registerRoutes = async (
userDAL, userDAL,
userAliasDAL, userAliasDAL,
permissionService, permissionService,
licenseService licenseService,
tokenService,
smtpService
}); });
const telemetryService = telemetryServiceFactory({ const telemetryService = telemetryServiceFactory({

View File

@ -9,7 +9,6 @@ import { generateSrpServerKey, srpCheckClientProof } from "@app/lib/crypto";
import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { getUserPrivateKey } from "@app/lib/crypto/srp"; import { getUserPrivateKey } from "@app/lib/crypto/srp";
import { BadRequestError, DatabaseError, UnauthorizedError } from "@app/lib/errors"; import { BadRequestError, DatabaseError, UnauthorizedError } from "@app/lib/errors";
import { logger } from "@app/lib/logger";
import { getServerCfg } from "@app/services/super-admin/super-admin-service"; import { getServerCfg } from "@app/services/super-admin/super-admin-service";
import { TTokenDALFactory } from "../auth-token/auth-token-dal"; import { TTokenDALFactory } from "../auth-token/auth-token-dal";
@ -259,13 +258,7 @@ export const authLoginServiceFactory = ({
}); });
// from password decrypt the private key // from password decrypt the private key
if (password) { if (password) {
const privateKey = await getUserPrivateKey(password, userEnc).catch((err) => { const privateKey = await getUserPrivateKey(password, userEnc);
logger.error(
err,
`loginExchangeClientProof: private key generation failed for [userId=${user.id}] and [email=${user.email}] `
);
return "";
});
const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND); const hashedPassword = await bcrypt.hash(password, cfg.BCRYPT_SALT_ROUND);
const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey); const { iv, tag, ciphertext, encoding } = infisicalSymmetricEncypt(privateKey);
await userDAL.updateUserEncryptionByUserId(userEnc.userId, { await userDAL.updateUserEncryptionByUserId(userEnc.userId, {

View File

@ -165,8 +165,7 @@ export const authSignupServiceFactory = ({
protectedKeyTag, protectedKeyTag,
encryptedPrivateKey, encryptedPrivateKey,
iv: encryptedPrivateKeyIV, iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag, tag: encryptedPrivateKeyTag
encryptionVersion: 2
}); });
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey); const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
const updateduser = await authDAL.transaction(async (tx) => { const updateduser = await authDAL.transaction(async (tx) => {
@ -326,8 +325,7 @@ export const authSignupServiceFactory = ({
protectedKeyTag, protectedKeyTag,
encryptedPrivateKey, encryptedPrivateKey,
iv: encryptedPrivateKeyIV, iv: encryptedPrivateKeyIV,
tag: encryptedPrivateKeyTag, tag: encryptedPrivateKeyTag
encryptionVersion: 2
}); });
const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey); const { tag, encoding, ciphertext, iv } = infisicalSymmetricEncypt(privateKey);
const updateduser = await authDAL.transaction(async (tx) => { const updateduser = await authDAL.transaction(async (tx) => {

View File

@ -98,7 +98,6 @@ export const superAdminServiceFactory = ({
if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" }); if (existingUser) throw new BadRequestError({ name: "Admin sign up", message: "User already exist" });
const privateKey = await getUserPrivateKey(password, { const privateKey = await getUserPrivateKey(password, {
encryptionVersion: 2,
salt, salt,
protectedKey, protectedKey,
protectedKeyIV, protectedKeyIV,

View File

@ -26,6 +26,13 @@ A typical workflow for using identities consists of four steps:
3. Authenticating the identity with the Infisical API based on the configured authentication method on it and receiving a short-lived access token back. 3. Authenticating the identity with the Infisical API based on the configured authentication method on it and receiving a short-lived access token back.
4. Authenticating subsequent requests with the Infisical API using the short-lived access token. 4. Authenticating subsequent requests with the Infisical API using the short-lived access token.
<Note>
Currently, identities can only be used to make authenticated requests to the Infisical API, SDKs, Terraform, Kubernetes Operator, and Infisical Agent. They do not work with clients such as CLI, Ansible look up plugin, etc.
Machine Identity support for the rest of the clients is planned to be released in the current quarter.
</Note>
## Authentication Methods ## Authentication Methods
To interact with various resources in Infisical, Machine Identities are able to authenticate using: To interact with various resources in Infisical, Machine Identities are able to authenticate using:

View File

@ -14,6 +14,8 @@ then you should contact sales@infisical.com to purchase an enterprise license to
You can configure your organization in Infisical to have members authenticate with the platform via [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol). You can configure your organization in Infisical to have members authenticate with the platform via [LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol).
To note, configuring LDAP retains the end-to-end encrypted nature of authentication in Infisical because we decouple the authentication and decryption steps; the LDAP server cannot and will not have access to the decryption key needed to decrypt your secrets.
LDAP providers: LDAP providers:
- Active Directory - Active Directory

View File

@ -15,6 +15,9 @@ description: "Learn how to log in to Infisical via SSO protocols."
You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0). You can configure your organization in Infisical to have members authenticate with the platform via protocols like [SAML 2.0](https://en.wikipedia.org/wiki/SAML_2.0).
To note, Infisical's SSO implementation decouples the **authentication** and **decryption** steps  which implies that no
Identity Provider can have access to the decryption key needed to decrypt your secrets (this also implies that Infisical requires entering the user's Master Password on top of authenticating with SSO).
## Identity providers ## Identity providers
Infisical supports these and many other identity providers: Infisical supports these and many other identity providers:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

View File

@ -7,9 +7,7 @@ Prerequisites:
- Set up and add envars to [Infisical Cloud](https://app.infisical.com) - Set up and add envars to [Infisical Cloud](https://app.infisical.com)
<AccordionGroup> <Steps>
<Accordion title="Push secrets to Bitbucket from Infisical">
<Steps>
<Step title="Authorize Infisical for Bitbucket"> <Step title="Authorize Infisical for Bitbucket">
Navigate to your project's integrations tab in Infisical. Navigate to your project's integrations tab in Infisical.
@ -19,50 +17,16 @@ Prerequisites:
![integrations bitbucket authorization](../../images/integrations/bitbucket/integrations-bitbucket-auth.png) ![integrations bitbucket authorization](../../images/integrations/bitbucket/integrations-bitbucket-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>
</Step> </Step>
<Step title="Start integration"> <Step title="Start integration">
Select which Infisical environment secrets you want to sync to which Bitbucket repo and press start integration to start syncing secrets to the repo. Select which Infisical environment secrets you want to sync to which Bitbucket repo and press start integration to start syncing secrets to the repo.
![integrations bitbucket](../../images/integrations/bitbucket/integrations-bitbucket.png) ![integrations bitbucket](../../images/integrations/bitbucket/integrations-bitbucket.png)
</Step> </Step>
</Steps> </Steps>
</Accordion>
<Accordion title="Pull secrets in Bitbucket pipelines from Infisical">
<Steps>
<Step title="Configure Infisical Access">
Configure a [Machine Identity](https://infisical.com/docs/documentation/platform/identities/universal-auth) for your project and give it permissions to read secrets from your desired Infisical projects and environments.
</Step>
<Step title="Initialize Bitbucket variables">
Create Bitbucket variables (can be either workspace, repository, or deployment-level) to store Machine Identity Client ID and Client Secret.
![integrations bitbucket](../../images/integrations/bitbucket/integrations-bitbucket-env.png)
</Step>
<Step title="Integrate Infisical secrets into the pipeline">
Edit your Bitbucket pipeline YAML file to include the use of the Infisical CLI to fetch and inject secrets into any script or command within the pipeline.
#### Example
```yaml
image: atlassian/default-image:3
pipelines:
default:
- step:
name: Build application with secrets from Infisical
script:
- apt update && apt install -y curl
- curl -1sLf 'https://dl.cloudsmith.io/public/infisical/infisical-cli/setup.deb.sh' | bash
- apt-get update && apt-get install -y infisical
- export INFISICAL_TOKEN=$(infisical login --method=universal-auth --client-id=$INFISICAL_CLIENT_ID --client-secret=$INFISICAL_CLIENT_SECRET --silent --plain)
- infisical run --projectId=1d0443c1-cd43-4b3a-91a3-9d5f81254a89 --env=dev -- npm run build
```
<Tip>
Set the values of `projectId` and `env` flags in the `infisical run` command to your intended source path. For more options, refer to the CLI command reference [here](https://infisical.com/docs/cli/commands/run).
</Tip>
</Step>
</Steps>
</Accordion>
</AccordionGroup>

View File

@ -27,6 +27,12 @@ Prerequisites:
![integrations terraform cloud authorization](../../images/integrations/terraform/integrations-terraformcloud-auth.png) ![integrations terraform cloud authorization](../../images/integrations/terraform/integrations-terraformcloud-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>
</Step> </Step>
<Step title="Start integration"> <Step title="Start integration">
Select which Infisical environment secrets and Terraform Cloud variable type you want to sync to which Terraform Cloud workspace/project and press create integration to start syncing secrets to Terraform Cloud. Select which Infisical environment secrets and Terraform Cloud variable type you want to sync to which Terraform Cloud workspace/project and press create integration to start syncing secrets to Terraform Cloud.

View File

@ -638,10 +638,5 @@
], ],
"integrations": { "integrations": {
"intercom": "hsg644ru" "intercom": "hsg644ru"
},
"analytics": {
"koala": {
"publicApiKey": "pk_b50d7184e0e39ddd5cdb43cf6abeadd9b97d"
}
} }
} }

View File

@ -19,9 +19,6 @@ From local development to production, Infisical SDKs provide the easiest way for
<Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23"> <Card href="/sdks/languages/java" title="Java" icon="java" color="#e41f23">
Manage secrets for your Java application on demand Manage secrets for your Java application on demand
</Card> </Card>
<Card href="/sdks/languages/go" title="Go icon="golang" color="#367B99">
Manage secrets for your Go application on demand
</Card>
<Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833"> <Card href="/sdks/languages/csharp" title="C#" icon="bars" color="#368833">
Manage secrets for your C#/.NET application on demand Manage secrets for your C#/.NET application on demand
</Card> </Card>

View File

@ -25,10 +25,6 @@ Used to configure platform-specific security and operational settings
https://app.infisical.com). https://app.infisical.com).
</ParamField> </ParamField>
<ParamField query="TELEMETRY_ENABLED" type="string" default="true" optional>
Telemetry helps us improve Infisical but if you want to dsiable it you may set this to `false`.
</ParamField>
## Data Layer ## Data Layer
The platform utilizes Postgres to persist all of its data and Redis for caching and backgroud tasks The platform utilizes Postgres to persist all of its data and Redis for caching and backgroud tasks

View File

@ -6,8 +6,6 @@ import { faUpload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { parseDocument, Scalar, YAMLMap } from "yaml"; import { parseDocument, Scalar, YAMLMap } from "yaml";
import { SecretType } from "@app/hooks/api/types";
import Button from "../basic/buttons/Button"; import Button from "../basic/buttons/Button";
import Error from "../basic/Error"; import Error from "../basic/Error";
import { createNotification } from "../notifications"; import { createNotification } from "../notifications";
@ -36,6 +34,7 @@ const DropZone = ({
}: DropZoneProps) => { }: DropZoneProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const handleDragEnter = (e: DragEvent) => { const handleDragEnter = (e: DragEvent) => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
@ -67,7 +66,7 @@ const DropZone = ({
key, key,
value: keyPairs[key as keyof typeof keyPairs].value, value: keyPairs[key as keyof typeof keyPairs].value,
comment: keyPairs[key as keyof typeof keyPairs].comments.join("\n"), comment: keyPairs[key as keyof typeof keyPairs].comments.join("\n"),
type: SecretType.Shared, type: "shared",
tags: [] tags: []
})); }));
break; break;
@ -80,7 +79,7 @@ const DropZone = ({
key, key,
value: keyPairs[key as keyof typeof keyPairs], value: keyPairs[key as keyof typeof keyPairs],
comment: "", comment: "",
type: SecretType.Shared, type: "shared",
tags: [] tags: []
})); }));
break; break;
@ -103,7 +102,7 @@ const DropZone = ({
key, key,
value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? "", value: keyPairs[key as keyof typeof keyPairs]?.toString() ?? "",
comment, comment,
type: SecretType.Shared, type: "shared",
tags: [] tags: []
}; };
}); });
@ -133,7 +132,7 @@ const DropZone = ({
if (file === undefined) { if (file === undefined) {
createNotification({ createNotification({
text: "You can't inject files from VS Code. Click 'Reveal in finder', and drag your file directly from the directory where it's located.", text: "You can't inject files from VS Code. Click 'Reveal in finder', and drag your file directly from the directory where it's located.",
type: "error" type: "error",
}); });
setLoading(false); setLoading(false);
return; return;

View File

@ -26,4 +26,4 @@ export const createNotification = (
type: myProps?.type || "info", type: myProps?.type || "info",
}); });
export const NotificationContainer = () => <ToastContainer pauseOnHover toastClassName="border border-mineshaft-500" style={{ width: "400px" }} />; export const NotificationContainer = () => <ToastContainer hideProgressBar />;

View File

@ -1,7 +1,5 @@
import { SecretDataProps } from "public/data/frequentInterfaces"; import { SecretDataProps } from "public/data/frequentInterfaces";
import { SecretType } from "@app/hooks/api/types";
/** /**
* This function downloads the secrets as a .env file * This function downloads the secrets as a .env file
* @param {object} obj * @param {object} obj
@ -12,8 +10,8 @@ const checkOverrides = async ({ data }: { data: SecretDataProps[] }) => {
let secrets: SecretDataProps[] = data!.map((secret) => Object.create(secret)); let secrets: SecretDataProps[] = data!.map((secret) => Object.create(secret));
const overridenSecrets = data!.filter((secret) => const overridenSecrets = data!.filter((secret) =>
secret.valueOverride === undefined || secret?.value !== secret?.valueOverride secret.valueOverride === undefined || secret?.value !== secret?.valueOverride
? SecretType.Shared ? "shared"
: SecretType.Personal : "personal"
); );
if (overridenSecrets.length) { if (overridenSecrets.length) {
overridenSecrets.forEach((secret) => { overridenSecrets.forEach((secret) => {

View File

@ -3,7 +3,6 @@ import crypto from "crypto";
import { SecretDataProps, Tag } from "public/data/frequentInterfaces"; import { SecretDataProps, Tag } from "public/data/frequentInterfaces";
import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; import { fetchUserWsKey } from "@app/hooks/api/keys/queries";
import { SecretType } from "@app/hooks/api/types";
import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto"; import { decryptAssymmetric, encryptSymmetric } from "../cryptography/crypto";
@ -21,7 +20,7 @@ interface EncryptedSecretProps {
secretValueCiphertext: string; secretValueCiphertext: string;
secretValueIV: string; secretValueIV: string;
secretValueTag: string; secretValueTag: string;
type: SecretType; type: "personal" | "shared";
tags: Tag[]; tags: Tag[];
} }
@ -109,8 +108,8 @@ const encryptSecrets = async ({
secretCommentTag, secretCommentTag,
type: type:
secret.valueOverride === undefined || secret?.value !== secret?.valueOverride secret.valueOverride === undefined || secret?.value !== secret?.valueOverride
? SecretType.Shared ? "shared"
: SecretType.Personal, : "personal",
tags: secret.tags tags: secret.tags
}; };

View File

@ -15,7 +15,7 @@ const replaceContentWithDot = (str: string) => {
}; };
const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?: boolean) => { const syntaxHighlight = (content?: string | null, isVisible?: boolean, isImport?: boolean) => {
if (isImport && !content) return "IMPORTED"; if (isImport) return "IMPORTED";
if (content === "") return "EMPTY"; if (content === "") return "EMPTY";
if (!content) return "EMPTY"; if (!content) return "EMPTY";
if (!isVisible) return replaceContentWithDot(content); if (!isVisible) return replaceContentWithDot(content);

View File

@ -279,28 +279,7 @@ export const useGetImportedSecretsAllEnvs = ({
[(secretImports || []).map((response) => response.data)] [(secretImports || []).map((response) => response.data)]
); );
const getImportedSecretByKey = useCallback( return { secretImports, isImportedSecretPresentInEnv };
(envSlug: string, secretName: string) => {
const selectedEnvIndex = environments.indexOf(envSlug);
if (selectedEnvIndex !== -1) {
const secret = secretImports?.[selectedEnvIndex]?.data?.find(({ secrets }) =>
secrets.find((s) => s.key === secretName)
);
if (!secret) return undefined;
return {
secret: secret?.secrets.find((s) => s.key === secretName),
environmentInfo: secret?.environmentInfo
};
}
return undefined;
},
[(secretImports || []).map((response) => response.data)]
);
return { secretImports, isImportedSecretPresentInEnv, getImportedSecretByKey };
}; };
export const useGetImportedFoldersByEnv = ({ export const useGetImportedFoldersByEnv = ({

View File

@ -7,7 +7,7 @@ import {
} from "@app/components/utilities/cryptography/crypto"; } from "@app/components/utilities/cryptography/crypto";
import { apiRequest } from "@app/config/request"; import { apiRequest } from "@app/config/request";
import { DecryptedSecret, SecretType } from "../secrets/types"; import { DecryptedSecret } from "../secrets/types";
import { import {
TGetSecretSnapshotsDTO, TGetSecretSnapshotsDTO,
TSecretRollbackDTO, TSecretRollbackDTO,
@ -112,7 +112,7 @@ export const useGetSnapshotSecrets = ({ decryptFileKey, snapshotId }: TSnapshotD
version: encSecret.version version: encSecret.version
}; };
if (encSecret.type === SecretType.Personal) { if (encSecret.type === "personal") {
personalSecrets[decryptedSecret.key] = { id: encSecret.secretId, value: secretValue }; personalSecrets[decryptedSecret.key] = { id: encSecret.secretId, value: secretValue };
} else { } else {
sharedSecrets.push(decryptedSecret); sharedSecrets.push(decryptedSecret);

View File

@ -14,7 +14,6 @@ import {
EncryptedSecret, EncryptedSecret,
EncryptedSecretVersion, EncryptedSecretVersion,
GetSecretVersionsDTO, GetSecretVersionsDTO,
SecretType,
TGetProjectSecretsAllEnvDTO, TGetProjectSecretsAllEnvDTO,
TGetProjectSecretsDTO, TGetProjectSecretsDTO,
TGetProjectSecretsKey TGetProjectSecretsKey
@ -78,7 +77,7 @@ export const decryptSecrets = (
skipMultilineEncoding: encSecret.skipMultilineEncoding skipMultilineEncoding: encSecret.skipMultilineEncoding
}; };
if (encSecret.type === SecretType.Personal) { if (encSecret.type === "personal") {
personalSecrets[decryptedSecret.key] = { personalSecrets[decryptedSecret.key] = {
id: encSecret.id, id: encSecret.id,
value: secretValue value: secretValue

View File

@ -1,16 +1,11 @@
import type { UserWsKeyPair } from "../keys/types"; import type { UserWsKeyPair } from "../keys/types";
import type { WsTag } from "../tags/types"; import type { WsTag } from "../tags/types";
export enum SecretType {
Shared = "shared",
Personal = "personal"
}
export type EncryptedSecret = { export type EncryptedSecret = {
id: string; id: string;
version: number; version: number;
workspace: string; workspace: string;
type: SecretType; type: "shared" | "personal";
environment: string; environment: string;
secretKeyCiphertext: string; secretKeyCiphertext: string;
secretKeyIV: string; secretKeyIV: string;
@ -54,7 +49,7 @@ export type EncryptedSecretVersion = {
secretId: string; secretId: string;
version: number; version: number;
workspace: string; workspace: string;
type: SecretType; type: string;
isDeleted: boolean; isDeleted: boolean;
envId: string; envId: string;
secretKeyCiphertext: string; secretKeyCiphertext: string;
@ -106,14 +101,14 @@ export type TCreateSecretsV3DTO = {
secretPath: string; secretPath: string;
workspaceId: string; workspaceId: string;
environment: string; environment: string;
type: SecretType; type: string;
}; };
export type TUpdateSecretsV3DTO = { export type TUpdateSecretsV3DTO = {
latestFileKey: UserWsKeyPair; latestFileKey: UserWsKeyPair;
workspaceId: string; workspaceId: string;
environment: string; environment: string;
type: SecretType; type: string;
secretPath: string; secretPath: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
newSecretName?: string; newSecretName?: string;
@ -129,7 +124,7 @@ export type TUpdateSecretsV3DTO = {
export type TDeleteSecretsV3DTO = { export type TDeleteSecretsV3DTO = {
workspaceId: string; workspaceId: string;
environment: string; environment: string;
type: SecretType; type: "shared" | "personal";
secretPath: string; secretPath: string;
secretName: string; secretName: string;
secretId?: string; secretId?: string;
@ -145,7 +140,7 @@ export type TCreateSecretBatchDTO = {
secretValue: string; secretValue: string;
secretComment: string; secretComment: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
type: SecretType; type: "shared" | "personal";
metadata?: { metadata?: {
source?: string; source?: string;
}; };
@ -158,7 +153,7 @@ export type TUpdateSecretBatchDTO = {
secretPath: string; secretPath: string;
latestFileKey: UserWsKeyPair; latestFileKey: UserWsKeyPair;
secrets: Array<{ secrets: Array<{
type: SecretType; type: "shared" | "personal";
secretName: string; secretName: string;
skipMultilineEncoding?: boolean; skipMultilineEncoding?: boolean;
secretValue: string; secretValue: string;
@ -173,14 +168,14 @@ export type TDeleteSecretBatchDTO = {
secretPath: string; secretPath: string;
secrets: Array<{ secrets: Array<{
secretName: string; secretName: string;
type: SecretType; type: "shared" | "personal";
}>; }>;
}; };
export type CreateSecretDTO = { export type CreateSecretDTO = {
workspaceId: string; workspaceId: string;
environment: string; environment: string;
type: SecretType; type: "shared" | "personal";
secretKey: string; secretKey: string;
secretKeyCiphertext: string; secretKeyCiphertext: string;
secretKeyIV: string; secretKeyIV: string;

View File

@ -47,7 +47,7 @@ import { ProjectPermissionActions, ProjectPermissionSub, useSubscription } from
import { interpolateSecrets } from "@app/helpers/secret"; import { interpolateSecrets } from "@app/helpers/secret";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useCreateFolder, useDeleteSecretBatch, useGetUserWsKey } from "@app/hooks/api"; import { useCreateFolder, useDeleteSecretBatch, useGetUserWsKey } from "@app/hooks/api";
import { DecryptedSecret, SecretType, TImportedSecrets, WsTag } from "@app/hooks/api/types"; import { DecryptedSecret, TImportedSecrets, WsTag } from "@app/hooks/api/types";
import { debounce } from "@app/lib/fn/debounce"; import { debounce } from "@app/lib/fn/debounce";
import { import {
@ -211,7 +211,7 @@ export const ActionBar = ({
secretPath, secretPath,
workspaceId, workspaceId,
environment, environment,
secrets: bulkDeletedSecrets.map(({ key }) => ({ secretName: key, type: SecretType.Shared })) secrets: bulkDeletedSecrets.map(({ key }) => ({ secretName: key, type: "shared" }))
}); });
resetSelectedSecret(); resetSelectedSecret();
handlePopUpClose("bulkDeleteSecrets"); handlePopUpClose("bulkDeleteSecrets");

View File

@ -6,7 +6,7 @@ import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2"; import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { useCreateSecretV3 } from "@app/hooks/api"; import { useCreateSecretV3 } from "@app/hooks/api";
import { SecretType, UserWsKeyPair } from "@app/hooks/api/types"; import { UserWsKeyPair } from "@app/hooks/api/types";
import { PopUpNames, usePopUpAction, usePopUpState } from "../../SecretMainPage.store"; import { PopUpNames, usePopUpAction, usePopUpState } from "../../SecretMainPage.store";
@ -56,7 +56,7 @@ export const CreateSecretForm = ({
secretName: key, secretName: key,
secretValue: value || "", secretValue: value || "",
secretComment: "", secretComment: "",
type: SecretType.Shared, type: "shared",
latestFileKey: decryptFileKey latestFileKey: decryptFileKey
}); });
closePopUp(PopUpNames.CreateSecretForm); closePopUp(PopUpNames.CreateSecretForm);

View File

@ -16,7 +16,7 @@ import { usePopUp, useToggle } from "@app/hooks";
import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api"; import { useCreateSecretBatch, useUpdateSecretBatch } from "@app/hooks/api";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { secretKeys } from "@app/hooks/api/secrets/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret, SecretType, UserWsKeyPair } from "@app/hooks/api/types"; import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types";
import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store"; import { PopUpNames, usePopUpAction } from "../../SecretMainPage.store";
import { CopySecretsFromBoard } from "./CopySecretsFromBoard"; import { CopySecretsFromBoard } from "./CopySecretsFromBoard";
@ -170,7 +170,7 @@ export const SecretDropzone = ({
workspaceId, workspaceId,
environment, environment,
secrets: Object.entries(create).map(([secretName, secData]) => ({ secrets: Object.entries(create).map(([secretName, secData]) => ({
type: SecretType.Shared, type: "shared",
secretComment: secData.comments.join("\n"), secretComment: secData.comments.join("\n"),
secretValue: secData.value, secretValue: secData.value,
secretName secretName
@ -184,7 +184,7 @@ export const SecretDropzone = ({
workspaceId, workspaceId,
environment, environment,
secrets: Object.entries(update).map(([secretName, secData]) => ({ secrets: Object.entries(update).map(([secretName, secData]) => ({
type: SecretType.Shared, type: "shared",
secretComment: secData.comments.join("\n"), secretComment: secData.comments.join("\n"),
secretValue: secData.value, secretValue: secData.value,
secretName secretName

View File

@ -10,7 +10,7 @@ import { usePopUp } from "@app/hooks";
import { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "@app/hooks/api"; import { useCreateSecretV3, useDeleteSecretV3, useUpdateSecretV3 } from "@app/hooks/api";
import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries"; import { secretApprovalRequestKeys } from "@app/hooks/api/secretApprovalRequest/queries";
import { secretKeys } from "@app/hooks/api/secrets/queries"; import { secretKeys } from "@app/hooks/api/secrets/queries";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types"; import { DecryptedSecret } from "@app/hooks/api/secrets/types";
import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries"; import { secretSnapshotKeys } from "@app/hooks/api/secretSnapshots/queries";
import { UserWsKeyPair, WsTag } from "@app/hooks/api/types"; import { UserWsKeyPair, WsTag } from "@app/hooks/api/types";
@ -119,7 +119,7 @@ export const SecretListView = ({
const handleSecretOperation = async ( const handleSecretOperation = async (
operation: "create" | "update" | "delete", operation: "create" | "update" | "delete",
type: SecretType, type: "shared" | "personal",
key: string, key: string,
{ {
value, value,
@ -227,25 +227,23 @@ export const SecretListView = ({
try { try {
// personal secret change // personal secret change
if (overrideAction === "deleted") { if (overrideAction === "deleted") {
await handleSecretOperation("delete", SecretType.Personal, oldKey, { await handleSecretOperation("delete", "personal", oldKey, {
secretId: orgSecret.idOverride secretId: orgSecret.idOverride
}); });
} else if (overrideAction && idOverride) { } else if (overrideAction && idOverride) {
await handleSecretOperation("update", SecretType.Personal, oldKey, { await handleSecretOperation("update", "personal", oldKey, {
value: valueOverride, value: valueOverride,
newKey: hasKeyChanged ? key : undefined, newKey: hasKeyChanged ? key : undefined,
secretId: orgSecret.idOverride, secretId: orgSecret.idOverride,
skipMultilineEncoding: modSecret.skipMultilineEncoding skipMultilineEncoding: modSecret.skipMultilineEncoding
}); });
} else if (overrideAction) { } else if (overrideAction) {
await handleSecretOperation("create", SecretType.Personal, oldKey, { await handleSecretOperation("create", "personal", oldKey, { value: valueOverride });
value: valueOverride
});
} }
// shared secret change // shared secret change
if (!isSharedSecUnchanged) { if (!isSharedSecUnchanged) {
await handleSecretOperation("update", SecretType.Shared, oldKey, { await handleSecretOperation("update", "shared", oldKey, {
value, value,
tags: tagIds, tags: tagIds,
comment, comment,
@ -288,7 +286,7 @@ export const SecretListView = ({
const handleSecretDelete = useCallback(async () => { const handleSecretDelete = useCallback(async () => {
const { key, id: secretId } = popUp.deleteSecret?.data as DecryptedSecret; const { key, id: secretId } = popUp.deleteSecret?.data as DecryptedSecret;
try { try {
await handleSecretOperation("delete", SecretType.Shared, key, { secretId }); await handleSecretOperation("delete", "shared", key, { secretId });
// wrap this in another function and then reuse // wrap this in another function and then reuse
queryClient.invalidateQueries( queryClient.invalidateQueries(
secretKeys.getProjectSecret({ workspaceId, environment, secretPath }) secretKeys.getProjectSecret({ workspaceId, environment, secretPath })

View File

@ -64,7 +64,7 @@ import {
} from "@app/hooks/api"; } from "@app/hooks/api";
import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries"; import { useUpdateFolderBatch } from "@app/hooks/api/secretFolders/queries";
import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types"; import { TUpdateFolderBatchDTO } from "@app/hooks/api/secretFolders/types";
import { SecretType, TSecretFolder } from "@app/hooks/api/types"; import { TSecretFolder } from "@app/hooks/api/types";
import { ProjectVersion } from "@app/hooks/api/workspace/types"; import { ProjectVersion } from "@app/hooks/api/workspace/types";
import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm"; import { FolderForm } from "../SecretMainPage/components/ActionBar/FolderForm";
@ -188,7 +188,7 @@ export const SecretOverviewPage = () => {
environments: userAvailableEnvs.map(({ slug }) => slug) environments: userAvailableEnvs.map(({ slug }) => slug)
}); });
const { isImportedSecretPresentInEnv, getImportedSecretByKey } = useGetImportedSecretsAllEnvs({ const { isImportedSecretPresentInEnv } = useGetImportedSecretsAllEnvs({
projectId: workspaceId, projectId: workspaceId,
decryptFileKey: latestFileKey!, decryptFileKey: latestFileKey!,
path: secretPath, path: secretPath,
@ -320,7 +320,7 @@ export const SecretOverviewPage = () => {
secretName: key, secretName: key,
secretValue: value, secretValue: value,
secretComment: "", secretComment: "",
type: SecretType.Shared, type: "shared",
latestFileKey: latestFileKey! latestFileKey: latestFileKey!
}); });
createNotification({ createNotification({
@ -344,13 +344,7 @@ export const SecretOverviewPage = () => {
} }
}; };
const handleSecretUpdate = async ( const handleSecretUpdate = async (env: string, key: string, value: string, secretId?: string) => {
env: string,
key: string,
value: string,
type = SecretType.Shared,
secretId?: string
) => {
try { try {
await updateSecretV3({ await updateSecretV3({
environment: env, environment: env,
@ -359,7 +353,7 @@ export const SecretOverviewPage = () => {
secretId, secretId,
secretName: key, secretName: key,
secretValue: value, secretValue: value,
type, type: "shared",
latestFileKey: latestFileKey! latestFileKey: latestFileKey!
}); });
createNotification({ createNotification({
@ -383,7 +377,7 @@ export const SecretOverviewPage = () => {
secretPath, secretPath,
secretName: key, secretName: key,
secretId, secretId,
type: SecretType.Shared type: "shared"
}); });
createNotification({ createNotification({
type: "success", type: "success",
@ -813,7 +807,6 @@ export const SecretOverviewPage = () => {
isSelected={selectedEntries.secret[key]} isSelected={selectedEntries.secret[key]}
onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)} onToggleSecretSelect={() => toggleSelectedEntry(EntryType.SECRET, key)}
secretPath={secretPath} secretPath={secretPath}
getImportedSecretByKey={getImportedSecretByKey}
isImportedSecretPresentInEnv={isImportedSecretPresentInEnv} isImportedSecretPresentInEnv={isImportedSecretPresentInEnv}
onSecretCreate={handleSecretCreate} onSecretCreate={handleSecretCreate}
onSecretDelete={handleSecretDelete} onSecretDelete={handleSecretDelete}

View File

@ -18,7 +18,7 @@ import {
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { useCreateFolder, useCreateSecretV3, useUpdateSecretV3 } from "@app/hooks/api"; import { useCreateFolder, useCreateSecretV3, useUpdateSecretV3 } from "@app/hooks/api";
import { DecryptedSecret, SecretType, UserWsKeyPair } from "@app/hooks/api/types"; import { DecryptedSecret, UserWsKeyPair } from "@app/hooks/api/types";
const typeSchema = z const typeSchema = z
.object({ .object({
@ -103,7 +103,7 @@ export const CreateSecretForm = ({
secretPath, secretPath,
secretName: key, secretName: key,
secretValue: value || "", secretValue: value || "",
type: SecretType.Shared, type: "shared",
latestFileKey: decryptFileKey latestFileKey: decryptFileKey
}); });
} }
@ -115,7 +115,7 @@ export const CreateSecretForm = ({
secretName: key, secretName: key,
secretValue: value || "", secretValue: value || "",
secretComment: "", secretComment: "",
type: SecretType.Shared, type: "shared",
latestFileKey: decryptFileKey latestFileKey: decryptFileKey
}); });
}); });

View File

@ -10,33 +10,24 @@ import { IconButton, Tooltip } from "@app/components/v2";
import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput"; import { InfisicalSecretInput } from "@app/components/v2/InfisicalSecretInput";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { SecretType } from "@app/hooks/api/types";
type Props = { type Props = {
defaultValue?: string | null; defaultValue?: string | null;
secretName: string; secretName: string;
secretId?: string; secretId?: string;
isOverride?: boolean;
isCreatable?: boolean; isCreatable?: boolean;
isVisible?: boolean; isVisible?: boolean;
isImportedSecret: boolean; isImportedSecret: boolean;
environment: string; environment: string;
secretPath: string; secretPath: string;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>; onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: ( onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
env: string,
key: string,
value: string,
type?: SecretType,
secretId?: string
) => Promise<void>;
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>; onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
}; };
export const SecretEditRow = ({ export const SecretEditRow = ({
defaultValue, defaultValue,
isCreatable, isCreatable,
isOverride,
isImportedSecret, isImportedSecret,
onSecretUpdate, onSecretUpdate,
secretName, secretName,
@ -82,13 +73,7 @@ export const SecretEditRow = ({
if (isCreatable) { if (isCreatable) {
await onSecretCreate(environment, secretName, value); await onSecretCreate(environment, secretName, value);
} else { } else {
await onSecretUpdate( await onSecretUpdate(environment, secretName, value, secretId);
environment,
secretName,
value,
isOverride ? SecretType.Personal : SecretType.Shared,
secretId
);
} }
} }
reset({ value }); reset({ value });
@ -108,13 +93,12 @@ export const SecretEditRow = ({
<div className="group flex w-full cursor-text items-center space-x-2"> <div className="group flex w-full cursor-text items-center space-x-2">
<div className="flex-grow border-r border-r-mineshaft-600 pr-2 pl-1"> <div className="flex-grow border-r border-r-mineshaft-600 pr-2 pl-1">
<Controller <Controller
disabled={isImportedSecret && !defaultValue} disabled={isImportedSecret}
control={control} control={control}
name="value" name="value"
render={({ field }) => ( render={({ field }) => (
<InfisicalSecretInput <InfisicalSecretInput
{...field} {...field}
isReadOnly={isImportedSecret}
value={field.value as string} value={field.value as string}
key="secret-input" key="secret-input"
isVisible={isVisible} isVisible={isVisible}

View File

@ -13,8 +13,7 @@ import { twMerge } from "tailwind-merge";
import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2"; import { Button, Checkbox, TableContainer, Td, Tooltip, Tr } from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { DecryptedSecret, SecretType } from "@app/hooks/api/secrets/types"; import { DecryptedSecret } from "@app/hooks/api/secrets/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
import { SecretEditRow } from "./SecretEditRow"; import { SecretEditRow } from "./SecretEditRow";
import SecretRenameRow from "./SecretRenameRow"; import SecretRenameRow from "./SecretRenameRow";
@ -28,19 +27,9 @@ type Props = {
onToggleSecretSelect: (key: string) => void; onToggleSecretSelect: (key: string) => void;
getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined; getSecretByKey: (slug: string, key: string) => DecryptedSecret | undefined;
onSecretCreate: (env: string, key: string, value: string) => Promise<void>; onSecretCreate: (env: string, key: string, value: string) => Promise<void>;
onSecretUpdate: ( onSecretUpdate: (env: string, key: string, value: string, secretId?: string) => Promise<void>;
env: string,
key: string,
value: string,
type?: SecretType,
secretId?: string
) => Promise<void>;
onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>; onSecretDelete: (env: string, key: string, secretId?: string) => Promise<void>;
isImportedSecretPresentInEnv: (env: string, secretName: string) => boolean; isImportedSecretPresentInEnv: (env: string, secretName: string) => boolean;
getImportedSecretByKey: (
env: string,
secretName: string
) => { secret?: DecryptedSecret; environmentInfo?: WorkspaceEnv } | undefined;
}; };
export const SecretOverviewTableRow = ({ export const SecretOverviewTableRow = ({
@ -52,7 +41,6 @@ export const SecretOverviewTableRow = ({
onSecretCreate, onSecretCreate,
onSecretDelete, onSecretDelete,
isImportedSecretPresentInEnv, isImportedSecretPresentInEnv,
getImportedSecretByKey,
expandableColWidth, expandableColWidth,
onToggleSecretSelect, onToggleSecretSelect,
isSelected isSelected
@ -65,8 +53,7 @@ export const SecretOverviewTableRow = ({
<> <>
<Tr isHoverable isSelectable onClick={() => setIsFormExpanded.toggle()} className="group"> <Tr isHoverable isSelectable onClick={() => setIsFormExpanded.toggle()} className="group">
<Td <Td
className={`sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding py-0 px-0 group-hover:bg-mineshaft-700 ${ className={`sticky left-0 z-10 bg-mineshaft-800 bg-clip-padding py-0 px-0 group-hover:bg-mineshaft-700 ${isFormExpanded && "border-t-2 border-mineshaft-500"
isFormExpanded && "border-t-2 border-mineshaft-500"
}`} }`}
> >
<div className="h-full w-full border-r border-mineshaft-600 py-2.5 px-5"> <div className="h-full w-full border-r border-mineshaft-600 py-2.5 px-5">
@ -145,8 +132,7 @@ export const SecretOverviewTableRow = ({
<Tr> <Tr>
<Td <Td
colSpan={totalCols} colSpan={totalCols}
className={`bg-bunker-600 px-0 py-0 ${ className={`bg-bunker-600 px-0 py-0 ${isFormExpanded && "border-b-2 border-mineshaft-500"
isFormExpanded && "border-b-2 border-mineshaft-500"
}`} }`}
> >
<div <div
@ -193,7 +179,6 @@ export const SecretOverviewTableRow = ({
const isCreatable = !secret; const isCreatable = !secret;
const isImportedSecret = isImportedSecretPresentInEnv(slug, secretKey); const isImportedSecret = isImportedSecretPresentInEnv(slug, secretKey);
const importedSecret = getImportedSecretByKey(slug, secretKey);
return ( return (
<tr <tr
@ -204,15 +189,8 @@ export const SecretOverviewTableRow = ({
className="flex h-full items-center" className="flex h-full items-center"
style={{ padding: "0.25rem 1rem" }} style={{ padding: "0.25rem 1rem" }}
> >
<div title={name} className="flex h-8 w-[8rem] items-center space-x-2 "> <div title={name} className="flex h-8 w-[8rem] items-center ">
<span className="truncate">{name}</span> <span className="truncate">{name}</span>
{isImportedSecret && (
<Tooltip
content={`Imported secret from the '${importedSecret?.environmentInfo?.name}' environment`}
>
<FontAwesomeIcon icon={faFileImport} />
</Tooltip>
)}
</div> </div>
</td> </td>
<td className="col-span-2 h-8 w-full"> <td className="col-span-2 h-8 w-full">
@ -220,13 +198,8 @@ export const SecretOverviewTableRow = ({
secretPath={secretPath} secretPath={secretPath}
isVisible={isSecretVisible} isVisible={isSecretVisible}
secretName={secretKey} secretName={secretKey}
defaultValue={ defaultValue={secret?.value}
secret?.valueOverride ||
secret?.value ||
importedSecret?.secret?.value
}
secretId={secret?.id} secretId={secret?.id}
isOverride={Boolean(secret?.valueOverride)}
isImportedSecret={isImportedSecret} isImportedSecret={isImportedSecret}
isCreatable={isCreatable} isCreatable={isCreatable}
onSecretDelete={onSecretDelete} onSecretDelete={onSecretDelete}

View File

@ -18,7 +18,7 @@ import {
} from "@app/context"; } from "@app/context";
import { useToggle } from "@app/hooks"; import { useToggle } from "@app/hooks";
import { useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api"; import { useGetUserWsKey, useUpdateSecretV3 } from "@app/hooks/api";
import { DecryptedSecret, SecretType } from "@app/hooks/api/types"; import { DecryptedSecret } from "@app/hooks/api/types";
import { SecretActionType } from "@app/views/SecretMainPage/components/SecretListView/SecretListView.utils"; import { SecretActionType } from "@app/views/SecretMainPage/components/SecretListView/SecretListView.utils";
type Props = { type Props = {
@ -38,6 +38,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { permission } = useProjectPermission(); const { permission } = useProjectPermission();
const secrets = environments.map((env) => getSecretByKey(env.slug, secretKey)); const secrets = environments.map((env) => getSecretByKey(env.slug, secretKey));
const isReadOnly = environments.some((env) => { const isReadOnly = environments.some((env) => {
@ -112,7 +113,7 @@ function SecretRenameRow({ environments, getSecretByKey, secretKey, secretPath }
secretName: secret.key, secretName: secret.key,
secretId: secret.id, secretId: secret.id,
secretValue: secret.value || "", secretValue: secret.value || "",
type: SecretType.Shared, type: "shared",
latestFileKey: decryptFileKey!, latestFileKey: decryptFileKey!,
tags: secret.tags.map((tag) => tag.id), tags: secret.tags.map((tag) => tag.id),
secretComment: secret.comment, secretComment: secret.comment,

View File

@ -13,12 +13,7 @@ import {
} from "@app/context"; } from "@app/context";
import { usePopUp } from "@app/hooks"; import { usePopUp } from "@app/hooks";
import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api"; import { useDeleteFolder, useDeleteSecretBatch } from "@app/hooks/api";
import { import { DecryptedSecret, TDeleteSecretBatchDTO, TSecretFolder } from "@app/hooks/api/types";
DecryptedSecret,
SecretType,
TDeleteSecretBatchDTO,
TSecretFolder
} from "@app/hooks/api/types";
export enum EntryType { export enum EntryType {
FOLDER = "folder", FOLDER = "folder",
@ -105,7 +100,7 @@ export const SelectionPanel = ({
...accum, ...accum,
{ {
secretName: entry.key, secretName: entry.key,
type: SecretType.Shared type: "shared" as "shared"
} }
]; ];
} }

View File

@ -11,6 +11,7 @@ import {
FormControl, FormControl,
Input, Input,
ModalClose, ModalClose,
SecretInput,
Select, Select,
SelectItem SelectItem
} from "@app/components/v2"; } from "@app/components/v2";
@ -124,7 +125,7 @@ export const AddShareSecretForm = ({
}; };
return ( return (
<form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}> <form className="flex w-full flex-col items-center" onSubmit={handleSubmit(onFormSubmit)}>
<div className={`${!inModal && "border border-mineshaft-600 bg-mineshaft-800 rounded-md p-6"}`}> <div className={`${!inModal && "border border-mineshaft-600 bg-mineshaft-800 p-4"}`}>
<div className="mb-4"> <div className="mb-4">
<Controller <Controller
control={control} control={control}
@ -136,25 +137,25 @@ export const AddShareSecretForm = ({
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<textarea <SecretInput
placeholder="Enter sensitive data to share via an encrypted link..." isVisible
{...field} {...field}
className="py-1.5 w-full h-40 placeholder:text-mineshaft-400 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/30 focus:border-primary-400/50 outline-none border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]" containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2 text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 min-h-[70px]"
/> />
</FormControl> </FormControl>
)} )}
/> />
</div> </div>
<div className="flex w-full flex-row justify-center"> <div className="flex w-full flex-row justify-center">
<div className="hidden sm:block sm:w-2/6 flex"> <div className="w-2/7 flex">
<Controller <Controller
control={control} control={control}
name="expiresAfterViews" name="expiresAfterViews"
defaultValue={1} defaultValue={6}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
className="mb-4 w-full" className="mb-4 w-full"
label="Expires after Views" label="Expires After Views"
isError={Boolean(error)} isError={Boolean(error)}
errorText="Please enter a valid number of views" errorText="Please enter a valid number of views"
> >
@ -163,16 +164,16 @@ export const AddShareSecretForm = ({
)} )}
/> />
</div> </div>
<div className="hidden sm:flex sm:w-1/7 items-center justify-center px-2 mx-auto"> <div className="w-1/7 flex items-center justify-center px-2">
<p className="px-4 text-sm text-gray-400">OR</p> <p className="px-4 text-sm text-gray-400">OR</p>
</div> </div>
<div className="w-full sm:w-3/6 flex justify-end"> <div className="w-4/7 flex">
<div className="flex justify-start"> <div className="flex w-full">
<div className="flex w-full pr-2 justify-center"> <div className="flex w-2/5 w-full justify-center">
<Controller <Controller
control={control} control={control}
name="expiresInValue" name="expiresInValue"
defaultValue={10} defaultValue={6}
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="Expires after Time" label="Expires after Time"
@ -184,7 +185,7 @@ export const AddShareSecretForm = ({
)} )}
/> />
</div> </div>
<div className="flex justify-center"> <div className="flex w-3/5 w-full justify-center">
<Controller <Controller
control={control} control={control}
name="expiresInUnit" name="expiresInUnit"
@ -195,7 +196,7 @@ export const AddShareSecretForm = ({
defaultValue={field.value} defaultValue={field.value}
{...field} {...field}
onValueChange={(e) => onChange(e)} onValueChange={(e) => onChange(e)}
className="w-full border border-mineshaft-600" className="w-full"
> >
{expirationUnitsAndActions.map(({ unit }) => ( {expirationUnitsAndActions.map(({ unit }) => (
<SelectItem value={unit} key={unit}> <SelectItem value={unit} key={unit}>
@ -210,7 +211,7 @@ export const AddShareSecretForm = ({
</div> </div>
</div> </div>
</div> </div>
<div className={`flex items-center ${!inModal && "justify-left pt-2"}`}> <div className={`flex items-center ${!inModal && "justify-left pt-1"}`}>
<Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}> <Button className="mr-4" type="submit" isDisabled={isSubmitting} isLoading={isSubmitting}>
{inModal ? "Create" : "Share Secret"} {inModal ? "Create" : "Share Secret"}
</Button> </Button>

View File

@ -15,9 +15,9 @@ export const ViewAndCopySharedSecret = ({
copyUrlToClipboard: () => void; copyUrlToClipboard: () => void;
}) => { }) => {
return ( return (
<div className={`flex w-full justify-center px-6 ${!inModal ? "mx-auto max-w-2xl" : ""}`}> <div className={`flex w-full justify-center ${!inModal ? "mx-auto max-w-[40rem]" : ""}`}>
<div className={`${!inModal ? "border border-mineshaft-600 bg-mineshaft-800 rounded-md p-4" : ""}`}> <div className={`${!inModal ? "border border-mineshaft-600 bg-mineshaft-800 p-4" : ""}`}>
<div className="my-2 flex items-center justify-end rounded-md border border-mineshaft-500 bg-mineshaft-700 p-2 text-base text-gray-400"> <div className="my-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{newSharedSecret}</p> <p className="mr-4 break-all">{newSharedSecret}</p>
<IconButton <IconButton
ariaLabel="copy icon" ariaLabel="copy icon"

View File

@ -3,7 +3,7 @@ import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { faArrowRight } from "@fortawesome/free-solid-svg-icons"; import { faArrowRight, faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto"; import { decryptSymmetric } from "@app/components/utilities/cryptography/crypto";
@ -54,17 +54,16 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
navigator.clipboard.writeText(decryptedSecret); navigator.clipboard.writeText(decryptedSecret);
setIsUrlCopied(true); setIsUrlCopied(true);
}; };
const { popUp, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["createSharedSecret"] as const);
return ( return (
<div className="h-screen dark:[color-scheme:dark] flex flex-col overflow-y-auto bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200"> <div className="h-screen bg-gradient-to-tr from-mineshaft-700 to-bunker-800 text-gray-200">
<Head> <Head>
<title>Secret Shared | Infisical</title> <title>Secret Shared | Infisical</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<div className="w-full flex flex-grow items-center justify-center dark:[color-scheme:dark]"> <div className="h-screen w-full flex-col items-center justify-center dark:[color-scheme:dark]">
<div className="relative"> <div className="mb-4 flex justify-center pt-8 md:pt-16">
<div className="mb-4 flex justify-center pt-8">
<Link href="https://infisical.com"> <Link href="https://infisical.com">
<Image <Image
src="/images/gradientLogo.svg" src="/images/gradientLogo.svg"
@ -75,12 +74,10 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
/> />
</Link> </Link>
</div> </div>
<div className="w-full flex justify-center"> <h1 className="mt-6 mb-4 bg-gradient-to-b from-white to-bunker-200 bg-clip-text px-4 text-center text-2xl font-medium text-transparent">
<h1 className={`${id ? "max-w-sm mb-4": "max-w-md mt-4 mb-6"} bg-gradient-to-b from-white to-bunker-200 bg-clip-text px-4 text-center text-3xl font-medium text-transparent`}> {id ? "Someone shared a secret on Infisical with you." : "Share Secrets with Infisical"}
{id ? "Someone shared a secret on Infisical with you" : "Share a secret with Infisical"}
</h1> </h1>
</div> <div className="m-auto mt-8 flex w-full max-w-xl justify-center px-4">
<div className="m-auto mt-4 flex w-full max-w-2xl justify-center px-6">
{id && ( {id && (
<SecretTable <SecretTable
isLoading={isLoading} isLoading={isLoading}
@ -92,67 +89,61 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
</div> </div>
{isNewSession && ( {isNewSession && (
<div className="px-0 sm:px-6">
<AddShareSecretModal <AddShareSecretModal
popUp={popUp} popUp={popUp}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
inModal={false} inModal={false}
isPublic isPublic
/> />
</div>
)}
{!isNewSession && (
<div className="flex flex-1 flex-col items-center justify-center px-6 pt-4">
<a
href="https://share.infisical.com/"
target="_blank"
rel="noopener noreferrer"
className="mt-3 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300 hover:text-mineshaft-100"
>
<Button
className="bg-mineshaft-700 text-bunker-200 w-full py-3"
colorSchema="primary"
variant="outline_bg"
size="sm"
onClick={() => {}}
rightIcon={<FontAwesomeIcon icon={faArrowRight} className="pl-2" />}
>
Share your own Secret
</Button>
</a>
</div>
)} )}
<div className="m-auto my-6 flex w-full max-w-xl justify-center px-8 px-4 sm:my-8"> <div className="m-auto my-6 flex w-full max-w-xl justify-center px-8 px-4 sm:my-8">
<div className="w-full border-t border-mineshaft-600" /> <div className="w-full border-t border-mineshaft-600" />
</div> </div>
<div className="flex flex-col justify-center items-center m-auto max-w-2xl px-6">
<div className="m-auto mb-12 flex flex max-w-2xl w-full flex-col justify-center rounded-md border border-primary-500/30 bg-primary/5 p-6 pt-5"> <div className="m-auto max-w-xl px-4">
<p className="pb-2 font-semibold text-mineshaft-100 md:pb-3 text-lg md:text-xl w-full"> {!isNewSession && (
Open source <span className="bg-clip-text text-transparent bg-gradient-to-tr from-yellow-500 to-primary-500">secret management</span> for developers <div className="flex flex-1 flex-col items-center justify-center px-4 pb-4">
<Button
className="bg-mineshaft-700 text-bunker-200"
colorSchema="primary"
variant="outline_bg"
size="sm"
onClick={() => {
handlePopUpOpen("createSharedSecret");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Share your own Secret
</Button>
</div>
)}
<div className="m-auto mb-8 flex flex max-w-xl flex-col justify-center gap-2 rounded-md border border-primary-500/30 bg-primary/5 p-6">
<p className="pb-2 font-semibold text-mineshaft-100 md:pb-4 md:text-xl">
Safe, Secure, & Open Source
</p> </p>
<div className="flex flex-col sm:flex-row gap-x-4"> <p className="md:text-md text-sm">
<p className="md:text-md text-md"> Infisical is the #1 {" "}
<a <a
href="https://github.com/infisical/infisical" href="https://github.com/infisical/infisical"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="bg-clip-text text-transparent bg-gradient-to-tr from-yellow-500 to-primary-500 text-bold" className="text-primary underline"
> >
Infisical open source
</a>{" "} is the all-in-one secret management platform to securely manage secrets, configs, and certificates across your team and infrastructure. </a>{" "}
secrets management platform for developers. <br className="hidden md:inline" />
<div className="pb-2" />
Infisical Secret Sharing uses end-to-end encrypted architecture to ensure that your secrets are truly private, even from our servers.
</p> </p>
<Link href="https://infisical.com"> <Link href="https://infisical.com">
<span className="mt-4 border border-mineshaft-400/40 w-[17.5rem] h-min py-2 px-3 rounded-md bg-mineshaft-600 cursor-pointer duration-200 hover:text-white hover:border-primary/60 hover:bg-primary/20"> <span className="mt-4 cursor-pointer duration-200 hover:text-primary">
Try Infisical <FontAwesomeIcon icon={faArrowRight} className="pl-1"/> Learn More <FontAwesomeIcon icon={faArrowRight} />
</span> </span>
</Link> </Link>
</div> </div>
</div> </div>
</div> <div className="bottom-0 flex w-full items-center justify-center bg-mineshaft-600 p-2 sm:absolute">
<AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} isPublic inModal />
</div>
</div>
<div className="mt-auto flex w-full items-center justify-center bg-mineshaft-600 p-2">
<p className="text-center text-sm text-mineshaft-300"> <p className="text-center text-sm text-mineshaft-300">
© 2024{" "} © 2024{" "}
<a className="text-primary" href="https://infisical.com"> <a className="text-primary" href="https://infisical.com">
@ -163,6 +154,8 @@ export const ShareSecretPublicPage = ({ isNewSession }: { isNewSession: boolean
156 2nd st, 3rd Floor, San Francisco, California, 94105, United States. 🇺🇸 156 2nd st, 3rd Floor, San Francisco, California, 94105, United States. 🇺🇸
</p> </p>
</div> </div>
<AddShareSecretModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} isPublic inModal />
</div>
</div> </div>
); );
}; };

View File

@ -16,7 +16,7 @@ export const SecretTable = ({
isUrlCopied, isUrlCopied,
copyUrlToClipboard copyUrlToClipboard
}: Props) => ( }: Props) => (
<div className="flex w-full items-center justify-center rounded-md border border-solid border-mineshaft-700 bg-mineshaft-800 p-2"> <div className="flex w-full items-center justify-center rounded border border-solid border-mineshaft-700 bg-mineshaft-800 p-2">
{isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>} {isLoading && <div className="bg-mineshaft-800 text-center text-bunker-400">Loading...</div>}
{!isLoading && !decryptedSecret && ( {!isLoading && !decryptedSecret && (
<Tr> <Tr>
@ -37,7 +37,7 @@ export const SecretTable = ({
colorSchema="primary" colorSchema="primary"
ariaLabel="copy to clipboard" ariaLabel="copy to clipboard"
onClick={copyUrlToClipboard} onClick={copyUrlToClipboard}
className="mx-1 flex max-h-8 items-center rounded absolute top-1 sm:top-2 right-0 sm:right-5" className="mx-1 flex max-h-8 items-center rounded"
size="xs" size="xs"
> >
<FontAwesomeIcon className="pr-2" icon={isUrlCopied ? faCheck : faCopy} /> Copy <FontAwesomeIcon className="pr-2" icon={isUrlCopied ? faCheck : faCopy} /> Copy