feat(rbac): added new zod validation and permission check for all org level api

This commit is contained in:
Akhil Mohan
2023-09-08 21:20:29 +05:30
parent 96437fd1b7
commit e342e88499
27 changed files with 1870 additions and 1593 deletions
@@ -8,14 +8,8 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import { sendMail } from "../../helpers/nodemailer";
import { TokenService } from "../../services";
import { EELicenseService } from "../../ee/services";
import {
ACCEPTED,
ADMIN,
INVITED,
MEMBER,
OWNER,
TOKEN_EMAIL_ORG_INVITATION
} from "../../variables";
import { ACCEPTED, INVITED, MEMBER, TOKEN_EMAIL_ORG_INVITATION } from "../../variables";
import * as reqValidator from "../../validation/membershipOrg";
import {
getJwtSignupLifetime,
getJwtSignupSecret,
@@ -23,6 +17,13 @@ import {
getSmtpConfigured
} from "../../config";
import { validateUserEmail } from "../../validation";
import { validateRequest } from "../../helpers/validation";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/**
* Delete organization membership with id [membershipOrgId] from organization
@@ -31,7 +32,9 @@ import { validateUserEmail } from "../../validation";
* @returns
*/
export const deleteMembershipOrg = async (req: Request, _res: Response) => {
const { membershipOrgId } = req.params;
const {
params: { membershipOrgId }
} = await validateRequest(reqValidator.DelOrgMembershipv1, req);
// check if organization membership to delete exists
const membershipOrgToDelete = await MembershipOrg.findOne({
@@ -42,21 +45,14 @@ export const deleteMembershipOrg = async (req: Request, _res: Response) => {
throw new Error("Failed to delete organization membership that doesn't exist");
}
// check if user is a member and admin of the organization
// whose membership we wish to delete
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: membershipOrgToDelete.organization
});
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
}
if (membershipOrg.role !== OWNER && membershipOrg.role !== ADMIN) {
// user is not an admin member of the organization
throw new Error("Insufficient role for deleting organization membership");
}
const { permission, membership: membershipOrg } = await getUserOrgPermissions(
req.user._id,
membershipOrgToDelete.organization.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Delete,
OrgPermissionSubjects.Member
);
// delete organization membership
await deleteMemberFromOrg({
@@ -96,22 +92,20 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
*/
export const inviteUserToOrganization = async (req: Request, res: Response) => {
let inviteeMembershipOrg, completeInviteLink;
const { organizationId, inviteeEmail } = req.body;
const {
body: { inviteeEmail, organizationId }
} = await validateRequest(reqValidator.InviteUserToOrgv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.Member
);
const host = req.headers.host;
const siteUrl = `${req.protocol}://${host}`;
// validate membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: new Types.ObjectId(organizationId)
});
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
}
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
const ssoConfig = await SSOConfig.findOne({
organization: new Types.ObjectId(organizationId)
});
@@ -119,9 +113,8 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
if (ssoConfig && ssoConfig.isActive) {
// case: SAML SSO is enabled for the organization
return res.status(400).send({
message:
"Failed to invite member due to SAML SSO configured for organization"
});
message: "Failed to invite member due to SAML SSO configured for organization"
});
}
if (plan.memberLimit !== null) {
@@ -231,7 +224,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
*/
export const verifyUserToOrganization = async (req: Request, res: Response) => {
let user;
const { email, organizationId, code } = req.body;
const {
body: { organizationId, email, code }
} = await validateRequest(reqValidator.VerifyUserToOrgv1, req);
user = await User.findOne({ email }).select("+publicKey");
@@ -1,28 +1,38 @@
import { Request, Response } from "express";
import {
IncidentContactOrg,
Membership,
MembershipOrg,
Organization,
Workspace,
IncidentContactOrg,
Membership,
MembershipOrg,
Organization,
Workspace
} from "../../models";
import { createOrganization as create } from "../../helpers/organization";
import { addMembershipsOrg } from "../../helpers/membershipOrg";
import { ACCEPTED, OWNER } from "../../variables";
import { getLicenseServerUrl, getSiteURL } from "../../config";
import { licenseServerKeyRequest } from "../../config/request";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/organization";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
WorkspacePermissionActions,
getUserOrgPermissions
} from "../../services/RoleService";
import { OrganizationNotFoundError } from "../../utils/errors";
import { ForbiddenError } from "@casl/ability";
export const getOrganizations = async (req: Request, res: Response) => {
const organizations = (
await MembershipOrg.find({
user: req.user._id,
status: ACCEPTED,
status: ACCEPTED
}).populate("organization")
).map((m) => m.organization);
return res.status(200).send({
organizations,
});
return res.status(200).send({
organizations
});
};
/**
@@ -33,28 +43,26 @@ export const getOrganizations = async (req: Request, res: Response) => {
* @returns
*/
export const createOrganization = async (req: Request, res: Response) => {
const { organizationName } = req.body;
if (organizationName.length < 1) {
throw new Error("Organization names must be at least 1-character long");
}
const {
body: { organizationName }
} = await validateRequest(reqValidator.CreateOrgv1, req);
// create organization and add user as member
const organization = await create({
email: req.user.email,
name: organizationName,
name: organizationName
});
await addMembershipsOrg({
userIds: [req.user._id.toString()],
organizationId: organization._id.toString(),
roles: [OWNER],
statuses: [ACCEPTED],
statuses: [ACCEPTED]
});
return res.status(200).send({
organization,
});
return res.status(200).send({
organization
});
};
/**
@@ -64,10 +72,23 @@ export const createOrganization = async (req: Request, res: Response) => {
* @returns
*/
export const getOrganization = async (req: Request, res: Response) => {
const organization = req.organization
return res.status(200).send({
organization,
});
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgv1, req);
// ensure user has membership
await getUserOrgPermissions(req.user._id, organizationId);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
return res.status(200).send({
organization
});
};
/**
@@ -77,15 +98,23 @@ export const getOrganization = async (req: Request, res: Response) => {
* @returns
*/
export const getOrganizationMembers = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Member
);
const users = await MembershipOrg.find({
organization: organizationId,
organization: organizationId
}).populate("user", "+publicKey");
return res.status(200).send({
users,
});
return res.status(200).send({
users
});
};
/**
@@ -94,17 +123,22 @@ export const getOrganizationMembers = async (req: Request, res: Response) => {
* @param res
* @returns
*/
export const getOrganizationWorkspaces = async (
req: Request,
res: Response
) => {
const { organizationId } = req.params;
export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgWorkspacesv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
WorkspacePermissionActions.Read,
OrgPermissionSubjects.Workspace
);
const workspacesSet = new Set(
(
await Workspace.find(
{
organization: organizationId,
organization: organizationId
},
"_id"
)
@@ -113,15 +147,15 @@ export const getOrganizationWorkspaces = async (
const workspaces = (
await Membership.find({
user: req.user._id,
user: req.user._id
}).populate("workspace")
)
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
.map((m) => m.workspace);
return res.status(200).send({
workspaces,
});
return res.status(200).send({
workspaces
});
};
/**
@@ -131,25 +165,33 @@ export const getOrganizationWorkspaces = async (
* @returns
*/
export const changeOrganizationName = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const { name } = req.body;
const {
params: { organizationId },
body: { name }
} = await validateRequest(reqValidator.ChangeOrgNamev1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.Settings
);
const organization = await Organization.findOneAndUpdate(
{
_id: organizationId,
_id: organizationId
},
{
name,
name
},
{
new: true,
new: true
}
);
return res.status(200).send({
message: "Successfully changed organization name",
organization,
});
return res.status(200).send({
message: "Successfully changed organization name",
organization
});
};
/**
@@ -158,19 +200,24 @@ export const changeOrganizationName = async (req: Request, res: Response) => {
* @param res
* @returns
*/
export const getOrganizationIncidentContacts = async (
req: Request,
res: Response
) => {
const { organizationId } = req.params;
export const getOrganizationIncidentContacts = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgIncidentContactv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.IncidentAccount
);
const incidentContactsOrg = await IncidentContactOrg.find({
organization: organizationId,
organization: organizationId
});
return res.status(200).send({
incidentContactsOrg,
});
return res.status(200).send({
incidentContactsOrg
});
};
/**
@@ -179,12 +226,17 @@ export const getOrganizationIncidentContacts = async (
* @param res
* @returns
*/
export const addOrganizationIncidentContact = async (
req: Request,
res: Response
) => {
const { organizationId } = req.params;
const { email } = req.body;
export const addOrganizationIncidentContact = async (req: Request, res: Response) => {
const {
params: { organizationId },
body: { email }
} = await validateRequest(reqValidator.CreateOrgIncideContact, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.IncidentAccount
);
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
{ email, organization: organizationId },
@@ -192,9 +244,9 @@ export const addOrganizationIncidentContact = async (
{ upsert: true, new: true }
);
return res.status(200).send({
incidentContactOrg,
});
return res.status(200).send({
incidentContactOrg
});
};
/**
@@ -203,22 +255,27 @@ export const addOrganizationIncidentContact = async (
* @param res
* @returns
*/
export const deleteOrganizationIncidentContact = async (
req: Request,
res: Response
) => {
const { organizationId } = req.params;
const { email } = req.body;
export const deleteOrganizationIncidentContact = async (req: Request, res: Response) => {
const {
params: { organizationId },
body: { email }
} = await validateRequest(reqValidator.DelOrgIncideContact, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Delete,
OrgPermissionSubjects.IncidentAccount
);
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
email,
organization: organizationId,
organization: organizationId
});
return res.status(200).send({
message: "Successfully deleted organization incident contact",
incidentContactOrg,
});
return res.status(200).send({
message: "Successfully deleted organization incident contact",
incidentContactOrg
});
};
/**
@@ -228,19 +285,41 @@ export const deleteOrganizationIncidentContact = async (
* @param res
* @returns
*/
export const createOrganizationPortalSession = async (
req: Request,
res: Response
) => {
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
export const createOrganizationPortalSession = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { pmtMethods }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`
);
if (pmtMethods.length < 1) {
// case: organization has no payment method on file
// -> redirect to add payment method portal
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
// -> redirect to add payment method portal
const {
data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`,
{
success_url: (await getSiteURL()) + "/dashboard",
cancel_url: (await getSiteURL()) + "/dashboard"
@@ -250,8 +329,12 @@ export const createOrganizationPortalSession = async (
} else {
// case: organization has payment method on file
// -> redirect to billing portal
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/billing-portal`,
const {
data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/billing-portal`,
{
return_url: (await getSiteURL()) + "/dashboard"
}
@@ -266,36 +349,43 @@ export const createOrganizationPortalSession = async (
* @param res
* @returns
*/
export const getOrganizationMembersAndTheirWorkspaces = async (
req: Request,
res: Response
) => {
const { organizationId } = req.params;
export const getOrganizationMembersAndTheirWorkspaces = async (req: Request, res: Response) => {
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
const workspacesSet = (
await Workspace.find(
{
organization: organizationId,
},
"_id"
)
).map((w) => w._id.toString());
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Member
);
ForbiddenError.from(permission).throwUnlessCan(
WorkspacePermissionActions.Read,
OrgPermissionSubjects.Workspace
);
const memberships = (
await Membership.find({
workspace: { $in: workspacesSet },
}).populate("workspace")
);
const userToWorkspaceIds: any = {};
const workspacesSet = (
await Workspace.find(
{
organization: organizationId
},
"_id"
)
).map((w) => w._id.toString());
memberships.forEach(membership => {
const user = membership.user.toString();
if (userToWorkspaceIds[user]) {
userToWorkspaceIds[user].push(membership.workspace);
} else {
userToWorkspaceIds[user] = [membership.workspace];
}
});
const memberships = await Membership.find({
workspace: { $in: workspacesSet }
}).populate("workspace");
const userToWorkspaceIds: any = {};
return res.json(userToWorkspaceIds);
memberships.forEach((membership) => {
const user = membership.user.toString();
if (userToWorkspaceIds[user]) {
userToWorkspaceIds[user].push(membership.workspace);
} else {
userToWorkspaceIds[user] = [membership.workspace];
}
});
return res.json(userToWorkspaceIds);
};
+8 -8
View File
@@ -21,8 +21,8 @@ export const createRole = async (req: Request, res: Response) => {
body: { workspaceId, name, description, slug, permissions, orgId }
} = await validateRequest(CreateRoleSchema, req);
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
if (orgPermission.cannot(GeneralPermissionActions.Create, OrgPermissionSubjects.Role)) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(GeneralPermissionActions.Create, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
@@ -59,8 +59,8 @@ export const updateRole = async (req: Request, res: Response) => {
} = await validateRequest(UpdateRoleSchema, req);
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
if (orgPermission.cannot(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role)) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
@@ -103,8 +103,8 @@ export const deleteRole = async (req: Request, res: Response) => {
throw BadRequestError({ message: "Role not found" });
}
const orgPermission = await getUserOrgPermissions(req.user.id, role.organization.toString());
if (orgPermission.cannot(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role)) {
const { permission } = await getUserOrgPermissions(req.user.id, role.organization.toString());
if (permission.cannot(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
await Role.findByIdAndDelete(role.id);
@@ -123,8 +123,8 @@ export const getRoles = async (req: Request, res: Response) => {
} = await validateRequest(GetRoleSchema, req);
const isOrgRole = !workspaceId;
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
if (orgPermission.cannot(GeneralPermissionActions.Read, OrgPermissionSubjects.Role)) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(GeneralPermissionActions.Read, OrgPermissionSubjects.Role)) {
throw BadRequestError({ message: "User doesn't have the permission." });
}
@@ -2,20 +2,45 @@ import { Request, Response } from "express";
import GitAppInstallationSession from "../../ee/models/gitAppInstallationSession";
import crypto from "crypto";
import { Types } from "mongoose";
import { UnauthorizedRequestError } from "../../utils/errors";
import { OrganizationNotFoundError, UnauthorizedRequestError } from "../../utils/errors";
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
import { MembershipOrg } from "../../models";
import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository"
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks";
import { ProbotOctokit } from "probot";
import { Organization } from "../../models";
import { validateRequest } from "../../helpers/validation";
import * as reqValidator from "../../validation/secretScanning";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
export const createInstallationSession = async (req: Request, res: Response) => {
const sessionId = crypto.randomBytes(16).toString("hex");
const {
params: { organizationId }
} = await validateRequest(reqValidator.CreateInstalLSessionv1, req);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.SecretScanning
);
await GitAppInstallationSession.findByIdAndUpdate(
req.organization,
organization,
{
organization: new Types.ObjectId(req.organization),
organization: organization.id,
sessionId: sessionId,
user: new Types.ObjectId(req.user._id)
},
@@ -24,31 +49,43 @@ export const createInstallationSession = async (req: Request, res: Response) =>
res.send({
sessionId: sessionId
})
}
});
};
export const linkInstallationToOrganization = async (req: Request, res: Response) => {
const { installationId, sessionId } = req.body
const {
body: { sessionId, installationId }
} = await validateRequest(reqValidator.LinkInstallationToOrgv1, req);
const installationSession = await GitAppInstallationSession.findOneAndDelete({ sessionId: sessionId })
const installationSession = await GitAppInstallationSession.findOneAndDelete({
sessionId: sessionId
});
if (!installationSession) {
throw UnauthorizedRequestError()
throw UnauthorizedRequestError();
}
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization })
if (!userMembership) {
throw UnauthorizedRequestError()
}
const { permission } = await getUserOrgPermissions(
req.user._id,
installationSession.organization.toString()
);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.SecretScanning
);
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({
organizationId: installationSession.organization,
}, {
installationId: installationId,
organizationId: installationSession.organization,
user: installationSession.user
}, {
upsert: true
}).lean()
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate(
{
organizationId: installationSession.organization
},
{
installationId: installationId,
organizationId: installationSession.organization,
user: installationSession.user
},
{
upsert: true
}
).lean();
const octokit = new ProbotOctokit({
auth: {
@@ -66,41 +103,68 @@ export const linkInstallationToOrganization = async (req: Request, res: Response
}
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
const { organizationId } = req.params
const { organizationId } = req.params;
try {
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean()
const appInstallation = await GitAppOrganizationInstallation.findOne({
organizationId: organizationId
}).lean();
if (!appInstallation) {
res.json({
appInstallationComplete: false
})
});
}
res.json({
appInstallationComplete: true
})
});
} catch {
res.json({
appInstallationComplete: false
})
});
}
}
};
export const getRisksForOrganization = async (req: Request, res: Response) => {
const { organizationId } = req.params
const risks = await GitRisks.find({ organization: organizationId }).sort({ createdAt: -1 }).lean()
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgRisksv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.SecretScanning
);
const risks = await GitRisks.find({ organization: organizationId })
.sort({ createdAt: -1 })
.lean();
res.json({
risks: risks
})
}
});
};
export const updateRisksStatus = async (req: Request, res: Response) => {
const { riskId } = req.params
const { status } = req.body
const isRiskResolved = status == STATUS_RESOLVED_FALSE_POSITIVE || status == STATUS_RESOLVED_REVOKED || status == STATUS_RESOLVED_NOT_REVOKED ? true : false
const {
params: { organizationId, riskId },
body: { status }
} = await validateRequest(reqValidator.UpdateRiskStatusv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.SecretScanning
);
const isRiskResolved =
status == STATUS_RESOLVED_FALSE_POSITIVE ||
status == STATUS_RESOLVED_REVOKED ||
status == STATUS_RESOLVED_NOT_REVOKED
? true
: false;
const risk = await GitRisks.findByIdAndUpdate(riskId, {
status: status,
isResolved: isRiskResolved
}).lean()
}).lean();
res.json(risk)
}
res.json(risk);
};
@@ -5,17 +5,21 @@ import {
Integration,
IntegrationAuth,
Membership,
MembershipOrg,
Organization,
ServiceToken,
Workspace,
Workspace
} from "../../models";
import {
createWorkspace as create,
deleteWorkspace as deleteWork,
} from "../../helpers/workspace";
import { createWorkspace as create, deleteWorkspace as deleteWork } from "../../helpers/workspace";
import { EELicenseService } from "../../ee/services";
import { addMemberships } from "../../helpers/membership";
import { ADMIN } from "../../variables";
import { OrganizationNotFoundError } from "../../utils/errors";
import {
OrgPermissionSubjects,
WorkspacePermissionActions,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/**
* Return public keys of members of workspace with id [workspaceId]
@@ -28,17 +32,17 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
const publicKeys = (
await Membership.find({
workspace: workspaceId,
workspace: workspaceId
}).populate<{ user: IUser }>("user", "publicKey")
).map((member) => {
return {
publicKey: member.user.publicKey,
userId: member.user._id,
userId: member.user._id
};
});
return res.status(200).send({
publicKeys,
publicKeys
});
};
@@ -52,11 +56,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const users = await Membership.find({
workspace: workspaceId,
workspace: workspaceId
}).populate("user", "+publicKey");
return res.status(200).send({
users,
users
});
};
@@ -69,12 +73,12 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
export const getWorkspaces = async (req: Request, res: Response) => {
const workspaces = (
await Membership.find({
user: req.user._id,
user: req.user._id
}).populate("workspace")
).map((m) => m.workspace);
return res.status(200).send({
workspaces,
workspaces
});
};
@@ -88,11 +92,11 @@ export const getWorkspace = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const workspace = await Workspace.findOne({
_id: workspaceId,
_id: workspaceId
});
return res.status(200).send({
workspace,
workspace
});
};
@@ -106,24 +110,28 @@ export const getWorkspace = async (req: Request, res: Response) => {
export const createWorkspace = async (req: Request, res: Response) => {
const { workspaceName, organizationId } = req.body;
// validate organization membership
const membershipOrg = await MembershipOrg.findOne({
user: req.user._id,
organization: new Types.ObjectId(organizationId),
});
if (!membershipOrg) {
throw new Error("Failed to validate organization membership");
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
WorkspacePermissionActions.Create,
OrgPermissionSubjects.Workspace
);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (plan.workspaceLimit !== null) {
// case: limit imposed on number of workspaces allowed
if (plan.workspacesUsed >= plan.workspaceLimit) {
// case: number of workspaces used exceeds the number of workspaces allowed
return res.status(400).send({
message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces.",
message:
"Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces."
});
}
}
@@ -135,17 +143,17 @@ export const createWorkspace = async (req: Request, res: Response) => {
// create workspace and add user as member
const workspace = await create({
name: workspaceName,
organizationId: new Types.ObjectId(organizationId),
organizationId: new Types.ObjectId(organizationId)
});
await addMemberships({
userIds: [req.user._id],
workspaceId: workspace._id.toString(),
roles: [ADMIN],
roles: [ADMIN]
});
return res.status(200).send({
workspace,
workspace
});
};
@@ -160,11 +168,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
// delete workspace
await deleteWork({
id: workspaceId,
id: workspaceId
});
return res.status(200).send({
message: "Successfully deleted workspace",
message: "Successfully deleted workspace"
});
};
@@ -180,19 +188,19 @@ export const changeWorkspaceName = async (req: Request, res: Response) => {
const workspace = await Workspace.findOneAndUpdate(
{
_id: workspaceId,
_id: workspaceId
},
{
name,
name
},
{
new: true,
new: true
}
);
return res.status(200).send({
message: "Successfully changed workspace name",
workspace,
workspace
});
};
@@ -206,11 +214,11 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const integrations = await Integration.find({
workspace: workspaceId,
workspace: workspaceId
});
return res.status(200).send({
integrations,
integrations
});
};
@@ -220,18 +228,15 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
* @param res
* @returns
*/
export const getWorkspaceIntegrationAuthorizations = async (
req: Request,
res: Response
) => {
export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
const authorizations = await IntegrationAuth.find({
workspace: workspaceId,
workspace: workspaceId
});
return res.status(200).send({
authorizations,
authorizations
});
};
@@ -241,18 +246,15 @@ export const getWorkspaceIntegrationAuthorizations = async (
* @param res
* @returns
*/
export const getWorkspaceServiceTokens = async (
req: Request,
res: Response
) => {
export const getWorkspaceServiceTokens = async (req: Request, res: Response) => {
const { workspaceId } = req.params;
// ?? FIX.
const serviceTokens = await ServiceToken.find({
user: req.user._id,
workspace: workspaceId,
workspace: workspaceId
});
return res.status(200).send({
serviceTokens,
serviceTokens
});
};
@@ -6,6 +6,15 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
import Role from "../../models/role";
import { BadRequestError } from "../../utils/errors";
import { CUSTOM } from "../../variables";
import * as reqValidator from "../../validation/organization";
import { validateRequest } from "../../helpers/validation";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
WorkspacePermissionActions,
getUserOrgPermissions
} from "../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/**
* Return memberships for organization with id [organizationId]
@@ -46,7 +55,15 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
}
}
*/
const { organizationId } = req.params;
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgMembersv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Member
);
const memberships = await MembershipOrg.find({
organization: organizationId
@@ -116,8 +133,15 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
}
}
*/
const { membershipId } = req.params;
const { role } = req.body;
const {
params: { organizationId, membershipId },
body: { role }
} = await validateRequest(reqValidator.UpdateOrgMemberv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.Member
);
const isCustomRole = !["admin", "member", "owner"].includes(role);
if (isCustomRole) {
@@ -191,7 +215,14 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
}
}
*/
const { membershipId } = req.params;
const {
params: { organizationId, membershipId }
} = await validateRequest(reqValidator.DeleteOrgMemberv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Delete,
OrgPermissionSubjects.Member
);
// delete organization membership
const membership = await deleteMembershipOrg({
@@ -247,7 +278,15 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
}
}
*/
const { organizationId } = req.params;
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgWorkspacesv2, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
WorkspacePermissionActions.Read,
OrgPermissionSubjects.Workspace
);
const workspacesSet = new Set(
(
@@ -3,228 +3,503 @@ import { Request, Response } from "express";
import { getLicenseServerUrl } from "../../../config";
import { licenseServerKeyRequest } from "../../../config/request";
import { EELicenseService } from "../../services";
import { validateRequest } from "../../../helpers/validation";
import * as reqValidator from "../../../validation/organization";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
import { Organization } from "../../../models";
import { OrganizationNotFoundError } from "../../../utils/errors";
export const getOrganizationPlansTable = async (req: Request, res: Response) => {
const billingCycle = req.query.billingCycle as string;
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
const {
query: { billingCycle },
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlansTablev1, req);
return res.status(200).send(data);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
);
return res.status(200).send(data);
};
/**
* Return the organization current plan's feature set
*/
export const getOrganizationPlan = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const workspaceId = req.query.workspaceId as string;
const {
query: { workspaceId },
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanv1, req);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId), new Types.ObjectId(workspaceId));
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
return res.status(200).send({
plan,
});
}
const plan = await EELicenseService.getPlan(
new Types.ObjectId(organizationId),
new Types.ObjectId(workspaceId)
);
return res.status(200).send({
plan
});
};
/**
* Return checkout url for pro trial
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const startOrganizationTrial = async (req: Request, res: Response) => {
const { organizationId } = req.params;
const { success_url } = req.body;
const {
params: { organizationId },
body: { success_url }
} = await validateRequest(reqValidator.StartOrgTrailv1, req);
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/session/trial`,
{
success_url
}
);
EELicenseService.delPlan(new Types.ObjectId(organizationId));
return res.status(200).send({
url
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.Billing
);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
}
const {
data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/session/trial`,
{
success_url
}
);
EELicenseService.delPlan(new Types.ObjectId(organizationId));
return res.status(200).send({
url
});
};
/**
* Return the organization's current plan's billing info
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getOrganizationPlanBillingInfo = async (req: Request, res: Response) => {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/billing`
);
return res.status(200).send(data);
}
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/cloud-plan/billing`
);
return res.status(200).send(data);
};
/**
* Return the organization's current plan's feature table
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getOrganizationPlanTable = async (req: Request, res: Response) => {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/table`
);
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPlanTablev1, req);
return res.status(200).send(data);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/cloud-plan/table`
);
return res.status(200).send(data);
};
export const getOrganizationBillingDetails = async (req: Request, res: Response) => {
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`
);
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgBillingDetailsv1, req);
return res.status(200).send(data);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details`
);
return res.status(200).send(data);
};
export const updateOrganizationBillingDetails = async (req: Request, res: Response) => {
const {
name,
email
} = req.body;
const {
params: { organizationId },
body: { name, email }
} = await validateRequest(reqValidator.UpdateOrgBillingDetailsv1, req);
const { data } = await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`,
{
...(name ? { name } : {}),
...(email ? { email } : {})
}
);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.Billing
);
return res.status(200).send(data);
}
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.patch(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details`,
{
...(name ? { name } : {}),
...(email ? { email } : {})
}
);
return res.status(200).send(data);
};
/**
* Return the organization's payment methods on file
*/
export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`
);
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgPmtMethodsv1, req);
return res.status(200).send(pmtMethods);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { pmtMethods }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`
);
return res.status(200).send(pmtMethods);
};
/**
* Return URL to add payment method for organization
*/
export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
const {
success_url,
cancel_url,
} = req.body;
const { data: { url } } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
{
success_url,
cancel_url,
}
);
return res.status(200).send({
url,
});
}
const {
params: { organizationId },
body: { success_url, cancel_url }
} = await validateRequest(reqValidator.CreateOrgPmtMethodv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { url }
} = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods`,
{
success_url,
cancel_url
}
);
return res.status(200).send({
url
});
};
/**
* Delete payment method with id [pmtMethodId] for organization
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
const { pmtMethodId } = req.params;
const {
params: { organizationId, pmtMethodId }
} = await validateRequest(reqValidator.DelOrgPmtMethodv1, req);
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods/${pmtMethodId}`,
);
return res.status(200).send(data);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Delete,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/payment-methods/${pmtMethodId}`
);
return res.status(200).send(data);
};
/**
* Return the organization's tax ids on file
*/
export const getOrganizationTaxIds = async (req: Request, res: Response) => {
const { data: { tax_ids } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`
);
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgTaxIdsv1, req);
return res.status(200).send(tax_ids);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { tax_ids }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/tax-ids`
);
return res.status(200).send(tax_ids);
};
/**
* Add tax id to organization
*/
export const addOrganizationTaxId = async (req: Request, res: Response) => {
const {
type,
value
} = req.body;
const {
params: { organizationId },
body: { type, value }
} = await validateRequest(reqValidator.CreateOrgTaxId, req);
const { data } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`,
{
type,
value
}
);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.Billing
);
return res.status(200).send(data);
}
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.post(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/tax-ids`,
{
type,
value
}
);
return res.status(200).send(data);
};
/**
* Delete tax id with id [taxId] from organization tax ids on file
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
const { taxId } = req.params;
const {
params: { organizationId, taxId }
} = await validateRequest(reqValidator.DelOrgTaxIdv1, req);
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids/${taxId}`,
);
return res.status(200).send(data);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Delete,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const { data } = await licenseServerKeyRequest.delete(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/billing-details/tax-ids/${taxId}`
);
return res.status(200).send(data);
};
/**
* Return organization's invoices on file
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getOrganizationInvoices = async (req: Request, res: Response) => {
const { data: { invoices } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices`
);
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgInvoicesv1, req);
return res.status(200).send(invoices);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { invoices }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/invoices`
);
return res.status(200).send(invoices);
};
/**
* Return organization's licenses on file
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getOrganizationLicenses = async (req: Request, res: Response) => {
const { data: { licenses } } = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/licenses`
);
const {
params: { organizationId }
} = await validateRequest(reqValidator.GetOrgLicencesv1, req);
return res.status(200).send(licenses);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Billing
);
const organization = await Organization.findById(organizationId);
if (!organization) {
throw OrganizationNotFoundError({
message: "Failed to find organization"
});
}
const {
data: { licenses }
} = await licenseServerKeyRequest.get(
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
organization.customerId
}/licenses`
);
return res.status(200).send(licenses);
};
+211 -192
View File
@@ -2,239 +2,258 @@ import { Request, Response } from "express";
import { Types } from "mongoose";
import { BotOrgService } from "../../../services";
import { SSOConfig } from "../../models";
import {
AuthMethod,
MembershipOrg,
User
} from "../../../models";
import { AuthMethod, MembershipOrg, User } from "../../../models";
import { getSSOConfigHelper } from "../../helpers/organizations";
import { client } from "../../../config";
import { ResourceNotFoundError } from "../../../utils/errors";
import { getSiteURL } from "../../../config";
import { EELicenseService } from "../../services";
import * as reqValidator from "../../../validation/sso";
import { validateRequest } from "../../../helpers/validation";
import {
GeneralPermissionActions,
OrgPermissionSubjects,
getUserOrgPermissions
} from "../../../services/RoleService";
import { ForbiddenError } from "@casl/ability";
/**
* Redirect user to appropriate SSO endpoint after successful authentication
* to finish inputting their master key for logging in or signing up
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const redirectSSO = async (req: Request, res: Response) => {
if (req.isUserCompleted) {
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
}
return res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
}
if (req.isUserCompleted) {
return res.redirect(
`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`
);
}
return res.redirect(
`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`
);
};
/**
* Return organization SAML SSO configuration
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const getSSOConfig = async (req: Request, res: Response) => {
const organizationId = req.query.organizationId as string;
const data = await getSSOConfigHelper({
organizationId: new Types.ObjectId(organizationId)
});
const {
query: { organizationId }
} = await validateRequest(reqValidator.GetSsoConfigv1, req);
return res.status(200).send(data);
}
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Read,
OrgPermissionSubjects.Sso
);
const data = await getSSOConfigHelper({
organizationId: new Types.ObjectId(organizationId)
});
return res.status(200).send(data);
};
/**
* Update organization SAML SSO configuration
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const updateSSOConfig = async (req: Request, res: Response) => {
const {
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
} = await validateRequest(reqValidator.UpdateSsoConfigv1, req);
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Edit,
OrgPermissionSubjects.Sso
);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (!plan.samlSSO)
return res.status(400).send({
message:
"Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
});
interface PatchUpdate {
authProvider?: string;
isActive?: boolean;
encryptedEntryPoint?: string;
entryPointIV?: string;
entryPointTag?: string;
encryptedIssuer?: string;
issuerIV?: string;
issuerTag?: string;
encryptedCert?: string;
certIV?: string;
certTag?: string;
}
const update: PatchUpdate = {};
if (authProvider) {
update.authProvider = authProvider;
}
if (isActive !== undefined) {
update.isActive = isActive;
}
const key = await BotOrgService.getSymmetricKey(new Types.ObjectId(organizationId));
if (entryPoint) {
const {
organizationId,
authProvider,
isActive,
entryPoint,
issuer,
cert,
} = req.body;
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = client.encryptSymmetric(entryPoint, key);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (!plan.samlSSO) return res.status(400).send({
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
update.encryptedEntryPoint = encryptedEntryPoint;
update.entryPointIV = entryPointIV;
update.entryPointTag = entryPointTag;
}
if (issuer) {
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = client.encryptSymmetric(issuer, key);
update.encryptedIssuer = encryptedIssuer;
update.issuerIV = issuerIV;
update.issuerTag = issuerTag;
}
if (cert) {
const {
ciphertext: encryptedCert,
iv: certIV,
tag: certTag
} = client.encryptSymmetric(cert, key);
update.encryptedCert = encryptedCert;
update.certIV = certIV;
update.certTag = certTag;
}
const ssoConfig = await SSOConfig.findOneAndUpdate(
{
organization: new Types.ObjectId(organizationId)
},
update,
{
new: true
}
);
if (!ssoConfig)
throw ResourceNotFoundError({
message: "Failed to find SSO config to update"
});
interface PatchUpdate {
authProvider?: string;
isActive?: boolean;
encryptedEntryPoint?: string;
entryPointIV?: string;
entryPointTag?: string;
encryptedIssuer?: string;
issuerIV?: string;
issuerTag?: string;
encryptedCert?: string;
certIV?: string;
certTag?: string;
}
const update: PatchUpdate = {};
if (authProvider) {
update.authProvider = authProvider;
}
if (isActive !== undefined) {
update.isActive = isActive;
}
const key = await BotOrgService.getSymmetricKey(
new Types.ObjectId(organizationId)
);
if (entryPoint) {
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = client.encryptSymmetric(entryPoint, key);
update.encryptedEntryPoint = encryptedEntryPoint;
update.entryPointIV = entryPointIV;
update.entryPointTag = entryPointTag;
}
if (issuer) {
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = client.encryptSymmetric(issuer, key);
update.encryptedIssuer = encryptedIssuer;
update.issuerIV = issuerIV;
update.issuerTag = issuerTag;
}
if (update.isActive !== undefined) {
const membershipOrgs = await MembershipOrg.find({
organization: new Types.ObjectId(organizationId)
}).select("user");
if (cert) {
const {
ciphertext: encryptedCert,
iv: certIV,
tag: certTag
} = client.encryptSymmetric(cert, key);
update.encryptedCert = encryptedCert;
update.certIV = certIV;
update.certTag = certTag;
}
const ssoConfig = await SSOConfig.findOneAndUpdate(
if (update.isActive) {
await User.updateMany(
{
organization: new Types.ObjectId(organizationId)
_id: {
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
}
},
update,
{
new: true
authMethods: [ssoConfig.authProvider]
}
);
if (!ssoConfig) throw ResourceNotFoundError({
message: "Failed to find SSO config to update"
});
if (update.isActive !== undefined) {
const membershipOrgs = await MembershipOrg.find({
organization: new Types.ObjectId(organizationId)
}).select("user");
if (update.isActive) {
await User.updateMany(
{
_id: {
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
}
},
{
authMethods: [ssoConfig.authProvider],
}
);
} else {
await User.updateMany(
{
_id: {
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
}
},
{
authMethods: [AuthMethod.EMAIL],
}
);
);
} else {
await User.updateMany(
{
_id: {
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
}
},
{
authMethods: [AuthMethod.EMAIL]
}
);
}
return res.status(200).send(ssoConfig);
}
}
return res.status(200).send(ssoConfig);
};
/**
* Create organization SAML SSO configuration
* @param req
* @param res
* @returns
* @param req
* @param res
* @returns
*/
export const createSSOConfig = async (req: Request, res: Response) => {
const {
organizationId,
authProvider,
isActive,
entryPoint,
issuer,
cert
} = req.body;
const {
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
} = await validateRequest(reqValidator.CreateSsoConfigv1, req);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (!plan.samlSSO) return res.status(400).send({
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
ForbiddenError.from(permission).throwUnlessCan(
GeneralPermissionActions.Create,
OrgPermissionSubjects.Sso
);
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
if (!plan.samlSSO)
return res.status(400).send({
message:
"Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
});
const key = await BotOrgService.getSymmetricKey(
new Types.ObjectId(organizationId)
);
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = client.encryptSymmetric(entryPoint, key);
const key = await BotOrgService.getSymmetricKey(new Types.ObjectId(organizationId));
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = client.encryptSymmetric(issuer, key);
const {
ciphertext: encryptedEntryPoint,
iv: entryPointIV,
tag: entryPointTag
} = client.encryptSymmetric(entryPoint, key);
const {
ciphertext: encryptedCert,
iv: certIV,
tag: certTag
} = client.encryptSymmetric(cert, key);
const ssoConfig = await new SSOConfig({
organization: new Types.ObjectId(organizationId),
authProvider,
isActive,
encryptedEntryPoint,
entryPointIV,
entryPointTag,
encryptedIssuer,
issuerIV,
issuerTag,
encryptedCert,
certIV,
certTag
}).save();
const {
ciphertext: encryptedIssuer,
iv: issuerIV,
tag: issuerTag
} = client.encryptSymmetric(issuer, key);
return res.status(200).send(ssoConfig);
}
const {
ciphertext: encryptedCert,
iv: certIV,
tag: certTag
} = client.encryptSymmetric(cert, key);
const ssoConfig = await new SSOConfig({
organization: new Types.ObjectId(organizationId),
authProvider,
isActive,
encryptedEntryPoint,
entryPointIV,
entryPointTag,
encryptedIssuer,
issuerIV,
issuerTag,
encryptedCert,
certIV,
certTag
}).save();
return res.status(200).send(ssoConfig);
};
+78 -188
View File
@@ -1,237 +1,127 @@
import express from "express";
const router = express.Router();
import {
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../../middleware";
import { body, param, query } from "express-validator";
import { requireAuth } from "../../../middleware";
import { organizationsController } from "../../controllers/v1";
import {
ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER
} from "../../../variables";
import { AuthMode } from "../../../variables";
router.get(
"/:organizationId/plans/table",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("billingCycle").exists().isString().isIn(["monthly", "yearly"]),
validateRequest,
organizationsController.getOrganizationPlansTable
"/:organizationId/plans/table",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationPlansTable
);
router.get(
"/:organizationId/plan",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("workspaceId").optional().isString(),
validateRequest,
organizationsController.getOrganizationPlan
"/:organizationId/plan",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationPlan
);
router.post(
"/:organizationId/session/trial",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("success_url").exists().trim(),
validateRequest,
organizationsController.startOrganizationTrial
"/:organizationId/session/trial",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.startOrganizationTrial
);
router.get(
"/:organizationId/plan/billing",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("workspaceId").optional().isString(),
validateRequest,
organizationsController.getOrganizationPlanBillingInfo
"/:organizationId/plan/billing",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationPlanBillingInfo
);
router.get(
"/:organizationId/plan/table",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
query("workspaceId").optional().isString(),
validateRequest,
organizationsController.getOrganizationPlanTable
"/:organizationId/plan/table",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationPlanTable
);
router.get(
"/:organizationId/billing-details",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationBillingDetails
"/:organizationId/billing-details",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationBillingDetails
);
router.patch(
"/:organizationId/billing-details",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("email").optional().isString().trim(),
body("name").optional().isString().trim(),
validateRequest,
organizationsController.updateOrganizationBillingDetails
"/:organizationId/billing-details",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.updateOrganizationBillingDetails
);
router.get(
"/:organizationId/billing-details/payment-methods",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationPmtMethods
"/:organizationId/billing-details/payment-methods",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationPmtMethods
);
router.post(
"/:organizationId/billing-details/payment-methods",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("success_url").exists().isString(),
body("cancel_url").exists().isString(),
validateRequest,
organizationsController.addOrganizationPmtMethod
"/:organizationId/billing-details/payment-methods",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.addOrganizationPmtMethod
);
router.delete(
"/:organizationId/billing-details/payment-methods/:pmtMethodId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
param("pmtMethodId").exists().trim(),
validateRequest,
organizationsController.deleteOrganizationPmtMethod
"/:organizationId/billing-details/payment-methods/:pmtMethodId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.deleteOrganizationPmtMethod
);
router.get(
"/:organizationId/billing-details/tax-ids",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationTaxIds
"/:organizationId/billing-details/tax-ids",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationTaxIds
);
router.post(
"/:organizationId/billing-details/tax-ids",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("type").exists().isString(),
body("value").exists().isString(),
validateRequest,
organizationsController.addOrganizationTaxId
"/:organizationId/billing-details/tax-ids",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.addOrganizationTaxId
);
router.delete(
"/:organizationId/billing-details/tax-ids/:taxId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
param("taxId").exists().trim(),
validateRequest,
organizationsController.deleteOrganizationTaxId
"/:organizationId/billing-details/tax-ids/:taxId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.deleteOrganizationTaxId
);
router.get(
"/:organizationId/invoices",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationInvoices
"/:organizationId/invoices",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationInvoices
);
router.get(
"/:organizationId/licenses",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationsController.getOrganizationLicenses
"/:organizationId/licenses",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationsController.getOrganizationLicenses
);
export default router;
export default router;
+14 -42
View File
@@ -1,81 +1,53 @@
import express from "express";
const router = express.Router();
import { requireAuth } from "../../../middleware";
import {
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../../middleware";
import { body, param } from "express-validator";
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../../controllers/v1/secretScanningController";
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables";
createInstallationSession,
getCurrentOrganizationInstallationStatus,
getRisksForOrganization,
linkInstallationToOrganization,
updateRisksStatus
} from "../../../controllers/v1/secretScanningController";
import { AuthMode } from "../../../variables";
router.post(
"/create-installation-session/organization/:organizationId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("organizationId").exists().trim(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
createInstallationSession
);
router.post(
"/link-installation",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
body("installationId").exists().trim(),
body("sessionId").exists().trim(),
validateRequest,
linkInstallationToOrganization
);
router.get(
"/installation-status/organization/:organizationId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("organizationId").exists().trim(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
getCurrentOrganizationInstallationStatus
);
router.get(
"/organization/:organizationId/risks",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("organizationId").exists().trim(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
getRisksForOrganization
);
router.post(
"/organization/:organizationId/risks/:riskId/status",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
acceptedAuthModes: [AuthMode.JWT]
}),
param("organizationId").exists().trim(),
param("riskId").exists().trim(),
body("status").exists(),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
validateRequest,
updateRisksStatus
);
export default router;
export default router;
+59 -110
View File
@@ -1,146 +1,95 @@
import express from "express";
const router = express.Router();
import passport from "passport";
import {
AuthProvider
} from "../../models";
import {
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../../middleware";
import { body, query } from "express-validator";
import { requireAuth } from "../../../middleware";
import { ssoController } from "../../controllers/v1";
import { authLimiter } from "../../../helpers/rateLimiter";
import {
ACCEPTED,
ADMIN,
AuthMode,
OWNER
} from "../../../variables";
import { AuthMode } from "../../../variables";
router.get(
"/redirect/google",
authLimiter,
(req, res, next) => {
passport.authenticate("google", {
scope: ["profile", "email"],
session: false,
...(req.query.callback_port ? {
state: req.query.callback_port as string
} : {})
})(req, res, next);
}
);
router.get("/redirect/google", authLimiter, (req, res, next) => {
passport.authenticate("google", {
scope: ["profile", "email"],
session: false,
...(req.query.callback_port
? {
state: req.query.callback_port as string
}
: {})
})(req, res, next);
});
router.get(
"/google",
passport.authenticate("google", {
failureRedirect: "/login/provider/error",
session: false
passport.authenticate("google", {
failureRedirect: "/login/provider/error",
session: false
}),
ssoController.redirectSSO
);
router.get(
"/redirect/github",
authLimiter,
(req, res, next) => {
passport.authenticate("github", {
session: false,
...(req.query.callback_port ? {
state: req.query.callback_port as string
} : {})
})(req, res, next);
}
);
router.get("/redirect/github", authLimiter, (req, res, next) => {
passport.authenticate("github", {
session: false,
...(req.query.callback_port
? {
state: req.query.callback_port as string
}
: {})
})(req, res, next);
});
router.get(
"/github",
authLimiter,
passport.authenticate("github", {
failureRedirect: "/login/provider/error",
session: false
passport.authenticate("github", {
failureRedirect: "/login/provider/error",
session: false
}),
ssoController.redirectSSO
);
router.get(
"/redirect/saml2/:ssoIdentifier",
authLimiter,
(req, res, next) => {
const options = {
failureRedirect: "/",
additionalParams: {
RelayState: req.query.callback_port ?? ""
},
};
passport.authenticate("saml", options)(req, res, next);
}
);
router.get("/redirect/saml2/:ssoIdentifier", authLimiter, (req, res, next) => {
const options = {
failureRedirect: "/",
additionalParams: {
RelayState: req.query.callback_port ?? ""
}
};
passport.authenticate("saml", options)(req, res, next);
});
router.post("/saml2/:ssoIdentifier",
passport.authenticate("saml", {
failureRedirect: "/login/provider/error",
failureFlash: true,
router.post(
"/saml2/:ssoIdentifier",
passport.authenticate("saml", {
failureRedirect: "/login/provider/error",
failureFlash: true,
session: false
}),
ssoController.redirectSSO
);
router.get(
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
locationOrganizationId: "query"
}),
query("organizationId").exists().trim(),
validateRequest,
ssoController.getSSOConfig
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
ssoController.getSSOConfig
);
router.post(
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
locationOrganizationId: "body"
}),
body("organizationId").exists().trim(),
body("authProvider").exists().isString().isIn([AuthProvider.OKTA_SAML]),
body("isActive").exists().isBoolean(),
body("entryPoint").exists().isString(),
body("issuer").exists().isString(),
body("cert").exists().isString(),
validateRequest,
ssoController.createSSOConfig
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
ssoController.createSSOConfig
);
router.patch(
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
locationOrganizationId: "body"
}),
body("organizationId").exists().trim(),
body("authProvider").optional().isString(),
body("isActive").optional().isBoolean(),
body("entryPoint").optional().isString(),
body("issuer").optional().isString(),
body("cert").optional().isString(),
validateRequest,
ssoController.updateSSOConfig
"/config",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
ssoController.updateSSOConfig
);
export default router;
export default router;
+11 -14
View File
@@ -8,23 +8,20 @@ import { AuthMode } from "../../variables";
// TODO endpoint: consider moving these endpoints to be under /organization to be more RESTful
router.post(
"/signup",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
body("inviteeEmail").exists().trim().notEmpty().isEmail(),
body("organizationId").exists().trim().notEmpty(),
validateRequest,
membershipOrgController.inviteUserToOrganization
"/signup",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
membershipOrgController.inviteUserToOrganization
);
router.post(
"/verify",
body("email").exists().trim().notEmpty(),
body("organizationId").exists().trim().notEmpty(),
body("code").exists().trim().notEmpty(),
validateRequest,
membershipOrgController.verifyUserToOrganization
"/verify",
body("email").exists().trim().notEmpty(),
body("organizationId").exists().trim().notEmpty(),
body("code").exists().trim().notEmpty(),
validateRequest,
membershipOrgController.verifyUserToOrganization
);
export default router;
+37 -33
View File
@@ -9,45 +9,49 @@ import { AuthMode } from "../../variables";
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
// TODO endpoint: consider moving these endpoints to be under /workspace to be more RESTful
router.get( // TODO endpoint: deprecate - used for old CLI (deprecate)
"/:workspaceId/connect",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
param("workspaceId").exists().trim(),
validateRequest,
membershipController.validateMembership
router.get(
// TODO endpoint: deprecate - used for old CLI (deprecate)
"/:workspaceId/connect",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
param("workspaceId").exists().trim(),
validateRequest,
membershipController.validateMembership
);
router.delete( // TODO endpoint: check dashboard
"/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
param("membershipId").exists().trim(),
validateRequest,
membershipController.deleteMembership
router.delete(
// TODO endpoint: check dashboard
"/:membershipId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
param("membershipId").exists().trim(),
validateRequest,
membershipController.deleteMembership
);
router.post( // TODO endpoint: check dashboard
"/:membershipId/change-role",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
body("role").exists().trim(),
validateRequest,
membershipController.changeMembershipRole
router.post(
// TODO endpoint: check dashboard
"/:membershipId/change-role",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
body("role").exists().trim(),
validateRequest,
membershipController.changeMembershipRole
);
router.post( // TODO endpoint: check dashboard
"/:membershipId/deny-permissions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
param("membershipId").isMongoId().exists().trim(),
body("permissions").isArray().exists(),
validateRequest,
EEMembershipControllers.denyMembershipPermissions
router.post(
// TODO endpoint: check dashboard
"/:membershipId/deny-permissions",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
param("membershipId").isMongoId().exists().trim(),
body("permissions").isArray().exists(),
validateRequest,
EEMembershipControllers.denyMembershipPermissions
);
export default router;
+14 -15
View File
@@ -5,24 +5,23 @@ import { requireAuth, validateRequest } from "../../middleware";
import { membershipOrgController } from "../../controllers/v1";
import { AuthMode } from "../../variables";
router.post( // TODO endpoint: check dashboard
"/membershipOrg/:membershipOrgId/change-role",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
param("membershipOrgId"),
validateRequest,
membershipOrgController.changeMembershipOrgRole
router.post(
// TODO endpoint: check dashboard
"/membershipOrg/:membershipOrgId/change-role",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
param("membershipOrgId"),
validateRequest,
membershipOrgController.changeMembershipOrgRole
);
router.delete(
"/:membershipOrgId", // TODO endpoint: check dashboard
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
param("membershipOrgId").exists().trim(),
validateRequest,
membershipOrgController.deleteMembershipOrg
"/:membershipOrgId", // TODO endpoint: check dashboard
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
membershipOrgController.deleteMembershipOrg
);
export default router;
+65 -132
View File
@@ -1,166 +1,99 @@
import express from "express";
const router = express.Router();
import { body, param } from "express-validator";
import {
requireAuth,
requireOrganizationAuth,
validateRequest,
} from "../../middleware";
import {
ACCEPTED,
ADMIN,
AuthMode,
MEMBER,
OWNER
} from "../../variables";
import { requireAuth } from "../../middleware";
import { AuthMode } from "../../variables";
import { organizationController } from "../../controllers/v1";
router.get( // TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
organizationController.getOrganizations
router.get(
// TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.getOrganizations
);
router.post( // not used on frontend
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
body("organizationName").exists().trim().notEmpty(),
validateRequest,
organizationController.createOrganization
router.post(
// not used on frontend
"/",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.createOrganization
);
router.get(
"/:organizationId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganization
"/:organizationId",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.getOrganization
);
router.get( // TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
"/:organizationId/users",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationMembers
router.get(
// TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
"/:organizationId/users",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.getOrganizationMembers
);
router.get( // TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationWorkspaces
router.get(
// TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.getOrganizationWorkspaces
);
router.patch(
"/:organizationId/name",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("name").exists().trim().notEmpty(),
validateRequest,
organizationController.changeOrganizationName
"/:organizationId/name",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.changeOrganizationName
);
router.get(
"/:organizationId/incidentContactOrg",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationIncidentContacts
"/:organizationId/incidentContactOrg",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.getOrganizationIncidentContacts
);
router.post(
"/:organizationId/incidentContactOrg",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("email").exists().trim().notEmpty(),
validateRequest,
organizationController.addOrganizationIncidentContact
"/:organizationId/incidentContactOrg",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.addOrganizationIncidentContact
);
router.delete(
"/:organizationId/incidentContactOrg",
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
body("email").exists().trim().notEmpty(),
validateRequest,
organizationController.deleteOrganizationIncidentContact
"/:organizationId/incidentContactOrg",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.deleteOrganizationIncidentContact
);
router.post(
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE
requireAuth({
acceptedAuthModes: [AuthMode.JWT],
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.createOrganizationPortalSession
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.createOrganizationPortalSession
);
router.get(
"/:organizationId/workspace-memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED],
}),
param("organizationId").exists().trim(),
validateRequest,
organizationController.getOrganizationMembersAndTheirWorkspaces
"/:organizationId/workspace-memberships",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
organizationController.getOrganizationMembersAndTheirWorkspaces
);
export default router;
+12 -51
View File
@@ -2,95 +2,56 @@ import express from "express";
const router = express.Router();
import {
requireAuth,
requireMembershipOrgAuth,
requireOrganizationAuth,
validateRequest
requireOrganizationAuth
} from "../../middleware";
import { body, param } from "express-validator";
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../variables";
import { ACCEPTED, ADMIN, AuthMode, OWNER } from "../../variables";
import { organizationsController } from "../../controllers/v2";
// TODO: /POST to create membership
router.get(
"/:organizationId/memberships",
param("organizationId").exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN, MEMBER],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.getOrganizationMemberships
);
router.patch(
"/:organizationId/memberships/:membershipId",
param("organizationId").exists().trim(),
param("membershipId").exists().trim(),
body("role").exists().isString().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
requireMembershipOrgAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.updateOrganizationMembership
);
router.delete(
"/:organizationId/memberships/:membershipId",
param("organizationId").exists().trim(),
param("membershipId").exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
requireMembershipOrgAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.deleteOrganizationMembership
);
router.get(
"/:organizationId/workspaces",
param("organizationId").exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
}),
organizationsController.getOrganizationWorkspaces
);
router.get(
// TODO endpoint: deprecate service accounts
"/:organizationId/service-accounts",
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED]
}),
organizationsController.getOrganizationWorkspaces
);
router.get( // TODO endpoint: deprecate service accounts
"/:organizationId/service-accounts",
param("organizationId").exists().trim(),
validateRequest,
requireAuth({
acceptedAuthModes: [AuthMode.JWT]
}),
requireOrganizationAuth({
acceptedRoles: [OWNER, ADMIN],
acceptedStatuses: [ACCEPTED],
}),
organizationsController.getOrganizationServiceAccounts
organizationsController.getOrganizationServiceAccounts
);
export default router;
+32 -22
View File
@@ -2,6 +2,7 @@ import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@ca
import { MembershipOrg } from "../models";
import { IRole } from "../models/role";
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
import { ACCEPTED } from "../variables";
export enum GeneralPermissionActions {
Read = "read",
@@ -10,34 +11,37 @@ export enum GeneralPermissionActions {
Delete = "delete"
}
export enum WorkspacePermissionActions {
Read = "read",
Create = "create"
}
export enum OrgPermissionSubjects {
Workspace = "workspace",
Role = "role",
Member = "member",
Settings = "settings",
ServiceAccount = "service-account",
IncidentAccount = "incident-contact",
Sso = "sso",
Billing = "billing"
Billing = "billing",
SecretScanning = "secret-scanning"
}
export type OrgPermissionSet =
| [GeneralPermissionActions, OrgPermissionSubjects.Workspace]
| [WorkspacePermissionActions, OrgPermissionSubjects.Workspace]
| [GeneralPermissionActions, OrgPermissionSubjects.Role]
| [GeneralPermissionActions, OrgPermissionSubjects.Member]
| [GeneralPermissionActions, OrgPermissionSubjects.Settings]
| [GeneralPermissionActions, OrgPermissionSubjects.ServiceAccount]
| [GeneralPermissionActions, OrgPermissionSubjects.IncidentAccount]
| [GeneralPermissionActions, OrgPermissionSubjects.Sso]
| [GeneralPermissionActions, OrgPermissionSubjects.SecretScanning]
| [GeneralPermissionActions, OrgPermissionSubjects.Billing];
const buildAdminPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
// ws permissions
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Workspace);
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Workspace);
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Workspace);
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.Workspace);
can(WorkspacePermissionActions.Read, OrgPermissionSubjects.Workspace);
can(WorkspacePermissionActions.Create, OrgPermissionSubjects.Workspace);
// role permission
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Role);
@@ -49,16 +53,16 @@ const buildAdminPermission = () => {
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Member);
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.Member);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
can(GeneralPermissionActions.Create, OrgPermissionSubjects.SecretScanning);
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.SecretScanning);
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.SecretScanning);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Settings);
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Settings);
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Settings);
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.Settings);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.ServiceAccount);
can(GeneralPermissionActions.Create, OrgPermissionSubjects.ServiceAccount);
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.ServiceAccount);
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.ServiceAccount);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(GeneralPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.IncidentAccount);
@@ -82,14 +86,15 @@ export const adminPermissions = buildAdminPermission();
const buildMemberPermission = () => {
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Workspace);
can(WorkspacePermissionActions.Read, OrgPermissionSubjects.Workspace);
can(WorkspacePermissionActions.Create, OrgPermissionSubjects.Workspace);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Member);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Settings);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Billing);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Sso);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.ServiceAccount);
can(GeneralPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
return build();
};
@@ -98,23 +103,28 @@ export const memberPermissions = buildMemberPermission();
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
// TODO(akhilmhdh): speed this up by pulling from cache later
const orgMembership = await MembershipOrg.findOne({ user: userId, organization: orgId })
const membership = await MembershipOrg.findOne({
user: userId,
organization: orgId,
status: ACCEPTED
})
.populate<{ customRole: IRole & { permissions: RawRuleOf<MongoAbility<OrgPermissionSet>>[] } }>(
"customRole"
)
.exec();
if (!orgMembership || (orgMembership.role === "custom" && !orgMembership.customRole)) {
if (!membership || (membership.role === "custom" && !membership.customRole)) {
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
}
if (orgMembership.role === "admin" || orgMembership.role === "owner") return adminPermissions;
if (membership.role === "admin" || membership.role === "owner")
return { permission: adminPermissions, membership };
if (orgMembership.role === "member") return memberPermissions;
if (membership.role === "member") return { permission: memberPermissions, membership };
if (orgMembership.role === "custom") {
const permission = createMongoAbility<OrgPermissionSet>(orgMembership.customRole.permissions);
return permission;
if (membership.role === "custom") {
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions);
return { permission, membership };
}
throw BadRequestError({ message: "User role not found" });
+55 -39
View File
@@ -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()
})
});
+159 -15
View File
@@ -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() })
});
+25
View File
@@ -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() })
});
+28
View File
@@ -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
View File
@@ -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()
})
});