Compare commits

..

23 Commits

Author SHA1 Message Date
0ec56c9928 docs: add podman compose docs 2025-07-10 18:57:25 +04:00
35520cfe99 Merge pull request #3989 from Infisical/add-access-token-index
add index for referencing columns in identity access token
2025-07-10 09:48:39 -04:00
Sid
ba0f6e60e2 fix: yaml secret file parsing (#3837) 2025-07-10 15:33:59 +05:30
579c68b2a3 Merge pull request #3991 from Infisical/helm-update-v0.9.4
Update Helm chart to version v0.9.4
2025-07-10 14:03:10 +04:00
f4ea3e1c75 Update Helm chart to version v0.9.4 2025-07-10 10:02:02 +00:00
7d37ea318f Merge pull request #3990 from Infisical/daniel/operator-logs
fix: add request ID to error logs
2025-07-10 13:57:44 +04:00
5cb7ecc354 fix: update go sdk 2025-07-10 13:35:59 +04:00
5e85de3937 fix lint and short index name 2025-07-09 23:36:55 -04:00
8719e3e75e add index for referencing columns in identity access token
This PR will address issue with very long identity deletions due to a sequential scan over ALL identity access rows during CASCADE
2025-07-09 23:19:01 -04:00
69ece1f3e3 Merge pull request #3986 from Infisical/update-email-reinvite-job
Add jitter and increase window to 12 m
2025-07-09 22:03:02 -04:00
d5cd6f79f9 Merge branch 'main' into update-email-reinvite-job 2025-07-09 19:57:15 -04:00
19c0731166 Add jitter and increase window to 12 m 2025-07-09 19:54:35 -04:00
f636cc678b Merge pull request #3985 from Infisical/move-migration-logger-init-to-knexfile
fix(migration): move logger init for standalone migration to entry knexfile
2025-07-09 19:16:31 -04:00
ff8ad14e1b fix: move logger init for standalone migration to entry knexfile 2025-07-09 16:14:11 -07:00
d683d3adb3 Merge pull request #3984 from Infisical/ENG-3149
Dockerfile for mintlify docs
2025-07-09 17:32:02 -04:00
d9b8cd1204 Utilize cache 2025-07-09 17:28:10 -04:00
27b5e2aa68 Dockerfile for mintlify docs 2025-07-09 17:20:26 -04:00
692121445d Merge pull request #3862 from vespersio/patch-1
 PR: fix infisical-schema-migration CrashLoopBackOff when upgrading to 0.133.0 #3849
2025-07-09 16:38:01 +08:00
Sid
9a940dce64 fix: support email link template pre-fill (#3979)
* fix: support email link template pre-fill

* fix: remove support dropdown from personal settings

* fix: update support template

---------

Co-authored-by: sidwebworks <xodeveloper@gmail.com>
2025-07-08 22:15:55 +05:30
7e523546b3 Merge pull request #3981 from Infisical/fix-integrations-audit-log-type
fix(typo): add missing space on integrations audit log upgrade prompt
2025-07-08 08:56:19 -07:00
814d6e2709 fix: add missing space on integrations audit log upgrade prompt 2025-07-08 08:48:14 -07:00
ba57899a56 Update 20250602155451_fix-secret-versions.ts 2025-07-02 00:50:33 +04:00
aef3a7436f fix 20250602155451_fix-secret-versions.ts
fix infisical-schema-migration CrashLoopBackOff when upgrading to 0.133.0 #3849
2025-06-26 13:48:41 +03:00
17 changed files with 414 additions and 168 deletions

View File

@ -4,6 +4,7 @@ import "ts-node/register";
import dotenv from "dotenv"; import dotenv from "dotenv";
import type { Knex } from "knex"; import type { Knex } from "knex";
import path from "path"; import path from "path";
import { initLogger } from "@app/lib/logger";
// Update with your config settings. . // Update with your config settings. .
dotenv.config({ dotenv.config({
@ -13,6 +14,8 @@ dotenv.config({
path: path.join(__dirname, "../../../.env") path: path.join(__dirname, "../../../.env")
}); });
initLogger();
export default { export default {
development: { development: {
client: "postgres", client: "postgres",

View File

@ -0,0 +1,48 @@
import { Knex } from "knex";
import { TableName } from "../schemas";
const MIGRATION_TIMEOUT = 30 * 60 * 1000; // 30 minutes
export async function up(knex: Knex): Promise<void> {
const result = await knex.raw("SHOW statement_timeout");
const originalTimeout = result.rows[0].statement_timeout;
try {
await knex.raw(`SET statement_timeout = ${MIGRATION_TIMEOUT}`);
// iat means IdentityAccessToken
await knex.raw(`
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_iat_identity_id
ON ${TableName.IdentityAccessToken} ("identityId")
`);
await knex.raw(`
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_iat_ua_client_secret_id
ON ${TableName.IdentityAccessToken} ("identityUAClientSecretId")
`);
} finally {
await knex.raw(`SET statement_timeout = '${originalTimeout}'`);
}
}
export async function down(knex: Knex): Promise<void> {
const result = await knex.raw("SHOW statement_timeout");
const originalTimeout = result.rows[0].statement_timeout;
try {
await knex.raw(`SET statement_timeout = ${MIGRATION_TIMEOUT}`);
await knex.raw(`
DROP INDEX IF EXISTS idx_iat_identity_id
`);
await knex.raw(`
DROP INDEX IF EXISTS idx_iat_ua_client_secret_id
`);
} finally {
await knex.raw(`SET statement_timeout = '${originalTimeout}'`);
}
}
export const config = { transaction: false };

View File

@ -108,16 +108,16 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
const now = new Date(); const now = new Date();
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
const oneMonthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); const oneMonthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
const threeMonthsAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000); const twelveMonthsAgo = new Date(now.getTime() - 360 * 24 * 60 * 60 * 1000);
const memberships = await db const memberships = await db
.replicaNode()(TableName.OrgMembership) .replicaNode()(TableName.OrgMembership)
.where("status", "invited") .where("status", "invited")
.where((qb) => { .where((qb) => {
// lastInvitedAt is null AND createdAt is between 1 week and 3 months ago // lastInvitedAt is null AND createdAt is between 1 week and 12 months ago
void qb void qb
.whereNull(`${TableName.OrgMembership}.lastInvitedAt`) .whereNull(`${TableName.OrgMembership}.lastInvitedAt`)
.whereBetween(`${TableName.OrgMembership}.createdAt`, [threeMonthsAgo, oneWeekAgo]); .whereBetween(`${TableName.OrgMembership}.createdAt`, [twelveMonthsAgo, oneWeekAgo]);
}) })
.orWhere((qb) => { .orWhere((qb) => {
// lastInvitedAt is older than 1 week ago AND createdAt is younger than 1 month ago // lastInvitedAt is older than 1 week ago AND createdAt is younger than 1 month ago

View File

@ -36,6 +36,8 @@ import { getConfig } from "@app/lib/config/env";
import { generateAsymmetricKeyPair } from "@app/lib/crypto"; import { generateAsymmetricKeyPair } from "@app/lib/crypto";
import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { generateSymmetricKey, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption";
import { generateUserSrpKeys } from "@app/lib/crypto/srp"; import { generateUserSrpKeys } from "@app/lib/crypto/srp";
import { applyJitter } from "@app/lib/dates";
import { delay as delayMs } from "@app/lib/delay";
import { import {
BadRequestError, BadRequestError,
ForbiddenRequestError, ForbiddenRequestError,
@ -44,9 +46,10 @@ import {
UnauthorizedError UnauthorizedError
} from "@app/lib/errors"; } from "@app/lib/errors";
import { groupBy } from "@app/lib/fn"; import { groupBy } from "@app/lib/fn";
import { logger } from "@app/lib/logger";
import { alphaNumericNanoId } from "@app/lib/nanoid"; import { alphaNumericNanoId } from "@app/lib/nanoid";
import { isDisposableEmail } from "@app/lib/validator"; import { isDisposableEmail } from "@app/lib/validator";
import { TQueueServiceFactory } from "@app/queue"; import { QueueName, TQueueServiceFactory } from "@app/queue";
import { getDefaultOrgMembershipRoleForUpdateOrg } from "@app/services/org/org-role-fns"; import { getDefaultOrgMembershipRoleForUpdateOrg } from "@app/services/org/org-role-fns";
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal"; import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal"; import { TUserAliasDALFactory } from "@app/services/user-alias/user-alias-dal";
@ -1438,6 +1441,8 @@ export const orgServiceFactory = ({
* Re-send emails to users who haven't accepted an invite yet * Re-send emails to users who haven't accepted an invite yet
*/ */
const notifyInvitedUsers = async () => { const notifyInvitedUsers = async () => {
logger.info(`${QueueName.DailyResourceCleanUp}: notify invited users started`);
const invitedUsers = await orgMembershipDAL.findRecentInvitedMemberships(); const invitedUsers = await orgMembershipDAL.findRecentInvitedMemberships();
const appCfg = getConfig(); const appCfg = getConfig();
@ -1461,24 +1466,32 @@ export const orgServiceFactory = ({
}); });
if (invitedUser.inviteEmail) { if (invitedUser.inviteEmail) {
await smtpService.sendMail({ await delayMs(Math.max(0, applyJitter(0, 2000)));
template: SmtpTemplates.OrgInvite,
subjectLine: `Reminder: You have been invited to ${org.name} on Infisical`, try {
recipients: [invitedUser.inviteEmail], await smtpService.sendMail({
substitutions: { template: SmtpTemplates.OrgInvite,
organizationName: org.name, subjectLine: `Reminder: You have been invited to ${org.name} on Infisical`,
email: invitedUser.inviteEmail, recipients: [invitedUser.inviteEmail],
organizationId: org.id.toString(), substitutions: {
token, organizationName: org.name,
callback_url: `${appCfg.SITE_URL}/signupinvite` email: invitedUser.inviteEmail,
} organizationId: org.id.toString(),
}); token,
notifiedUsers.push(invitedUser.id); callback_url: `${appCfg.SITE_URL}/signupinvite`
}
});
notifiedUsers.push(invitedUser.id);
} catch (err) {
logger.error(err, `${QueueName.DailyResourceCleanUp}: notify invited users failed to send email`);
}
} }
}) })
); );
await orgMembershipDAL.updateLastInvitedAtByIds(notifiedUsers); await orgMembershipDAL.updateLastInvitedAtByIds(notifiedUsers);
logger.info(`${QueueName.DailyResourceCleanUp}: notify invited users completed`);
}; };
return { return {

6
docs/Dockerfile Normal file
View File

@ -0,0 +1,6 @@
FROM node:20-alpine
WORKDIR /app
RUN npm install -g mint
COPY . .
EXPOSE 3000
CMD ["mint", "dev"]

View File

@ -4,17 +4,20 @@ description: "Read how to run Infisical with Docker Compose template."
--- ---
This self-hosting guide will walk you through the steps to self-host Infisical using Docker Compose. This self-hosting guide will walk you through the steps to self-host Infisical using Docker Compose.
## Prerequisites
- [Docker](https://docs.docker.com/engine/install/)
- [Docker compose](https://docs.docker.com/compose/install/)
<Warning> <Tabs>
This Docker Compose configuration is not designed for high-availability production scenarios. <Tab title="Docker Compose">
It includes just the essential components needed to set up an Infisical proof of concept (POC). ## Prerequisites
To run Infisical in a highly available manner, give the [Docker Swarm guide](/self-hosting/deployment-options/docker-swarm). - [Docker](https://docs.docker.com/engine/install/)
</Warning> - [Docker compose](https://docs.docker.com/compose/install/)
## Verify prerequisites <Warning>
This Docker Compose configuration is not designed for high-availability production scenarios.
It includes just the essential components needed to set up an Infisical proof of concept (POC).
To run Infisical in a highly available manner, give the [Docker Swarm guide](/self-hosting/deployment-options/docker-swarm).
</Warning>
## Verify prerequisites
To verify that Docker compose and Docker are installed on the machine where you plan to install Infisical, run the following commands. To verify that Docker compose and Docker are installed on the machine where you plan to install Infisical, run the following commands.
Check for docker installation Check for docker installation
@ -27,55 +30,145 @@ To run Infisical in a highly available manner, give the [Docker Swarm guide](/se
docker-compose docker-compose
``` ```
## Download docker compose file ## Download docker compose file
You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`. You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`.
If your system doesn't have either of these, you can use a equivalent command that works with your machine. If your system doesn't have either of these, you can use a equivalent command that works with your machine.
<Tabs>
<Tab title="curl">
```bash
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
<Tab title="wget">
```bash
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
</Tabs>
## Configure instance credentials
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
The default credentials can be downloaded using the one of the commands listed below.
<Tabs>
<Tab title="curl">
```bash
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
<Tab title="wget">
```bash
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
</Tabs>
Once downloaded, the credentials file will be saved to your working directly as `.env` file.
View all available configurations [here](/self-hosting/configuration/envars).
<Warning>
The default .env file contains credentials that are intended solely for testing purposes.
Please generate a new `ENCRYPTION_KEY` and `AUTH_SECRET` for use outside of testing.
Instructions to do so, can be found [here](/self-hosting/configuration/envars).
</Warning>
## Start Infisical
Run the command below to start Infisical and all related services.
<Tabs>
<Tab title="curl">
```bash ```bash
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml docker-compose -f docker-compose.prod.yml up
``` ```
</Tab> </Tab>
<Tab title="wget"> <Tab title="Podman Compose">
```bash Podman Compose is an alternative way to run Infisical using Podman as a replacement for Docker. Podman is backwards compatible with Docker Compose files.
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
## Prerequisites
- [Podman](https://podman-desktop.io/docs/installation)
- [Podman Compose](https://podman-desktop.io/docs/compose)
<Warning>
This Docker Compose configuration is not designed for high-availability production scenarios.
It includes just the essential components needed to set up an Infisical proof of concept (POC).
To run Infisical in a highly available manner, give the [Docker Swarm guide](/self-hosting/deployment-options/docker-swarm).
</Warning>
## Verify prerequisites
To verify that Podman compose and Podman are installed on the machine where you plan to install Infisical, run the following commands.
Check for podman installation
```bash
podman version
```
Check for podman compose installation
```bash
podman-compose version
```
## Download Docker Compose file
You can obtain the Infisical docker compose file by using a command-line downloader such as `wget` or `curl`.
If your system doesn't have either of these, you can use a equivalent command that works with your machine.
<Tabs>
<Tab title="curl">
```bash
curl -o docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
<Tab title="wget">
```bash
wget -O docker-compose.prod.yml https://raw.githubusercontent.com/Infisical/infisical/main/docker-compose.prod.yml
```
</Tab>
</Tabs>
## Configure instance credentials
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
The default credentials can be downloaded using the one of the commands listed below.
<Tabs>
<Tab title="curl">
```bash
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
<Tab title="wget">
```bash
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
</Tabs>
<Note>
Make sure to rename the `.env.example` file to `.env` before starting Infisical. Additionally it's important that the `.env` file is in the same directory as the `docker-compose.prod.yml` file.
</Note>
## Setup Podman
Run the commands below to setup Podman for first time use.
```bash
podman machine init --now
podman machine set --rootful
podman machine start
```
<Note>
If you are using a rootless podman installation, you can skip the `podman machine set --rootful` command.
</Note>
## Start Infisical
Run the command below to start Infisical and all related services.
```bash
podman-compose -f docker-compose.prod.yml up
``` ```
</Tab> </Tab>
</Tabs> </Tabs>
## Configure instance credentials
Infisical requires a set of credentials used for connecting to dependent services such as Postgres, Redis, etc.
The default credentials can be downloaded using the one of the commands listed below.
<Tabs>
<Tab title="curl">
```bash
curl -o .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
<Tab title="wget">
```bash
wget -O .env https://raw.githubusercontent.com/Infisical/infisical/main/.env.example
```
</Tab>
</Tabs>
Once downloaded, the credentials file will be saved to your working directly as `.env` file.
View all available configurations [here](/self-hosting/configuration/envars).
<Warning>
The default .env file contains credentials that are intended solely for testing purposes.
Please generate a new `ENCRYPTION_KEY` and `AUTH_SECRET` for use outside of testing.
Instructions to do so, can be found [here](/self-hosting/configuration/envars).
</Warning>
## Start Infisical
Run the command below to start Infisical and all related services.
```bash
docker-compose -f docker-compose.prod.yml up
```
Your Infisical instance should now be running on port `80`. To access your instance, visit `http://localhost:80`. Your Infisical instance should now be running on port `80`. To access your instance, visit `http://localhost:80`.

View File

@ -77,3 +77,91 @@ export const parseJson = (src: ArrayBuffer | string) => {
}); });
return env; return env;
}; };
/**
* Parses simple flat YAML with support for multiline strings using |, |-, and >.
* @param {ArrayBuffer | string} src
* @returns {Record<string, { value: string, comments: string[] }>}
*/
export function parseYaml(src: ArrayBuffer | string) {
const result: Record<string, { value: string; comments: string[] }> = {};
const content = src.toString().replace(/\r\n?/g, "\n");
const lines = content.split("\n");
let i = 0;
let comments: string[] = [];
while (i < lines.length) {
const line = lines[i].trim();
// Collect comment
if (line.startsWith("#")) {
comments.push(line.slice(1).trim());
i += 1; // move to next line
} else {
// Match key: value or key: |, key: >, etc.
const keyMatch = lines[i].match(/^([\w.-]+)\s*:\s*(.*)$/);
if (keyMatch) {
const [, key, rawValue] = keyMatch;
let value = rawValue.trim();
// Multiline string handling
if (value === "|-" || value === "|" || value === ">") {
const isFolded = value === ">";
const baseIndent = lines[i + 1]?.match(/^(\s*)/)?.[1]?.length ?? 0;
const collectedLines: string[] = [];
i += 1; // move to first content line
while (i < lines.length) {
const current = lines[i];
const currentIndent = current.match(/^(\s*)/)?.[1]?.length ?? 0;
if (current.trim() === "" || currentIndent >= baseIndent) {
collectedLines.push(current.slice(baseIndent));
i += 1; // move to next line
} else {
break;
}
}
if (isFolded) {
// Join lines with space for `>` folded style
value = collectedLines.map((l) => l.trim()).join(" ");
} else {
// Keep lines with newlines for `|` and `|-`
value = collectedLines.join("\n");
}
} else {
// Inline value — strip quotes and inline comment
const commentIndex = value.indexOf(" #");
if (commentIndex !== -1) {
value = value.slice(0, commentIndex).trim();
}
// Remove surrounding quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
i += 1; // advance to next line
}
result[key] = {
value,
comments: [...comments]
};
comments = []; // reset
} else {
i += 1; // skip unknown line
}
}
}
return result;
}

View File

@ -54,33 +54,55 @@ const getPlan = (subscription: SubscriptionPlan) => {
return "Free"; return "Free";
}; };
const getFormattedSupportEmailLink = (variables: { org_id: string; domain: string }) => {
const email = "support@infisical.com";
const body = `Hello Infisical Support Team,
Issue Details:
[What you did]
[What you expected to happen]
[What actually happened]
[Any error request IDs]
[Any supporting screenshots or video recording of the issue/request at hand]
Account Info:
- Organization ID: ${variables.org_id}
- Domain: ${variables.domain}
Thank you,
[Your Name]`;
return `mailto:${email}?body=${encodeURIComponent(body)}`;
};
export const INFISICAL_SUPPORT_OPTIONS = [ export const INFISICAL_SUPPORT_OPTIONS = [
[ [
<FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />, <FontAwesomeIcon key={1} className="pr-4 text-sm" icon={faSlack} />,
"Support Forum", "Support Forum",
"https://infisical.com/slack" () => "https://infisical.com/slack"
], ],
[ [
<FontAwesomeIcon key={2} className="pr-4 text-sm" icon={faBook} />, <FontAwesomeIcon key={2} className="pr-4 text-sm" icon={faBook} />,
"Read Docs", "Read Docs",
"https://infisical.com/docs/documentation/getting-started/introduction" () => "https://infisical.com/docs/documentation/getting-started/introduction"
], ],
[ [
<FontAwesomeIcon key={3} className="pr-4 text-sm" icon={faGithub} />, <FontAwesomeIcon key={3} className="pr-4 text-sm" icon={faGithub} />,
"GitHub Issues", "GitHub Issues",
"https://github.com/Infisical/infisical/issues" () => "https://github.com/Infisical/infisical/issues"
], ],
[ [
<FontAwesomeIcon key={4} className="pr-4 text-sm" icon={faEnvelope} />, <FontAwesomeIcon key={4} className="pr-4 text-sm" icon={faEnvelope} />,
"Email Support", "Email Support",
"mailto:support@infisical.com" getFormattedSupportEmailLink
], ],
[ [
<FontAwesomeIcon key={5} className="pr-4 text-sm" icon={faUsers} />, <FontAwesomeIcon key={5} className="pr-4 text-sm" icon={faUsers} />,
"Instance Admins", "Instance Admins",
"server-admins" () => "server-admins"
] ]
]; ] as const;
export const Navbar = () => { export const Navbar = () => {
const { user } = useUser(); const { user } = useUser();
@ -258,7 +280,15 @@ export const Navbar = () => {
</div> </div>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" side="bottom" className="mt-3 p-1"> <DropdownMenuContent align="end" side="bottom" className="mt-3 p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => { {INFISICAL_SUPPORT_OPTIONS.map(([icon, text, getUrl]) => {
const url =
text === "Email Support"
? getUrl({
org_id: currentOrg.id,
domain: window.location.origin
})
: getUrl();
if (url === "server-admins" && isInfisicalCloud()) { if (url === "server-admins" && isInfisicalCloud()) {
return null; return null;
} }

View File

@ -1,19 +1,11 @@
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { faArrowLeft, faInfo, faMobile, faQuestion } from "@fortawesome/free-solid-svg-icons"; import { faArrowLeft, faMobile } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, Outlet } from "@tanstack/react-router"; import { Link, Outlet } from "@tanstack/react-router";
import { WishForm } from "@app/components/features/WishForm"; import { WishForm } from "@app/components/features/WishForm";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger
} from "@app/components/v2";
import { envConfig } from "@app/config/env";
import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner"; import { InsecureConnectionBanner } from "../OrganizationLayout/components/InsecureConnectionBanner";
import { INFISICAL_SUPPORT_OPTIONS } from "../OrganizationLayout/components/NavBar/Navbar";
export const PersonalSettingsLayout = () => { export const PersonalSettingsLayout = () => {
const { t } = useTranslation(); const { t } = useTranslation();
@ -36,37 +28,6 @@ export const PersonalSettingsLayout = () => {
<div className="relative mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400"> <div className="relative mt-10 flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400">
{(window.location.origin.includes("https://app.infisical.com") || {(window.location.origin.includes("https://app.infisical.com") ||
window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />} window.location.origin.includes("https://gamma.infisical.com")) && <WishForm />}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<div className="mb-2 w-full pl-5 duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faQuestion} className="mr-3 px-[0.1rem]" />
Help & Support
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
{INFISICAL_SUPPORT_OPTIONS.map(([icon, text, url]) => (
<DropdownMenuItem key={url as string}>
<a
target="_blank"
rel="noopener noreferrer"
href={String(url)}
className="flex w-full items-center rounded-md font-normal text-mineshaft-300 duration-200"
>
<div className="relative flex w-full cursor-pointer select-none items-center justify-start rounded-md">
{icon}
<div className="text-sm">{text}</div>
</div>
</a>
</DropdownMenuItem>
))}
{envConfig.PLATFORM_VERSION && (
<div className="mb-2 mt-2 w-full cursor-default pl-5 text-sm duration-200 hover:text-mineshaft-200">
<FontAwesomeIcon icon={faInfo} className="mr-4 px-[0.1rem]" />
Version: {envConfig.PLATFORM_VERSION}
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
</div> </div>
) )
</nav> </nav>

View File

@ -1,5 +1,3 @@
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { Button, Tooltip } from "@app/components/v2"; import { Button, Tooltip } from "@app/components/v2";
@ -10,41 +8,25 @@ type Props = {
label: string; label: string;
onClear: () => void; onClear: () => void;
children: React.ReactNode; children: React.ReactNode;
tooltipText?: string;
}; };
export const LogFilterItem = ({ export const LogFilterItem = ({ label, onClear, hoverTooltip, children, className }: Props) => {
label,
onClear,
hoverTooltip,
children,
className,
tooltipText
}: Props) => {
return ( return (
<div className={twMerge("flex flex-col justify-between", className)}> <Tooltip className="relative top-4" content={hoverTooltip} isDisabled={!hoverTooltip}>
<div className="flex items-center pr-1"> <div className={twMerge("flex flex-col justify-between", className)}>
<p className="text-xs opacity-60">{label}</p> <div className="flex items-center justify-between pr-1">
{tooltipText && ( <p className="text-xs opacity-60">{label}</p>
<Tooltip content={tooltipText} className="max-w-sm"> <Button
<FontAwesomeIcon onClick={() => onClear()}
icon={faInfoCircle} variant="link"
className="-mt-[0.05rem] ml-1 text-[11px] text-mineshaft-400" className="font-normal text-mineshaft-400 transition-all duration-75 hover:text-mineshaft-300"
/> size="xs"
</Tooltip> >
)} Clear
<Button </Button>
onClick={() => onClear()} </div>
variant="link" {children}
className="ml-auto font-normal text-mineshaft-400 transition-all duration-75 hover:text-mineshaft-300"
size="xs"
>
Clear
</Button>
</div> </div>
<Tooltip className="relative top-4" content={hoverTooltip} isDisabled={!hoverTooltip}> </Tooltip>
<div>{children}</div>
</Tooltip>
</div>
); );
}; };

View File

@ -366,7 +366,6 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
</LogFilterItem> </LogFilterItem>
<LogFilterItem <LogFilterItem
label="Secret Path" label="Secret Path"
tooltipText="Enter the exact secret path (wildcards like * are not supported)"
hoverTooltip={ hoverTooltip={
!selectedProject !selectedProject
? "Select a project before filtering by secret path." ? "Select a project before filtering by secret path."
@ -381,7 +380,10 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
control={control} control={control}
name="secretPath" name="secretPath"
render={({ field: { onChange, value, ...field } }) => ( render={({ field: { onChange, value, ...field } }) => (
<FormControl className="w-full"> <FormControl
tooltipText="Filter audit logs related to events that occurred on a specific secret path."
className="w-full"
>
<Input <Input
placeholder="Enter secret path" placeholder="Enter secret path"
className="disabled:cursor-not-allowed" className="disabled:cursor-not-allowed"
@ -401,7 +403,6 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
? "Select a project before filtering by secret key." ? "Select a project before filtering by secret key."
: undefined : undefined
} }
tooltipText="Enter the exact secret key name (wildcards like * are not supported)"
className={twMerge(!selectedProject && "opacity-50")} className={twMerge(!selectedProject && "opacity-50")}
label="Secret Key" label="Secret Key"
onClear={() => { onClear={() => {
@ -412,7 +413,10 @@ export const LogsFilter = ({ presets, setFilter, filter }: Props) => {
control={control} control={control}
name="secretKey" name="secretKey"
render={({ field: { onChange, value, ...field } }) => ( render={({ field: { onChange, value, ...field } }) => (
<FormControl className="w-full"> <FormControl
tooltipText="Filter audit logs related to a specific secret."
className="w-full"
>
<Input <Input
isDisabled={!selectedProject} isDisabled={!selectedProject}
{...field} {...field}

View File

@ -70,7 +70,7 @@ export const IntegrationAuditLogsSection = ({ integration }: Props) => {
upgrade your subscription upgrade your subscription
</a> </a>
</a> </a>
)} )}{" "}
to view integration logs to view integration logs
</p> </p>
</div> </div>

View File

@ -9,7 +9,7 @@ import { twMerge } from "tailwind-merge";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
// TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality // TODO:(akhilmhdh) convert all the util functions like this into a lib folder grouped by functionality
import { parseDotEnv, parseJson } from "@app/components/utilities/parseSecrets"; import { parseDotEnv, parseJson, parseYaml } from "@app/components/utilities/parseSecrets";
import { Button, Lottie, Modal, ModalContent } from "@app/components/v2"; import { Button, Lottie, Modal, ModalContent } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
import { usePopUp, useToggle } from "@app/hooks"; import { usePopUp, useToggle } from "@app/hooks";
@ -127,7 +127,7 @@ export const SecretDropzone = ({
} }
}; };
const parseFile = (file?: File, isJson?: boolean) => { const parseFile = (file?: File) => {
const reader = new FileReader(); const reader = new FileReader();
if (!file) { if (!file) {
createNotification({ createNotification({
@ -140,10 +140,25 @@ export const SecretDropzone = ({
setIsLoading.on(); setIsLoading.on();
reader.onload = (event) => { reader.onload = (event) => {
if (!event?.target?.result) return; if (!event?.target?.result) return;
// parse function's argument looks like to be ArrayBuffer
const env = isJson let env: TParsedEnv;
? parseJson(event.target.result as ArrayBuffer)
: parseDotEnv(event.target.result as ArrayBuffer); const src = event.target.result as ArrayBuffer;
switch (file.type) {
case "application/json":
env = parseJson(src);
break;
case "text/yaml":
case "application/x-yaml":
case "application/yaml":
env = parseYaml(src);
break;
default:
env = parseDotEnv(src);
break;
}
setIsLoading.off(); setIsLoading.off();
handleParsedEnv(env); handleParsedEnv(env);
}; };
@ -165,12 +180,12 @@ export const SecretDropzone = ({
e.dataTransfer.dropEffect = "copy"; e.dataTransfer.dropEffect = "copy";
setDragActive.off(); setDragActive.off();
parseFile(e.dataTransfer.files[0], e.dataTransfer.files[0].type === "application/json"); parseFile(e.dataTransfer.files[0]);
}; };
const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => { const handleFileUpload = (e: ChangeEvent<HTMLInputElement>) => {
e.preventDefault(); e.preventDefault();
parseFile(e.target?.files?.[0], e.target?.files?.[0]?.type === "application/json"); parseFile(e.target?.files?.[0]);
}; };
const handleSaveSecrets = async () => { const handleSaveSecrets = async () => {

View File

@ -13,9 +13,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes # This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version. # to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/) # Versions are expected to follow Semantic Versioning (https://semver.org/)
version: v0.9.3 version: v0.9.4
# This is the version number of the application being deployed. This version number should be # This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to # incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using. # follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes. # It is recommended to use it with quotes.
appVersion: "v0.9.3" appVersion: "v0.9.4"

View File

@ -32,7 +32,7 @@ controllerManager:
- ALL - ALL
image: image:
repository: infisical/kubernetes-operator repository: infisical/kubernetes-operator
tag: v0.9.3 tag: v0.9.4
resources: resources:
limits: limits:
cpu: 500m cpu: 500m

View File

@ -5,7 +5,7 @@ go 1.21
require ( require (
github.com/Masterminds/sprig/v3 v3.3.0 github.com/Masterminds/sprig/v3 v3.3.0
github.com/aws/smithy-go v1.20.3 github.com/aws/smithy-go v1.20.3
github.com/infisical/go-sdk v0.4.4 github.com/infisical/go-sdk v0.5.97
github.com/lestrrat-go/jwx/v2 v2.1.4 github.com/lestrrat-go/jwx/v2 v2.1.4
github.com/onsi/ginkgo/v2 v2.6.0 github.com/onsi/ginkgo/v2 v2.6.0
github.com/onsi/gomega v1.24.1 github.com/onsi/gomega v1.24.1
@ -43,6 +43,7 @@ require (
github.com/google/s2a-go v0.1.7 // indirect github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.5 // indirect github.com/googleapis/gax-go/v2 v2.12.5 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/huandu/xstrings v1.5.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
@ -105,7 +106,7 @@ require (
go.uber.org/multierr v1.6.0 // indirect go.uber.org/multierr v1.6.0 // indirect
go.uber.org/zap v1.24.0 // indirect go.uber.org/zap v1.24.0 // indirect
golang.org/x/crypto v0.32.0 golang.org/x/crypto v0.32.0
golang.org/x/net v0.27.0 // indirect golang.org/x/net v0.33.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sys v0.29.0 // indirect golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect golang.org/x/term v0.28.0 // indirect

View File

@ -228,13 +228,15 @@ github.com/googleapis/gax-go/v2 v2.12.5 h1:8gw9KZK8TiVKB6q3zHY3SBzLnrGp6HQjyfYBY
github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E= github.com/googleapis/gax-go/v2 v2.12.5/go.mod h1:BUDKcWo+RaKq5SC9vVYL0wLADa3VcfswbOMMRmB9H3E=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI=
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
github.com/infisical/go-sdk v0.4.4 h1:Z4CBzxfhiY6ikjRimOEeyEEnb3QT/BKw3OzNFH7Pe+U= github.com/infisical/go-sdk v0.5.97 h1:veOi6Hduda6emtwjdUI5SBg2qd2iDQc5xLKqZ15KSoM=
github.com/infisical/go-sdk v0.4.4/go.mod h1:6fWzAwTPIoKU49mQ2Oxu+aFnJu9n7k2JcNrZjzhHM2M= github.com/infisical/go-sdk v0.5.97/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
@ -479,8 +481,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=