diff --git a/backend/src/controllers/v3/index.ts b/backend/src/controllers/v3/index.ts index fa853d8c5..e52b1a0ea 100644 --- a/backend/src/controllers/v3/index.ts +++ b/backend/src/controllers/v3/index.ts @@ -2,12 +2,10 @@ import * as secretsController from "./secretsController"; import * as workspacesController from "./workspacesController"; import * as authController from "./authController"; import * as signupController from "./signupController"; -import * as serviceTokenDataController from "./serviceTokenDataController"; export { authController, secretsController, signupController, - workspacesController, - serviceTokenDataController + workspacesController } diff --git a/backend/src/ee/controllers/v3/index.ts b/backend/src/ee/controllers/v3/index.ts new file mode 100644 index 000000000..af6f3d306 --- /dev/null +++ b/backend/src/ee/controllers/v3/index.ts @@ -0,0 +1,5 @@ +import * as serviceTokenDataController from "./serviceTokenDataController"; + +export { + serviceTokenDataController +} \ No newline at end of file diff --git a/backend/src/controllers/v3/serviceTokenDataController.ts b/backend/src/ee/controllers/v3/serviceTokenDataController.ts similarity index 83% rename from backend/src/controllers/v3/serviceTokenDataController.ts rename to backend/src/ee/controllers/v3/serviceTokenDataController.ts index 749489062..2c5d6d3d4 100644 --- a/backend/src/controllers/v3/serviceTokenDataController.ts +++ b/backend/src/ee/controllers/v3/serviceTokenDataController.ts @@ -4,29 +4,30 @@ import { IServiceTokenDataV3, IUser, ServiceTokenDataV3, - ServiceTokenDataV3Key -} from "../../models"; + ServiceTokenDataV3Key, + Workspace +} from "../../../models"; import { IServiceTokenV3Scope, IServiceTokenV3TrustedIp -} from "../../models/serviceTokenDataV3"; +} from "../../../models/serviceTokenDataV3"; import { ActorType, EventType -} from "../../ee/models"; -import { validateRequest } from "../../helpers/validation"; -import * as reqValidator from "../../validation/serviceTokenDataV3"; -import { createToken } from "../../helpers/auth"; +} from "../../models"; +import { validateRequest } from "../../../helpers/validation"; +import * as reqValidator from "../../../validation/serviceTokenDataV3"; +import { createToken } from "../../../helpers/auth"; import { ProjectPermissionActions, ProjectPermissionSub, getUserProjectPermissions -} from "../../ee/services/ProjectRoleService"; +} from "../../services/ProjectRoleService"; import { ForbiddenError } from "@casl/ability"; -import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; -import { extractIPDetails, isValidIpOrCidr } from "../../utils/ip"; -import { EEAuditLogService } from "../../ee/services"; -import { getJwtServiceTokenSecret } from "../../config"; +import { BadRequestError, ResourceNotFoundError } from "../../../utils/errors"; +import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip"; +import { EEAuditLogService, EELicenseService } from "../../services"; +import { getJwtServiceTokenSecret } from "../../../config"; /** * Return project key for service token @@ -80,8 +81,17 @@ export const createServiceTokenData = async (req: Request, res: Response) => { ProjectPermissionSub.ServiceTokens ); + const workspace = await Workspace.findById(workspaceId); + if (!workspace) throw BadRequestError({ message: "Workspace not found" }); + + const plan = await EELicenseService.getPlan(workspace.organization); + // validate trusted ips const reformattedTrustedIps = trustedIps.map((trustedIp) => { + if (!plan.ipAllowlisting && trustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({ + message: "Failed to add IP access range to service token due to plan restriction. Upgrade plan to add IP access range." + }); + const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress); if (!isValidIPOrCidr) return res.status(400).send({ @@ -173,7 +183,6 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { } = await validateRequest(reqValidator.UpdateServiceTokenV3, req); let serviceTokenData = await ServiceTokenDataV3.findById(serviceTokenDataId); - if (!serviceTokenData) throw ResourceNotFoundError({ message: "Service token not found" }); @@ -188,10 +197,19 @@ export const updateServiceTokenData = async (req: Request, res: Response) => { ProjectPermissionSub.ServiceTokens ); + const workspace = await Workspace.findById(serviceTokenData.workspace); + if (!workspace) throw BadRequestError({ message: "Workspace not found" }); + + const plan = await EELicenseService.getPlan(workspace.organization); + // validate trusted ips let reformattedTrustedIps; if (trustedIps) { reformattedTrustedIps = trustedIps.map((trustedIp) => { + if (!plan.ipAllowlisting && trustedIp.ipAddress !== "0.0.0.0/0") return res.status(400).send({ + message: "Failed to update IP access range to service token due to plan restriction. Upgrade plan to update IP access range." + }); + const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress); if (!isValidIPOrCidr) return res.status(400).send({ diff --git a/backend/src/ee/routes/v3/index.ts b/backend/src/ee/routes/v3/index.ts new file mode 100644 index 000000000..7f75a2755 --- /dev/null +++ b/backend/src/ee/routes/v3/index.ts @@ -0,0 +1,5 @@ +import serviceTokenData from "./serviceTokenData"; + +export { + serviceTokenData +} \ No newline at end of file diff --git a/backend/src/routes/v3/serviceTokenData.ts b/backend/src/ee/routes/v3/serviceTokenData.ts similarity index 88% rename from backend/src/routes/v3/serviceTokenData.ts rename to backend/src/ee/routes/v3/serviceTokenData.ts index 83c111f2a..2f421c7ec 100644 --- a/backend/src/routes/v3/serviceTokenData.ts +++ b/backend/src/ee/routes/v3/serviceTokenData.ts @@ -1,7 +1,7 @@ import express from "express"; const router = express.Router(); -import { requireAuth } from "../../middleware"; -import { AuthMode } from "../../variables"; +import { requireAuth } from "../../../middleware"; +import { AuthMode } from "../../../variables"; import { serviceTokenDataController } from "../../controllers/v3"; router.get( diff --git a/backend/src/index.ts b/backend/src/index.ts index bd2f2b12a..3b807b2f5 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -27,6 +27,9 @@ import { roles as v1RoleRouter, secretScanning as v1SecretScanningRouter } from "./ee/routes/v1"; +import { + serviceTokenData as v3ServiceTokenDataRouter +} from "./ee/routes/v3"; import { auth as v1AuthRouter, bot as v1BotRouter, @@ -55,7 +58,6 @@ import { organizations as v2OrganizationsRouter, secret as v2SecretRouter, // begin to phase out secrets as v2SecretsRouter, - serviceAccounts as v2ServiceAccountsRouter, serviceTokenData as v2ServiceTokenDataRouter, signup as v2SignupRouter, tags as v2TagsRouter, @@ -66,8 +68,7 @@ import { auth as v3AuthRouter, secrets as v3SecretsRouter, signup as v3SignupRouter, - workspaces as v3WorkspacesRouter, - serviceTokenData as v3ServiceTokenDataRouter + workspaces as v3WorkspacesRouter } from "./routes/v3"; import { healthCheck } from "./routes/status"; import { getLogger } from "./utils/logger"; @@ -155,6 +156,7 @@ const main = async () => { app.use("/api/v1/organizations", eeOrganizationsRouter); app.use("/api/v1/sso", eeSSORouter); app.use("/api/v1/cloud-products", eeCloudProductsRouter); + app.use("/api/v3/service-token", v3ServiceTokenDataRouter); // v1 routes app.use("/api/v1/signup", v1SignupRouter); @@ -198,8 +200,6 @@ const main = async () => { app.use("/api/v3/secrets", v3SecretsRouter); app.use("/api/v3/workspaces", v3WorkspacesRouter); app.use("/api/v3/signup", v3SignupRouter); - app.use("/api/v3/service-token", v3ServiceTokenDataRouter); - // api docs app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile)); diff --git a/backend/src/routes/v3/index.ts b/backend/src/routes/v3/index.ts index c122fc6ed..1a95439ab 100644 --- a/backend/src/routes/v3/index.ts +++ b/backend/src/routes/v3/index.ts @@ -2,12 +2,10 @@ import auth from "./auth"; import secrets from "./secrets"; import workspaces from "./workspaces"; import signup from "./signup"; -import serviceTokenData from "./serviceTokenData"; export { auth, secrets, signup, - workspaces, - serviceTokenData + workspaces } diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx index 0b46a3eb5..07e04e4d6 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/AddServiceTokenV3Modal.tsx @@ -20,9 +20,13 @@ import { Modal, ModalContent, Select, - SelectItem + SelectItem, + UpgradePlanModal } from "@app/components/v2"; -import { useWorkspace } from "@app/context"; +import { + useSubscription, + useWorkspace +} from "@app/context"; import { useCreateServiceTokenV3, useGetUserWsKey, @@ -88,14 +92,17 @@ const schema = yup.object({ export type FormData = yup.InferType; type Props = { - popUp: UsePopUpState<["serviceTokenV3"]>; + popUp: UsePopUpState<["serviceTokenV3", "upgradePlan"]>; + handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void; handlePopUpToggle: (popUpName: keyof UsePopUpState<["serviceTokenV3"]>, state?: boolean) => void; }; export const AddServiceTokenV3Modal = ({ popUp, + handlePopUpOpen, handlePopUpToggle }: Props) => { + const { subscription } = useSubscription(); const { currentWorkspace } = useWorkspace(); const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? ""); @@ -400,19 +407,39 @@ export const AddServiceTokenV3Modal = ({ control={control} name={`trustedIps.${index}.ipAddress`} defaultValue="0.0.0.0/0" - render={({ field, fieldState: { error } }) => ( - - - - )} + render={({ field, fieldState: { error } }) => { + return ( + + { + if (subscription?.ipAllowlisting) { + field.onChange(e); + return; + } + + handlePopUpOpen("upgradePlan"); + }} + placeholder="123.456.789.0" + /> + + ); + }} /> removeTrustedIp(index)} + onClick={() => { + if (subscription?.ipAllowlisting) { + removeTrustedIp(index); + return; + } + + handlePopUpOpen("upgradePlan"); + }} size="lg" colorSchema="danger" variant="plain" @@ -426,11 +453,16 @@ export const AddServiceTokenV3Modal = ({
+ handlePopUpToggle("upgradePlan", isOpen)} + text="You can use IP allowlisting if you switch to Infisical's Pro plan." + /> ); diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx index 574415742..3b00202f9 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ServiceTokenV3Section/ServiceTokenV3Section.tsx @@ -23,7 +23,8 @@ export const ServiceTokenV3Section = withProjectPermission( const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "serviceTokenV3", - "deleteServiceTokenV3" + "deleteServiceTokenV3", + "upgradePlan" ] as const); const onDeleteServiceTokenDataSubmit = async (serviceTokenDataId: string) => { @@ -74,6 +75,7 @@ export const ServiceTokenV3Section = withProjectPermission( />