mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 10:35:17 +00:00
feat(rbac): added new zod validation and permission check for all org level api
This commit is contained in:
@@ -1,16 +1,10 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
MembershipOrg,
|
||||
} from "../models";
|
||||
import {
|
||||
validateMembershipOrg,
|
||||
} from "../helpers/membershipOrg";
|
||||
import {
|
||||
MembershipOrgNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../utils/errors";
|
||||
import { MembershipOrg } from "../models";
|
||||
import { validateMembershipOrg } from "../helpers/membershipOrg";
|
||||
import { MembershipOrgNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { AuthData } from "../interfaces/middleware";
|
||||
import { ActorType } from "../ee/models";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||
@@ -22,35 +16,57 @@ import { ActorType } from "../ee/models";
|
||||
* @param {MembershipOrg} - validated organization membership
|
||||
*/
|
||||
export const validateClientForMembershipOrg = async ({
|
||||
authData,
|
||||
membershipOrgId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
authData,
|
||||
membershipOrgId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
authData: AuthData;
|
||||
membershipOrgId: Types.ObjectId;
|
||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||
acceptedStatuses: Array<"invited" | "accepted">;
|
||||
authData: AuthData;
|
||||
membershipOrgId: Types.ObjectId;
|
||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||
acceptedStatuses: Array<"invited" | "accepted">;
|
||||
}) => {
|
||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||
|
||||
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||
message: "Failed to find organization membership ",
|
||||
});
|
||||
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
case ActorType.SERVICE:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service account client authorization for organization membership",
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!membershipOrg)
|
||||
throw MembershipOrgNotFoundError({
|
||||
message: "Failed to find organization membership "
|
||||
});
|
||||
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
case ActorType.SERVICE:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service account client authorization for organization membership"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const DelOrgMembershipv1 = z.object({
|
||||
params: z.object({
|
||||
membershipOrgId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const InviteUserToOrgv1 = z.object({
|
||||
body: z.object({
|
||||
inviteeEmail: z.string().trim().email(),
|
||||
organizationId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const VerifyUserToOrgv1 = z.object({
|
||||
body: z.object({
|
||||
email: z.string().trim().email(),
|
||||
organizationId: z.string().trim(),
|
||||
code: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IUser,
|
||||
Organization,
|
||||
} from "../models";
|
||||
import {
|
||||
OrganizationNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../utils/errors";
|
||||
import { z } from "zod";
|
||||
import { IUser, Organization } from "../models";
|
||||
import { OrganizationNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { validateUserClientForOrganization } from "./user";
|
||||
import { AuthData } from "../interfaces/middleware";
|
||||
import { ActorType } from "../ee/models";
|
||||
@@ -21,7 +16,7 @@ export const validateClientForOrganization = async ({
|
||||
authData,
|
||||
organizationId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
authData: AuthData;
|
||||
organizationId: Types.ObjectId;
|
||||
@@ -32,10 +27,10 @@ export const validateClientForOrganization = async ({
|
||||
|
||||
if (!organization) {
|
||||
throw OrganizationNotFoundError({
|
||||
message: "Failed to find organization",
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let membershipOrg;
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
@@ -43,13 +38,162 @@ export const validateClientForOrganization = async ({
|
||||
user: authData.authPayload as IUser,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return { organization, membershipOrg };
|
||||
return { organization, membershipOrg };
|
||||
case ActorType.SERVICE:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for organization",
|
||||
message: "Failed service token authorization for organization"
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const GetOrgPlansTablev1 = z.object({
|
||||
query: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgPlanv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||
});
|
||||
|
||||
export const StartOrgTrailv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ success_url: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgPlanBillingInfov1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||
});
|
||||
|
||||
export const GetOrgPlanTablev1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||
});
|
||||
|
||||
export const GetOrgBillingDetailsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const UpdateOrgBillingDetailsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
email: z.string().trim().email().optional(),
|
||||
name: z.string().trim().optional()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgPmtMethodsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgPmtMethodv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
success_url: z.string().trim(),
|
||||
cancel_url: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DelOrgPmtMethodv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
pmtMethodId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgTaxIdsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgTaxId = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
type: z.string().trim(),
|
||||
value: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DelOrgTaxIdv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
taxId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgInvoicesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgLicencesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgv1 = z.object({
|
||||
body: z.object({
|
||||
organizationName: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgMembersv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgWorkspacesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const ChangeOrgNamev1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ name: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgIncidentContactv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgIncideContact = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ email: z.string().email().trim() })
|
||||
});
|
||||
|
||||
export const DelOrgIncideContact = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ email: z.string().email().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgPortalSessionv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgMembersAndWsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgMembersv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const UpdateOrgMemberv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
body: z.object({
|
||||
role: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DeleteOrgMemberv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgWorkspacesv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const CreateInstalLSessionv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const LinkInstallationToOrgv1 = z.object({
|
||||
body: z.object({
|
||||
installationId: z.number(),
|
||||
sessionId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgInstallStatusv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgRisksv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const UpdateRiskStatusv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
||||
body: z.object({ status: z.string().trim() })
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { z } from "zod";
|
||||
import { AuthProvider } from "../ee/models";
|
||||
|
||||
export const GetSsoConfigv1 = z.object({
|
||||
query: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateSsoConfigv1 = z.object({
|
||||
body: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
authProvider: z.nativeEnum(AuthProvider),
|
||||
isActive: z.boolean(),
|
||||
entryPoint: z.string().trim(),
|
||||
issuer: z.string().trim(),
|
||||
cert: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const UpdateSsoConfigv1 = z.object({
|
||||
body: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
authProvider: z.nativeEnum(AuthProvider).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
entryPoint: z.string().trim().optional(),
|
||||
issuer: z.string().trim().optional(),
|
||||
cert: z.string().trim().optional()
|
||||
})
|
||||
});
|
||||
+105
-112
@@ -1,25 +1,14 @@
|
||||
import net from "net";
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IServiceTokenData,
|
||||
IUser,
|
||||
SecretBlindIndexData,
|
||||
Workspace,
|
||||
} from "../models";
|
||||
import {
|
||||
ActorType,
|
||||
TrustedIP
|
||||
} from "../ee/models";
|
||||
import { IServiceTokenData, IUser, SecretBlindIndexData, Workspace } from "../models";
|
||||
import { ActorType, TrustedIP } from "../ee/models";
|
||||
import { validateUserClientForWorkspace } from "./user";
|
||||
import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData";
|
||||
import {
|
||||
BadRequestError,
|
||||
UnauthorizedRequestError,
|
||||
WorkspaceNotFoundError,
|
||||
} from "../utils/errors";
|
||||
import { BadRequestError, UnauthorizedRequestError, WorkspaceNotFoundError } from "../utils/errors";
|
||||
import { BotService } from "../services";
|
||||
import { AuthData } from "../interfaces/middleware";
|
||||
import { extractIPDetails } from "../utils/ip";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
||||
@@ -32,106 +21,110 @@ import { extractIPDetails } from "../utils/ip";
|
||||
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||
*/
|
||||
export const validateClientForWorkspace = async ({
|
||||
authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled,
|
||||
requireE2EEOff,
|
||||
checkIPAllowlist
|
||||
authData,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions,
|
||||
requireBlindIndicesEnabled,
|
||||
requireE2EEOff,
|
||||
checkIPAllowlist
|
||||
}: {
|
||||
authData: AuthData;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
acceptedRoles: Array<"admin" | "member">;
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled: boolean;
|
||||
requireE2EEOff: boolean;
|
||||
checkIPAllowlist: boolean;
|
||||
authData: AuthData;
|
||||
workspaceId: Types.ObjectId;
|
||||
environment?: string;
|
||||
acceptedRoles: Array<"admin" | "member">;
|
||||
requiredPermissions?: string[];
|
||||
requireBlindIndicesEnabled: boolean;
|
||||
requireE2EEOff: boolean;
|
||||
checkIPAllowlist: boolean;
|
||||
}) => {
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
const workspace = await Workspace.findById(workspaceId);
|
||||
|
||||
if (!workspace) throw WorkspaceNotFoundError({
|
||||
message: "Failed to find workspace",
|
||||
});
|
||||
if (!workspace)
|
||||
throw WorkspaceNotFoundError({
|
||||
message: "Failed to find workspace"
|
||||
});
|
||||
|
||||
if (requireBlindIndicesEnabled) {
|
||||
// case: blind indices are not enabled for secrets in this workspace
|
||||
// (i.e. workspace was created before blind indices were introduced
|
||||
// and no admin has enabled it)
|
||||
|
||||
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||
workspace: new Types.ObjectId(workspaceId),
|
||||
});
|
||||
|
||||
if (!secretBlindIndexData) throw UnauthorizedRequestError({
|
||||
message: "Failed workspace authorization due to blind indices not being enabled",
|
||||
});
|
||||
}
|
||||
|
||||
if (requireE2EEOff) {
|
||||
const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId);
|
||||
|
||||
if (isWorkspaceE2EE) throw BadRequestError({
|
||||
message: "Failed workspace authorization due to end-to-end encryption not being disabled",
|
||||
});
|
||||
}
|
||||
|
||||
let membership;
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload as IUser,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions,
|
||||
});
|
||||
|
||||
return ({ membership, workspace });
|
||||
case ActorType.SERVICE:
|
||||
if (checkIPAllowlist) {
|
||||
const trustedIps = await TrustedIP.find({
|
||||
workspace: workspaceId
|
||||
});
|
||||
|
||||
if (trustedIps.length > 0) {
|
||||
// case: check the IP address of the inbound request against trusted IPs
|
||||
if (requireBlindIndicesEnabled) {
|
||||
// case: blind indices are not enabled for secrets in this workspace
|
||||
// (i.e. workspace was created before blind indices were introduced
|
||||
// and no admin has enabled it)
|
||||
|
||||
const blockList = new net.BlockList();
|
||||
|
||||
for (const trustedIp of trustedIps) {
|
||||
if (trustedIp.prefix !== undefined) {
|
||||
blockList.addSubnet(
|
||||
trustedIp.ipAddress,
|
||||
trustedIp.prefix,
|
||||
trustedIp.type
|
||||
);
|
||||
} else {
|
||||
blockList.addAddress(
|
||||
trustedIp.ipAddress,
|
||||
trustedIp.type
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const { type } = extractIPDetails(authData.ipAddress);
|
||||
const check = blockList.check(authData.ipAddress, type);
|
||||
|
||||
if (!check) throw UnauthorizedRequestError({
|
||||
message: "Failed workspace authorization"
|
||||
});
|
||||
}
|
||||
}
|
||||
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||
workspace: new Types.ObjectId(workspaceId)
|
||||
});
|
||||
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload as IServiceTokenData,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions,
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (!secretBlindIndexData)
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed workspace authorization due to blind indices not being enabled"
|
||||
});
|
||||
}
|
||||
|
||||
if (requireE2EEOff) {
|
||||
const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId);
|
||||
|
||||
if (isWorkspaceE2EE)
|
||||
throw BadRequestError({
|
||||
message: "Failed workspace authorization due to end-to-end encryption not being disabled"
|
||||
});
|
||||
}
|
||||
|
||||
let membership;
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
membership = await validateUserClientForWorkspace({
|
||||
user: authData.authPayload as IUser,
|
||||
workspaceId,
|
||||
environment,
|
||||
acceptedRoles,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return { membership, workspace };
|
||||
case ActorType.SERVICE:
|
||||
if (checkIPAllowlist) {
|
||||
const trustedIps = await TrustedIP.find({
|
||||
workspace: workspaceId
|
||||
});
|
||||
|
||||
if (trustedIps.length > 0) {
|
||||
// case: check the IP address of the inbound request against trusted IPs
|
||||
|
||||
const blockList = new net.BlockList();
|
||||
|
||||
for (const trustedIp of trustedIps) {
|
||||
if (trustedIp.prefix !== undefined) {
|
||||
blockList.addSubnet(trustedIp.ipAddress, trustedIp.prefix, trustedIp.type);
|
||||
} else {
|
||||
blockList.addAddress(trustedIp.ipAddress, trustedIp.type);
|
||||
}
|
||||
}
|
||||
|
||||
const { type } = extractIPDetails(authData.ipAddress);
|
||||
const check = blockList.check(authData.ipAddress, type);
|
||||
|
||||
if (!check)
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed workspace authorization"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await validateServiceTokenDataClientForWorkspace({
|
||||
serviceTokenData: authData.authPayload as IServiceTokenData,
|
||||
workspaceId,
|
||||
environment,
|
||||
requiredPermissions
|
||||
});
|
||||
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
export const CreateWorkspacev1 = z.object({
|
||||
body: z.object({
|
||||
workspaceName: z.string().trim(),
|
||||
organizationId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user