final review changes

This commit is contained in:
x032205
2025-10-22 21:41:33 -04:00
parent 4a7f4e101b
commit 39c66889a9
2 changed files with 199 additions and 91 deletions
@@ -1,6 +1,6 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { SingleValue } from "react-select"; import { SingleValue } from "react-select";
import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; import { faCopy, faQuestionCircle, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useNavigate } from "@tanstack/react-router"; import { useNavigate } from "@tanstack/react-router";
import { z } from "zod"; import { z } from "zod";
@@ -8,14 +8,21 @@ import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { import {
Button, Button,
Checkbox,
FilterableSelect, FilterableSelect,
FormLabel, FormLabel,
IconButton, IconButton,
Input, Input,
ModalClose ModalClose,
Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { useOrganization } from "@app/context"; import {
OrgPermissionIdentityActions,
OrgPermissionSubjects,
useOrganization,
useOrgPermission
} from "@app/context";
import { import {
useAddIdentityTokenAuth, useAddIdentityTokenAuth,
useCreateTokenIdentityTokenAuth, useCreateTokenIdentityTokenAuth,
@@ -27,7 +34,7 @@ import { slugSchema } from "@app/lib/schemas";
import { RelayOption } from "./RelayOption"; import { RelayOption } from "./RelayOption";
const formSchema = z.object({ const baseFormSchema = z.object({
name: slugSchema({ field: "name" }), name: slugSchema({ field: "name" }),
instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")), instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")),
relay: z relay: z
@@ -39,7 +46,10 @@ const formSchema = z.object({
{ required_error: "Relay is required" } { required_error: "Relay is required" }
) )
.nullable() .nullable()
.refine((val) => val !== null, { message: "Relay is required" }), .refine((val) => val !== null, { message: "Relay is required" })
});
const formSchemaWithIdentity = baseFormSchema.extend({
identity: z identity: z
.object( .object(
{ {
@@ -52,6 +62,10 @@ const formSchema = z.object({
.refine((val) => val !== null, { message: "Identity is required" }) .refine((val) => val !== null, { message: "Identity is required" })
}); });
const formSchemaWithToken = baseFormSchema.extend({
identityToken: z.string().min(1, "Token is required")
});
export const GatewayCliDeploymentMethod = () => { export const GatewayCliDeploymentMethod = () => {
const { protocol, hostname, port } = window.location; const { protocol, hostname, port } = window.location;
const portSuffix = port && port !== "80" ? `:${port}` : ""; const portSuffix = port && port !== "80" ? `:${port}` : "";
@@ -61,6 +75,7 @@ export const GatewayCliDeploymentMethod = () => {
from: ROUTE_PATHS.Organization.NetworkingPage.path from: ROUTE_PATHS.Organization.NetworkingPage.path
}); });
const [autogenerateToken, setAutogenerateToken] = useState(true);
const [step, setStep] = useState<"form" | "command">("form"); const [step, setStep] = useState<"form" | "command">("form");
const [name, setName] = useState(""); const [name, setName] = useState("");
const [instanceDomain, setInstanceDomain] = useState(siteURL); const [instanceDomain, setInstanceDomain] = useState(siteURL);
@@ -90,6 +105,12 @@ export const GatewayCliDeploymentMethod = () => {
const { currentOrg } = useOrganization(); const { currentOrg } = useOrganization();
const organizationId = currentOrg?.id || ""; const organizationId = currentOrg?.id || "";
const { permission } = useOrgPermission();
const canCreateToken = permission.can(
OrgPermissionIdentityActions.CreateToken,
OrgPermissionSubjects.Identity
);
const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } =
useGetIdentityMembershipOrgs({ useGetIdentityMembershipOrgs({
organizationId, organizationId,
@@ -105,48 +126,68 @@ export const GatewayCliDeploymentMethod = () => {
const handleGenerateCommand = async () => { const handleGenerateCommand = async () => {
setFormErrors([]); setFormErrors([]);
const validation = formSchema.safeParse({ name, relay, identity, instanceDomain });
if (!validation.success) {
setFormErrors(validation.error.issues);
return;
}
const validatedIdentity = validation.data.identity; if (canCreateToken && autogenerateToken) {
const validation = formSchemaWithIdentity.safeParse({
try { name,
const { data: identityTokenAuth } = await refetch(); relay,
if (!identityTokenAuth) { identity,
await addIdentityTokenAuth({ instanceDomain
identityId: validatedIdentity.id, });
organizationId, if (!validation.success) {
accessTokenTTL: 2592000, setFormErrors(validation.error.issues);
accessTokenMaxTTL: 2592000, return;
accessTokenNumUsesLimit: 0,
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
});
createNotification({
text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.",
type: "warning"
});
} }
const token = await createToken({ const validatedIdentity = validation.data.identity;
identityId: validatedIdentity.id,
name: `gateway token for ${name} (autogenerated)` try {
}); const { data: identityTokenAuth } = await refetch();
setIdentityToken(token.accessToken); if (!identityTokenAuth) {
createNotification({ await addIdentityTokenAuth({
text: "Automatically generated a token for the selected identity.", identityId: validatedIdentity.id,
type: "info" organizationId,
accessTokenTTL: 2592000,
accessTokenMaxTTL: 2592000,
accessTokenNumUsesLimit: 0,
accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }]
});
createNotification({
text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.",
type: "warning"
});
}
const token = await createToken({
identityId: validatedIdentity.id,
name: `gateway token for ${name} (autogenerated)`
});
setIdentityToken(token.accessToken);
createNotification({
text: "Automatically generated a token for the selected identity.",
type: "info"
});
setStep("command");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to generate token for the selected identity",
type: "error"
});
setIdentityToken("");
}
} else {
const validation = formSchemaWithToken.safeParse({
name,
relay,
identityToken,
instanceDomain
}); });
if (!validation.success) {
setFormErrors(validation.error.issues);
return;
}
setStep("command"); setStep("command");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to generate token for the selected identity",
type: "error"
});
setIdentityToken("");
} }
}; };
@@ -239,8 +280,8 @@ export const GatewayCliDeploymentMethod = () => {
{errors.relay && <p className="mt-1 text-sm text-red">{errors.relay}</p>} {errors.relay && <p className="mt-1 text-sm text-red">{errors.relay}</p>}
<FormLabel <FormLabel
label="Instance Domain" label="Infisical Instance Host Address"
tooltipText="The domain of the infisical instance that's accessible by the gateway." tooltipText="The host address of the infisical instance that's accessible by the gateway."
className="mt-4" className="mt-4"
/> />
<Input <Input
@@ -251,28 +292,79 @@ export const GatewayCliDeploymentMethod = () => {
/> />
{errors.instanceDomain && <p className="mt-1 text-sm text-red">{errors.instanceDomain}</p>} {errors.instanceDomain && <p className="mt-1 text-sm text-red">{errors.instanceDomain}</p>}
<FormLabel {canCreateToken && autogenerateToken ? (
label="Identity" <>
tooltipText="The identity that your gateway will use for authentication." <FormLabel
className="mt-4" label="Identity"
/> tooltipText="The identity that your gateway will use for authentication."
<FilterableSelect className="mt-4"
value={identity} />
onChange={(e) => <FilterableSelect
setIdentity( value={identity}
e as SingleValue<{ onChange={(e) =>
id: string; setIdentity(
name: string; e as SingleValue<{
}> id: string;
) name: string;
} }>
isLoading={isIdentitiesLoading} )
placeholder="Select identity..." }
options={identityMembershipOrgs.map((membership) => membership.identity)} isLoading={isIdentitiesLoading}
getOptionValue={(option) => option.id} placeholder="Select identity..."
getOptionLabel={(option) => option.name} options={identityMembershipOrgs.map((membership) => membership.identity)}
/> getOptionValue={(option) => option.id}
{errors.identity && <p className="mt-1 text-sm text-red">{errors.identity}</p>} getOptionLabel={(option) => option.name}
/>
{errors.identity && <p className="mt-1 text-sm text-red">{errors.identity}</p>}
</>
) : (
<>
<FormLabel
label="Identity Token"
tooltipText="The identity token that your relay will use for authentication."
className="mt-4"
/>
<Input
value={identityToken}
onChange={(e) => setIdentityToken(e.target.value)}
placeholder="Enter identity token..."
isError={Boolean(errors.identityToken)}
/>
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
</>
)}
{canCreateToken && (
<div className="mt-2">
<Checkbox
isChecked={autogenerateToken}
onCheckedChange={(e) => {
setAutogenerateToken(Boolean(e));
}}
id="autogenerate-token"
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn't already configured. By default, it will be configured to allow all IP
addresses with a token TTL of 30 days. You can manage these settings in Access
Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="mt-0.5 ml-1" />
</Tooltip>
</div>
</Checkbox>
</div>
)}
<div className="mt-6 flex items-center"> <div className="mt-6 flex items-center">
<Button <Button
@@ -1,17 +1,19 @@
import { useMemo, useState } from "react"; import { useMemo, useState } from "react";
import { SingleValue } from "react-select"; import { SingleValue } from "react-select";
import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; import { faCopy, faQuestionCircle, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { import {
Button, Button,
Checkbox,
FilterableSelect, FilterableSelect,
FormLabel, FormLabel,
IconButton, IconButton,
Input, Input,
ModalClose ModalClose,
Tooltip
} from "@app/components/v2"; } from "@app/components/v2";
import { import {
OrgPermissionIdentityActions, OrgPermissionIdentityActions,
@@ -19,7 +21,6 @@ import {
useOrganization, useOrganization,
useOrgPermission useOrgPermission
} from "@app/context"; } from "@app/context";
import { import {
useAddIdentityTokenAuth, useAddIdentityTokenAuth,
useCreateTokenIdentityTokenAuth, useCreateTokenIdentityTokenAuth,
@@ -56,7 +57,7 @@ export const RelayCliDeploymentMethod = () => {
const portSuffix = port && port !== "80" ? `:${port}` : ""; const portSuffix = port && port !== "80" ? `:${port}` : "";
const siteURL = `${protocol}//${hostname}${portSuffix}`; const siteURL = `${protocol}//${hostname}${portSuffix}`;
const [addTokenManually, setAddTokenManually] = useState(false); const [autogenerateToken, setAutogenerateToken] = useState(true);
const [step, setStep] = useState<"form" | "command">("form"); const [step, setStep] = useState<"form" | "command">("form");
const [name, setName] = useState(""); const [name, setName] = useState("");
const [host, setHost] = useState(""); const [host, setHost] = useState("");
@@ -104,7 +105,7 @@ export const RelayCliDeploymentMethod = () => {
const handleGenerateCommand = async () => { const handleGenerateCommand = async () => {
setFormErrors([]); setFormErrors([]);
if (canCreateToken) { if (canCreateToken && autogenerateToken) {
const validation = formSchemaWithIdentity.safeParse({ name, host, instanceDomain, identity }); const validation = formSchemaWithIdentity.safeParse({ name, host, instanceDomain, identity });
if (!validation.success) { if (!validation.success) {
setFormErrors(validation.error.issues); setFormErrors(validation.error.issues);
@@ -244,8 +245,8 @@ export const RelayCliDeploymentMethod = () => {
{errors.host && <p className="mt-1 text-sm text-red">{errors.host}</p>} {errors.host && <p className="mt-1 text-sm text-red">{errors.host}</p>}
<FormLabel <FormLabel
label="Instance Domain" label="Infisical Instance Host Address"
tooltipText="The domain of the infisical instance that's accessible by the relay." tooltipText="The host address of the infisical instance that's accessible by the relay."
className="mt-4" className="mt-4"
/> />
<Input <Input
@@ -256,7 +257,7 @@ export const RelayCliDeploymentMethod = () => {
/> />
{errors.instanceDomain && <p className="mt-1 text-sm text-red">{errors.instanceDomain}</p>} {errors.instanceDomain && <p className="mt-1 text-sm text-red">{errors.instanceDomain}</p>}
{canCreateToken && !addTokenManually ? ( {canCreateToken && autogenerateToken ? (
<> <>
<FormLabel <FormLabel
label="Identity" label="Identity"
@@ -279,15 +280,6 @@ export const RelayCliDeploymentMethod = () => {
getOptionValue={(option) => option.id} getOptionValue={(option) => option.id}
getOptionLabel={(option) => option.name} getOptionLabel={(option) => option.name}
/> />
<p className="mt-1 text-xs text-yellow">
Selecting an identity will automatically enable token auth and generate a token.{" "}
<button
onClick={() => setAddTokenManually(true)}
className="cursor-pointer underline transition-opacity hover:opacity-70"
>
Add token manually
</button>
</p>
{errors.identity && <p className="mt-1 text-sm text-red">{errors.identity}</p>} {errors.identity && <p className="mt-1 text-sm text-red">{errors.identity}</p>}
</> </>
) : ( ) : (
@@ -303,18 +295,42 @@ export const RelayCliDeploymentMethod = () => {
placeholder="Enter identity token..." placeholder="Enter identity token..."
isError={Boolean(errors.identityToken)} isError={Boolean(errors.identityToken)}
/> />
{canCreateToken && (
<button
onClick={() => setAddTokenManually(false)}
className="mt-1 cursor-pointer text-xs text-mineshaft-400 underline transition-opacity hover:opacity-70"
>
Autogenerate token for identity
</button>
)}
{errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>} {errors.identityToken && <p className="mt-1 text-sm text-red">{errors.identityToken}</p>}
</> </>
)} )}
{canCreateToken && (
<div className="mt-2">
<Checkbox
isChecked={autogenerateToken}
onCheckedChange={(e) => {
setAutogenerateToken(Boolean(e));
}}
id="autogenerate-token"
className="mr-2"
>
<div className="flex items-center">
<span>Automatically enable token auth and generate a token for identity</span>
<Tooltip
className="max-w-md"
content={
<>
Token authentication will be automatically enabled for the selected identity if
it isn't already configured. By default, it will be configured to allow all IP
addresses with a token TTL of 30 days. You can manage these settings in Access
Control.
<br />
<br />A token will automatically be generated to be used with the CLI command.
</>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="mt-0.5 ml-1" />
</Tooltip>
</div>
</Checkbox>
</div>
)}
<div className="mt-6 flex items-center"> <div className="mt-6 flex items-center">
<Button <Button
className="mr-4" className="mr-4"