mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-05 19:26:38 +00:00
Merge pull request #4820 from Infisical/daniel/cert-auth
feat(app-connections/azure-client-secrets): certificate auth
This commit is contained in:
@@ -12,12 +12,14 @@ const syntaxHighlight = (
|
||||
isVisible?: boolean,
|
||||
isImport?: boolean,
|
||||
isLoadingValue?: boolean,
|
||||
isErrorLoadingValue?: boolean
|
||||
isErrorLoadingValue?: boolean,
|
||||
placeholder?: string
|
||||
) => {
|
||||
if (isLoadingValue) return HIDDEN_SECRET_VALUE;
|
||||
if (isErrorLoadingValue)
|
||||
return <span className="ph-no-capture text-red/75">Error loading secret value.</span>;
|
||||
if (isImport && !content) return "IMPORTED";
|
||||
if (placeholder && (content === "" || !content)) return placeholder;
|
||||
if (content === "") return "EMPTY";
|
||||
if (!content) return "EMPTY";
|
||||
if (!isVisible) return HIDDEN_SECRET_VALUE;
|
||||
@@ -79,6 +81,7 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
canEditButNotView,
|
||||
isLoadingValue,
|
||||
isErrorLoadingValue,
|
||||
placeholder,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
@@ -93,18 +96,25 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
||||
<div className="relative overflow-hidden">
|
||||
<pre aria-hidden className="m-0">
|
||||
<code className={`inline-block w-full ${commonClassName}`}>
|
||||
<span style={{ whiteSpace: "break-spaces" }}>
|
||||
<span
|
||||
className={twMerge(
|
||||
"whitespace-break-spaces",
|
||||
placeholder && !value && "text-gray-500/50"
|
||||
)}
|
||||
>
|
||||
{syntaxHighlight(
|
||||
value,
|
||||
isVisible || (isSecretFocused && !valueAlwaysHidden),
|
||||
isImport,
|
||||
isLoadingValue,
|
||||
isErrorLoadingValue
|
||||
isErrorLoadingValue,
|
||||
placeholder
|
||||
)}
|
||||
</span>
|
||||
</code>
|
||||
</pre>
|
||||
<textarea
|
||||
placeholder={placeholder}
|
||||
style={{ whiteSpace: "break-spaces" }}
|
||||
aria-label="secret value"
|
||||
ref={ref}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { faGithub, IconDefinition } from "@fortawesome/free-brands-svg-icons";
|
||||
import {
|
||||
faBullseye,
|
||||
faCertificate,
|
||||
faKey,
|
||||
faLink,
|
||||
faLock,
|
||||
@@ -211,6 +212,8 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
|
||||
case AzureKeyVaultConnectionMethod.ClientSecret:
|
||||
case AzureDevOpsConnectionMethod.ClientSecret:
|
||||
return { name: "Client Secret", icon: faKey };
|
||||
case AzureClientSecretsConnectionMethod.Certificate:
|
||||
return { name: "Certificate", icon: faCertificate };
|
||||
default:
|
||||
throw new Error(`Unhandled App Connection Method: ${method}`);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-con
|
||||
|
||||
export enum AzureClientSecretsConnectionMethod {
|
||||
OAuth = "oauth",
|
||||
ClientSecret = "client-secret"
|
||||
ClientSecret = "client-secret",
|
||||
Certificate = "certificate"
|
||||
}
|
||||
|
||||
export type TAzureClientSecretsConnection = TRootAppConnection & {
|
||||
@@ -24,4 +25,13 @@ export type TAzureClientSecretsConnection = TRootAppConnection & {
|
||||
tenantId: string;
|
||||
};
|
||||
}
|
||||
| {
|
||||
method: AzureClientSecretsConnectionMethod.Certificate;
|
||||
credentials: {
|
||||
clientId: string;
|
||||
tenantId: string;
|
||||
certificateBody: string;
|
||||
privateKey: string;
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
+103
-4
@@ -6,7 +6,15 @@ import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
ModalClose,
|
||||
SecretInput,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
APP_CONNECTION_MAP,
|
||||
getAppConnectionMethodDetails,
|
||||
@@ -27,10 +35,13 @@ import {
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
type CertificateForm = z.infer<typeof certificateSchema>;
|
||||
|
||||
type TInputFormData = ClientSecretForm | CertificateForm;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureClientSecretsConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
onSubmit: (formData: TInputFormData) => Promise<void>;
|
||||
projectId: string | undefined | null;
|
||||
};
|
||||
|
||||
@@ -53,7 +64,21 @@ const clientSecretSchema = baseSchema.extend({
|
||||
})
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [oauthSchema, clientSecretSchema]);
|
||||
const certificateSchema = baseSchema.extend({
|
||||
method: z.literal(AzureClientSecretsConnectionMethod.Certificate),
|
||||
credentials: z.object({
|
||||
clientId: z.string().trim().min(1, "Client ID is required"),
|
||||
certificateBody: z.string().trim().min(1, "Certificate is required"),
|
||||
privateKey: z.string().trim().min(1, "Private Key is required"),
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required")
|
||||
})
|
||||
});
|
||||
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
oauthSchema,
|
||||
clientSecretSchema,
|
||||
certificateSchema
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
@@ -96,6 +121,20 @@ const getDefaultValues = (appConnection?: TAzureClientSecretsConnection): Partia
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AzureClientSecretsConnectionMethod.Certificate:
|
||||
if ("clientId" in credentials && "tenantId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureClientSecretsConnectionMethod.Certificate,
|
||||
credentials: {
|
||||
clientId: credentials.clientId,
|
||||
tenantId: credentials.tenantId,
|
||||
certificateBody: "",
|
||||
privateKey: ""
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
@@ -152,6 +191,9 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, proj
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
case AzureClientSecretsConnectionMethod.Certificate:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
@@ -207,7 +249,11 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, proj
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
name={
|
||||
selectedMethod === AzureClientSecretsConnectionMethod.OAuth
|
||||
? "tenantId"
|
||||
: "credentials.tenantId"
|
||||
}
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
@@ -264,6 +310,59 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit, proj
|
||||
</>
|
||||
)}
|
||||
|
||||
{selectedMethod === AzureClientSecretsConnectionMethod.Certificate && (
|
||||
<>
|
||||
<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.certificateBody"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Certificate"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<SecretInput
|
||||
containerClassName="text-gray-400 group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
placeholder="-----BEGIN CERTIFICATE-----..."
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="credentials.privateKey"
|
||||
control={control}
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
label="Private Key"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<SecretInput
|
||||
placeholder="-----BEGIN PRIVATE KEY-----..."
|
||||
containerClassName="text-gray-400 group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
|
||||
value={value}
|
||||
onChange={(e) => onChange(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
Reference in New Issue
Block a user