mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +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 { sendMail } from "../../helpers/nodemailer";
|
||||||
import { TokenService } from "../../services";
|
import { TokenService } from "../../services";
|
||||||
import { EELicenseService } from "../../ee/services";
|
import { EELicenseService } from "../../ee/services";
|
||||||
import {
|
import { ACCEPTED, INVITED, MEMBER, TOKEN_EMAIL_ORG_INVITATION } from "../../variables";
|
||||||
ACCEPTED,
|
import * as reqValidator from "../../validation/membershipOrg";
|
||||||
ADMIN,
|
|
||||||
INVITED,
|
|
||||||
MEMBER,
|
|
||||||
OWNER,
|
|
||||||
TOKEN_EMAIL_ORG_INVITATION
|
|
||||||
} from "../../variables";
|
|
||||||
import {
|
import {
|
||||||
getJwtSignupLifetime,
|
getJwtSignupLifetime,
|
||||||
getJwtSignupSecret,
|
getJwtSignupSecret,
|
||||||
@@ -23,6 +17,13 @@ import {
|
|||||||
getSmtpConfigured
|
getSmtpConfigured
|
||||||
} from "../../config";
|
} from "../../config";
|
||||||
import { validateUserEmail } from "../../validation";
|
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
|
* Delete organization membership with id [membershipOrgId] from organization
|
||||||
@@ -31,7 +32,9 @@ import { validateUserEmail } from "../../validation";
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteMembershipOrg = async (req: Request, _res: Response) => {
|
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
|
// check if organization membership to delete exists
|
||||||
const membershipOrgToDelete = await MembershipOrg.findOne({
|
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");
|
throw new Error("Failed to delete organization membership that doesn't exist");
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if user is a member and admin of the organization
|
const { permission, membership: membershipOrg } = await getUserOrgPermissions(
|
||||||
// whose membership we wish to delete
|
req.user._id,
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
membershipOrgToDelete.organization.toString()
|
||||||
user: req.user._id,
|
);
|
||||||
organization: membershipOrgToDelete.organization
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
});
|
GeneralPermissionActions.Delete,
|
||||||
|
OrgPermissionSubjects.Member
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete organization membership
|
// delete organization membership
|
||||||
await deleteMemberFromOrg({
|
await deleteMemberFromOrg({
|
||||||
@@ -96,22 +92,20 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
||||||
let inviteeMembershipOrg, completeInviteLink;
|
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 host = req.headers.host;
|
||||||
const siteUrl = `${req.protocol}://${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 plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||||
|
|
||||||
const ssoConfig = await SSOConfig.findOne({
|
const ssoConfig = await SSOConfig.findOne({
|
||||||
organization: new Types.ObjectId(organizationId)
|
organization: new Types.ObjectId(organizationId)
|
||||||
});
|
});
|
||||||
@@ -119,9 +113,8 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
if (ssoConfig && ssoConfig.isActive) {
|
if (ssoConfig && ssoConfig.isActive) {
|
||||||
// case: SAML SSO is enabled for the organization
|
// case: SAML SSO is enabled for the organization
|
||||||
return res.status(400).send({
|
return res.status(400).send({
|
||||||
message:
|
message: "Failed to invite member due to SAML SSO configured for organization"
|
||||||
"Failed to invite member due to SAML SSO configured for organization"
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plan.memberLimit !== null) {
|
if (plan.memberLimit !== null) {
|
||||||
@@ -231,7 +224,10 @@ export const inviteUserToOrganization = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
export const verifyUserToOrganization = async (req: Request, res: Response) => {
|
||||||
let user;
|
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");
|
user = await User.findOne({ email }).select("+publicKey");
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,38 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import {
|
import {
|
||||||
IncidentContactOrg,
|
IncidentContactOrg,
|
||||||
Membership,
|
Membership,
|
||||||
MembershipOrg,
|
MembershipOrg,
|
||||||
Organization,
|
Organization,
|
||||||
Workspace,
|
Workspace
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import { createOrganization as create } from "../../helpers/organization";
|
import { createOrganization as create } from "../../helpers/organization";
|
||||||
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
import { addMembershipsOrg } from "../../helpers/membershipOrg";
|
||||||
import { ACCEPTED, OWNER } from "../../variables";
|
import { ACCEPTED, OWNER } from "../../variables";
|
||||||
import { getLicenseServerUrl, getSiteURL } from "../../config";
|
import { getLicenseServerUrl, getSiteURL } from "../../config";
|
||||||
import { licenseServerKeyRequest } from "../../config/request";
|
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) => {
|
export const getOrganizations = async (req: Request, res: Response) => {
|
||||||
const organizations = (
|
const organizations = (
|
||||||
await MembershipOrg.find({
|
await MembershipOrg.find({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
status: ACCEPTED,
|
status: ACCEPTED
|
||||||
}).populate("organization")
|
}).populate("organization")
|
||||||
).map((m) => m.organization);
|
).map((m) => m.organization);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organizations,
|
organizations
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,28 +43,26 @@ export const getOrganizations = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createOrganization = async (req: Request, res: Response) => {
|
export const createOrganization = async (req: Request, res: Response) => {
|
||||||
const { organizationName } = req.body;
|
const {
|
||||||
|
body: { organizationName }
|
||||||
if (organizationName.length < 1) {
|
} = await validateRequest(reqValidator.CreateOrgv1, req);
|
||||||
throw new Error("Organization names must be at least 1-character long");
|
|
||||||
}
|
|
||||||
|
|
||||||
// create organization and add user as member
|
// create organization and add user as member
|
||||||
const organization = await create({
|
const organization = await create({
|
||||||
email: req.user.email,
|
email: req.user.email,
|
||||||
name: organizationName,
|
name: organizationName
|
||||||
});
|
});
|
||||||
|
|
||||||
await addMembershipsOrg({
|
await addMembershipsOrg({
|
||||||
userIds: [req.user._id.toString()],
|
userIds: [req.user._id.toString()],
|
||||||
organizationId: organization._id.toString(),
|
organizationId: organization._id.toString(),
|
||||||
roles: [OWNER],
|
roles: [OWNER],
|
||||||
statuses: [ACCEPTED],
|
statuses: [ACCEPTED]
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
organization,
|
organization
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,10 +72,23 @@ export const createOrganization = async (req: Request, res: Response) => {
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganization = async (req: Request, res: Response) => {
|
export const getOrganization = async (req: Request, res: Response) => {
|
||||||
const organization = req.organization
|
const {
|
||||||
return res.status(200).send({
|
params: { organizationId }
|
||||||
organization,
|
} = 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
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationMembers = async (req: Request, res: Response) => {
|
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({
|
const users = await MembershipOrg.find({
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
}).populate("user", "+publicKey");
|
}).populate("user", "+publicKey");
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
users,
|
users
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -94,17 +123,22 @@ export const getOrganizationMembers = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationWorkspaces = async (
|
export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
const {
|
||||||
res: Response
|
params: { organizationId }
|
||||||
) => {
|
} = await validateRequest(reqValidator.GetOrgWorkspacesv1, req);
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
WorkspacePermissionActions.Read,
|
||||||
|
OrgPermissionSubjects.Workspace
|
||||||
|
);
|
||||||
|
|
||||||
const workspacesSet = new Set(
|
const workspacesSet = new Set(
|
||||||
(
|
(
|
||||||
await Workspace.find(
|
await Workspace.find(
|
||||||
{
|
{
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
},
|
},
|
||||||
"_id"
|
"_id"
|
||||||
)
|
)
|
||||||
@@ -113,15 +147,15 @@ export const getOrganizationWorkspaces = async (
|
|||||||
|
|
||||||
const workspaces = (
|
const workspaces = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
user: req.user._id,
|
user: req.user._id
|
||||||
}).populate("workspace")
|
}).populate("workspace")
|
||||||
)
|
)
|
||||||
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
.filter((m) => workspacesSet.has(m.workspace._id.toString()))
|
||||||
.map((m) => m.workspace);
|
.map((m) => m.workspace);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspaces,
|
workspaces
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,25 +165,33 @@ export const getOrganizationWorkspaces = async (
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const changeOrganizationName = async (req: Request, res: Response) => {
|
export const changeOrganizationName = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params;
|
const {
|
||||||
const { name } = req.body;
|
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(
|
const organization = await Organization.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: organizationId,
|
_id: organizationId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name,
|
name
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully changed organization name",
|
message: "Successfully changed organization name",
|
||||||
organization,
|
organization
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -158,19 +200,24 @@ export const changeOrganizationName = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationIncidentContacts = async (
|
export const getOrganizationIncidentContacts = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
const {
|
||||||
res: Response
|
params: { organizationId }
|
||||||
) => {
|
} = await validateRequest(reqValidator.GetOrgIncidentContactv1, req);
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
GeneralPermissionActions.Read,
|
||||||
|
OrgPermissionSubjects.IncidentAccount
|
||||||
|
);
|
||||||
|
|
||||||
const incidentContactsOrg = await IncidentContactOrg.find({
|
const incidentContactsOrg = await IncidentContactOrg.find({
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
incidentContactsOrg,
|
incidentContactsOrg
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -179,12 +226,17 @@ export const getOrganizationIncidentContacts = async (
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const addOrganizationIncidentContact = async (
|
export const addOrganizationIncidentContact = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
const {
|
||||||
res: Response
|
params: { organizationId },
|
||||||
) => {
|
body: { email }
|
||||||
const { organizationId } = req.params;
|
} = await validateRequest(reqValidator.CreateOrgIncideContact, req);
|
||||||
const { email } = req.body;
|
|
||||||
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
GeneralPermissionActions.Create,
|
||||||
|
OrgPermissionSubjects.IncidentAccount
|
||||||
|
);
|
||||||
|
|
||||||
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
|
const incidentContactOrg = await IncidentContactOrg.findOneAndUpdate(
|
||||||
{ email, organization: organizationId },
|
{ email, organization: organizationId },
|
||||||
@@ -192,9 +244,9 @@ export const addOrganizationIncidentContact = async (
|
|||||||
{ upsert: true, new: true }
|
{ upsert: true, new: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
incidentContactOrg,
|
incidentContactOrg
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -203,22 +255,27 @@ export const addOrganizationIncidentContact = async (
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteOrganizationIncidentContact = async (
|
export const deleteOrganizationIncidentContact = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
const {
|
||||||
res: Response
|
params: { organizationId },
|
||||||
) => {
|
body: { email }
|
||||||
const { organizationId } = req.params;
|
} = await validateRequest(reqValidator.DelOrgIncideContact, req);
|
||||||
const { email } = req.body;
|
|
||||||
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
GeneralPermissionActions.Delete,
|
||||||
|
OrgPermissionSubjects.IncidentAccount
|
||||||
|
);
|
||||||
|
|
||||||
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
|
const incidentContactOrg = await IncidentContactOrg.findOneAndDelete({
|
||||||
email,
|
email,
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully deleted organization incident contact",
|
message: "Successfully deleted organization incident contact",
|
||||||
incidentContactOrg,
|
incidentContactOrg
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -228,19 +285,41 @@ export const deleteOrganizationIncidentContact = async (
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createOrganizationPortalSession = async (
|
export const createOrganizationPortalSession = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
const {
|
||||||
res: Response
|
params: { organizationId }
|
||||||
) => {
|
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, req);
|
||||||
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
|
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
|
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) {
|
if (pmtMethods.length < 1) {
|
||||||
// case: organization has no payment method on file
|
// case: organization has no payment method on file
|
||||||
// -> redirect to add payment method portal
|
// -> redirect to add payment method portal
|
||||||
const { data: { url } } = await licenseServerKeyRequest.post(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
|
data: { url }
|
||||||
|
} = await licenseServerKeyRequest.post(
|
||||||
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
|
||||||
|
organization.customerId
|
||||||
|
}/billing-details/payment-methods`,
|
||||||
{
|
{
|
||||||
success_url: (await getSiteURL()) + "/dashboard",
|
success_url: (await getSiteURL()) + "/dashboard",
|
||||||
cancel_url: (await getSiteURL()) + "/dashboard"
|
cancel_url: (await getSiteURL()) + "/dashboard"
|
||||||
@@ -250,8 +329,12 @@ export const createOrganizationPortalSession = async (
|
|||||||
} else {
|
} else {
|
||||||
// case: organization has payment method on file
|
// case: organization has payment method on file
|
||||||
// -> redirect to billing portal
|
// -> redirect to billing portal
|
||||||
const { data: { url } } = await licenseServerKeyRequest.post(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/billing-portal`,
|
data: { url }
|
||||||
|
} = await licenseServerKeyRequest.post(
|
||||||
|
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${
|
||||||
|
organization.customerId
|
||||||
|
}/billing-details/billing-portal`,
|
||||||
{
|
{
|
||||||
return_url: (await getSiteURL()) + "/dashboard"
|
return_url: (await getSiteURL()) + "/dashboard"
|
||||||
}
|
}
|
||||||
@@ -266,36 +349,43 @@ export const createOrganizationPortalSession = async (
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationMembersAndTheirWorkspaces = async (
|
export const getOrganizationMembersAndTheirWorkspaces = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
const {
|
||||||
res: Response
|
params: { organizationId }
|
||||||
) => {
|
} = await validateRequest(reqValidator.GetOrgMembersv1, req);
|
||||||
const { organizationId } = req.params;
|
|
||||||
|
|
||||||
const workspacesSet = (
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
await Workspace.find(
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
{
|
GeneralPermissionActions.Read,
|
||||||
organization: organizationId,
|
OrgPermissionSubjects.Member
|
||||||
},
|
);
|
||||||
"_id"
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
)
|
WorkspacePermissionActions.Read,
|
||||||
).map((w) => w._id.toString());
|
OrgPermissionSubjects.Workspace
|
||||||
|
);
|
||||||
|
|
||||||
const memberships = (
|
const workspacesSet = (
|
||||||
await Membership.find({
|
await Workspace.find(
|
||||||
workspace: { $in: workspacesSet },
|
{
|
||||||
}).populate("workspace")
|
organization: organizationId
|
||||||
);
|
},
|
||||||
const userToWorkspaceIds: any = {};
|
"_id"
|
||||||
|
)
|
||||||
|
).map((w) => w._id.toString());
|
||||||
|
|
||||||
memberships.forEach(membership => {
|
const memberships = await Membership.find({
|
||||||
const user = membership.user.toString();
|
workspace: { $in: workspacesSet }
|
||||||
if (userToWorkspaceIds[user]) {
|
}).populate("workspace");
|
||||||
userToWorkspaceIds[user].push(membership.workspace);
|
const userToWorkspaceIds: any = {};
|
||||||
} else {
|
|
||||||
userToWorkspaceIds[user] = [membership.workspace];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
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 }
|
body: { workspaceId, name, description, slug, permissions, orgId }
|
||||||
} = await validateRequest(CreateRoleSchema, req);
|
} = await validateRequest(CreateRoleSchema, req);
|
||||||
|
|
||||||
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
|
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
|
||||||
if (orgPermission.cannot(GeneralPermissionActions.Create, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(GeneralPermissionActions.Create, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the permission." });
|
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);
|
} = await validateRequest(UpdateRoleSchema, req);
|
||||||
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
||||||
|
|
||||||
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
|
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
|
||||||
if (orgPermission.cannot(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the permission." });
|
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" });
|
throw BadRequestError({ message: "Role not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const orgPermission = await getUserOrgPermissions(req.user.id, role.organization.toString());
|
const { permission } = await getUserOrgPermissions(req.user.id, role.organization.toString());
|
||||||
if (orgPermission.cannot(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the permission." });
|
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||||
}
|
}
|
||||||
await Role.findByIdAndDelete(role.id);
|
await Role.findByIdAndDelete(role.id);
|
||||||
@@ -123,8 +123,8 @@ export const getRoles = async (req: Request, res: Response) => {
|
|||||||
} = await validateRequest(GetRoleSchema, req);
|
} = await validateRequest(GetRoleSchema, req);
|
||||||
const isOrgRole = !workspaceId;
|
const isOrgRole = !workspaceId;
|
||||||
|
|
||||||
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
|
const { permission } = await getUserOrgPermissions(req.user.id, orgId);
|
||||||
if (orgPermission.cannot(GeneralPermissionActions.Read, OrgPermissionSubjects.Role)) {
|
if (permission.cannot(GeneralPermissionActions.Read, OrgPermissionSubjects.Role)) {
|
||||||
throw BadRequestError({ message: "User doesn't have the permission." });
|
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 GitAppInstallationSession from "../../ee/models/gitAppInstallationSession";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import { UnauthorizedRequestError } from "../../utils/errors";
|
import { OrganizationNotFoundError, UnauthorizedRequestError } from "../../utils/errors";
|
||||||
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
|
import GitAppOrganizationInstallation from "../../ee/models/gitAppOrganizationInstallation";
|
||||||
import { MembershipOrg } from "../../models";
|
|
||||||
import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository"
|
import { scanGithubFullRepoForSecretLeaks } from "../../queues/secret-scanning/githubScanFullRepository"
|
||||||
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
|
import { getSecretScanningGitAppId, getSecretScanningPrivateKey } from "../../config";
|
||||||
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks";
|
import GitRisks, { STATUS_RESOLVED_FALSE_POSITIVE, STATUS_RESOLVED_NOT_REVOKED, STATUS_RESOLVED_REVOKED } from "../../ee/models/gitRisks";
|
||||||
import { ProbotOctokit } from "probot";
|
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) => {
|
export const createInstallationSession = async (req: Request, res: Response) => {
|
||||||
const sessionId = crypto.randomBytes(16).toString("hex");
|
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(
|
await GitAppInstallationSession.findByIdAndUpdate(
|
||||||
req.organization,
|
organization,
|
||||||
{
|
{
|
||||||
organization: new Types.ObjectId(req.organization),
|
organization: organization.id,
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
user: new Types.ObjectId(req.user._id)
|
user: new Types.ObjectId(req.user._id)
|
||||||
},
|
},
|
||||||
@@ -24,31 +49,43 @@ export const createInstallationSession = async (req: Request, res: Response) =>
|
|||||||
|
|
||||||
res.send({
|
res.send({
|
||||||
sessionId: sessionId
|
sessionId: sessionId
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const linkInstallationToOrganization = async (req: Request, res: Response) => {
|
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) {
|
if (!installationSession) {
|
||||||
throw UnauthorizedRequestError()
|
throw UnauthorizedRequestError();
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMembership = await MembershipOrg.find({ user: req.user._id, organization: installationSession.organization })
|
const { permission } = await getUserOrgPermissions(
|
||||||
if (!userMembership) {
|
req.user._id,
|
||||||
throw UnauthorizedRequestError()
|
installationSession.organization.toString()
|
||||||
}
|
);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
GeneralPermissionActions.Edit,
|
||||||
|
OrgPermissionSubjects.SecretScanning
|
||||||
|
);
|
||||||
|
|
||||||
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate({
|
const installationLink = await GitAppOrganizationInstallation.findOneAndUpdate(
|
||||||
organizationId: installationSession.organization,
|
{
|
||||||
}, {
|
organizationId: installationSession.organization
|
||||||
installationId: installationId,
|
},
|
||||||
organizationId: installationSession.organization,
|
{
|
||||||
user: installationSession.user
|
installationId: installationId,
|
||||||
}, {
|
organizationId: installationSession.organization,
|
||||||
upsert: true
|
user: installationSession.user
|
||||||
}).lean()
|
},
|
||||||
|
{
|
||||||
|
upsert: true
|
||||||
|
}
|
||||||
|
).lean();
|
||||||
|
|
||||||
const octokit = new ProbotOctokit({
|
const octokit = new ProbotOctokit({
|
||||||
auth: {
|
auth: {
|
||||||
@@ -66,41 +103,68 @@ export const linkInstallationToOrganization = async (req: Request, res: Response
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
|
export const getCurrentOrganizationInstallationStatus = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params
|
const { organizationId } = req.params;
|
||||||
try {
|
try {
|
||||||
const appInstallation = await GitAppOrganizationInstallation.findOne({ organizationId: organizationId }).lean()
|
const appInstallation = await GitAppOrganizationInstallation.findOne({
|
||||||
|
organizationId: organizationId
|
||||||
|
}).lean();
|
||||||
if (!appInstallation) {
|
if (!appInstallation) {
|
||||||
res.json({
|
res.json({
|
||||||
appInstallationComplete: false
|
appInstallationComplete: false
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
appInstallationComplete: true
|
appInstallationComplete: true
|
||||||
})
|
});
|
||||||
} catch {
|
} catch {
|
||||||
res.json({
|
res.json({
|
||||||
appInstallationComplete: false
|
appInstallationComplete: false
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
export const getRisksForOrganization = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params
|
const {
|
||||||
const risks = await GitRisks.find({ organization: organizationId }).sort({ createdAt: -1 }).lean()
|
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({
|
res.json({
|
||||||
risks: risks
|
risks: risks
|
||||||
})
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
export const updateRisksStatus = async (req: Request, res: Response) => {
|
export const updateRisksStatus = async (req: Request, res: Response) => {
|
||||||
const { riskId } = req.params
|
const {
|
||||||
const { status } = req.body
|
params: { organizationId, riskId },
|
||||||
const isRiskResolved = status == STATUS_RESOLVED_FALSE_POSITIVE || status == STATUS_RESOLVED_REVOKED || status == STATUS_RESOLVED_NOT_REVOKED ? true : false
|
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, {
|
const risk = await GitRisks.findByIdAndUpdate(riskId, {
|
||||||
status: status,
|
status: status,
|
||||||
isResolved: isRiskResolved
|
isResolved: isRiskResolved
|
||||||
}).lean()
|
}).lean();
|
||||||
|
|
||||||
res.json(risk)
|
res.json(risk);
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -5,17 +5,21 @@ import {
|
|||||||
Integration,
|
Integration,
|
||||||
IntegrationAuth,
|
IntegrationAuth,
|
||||||
Membership,
|
Membership,
|
||||||
MembershipOrg,
|
Organization,
|
||||||
ServiceToken,
|
ServiceToken,
|
||||||
Workspace,
|
Workspace
|
||||||
} from "../../models";
|
} from "../../models";
|
||||||
import {
|
import { createWorkspace as create, deleteWorkspace as deleteWork } from "../../helpers/workspace";
|
||||||
createWorkspace as create,
|
|
||||||
deleteWorkspace as deleteWork,
|
|
||||||
} from "../../helpers/workspace";
|
|
||||||
import { EELicenseService } from "../../ee/services";
|
import { EELicenseService } from "../../ee/services";
|
||||||
import { addMemberships } from "../../helpers/membership";
|
import { addMemberships } from "../../helpers/membership";
|
||||||
import { ADMIN } from "../../variables";
|
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]
|
* Return public keys of members of workspace with id [workspaceId]
|
||||||
@@ -28,17 +32,17 @@ export const getWorkspacePublicKeys = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
const publicKeys = (
|
const publicKeys = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).populate<{ user: IUser }>("user", "publicKey")
|
}).populate<{ user: IUser }>("user", "publicKey")
|
||||||
).map((member) => {
|
).map((member) => {
|
||||||
return {
|
return {
|
||||||
publicKey: member.user.publicKey,
|
publicKey: member.user.publicKey,
|
||||||
userId: member.user._id,
|
userId: member.user._id
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
publicKeys,
|
publicKeys
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -52,11 +56,11 @@ export const getWorkspaceMemberships = async (req: Request, res: Response) => {
|
|||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const users = await Membership.find({
|
const users = await Membership.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).populate("user", "+publicKey");
|
}).populate("user", "+publicKey");
|
||||||
|
|
||||||
return res.status(200).send({
|
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) => {
|
export const getWorkspaces = async (req: Request, res: Response) => {
|
||||||
const workspaces = (
|
const workspaces = (
|
||||||
await Membership.find({
|
await Membership.find({
|
||||||
user: req.user._id,
|
user: req.user._id
|
||||||
}).populate("workspace")
|
}).populate("workspace")
|
||||||
).map((m) => m.workspace);
|
).map((m) => m.workspace);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspaces,
|
workspaces
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -88,11 +92,11 @@ export const getWorkspace = async (req: Request, res: Response) => {
|
|||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const workspace = await Workspace.findOne({
|
const workspace = await Workspace.findOne({
|
||||||
_id: workspaceId,
|
_id: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
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) => {
|
export const createWorkspace = async (req: Request, res: Response) => {
|
||||||
const { workspaceName, organizationId } = req.body;
|
const { workspaceName, organizationId } = req.body;
|
||||||
|
|
||||||
// validate organization membership
|
const organization = await Organization.findById(organizationId);
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
if (!organization) {
|
||||||
user: req.user._id,
|
throw OrganizationNotFoundError({
|
||||||
organization: new Types.ObjectId(organizationId),
|
message: "Failed to find organization"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg) {
|
|
||||||
throw new Error("Failed to validate organization membership");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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));
|
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
||||||
|
|
||||||
if (plan.workspaceLimit !== null) {
|
if (plan.workspaceLimit !== null) {
|
||||||
// case: limit imposed on number of workspaces allowed
|
// case: limit imposed on number of workspaces allowed
|
||||||
if (plan.workspacesUsed >= plan.workspaceLimit) {
|
if (plan.workspacesUsed >= plan.workspaceLimit) {
|
||||||
// case: number of workspaces used exceeds the number of workspaces allowed
|
// case: number of workspaces used exceeds the number of workspaces allowed
|
||||||
return res.status(400).send({
|
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
|
// create workspace and add user as member
|
||||||
const workspace = await create({
|
const workspace = await create({
|
||||||
name: workspaceName,
|
name: workspaceName,
|
||||||
organizationId: new Types.ObjectId(organizationId),
|
organizationId: new Types.ObjectId(organizationId)
|
||||||
});
|
});
|
||||||
|
|
||||||
await addMemberships({
|
await addMemberships({
|
||||||
userIds: [req.user._id],
|
userIds: [req.user._id],
|
||||||
workspaceId: workspace._id.toString(),
|
workspaceId: workspace._id.toString(),
|
||||||
roles: [ADMIN],
|
roles: [ADMIN]
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
workspace,
|
workspace
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -160,11 +168,11 @@ export const deleteWorkspace = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// delete workspace
|
// delete workspace
|
||||||
await deleteWork({
|
await deleteWork({
|
||||||
id: workspaceId,
|
id: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
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(
|
const workspace = await Workspace.findOneAndUpdate(
|
||||||
{
|
{
|
||||||
_id: workspaceId,
|
_id: workspaceId
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name,
|
name
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
new: true,
|
new: true
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
message: "Successfully changed workspace name",
|
message: "Successfully changed workspace name",
|
||||||
workspace,
|
workspace
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -206,11 +214,11 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
|||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const integrations = await Integration.find({
|
const integrations = await Integration.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
integrations,
|
integrations
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -220,18 +228,15 @@ export const getWorkspaceIntegrations = async (req: Request, res: Response) => {
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceIntegrationAuthorizations = async (
|
export const getWorkspaceIntegrationAuthorizations = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => {
|
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
|
|
||||||
const authorizations = await IntegrationAuth.find({
|
const authorizations = await IntegrationAuth.find({
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
authorizations,
|
authorizations
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,18 +246,15 @@ export const getWorkspaceIntegrationAuthorizations = async (
|
|||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getWorkspaceServiceTokens = async (
|
export const getWorkspaceServiceTokens = async (req: Request, res: Response) => {
|
||||||
req: Request,
|
|
||||||
res: Response
|
|
||||||
) => {
|
|
||||||
const { workspaceId } = req.params;
|
const { workspaceId } = req.params;
|
||||||
// ?? FIX.
|
// ?? FIX.
|
||||||
const serviceTokens = await ServiceToken.find({
|
const serviceTokens = await ServiceToken.find({
|
||||||
user: req.user._id,
|
user: req.user._id,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceTokens,
|
serviceTokens
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,6 +6,15 @@ import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
|||||||
import Role from "../../models/role";
|
import Role from "../../models/role";
|
||||||
import { BadRequestError } from "../../utils/errors";
|
import { BadRequestError } from "../../utils/errors";
|
||||||
import { CUSTOM } from "../../variables";
|
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]
|
* 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({
|
const memberships = await MembershipOrg.find({
|
||||||
organization: organizationId
|
organization: organizationId
|
||||||
@@ -116,8 +133,15 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const { membershipId } = req.params;
|
const {
|
||||||
const { role } = req.body;
|
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);
|
const isCustomRole = !["admin", "member", "owner"].includes(role);
|
||||||
if (isCustomRole) {
|
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
|
// delete organization membership
|
||||||
const membership = await deleteMembershipOrg({
|
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(
|
const workspacesSet = new Set(
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -3,228 +3,503 @@ import { Request, Response } from "express";
|
|||||||
import { getLicenseServerUrl } from "../../../config";
|
import { getLicenseServerUrl } from "../../../config";
|
||||||
import { licenseServerKeyRequest } from "../../../config/request";
|
import { licenseServerKeyRequest } from "../../../config/request";
|
||||||
import { EELicenseService } from "../../services";
|
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) => {
|
export const getOrganizationPlansTable = async (req: Request, res: Response) => {
|
||||||
const billingCycle = req.query.billingCycle as string;
|
const {
|
||||||
|
query: { billingCycle },
|
||||||
const { data } = await licenseServerKeyRequest.get(
|
params: { organizationId }
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/cloud-products?billing-cycle=${billingCycle}`
|
} = 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
|
* Return the organization current plan's feature set
|
||||||
*/
|
*/
|
||||||
export const getOrganizationPlan = async (req: Request, res: Response) => {
|
export const getOrganizationPlan = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params;
|
const {
|
||||||
const workspaceId = req.query.workspaceId as string;
|
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({
|
const plan = await EELicenseService.getPlan(
|
||||||
plan,
|
new Types.ObjectId(organizationId),
|
||||||
});
|
new Types.ObjectId(workspaceId)
|
||||||
}
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
plan
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return checkout url for pro trial
|
* Return checkout url for pro trial
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const startOrganizationTrial = async (req: Request, res: Response) => {
|
export const startOrganizationTrial = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params;
|
const {
|
||||||
const { success_url } = req.body;
|
params: { organizationId },
|
||||||
|
body: { success_url }
|
||||||
|
} = await validateRequest(reqValidator.StartOrgTrailv1, req);
|
||||||
|
|
||||||
const { data: { url } } = await licenseServerKeyRequest.post(
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/session/trial`,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
{
|
GeneralPermissionActions.Create,
|
||||||
success_url
|
OrgPermissionSubjects.Billing
|
||||||
}
|
);
|
||||||
);
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
GeneralPermissionActions.Edit,
|
||||||
EELicenseService.delPlan(new Types.ObjectId(organizationId));
|
OrgPermissionSubjects.Billing
|
||||||
|
);
|
||||||
return res.status(200).send({
|
|
||||||
url
|
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
|
* Return the organization's current plan's billing info
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationPlanBillingInfo = async (req: Request, res: Response) => {
|
export const getOrganizationPlanBillingInfo = async (req: Request, res: Response) => {
|
||||||
const { data } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/billing`
|
params: { organizationId }
|
||||||
);
|
} = await validateRequest(reqValidator.GetOrgPlanBillingInfov1, 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/billing`
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send(data);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the organization's current plan's feature table
|
* Return the organization's current plan's feature table
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationPlanTable = async (req: Request, res: Response) => {
|
export const getOrganizationPlanTable = async (req: Request, res: Response) => {
|
||||||
const { data } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/cloud-plan/table`
|
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) => {
|
export const getOrganizationBillingDetails = async (req: Request, res: Response) => {
|
||||||
const { data } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`
|
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) => {
|
export const updateOrganizationBillingDetails = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
name,
|
params: { organizationId },
|
||||||
email
|
body: { name, email }
|
||||||
} = req.body;
|
} = await validateRequest(reqValidator.UpdateOrgBillingDetailsv1, req);
|
||||||
|
|
||||||
const { data } = await licenseServerKeyRequest.patch(
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details`,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
{
|
GeneralPermissionActions.Edit,
|
||||||
...(name ? { name } : {}),
|
OrgPermissionSubjects.Billing
|
||||||
...(email ? { email } : {})
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
* Return the organization's payment methods on file
|
||||||
*/
|
*/
|
||||||
export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
|
export const getOrganizationPmtMethods = async (req: Request, res: Response) => {
|
||||||
const { data: { pmtMethods } } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`
|
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
|
* Return URL to add payment method for organization
|
||||||
*/
|
*/
|
||||||
export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
|
export const addOrganizationPmtMethod = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
success_url,
|
params: { organizationId },
|
||||||
cancel_url,
|
body: { success_url, cancel_url }
|
||||||
} = req.body;
|
} = await validateRequest(reqValidator.CreateOrgPmtMethodv1, req);
|
||||||
|
|
||||||
const { data: { url } } = await licenseServerKeyRequest.post(
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods`,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
{
|
GeneralPermissionActions.Create,
|
||||||
success_url,
|
OrgPermissionSubjects.Billing
|
||||||
cancel_url,
|
);
|
||||||
}
|
|
||||||
);
|
const organization = await Organization.findById(organizationId);
|
||||||
|
if (!organization) {
|
||||||
return res.status(200).send({
|
throw OrganizationNotFoundError({
|
||||||
url,
|
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
|
* Delete payment method with id [pmtMethodId] for organization
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteOrganizationPmtMethod = async (req: Request, res: Response) => {
|
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(
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/payment-methods/${pmtMethodId}`,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
);
|
GeneralPermissionActions.Delete,
|
||||||
|
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.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
|
* Return the organization's tax ids on file
|
||||||
*/
|
*/
|
||||||
export const getOrganizationTaxIds = async (req: Request, res: Response) => {
|
export const getOrganizationTaxIds = async (req: Request, res: Response) => {
|
||||||
const { data: { tax_ids } } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`
|
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
|
* Add tax id to organization
|
||||||
*/
|
*/
|
||||||
export const addOrganizationTaxId = async (req: Request, res: Response) => {
|
export const addOrganizationTaxId = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
type,
|
params: { organizationId },
|
||||||
value
|
body: { type, value }
|
||||||
} = req.body;
|
} = await validateRequest(reqValidator.CreateOrgTaxId, req);
|
||||||
|
|
||||||
const { data } = await licenseServerKeyRequest.post(
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids`,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
{
|
GeneralPermissionActions.Create,
|
||||||
type,
|
OrgPermissionSubjects.Billing
|
||||||
value
|
);
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
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
|
* Delete tax id with id [taxId] from organization tax ids on file
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteOrganizationTaxId = async (req: Request, res: Response) => {
|
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(
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/billing-details/tax-ids/${taxId}`,
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
);
|
GeneralPermissionActions.Delete,
|
||||||
|
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.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
|
* Return organization's invoices on file
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
export const getOrganizationInvoices = async (req: Request, res: Response) => {
|
||||||
const { data: { invoices } } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/invoices`
|
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
|
* Return organization's licenses on file
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getOrganizationLicenses = async (req: Request, res: Response) => {
|
export const getOrganizationLicenses = async (req: Request, res: Response) => {
|
||||||
const { data: { licenses } } = await licenseServerKeyRequest.get(
|
const {
|
||||||
`${await getLicenseServerUrl()}/api/license-server/v1/customers/${req.organization.customerId}/licenses`
|
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 { Types } from "mongoose";
|
||||||
import { BotOrgService } from "../../../services";
|
import { BotOrgService } from "../../../services";
|
||||||
import { SSOConfig } from "../../models";
|
import { SSOConfig } from "../../models";
|
||||||
import {
|
import { AuthMethod, MembershipOrg, User } from "../../../models";
|
||||||
AuthMethod,
|
|
||||||
MembershipOrg,
|
|
||||||
User
|
|
||||||
} from "../../../models";
|
|
||||||
import { getSSOConfigHelper } from "../../helpers/organizations";
|
import { getSSOConfigHelper } from "../../helpers/organizations";
|
||||||
import { client } from "../../../config";
|
import { client } from "../../../config";
|
||||||
import { ResourceNotFoundError } from "../../../utils/errors";
|
import { ResourceNotFoundError } from "../../../utils/errors";
|
||||||
import { getSiteURL } from "../../../config";
|
import { getSiteURL } from "../../../config";
|
||||||
import { EELicenseService } from "../../services";
|
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
|
* Redirect user to appropriate SSO endpoint after successful authentication
|
||||||
* to finish inputting their master key for logging in or signing up
|
* to finish inputting their master key for logging in or signing up
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const redirectSSO = async (req: Request, res: Response) => {
|
export const redirectSSO = async (req: Request, res: Response) => {
|
||||||
if (req.isUserCompleted) {
|
if (req.isUserCompleted) {
|
||||||
return res.redirect(`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
return res.redirect(
|
||||||
}
|
`${await getSiteURL()}/login/sso?token=${encodeURIComponent(req.providerAuthToken)}`
|
||||||
|
);
|
||||||
return res.redirect(`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`);
|
}
|
||||||
}
|
|
||||||
|
return res.redirect(
|
||||||
|
`${await getSiteURL()}/signup/sso?token=${encodeURIComponent(req.providerAuthToken)}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return organization SAML SSO configuration
|
* Return organization SAML SSO configuration
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const getSSOConfig = async (req: Request, res: Response) => {
|
export const getSSOConfig = async (req: Request, res: Response) => {
|
||||||
const organizationId = req.query.organizationId as string;
|
const {
|
||||||
|
query: { organizationId }
|
||||||
const data = await getSSOConfigHelper({
|
} = await validateRequest(reqValidator.GetSsoConfigv1, req);
|
||||||
organizationId: new Types.ObjectId(organizationId)
|
|
||||||
});
|
|
||||||
|
|
||||||
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
|
* Update organization SAML SSO configuration
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const updateSSOConfig = async (req: Request, res: Response) => {
|
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 {
|
const {
|
||||||
organizationId,
|
ciphertext: encryptedEntryPoint,
|
||||||
authProvider,
|
iv: entryPointIV,
|
||||||
isActive,
|
tag: entryPointTag
|
||||||
entryPoint,
|
} = client.encryptSymmetric(entryPoint, key);
|
||||||
issuer,
|
|
||||||
cert,
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
update.encryptedEntryPoint = encryptedEntryPoint;
|
||||||
|
update.entryPointIV = entryPointIV;
|
||||||
if (!plan.samlSSO) return res.status(400).send({
|
update.entryPointTag = entryPointTag;
|
||||||
message: "Failed to update SAML SSO configuration due to plan restriction. Upgrade plan to update SSO configuration."
|
}
|
||||||
|
|
||||||
|
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) {
|
if (update.isActive !== undefined) {
|
||||||
const {
|
const membershipOrgs = await MembershipOrg.find({
|
||||||
ciphertext: encryptedIssuer,
|
organization: new Types.ObjectId(organizationId)
|
||||||
iv: issuerIV,
|
}).select("user");
|
||||||
tag: issuerTag
|
|
||||||
} = client.encryptSymmetric(issuer, key);
|
|
||||||
|
|
||||||
update.encryptedIssuer = encryptedIssuer;
|
|
||||||
update.issuerIV = issuerIV;
|
|
||||||
update.issuerTag = issuerTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cert) {
|
if (update.isActive) {
|
||||||
const {
|
await User.updateMany(
|
||||||
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)
|
_id: {
|
||||||
|
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
update,
|
|
||||||
{
|
{
|
||||||
new: true
|
authMethods: [ssoConfig.authProvider]
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
if (!ssoConfig) throw ResourceNotFoundError({
|
await User.updateMany(
|
||||||
message: "Failed to find SSO config to update"
|
{
|
||||||
});
|
_id: {
|
||||||
|
$in: membershipOrgs.map((membershipOrg) => membershipOrg.user)
|
||||||
if (update.isActive !== undefined) {
|
}
|
||||||
const membershipOrgs = await MembershipOrg.find({
|
},
|
||||||
organization: new Types.ObjectId(organizationId)
|
{
|
||||||
}).select("user");
|
authMethods: [AuthMethod.EMAIL]
|
||||||
|
|
||||||
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],
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return res.status(200).send(ssoConfig);
|
|
||||||
}
|
return res.status(200).send(ssoConfig);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create organization SAML SSO configuration
|
* Create organization SAML SSO configuration
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const createSSOConfig = async (req: Request, res: Response) => {
|
export const createSSOConfig = async (req: Request, res: Response) => {
|
||||||
const {
|
const {
|
||||||
organizationId,
|
body: { organizationId, authProvider, isActive, entryPoint, issuer, cert }
|
||||||
authProvider,
|
} = await validateRequest(reqValidator.CreateSsoConfigv1, req);
|
||||||
isActive,
|
|
||||||
entryPoint,
|
|
||||||
issuer,
|
|
||||||
cert
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const plan = await EELicenseService.getPlan(new Types.ObjectId(organizationId));
|
const { permission } = await getUserOrgPermissions(req.user._id, organizationId);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
if (!plan.samlSSO) return res.status(400).send({
|
GeneralPermissionActions.Create,
|
||||||
message: "Failed to create SAML SSO configuration due to plan restriction. Upgrade plan to add SSO configuration."
|
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 {
|
const key = await BotOrgService.getSymmetricKey(new Types.ObjectId(organizationId));
|
||||||
ciphertext: encryptedEntryPoint,
|
|
||||||
iv: entryPointIV,
|
|
||||||
tag: entryPointTag
|
|
||||||
} = client.encryptSymmetric(entryPoint, key);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ciphertext: encryptedIssuer,
|
ciphertext: encryptedEntryPoint,
|
||||||
iv: issuerIV,
|
iv: entryPointIV,
|
||||||
tag: issuerTag
|
tag: entryPointTag
|
||||||
} = client.encryptSymmetric(issuer, key);
|
} = client.encryptSymmetric(entryPoint, key);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
ciphertext: encryptedCert,
|
ciphertext: encryptedIssuer,
|
||||||
iv: certIV,
|
iv: issuerIV,
|
||||||
tag: certTag
|
tag: issuerTag
|
||||||
} = client.encryptSymmetric(cert, key);
|
} = client.encryptSymmetric(issuer, 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);
|
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";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import { requireAuth } from "../../../middleware";
|
||||||
requireAuth,
|
|
||||||
requireOrganizationAuth,
|
|
||||||
validateRequest,
|
|
||||||
} from "../../../middleware";
|
|
||||||
import { body, param, query } from "express-validator";
|
|
||||||
import { organizationsController } from "../../controllers/v1";
|
import { organizationsController } from "../../controllers/v1";
|
||||||
import {
|
import { AuthMode } from "../../../variables";
|
||||||
ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER
|
|
||||||
} from "../../../variables";
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/plans/table",
|
"/:organizationId/plans/table",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationPlansTable
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
query("billingCycle").exists().isString().isIn(["monthly", "yearly"]),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationPlansTable
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/plan",
|
"/:organizationId/plan",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationPlan
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
query("workspaceId").optional().isString(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationPlan
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:organizationId/session/trial",
|
"/:organizationId/session/trial",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.startOrganizationTrial
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("success_url").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.startOrganizationTrial
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/plan/billing",
|
"/:organizationId/plan/billing",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationPlanBillingInfo
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
query("workspaceId").optional().isString(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationPlanBillingInfo
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/plan/table",
|
"/:organizationId/plan/table",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationPlanTable
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
query("workspaceId").optional().isString(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationPlanTable
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/billing-details",
|
"/:organizationId/billing-details",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationBillingDetails
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationBillingDetails
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:organizationId/billing-details",
|
"/:organizationId/billing-details",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.updateOrganizationBillingDetails
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("email").optional().isString().trim(),
|
|
||||||
body("name").optional().isString().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.updateOrganizationBillingDetails
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/billing-details/payment-methods",
|
"/:organizationId/billing-details/payment-methods",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationPmtMethods
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationPmtMethods
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:organizationId/billing-details/payment-methods",
|
"/:organizationId/billing-details/payment-methods",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.addOrganizationPmtMethod
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("success_url").exists().isString(),
|
|
||||||
body("cancel_url").exists().isString(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.addOrganizationPmtMethod
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
"/:organizationId/billing-details/payment-methods/:pmtMethodId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.deleteOrganizationPmtMethod
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
param("pmtMethodId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.deleteOrganizationPmtMethod
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/billing-details/tax-ids",
|
"/:organizationId/billing-details/tax-ids",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationTaxIds
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationTaxIds
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:organizationId/billing-details/tax-ids",
|
"/:organizationId/billing-details/tax-ids",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.addOrganizationTaxId
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("type").exists().isString(),
|
|
||||||
body("value").exists().isString(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.addOrganizationTaxId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:organizationId/billing-details/tax-ids/:taxId",
|
"/:organizationId/billing-details/tax-ids/:taxId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.deleteOrganizationTaxId
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
param("taxId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.deleteOrganizationTaxId
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/invoices",
|
"/:organizationId/invoices",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationInvoices
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationInvoices
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/licenses",
|
"/:organizationId/licenses",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationsController.getOrganizationLicenses
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationsController.getOrganizationLicenses
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,81 +1,53 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
import { requireAuth } from "../../../middleware";
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
createInstallationSession,
|
||||||
requireOrganizationAuth,
|
getCurrentOrganizationInstallationStatus,
|
||||||
validateRequest,
|
getRisksForOrganization,
|
||||||
} from "../../../middleware";
|
linkInstallationToOrganization,
|
||||||
import { body, param } from "express-validator";
|
updateRisksStatus
|
||||||
import { createInstallationSession, getCurrentOrganizationInstallationStatus, getRisksForOrganization, linkInstallationToOrganization, updateRisksStatus } from "../../../controllers/v1/secretScanningController";
|
} from "../../../controllers/v1/secretScanningController";
|
||||||
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../../variables";
|
import { AuthMode } from "../../../variables";
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/create-installation-session/organization/:organizationId",
|
"/create-installation-session/organization/:organizationId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
validateRequest,
|
|
||||||
createInstallationSession
|
createInstallationSession
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/link-installation",
|
"/link-installation",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
body("installationId").exists().trim(),
|
|
||||||
body("sessionId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
linkInstallationToOrganization
|
linkInstallationToOrganization
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/installation-status/organization/:organizationId",
|
"/installation-status/organization/:organizationId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
validateRequest,
|
|
||||||
getCurrentOrganizationInstallationStatus
|
getCurrentOrganizationInstallationStatus
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/organization/:organizationId/risks",
|
"/organization/:organizationId/risks",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
validateRequest,
|
|
||||||
getRisksForOrganization
|
getRisksForOrganization
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/organization/:organizationId/risks/:riskId/status",
|
"/organization/:organizationId/risks/:riskId/status",
|
||||||
requireAuth({
|
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
|
updateRisksStatus
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
+59
-110
@@ -1,146 +1,95 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import passport from "passport";
|
import passport from "passport";
|
||||||
import {
|
import { requireAuth } from "../../../middleware";
|
||||||
AuthProvider
|
|
||||||
} from "../../models";
|
|
||||||
import {
|
|
||||||
requireAuth,
|
|
||||||
requireOrganizationAuth,
|
|
||||||
validateRequest,
|
|
||||||
} from "../../../middleware";
|
|
||||||
import { body, query } from "express-validator";
|
|
||||||
import { ssoController } from "../../controllers/v1";
|
import { ssoController } from "../../controllers/v1";
|
||||||
import { authLimiter } from "../../../helpers/rateLimiter";
|
import { authLimiter } from "../../../helpers/rateLimiter";
|
||||||
import {
|
import { AuthMode } from "../../../variables";
|
||||||
ACCEPTED,
|
|
||||||
ADMIN,
|
|
||||||
AuthMode,
|
|
||||||
OWNER
|
|
||||||
} from "../../../variables";
|
|
||||||
|
|
||||||
router.get(
|
router.get("/redirect/google", authLimiter, (req, res, next) => {
|
||||||
"/redirect/google",
|
passport.authenticate("google", {
|
||||||
authLimiter,
|
scope: ["profile", "email"],
|
||||||
(req, res, next) => {
|
session: false,
|
||||||
passport.authenticate("google", {
|
...(req.query.callback_port
|
||||||
scope: ["profile", "email"],
|
? {
|
||||||
session: false,
|
state: req.query.callback_port as string
|
||||||
...(req.query.callback_port ? {
|
}
|
||||||
state: req.query.callback_port as string
|
: {})
|
||||||
} : {})
|
})(req, res, next);
|
||||||
})(req, res, next);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/google",
|
"/google",
|
||||||
passport.authenticate("google", {
|
passport.authenticate("google", {
|
||||||
failureRedirect: "/login/provider/error",
|
failureRedirect: "/login/provider/error",
|
||||||
session: false
|
session: false
|
||||||
}),
|
}),
|
||||||
ssoController.redirectSSO
|
ssoController.redirectSSO
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get("/redirect/github", authLimiter, (req, res, next) => {
|
||||||
"/redirect/github",
|
passport.authenticate("github", {
|
||||||
authLimiter,
|
session: false,
|
||||||
(req, res, next) => {
|
...(req.query.callback_port
|
||||||
passport.authenticate("github", {
|
? {
|
||||||
session: false,
|
state: req.query.callback_port as string
|
||||||
...(req.query.callback_port ? {
|
}
|
||||||
state: req.query.callback_port as string
|
: {})
|
||||||
} : {})
|
})(req, res, next);
|
||||||
})(req, res, next);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/github",
|
"/github",
|
||||||
authLimiter,
|
authLimiter,
|
||||||
passport.authenticate("github", {
|
passport.authenticate("github", {
|
||||||
failureRedirect: "/login/provider/error",
|
failureRedirect: "/login/provider/error",
|
||||||
session: false
|
session: false
|
||||||
}),
|
}),
|
||||||
ssoController.redirectSSO
|
ssoController.redirectSSO
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get("/redirect/saml2/:ssoIdentifier", authLimiter, (req, res, next) => {
|
||||||
"/redirect/saml2/:ssoIdentifier",
|
const options = {
|
||||||
authLimiter,
|
failureRedirect: "/",
|
||||||
(req, res, next) => {
|
additionalParams: {
|
||||||
const options = {
|
RelayState: req.query.callback_port ?? ""
|
||||||
failureRedirect: "/",
|
}
|
||||||
additionalParams: {
|
};
|
||||||
RelayState: req.query.callback_port ?? ""
|
passport.authenticate("saml", options)(req, res, next);
|
||||||
},
|
});
|
||||||
};
|
|
||||||
passport.authenticate("saml", options)(req, res, next);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
router.post("/saml2/:ssoIdentifier",
|
router.post(
|
||||||
passport.authenticate("saml", {
|
"/saml2/:ssoIdentifier",
|
||||||
failureRedirect: "/login/provider/error",
|
passport.authenticate("saml", {
|
||||||
failureFlash: true,
|
failureRedirect: "/login/provider/error",
|
||||||
|
failureFlash: true,
|
||||||
session: false
|
session: false
|
||||||
}),
|
}),
|
||||||
ssoController.redirectSSO
|
ssoController.redirectSSO
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/config",
|
"/config",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
ssoController.getSSOConfig
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
locationOrganizationId: "query"
|
|
||||||
}),
|
|
||||||
query("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
ssoController.getSSOConfig
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/config",
|
"/config",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
ssoController.createSSOConfig
|
||||||
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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/config",
|
"/config",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
ssoController.updateSSOConfig
|
||||||
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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
// TODO endpoint: consider moving these endpoints to be under /organization to be more RESTful
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/signup",
|
"/signup",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
body("inviteeEmail").exists().trim().notEmpty().isEmail(),
|
membershipOrgController.inviteUserToOrganization
|
||||||
body("organizationId").exists().trim().notEmpty(),
|
|
||||||
validateRequest,
|
|
||||||
membershipOrgController.inviteUserToOrganization
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/verify",
|
"/verify",
|
||||||
body("email").exists().trim().notEmpty(),
|
body("email").exists().trim().notEmpty(),
|
||||||
body("organizationId").exists().trim().notEmpty(),
|
body("organizationId").exists().trim().notEmpty(),
|
||||||
body("code").exists().trim().notEmpty(),
|
body("code").exists().trim().notEmpty(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
membershipOrgController.verifyUserToOrganization
|
membershipOrgController.verifyUserToOrganization
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -9,45 +9,49 @@ import { AuthMode } from "../../variables";
|
|||||||
// note: ALL DEPRECIATED (moved to api/v2/workspace/:workspaceId/memberships/:membershipId)
|
// 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
|
// TODO endpoint: consider moving these endpoints to be under /workspace to be more RESTful
|
||||||
|
|
||||||
router.get( // TODO endpoint: deprecate - used for old CLI (deprecate)
|
router.get(
|
||||||
"/:workspaceId/connect",
|
// TODO endpoint: deprecate - used for old CLI (deprecate)
|
||||||
requireAuth({
|
"/:workspaceId/connect",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
param("workspaceId").exists().trim(),
|
}),
|
||||||
validateRequest,
|
param("workspaceId").exists().trim(),
|
||||||
membershipController.validateMembership
|
validateRequest,
|
||||||
|
membershipController.validateMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete( // TODO endpoint: check dashboard
|
router.delete(
|
||||||
"/:membershipId",
|
// TODO endpoint: check dashboard
|
||||||
requireAuth({
|
"/:membershipId",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
param("membershipId").exists().trim(),
|
}),
|
||||||
validateRequest,
|
param("membershipId").exists().trim(),
|
||||||
membershipController.deleteMembership
|
validateRequest,
|
||||||
|
membershipController.deleteMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post( // TODO endpoint: check dashboard
|
router.post(
|
||||||
"/:membershipId/change-role",
|
// TODO endpoint: check dashboard
|
||||||
requireAuth({
|
"/:membershipId/change-role",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
body("role").exists().trim(),
|
}),
|
||||||
validateRequest,
|
body("role").exists().trim(),
|
||||||
membershipController.changeMembershipRole
|
validateRequest,
|
||||||
|
membershipController.changeMembershipRole
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post( // TODO endpoint: check dashboard
|
router.post(
|
||||||
"/:membershipId/deny-permissions",
|
// TODO endpoint: check dashboard
|
||||||
requireAuth({
|
"/:membershipId/deny-permissions",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
param("membershipId").isMongoId().exists().trim(),
|
}),
|
||||||
body("permissions").isArray().exists(),
|
param("membershipId").isMongoId().exists().trim(),
|
||||||
validateRequest,
|
body("permissions").isArray().exists(),
|
||||||
EEMembershipControllers.denyMembershipPermissions
|
validateRequest,
|
||||||
|
EEMembershipControllers.denyMembershipPermissions
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -5,24 +5,23 @@ import { requireAuth, validateRequest } from "../../middleware";
|
|||||||
import { membershipOrgController } from "../../controllers/v1";
|
import { membershipOrgController } from "../../controllers/v1";
|
||||||
import { AuthMode } from "../../variables";
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
router.post( // TODO endpoint: check dashboard
|
router.post(
|
||||||
"/membershipOrg/:membershipOrgId/change-role",
|
// TODO endpoint: check dashboard
|
||||||
requireAuth({
|
"/membershipOrg/:membershipOrgId/change-role",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
param("membershipOrgId"),
|
}),
|
||||||
validateRequest,
|
param("membershipOrgId"),
|
||||||
membershipOrgController.changeMembershipOrgRole
|
validateRequest,
|
||||||
|
membershipOrgController.changeMembershipOrgRole
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:membershipOrgId", // TODO endpoint: check dashboard
|
"/:membershipOrgId", // TODO endpoint: check dashboard
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
param("membershipOrgId").exists().trim(),
|
membershipOrgController.deleteMembershipOrg
|
||||||
validateRequest,
|
|
||||||
membershipOrgController.deleteMembershipOrg
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -1,166 +1,99 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import { body, param } from "express-validator";
|
import { requireAuth } from "../../middleware";
|
||||||
import {
|
import { AuthMode } from "../../variables";
|
||||||
requireAuth,
|
|
||||||
requireOrganizationAuth,
|
|
||||||
validateRequest,
|
|
||||||
} from "../../middleware";
|
|
||||||
import {
|
|
||||||
ACCEPTED,
|
|
||||||
ADMIN,
|
|
||||||
AuthMode,
|
|
||||||
MEMBER,
|
|
||||||
OWNER
|
|
||||||
} from "../../variables";
|
|
||||||
import { organizationController } from "../../controllers/v1";
|
import { organizationController } from "../../controllers/v1";
|
||||||
|
|
||||||
router.get( // TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
|
router.get(
|
||||||
"/",
|
// TODO endpoint: deprecate (moved to api/v2/users/me/organizations)
|
||||||
requireAuth({
|
"/",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
organizationController.getOrganizations
|
}),
|
||||||
|
organizationController.getOrganizations
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post( // not used on frontend
|
router.post(
|
||||||
"/",
|
// not used on frontend
|
||||||
requireAuth({
|
"/",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
body("organizationName").exists().trim().notEmpty(),
|
}),
|
||||||
validateRequest,
|
organizationController.createOrganization
|
||||||
organizationController.createOrganization
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId",
|
"/:organizationId",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.getOrganization
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganization
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get( // TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
|
router.get(
|
||||||
"/:organizationId/users",
|
// TODO endpoint: deprecate (moved to api/v2/organizations/:organizationId/memberships)
|
||||||
requireAuth({
|
"/:organizationId/users",
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
requireOrganizationAuth({
|
}),
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
organizationController.getOrganizationMembers
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganizationMembers
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get( // TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
|
router.get(
|
||||||
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
|
// TODO endpoint: move to /v2/users/me/organizations/:organizationId/workspaces
|
||||||
requireAuth({
|
"/:organizationId/my-workspaces", // deprecated (moved to api/v2/organizations/:organizationId/workspaces)
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
requireAuth({
|
||||||
}),
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
requireOrganizationAuth({
|
}),
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
organizationController.getOrganizationWorkspaces
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganizationWorkspaces
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:organizationId/name",
|
"/:organizationId/name",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.changeOrganizationName
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("name").exists().trim().notEmpty(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.changeOrganizationName
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/incidentContactOrg",
|
"/:organizationId/incidentContactOrg",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.getOrganizationIncidentContacts
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganizationIncidentContacts
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:organizationId/incidentContactOrg",
|
"/:organizationId/incidentContactOrg",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.addOrganizationIncidentContact
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("email").exists().trim().notEmpty(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.addOrganizationIncidentContact
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:organizationId/incidentContactOrg",
|
"/:organizationId/incidentContactOrg",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.deleteOrganizationIncidentContact
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
body("email").exists().trim().notEmpty(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.deleteOrganizationIncidentContact
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.post(
|
router.post(
|
||||||
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE
|
"/:organizationId/customer-portal-session", // TODO endpoint: move to EE
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT],
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.createOrganizationPortalSession
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.createOrganizationPortalSession
|
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/workspace-memberships",
|
"/:organizationId/workspace-memberships",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT]
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
organizationController.getOrganizationMembersAndTheirWorkspaces
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED],
|
|
||||||
}),
|
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
organizationController.getOrganizationMembersAndTheirWorkspaces
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -2,95 +2,56 @@ import express from "express";
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireMembershipOrgAuth,
|
requireOrganizationAuth
|
||||||
requireOrganizationAuth,
|
|
||||||
validateRequest
|
|
||||||
} from "../../middleware";
|
} from "../../middleware";
|
||||||
import { body, param } from "express-validator";
|
import { ACCEPTED, ADMIN, AuthMode, OWNER } from "../../variables";
|
||||||
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../variables";
|
|
||||||
import { organizationsController } from "../../controllers/v2";
|
import { organizationsController } from "../../controllers/v2";
|
||||||
|
|
||||||
// TODO: /POST to create membership
|
// TODO: /POST to create membership
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/memberships",
|
"/:organizationId/memberships",
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
|
||||||
acceptedStatuses: [ACCEPTED]
|
|
||||||
}),
|
|
||||||
organizationsController.getOrganizationMemberships
|
organizationsController.getOrganizationMemberships
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:organizationId/memberships/:membershipId",
|
"/:organizationId/memberships/:membershipId",
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
param("membershipId").exists().trim(),
|
|
||||||
body("role").exists().isString().trim(),
|
|
||||||
validateRequest,
|
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
|
||||||
acceptedStatuses: [ACCEPTED]
|
|
||||||
}),
|
|
||||||
requireMembershipOrgAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
|
||||||
acceptedStatuses: [ACCEPTED]
|
|
||||||
}),
|
|
||||||
organizationsController.updateOrganizationMembership
|
organizationsController.updateOrganizationMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:organizationId/memberships/:membershipId",
|
"/:organizationId/memberships/:membershipId",
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
param("membershipId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
|
||||||
acceptedStatuses: [ACCEPTED]
|
|
||||||
}),
|
|
||||||
requireMembershipOrgAuth({
|
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
|
||||||
acceptedStatuses: [ACCEPTED]
|
|
||||||
}),
|
|
||||||
organizationsController.deleteOrganizationMembership
|
organizationsController.deleteOrganizationMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/workspaces",
|
"/:organizationId/workspaces",
|
||||||
param("organizationId").exists().trim(),
|
|
||||||
validateRequest,
|
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
|
organizationsController.getOrganizationWorkspaces
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get(
|
||||||
|
// TODO endpoint: deprecate service accounts
|
||||||
|
"/:organizationId/service-accounts",
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AuthMode.JWT]
|
||||||
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED]
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.getOrganizationWorkspaces
|
organizationsController.getOrganizationServiceAccounts
|
||||||
);
|
|
||||||
|
|
||||||
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
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@ca
|
|||||||
import { MembershipOrg } from "../models";
|
import { MembershipOrg } from "../models";
|
||||||
import { IRole } from "../models/role";
|
import { IRole } from "../models/role";
|
||||||
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
import { ACCEPTED } from "../variables";
|
||||||
|
|
||||||
export enum GeneralPermissionActions {
|
export enum GeneralPermissionActions {
|
||||||
Read = "read",
|
Read = "read",
|
||||||
@@ -10,34 +11,37 @@ export enum GeneralPermissionActions {
|
|||||||
Delete = "delete"
|
Delete = "delete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum WorkspacePermissionActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create"
|
||||||
|
}
|
||||||
|
|
||||||
export enum OrgPermissionSubjects {
|
export enum OrgPermissionSubjects {
|
||||||
Workspace = "workspace",
|
Workspace = "workspace",
|
||||||
Role = "role",
|
Role = "role",
|
||||||
Member = "member",
|
Member = "member",
|
||||||
Settings = "settings",
|
Settings = "settings",
|
||||||
ServiceAccount = "service-account",
|
|
||||||
IncidentAccount = "incident-contact",
|
IncidentAccount = "incident-contact",
|
||||||
Sso = "sso",
|
Sso = "sso",
|
||||||
Billing = "billing"
|
Billing = "billing",
|
||||||
|
SecretScanning = "secret-scanning"
|
||||||
}
|
}
|
||||||
|
|
||||||
export type OrgPermissionSet =
|
export type OrgPermissionSet =
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Workspace]
|
| [WorkspacePermissionActions, OrgPermissionSubjects.Workspace]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Role]
|
| [GeneralPermissionActions, OrgPermissionSubjects.Role]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Member]
|
| [GeneralPermissionActions, OrgPermissionSubjects.Member]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Settings]
|
| [GeneralPermissionActions, OrgPermissionSubjects.Settings]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.ServiceAccount]
|
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
| [GeneralPermissionActions, OrgPermissionSubjects.IncidentAccount]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Sso]
|
| [GeneralPermissionActions, OrgPermissionSubjects.Sso]
|
||||||
|
| [GeneralPermissionActions, OrgPermissionSubjects.SecretScanning]
|
||||||
| [GeneralPermissionActions, OrgPermissionSubjects.Billing];
|
| [GeneralPermissionActions, OrgPermissionSubjects.Billing];
|
||||||
|
|
||||||
const buildAdminPermission = () => {
|
const buildAdminPermission = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
// ws permissions
|
// ws permissions
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
can(WorkspacePermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||||
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
can(WorkspacePermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Workspace);
|
|
||||||
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.Workspace);
|
|
||||||
// role permission
|
// role permission
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Role);
|
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Role);
|
||||||
@@ -49,16 +53,16 @@ const buildAdminPermission = () => {
|
|||||||
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Member);
|
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Member);
|
||||||
can(GeneralPermissionActions.Delete, 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.Read, OrgPermissionSubjects.Settings);
|
||||||
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Settings);
|
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Settings);
|
||||||
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Settings);
|
||||||
can(GeneralPermissionActions.Delete, 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.Read, OrgPermissionSubjects.IncidentAccount);
|
||||||
can(GeneralPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
|
can(GeneralPermissionActions.Create, OrgPermissionSubjects.IncidentAccount);
|
||||||
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.IncidentAccount);
|
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.IncidentAccount);
|
||||||
@@ -82,14 +86,15 @@ export const adminPermissions = buildAdminPermission();
|
|||||||
const buildMemberPermission = () => {
|
const buildMemberPermission = () => {
|
||||||
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
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.Member);
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Settings);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Settings);
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Billing);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Billing);
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Sso);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Sso);
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.IncidentAccount);
|
||||||
can(GeneralPermissionActions.Read, OrgPermissionSubjects.ServiceAccount);
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.SecretScanning);
|
||||||
|
|
||||||
return build();
|
return build();
|
||||||
};
|
};
|
||||||
@@ -98,23 +103,28 @@ export const memberPermissions = buildMemberPermission();
|
|||||||
|
|
||||||
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||||
// TODO(akhilmhdh): speed this up by pulling from cache later
|
// 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>>[] } }>(
|
.populate<{ customRole: IRole & { permissions: RawRuleOf<MongoAbility<OrgPermissionSet>>[] } }>(
|
||||||
"customRole"
|
"customRole"
|
||||||
)
|
)
|
||||||
.exec();
|
.exec();
|
||||||
|
|
||||||
if (!orgMembership || (orgMembership.role === "custom" && !orgMembership.customRole)) {
|
if (!membership || (membership.role === "custom" && !membership.customRole)) {
|
||||||
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
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") {
|
if (membership.role === "custom") {
|
||||||
const permission = createMongoAbility<OrgPermissionSet>(orgMembership.customRole.permissions);
|
const permission = createMongoAbility<OrgPermissionSet>(membership.customRole.permissions);
|
||||||
return permission;
|
return { permission, membership };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw BadRequestError({ message: "User role not found" });
|
throw BadRequestError({ message: "User role not found" });
|
||||||
|
|||||||
@@ -1,16 +1,10 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import { MembershipOrg } from "../models";
|
||||||
MembershipOrg,
|
import { validateMembershipOrg } from "../helpers/membershipOrg";
|
||||||
} from "../models";
|
import { MembershipOrgNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
import {
|
|
||||||
validateMembershipOrg,
|
|
||||||
} from "../helpers/membershipOrg";
|
|
||||||
import {
|
|
||||||
MembershipOrgNotFoundError,
|
|
||||||
UnauthorizedRequestError,
|
|
||||||
} from "../utils/errors";
|
|
||||||
import { AuthData } from "../interfaces/middleware";
|
import { AuthData } from "../interfaces/middleware";
|
||||||
import { ActorType } from "../ee/models";
|
import { ActorType } from "../ee/models";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
* Validate authenticated clients for organization membership with id [membershipOrgId] based
|
||||||
@@ -22,35 +16,57 @@ import { ActorType } from "../ee/models";
|
|||||||
* @param {MembershipOrg} - validated organization membership
|
* @param {MembershipOrg} - validated organization membership
|
||||||
*/
|
*/
|
||||||
export const validateClientForMembershipOrg = async ({
|
export const validateClientForMembershipOrg = async ({
|
||||||
authData,
|
authData,
|
||||||
membershipOrgId,
|
membershipOrgId,
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses
|
||||||
}: {
|
}: {
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
membershipOrgId: Types.ObjectId;
|
membershipOrgId: Types.ObjectId;
|
||||||
acceptedRoles: Array<"owner" | "admin" | "member">;
|
acceptedRoles: Array<"owner" | "admin" | "member">;
|
||||||
acceptedStatuses: Array<"invited" | "accepted">;
|
acceptedStatuses: Array<"invited" | "accepted">;
|
||||||
}) => {
|
}) => {
|
||||||
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
const membershipOrg = await MembershipOrg.findById(membershipOrgId);
|
||||||
|
|
||||||
if (!membershipOrg) throw MembershipOrgNotFoundError({
|
if (!membershipOrg)
|
||||||
message: "Failed to find organization membership ",
|
throw MembershipOrgNotFoundError({
|
||||||
});
|
message: "Failed to find organization membership "
|
||||||
|
});
|
||||||
switch (authData.actor.type) {
|
|
||||||
case ActorType.USER:
|
switch (authData.actor.type) {
|
||||||
await validateMembershipOrg({
|
case ActorType.USER:
|
||||||
userId: authData.authPayload._id,
|
await validateMembershipOrg({
|
||||||
organizationId: membershipOrg.organization,
|
userId: authData.authPayload._id,
|
||||||
acceptedRoles,
|
organizationId: membershipOrg.organization,
|
||||||
acceptedStatuses,
|
acceptedRoles,
|
||||||
});
|
acceptedStatuses
|
||||||
|
});
|
||||||
return membershipOrg;
|
|
||||||
case ActorType.SERVICE:
|
return membershipOrg;
|
||||||
throw UnauthorizedRequestError({
|
case ActorType.SERVICE:
|
||||||
message: "Failed service account client authorization for organization membership",
|
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 { Types } from "mongoose";
|
||||||
import {
|
import { z } from "zod";
|
||||||
IUser,
|
import { IUser, Organization } from "../models";
|
||||||
Organization,
|
import { OrganizationNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
} from "../models";
|
|
||||||
import {
|
|
||||||
OrganizationNotFoundError,
|
|
||||||
UnauthorizedRequestError,
|
|
||||||
} from "../utils/errors";
|
|
||||||
import { validateUserClientForOrganization } from "./user";
|
import { validateUserClientForOrganization } from "./user";
|
||||||
import { AuthData } from "../interfaces/middleware";
|
import { AuthData } from "../interfaces/middleware";
|
||||||
import { ActorType } from "../ee/models";
|
import { ActorType } from "../ee/models";
|
||||||
@@ -21,7 +16,7 @@ export const validateClientForOrganization = async ({
|
|||||||
authData,
|
authData,
|
||||||
organizationId,
|
organizationId,
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses
|
||||||
}: {
|
}: {
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
organizationId: Types.ObjectId;
|
organizationId: Types.ObjectId;
|
||||||
@@ -32,10 +27,10 @@ export const validateClientForOrganization = async ({
|
|||||||
|
|
||||||
if (!organization) {
|
if (!organization) {
|
||||||
throw OrganizationNotFoundError({
|
throw OrganizationNotFoundError({
|
||||||
message: "Failed to find organization",
|
message: "Failed to find organization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let membershipOrg;
|
let membershipOrg;
|
||||||
switch (authData.actor.type) {
|
switch (authData.actor.type) {
|
||||||
case ActorType.USER:
|
case ActorType.USER:
|
||||||
@@ -43,13 +38,162 @@ export const validateClientForOrganization = async ({
|
|||||||
user: authData.authPayload as IUser,
|
user: authData.authPayload as IUser,
|
||||||
organization,
|
organization,
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses
|
||||||
});
|
});
|
||||||
|
|
||||||
return { organization, membershipOrg };
|
return { organization, membershipOrg };
|
||||||
case ActorType.SERVICE:
|
case ActorType.SERVICE:
|
||||||
throw UnauthorizedRequestError({
|
throw UnauthorizedRequestError({
|
||||||
message: "Failed service token authorization for organization",
|
message: "Failed service token authorization for organization"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const GetOrgPlansTablev1 = z.object({
|
||||||
|
query: z.object({ billingCycle: z.enum(["monthly", "yearly"]) }),
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgPlanv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const StartOrgTrailv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({ success_url: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgPlanBillingInfov1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgPlanTablev1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
query: z.object({ workspaceId: z.string().trim().optional() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgBillingDetailsv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateOrgBillingDetailsv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({
|
||||||
|
email: z.string().trim().email().optional(),
|
||||||
|
name: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgPmtMethodsv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOrgPmtMethodv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({
|
||||||
|
success_url: z.string().trim(),
|
||||||
|
cancel_url: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DelOrgPmtMethodv1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
pmtMethodId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgTaxIdsv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOrgTaxId = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({
|
||||||
|
type: z.string().trim(),
|
||||||
|
value: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DelOrgTaxIdv1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
taxId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgInvoicesv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgLicencesv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOrgv1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
organizationName: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgv1 = z.object({
|
||||||
|
params: z.object({
|
||||||
|
organizationId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgMembersv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgWorkspacesv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ChangeOrgNamev1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({ name: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgIncidentContactv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOrgIncideContact = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({ email: z.string().email().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DelOrgIncideContact = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() }),
|
||||||
|
body: z.object({ email: z.string().email().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateOrgPortalSessionv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgMembersAndWsv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgMembersv2 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateOrgMemberv2 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() }),
|
||||||
|
body: z.object({
|
||||||
|
role: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteOrgMemberv2 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim(), membershipId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgWorkspacesv2 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const CreateInstalLSessionv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LinkInstallationToOrgv1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
installationId: z.number(),
|
||||||
|
sessionId: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgInstallStatusv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetOrgRisksv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateRiskStatusv1 = z.object({
|
||||||
|
params: z.object({ organizationId: z.string().trim(), riskId: z.string().trim() }),
|
||||||
|
body: z.object({ status: z.string().trim() })
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
import { AuthProvider } from "../ee/models";
|
||||||
|
|
||||||
|
export const GetSsoConfigv1 = z.object({
|
||||||
|
query: z.object({ organizationId: z.string().trim() })
|
||||||
|
});
|
||||||
|
|
||||||
|
export const CreateSsoConfigv1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
authProvider: z.nativeEnum(AuthProvider),
|
||||||
|
isActive: z.boolean(),
|
||||||
|
entryPoint: z.string().trim(),
|
||||||
|
issuer: z.string().trim(),
|
||||||
|
cert: z.string().trim()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateSsoConfigv1 = z.object({
|
||||||
|
body: z.object({
|
||||||
|
organizationId: z.string().trim(),
|
||||||
|
authProvider: z.nativeEnum(AuthProvider).optional(),
|
||||||
|
isActive: z.boolean().optional(),
|
||||||
|
entryPoint: z.string().trim().optional(),
|
||||||
|
issuer: z.string().trim().optional(),
|
||||||
|
cert: z.string().trim().optional()
|
||||||
|
})
|
||||||
|
});
|
||||||
+105
-112
@@ -1,25 +1,14 @@
|
|||||||
import net from "net";
|
import net from "net";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import { IServiceTokenData, IUser, SecretBlindIndexData, Workspace } from "../models";
|
||||||
IServiceTokenData,
|
import { ActorType, TrustedIP } from "../ee/models";
|
||||||
IUser,
|
|
||||||
SecretBlindIndexData,
|
|
||||||
Workspace,
|
|
||||||
} from "../models";
|
|
||||||
import {
|
|
||||||
ActorType,
|
|
||||||
TrustedIP
|
|
||||||
} from "../ee/models";
|
|
||||||
import { validateUserClientForWorkspace } from "./user";
|
import { validateUserClientForWorkspace } from "./user";
|
||||||
import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData";
|
import { validateServiceTokenDataClientForWorkspace } from "./serviceTokenData";
|
||||||
import {
|
import { BadRequestError, UnauthorizedRequestError, WorkspaceNotFoundError } from "../utils/errors";
|
||||||
BadRequestError,
|
|
||||||
UnauthorizedRequestError,
|
|
||||||
WorkspaceNotFoundError,
|
|
||||||
} from "../utils/errors";
|
|
||||||
import { BotService } from "../services";
|
import { BotService } from "../services";
|
||||||
import { AuthData } from "../interfaces/middleware";
|
import { AuthData } from "../interfaces/middleware";
|
||||||
import { extractIPDetails } from "../utils/ip";
|
import { extractIPDetails } from "../utils/ip";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate authenticated clients for workspace with id [workspaceId] based
|
* 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
|
* @param {String[]} obj.requiredPermissions - required permissions as part of the endpoint
|
||||||
*/
|
*/
|
||||||
export const validateClientForWorkspace = async ({
|
export const validateClientForWorkspace = async ({
|
||||||
authData,
|
authData,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
requiredPermissions,
|
requiredPermissions,
|
||||||
requireBlindIndicesEnabled,
|
requireBlindIndicesEnabled,
|
||||||
requireE2EEOff,
|
requireE2EEOff,
|
||||||
checkIPAllowlist
|
checkIPAllowlist
|
||||||
}: {
|
}: {
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
workspaceId: Types.ObjectId;
|
workspaceId: Types.ObjectId;
|
||||||
environment?: string;
|
environment?: string;
|
||||||
acceptedRoles: Array<"admin" | "member">;
|
acceptedRoles: Array<"admin" | "member">;
|
||||||
requiredPermissions?: string[];
|
requiredPermissions?: string[];
|
||||||
requireBlindIndicesEnabled: boolean;
|
requireBlindIndicesEnabled: boolean;
|
||||||
requireE2EEOff: boolean;
|
requireE2EEOff: boolean;
|
||||||
checkIPAllowlist: boolean;
|
checkIPAllowlist: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const workspace = await Workspace.findById(workspaceId);
|
const workspace = await Workspace.findById(workspaceId);
|
||||||
|
|
||||||
if (!workspace) throw WorkspaceNotFoundError({
|
if (!workspace)
|
||||||
message: "Failed to find workspace",
|
throw WorkspaceNotFoundError({
|
||||||
});
|
message: "Failed to find workspace"
|
||||||
|
});
|
||||||
|
|
||||||
if (requireBlindIndicesEnabled) {
|
if (requireBlindIndicesEnabled) {
|
||||||
// case: blind indices are not enabled for secrets in this workspace
|
// case: blind indices are not enabled for secrets in this workspace
|
||||||
// (i.e. workspace was created before blind indices were introduced
|
// (i.e. workspace was created before blind indices were introduced
|
||||||
// and no admin has enabled it)
|
// 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
|
|
||||||
|
|
||||||
const blockList = new net.BlockList();
|
const secretBlindIndexData = await SecretBlindIndexData.exists({
|
||||||
|
workspace: new Types.ObjectId(workspaceId)
|
||||||
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({
|
if (!secretBlindIndexData)
|
||||||
serviceTokenData: authData.authPayload as IServiceTokenData,
|
throw UnauthorizedRequestError({
|
||||||
workspaceId,
|
message: "Failed workspace authorization due to blind indices not being enabled"
|
||||||
environment,
|
});
|
||||||
requiredPermissions,
|
}
|
||||||
});
|
|
||||||
|
if (requireE2EEOff) {
|
||||||
return {};
|
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;
|
updatedAt: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TPermission = {
|
export type TPermission = TWorkspacePermission | TGeneralPermission;
|
||||||
|
|
||||||
|
type TGeneralPermission = {
|
||||||
condition?: Record<string, any>;
|
condition?: Record<string, any>;
|
||||||
action: "read" | "edit" | "create" | "delete";
|
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 = {
|
export type TCreateRoleDTO = {
|
||||||
|
|||||||
+5
-5
@@ -20,7 +20,7 @@ import {
|
|||||||
TFormSchema
|
TFormSchema
|
||||||
} from "./OrgRoleModifySection.utils";
|
} from "./OrgRoleModifySection.utils";
|
||||||
import { RolePermission } from "./RolePermission";
|
import { RolePermission } from "./RolePermission";
|
||||||
import { ServiceAccountPermission } from "./ServiceAccountPermission";
|
import { SecretScannigPermission } from "./SecretScanningPermission";
|
||||||
import { SettingsPermission } from "./SettingsPermission";
|
import { SettingsPermission } from "./SettingsPermission";
|
||||||
import { SsoPermission } from "./SsoPermission";
|
import { SsoPermission } from "./SsoPermission";
|
||||||
import { WorkspacePermission } from "./WorkspacePermission";
|
import { WorkspacePermission } from "./WorkspacePermission";
|
||||||
@@ -183,15 +183,15 @@ export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
<SsoPermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
<SecretScannigPermission
|
||||||
</div>
|
|
||||||
<div className="flex flex-col space-y-4">
|
|
||||||
<ServiceAccountPermission
|
|
||||||
isNonEditable={isNonEditable}
|
isNonEditable={isNonEditable}
|
||||||
control={control}
|
control={control}
|
||||||
setValue={setValue}
|
setValue={setValue}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col space-y-4">
|
||||||
|
<SsoPermission isNonEditable={isNonEditable} control={control} setValue={setValue} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4 mt-12">
|
<div className="flex items-center space-x-4 mt-12">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
+15
-69
@@ -3,8 +3,6 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { TPermission } from "@app/hooks/api/roles/types";
|
import { TPermission } from "@app/hooks/api/roles/types";
|
||||||
|
|
||||||
const PERMISSION_ACTIONS = ["read", "create", "edit", "delete"] as const;
|
|
||||||
|
|
||||||
const generalPermissionSchema = z.object({
|
const generalPermissionSchema = z.object({
|
||||||
read: z.boolean().optional(),
|
read: z.boolean().optional(),
|
||||||
edit: z.boolean().optional(),
|
edit: z.boolean().optional(),
|
||||||
@@ -17,12 +15,16 @@ export const formSchema = z.object({
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
slug: z.string(),
|
slug: z.string(),
|
||||||
permissions: z.object({
|
permissions: z.object({
|
||||||
workspace: z.record(generalPermissionSchema),
|
workspace: z.object({
|
||||||
|
read: z.boolean().optional(),
|
||||||
|
create: z.boolean().optional()
|
||||||
|
}),
|
||||||
member: generalPermissionSchema,
|
member: generalPermissionSchema,
|
||||||
role: generalPermissionSchema,
|
role: generalPermissionSchema,
|
||||||
settings: generalPermissionSchema,
|
settings: generalPermissionSchema,
|
||||||
"service-account": generalPermissionSchema,
|
"service-account": generalPermissionSchema,
|
||||||
"incident-contact": generalPermissionSchema,
|
"incident-contact": generalPermissionSchema,
|
||||||
|
"secret-scanning": generalPermissionSchema,
|
||||||
sso: generalPermissionSchema,
|
sso: generalPermissionSchema,
|
||||||
billing: generalPermissionSchema
|
billing: generalPermissionSchema
|
||||||
})
|
})
|
||||||
@@ -30,25 +32,6 @@ export const formSchema = z.object({
|
|||||||
|
|
||||||
export type TFormSchema = z.infer<typeof formSchema>;
|
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
|
// convert role permission to form compatiable data structure
|
||||||
export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
||||||
const formVal: TFormSchema["permissions"] = {
|
const formVal: TFormSchema["permissions"] = {
|
||||||
@@ -59,68 +42,31 @@ export const rolePermission2Form = (permissions: TPermission[] = []) => {
|
|||||||
sso: {},
|
sso: {},
|
||||||
member: {},
|
member: {},
|
||||||
"service-account": {},
|
"service-account": {},
|
||||||
"incident-contact": {}
|
"incident-contact": {},
|
||||||
|
"secret-scanning": {}
|
||||||
};
|
};
|
||||||
|
|
||||||
permissions.forEach((permission) => {
|
permissions.forEach((permission) => {
|
||||||
switch (permission.subject) {
|
// akhilmhdh: this is typecast as workspace key else i would need an if loop with same condition on both side
|
||||||
case "workspace":
|
formVal[permission.subject][permission.action as keyof typeof formVal.workspace] = true;
|
||||||
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;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return formVal;
|
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"]) => {
|
export const formRolePermission2API = (formVal: TFormSchema["permissions"]) => {
|
||||||
const permissions: TPermission[] = [];
|
const permissions: TPermission[] = [];
|
||||||
if (formVal?.workspace) {
|
(Object.keys(formVal) as Array<keyof typeof formVal>).forEach((rule) => {
|
||||||
// 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) => {
|
|
||||||
// all these type annotations are due to Object.keys of ts cannot infer and put it just a string[]
|
// all these type annotations are due to Object.keys of ts cannot infer and put it just a string[]
|
||||||
// quite annoying i know
|
// 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>
|
keyof z.infer<typeof generalPermissionSchema>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
actions.forEach((action) => {
|
actions.forEach((action) => {
|
||||||
if (rules[rule][action]) {
|
// 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
|
||||||
permissions.push({ action, subject: rule });
|
if (formVal[rule][action as keyof typeof formVal.workspace]) {
|
||||||
|
permissions.push({ subject: rule, action } as any);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+13
-13
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
@@ -30,10 +30,10 @@ const PERMISSIONS = [
|
|||||||
{ action: "delete", label: "Remove" }
|
{ action: "delete", label: "Remove" }
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: Props) => {
|
export const SecretScannigPermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||||
const rule = useWatch({
|
const rule = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "permissions.service-account"
|
name: "permissions.secret-scanning"
|
||||||
});
|
});
|
||||||
const [isCustom, setIsCustom] = useToggle();
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
|||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setIsCustom.off();
|
setIsCustom.off();
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.service-account",
|
"permissions.secret-scanning",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
@@ -68,7 +68,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
|||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setIsCustom.off();
|
setIsCustom.off();
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.service-account",
|
"permissions.secret-scanning",
|
||||||
{ read: true, edit: true, create: true, delete: true },
|
{ read: true, edit: true, create: true, delete: true },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
@@ -76,7 +76,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
|||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setIsCustom.off();
|
setIsCustom.off();
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.service-account",
|
"permissions.secret-scanning",
|
||||||
{ read: true, edit: false, create: false, delete: false },
|
{ read: true, edit: false, create: false, delete: false },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
@@ -84,7 +84,7 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
|||||||
default:
|
default:
|
||||||
setIsCustom.on();
|
setIsCustom.on();
|
||||||
setValue(
|
setValue(
|
||||||
"permissions.service-account",
|
"permissions.secret-scanning",
|
||||||
{ read: false, edit: false, create: false, delete: false },
|
{ read: false, edit: false, create: false, delete: false },
|
||||||
{ shouldDirty: true }
|
{ shouldDirty: true }
|
||||||
);
|
);
|
||||||
@@ -101,11 +101,11 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
|||||||
>
|
>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={faLaptopCode} className="text-4xl" />
|
<FontAwesomeIcon icon={faMagnifyingGlass} className="text-4xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="font-medium mb-1 text-lg">Service Accounts</div>
|
<div className="font-medium mb-1 text-lg">Secret Scanning</div>
|
||||||
<div className="text-xs font-light">Service Account management control</div>
|
<div className="text-xs font-light">Secret scanning management control</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
@@ -129,14 +129,14 @@ export const ServiceAccountPermission = ({ isNonEditable, setValue, control }: P
|
|||||||
{isCustom &&
|
{isCustom &&
|
||||||
PERMISSIONS.map(({ action, label }) => (
|
PERMISSIONS.map(({ action, label }) => (
|
||||||
<Controller
|
<Controller
|
||||||
name={`permissions.service-account.${action}`}
|
name={`permissions.role.${action}`}
|
||||||
key={`permissions.service-account.${action}`}
|
key={`permissions.role.${action}`}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
isChecked={field.value}
|
isChecked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
id={`permissions.service-account.${action}`}
|
id={`permissions.role.${action}`}
|
||||||
isDisabled={isNonEditable}
|
isDisabled={isNonEditable}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
+58
-141
@@ -1,23 +1,12 @@
|
|||||||
import { useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { Control, Controller, UseFormSetValue, useWatch } from "react-hook-form";
|
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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
import {
|
import { Checkbox, Select, SelectItem } from "@app/components/v2";
|
||||||
Checkbox,
|
import { useToggle } from "@app/hooks";
|
||||||
Select,
|
|
||||||
SelectItem,
|
|
||||||
Table,
|
|
||||||
TableContainer,
|
|
||||||
TBody,
|
|
||||||
Td,
|
|
||||||
Th,
|
|
||||||
THead,
|
|
||||||
Tr
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { useWorkspace } from "@app/context";
|
|
||||||
|
|
||||||
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
import { TFormSchema } from "./OrgRoleModifySection.utils";
|
||||||
|
|
||||||
@@ -34,48 +23,50 @@ enum Permission {
|
|||||||
Custom = "custom"
|
Custom = "custom"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props) => {
|
const PERMISSIONS = [
|
||||||
const { workspaces } = useWorkspace();
|
{ action: "read", label: "Read" },
|
||||||
|
{ action: "create", label: "Create" }
|
||||||
|
] as const;
|
||||||
|
|
||||||
const customWorkspaceRule = useWatch({
|
export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props) => {
|
||||||
|
const rule = useWatch({
|
||||||
control,
|
control,
|
||||||
name: "permissions.workspace.custom"
|
name: "permissions.workspace"
|
||||||
});
|
});
|
||||||
const isCustom = Boolean(customWorkspaceRule);
|
const [isCustom, setIsCustom] = useToggle();
|
||||||
const allWorkspaceRule = useWatch({ control, name: "permissions.workspace.all" });
|
|
||||||
|
|
||||||
const selectedPermissionCategory = useMemo(() => {
|
const selectedPermissionCategory = useMemo(() => {
|
||||||
const { read, delete: del, edit, create } = allWorkspaceRule || {};
|
let score = 0;
|
||||||
if (read && del && edit && create) return Permission.FullAccess;
|
const actions = Object.keys(rule || {}) as Array<keyof typeof rule>;
|
||||||
if (read) return Permission.ReadOnly;
|
const totalActions = PERMISSIONS.length;
|
||||||
return Permission.NoAccess;
|
actions.forEach((key) => (score += rule[key] ? 1 : 0));
|
||||||
}, [allWorkspaceRule]);
|
|
||||||
|
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) => {
|
const handlePermissionChange = (val: Permission) => {
|
||||||
|
val === Permission.Custom ? setIsCustom.on() : setIsCustom.off();
|
||||||
switch (val) {
|
switch (val) {
|
||||||
case Permission.NoAccess:
|
case Permission.NoAccess:
|
||||||
setValue("permissions.workspace", {}, { shouldDirty: true });
|
setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true });
|
||||||
break;
|
break;
|
||||||
case Permission.FullAccess:
|
case Permission.FullAccess:
|
||||||
setValue(
|
setValue("permissions.workspace", { read: true, create: true }, { shouldDirty: true });
|
||||||
"permissions.workspace",
|
|
||||||
{ all: { read: true, edit: true, create: true, delete: true } },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
case Permission.ReadOnly:
|
case Permission.ReadOnly:
|
||||||
setValue(
|
setValue("permissions.workspace", { read: true, create: false }, { shouldDirty: true });
|
||||||
"permissions.workspace",
|
|
||||||
{ all: { read: true, edit: false, create: false, delete: false } },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
setValue(
|
setValue("permissions.workspace", { read: false, create: false }, { shouldDirty: true });
|
||||||
"permissions.workspace",
|
|
||||||
{ custom: { read: false, edit: false, create: false, delete: false } },
|
|
||||||
{ shouldDirty: true }
|
|
||||||
);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -84,23 +75,22 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
<div
|
<div
|
||||||
className={twMerge(
|
className={twMerge(
|
||||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||||
(selectedPermissionCategory !== Permission.NoAccess || isCustom) &&
|
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||||
"border-l-2 border-primary-600"
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<div>
|
<div>
|
||||||
<FontAwesomeIcon icon={faClipboardList} className="text-4xl" />
|
<FontAwesomeIcon icon={faMoneyBill} className="text-4xl" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-grow flex flex-col">
|
<div className="flex-grow flex flex-col">
|
||||||
<div className="font-medium mb-1 text-lg">Projects</div>
|
<div className="font-medium mb-1 text-lg">Project</div>
|
||||||
<div className="text-xs font-light">User project access control</div>
|
<div className="text-xs font-light">Project management control</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={Permission.NoAccess}
|
defaultValue={Permission.NoAccess}
|
||||||
isDisabled={isNonEditable}
|
isDisabled={isNonEditable}
|
||||||
value={isCustom ? Permission.Custom : selectedPermissionCategory}
|
value={selectedPermissionCategory}
|
||||||
onValueChange={handlePermissionChange}
|
onValueChange={handlePermissionChange}
|
||||||
>
|
>
|
||||||
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
<SelectItem value={Permission.NoAccess}>No Access</SelectItem>
|
||||||
@@ -112,100 +102,27 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
|||||||
</div>
|
</div>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={false}
|
initial={false}
|
||||||
animate={{ height: isCustom ? "auto" : 0 }}
|
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||||
className="overflow-hidden"
|
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||||
>
|
>
|
||||||
<TableContainer className="border-mineshaft-500 mt-6">
|
{isCustom &&
|
||||||
<Table>
|
PERMISSIONS.map(({ action, label }) => (
|
||||||
<THead>
|
<Controller
|
||||||
<Tr>
|
name={`permissions.workspace.${action}`}
|
||||||
<Th />
|
key={`permissions.workspace.${action}`}
|
||||||
<Th className="text-center">Read</Th>
|
control={control}
|
||||||
<Th className="text-center">Create</Th>
|
render={({ field }) => (
|
||||||
<Th className="text-center">Edit</Th>
|
<Checkbox
|
||||||
<Th className="text-center">Delete</Th>
|
isChecked={field.value}
|
||||||
</Tr>
|
onCheckedChange={field.onChange}
|
||||||
</THead>
|
id={`permissions.workspace.${action}`}
|
||||||
<TBody>
|
isDisabled={isNonEditable}
|
||||||
{isCustom &&
|
>
|
||||||
workspaces?.map(({ name, _id: id }) => (
|
{label}
|
||||||
<Tr key={`custom-role-ws-${name}`}>
|
</Checkbox>
|
||||||
<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>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user