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

View File

@@ -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
}

View File

@@ -0,0 +1,5 @@
import * as serviceTokenDataController from "./serviceTokenDataController";
export {
serviceTokenDataController
}

View File

@@ -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({

View File

@@ -0,0 +1,5 @@
import serviceTokenData from "./serviceTokenData";
export {
serviceTokenData
}

View File

@@ -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(

View File

@@ -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));

View File

@@ -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
}

View File

@@ -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<typeof schema>;
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 } }) => (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Trusted IP" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="123.456.789.0" />
</FormControl>
)}
render={({ field, fieldState: { error } }) => {
return (
<FormControl
className="mb-0 flex-grow"
label={index === 0 ? "Trusted IP" : undefined}
isError={Boolean(error)}
errorText={error?.message}
>
<Input
value={field.value}
onChange={(e) => {
if (subscription?.ipAllowlisting) {
field.onChange(e);
return;
}
handlePopUpOpen("upgradePlan");
}}
placeholder="123.456.789.0"
/>
</FormControl>
);
}}
/>
<IconButton
onClick={() => removeTrustedIp(index)}
onClick={() => {
if (subscription?.ipAllowlisting) {
removeTrustedIp(index);
return;
}
handlePopUpOpen("upgradePlan");
}}
size="lg"
colorSchema="danger"
variant="plain"
@@ -426,11 +453,16 @@ export const AddServiceTokenV3Modal = ({
<div className="my-4 ml-1">
<Button
variant="outline_bg"
onClick={() =>
appendTrustedIp({
ipAddress: "0.0.0.0/0"
})
}
onClick={() => {
if (subscription?.ipAllowlisting) {
appendTrustedIp({
ipAddress: "0.0.0.0/0"
})
return;
}
handlePopUpOpen("upgradePlan");
}}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
>
@@ -478,6 +510,11 @@ export const AddServiceTokenV3Modal = ({
</Button>
</div>
</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>
</Modal>
);

View File

@@ -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(
/>
<AddServiceTokenV3Modal
popUp={popUp}
handlePopUpOpen={handlePopUpOpen}
handlePopUpToggle={handlePopUpToggle}
/>
<DeleteActionModal