mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-13 02:29:26 +00:00
Merge remote-tracking branch 'origin/main' into feature/secrets-detection-in-secrets-manager
This commit is contained in:
@@ -1,26 +1,14 @@
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { BoundCanProps, Can } from "@casl/react";
|
||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { TOrgPermission, useOrgPermission } from "@app/context/OrgPermissionContext";
|
||||
|
||||
import { Tooltip } from "../v2";
|
||||
import { AccessRestrictedBanner, Tooltip } from "../v2";
|
||||
|
||||
export const OrgPermissionGuardBanner = () => {
|
||||
return (
|
||||
<div className="container mx-auto flex h-full items-center justify-center">
|
||||
<div className="flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
|
||||
<div className="text-sm">
|
||||
Your role has limited permissions, please <br /> contact your admin to gain access
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AccessRestrictedBanner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { ReactNode } from "react";
|
||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { AccessRestrictedBanner } from "@app/components/v2";
|
||||
|
||||
type Props = {
|
||||
containerClassName?: string;
|
||||
className?: string;
|
||||
children?: ReactNode;
|
||||
};
|
||||
|
||||
export const PermissionDeniedBanner = ({ containerClassName, className, children }: Props) => {
|
||||
export const PermissionDeniedBanner = ({ containerClassName }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
@@ -17,22 +14,7 @@ export const PermissionDeniedBanner = ({ containerClassName, className, children
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div className={twMerge("rounded-md bg-mineshaft-800 p-16 text-bunker-300", className)}>
|
||||
<div className="flex items-end space-x-12">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
|
||||
{children || (
|
||||
<div className="text-sm">
|
||||
Your role has limited permissions, please <br /> contact your administrator to gain
|
||||
access
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AccessRestrictedBanner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
import { AbilityTuple, MongoAbility } from "@casl/ability";
|
||||
import { Can } from "@casl/react";
|
||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { AccessRestrictedBanner } from "@app/components/v2";
|
||||
import { ProjectPermissionSet, useProjectPermission } from "@app/context/ProjectPermissionContext";
|
||||
|
||||
import { Tooltip } from "../v2/Tooltip";
|
||||
@@ -11,17 +10,7 @@ import { Tooltip } from "../v2/Tooltip";
|
||||
export const ProjectPermissionGuardBanner = () => {
|
||||
return (
|
||||
<div className="container mx-auto flex h-full items-center justify-center">
|
||||
<div className="flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
|
||||
<div className="text-sm">
|
||||
Your role has limited permissions, please <br /> contact your admin to gain access
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AccessRestrictedBanner />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -76,7 +76,6 @@ export const CreateSecretRotationV2Modal = ({ onOpenChange, isOpen, ...props }:
|
||||
</div>
|
||||
)
|
||||
}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
className={selectedRotation ? "max-w-2xl" : "max-w-3xl"}
|
||||
subTitle={
|
||||
selectedRotation ? undefined : "Select a provider to create a secret rotation for."
|
||||
|
||||
@@ -75,7 +75,6 @@ export const CreateSecretScanningDataSourceModal = ({ onOpenChange, isOpen, ...p
|
||||
</div>
|
||||
)
|
||||
}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
className={selectedDataSource ? "max-w-2xl" : "max-w-3xl"}
|
||||
subTitle={
|
||||
selectedDataSource ? undefined : "Select a data source to configure secret scanning for."
|
||||
|
||||
@@ -56,7 +56,6 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop
|
||||
"Add Sync"
|
||||
)
|
||||
}
|
||||
onPointerDownOutside={(e) => e.preventDefault()}
|
||||
className="max-w-2xl"
|
||||
bodyClassName="overflow-visible"
|
||||
subTitle={selectedSync ? undefined : "Select a third-party service to sync secrets to."}
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import {
|
||||
useRenderConnectionListServices
|
||||
} from "@app/hooks/api/appConnections/render";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync";
|
||||
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { FormControl, Switch, Tooltip } from "@app/components/v2";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
|
||||
export const RenderSyncOptionsFields = () => {
|
||||
const { control } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name="syncOptions.autoRedeployServices"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl className="mt-4" isError={Boolean(error?.message)} errorText={error?.message}>
|
||||
<Switch
|
||||
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
|
||||
id="auto-redeploy-services"
|
||||
thumbClassName="bg-mineshaft-800"
|
||||
isChecked={value}
|
||||
onCheckedChange={onChange}
|
||||
>
|
||||
Auto Redeploy Services On Sync
|
||||
<Tooltip
|
||||
className="max-w-md"
|
||||
content={
|
||||
<p>If enabled, services will be automatically redeployed upon secret changes.</p>
|
||||
}
|
||||
>
|
||||
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
|
||||
</Tooltip>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+4
-1
@@ -14,6 +14,7 @@ import { SecretSync, useSecretSyncOption } from "@app/hooks/api/secretSyncs";
|
||||
import { TSecretSyncForm } from "../schemas";
|
||||
import { AwsParameterStoreSyncOptionsFields } from "./AwsParameterStoreSyncOptionsFields";
|
||||
import { AwsSecretsManagerSyncOptionsFields } from "./AwsSecretsManagerSyncOptionsFields";
|
||||
import { RenderSyncOptionsFields } from "./RenderSyncOptionsFields";
|
||||
|
||||
type Props = {
|
||||
hideInitialSync?: boolean;
|
||||
@@ -38,6 +39,9 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
case SecretSync.AWSSecretsManager:
|
||||
AdditionalSyncOptionsFieldsComponent = <AwsSecretsManagerSyncOptionsFields />;
|
||||
break;
|
||||
case SecretSync.Render:
|
||||
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsFields />;
|
||||
break;
|
||||
case SecretSync.GitHub:
|
||||
case SecretSync.GCPSecretManager:
|
||||
case SecretSync.AzureKeyVault:
|
||||
@@ -54,7 +58,6 @@ export const SecretSyncOptionsFields = ({ hideInitialSync }: Props) => {
|
||||
case SecretSync.OnePass:
|
||||
case SecretSync.OCIVault:
|
||||
case SecretSync.Heroku:
|
||||
case SecretSync.Render:
|
||||
case SecretSync.Flyio:
|
||||
case SecretSync.GitLab:
|
||||
case SecretSync.CloudflarePages:
|
||||
|
||||
+21
@@ -2,8 +2,29 @@ import { useFormContext } from "react-hook-form";
|
||||
|
||||
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||
import { TSecretSyncForm } from "@app/components/secret-syncs/forms/schemas";
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
export const RenderSyncOptionsReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||
|
||||
const [{ autoRedeployServices }] = watch(["syncOptions"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{autoRedeployServices ? (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant="success">Enabled</Badge>
|
||||
</GenericFieldLabel>
|
||||
) : (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant="danger">Disabled</Badge>
|
||||
</GenericFieldLabel>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const RenderSyncReviewFields = () => {
|
||||
const { watch } = useFormContext<TSecretSyncForm & { destination: SecretSync.Render }>();
|
||||
const serviceName = watch("destinationConfig.serviceName");
|
||||
|
||||
+2
-1
@@ -35,7 +35,7 @@ import { HumanitecSyncReviewFields } from "./HumanitecSyncReviewFields";
|
||||
import { OCIVaultSyncReviewFields } from "./OCIVaultSyncReviewFields";
|
||||
import { OnePassSyncReviewFields } from "./OnePassSyncReviewFields";
|
||||
import { RailwaySyncReviewFields } from "./RailwaySyncReviewFields";
|
||||
import { RenderSyncReviewFields } from "./RenderSyncReviewFields";
|
||||
import { RenderSyncOptionsReviewFields, RenderSyncReviewFields } from "./RenderSyncReviewFields";
|
||||
import { SupabaseSyncReviewFields } from "./SupabaseSyncReviewFields";
|
||||
import { TeamCitySyncReviewFields } from "./TeamCitySyncReviewFields";
|
||||
import { TerraformCloudSyncReviewFields } from "./TerraformCloudSyncReviewFields";
|
||||
@@ -121,6 +121,7 @@ export const SecretSyncReviewFields = () => {
|
||||
break;
|
||||
case SecretSync.Render:
|
||||
DestinationFieldsComponent = <RenderSyncReviewFields />;
|
||||
AdditionalSyncOptionsFieldsComponent = <RenderSyncOptionsReviewFields />;
|
||||
break;
|
||||
case SecretSync.Flyio:
|
||||
DestinationFieldsComponent = <FlyioSyncReviewFields />;
|
||||
|
||||
+6
-2
@@ -2,9 +2,13 @@ import { z } from "zod";
|
||||
|
||||
import { BaseSecretSyncSchema } from "@app/components/secret-syncs/forms/schemas/base-secret-sync-schema";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/render-sync";
|
||||
import { RenderSyncScope, RenderSyncType } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||
|
||||
export const RenderSyncDestinationSchema = BaseSecretSyncSchema().merge(
|
||||
export const RenderSyncDestinationSchema = BaseSecretSyncSchema(
|
||||
z.object({
|
||||
autoRedeployServices: z.boolean().optional()
|
||||
})
|
||||
).merge(
|
||||
z.object({
|
||||
destination: z.literal(SecretSync.Render),
|
||||
destinationConfig: z.discriminatedUnion("scope", [
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ReactNode } from "react";
|
||||
|
||||
type Props = {
|
||||
title?: string;
|
||||
body?: ReactNode;
|
||||
};
|
||||
|
||||
export const AccessRestrictedBanner = ({
|
||||
title = "Access Restricted",
|
||||
|
||||
body = (
|
||||
<>
|
||||
Your current role doesn't provide access to this feature.
|
||||
<br /> Contact your administrator to request access.
|
||||
</>
|
||||
)
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="flex items-center rounded-md border border-mineshaft-500 bg-gradient-to-br from-mineshaft-900 to-mineshaft-600 px-16 py-12 text-center text-bunker-300">
|
||||
<div>
|
||||
<div className="text-4xl font-medium text-bunker-100">{title}</div>
|
||||
<div className="-mt-1 text-sm">{body}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./AccessRestrictedBanner";
|
||||
@@ -1,4 +1,5 @@
|
||||
/* eslint-disable react-refresh/only-export-components */
|
||||
export * from "./AccessRestrictedBanner";
|
||||
export * from "./Accordion";
|
||||
export * from "./Alert";
|
||||
export * from "./Badge";
|
||||
|
||||
@@ -171,7 +171,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
case RenderConnectionMethod.ApiKey:
|
||||
case ChecklyConnectionMethod.ApiKey:
|
||||
return { name: "API Key", icon: faKey };
|
||||
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
return { name: "Client Secret", icon: faKey };
|
||||
default:
|
||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,9 @@ import {
|
||||
SecretSyncImportBehavior,
|
||||
SecretSyncInitialSyncBehavior
|
||||
} from "@app/hooks/api/secretSyncs";
|
||||
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/render-sync";
|
||||
import { GcpSyncScope } from "@app/hooks/api/secretSyncs/types/gcp-sync";
|
||||
import { HumanitecSyncScope } from "@app/hooks/api/secretSyncs/types/humanitec-sync";
|
||||
import { RenderSyncScope } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||
|
||||
export const SECRET_SYNC_MAP: Record<SecretSync, { name: string; image: string }> = {
|
||||
[SecretSync.AWSParameterStore]: { name: "AWS Parameter Store", image: "Amazon Web Services.png" },
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import { ComponentType } from "react";
|
||||
import { Abilities, AbilityTuple, Generics, SubjectType } from "@casl/ability";
|
||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { AccessRestrictedBanner } from "@app/components/v2";
|
||||
import { TOrgPermission, useOrgPermission } from "@app/context";
|
||||
|
||||
type Props<T extends Abilities> = (T extends AbilityTuple
|
||||
@@ -14,11 +13,11 @@ type Props<T extends Abilities> = (T extends AbilityTuple
|
||||
: {
|
||||
action: string;
|
||||
subject: string;
|
||||
}) & { className?: string; containerClassName?: string };
|
||||
}) & { containerClassName?: string };
|
||||
|
||||
export const withPermission = <T extends object, J extends TOrgPermission>(
|
||||
Component: ComponentType<T>,
|
||||
{ action, subject, className, containerClassName }: Props<Generics<J>["abilities"]>
|
||||
{ action, subject, containerClassName }: Props<Generics<J>["abilities"]>
|
||||
) => {
|
||||
const HOC = (hocProps: T) => {
|
||||
const { permission } = useOrgPermission();
|
||||
@@ -33,22 +32,7 @@ export const withPermission = <T extends object, J extends TOrgPermission>(
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-2 text-4xl font-medium">Access Restricted</div>
|
||||
<div className="text-sm">
|
||||
Your role has limited permissions, please <br /> contact your admin to gain access
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AccessRestrictedBanner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { ComponentType } from "react";
|
||||
import { AbilityTuple } from "@casl/ability";
|
||||
import { faLock } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { AccessRestrictedBanner } from "@app/components/v2";
|
||||
import { useProjectPermission } from "@app/context";
|
||||
import { ProjectPermissionSet } from "@app/context/ProjectPermissionContext";
|
||||
|
||||
type Props<T extends AbilityTuple> = {
|
||||
className?: string;
|
||||
containerClassName?: string;
|
||||
action: T[0];
|
||||
subject: T[1];
|
||||
@@ -16,7 +14,7 @@ type Props<T extends AbilityTuple> = {
|
||||
|
||||
export const withProjectPermission = <T extends object>(
|
||||
Component: ComponentType<Omit<Props<ProjectPermissionSet>, "action" | "subject"> & T>,
|
||||
{ action, subject, className, containerClassName }: Props<ProjectPermissionSet>
|
||||
{ action, subject, containerClassName }: Props<ProjectPermissionSet>
|
||||
) => {
|
||||
const HOC = (hocProps: Omit<Props<ProjectPermissionSet>, "action" | "subject"> & T) => {
|
||||
const { permission } = useProjectPermission();
|
||||
@@ -31,23 +29,7 @@ export const withProjectPermission = <T extends object>(
|
||||
containerClassName
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex items-end space-x-12 rounded-md bg-mineshaft-800 p-16 text-bunker-300",
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLock} size="6x" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-2 text-4xl font-medium">Permission Denied</div>
|
||||
<div className="text-sm">
|
||||
You do not have permission to this page. <br /> Kindly contact your organization
|
||||
administrator
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AccessRestrictedBanner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const useCreateAccessApprovalPolicy = () => {
|
||||
|
||||
return useMutation<object, object, TCreateAccessPolicyDTO>({
|
||||
mutationFn: async ({
|
||||
environment,
|
||||
environments,
|
||||
projectSlug,
|
||||
approvals,
|
||||
approvers,
|
||||
@@ -29,7 +29,7 @@ export const useCreateAccessApprovalPolicy = () => {
|
||||
approvalsRequired
|
||||
}) => {
|
||||
const { data } = await apiRequest.post("/api/v1/access-approvals/policies", {
|
||||
environment,
|
||||
environments,
|
||||
projectSlug,
|
||||
approvals,
|
||||
bypassers,
|
||||
@@ -63,7 +63,8 @@ export const useUpdateAccessApprovalPolicy = () => {
|
||||
secretPath,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
approvalsRequired,
|
||||
environments
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/access-approvals/policies/${id}`, {
|
||||
approvals,
|
||||
@@ -73,7 +74,8 @@ export const useUpdateAccessApprovalPolicy = () => {
|
||||
name,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals,
|
||||
approvalsRequired
|
||||
approvalsRequired,
|
||||
environments
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -8,9 +8,8 @@ export type TAccessApprovalPolicy = {
|
||||
name: string;
|
||||
approvals: number;
|
||||
secretPath: string;
|
||||
envId: string;
|
||||
workspace: string;
|
||||
environment: WorkspaceEnv;
|
||||
environments: WorkspaceEnv[];
|
||||
projectId: string;
|
||||
policyType: PolicyType;
|
||||
approversRequired: boolean;
|
||||
@@ -166,7 +165,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
|
||||
export type TCreateAccessPolicyDTO = {
|
||||
projectSlug: string;
|
||||
name?: string;
|
||||
environment: string;
|
||||
environments: string[];
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
approvals?: number;
|
||||
@@ -182,7 +181,7 @@ export type TUpdateAccessPolicyDTO = {
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
secretPath?: string;
|
||||
environment?: string;
|
||||
environments?: string[];
|
||||
approvals?: number;
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
allowedSelfApprovals: boolean;
|
||||
|
||||
@@ -2,15 +2,26 @@ import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
|
||||
|
||||
export enum AzureClientSecretsConnectionMethod {
|
||||
OAuth = "oauth"
|
||||
OAuth = "oauth",
|
||||
ClientSecret = "client-secret"
|
||||
}
|
||||
|
||||
export type TAzureClientSecretsConnection = TRootAppConnection & {
|
||||
app: AppConnection.AzureClientSecrets;
|
||||
} & {
|
||||
method: AzureClientSecretsConnectionMethod.OAuth;
|
||||
credentials: {
|
||||
code: string;
|
||||
tenantId: string;
|
||||
};
|
||||
};
|
||||
} & (
|
||||
| {
|
||||
method: AzureClientSecretsConnectionMethod.OAuth;
|
||||
credentials: {
|
||||
code: string;
|
||||
tenantId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
method: AzureClientSecretsConnectionMethod.ClientSecret;
|
||||
credentials: {
|
||||
clientSecret: string;
|
||||
clientId: string;
|
||||
tenantId: string;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ export const useCreateSecretApprovalPolicy = () => {
|
||||
|
||||
return useMutation<object, object, TCreateSecretPolicyDTO>({
|
||||
mutationFn: async ({
|
||||
environment,
|
||||
environments,
|
||||
workspaceId,
|
||||
approvals,
|
||||
approvers,
|
||||
@@ -21,7 +21,7 @@ export const useCreateSecretApprovalPolicy = () => {
|
||||
allowedSelfApprovals
|
||||
}) => {
|
||||
const { data } = await apiRequest.post("/api/v1/secret-approvals", {
|
||||
environment,
|
||||
environments,
|
||||
workspaceId,
|
||||
approvals,
|
||||
approvers,
|
||||
@@ -53,7 +53,8 @@ export const useUpdateSecretApprovalPolicy = () => {
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
allowedSelfApprovals,
|
||||
environments
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch(`/api/v1/secret-approvals/${id}`, {
|
||||
approvals,
|
||||
@@ -62,7 +63,8 @@ export const useUpdateSecretApprovalPolicy = () => {
|
||||
secretPath,
|
||||
name,
|
||||
enforcementLevel,
|
||||
allowedSelfApprovals
|
||||
allowedSelfApprovals,
|
||||
environments
|
||||
});
|
||||
return data;
|
||||
},
|
||||
|
||||
@@ -5,8 +5,7 @@ export type TSecretApprovalPolicy = {
|
||||
id: string;
|
||||
workspace: string;
|
||||
name: string;
|
||||
envId: string;
|
||||
environment: WorkspaceEnv;
|
||||
environments: WorkspaceEnv[];
|
||||
secretPath?: string;
|
||||
approvals: number;
|
||||
approvers: Approver[];
|
||||
@@ -48,7 +47,7 @@ export type TGetSecretApprovalPolicyOfBoardDTO = {
|
||||
export type TCreateSecretPolicyDTO = {
|
||||
workspaceId: string;
|
||||
name?: string;
|
||||
environment: string;
|
||||
environments: string[];
|
||||
secretPath: string;
|
||||
approvers?: Approver[];
|
||||
bypassers?: Bypasser[];
|
||||
@@ -68,6 +67,7 @@ export type TUpdateSecretPolicyDTO = {
|
||||
enforcementLevel?: EnforcementLevel;
|
||||
// for invalidating list
|
||||
workspaceId: string;
|
||||
environments?: string[];
|
||||
};
|
||||
|
||||
export type TDeleteSecretPolicyDTO = {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { SecretSync, SecretSyncImportBehavior } from "@app/hooks/api/secretSyncs";
|
||||
import { DiscriminativePick } from "@app/types";
|
||||
|
||||
import { TRenderSync } from "../render-sync";
|
||||
import { TOnePassSync } from "./1password-sync";
|
||||
import { TAwsParameterStoreSync } from "./aws-parameter-store-sync";
|
||||
import { TAwsSecretsManagerSync } from "./aws-secrets-manager-sync";
|
||||
@@ -24,6 +23,7 @@ import { THerokuSync } from "./heroku-sync";
|
||||
import { THumanitecSync } from "./humanitec-sync";
|
||||
import { TOCIVaultSync } from "./oci-vault-sync";
|
||||
import { TRailwaySync } from "./railway-sync";
|
||||
import { TRenderSync } from "./render-sync";
|
||||
import { TSupabaseSync } from "./supabase";
|
||||
import { TTeamCitySync } from "./teamcity-sync";
|
||||
import { TTerraformCloudSync } from "./terraform-cloud-sync";
|
||||
|
||||
+5
-1
@@ -1,6 +1,6 @@
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import { SecretSync } from "@app/hooks/api/secretSyncs";
|
||||
import { TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||
import { RootSyncOptions, TRootSecretSync } from "@app/hooks/api/secretSyncs/types/root-sync";
|
||||
|
||||
export type TRenderSync = TRootSecretSync & {
|
||||
destination: SecretSync.Render;
|
||||
@@ -16,6 +16,10 @@ export type TRenderSync = TRootSecretSync & {
|
||||
name: string;
|
||||
id: string;
|
||||
};
|
||||
|
||||
syncOptions: RootSyncOptions & {
|
||||
autoRedeployServices?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export enum RenderSyncScope {
|
||||
@@ -49,7 +49,8 @@ export const usePathAccessPolicies = ({ secretPath, environment }: Params) => {
|
||||
return useMemo(() => {
|
||||
const pathPolicies = policies?.filter(
|
||||
(policy) =>
|
||||
policy.environment.slug === environment && matchesPath(secretPath, policy.secretPath)
|
||||
policy.environments?.some((env) => env.slug === environment) &&
|
||||
matchesPath(secretPath, policy.secretPath)
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -22,7 +22,6 @@ export const KmipPage = () => {
|
||||
description="Integrate with Infisical KMS via Key Management Interoperability Protocol."
|
||||
/>
|
||||
<ProjectPermissionCan
|
||||
passThrough={false}
|
||||
renderGuardBanner
|
||||
I={ProjectPermissionKmipActions.ReadClients}
|
||||
a={ProjectPermissionSub.Kmip}
|
||||
|
||||
@@ -22,7 +22,6 @@ export const OverviewPage = () => {
|
||||
description="Manage keys and perform cryptographic operations."
|
||||
/>
|
||||
<ProjectPermissionCan
|
||||
passThrough={false}
|
||||
renderGuardBanner
|
||||
I={ProjectPermissionActions.Read}
|
||||
a={ProjectPermissionSub.Cmek}
|
||||
|
||||
+2
-2
@@ -114,7 +114,7 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
return <AzureClientSecretsConnectionForm />;
|
||||
return <AzureClientSecretsConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureDevOps:
|
||||
return <AzureDevOpsConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Windmill:
|
||||
@@ -222,7 +222,7 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.Camunda:
|
||||
return <CamundaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.AzureClientSecrets:
|
||||
return <AzureClientSecretsConnectionForm appConnection={appConnection} />;
|
||||
return <AzureClientSecretsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureDevOps:
|
||||
return <AzureDevOpsConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.Windmill:
|
||||
|
||||
+142
-41
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-case-declarations */
|
||||
import crypto from "crypto";
|
||||
|
||||
import { useState } from "react";
|
||||
@@ -20,19 +21,83 @@ import {
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureClientSecretsConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.AzureClientSecrets),
|
||||
method: z.nativeEnum(AzureClientSecretsConnectionMethod),
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required")
|
||||
method: z.nativeEnum(AzureClientSecretsConnectionMethod)
|
||||
});
|
||||
|
||||
const oauthSchema = baseSchema.extend({
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required"),
|
||||
method: z.literal(AzureClientSecretsConnectionMethod.OAuth)
|
||||
});
|
||||
|
||||
const clientSecretSchema = baseSchema.extend({
|
||||
method: z.literal(AzureClientSecretsConnectionMethod.ClientSecret),
|
||||
credentials: z.object({
|
||||
clientSecret: z.string().trim().min(1, "Client Secret is required"),
|
||||
clientId: z.string().trim().min(1, "Client ID is required"),
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required")
|
||||
})
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [oauthSchema, clientSecretSchema]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
|
||||
const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partial<FormData> => {
|
||||
if (!appConnection) {
|
||||
return {
|
||||
app: AppConnection.AzureClientSecrets,
|
||||
method: AzureClientSecretsConnectionMethod.OAuth
|
||||
};
|
||||
}
|
||||
|
||||
const base = {
|
||||
name: appConnection.name,
|
||||
description: appConnection.description,
|
||||
app: appConnection.app,
|
||||
method: appConnection.method
|
||||
};
|
||||
const { credentials } = appConnection;
|
||||
|
||||
switch (appConnection.method) {
|
||||
case AzureClientSecretsConnectionMethod.OAuth:
|
||||
if ("tenantId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureClientSecretsConnectionMethod.OAuth,
|
||||
tenantId: credentials.tenantId
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
if ("clientSecret" in credentials && "clientId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureClientSecretsConnectionMethod.ClientSecret,
|
||||
credentials: {
|
||||
clientSecret: credentials.clientSecret,
|
||||
clientId: credentials.clientId,
|
||||
tenantId: credentials.tenantId
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -43,70 +108,51 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection
|
||||
? {
|
||||
...appConnection,
|
||||
tenantId: appConnection.credentials.tenantId
|
||||
}
|
||||
: {
|
||||
app: AppConnection.AzureClientSecrets,
|
||||
method: AzureClientSecretsConnectionMethod.OAuth
|
||||
}
|
||||
defaultValues: getDefaultValues(appConnection)
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
setIsRedirecting(true);
|
||||
const onSubmitHandler = (formData: FormData) => {
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureClientSecretsConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
|
||||
switch (formData.method) {
|
||||
case AzureClientSecretsConnectionMethod.OAuth:
|
||||
window.location.assign(
|
||||
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets`
|
||||
setIsRedirecting(true);
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureClientSecretsConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
window.location.assign(
|
||||
`https://login.microsoftonline.com/${formData.tenantId || "common"}/oauth2/v2.0/authorize?client_id=${oauthClientId}&response_type=code&redirect_uri=${window.location.origin}/organization/app-connections/azure/oauth/callback&response_mode=query&scope=https://graph.microsoft.com/.default%20openid%20offline_access&state=${state}<:>azure-client-secrets`
|
||||
);
|
||||
break;
|
||||
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
onSubmit(formData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
};
|
||||
|
||||
const isMissingConfig = !oauthClientId;
|
||||
|
||||
const isMissingConfig =
|
||||
selectedMethod === AzureClientSecretsConnectionMethod.OAuth && !oauthClientId;
|
||||
const methodDetails = getAppConnectionMethodDetails(selectedMethod);
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<form onSubmit={handleSubmit(onSubmitHandler)}>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText="The Directory (tenant) ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
@@ -146,6 +192,61 @@ export const AzureClientSecretsConnectionForm = ({ appConnection }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText="The Directory (tenant) ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setValue("credentials.tenantId", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Access Token-specific fields */}
|
||||
{selectedMethod === AzureClientSecretsConnectionMethod.ClientSecret && (
|
||||
<>
|
||||
<Controller
|
||||
name="credentials.clientId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Client ID"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="00000000-0000-0000-0000-000000000000" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.clientSecret"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Client Secret"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} type="password" placeholder="Enter your Client Secret" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
+2
-2
@@ -155,8 +155,8 @@ export const SpecificPrivilegeSecretForm = ({
|
||||
|
||||
const selectablePaths = useMemo(() => {
|
||||
if (!policies) return [];
|
||||
const environmentPolicies = policies.filter(
|
||||
(policy) => policy.environment.slug === selectedEnvironment
|
||||
const environmentPolicies = policies.filter((policy) =>
|
||||
policy.environments.find((env) => env.slug === selectedEnvironment)
|
||||
);
|
||||
|
||||
privilegeForm.setValue("secretPath", "", {
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render";
|
||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync";
|
||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||
|
||||
import { getSecretSyncDestinationColValues } from "../helpers";
|
||||
import { SecretSyncTableCell } from "../SecretSyncTableCell";
|
||||
|
||||
+18
-6
@@ -166,17 +166,20 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
||||
const filteredPolicies = useMemo(
|
||||
() =>
|
||||
policies
|
||||
.filter(({ policyType, environment, name, secretPath }) => {
|
||||
.filter(({ policyType, environments, name, secretPath }) => {
|
||||
if (filters.type && policyType !== filters.type) return false;
|
||||
|
||||
if (filters.environmentIds.length && !filters.environmentIds.includes(environment.id))
|
||||
if (
|
||||
filters.environmentIds.length &&
|
||||
!environments.some((env) => filters.environmentIds.includes(env.id))
|
||||
)
|
||||
return false;
|
||||
|
||||
const searchValue = search.trim().toLowerCase();
|
||||
|
||||
return (
|
||||
name.toLowerCase().includes(searchValue) ||
|
||||
environment.name.toLowerCase().includes(searchValue) ||
|
||||
environments.some((env) => env.name.toLowerCase().includes(searchValue)) ||
|
||||
(secretPath ?? "*").toLowerCase().includes(searchValue)
|
||||
);
|
||||
})
|
||||
@@ -189,9 +192,18 @@ export const ApprovalPolicyList = ({ workspaceId }: IProps) => {
|
||||
.toLowerCase()
|
||||
.localeCompare(policyTwo.policyType.toLowerCase());
|
||||
case PolicyOrderBy.Environment:
|
||||
return policyOne.environment.name
|
||||
.toLowerCase()
|
||||
.localeCompare(policyTwo.environment.name.toLowerCase());
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const getFirstEnvName = (policy: { environments: { name: string }[] }) => {
|
||||
if (!policy.environments?.length) return "";
|
||||
return (
|
||||
policy.environments
|
||||
.map((env) => env.name?.toLowerCase() || "")
|
||||
.filter((name) => name)
|
||||
.sort()[0] || ""
|
||||
);
|
||||
};
|
||||
|
||||
return getFirstEnvName(policyOne).localeCompare(getFirstEnvName(policyTwo));
|
||||
case PolicyOrderBy.SecretPath:
|
||||
return (policyOne.secretPath ?? "*")
|
||||
.toLowerCase()
|
||||
|
||||
+17
-16
@@ -54,7 +54,7 @@ type Props = {
|
||||
|
||||
const formSchema = z
|
||||
.object({
|
||||
environment: z.object({ slug: z.string(), name: z.string() }),
|
||||
environments: z.array(z.object({ slug: z.string(), name: z.string() })).min(1),
|
||||
name: z.string().optional(),
|
||||
secretPath: z.string().trim().min(1),
|
||||
approvals: z.number().min(1).default(1),
|
||||
@@ -134,7 +134,7 @@ const Form = ({
|
||||
values: editValues
|
||||
? ({
|
||||
...editValues,
|
||||
environment: editValues.environment,
|
||||
environments: editValues.environments,
|
||||
userApprovers:
|
||||
editValues?.approvers
|
||||
?.filter((approver) => approver.type === ApproverType.User)
|
||||
@@ -191,7 +191,7 @@ const Form = ({
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const { data: groups } = useListWorkspaceGroups(projectId);
|
||||
|
||||
const environments = currentWorkspace?.environments || [];
|
||||
const availableEnvironments = currentWorkspace?.environments || [];
|
||||
const isAccessPolicyType = watch("policyType") === PolicyType.AccessPolicy;
|
||||
|
||||
const { mutateAsync: createAccessApprovalPolicy } = useCreateAccessApprovalPolicy();
|
||||
@@ -204,11 +204,11 @@ const Form = ({
|
||||
|
||||
const formUserBypassers = watch("userBypassers");
|
||||
const formGroupBypassers = watch("groupBypassers");
|
||||
const formEnvironment = watch("environment")?.slug;
|
||||
const formEnvironments = watch("environments");
|
||||
const bypasserCount = (formUserBypassers || []).length + (formGroupBypassers || []).length;
|
||||
|
||||
const handleCreatePolicy = async ({
|
||||
environment,
|
||||
environments,
|
||||
groupApprovers,
|
||||
userApprovers,
|
||||
groupBypassers,
|
||||
@@ -226,7 +226,7 @@ const Form = ({
|
||||
...data,
|
||||
approvers: [...userApprovers, ...groupApprovers],
|
||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||
environment: environment.slug,
|
||||
environments: environments.map((env) => env.slug),
|
||||
workspaceId: currentWorkspace?.id || ""
|
||||
});
|
||||
} else {
|
||||
@@ -242,7 +242,7 @@ const Form = ({
|
||||
numberOfApprovals: el.approvals
|
||||
})),
|
||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||
environment: environment.slug,
|
||||
environments: environments.map((env) => env.slug),
|
||||
projectSlug
|
||||
});
|
||||
}
|
||||
@@ -261,7 +261,7 @@ const Form = ({
|
||||
};
|
||||
|
||||
const handleUpdatePolicy = async ({
|
||||
environment,
|
||||
environments,
|
||||
userApprovers,
|
||||
groupApprovers,
|
||||
userBypassers,
|
||||
@@ -281,7 +281,8 @@ const Form = ({
|
||||
...data,
|
||||
approvers: [...userApprovers, ...groupApprovers],
|
||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||
workspaceId: currentWorkspace?.id || ""
|
||||
workspaceId: currentWorkspace?.id || "",
|
||||
environments: environments.map((env) => env.slug)
|
||||
});
|
||||
} else {
|
||||
await updateAccessApprovalPolicy({
|
||||
@@ -297,7 +298,7 @@ const Form = ({
|
||||
numberOfApprovals: el.approvals
|
||||
})),
|
||||
bypassers: bypassers.length > 0 ? bypassers : undefined,
|
||||
environment: environment.slug,
|
||||
environments: environments.map((env) => env.slug),
|
||||
projectSlug
|
||||
});
|
||||
}
|
||||
@@ -479,28 +480,28 @@ const Form = ({
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
environment={formEnvironment}
|
||||
environment={formEnvironments?.[0]?.slug || ""}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="environment"
|
||||
name="environments"
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Environment"
|
||||
label="Environments"
|
||||
isRequired
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
className="flex-1"
|
||||
>
|
||||
<FilterableSelect
|
||||
isDisabled={isEditMode}
|
||||
value={value}
|
||||
isMulti
|
||||
onChange={onChange}
|
||||
placeholder="Select environment..."
|
||||
options={environments}
|
||||
placeholder="Select environments..."
|
||||
options={availableEnvironments}
|
||||
getOptionValue={(option) => option.slug}
|
||||
getOptionLabel={(option) => option.name}
|
||||
/>
|
||||
|
||||
+2
-2
@@ -37,7 +37,7 @@ import { TWorkspaceUser } from "@app/hooks/api/users/types";
|
||||
interface IPolicy {
|
||||
id: string;
|
||||
name: string;
|
||||
environment: WorkspaceEnv;
|
||||
environments: WorkspaceEnv[];
|
||||
projectId?: string;
|
||||
secretPath?: string;
|
||||
approvals: number;
|
||||
@@ -112,7 +112,7 @@ export const ApprovalPolicyRow = ({
|
||||
onClick={() => setIsExpanded.toggle()}
|
||||
>
|
||||
<Td>{policy.name || <span className="text-mineshaft-400">Unnamed Policy</span>}</Td>
|
||||
<Td>{policy.environment.name}</Td>
|
||||
<Td>{policy.environments.map((env) => env.name).join(", ")}</Td>
|
||||
<Td>{policy.secretPath || "*"}</Td>
|
||||
<Td>
|
||||
<Badge
|
||||
|
||||
@@ -975,12 +975,12 @@ const Page = () => {
|
||||
/>
|
||||
)}
|
||||
{noAccessSecretCount > 0 && <SecretNoAccessListView count={noAccessSecretCount} />}
|
||||
{!canReadSecret &&
|
||||
!canReadDynamicSecret &&
|
||||
!canReadSecretImports &&
|
||||
folders?.length === 0 && <PermissionDeniedBanner />}
|
||||
</div>
|
||||
</div>
|
||||
{!canReadSecret &&
|
||||
!canReadDynamicSecret &&
|
||||
!canReadSecretImports &&
|
||||
folders?.length === 0 && <PermissionDeniedBanner />}
|
||||
{!isDetailsLoading &&
|
||||
(totalCount > 0 ||
|
||||
pendingChanges.secrets.length > 0 ||
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||
import { useRenderConnectionListServices } from "@app/hooks/api/appConnections/render";
|
||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/render-sync";
|
||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TRenderSync;
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { GenericFieldLabel } from "@app/components/secret-syncs";
|
||||
import { Badge } from "@app/components/v2";
|
||||
import { TRenderSync } from "@app/hooks/api/secretSyncs/types/render-sync";
|
||||
|
||||
type Props = {
|
||||
secretSync: TRenderSync;
|
||||
};
|
||||
|
||||
export const RenderSyncOptionsSection = ({ secretSync }: Props) => {
|
||||
const {
|
||||
syncOptions: { autoRedeployServices }
|
||||
} = secretSync;
|
||||
|
||||
return (
|
||||
<GenericFieldLabel label="Auto Redeploy Services">
|
||||
<Badge variant={autoRedeployServices ? "success" : "danger"}>
|
||||
{autoRedeployServices ? "Enabled" : "Disabled"}
|
||||
</Badge>
|
||||
</GenericFieldLabel>
|
||||
);
|
||||
};
|
||||
+4
-1
@@ -13,6 +13,7 @@ import { SecretSync, TSecretSync } from "@app/hooks/api/secretSyncs";
|
||||
|
||||
import { AwsParameterStoreSyncOptionsSection } from "./AwsParameterStoreSyncOptionsSection";
|
||||
import { AwsSecretsManagerSyncOptionsSection } from "./AwsSecretsManagerSyncOptionsSection";
|
||||
import { RenderSyncOptionsSection } from "./RenderSyncOptionsSection";
|
||||
|
||||
type Props = {
|
||||
secretSync: TSecretSync;
|
||||
@@ -40,6 +41,9 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
||||
<AwsSecretsManagerSyncOptionsSection secretSync={secretSync} />
|
||||
);
|
||||
break;
|
||||
case SecretSync.Render:
|
||||
AdditionalSyncOptionsComponent = <RenderSyncOptionsSection secretSync={secretSync} />;
|
||||
break;
|
||||
case SecretSync.GitHub:
|
||||
case SecretSync.GCPSecretManager:
|
||||
case SecretSync.AzureKeyVault:
|
||||
@@ -56,7 +60,6 @@ export const SecretSyncOptionsSection = ({ secretSync, onEditOptions }: Props) =
|
||||
case SecretSync.OCIVault:
|
||||
case SecretSync.OnePass:
|
||||
case SecretSync.Heroku:
|
||||
case SecretSync.Render:
|
||||
case SecretSync.Flyio:
|
||||
case SecretSync.GitLab:
|
||||
case SecretSync.CloudflarePages:
|
||||
|
||||
+6
-8
@@ -1,6 +1,6 @@
|
||||
import { faBan } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { ContentLoader, EmptyState } from "@app/components/v2";
|
||||
import { AccessRestrictedBanner, ContentLoader, EmptyState } from "@app/components/v2";
|
||||
import { useSubscription, useWorkspace } from "@app/context";
|
||||
import { useGetSecretScanningConfig } from "@app/hooks/api/secretScanningV2";
|
||||
|
||||
@@ -16,16 +16,14 @@ export const ProjectScanningConfigTab = () => {
|
||||
|
||||
if (!subscription.secretScanning) {
|
||||
return (
|
||||
<div className="flex h-full w-full items-center justify-center px-20">
|
||||
<EmptyState
|
||||
className="rounded-md text-center"
|
||||
icon={faBan}
|
||||
title={
|
||||
<span>
|
||||
<div className="mt-60 flex h-full w-full items-center justify-center px-20">
|
||||
<AccessRestrictedBanner
|
||||
body={
|
||||
<>
|
||||
Your current plan doesn't support Secret Scanning.
|
||||
<br /> Please contact Infisical Support or reach out through our Slack channel for
|
||||
assistance.
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user