Add paywall to ST V3 ip allowlisting

This commit is contained in:
Tuan Dang
2023-09-30 21:55:18 +01:00
parent eb2f433f43
commit 839fcc2775
9 changed files with 109 additions and 46 deletions
+1 -3
View File
@@ -2,12 +2,10 @@ import * as secretsController from "./secretsController";
import * as workspacesController from "./workspacesController"; import * as workspacesController from "./workspacesController";
import * as authController from "./authController"; import * as authController from "./authController";
import * as signupController from "./signupController"; import * as signupController from "./signupController";
import * as serviceTokenDataController from "./serviceTokenDataController";
export { export {
authController, authController,
secretsController, secretsController,
signupController, signupController,
workspacesController, workspacesController
serviceTokenDataController
} }
+5
View File
@@ -0,0 +1,5 @@
import * as serviceTokenDataController from "./serviceTokenDataController";
export {
serviceTokenDataController
}
@@ -4,29 +4,30 @@ import {
IServiceTokenDataV3, IServiceTokenDataV3,
IUser, IUser,
ServiceTokenDataV3, ServiceTokenDataV3,
ServiceTokenDataV3Key ServiceTokenDataV3Key,
} from "../../models"; Workspace
} from "../../../models";
import { import {
IServiceTokenV3Scope, IServiceTokenV3Scope,
IServiceTokenV3TrustedIp IServiceTokenV3TrustedIp
} from "../../models/serviceTokenDataV3"; } from "../../../models/serviceTokenDataV3";
import { import {
ActorType, ActorType,
EventType EventType
} from "../../ee/models"; } from "../../models";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../validation/serviceTokenDataV3"; import * as reqValidator from "../../../validation/serviceTokenDataV3";
import { createToken } from "../../helpers/auth"; import { createToken } from "../../../helpers/auth";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionSub, ProjectPermissionSub,
getUserProjectPermissions getUserProjectPermissions
} from "../../ee/services/ProjectRoleService"; } from "../../services/ProjectRoleService";
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import { BadRequestError, ResourceNotFoundError } from "../../utils/errors"; import { BadRequestError, ResourceNotFoundError } from "../../../utils/errors";
import { extractIPDetails, isValidIpOrCidr } from "../../utils/ip"; import { extractIPDetails, isValidIpOrCidr } from "../../../utils/ip";
import { EEAuditLogService } from "../../ee/services"; import { EEAuditLogService, EELicenseService } from "../../services";
import { getJwtServiceTokenSecret } from "../../config"; import { getJwtServiceTokenSecret } from "../../../config";
/** /**
* Return project key for service token * Return project key for service token
@@ -80,8 +81,17 @@ export const createServiceTokenData = async (req: Request, res: Response) => {
ProjectPermissionSub.ServiceTokens 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 // validate trusted ips
const reformattedTrustedIps = trustedIps.map((trustedIp) => { 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); const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress);
if (!isValidIPOrCidr) return res.status(400).send({ if (!isValidIPOrCidr) return res.status(400).send({
@@ -173,7 +183,6 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
} = await validateRequest(reqValidator.UpdateServiceTokenV3, req); } = await validateRequest(reqValidator.UpdateServiceTokenV3, req);
let serviceTokenData = await ServiceTokenDataV3.findById(serviceTokenDataId); let serviceTokenData = await ServiceTokenDataV3.findById(serviceTokenDataId);
if (!serviceTokenData) throw ResourceNotFoundError({ if (!serviceTokenData) throw ResourceNotFoundError({
message: "Service token not found" message: "Service token not found"
}); });
@@ -188,10 +197,19 @@ export const updateServiceTokenData = async (req: Request, res: Response) => {
ProjectPermissionSub.ServiceTokens 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 // validate trusted ips
let reformattedTrustedIps; let reformattedTrustedIps;
if (trustedIps) { if (trustedIps) {
reformattedTrustedIps = trustedIps.map((trustedIp) => { 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); const isValidIPOrCidr = isValidIpOrCidr(trustedIp.ipAddress);
if (!isValidIPOrCidr) return res.status(400).send({ if (!isValidIPOrCidr) return res.status(400).send({
+5
View File
@@ -0,0 +1,5 @@
import serviceTokenData from "./serviceTokenData";
export {
serviceTokenData
}
@@ -1,7 +1,7 @@
import express from "express"; import express from "express";
const router = express.Router(); const router = express.Router();
import { requireAuth } from "../../middleware"; import { requireAuth } from "../../../middleware";
import { AuthMode } from "../../variables"; import { AuthMode } from "../../../variables";
import { serviceTokenDataController } from "../../controllers/v3"; import { serviceTokenDataController } from "../../controllers/v3";
router.get( router.get(
+5 -5
View File
@@ -27,6 +27,9 @@ import {
roles as v1RoleRouter, roles as v1RoleRouter,
secretScanning as v1SecretScanningRouter secretScanning as v1SecretScanningRouter
} from "./ee/routes/v1"; } from "./ee/routes/v1";
import {
serviceTokenData as v3ServiceTokenDataRouter
} from "./ee/routes/v3";
import { import {
auth as v1AuthRouter, auth as v1AuthRouter,
bot as v1BotRouter, bot as v1BotRouter,
@@ -55,7 +58,6 @@ import {
organizations as v2OrganizationsRouter, organizations as v2OrganizationsRouter,
secret as v2SecretRouter, // begin to phase out secret as v2SecretRouter, // begin to phase out
secrets as v2SecretsRouter, secrets as v2SecretsRouter,
serviceAccounts as v2ServiceAccountsRouter,
serviceTokenData as v2ServiceTokenDataRouter, serviceTokenData as v2ServiceTokenDataRouter,
signup as v2SignupRouter, signup as v2SignupRouter,
tags as v2TagsRouter, tags as v2TagsRouter,
@@ -66,8 +68,7 @@ import {
auth as v3AuthRouter, auth as v3AuthRouter,
secrets as v3SecretsRouter, secrets as v3SecretsRouter,
signup as v3SignupRouter, signup as v3SignupRouter,
workspaces as v3WorkspacesRouter, workspaces as v3WorkspacesRouter
serviceTokenData as v3ServiceTokenDataRouter
} from "./routes/v3"; } from "./routes/v3";
import { healthCheck } from "./routes/status"; import { healthCheck } from "./routes/status";
import { getLogger } from "./utils/logger"; import { getLogger } from "./utils/logger";
@@ -155,6 +156,7 @@ const main = async () => {
app.use("/api/v1/organizations", eeOrganizationsRouter); app.use("/api/v1/organizations", eeOrganizationsRouter);
app.use("/api/v1/sso", eeSSORouter); app.use("/api/v1/sso", eeSSORouter);
app.use("/api/v1/cloud-products", eeCloudProductsRouter); app.use("/api/v1/cloud-products", eeCloudProductsRouter);
app.use("/api/v3/service-token", v3ServiceTokenDataRouter);
// v1 routes // v1 routes
app.use("/api/v1/signup", v1SignupRouter); app.use("/api/v1/signup", v1SignupRouter);
@@ -198,8 +200,6 @@ const main = async () => {
app.use("/api/v3/secrets", v3SecretsRouter); app.use("/api/v3/secrets", v3SecretsRouter);
app.use("/api/v3/workspaces", v3WorkspacesRouter); app.use("/api/v3/workspaces", v3WorkspacesRouter);
app.use("/api/v3/signup", v3SignupRouter); app.use("/api/v3/signup", v3SignupRouter);
app.use("/api/v3/service-token", v3ServiceTokenDataRouter);
// api docs // api docs
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile)); app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerFile));
+1 -3
View File
@@ -2,12 +2,10 @@ import auth from "./auth";
import secrets from "./secrets"; import secrets from "./secrets";
import workspaces from "./workspaces"; import workspaces from "./workspaces";
import signup from "./signup"; import signup from "./signup";
import serviceTokenData from "./serviceTokenData";
export { export {
auth, auth,
secrets, secrets,
signup, signup,
workspaces, workspaces
serviceTokenData
} }
@@ -20,9 +20,13 @@ import {
Modal, Modal,
ModalContent, ModalContent,
Select, Select,
SelectItem SelectItem,
UpgradePlanModal
} from "@app/components/v2"; } from "@app/components/v2";
import { useWorkspace } from "@app/context"; import {
useSubscription,
useWorkspace
} from "@app/context";
import { import {
useCreateServiceTokenV3, useCreateServiceTokenV3,
useGetUserWsKey, useGetUserWsKey,
@@ -88,14 +92,17 @@ const schema = yup.object({
export type FormData = yup.InferType<typeof schema>; export type FormData = yup.InferType<typeof schema>;
type Props = { type Props = {
popUp: UsePopUpState<["serviceTokenV3"]>; popUp: UsePopUpState<["serviceTokenV3", "upgradePlan"]>;
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["serviceTokenV3"]>, state?: boolean) => void; handlePopUpToggle: (popUpName: keyof UsePopUpState<["serviceTokenV3"]>, state?: boolean) => void;
}; };
export const AddServiceTokenV3Modal = ({ export const AddServiceTokenV3Modal = ({
popUp, popUp,
handlePopUpOpen,
handlePopUpToggle handlePopUpToggle
}: Props) => { }: Props) => {
const { subscription } = useSubscription();
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? ""); const { data: latestFileKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
@@ -400,19 +407,39 @@ export const AddServiceTokenV3Modal = ({
control={control} control={control}
name={`trustedIps.${index}.ipAddress`} name={`trustedIps.${index}.ipAddress`}
defaultValue="0.0.0.0/0" defaultValue="0.0.0.0/0"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => {
return (
<FormControl <FormControl
className="mb-0 flex-grow" className="mb-0 flex-grow"
label={index === 0 ? "Trusted IP" : undefined} label={index === 0 ? "Trusted IP" : undefined}
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
> >
<Input {...field} placeholder="123.456.789.0" /> <Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl> </FormControl>
)} );
}}
/> />
<IconButton <IconButton
onClick={() => removeTrustedIp(index)} onClick={() => {
if (subscription?.ipAllowlisting) {
removeTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg" size="lg"
colorSchema="danger" colorSchema="danger"
variant="plain" variant="plain"
@@ -426,11 +453,16 @@ export const AddServiceTokenV3Modal = ({
<div className="my-4 ml-1"> <div className="my-4 ml-1">
<Button <Button
variant="outline_bg" variant="outline_bg"
onClick={() => onClick={() => {
if (subscription?.ipAllowlisting) {
appendTrustedIp({ appendTrustedIp({
ipAddress: "0.0.0.0/0" ipAddress: "0.0.0.0/0"
}) })
return;
} }
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />} leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs" size="xs"
> >
@@ -478,6 +510,11 @@ export const AddServiceTokenV3Modal = ({
</Button> </Button>
</div> </div>
</form> </form>
<UpgradePlanModal
isOpen={popUp?.upgradePlan?.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
/>
</ModalContent> </ModalContent>
</Modal> </Modal>
); );
@@ -23,7 +23,8 @@ export const ServiceTokenV3Section = withProjectPermission(
const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3(); const { mutateAsync: deleteMutateAsync } = useDeleteServiceTokenV3();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"serviceTokenV3", "serviceTokenV3",
"deleteServiceTokenV3" "deleteServiceTokenV3",
"upgradePlan"
] as const); ] as const);
const onDeleteServiceTokenDataSubmit = async (serviceTokenDataId: string) => { const onDeleteServiceTokenDataSubmit = async (serviceTokenDataId: string) => {
@@ -74,6 +75,7 @@ export const ServiceTokenV3Section = withProjectPermission(
/> />
<AddServiceTokenV3Modal <AddServiceTokenV3Modal
popUp={popUp} popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle} handlePopUpToggle={handlePopUpToggle}
/> />
<DeleteActionModal <DeleteActionModal