mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 01:16:49 +00:00
merge main
This commit is contained in:
+6
-4
@@ -92,9 +92,9 @@ const CreateForm = ({ app, onComplete }: CreateFormProps) => {
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm />;
|
||||
return <AzureKeyVaultConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationConnectionForm />;
|
||||
return <AzureAppConfigurationConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Databricks:
|
||||
return <DatabricksConnectionForm onSubmit={onSubmit} />;
|
||||
case AppConnection.Humanitec:
|
||||
@@ -200,9 +200,11 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
|
||||
case AppConnection.GCP:
|
||||
return <GcpConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureKeyVault:
|
||||
return <AzureKeyVaultConnectionForm appConnection={appConnection} />;
|
||||
return <AzureKeyVaultConnectionForm appConnection={appConnection} onSubmit={onSubmit} />;
|
||||
case AppConnection.AzureAppConfiguration:
|
||||
return <AzureAppConfigurationConnectionForm appConnection={appConnection} />;
|
||||
return (
|
||||
<AzureAppConfigurationConnectionForm appConnection={appConnection} onSubmit={onSubmit} />
|
||||
);
|
||||
case AppConnection.Databricks:
|
||||
return <DatabricksConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
|
||||
case AppConnection.Humanitec:
|
||||
|
||||
+143
-39
@@ -20,19 +20,83 @@ import {
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureAppConfigurationConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.AzureAppConfiguration),
|
||||
method: z.nativeEnum(AzureAppConfigurationConnectionMethod),
|
||||
tenantId: z.string().trim().optional()
|
||||
method: z.nativeEnum(AzureAppConfigurationConnectionMethod)
|
||||
});
|
||||
|
||||
const oauthSchema = baseSchema.extend({
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required"),
|
||||
method: z.literal(AzureAppConfigurationConnectionMethod.OAuth)
|
||||
});
|
||||
|
||||
const clientSecretSchema = baseSchema.extend({
|
||||
method: z.literal(AzureAppConfigurationConnectionMethod.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 AzureAppConfigurationConnectionForm = ({ appConnection }: Props) => {
|
||||
const getDefaultValues = (appConnection?: TAzureAppConfigurationConnection): Partial<FormData> => {
|
||||
if (!appConnection) {
|
||||
return {
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
method: AzureAppConfigurationConnectionMethod.OAuth
|
||||
};
|
||||
}
|
||||
|
||||
const base = {
|
||||
name: appConnection.name,
|
||||
description: appConnection.description,
|
||||
app: appConnection.app,
|
||||
method: appConnection.method
|
||||
};
|
||||
const { credentials } = appConnection;
|
||||
|
||||
switch (appConnection.method) {
|
||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||
if ("tenantId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureAppConfigurationConnectionMethod.OAuth,
|
||||
tenantId: credentials.tenantId
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AzureAppConfigurationConnectionMethod.ClientSecret:
|
||||
if ("clientSecret" in credentials && "clientId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureAppConfigurationConnectionMethod.ClientSecret,
|
||||
credentials: {
|
||||
clientSecret: credentials.clientSecret,
|
||||
clientId: credentials.clientId,
|
||||
tenantId: credentials.tenantId
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureAppConfigurationConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -43,41 +107,36 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection }: Props) =>
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection
|
||||
? {
|
||||
...appConnection,
|
||||
tenantId: appConnection.credentials.tenantId
|
||||
}
|
||||
: {
|
||||
app: AppConnection.AzureAppConfiguration,
|
||||
method: AzureAppConfigurationConnectionMethod.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 = async (formData: FormData) => {
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureAppConfigurationConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
|
||||
switch (formData.method) {
|
||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||
setIsRedirecting(true);
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureAppConfigurationConnectionFormData",
|
||||
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://azconfig.io/.default%20openid%20offline_access&state=${state}<:>azure-app-configuration`
|
||||
);
|
||||
break;
|
||||
case AzureAppConfigurationConnectionMethod.ClientSecret:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
@@ -89,6 +148,9 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection }: Props) =>
|
||||
case AzureAppConfigurationConnectionMethod.OAuth:
|
||||
isMissingConfig = !oauthClientId;
|
||||
break;
|
||||
case AzureAppConfigurationConnectionMethod.ClientSecret:
|
||||
isMissingConfig = false;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${selectedMethod}`);
|
||||
}
|
||||
@@ -97,25 +159,8 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection }: Props) =>
|
||||
|
||||
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 Azure Active Directory (Entra ID) Tenant ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
@@ -155,6 +200,65 @@ export const AzureAppConfigurationConnectionForm = ({ appConnection }: Props) =>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText="The Azure Active Directory (Entra ID) Tenant ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
isOptional={selectedMethod === AzureAppConfigurationConnectionMethod.OAuth}
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Client Secret-specific fields */}
|
||||
{selectedMethod === AzureAppConfigurationConnectionMethod.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="~JzD8e6S.tH~w8XRaNnKcb7W1fM4rCns7FY"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
+7
-3
@@ -121,7 +121,7 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
|
||||
const onSubmitHandler = (formData: FormData) => {
|
||||
const onSubmitHandler = async (formData: FormData) => {
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
switch (formData.method) {
|
||||
case AzureClientSecretsConnectionMethod.OAuth:
|
||||
@@ -137,7 +137,7 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr
|
||||
break;
|
||||
|
||||
case AzureClientSecretsConnectionMethod.ClientSecret:
|
||||
onSubmit(formData);
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
@@ -240,7 +240,11 @@ export const AzureClientSecretsConnectionForm = ({ appConnection, onSubmit }: Pr
|
||||
label="Client Secret"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} type="password" placeholder="Enter your Client Secret" />
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="~JzD8e6S.tH~w8XRaNnKcb7W1fM4rCns7FY"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
+100
-16
@@ -21,13 +21,6 @@ import {
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type AccessTokenForm = z.infer<typeof accessTokenSchema>;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureDevOpsConnection;
|
||||
onSubmit: (formData: AccessTokenForm) => Promise<void>;
|
||||
};
|
||||
|
||||
// Base schema with common fields
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.AzureDevOps),
|
||||
@@ -49,10 +42,30 @@ const accessTokenSchema = baseSchema.extend({
|
||||
})
|
||||
});
|
||||
|
||||
const clientSecretSchema = baseSchema.extend({
|
||||
method: z.literal(AzureDevOpsConnectionMethod.ClientSecret),
|
||||
credentials: z.object({
|
||||
clientSecret: z.string().trim().min(1, "Client Secret is required"),
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required"),
|
||||
clientId: z.string().trim().min(1, "Client ID is required"),
|
||||
orgName: z.string().trim().min(1, "Organization name is required")
|
||||
})
|
||||
});
|
||||
|
||||
// Union schema
|
||||
const formSchema = z.discriminatedUnion("method", [oauthSchema, accessTokenSchema]);
|
||||
const formSchema = z.discriminatedUnion("method", [
|
||||
oauthSchema,
|
||||
accessTokenSchema,
|
||||
clientSecretSchema
|
||||
]);
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
type OnSubmitForm = z.infer<typeof accessTokenSchema> | z.infer<typeof clientSecretSchema>;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureDevOpsConnection;
|
||||
onSubmit: (formData: OnSubmitForm) => Promise<void>;
|
||||
};
|
||||
|
||||
const getDefaultValues = (appConnection?: TAzureDevOpsConnection): Partial<FormData> => {
|
||||
if (!appConnection) {
|
||||
@@ -93,6 +106,25 @@ const getDefaultValues = (appConnection?: TAzureDevOpsConnection): Partial<FormD
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AzureDevOpsConnectionMethod.ClientSecret:
|
||||
if (
|
||||
"clientSecret" in credentials &&
|
||||
"tenantId" in credentials &&
|
||||
"clientId" in credentials &&
|
||||
"orgName" in credentials
|
||||
) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureDevOpsConnectionMethod.ClientSecret,
|
||||
credentials: {
|
||||
clientSecret: credentials.clientSecret,
|
||||
tenantId: credentials.tenantId,
|
||||
clientId: credentials.clientId,
|
||||
orgName: credentials.orgName
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
@@ -118,7 +150,8 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
formState: { isSubmitting, isDirty }
|
||||
formState: { isSubmitting, isDirty },
|
||||
setValue
|
||||
} = form;
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
@@ -138,11 +171,12 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
`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-devops`
|
||||
);
|
||||
break;
|
||||
|
||||
case AzureDevOpsConnectionMethod.AccessToken:
|
||||
onSubmit(formData);
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
case AzureDevOpsConnectionMethod.ClientSecret:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
@@ -196,8 +230,8 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* OAuth-specific fields */}
|
||||
{selectedMethod === AzureDevOpsConnectionMethod.OAuth && (
|
||||
{(selectedMethod === AzureDevOpsConnectionMethod.OAuth ||
|
||||
selectedMethod === AzureDevOpsConnectionMethod.ClientSecret) && (
|
||||
<>
|
||||
<Controller
|
||||
name="tenantId"
|
||||
@@ -209,7 +243,14 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
label="Tenant ID"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="00000000-0000-0000-0000-000000000000"
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setValue("credentials.tenantId", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
@@ -223,7 +264,50 @@ export const AzureDevOpsConnectionForm = ({ appConnection, onSubmit }: Props) =>
|
||||
label="Organization Name"
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="myorganization" />
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="myorganization"
|
||||
onChange={(e) => {
|
||||
field.onChange(e.target.value);
|
||||
setValue("credentials.orgName", e.target.value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Client Secret-specific fields */}
|
||||
{selectedMethod === AzureDevOpsConnectionMethod.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="~JzD8e6S.tH~w8XRaNnKcb7W1fM4rCns7FY"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
+144
-37
@@ -20,19 +20,83 @@ import {
|
||||
GenericAppConnectionsFields
|
||||
} from "./GenericAppConnectionFields";
|
||||
|
||||
type ClientSecretForm = z.infer<typeof clientSecretSchema>;
|
||||
|
||||
type Props = {
|
||||
appConnection?: TAzureKeyVaultConnection;
|
||||
onSubmit: (formData: ClientSecretForm) => Promise<void>;
|
||||
};
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
const baseSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.AzureKeyVault),
|
||||
method: z.nativeEnum(AzureKeyVaultConnectionMethod),
|
||||
tenantId: z.string().trim().optional()
|
||||
method: z.nativeEnum(AzureKeyVaultConnectionMethod)
|
||||
});
|
||||
|
||||
const oauthSchema = baseSchema.extend({
|
||||
tenantId: z.string().trim().min(1, "Tenant ID is required"),
|
||||
method: z.literal(AzureKeyVaultConnectionMethod.OAuth)
|
||||
});
|
||||
|
||||
const clientSecretSchema = baseSchema.extend({
|
||||
method: z.literal(AzureKeyVaultConnectionMethod.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 AzureKeyVaultConnectionForm = ({ appConnection }: Props) => {
|
||||
const getDefaultValues = (appConnection?: TAzureKeyVaultConnection): Partial<FormData> => {
|
||||
if (!appConnection) {
|
||||
return {
|
||||
app: AppConnection.AzureKeyVault,
|
||||
method: AzureKeyVaultConnectionMethod.OAuth
|
||||
};
|
||||
}
|
||||
|
||||
const base = {
|
||||
name: appConnection.name,
|
||||
description: appConnection.description,
|
||||
app: appConnection.app,
|
||||
method: appConnection.method
|
||||
};
|
||||
const { credentials } = appConnection;
|
||||
|
||||
switch (appConnection.method) {
|
||||
case AzureKeyVaultConnectionMethod.OAuth:
|
||||
if ("tenantId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureKeyVaultConnectionMethod.OAuth,
|
||||
tenantId: credentials.tenantId
|
||||
};
|
||||
}
|
||||
break;
|
||||
case AzureKeyVaultConnectionMethod.ClientSecret:
|
||||
if ("clientSecret" in credentials && "clientId" in credentials) {
|
||||
return {
|
||||
...base,
|
||||
method: AzureKeyVaultConnectionMethod.ClientSecret,
|
||||
credentials: {
|
||||
clientSecret: credentials.clientSecret,
|
||||
clientId: credentials.clientId,
|
||||
tenantId: credentials.tenantId
|
||||
}
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
|
||||
return base;
|
||||
};
|
||||
|
||||
export const AzureKeyVaultConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(appConnection);
|
||||
const [isRedirecting, setIsRedirecting] = useState(false);
|
||||
|
||||
@@ -43,41 +107,37 @@ export const AzureKeyVaultConnectionForm = ({ appConnection }: Props) => {
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection
|
||||
? {
|
||||
...appConnection,
|
||||
tenantId: appConnection.credentials.tenantId
|
||||
}
|
||||
: {
|
||||
app: AppConnection.AzureKeyVault,
|
||||
method: AzureKeyVaultConnectionMethod.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 = async (formData: FormData) => {
|
||||
const state = crypto.randomBytes(16).toString("hex");
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureKeyVaultConnectionFormData",
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
|
||||
switch (formData.method) {
|
||||
case AzureKeyVaultConnectionMethod.OAuth:
|
||||
setIsRedirecting(true);
|
||||
localStorage.setItem("latestCSRFToken", state);
|
||||
localStorage.setItem(
|
||||
"azureKeyVaultConnectionFormData",
|
||||
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://vault.azure.net/.default%20openid%20offline_access&state=${state}<:>azure-key-vault`
|
||||
);
|
||||
break;
|
||||
case AzureKeyVaultConnectionMethod.ClientSecret:
|
||||
await onSubmit(formData);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${(formData as FormData).method}`);
|
||||
}
|
||||
@@ -89,6 +149,9 @@ export const AzureKeyVaultConnectionForm = ({ appConnection }: Props) => {
|
||||
case AzureKeyVaultConnectionMethod.OAuth:
|
||||
isMissingConfig = !oauthClientId;
|
||||
break;
|
||||
case AzureKeyVaultConnectionMethod.ClientSecret:
|
||||
isMissingConfig = false;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unhandled Azure Connection method: ${selectedMethod}`);
|
||||
}
|
||||
@@ -97,25 +160,9 @@ export const AzureKeyVaultConnectionForm = ({ appConnection }: Props) => {
|
||||
|
||||
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 Azure Active Directory (Entra ID) Tenant ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
isOptional
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="e4f34ea5-ad23-4291-8585-66d20d603cc8" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
@@ -155,6 +202,66 @@ export const AzureKeyVaultConnectionForm = ({ appConnection }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Controller
|
||||
name="tenantId"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText="The Azure Active Directory (Entra ID) Tenant ID."
|
||||
isError={Boolean(error?.message)}
|
||||
label="Tenant ID"
|
||||
isOptional={selectedMethod === AzureKeyVaultConnectionMethod.OAuth}
|
||||
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>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* Client Secret-specific fields */}
|
||||
{selectedMethod === AzureKeyVaultConnectionMethod.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="~JzD8e6S.tH~w8XRaNnKcb7W1fM4rCns7FY"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
+114
-5
@@ -3,11 +3,31 @@ import crypto from "crypto";
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem } from "@app/components/v2";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
ModalClose,
|
||||
Select,
|
||||
SelectItem,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
} from "@app/context/OrgPermissionContext/types";
|
||||
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
|
||||
import { isInfisicalCloud } from "@app/helpers/platform";
|
||||
import { gatewaysQueryKeys } from "@app/hooks/api";
|
||||
import {
|
||||
GitHubConnectionMethod,
|
||||
TGitHubConnection,
|
||||
@@ -26,7 +46,12 @@ type Props = {
|
||||
|
||||
const formSchema = genericAppConnectionFieldsSchema.extend({
|
||||
app: z.literal(AppConnection.GitHub),
|
||||
method: z.nativeEnum(GitHubConnectionMethod)
|
||||
method: z.nativeEnum(GitHubConnectionMethod),
|
||||
credentials: z
|
||||
.object({
|
||||
host: z.string().optional()
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
@@ -44,7 +69,8 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: appConnection ?? {
|
||||
app: AppConnection.GitHub,
|
||||
method: GitHubConnectionMethod.App
|
||||
method: GitHubConnectionMethod.App,
|
||||
gatewayId: null
|
||||
}
|
||||
});
|
||||
|
||||
@@ -55,6 +81,9 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
const selectedMethod = watch("method");
|
||||
|
||||
const onSubmit = (formData: FormData) => {
|
||||
@@ -66,15 +95,20 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
JSON.stringify({ ...formData, connectionId: appConnection?.id })
|
||||
);
|
||||
|
||||
const githubHost =
|
||||
formData.credentials?.host && formData.credentials.host.length > 0
|
||||
? `https://${formData.credentials.host}`
|
||||
: "https://github.com";
|
||||
|
||||
switch (formData.method) {
|
||||
case GitHubConnectionMethod.App:
|
||||
window.location.assign(
|
||||
`https://github.com/apps/${appClientSlug}/installations/new?state=${state}`
|
||||
`${githubHost}/apps/${appClientSlug}/installations/new?state=${state}`
|
||||
);
|
||||
break;
|
||||
case GitHubConnectionMethod.OAuth:
|
||||
window.location.assign(
|
||||
`https://github.com/login/oauth/authorize?client_id=${oauthClientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/organization/app-connections/github/oauth/callback&state=${state}`
|
||||
`${githubHost}/login/oauth/authorize?client_id=${oauthClientId}&response_type=code&scope=repo,admin:org&redirect_uri=${window.location.origin}/organization/app-connections/github/oauth/callback&state=${state}`
|
||||
);
|
||||
break;
|
||||
default:
|
||||
@@ -141,6 +175,81 @@ export const GitHubConnectionForm = ({ appConnection }: Props) => {
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{subscription.gateway && (
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="enterprise-options" className="data-[state=open]:border-none">
|
||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
||||
<div className="order-1 ml-3">GitHub Enterprise Options</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent childrenClassName="px-0">
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
<Controller
|
||||
name="credentials.host"
|
||||
control={control}
|
||||
shouldUnregister
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Hostname"
|
||||
isOptional
|
||||
>
|
||||
<Input {...field} placeholder="github.com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)}
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
+50
-46
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
@@ -78,6 +79,7 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
@@ -99,55 +101,57 @@ export const MsSqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
}}
|
||||
>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
{subscription.gateway && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
|
||||
+50
-46
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
@@ -75,6 +76,7 @@ export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
@@ -96,55 +98,57 @@ export const MySqlConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
}}
|
||||
>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
{subscription.gateway && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
|
||||
+50
-46
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
@@ -75,6 +76,7 @@ export const OracleDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
@@ -96,55 +98,57 @@ export const OracleDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
}}
|
||||
>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
{subscription.gateway && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
|
||||
+50
-46
@@ -6,6 +6,7 @@ import { z } from "zod";
|
||||
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Button, FormControl, ModalClose, Select, SelectItem, Tooltip } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import {
|
||||
OrgGatewayPermissionActions,
|
||||
OrgPermissionSubjects
|
||||
@@ -75,6 +76,7 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
const isPlatformManagedCredentials = appConnection?.isPlatformManagedCredentials ?? false;
|
||||
const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list());
|
||||
|
||||
@@ -96,55 +98,57 @@ export const PostgresConnectionForm = ({ appConnection, onSubmit }: Props) => {
|
||||
}}
|
||||
>
|
||||
{!isUpdate && <GenericAppConnectionsFields />}
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
{subscription.gateway && (
|
||||
<OrgPermissionCan
|
||||
I={OrgGatewayPermissionActions.AttachGateways}
|
||||
a={OrgPermissionSubjects.Gateway}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<Controller
|
||||
control={control}
|
||||
name="gatewayId"
|
||||
defaultValue=""
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
label="Gateway"
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
<Tooltip
|
||||
isDisabled={isAllowed}
|
||||
content="Restricted access. You don't have permission to attach gateways to resources."
|
||||
>
|
||||
<div>
|
||||
<Select
|
||||
isDisabled={!isAllowed}
|
||||
value={value as string}
|
||||
onValueChange={onChange}
|
||||
className="w-full border border-mineshaft-500"
|
||||
dropdownContainerClassName="max-w-none"
|
||||
isLoading={isGatewaysLoading}
|
||||
placeholder="Default: Internet Gateway"
|
||||
position="popper"
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
<SelectItem
|
||||
value={null as unknown as string}
|
||||
onClick={() => onChange(undefined)}
|
||||
>
|
||||
Internet Gateway
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{gateways?.map((el) => (
|
||||
<SelectItem value={el.id} key={el.id}>
|
||||
{el.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</div>
|
||||
</Tooltip>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
<Controller
|
||||
name="method"
|
||||
control={control}
|
||||
|
||||
+19
-10
@@ -30,7 +30,8 @@ type BaseFormData = {
|
||||
isUpdate?: boolean;
|
||||
};
|
||||
|
||||
type GithubFormData = BaseFormData & Pick<TGitHubConnection, "name" | "method" | "description">;
|
||||
type GithubFormData = BaseFormData &
|
||||
Pick<TGitHubConnection, "name" | "method" | "description" | "gatewayId" | "credentials">;
|
||||
|
||||
type GithubRadarFormData = BaseFormData &
|
||||
Pick<TGitHubRadarConnection, "name" | "method" | "description">;
|
||||
@@ -395,7 +396,7 @@ export const OAuthCallbackPage = () => {
|
||||
|
||||
clearState(AppConnection.GitHub);
|
||||
|
||||
const { connectionId, name, description, returnUrl } = formData;
|
||||
const { connectionId, name, description, returnUrl, gatewayId, credentials } = formData;
|
||||
|
||||
try {
|
||||
if (connectionId) {
|
||||
@@ -406,14 +407,18 @@ export const OAuthCallbackPage = () => {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
}
|
||||
installationId: installationId as string,
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
}
|
||||
: {
|
||||
connectionId,
|
||||
credentials: {
|
||||
code: code as string
|
||||
}
|
||||
code: code as string,
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
})
|
||||
});
|
||||
} else {
|
||||
@@ -426,14 +431,18 @@ export const OAuthCallbackPage = () => {
|
||||
method: GitHubConnectionMethod.App,
|
||||
credentials: {
|
||||
code: code as string,
|
||||
installationId: installationId as string
|
||||
}
|
||||
installationId: installationId as string,
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
}
|
||||
: {
|
||||
method: GitHubConnectionMethod.OAuth,
|
||||
credentials: {
|
||||
code: code as string
|
||||
}
|
||||
code: code as string,
|
||||
...(credentials?.host && { host: credentials.host })
|
||||
},
|
||||
gatewayId
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
+7
@@ -32,6 +32,13 @@ export const PmtMethodsTable = () => {
|
||||
|
||||
const handleDeletePmtMethodBtnClick = async () => {
|
||||
if (!currentOrg?.id || !pmtMethodToRemove) return;
|
||||
if (data?.length === 1) {
|
||||
createNotification({
|
||||
type: "error",
|
||||
text: "You must have at least one payment method"
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteOrgPmtMethod.mutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
|
||||
@@ -2,14 +2,11 @@
|
||||
import { useState } from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { NewProjectModal } from "@app/components/projects";
|
||||
import { PageHeader } from "@app/components/v2";
|
||||
import { useSubscription } from "@app/context";
|
||||
import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { AllProjectView } from "./components/AllProjectView";
|
||||
@@ -38,8 +35,6 @@ export const ProjectsPage = () => {
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { data: serverDetails, isLoading } = useFetchServerStatus();
|
||||
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||
@@ -52,30 +47,6 @@ export const ProjectsPage = () => {
|
||||
<title>{t("common.head-title", { title: t("settings.members.title") })}</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Helmet>
|
||||
|
||||
{!isLoading && !serverDetails?.redisConfigured && (
|
||||
<div className="mb-4 flex flex-col items-start justify-start text-3xl">
|
||||
<p className="mb-4 mr-4 font-semibold text-white">Announcements</p>
|
||||
<div className="flex w-full items-center rounded-md border border-blue-400/70 bg-blue-900/70 p-2 text-base text-mineshaft-100">
|
||||
<FontAwesomeIcon
|
||||
icon={faExclamationCircle}
|
||||
className="mr-4 p-4 text-2xl text-mineshaft-50"
|
||||
/>
|
||||
Attention: Updated versions of Infisical now require Redis for full functionality. Learn
|
||||
how to configure it
|
||||
<a
|
||||
href="https://infisical.com/docs/self-hosting/configuration/redis"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<span className="cursor-pointer pl-1 text-white underline underline-offset-2 duration-100 hover:text-blue-200 hover:decoration-blue-400">
|
||||
here
|
||||
</span>
|
||||
</a>
|
||||
.
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="mb-4 flex flex-col items-start justify-start">
|
||||
<PageHeader
|
||||
title={
|
||||
|
||||
Reference in New Issue
Block a user