mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(rbac): added new zod validation and permission check for all org level api
This commit is contained in:
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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" });
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
MembershipOrg,
|
||||
} from "../models";
|
||||
import {
|
||||
validateMembershipOrg,
|
||||
} from "../helpers/membershipOrg";
|
||||
import {
|
||||
MembershipOrgNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../utils/errors";
|
||||
import { MembershipOrg } from "../models";
|
||||
import { validateMembershipOrg } from "../helpers/membershipOrg";
|
||||
import { MembershipOrgNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { AuthData } from "../interfaces/middleware";
|
||||
import { ActorType } from "../ee/models";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||
@@ -22,35 +16,57 @@ import { ActorType } from "../ee/models";
|
||||
* @param {MembershipOrg} - validated organization membership
|
||||
*/
|
||||
export const validateClientForMembershipOrg = async ({
|
||||
authData,
|
||||
membershipOrgId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
authData,
|
||||
membershipOrgId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
authData: AuthData;
|
||||
membershipOrgId: Types.ObjectId;
|
||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||
acceptedStatuses: Array<"invited" | "accepted">;
|
||||
authData: AuthData;
|
||||
membershipOrgId: Types.ObjectId;
|
||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||
acceptedStatuses: Array<"invited" | "accepted">;
|
||||
}) => {
|
||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||
|
||||
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
||||
message: "Failed to find organization membership ",
|
||||
});
|
||||
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
case ActorType.SERVICE:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service account client authorization for organization membership",
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!membershipOrg)
|
||||
throw MembershipOrgNotFoundError({
|
||||
message: "Failed to find organization membership "
|
||||
});
|
||||
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
await validateMembershipOrg({
|
||||
userId: authData.authPayload._id,
|
||||
organizationId: membershipOrg.organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return membershipOrg;
|
||||
case ActorType.SERVICE:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service account client authorization for organization membership"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const DelOrgMembershipv1 = z.object({
|
||||
params: z.object({
|
||||
membershipOrgId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const InviteUserToOrgv1 = z.object({
|
||||
body: z.object({
|
||||
inviteeEmail: z.string().trim().email(),
|
||||
organizationId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const VerifyUserToOrgv1 = z.object({
|
||||
body: z.object({
|
||||
email: z.string().trim().email(),
|
||||
organizationId: z.string().trim(),
|
||||
code: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import { Types } from "mongoose";
|
||||
import {
|
||||
IUser,
|
||||
Organization,
|
||||
} from "../models";
|
||||
import {
|
||||
OrganizationNotFoundError,
|
||||
UnauthorizedRequestError,
|
||||
} from "../utils/errors";
|
||||
import { z } from "zod";
|
||||
import { IUser, Organization } from "../models";
|
||||
import { OrganizationNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||
import { validateUserClientForOrganization } from "./user";
|
||||
import { AuthData } from "../interfaces/middleware";
|
||||
import { ActorType } from "../ee/models";
|
||||
@@ -21,7 +16,7 @@ export const validateClientForOrganization = async ({
|
||||
authData,
|
||||
organizationId,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
acceptedStatuses
|
||||
}: {
|
||||
authData: AuthData;
|
||||
organizationId: Types.ObjectId;
|
||||
@@ -32,10 +27,10 @@ export const validateClientForOrganization = async ({
|
||||
|
||||
if (!organization) {
|
||||
throw OrganizationNotFoundError({
|
||||
message: "Failed to find organization",
|
||||
message: "Failed to find organization"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
let membershipOrg;
|
||||
switch (authData.actor.type) {
|
||||
case ActorType.USER:
|
||||
@@ -43,13 +38,162 @@ export const validateClientForOrganization = async ({
|
||||
user: authData.authPayload as IUser,
|
||||
organization,
|
||||
acceptedRoles,
|
||||
acceptedStatuses,
|
||||
acceptedStatuses
|
||||
});
|
||||
|
||||
return { organization, membershipOrg };
|
||||
return { organization, membershipOrg };
|
||||
case ActorType.SERVICE:
|
||||
throw UnauthorizedRequestError({
|
||||
message: "Failed service token authorization for organization",
|
||||
message: "Failed service token authorization for organization"
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const GetOrgPlansTablev1 = z.object({
|
||||
query: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgPlanv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||
});
|
||||
|
||||
export const StartOrgTrailv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ success_url: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgPlanBillingInfov1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||
});
|
||||
|
||||
export const GetOrgPlanTablev1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||
});
|
||||
|
||||
export const GetOrgBillingDetailsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const UpdateOrgBillingDetailsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
email: z.string().trim().email().optional(),
|
||||
name: z.string().trim().optional()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgPmtMethodsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgPmtMethodv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
success_url: z.string().trim(),
|
||||
cancel_url: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DelOrgPmtMethodv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
pmtMethodId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgTaxIdsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgTaxId = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({
|
||||
type: z.string().trim(),
|
||||
value: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DelOrgTaxIdv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim(),
|
||||
taxId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgInvoicesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgLicencesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgv1 = z.object({
|
||||
body: z.object({
|
||||
organizationName: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgv1 = z.object({
|
||||
params: z.object({
|
||||
organizationId: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const GetOrgMembersv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgWorkspacesv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const ChangeOrgNamev1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ name: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgIncidentContactv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgIncideContact = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ email: z.string().email().trim() })
|
||||
});
|
||||
|
||||
export const DelOrgIncideContact = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() }),
|
||||
body: z.object({ email: z.string().email().trim() })
|
||||
});
|
||||
|
||||
export const CreateOrgPortalSessionv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgMembersAndWsv1 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgMembersv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const UpdateOrgMemberv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||
body: z.object({
|
||||
role: z.string().trim()
|
||||
})
|
||||
});
|
||||
|
||||
export const DeleteOrgMemberv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() })
|
||||
});
|
||||
|
||||
export const GetOrgWorkspacesv2 = z.object({
|
||||
params: z.object({ organizationId: z.string().trim() })
|
||||
});
|
||||
|
||||
25
backend/src/validation/secretScanning.ts
Normal file
25
backend/src/validation/secretScanning.ts
Normal 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
backend/src/validation/sso.ts
Normal file
28
backend/src/validation/sso.ts
Normal 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()
|
||||
})
|
||||
});
|
||||
@@ -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()
|
||||
})
|
||||
});
|
||||
|
||||
@@ -15,10 +15,18 @@ export type TRole = {
|
||||
updatedAt: string;
|
||||
};
|
||||
|
||||
export type TPermission = {
|
||||
export type TPermission = TWorkspacePermission | TGeneralPermission;
|
||||
|
||||
type TGeneralPermission = {
|
||||
condition?: Record<string, any>;
|
||||
action: "read" | "edit" | "create" | "delete";
|
||||
subject: string;
|
||||
subject: "member" | "role" | "incident-contact" | "sso" | "billing" | "settings";
|
||||
};
|
||||
|
||||
type TWorkspacePermission = {
|
||||
condition?: Record<string, any>;
|
||||
action: "read" | "create";
|
||||
subject: "workspace";
|
||||
};
|
||||
|
||||
export type TCreateRoleDTO = {
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
TFormSchema
|
||||
} from "./OrgRoleModifySection.utils";
|
||||
import { RolePermission } from "./RolePermission";
|
||||
import { ServiceAccountPermission } from "./ServiceAccountPermission";
|
||||
import { SecretScannigPermission } from "./SecretScanningPermission";
|
||||
import { SettingsPermission } from "./SettingsPermission";
|
||||
import { SsoPermission } from "./SsoPermission";
|
||||
import { WorkspacePermission } from "./WorkspacePermission";
|
||||
@@ -183,15 +183,15 @@ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<SsoPermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<ServiceAccountPermission
|
||||
<SecretScannigPermission
|
||||
isNonEditable={isNonEditable}
|
||||
control={control}
|
||||
setValue={setValue}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<SsoPermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4 mt-12">
|
||||
<Button
|
||||
|
||||
@@ -3,8 +3,6 @@ import { z } from "zod";
|
||||
|
||||
import { TPermission } from "@app/hooks/api/roles/types";
|
||||
|
||||
const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const;
|
||||
|
||||
const generalPermissionSchema = z.object({
|
||||
read: z.boolean().optional(),
|
||||
edit: z.boolean().optional(),
|
||||
@@ -17,12 +15,16 @@ export const formSchema = z.object({
|
||||
description: z.string().optional(),
|
||||
slug: z.string(),
|
||||
permissions: z.object({
|
||||
workspace: z.record(generalPermissionSchema),
|
||||
workspace: z.object({
|
||||
read: z.boolean().optional(),
|
||||
create: z.boolean().optional()
|
||||
}),
|
||||
member: generalPermissionSchema,
|
||||
role: generalPermissionSchema,
|
||||
settings: generalPermissionSchema,
|
||||
"service-account": generalPermissionSchema,
|
||||
"incident-contact": generalPermissionSchema,
|
||||
"secret-scanning": generalPermissionSchema,
|
||||
sso: generalPermissionSchema,
|
||||
billing: generalPermissionSchema
|
||||
})
|
||||
@@ -30,25 +32,6 @@ export const formSchema = z.object({
|
||||
|
||||
export type TFormSchema = z.infer<typeof formSchema>;
|
||||
|
||||
const api2FormWorkspace = (
|
||||
formVal: TFormSchema["permissions"]["workspace"],
|
||||
permission: TPermission
|
||||
) => {
|
||||
if (permission.subject !== "workspace") return;
|
||||
const isCustomRule = Boolean(permission?.condition?.id);
|
||||
// full access
|
||||
if (isCustomRule && !formVal?.custom) {
|
||||
formVal.custom = { read: true, edit: true, delete: true, create: true };
|
||||
}
|
||||
|
||||
const workspaceId = permission?.condition?.id || "all";
|
||||
// initalize
|
||||
if (!formVal?.[workspaceId]) {
|
||||
formVal[workspaceId] = { read: false, edit: false, create: false, delete: false };
|
||||
}
|
||||
formVal[workspaceId][permission.action] = true;
|
||||
};
|
||||
|
||||
// convert role permission to form compatiable data structure
|
||||
export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
||||
const formVal: TFormSchema["permissions"] = {
|
||||
@@ -59,68 +42,31 @@ export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
||||
sso: {},
|
||||
member: {},
|
||||
"service-account": {},
|
||||
"incident-contact": {}
|
||||
"incident-contact": {},
|
||||
"secret-scanning": {}
|
||||
};
|
||||
|
||||
permissions.forEach((permission) => {
|
||||
switch (permission.subject) {
|
||||
case "workspace":
|
||||
api2FormWorkspace(formVal?.workspace, permission);
|
||||
break;
|
||||
default:
|
||||
// everything else follows same pattern
|
||||
// formVal[settings][read | write] = true
|
||||
formVal[permission.subject as keyof TFormSchema["permissions"]][permission.action] = true;
|
||||
break;
|
||||
}
|
||||
// akhilmhdh: this is typecast as workspace key else i would need an if loop with same condition on both side
|
||||
formVal[permission.subject][permission.action as keyof typeof formVal.workspace] = true;
|
||||
});
|
||||
|
||||
return formVal;
|
||||
};
|
||||
|
||||
const form2ApiWorkspace = (
|
||||
permissions: TPermission[],
|
||||
workspace: TFormSchema["permissions"]["workspace"]
|
||||
) => {
|
||||
const isFullAccess = PERMISSION_ACTIONS.every((action) => workspace?.all?.[action]);
|
||||
// if any of them is set in all push it without any condition
|
||||
PERMISSION_ACTIONS.forEach((action) => {
|
||||
if (workspace?.all?.[action]) permissions.push({ action, subject: "workspace" });
|
||||
});
|
||||
|
||||
if (!isFullAccess) {
|
||||
Object.keys(workspace)
|
||||
.filter((id) => id !== "all" && id !== "custom") // remove all and custom for iter
|
||||
.forEach((workspaceId) => {
|
||||
const actions = Object.keys(workspace[workspaceId]) as ["read", "edit", "create", "delete"];
|
||||
actions.forEach((action) => {
|
||||
// if not full access for an action
|
||||
if (!workspace?.all?.[action] && workspace[workspaceId][action]) {
|
||||
permissions.push({ action, subject: "workspace", condition: { id: workspaceId } });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
|
||||
const permissions: TPermission[] = [];
|
||||
if (formVal?.workspace) {
|
||||
// easy deep copy
|
||||
form2ApiWorkspace(permissions, JSON.parse(JSON.stringify(formVal.workspace)));
|
||||
}
|
||||
// other than workspace everything else follows same
|
||||
// if in future there is a different follow the above on how workspace is done
|
||||
const { workspace, ...rules } = formVal;
|
||||
(Object.keys(rules) as Array<keyof typeof rules>).forEach((rule) => {
|
||||
(Object.keys(formVal) as Array<keyof typeof formVal>).forEach((rule) => {
|
||||
// all these type annotations are due to Object.keys of ts cannot infer and put it just a string[]
|
||||
// quite annoying i know
|
||||
const actions = Object.keys(rules[rule]) as Array<
|
||||
const actions = Object.keys(formVal[rule]) as Array<
|
||||
keyof z.infer<typeof generalPermissionSchema>
|
||||
>;
|
||||
|
||||
actions.forEach((action) => {
|
||||
if (rules[rule][action]) {
|
||||
permissions.push({ action, subject: rule });
|
||||
// akhilmhdh: set it as any due to the union type bug i would end up writing an if else with same condition on both side
|
||||
if (formVal[rule][action as keyof typeof formVal.workspace]) {
|
||||
permissions.push({ subject: rule, action } as any);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
import { faLaptopCode } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
@@ -30,10 +30,10 @@ const PERMISSIONS = [
|
||||
{ action: "delete", label: "Remove" }
|
||||
] as const;
|
||||
|
||||
export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||
export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||
const rule = useWatch({
|
||||
control,
|
||||
name: "permissions.service-account"
|
||||
name: "permissions.secret-scanning"
|
||||
});
|
||||
const [isCustom, setIsCustom] = useToggle();
|
||||
|
||||
@@ -60,7 +60,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
||||
case Permission.NoAccess:
|
||||
setIsCustom.off();
|
||||
setValue(
|
||||
"permissions.service-account",
|
||||
"permissions.secret-scanning",
|
||||
{ read: false, edit: false, create: false, delete: false },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
@@ -68,7 +68,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
||||
case Permission.FullAccess:
|
||||
setIsCustom.off();
|
||||
setValue(
|
||||
"permissions.service-account",
|
||||
"permissions.secret-scanning",
|
||||
{ read: true, edit: true, create: true, delete: true },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
@@ -76,7 +76,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
||||
case Permission.ReadOnly:
|
||||
setIsCustom.off();
|
||||
setValue(
|
||||
"permissions.service-account",
|
||||
"permissions.secret-scanning",
|
||||
{ read: true, edit: false, create: false, delete: false },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
@@ -84,7 +84,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
||||
default:
|
||||
setIsCustom.on();
|
||||
setValue(
|
||||
"permissions.service-account",
|
||||
"permissions.secret-scanning",
|
||||
{ read: false, edit: false, create: false, delete: false },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
@@ -101,11 +101,11 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faLaptopCode} className="text-4xl" />
|
||||
<FontAwesomeIcon icon={faMagnifyingGlass} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">Service Accounts</div>
|
||||
<div className="text-xs font-light">Service Account management control</div>
|
||||
<div className="font-medium mb-1 text-lg">Secret Scanning</div>
|
||||
<div className="text-xs font-light">Secret scanning management control</div>
|
||||
</div>
|
||||
<div>
|
||||
<Select
|
||||
@@ -129,14 +129,14 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
<Controller
|
||||
name={`permissions.service-account.${action}`}
|
||||
key={`permissions.service-account.${action}`}
|
||||
name={`permissions.role.${action}`}
|
||||
key={`permissions.role.${action}`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
id={`permissions.service-account.${action}`}
|
||||
id={`permissions.role.${action}`}
|
||||
isDisabled={isNonEditable}
|
||||
>
|
||||
{label}
|
||||
@@ -1,23 +1,12 @@
|
||||
import { useMemo } from "react";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
import { faClipboardList } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faMoneyBill } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
Checkbox,
|
||||
Select,
|
||||
SelectItem,
|
||||
Table,
|
||||
TableContainer,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
||||
import { useToggle } from "@app/hooks";
|
||||
|
||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
||||
|
||||
@@ -34,48 +23,50 @@ enum Permission {
|
||||
Custom = "custom"
|
||||
}
|
||||
|
||||
export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||
const { workspaces } = useWorkspace();
|
||||
const PERMISSIONS = [
|
||||
{ action: "read", label: "Read" },
|
||||
{ action: "create", label: "Create" }
|
||||
] as const;
|
||||
|
||||
const customWorkspaceRule = useWatch({
|
||||
export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||
const rule = useWatch({
|
||||
control,
|
||||
name: "permissions.workspace.custom"
|
||||
name: "permissions.workspace"
|
||||
});
|
||||
const isCustom = Boolean(customWorkspaceRule);
|
||||
const allWorkspaceRule = useWatch({ control, name: "permissions.workspace.all" });
|
||||
const [isCustom, setIsCustom] = useToggle();
|
||||
|
||||
const selectedPermissionCategory = useMemo(() => {
|
||||
const { read, delete: del, edit, create } = allWorkspaceRule || {};
|
||||
if (read && del && edit && create) return Permission.FullAccess;
|
||||
if (read) return Permission.ReadOnly;
|
||||
return Permission.NoAccess;
|
||||
}, [allWorkspaceRule]);
|
||||
let score = 0;
|
||||
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||
const totalActions = PERMISSIONS.length;
|
||||
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
||||
|
||||
if (isCustom) return Permission.Custom;
|
||||
if (score === 0) return Permission.NoAccess;
|
||||
if (score === totalActions) return Permission.FullAccess;
|
||||
if (score === 1 && rule.read) return Permission.ReadOnly;
|
||||
|
||||
return Permission.Custom;
|
||||
}, [rule, isCustom]);
|
||||
|
||||
useEffect(() => {
|
||||
selectedPermissionCategory === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
||||
}, [selectedPermissionCategory]);
|
||||
|
||||
const handlePermissionChange = (val: Permission) => {
|
||||
val === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
||||
switch (val) {
|
||||
case Permission.NoAccess:
|
||||
setValue("permissions.workspace", {}, { shouldDirty: true });
|
||||
setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true });
|
||||
break;
|
||||
case Permission.FullAccess:
|
||||
setValue(
|
||||
"permissions.workspace",
|
||||
{ all: { read: true, edit: true, create: true, delete: true } },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
setValue("permissions.workspace", { read: true, create: true }, { shouldDirty: true });
|
||||
break;
|
||||
case Permission.ReadOnly:
|
||||
setValue(
|
||||
"permissions.workspace",
|
||||
{ all: { read: true, edit: false, create: false, delete: false } },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
setValue("permissions.workspace", { read: true, create: false }, { shouldDirty: true });
|
||||
break;
|
||||
default:
|
||||
setValue(
|
||||
"permissions.workspace",
|
||||
{ custom: { read: false, edit: false, create: false, delete: false } },
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true });
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -84,23 +75,22 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
(selectedPermissionCategory !== Permission.NoAccess || isCustom) &&
|
||||
"border-l-2 border-primary-600"
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center space-x-4">
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faClipboardList} className="text-4xl" />
|
||||
<FontAwesomeIcon icon={faMoneyBill} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">Projects</div>
|
||||
<div className="text-xs font-light">User project access control</div>
|
||||
<div className="font-medium mb-1 text-lg">Project</div>
|
||||
<div className="text-xs font-light">Project management control</div>
|
||||
</div>
|
||||
<div>
|
||||
<Select
|
||||
defaultValue={Permission.NoAccess}
|
||||
isDisabled={isNonEditable}
|
||||
value={isCustom ? Permission.Custom : selectedPermissionCategory}
|
||||
value={selectedPermissionCategory}
|
||||
onValueChange={handlePermissionChange}
|
||||
>
|
||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||
@@ -112,100 +102,27 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
||||
</div>
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isCustom ? "auto" : 0 }}
|
||||
className="overflow-hidden"
|
||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
>
|
||||
<TableContainer className="border-mineshaft-500 mt-6">
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th />
|
||||
<Th className="text-center">Read</Th>
|
||||
<Th className="text-center">Create</Th>
|
||||
<Th className="text-center">Edit</Th>
|
||||
<Th className="text-center">Delete</Th>
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isCustom &&
|
||||
workspaces?.map(({ name, _id: id }) => (
|
||||
<Tr key={`custom-role-ws-${name}`}>
|
||||
<Td>{name}</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
name={`permissions.workspace.${id}.read`}
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
id={`permissions.workspace.${id}.read`}
|
||||
isDisabled={isNonEditable}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
name={`permissions.workspace.${id}.create`}
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
id={`permissions.workspace.${id}.modify`}
|
||||
isDisabled={isNonEditable}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
name={`permissions.workspace.${id}.edit`}
|
||||
control={control}
|
||||
defaultValue={false}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
id={`permissions.workspace.${id}.modify`}
|
||||
isDisabled={isNonEditable}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
defaultValue={false}
|
||||
name={`permissions.workspace.${id}.delete`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="flex items-center justify-center">
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
id={`permissions.workspace.${id}.delete`}
|
||||
isDisabled={isNonEditable}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</Td>
|
||||
</Tr>
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
<Controller
|
||||
name={`permissions.workspace.${action}`}
|
||||
key={`permissions.workspace.${action}`}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
isChecked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
id={`permissions.workspace.${action}`}
|
||||
isDisabled={isNonEditable}
|
||||
>
|
||||
{label}
|
||||
</Checkbox>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user