mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat(rbac): implemented backend base apis for rbac permission system
This commit is contained in:
114
backend/package-lock.json
generated
114
backend/package-lock.json
generated
@@ -10,6 +10,8 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
||||||
|
"@casl/ability": "^6.5.0",
|
||||||
|
"@casl/mongoose": "^7.2.1",
|
||||||
"@godaddy/terminus": "^4.12.0",
|
"@godaddy/terminus": "^4.12.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
@@ -59,7 +61,8 @@
|
|||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"winston-loki": "^6.0.7"
|
"winston-loki": "^6.0.6",
|
||||||
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@jest/globals": "^29.3.1",
|
"@jest/globals": "^29.3.1",
|
||||||
@@ -3339,6 +3342,26 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@casl/ability": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/mongo2js": "^1.3.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/stalniy/casl/blob/master/BACKERS.md"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@casl/mongoose": {
|
||||||
|
"version": "7.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/mongoose/-/mongoose-7.2.1.tgz",
|
||||||
|
"integrity": "sha512-pojgSWYKNIwFM6wWDNct1YD0+8nIxhe2jp5jBbK8JGU60dEs2o0Yw3mCo2y7nBwbvRC2oEots/BlLMVb1Wdo8A==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@casl/ability": "^6.3.2",
|
||||||
|
"mongoose": "^6.0.13 || ^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@colors/colors": {
|
"node_modules/@colors/colors": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
@@ -6174,6 +6197,37 @@
|
|||||||
"url": "https://opencollective.com/typescript-eslint"
|
"url": "https://opencollective.com/typescript-eslint"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@ucast/core": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/js": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/mongo": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ucast/mongo2js": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@ucast/core": "^1.6.1",
|
||||||
|
"@ucast/js": "^3.0.0",
|
||||||
|
"@ucast/mongo": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@xmldom/xmldom": {
|
"node_modules/@xmldom/xmldom": {
|
||||||
"version": "0.8.10",
|
"version": "0.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
@@ -16626,6 +16680,14 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.21.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
|
||||||
|
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -19324,6 +19386,20 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@casl/ability": {
|
||||||
|
"version": "6.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/ability/-/ability-6.5.0.tgz",
|
||||||
|
"integrity": "sha512-3guc94ugr5ylZQIpJTLz0CDfwNi0mxKVECj1vJUPAvs+Lwunh/dcuUjwzc4MHM9D8JOYX0XUZMEPedpB3vIbOw==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/mongo2js": "^1.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@casl/mongoose": {
|
||||||
|
"version": "7.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@casl/mongoose/-/mongoose-7.2.1.tgz",
|
||||||
|
"integrity": "sha512-pojgSWYKNIwFM6wWDNct1YD0+8nIxhe2jp5jBbK8JGU60dEs2o0Yw3mCo2y7nBwbvRC2oEots/BlLMVb1Wdo8A==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"@colors/colors": {
|
"@colors/colors": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
@@ -21575,6 +21651,37 @@
|
|||||||
"eslint-visitor-keys": "^3.3.0"
|
"eslint-visitor-keys": "^3.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@ucast/core": {
|
||||||
|
"version": "1.10.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/core/-/core-1.10.2.tgz",
|
||||||
|
"integrity": "sha512-ons5CwXZ/51wrUPfoduC+cO7AS1/wRb0ybpQJ9RrssossDxVy4t49QxWoWgfBDvVKsz9VXzBk9z0wqTdZ+Cq8g=="
|
||||||
|
},
|
||||||
|
"@ucast/js": {
|
||||||
|
"version": "3.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/js/-/js-3.0.3.tgz",
|
||||||
|
"integrity": "sha512-jBBqt57T5WagkAjqfCIIE5UYVdaXYgGkOFYv2+kjq2AVpZ2RIbwCo/TujJpDlwTVluUI+WpnRpoGU2tSGlEvFQ==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/core": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@ucast/mongo": {
|
||||||
|
"version": "2.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo/-/mongo-2.4.3.tgz",
|
||||||
|
"integrity": "sha512-XcI8LclrHWP83H+7H2anGCEeDq0n+12FU2mXCTz6/Tva9/9ddK/iacvvhCyW6cijAAOILmt0tWplRyRhVyZLsA==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/core": "^1.4.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@ucast/mongo2js": {
|
||||||
|
"version": "1.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ucast/mongo2js/-/mongo2js-1.3.4.tgz",
|
||||||
|
"integrity": "sha512-ahazOr1HtelA5AC1KZ9x0UwPMqqimvfmtSm/PRRSeKKeE5G2SCqTgwiNzO7i9jS8zA3dzXpKVPpXMkcYLnyItA==",
|
||||||
|
"requires": {
|
||||||
|
"@ucast/core": "^1.6.1",
|
||||||
|
"@ucast/js": "^3.0.0",
|
||||||
|
"@ucast/mongo": "^2.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@xmldom/xmldom": {
|
"@xmldom/xmldom": {
|
||||||
"version": "0.8.10",
|
"version": "0.8.10",
|
||||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
|
||||||
@@ -29273,6 +29380,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||||
"dev": true
|
"dev": true
|
||||||
|
},
|
||||||
|
"zod": {
|
||||||
|
"version": "3.21.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
|
||||||
|
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
"@aws-sdk/client-secrets-manager": "^3.319.0",
|
||||||
|
"@casl/ability": "^6.5.0",
|
||||||
|
"@casl/mongoose": "^7.2.1",
|
||||||
"@godaddy/terminus": "^4.12.0",
|
"@godaddy/terminus": "^4.12.0",
|
||||||
"@node-saml/passport-saml": "^4.0.4",
|
"@node-saml/passport-saml": "^4.0.4",
|
||||||
"@octokit/rest": "^19.0.5",
|
"@octokit/rest": "^19.0.5",
|
||||||
@@ -50,7 +52,8 @@
|
|||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"utility-types": "^3.10.0",
|
"utility-types": "^3.10.0",
|
||||||
"winston": "^3.8.2",
|
"winston": "^3.8.2",
|
||||||
"winston-loki": "^6.0.7"
|
"winston-loki": "^6.0.6",
|
||||||
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"name": "infisical-api",
|
"name": "infisical-api",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import * as workspaceController from "./workspaceController";
|
|||||||
import * as secretScanningController from "./secretScanningController";
|
import * as secretScanningController from "./secretScanningController";
|
||||||
import * as webhookController from "./webhookController";
|
import * as webhookController from "./webhookController";
|
||||||
import * as secretImportController from "./secretImportController";
|
import * as secretImportController from "./secretImportController";
|
||||||
|
import * as roleController from "./roleController";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
authController,
|
authController,
|
||||||
@@ -35,5 +36,6 @@ export {
|
|||||||
workspaceController,
|
workspaceController,
|
||||||
secretScanningController,
|
secretScanningController,
|
||||||
webhookController,
|
webhookController,
|
||||||
|
roleController,
|
||||||
secretImportController
|
secretImportController
|
||||||
};
|
};
|
||||||
|
|||||||
159
backend/src/controllers/v1/roleController.ts
Normal file
159
backend/src/controllers/v1/roleController.ts
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import { Request, Response } from "express";
|
||||||
|
import { BadRequestError } from "../../utils/errors";
|
||||||
|
import Role from "../../models/role";
|
||||||
|
import {
|
||||||
|
GeneralPermissionActions,
|
||||||
|
OrgPermissionSubjects,
|
||||||
|
adminPermissions,
|
||||||
|
getUserOrgPermissions,
|
||||||
|
memberPermissions
|
||||||
|
} from "../../services/RoleService";
|
||||||
|
import { validateRequest } from "../../helpers/validation";
|
||||||
|
import {
|
||||||
|
CreateRoleSchema,
|
||||||
|
DeleteRoleSchema,
|
||||||
|
GetRoleSchema,
|
||||||
|
UpdateRoleSchema
|
||||||
|
} from "../../validation";
|
||||||
|
|
||||||
|
export const createRole = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
body: { workspaceId, name, description, slug, permissions, orgId }
|
||||||
|
} = await validateRequest(CreateRoleSchema, req);
|
||||||
|
|
||||||
|
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
|
||||||
|
if (orgPermission.cannot(GeneralPermissionActions.Create, OrgPermissionSubjects.Role)) {
|
||||||
|
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
||||||
|
|
||||||
|
const existingRole = await Role.findOne({ organization: orgId, workspace: workspaceId, slug });
|
||||||
|
if (existingRole) {
|
||||||
|
throw BadRequestError({ message: "Role already exist" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = new Role({
|
||||||
|
organization: orgId,
|
||||||
|
workspace: workspaceId,
|
||||||
|
isOrgRole,
|
||||||
|
name,
|
||||||
|
slug,
|
||||||
|
permissions,
|
||||||
|
description
|
||||||
|
});
|
||||||
|
await role.save();
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: "Successfully created role",
|
||||||
|
data: {
|
||||||
|
role
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateRole = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: { id },
|
||||||
|
body: { name, description, slug, permissions, workspaceId, orgId }
|
||||||
|
} = await validateRequest(UpdateRoleSchema, req);
|
||||||
|
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
|
||||||
|
|
||||||
|
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
|
||||||
|
if (orgPermission.cannot(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role)) {
|
||||||
|
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (slug) {
|
||||||
|
const existingRole = await Role.findOne({
|
||||||
|
organization: orgId,
|
||||||
|
slug,
|
||||||
|
isOrgRole,
|
||||||
|
workspace: workspaceId
|
||||||
|
});
|
||||||
|
if (existingRole && existingRole.id !== id) {
|
||||||
|
throw BadRequestError({ message: "Role already exist" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await Role.findByIdAndUpdate(
|
||||||
|
id,
|
||||||
|
{ name, description, slug, permissions },
|
||||||
|
{ returnDocument: "after" }
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
res.status(200).json({
|
||||||
|
message: "Successfully updated role",
|
||||||
|
data: {
|
||||||
|
role
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRole = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
params: { id }
|
||||||
|
} = await validateRequest(DeleteRoleSchema, req);
|
||||||
|
|
||||||
|
const role = await Role.findById(id);
|
||||||
|
if (!role) {
|
||||||
|
throw BadRequestError({ message: "Role not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const orgPermission = await getUserOrgPermissions(req.user.id, role.organization.toString());
|
||||||
|
if (orgPermission.cannot(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role)) {
|
||||||
|
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||||
|
}
|
||||||
|
await Role.findByIdAndDelete(role.id);
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: "Successfully deleted role",
|
||||||
|
data: {
|
||||||
|
role
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRoles = async (req: Request, res: Response) => {
|
||||||
|
const {
|
||||||
|
query: { workspaceId, orgId }
|
||||||
|
} = await validateRequest(GetRoleSchema, req);
|
||||||
|
const isOrgRole = !workspaceId;
|
||||||
|
|
||||||
|
const orgPermission = await getUserOrgPermissions(req.user.id, orgId);
|
||||||
|
if (orgPermission.cannot(GeneralPermissionActions.Read, OrgPermissionSubjects.Role)) {
|
||||||
|
throw BadRequestError({ message: "User doesn't have the permission." });
|
||||||
|
}
|
||||||
|
|
||||||
|
const roles = await Role.find({ organization: orgId, isOrgRole, workspace: workspaceId });
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
message: "Successfully fetched role list",
|
||||||
|
data: {
|
||||||
|
roles: [
|
||||||
|
{
|
||||||
|
name: "Owner",
|
||||||
|
slug: "owner",
|
||||||
|
description: "Complete administration access over the organization.",
|
||||||
|
permissions: adminPermissions.rules
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Admin",
|
||||||
|
slug: "admin",
|
||||||
|
description: "Complete administration access over the organization",
|
||||||
|
permissions: adminPermissions.rules
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Member",
|
||||||
|
slug: "member",
|
||||||
|
description: "Non-administrative role in an organization",
|
||||||
|
permissions: memberPermissions.rules
|
||||||
|
},
|
||||||
|
...roles
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
import { Request, Response } from "express";
|
import { Request, Response } from "express";
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import { Membership, MembershipOrg, ServiceAccount, Workspace } from "../../models";
|
||||||
Membership,
|
|
||||||
MembershipOrg,
|
|
||||||
ServiceAccount,
|
|
||||||
Workspace,
|
|
||||||
} from "../../models";
|
|
||||||
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
import { deleteMembershipOrg } from "../../helpers/membershipOrg";
|
||||||
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
import { updateSubscriptionOrgQuantity } from "../../helpers/organization";
|
||||||
|
import Role from "../../models/role";
|
||||||
|
import { BadRequestError } from "../../utils/errors";
|
||||||
|
import { CUSTOM } from "../../variables";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return memberships for organization with id [organizationId]
|
* Return memberships for organization with id [organizationId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getOrganizationMemberships = async (req: Request, res: Response) => {
|
export const getOrganizationMemberships = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Return organization memberships'
|
#swagger.summary = 'Return organization memberships'
|
||||||
#swagger.description = 'Return organization memberships'
|
#swagger.description = 'Return organization memberships'
|
||||||
|
|
||||||
@@ -48,24 +46,24 @@ export const getOrganizationMemberships = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const { organizationId } = req.params;
|
const { organizationId } = req.params;
|
||||||
|
|
||||||
const memberships = await MembershipOrg.find({
|
const memberships = await MembershipOrg.find({
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
}).populate("user", "+publicKey");
|
}).populate("user", "+publicKey");
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
memberships,
|
memberships
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update role of membership with id [membershipId] to role [role]
|
* Update role of membership with id [membershipId] to role [role]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const updateOrganizationMembership = async (req: Request, res: Response) => {
|
export const updateOrganizationMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Update organization membership'
|
#swagger.summary = 'Update organization membership'
|
||||||
#swagger.description = 'Update organization membership'
|
#swagger.description = 'Update organization membership'
|
||||||
|
|
||||||
@@ -118,31 +116,46 @@ export const updateOrganizationMembership = async (req: Request, res: Response)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const { membershipId } = req.params;
|
const { membershipId } = req.params;
|
||||||
const { role } = req.body;
|
const { role } = req.body;
|
||||||
|
|
||||||
const membership = await MembershipOrg.findByIdAndUpdate(
|
const isCustomRole = !["admin", "member", "owner"].includes(role);
|
||||||
membershipId,
|
if (isCustomRole) {
|
||||||
{
|
const orgRole = await Role.findOne({ slug: role, isOrgRole: true });
|
||||||
role,
|
if (!orgRole) throw BadRequestError({ message: "Role not found" });
|
||||||
}, {
|
|
||||||
new: true,
|
const membership = await MembershipOrg.findByIdAndUpdate(membershipId, {
|
||||||
}
|
role: CUSTOM,
|
||||||
);
|
customRole: orgRole
|
||||||
|
|
||||||
return res.status(200).send({
|
|
||||||
membership,
|
|
||||||
});
|
});
|
||||||
}
|
return res.status(200).send({
|
||||||
|
membership
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const membership = await MembershipOrg.findByIdAndUpdate(
|
||||||
|
membershipId,
|
||||||
|
{
|
||||||
|
role
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
membership
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete organization membership with id [membershipId]
|
* Delete organization membership with id [membershipId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
|
export const deleteOrganizationMembership = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Delete organization membership'
|
#swagger.summary = 'Delete organization membership'
|
||||||
#swagger.description = 'Delete organization membership'
|
#swagger.description = 'Delete organization membership'
|
||||||
|
|
||||||
@@ -178,30 +191,30 @@ export const deleteOrganizationMembership = async (req: Request, res: Response)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const { membershipId } = req.params;
|
const { membershipId } = req.params;
|
||||||
|
|
||||||
// delete organization membership
|
|
||||||
const membership = await deleteMembershipOrg({
|
|
||||||
membershipOrgId: membershipId,
|
|
||||||
});
|
|
||||||
|
|
||||||
await updateSubscriptionOrgQuantity({
|
// delete organization membership
|
||||||
organizationId: membership.organization.toString(),
|
const membership = await deleteMembershipOrg({
|
||||||
});
|
membershipOrgId: membershipId
|
||||||
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
await updateSubscriptionOrgQuantity({
|
||||||
membership,
|
organizationId: membership.organization.toString()
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
return res.status(200).send({
|
||||||
|
membership
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return workspaces for organization with id [organizationId] that user has
|
* Return workspaces for organization with id [organizationId] that user has
|
||||||
* access to
|
* access to
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
|
export const getOrganizationWorkspaces = async (req: Request, res: Response) => {
|
||||||
/*
|
/*
|
||||||
#swagger.summary = 'Return projects in organization that user is part of'
|
#swagger.summary = 'Return projects in organization that user is part of'
|
||||||
#swagger.description = 'Return projects in organization that user is part of'
|
#swagger.description = 'Return projects in organization that user is part of'
|
||||||
|
|
||||||
@@ -234,45 +247,45 @@ export const getOrganizationWorkspaces = async (req: Request, res: Response) =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const { organizationId } = req.params;
|
const { organizationId } = req.params;
|
||||||
|
|
||||||
const workspacesSet = new Set(
|
const workspacesSet = new Set(
|
||||||
(
|
(
|
||||||
await Workspace.find(
|
await Workspace.find(
|
||||||
{
|
{
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
},
|
},
|
||||||
"_id"
|
"_id"
|
||||||
)
|
)
|
||||||
).map((w) => w._id.toString())
|
).map((w) => w._id.toString())
|
||||||
);
|
);
|
||||||
|
|
||||||
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
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return service accounts for organization with id [organizationId]
|
* Return service accounts for organization with id [organizationId]
|
||||||
* @param req
|
* @param req
|
||||||
* @param res
|
* @param res
|
||||||
*/
|
*/
|
||||||
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
|
export const getOrganizationServiceAccounts = async (req: Request, res: Response) => {
|
||||||
const { organizationId } = req.params;
|
const { organizationId } = req.params;
|
||||||
|
|
||||||
const serviceAccounts = await ServiceAccount.find({
|
const serviceAccounts = await ServiceAccount.find({
|
||||||
organization: new Types.ObjectId(organizationId),
|
organization: new Types.ObjectId(organizationId)
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(200).send({
|
return res.status(200).send({
|
||||||
serviceAccounts,
|
serviceAccounts
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -11,29 +11,29 @@ import { BadRequestError, MembershipNotFoundError } from "../utils/errors";
|
|||||||
* @returns {Membership} membership - membership of user with id [userId] for workspace with id [workspaceId]
|
* @returns {Membership} membership - membership of user with id [userId] for workspace with id [workspaceId]
|
||||||
*/
|
*/
|
||||||
export const validateMembership = async ({
|
export const validateMembership = async ({
|
||||||
userId,
|
userId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
acceptedRoles,
|
acceptedRoles
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId | string;
|
userId: Types.ObjectId | string;
|
||||||
workspaceId: Types.ObjectId | string;
|
workspaceId: Types.ObjectId | string;
|
||||||
acceptedRoles?: Array<"admin" | "member">;
|
acceptedRoles?: Array<"admin" | "member" | "custom">;
|
||||||
}) => {
|
}) => {
|
||||||
const membership = await Membership.findOne({
|
const membership = await Membership.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId
|
||||||
}).populate("workspace");
|
}).populate("workspace");
|
||||||
|
|
||||||
if (!membership) {
|
if (!membership) {
|
||||||
throw MembershipNotFoundError({
|
throw MembershipNotFoundError({
|
||||||
message: "Failed to find workspace membership",
|
message: "Failed to find workspace membership"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedRoles) {
|
if (acceptedRoles) {
|
||||||
if (!acceptedRoles.includes(membership.role)) {
|
if (!acceptedRoles.includes(membership.role)) {
|
||||||
throw BadRequestError({
|
throw BadRequestError({
|
||||||
message: "Failed authorization for membership role",
|
message: "Failed authorization for membership role"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@ export const validateMembership = async ({
|
|||||||
* @return {Object} membership - membership
|
* @return {Object} membership - membership
|
||||||
*/
|
*/
|
||||||
export const findMembership = async (queryObj: any) => {
|
export const findMembership = async (queryObj: any) => {
|
||||||
const membership = await Membership.findOne(queryObj);
|
const membership = await Membership.findOne(queryObj);
|
||||||
return membership;
|
return membership;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -60,9 +60,9 @@ export const findMembership = async (queryObj: any) => {
|
|||||||
* @param {String[]} obj.roles - roles of users.
|
* @param {String[]} obj.roles - roles of users.
|
||||||
*/
|
*/
|
||||||
export const addMemberships = async ({
|
export const addMemberships = async ({
|
||||||
userIds,
|
userIds,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
roles,
|
roles
|
||||||
}: {
|
}: {
|
||||||
userIds: string[];
|
userIds: string[];
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@@ -74,15 +74,15 @@ export const addMemberships = async ({
|
|||||||
filter: {
|
filter: {
|
||||||
user: userId,
|
user: userId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
role: roles[idx],
|
role: roles[idx]
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
user: userId,
|
user: userId,
|
||||||
workspace: workspaceId,
|
workspace: workspaceId,
|
||||||
role: roles[idx],
|
role: roles[idx]
|
||||||
},
|
},
|
||||||
upsert: true,
|
upsert: true
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
await Membership.bulkWrite(operations as any);
|
await Membership.bulkWrite(operations as any);
|
||||||
@@ -94,8 +94,8 @@ export const addMemberships = async ({
|
|||||||
* @param {String} obj.membershipId - id of membership to delete
|
* @param {String} obj.membershipId - id of membership to delete
|
||||||
*/
|
*/
|
||||||
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
export const deleteMembership = async ({ membershipId }: { membershipId: string }) => {
|
||||||
const deletedMembership = await Membership.findOneAndDelete({
|
const deletedMembership = await Membership.findOneAndDelete({
|
||||||
_id: membershipId,
|
_id: membershipId
|
||||||
});
|
});
|
||||||
|
|
||||||
// delete keys associated with the membership
|
// delete keys associated with the membership
|
||||||
@@ -103,9 +103,9 @@ export const deleteMembership = async ({ membershipId }: { membershipId: string
|
|||||||
// case: membership had a registered user
|
// case: membership had a registered user
|
||||||
await Key.deleteMany({
|
await Key.deleteMany({
|
||||||
receiver: deletedMembership.user,
|
receiver: deletedMembership.user,
|
||||||
workspace: deletedMembership.workspace,
|
workspace: deletedMembership.workspace
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return deletedMembership;
|
return deletedMembership;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,6 @@
|
|||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import {
|
import { Key, Membership, MembershipOrg, Workspace } from "../models";
|
||||||
Key,
|
import { MembershipOrgNotFoundError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
Membership,
|
|
||||||
MembershipOrg,
|
|
||||||
Workspace,
|
|
||||||
} from "../models";
|
|
||||||
import {
|
|
||||||
MembershipOrgNotFoundError,
|
|
||||||
UnauthorizedRequestError,
|
|
||||||
} from "../utils/errors";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
* Validate that user with id [userId] is a member of organization with id [organizationId]
|
||||||
@@ -19,39 +11,43 @@ import {
|
|||||||
* @param {String[]} obj.acceptedRoles
|
* @param {String[]} obj.acceptedRoles
|
||||||
*/
|
*/
|
||||||
export const validateMembershipOrg = async ({
|
export const validateMembershipOrg = async ({
|
||||||
userId,
|
userId,
|
||||||
organizationId,
|
organizationId,
|
||||||
acceptedRoles,
|
acceptedRoles,
|
||||||
acceptedStatuses,
|
acceptedStatuses
|
||||||
}: {
|
}: {
|
||||||
userId: Types.ObjectId;
|
userId: Types.ObjectId;
|
||||||
organizationId: Types.ObjectId;
|
organizationId: Types.ObjectId;
|
||||||
acceptedRoles?: Array<"owner" | "admin" | "member">;
|
acceptedRoles?: Array<"owner" | "admin" | "member" | "custom">;
|
||||||
acceptedStatuses?: Array<"invited" | "accepted">;
|
acceptedStatuses?: Array<"invited" | "accepted">;
|
||||||
}) => {
|
}) => {
|
||||||
const membershipOrg = await MembershipOrg.findOne({
|
const membershipOrg = await MembershipOrg.findOne({
|
||||||
user: userId,
|
user: userId,
|
||||||
organization: organizationId,
|
organization: organizationId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!membershipOrg) {
|
if (!membershipOrg) {
|
||||||
throw MembershipOrgNotFoundError({ message: "Failed to find organization membership" });
|
throw MembershipOrgNotFoundError({ message: "Failed to find organization membership" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (acceptedRoles) {
|
if (acceptedRoles) {
|
||||||
if (!acceptedRoles.includes(membershipOrg.role)) {
|
if (!acceptedRoles.includes(membershipOrg.role)) {
|
||||||
throw UnauthorizedRequestError({ message: "Failed to validate organization membership role" });
|
throw UnauthorizedRequestError({
|
||||||
}
|
message: "Failed to validate organization membership role"
|
||||||
}
|
});
|
||||||
|
}
|
||||||
if (acceptedStatuses) {
|
}
|
||||||
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
|
||||||
throw UnauthorizedRequestError({ message: "Failed to validate organization membership status" });
|
if (acceptedStatuses) {
|
||||||
}
|
if (!acceptedStatuses.includes(membershipOrg.status)) {
|
||||||
}
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to validate organization membership status"
|
||||||
return membershipOrg;
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return membershipOrg;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return organization membership matching criteria specified in
|
* Return organization membership matching criteria specified in
|
||||||
@@ -60,8 +56,8 @@ export const validateMembershipOrg = async ({
|
|||||||
* @return {Object} membershipOrg - membership
|
* @return {Object} membershipOrg - membership
|
||||||
*/
|
*/
|
||||||
export const findMembershipOrg = (queryObj: any) => {
|
export const findMembershipOrg = (queryObj: any) => {
|
||||||
const membershipOrg = MembershipOrg.findOne(queryObj);
|
const membershipOrg = MembershipOrg.findOne(queryObj);
|
||||||
return membershipOrg;
|
return membershipOrg;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -73,15 +69,15 @@ export const findMembershipOrg = (queryObj: any) => {
|
|||||||
* @param {String[]} obj.roles - roles of users.
|
* @param {String[]} obj.roles - roles of users.
|
||||||
*/
|
*/
|
||||||
export const addMembershipsOrg = async ({
|
export const addMembershipsOrg = async ({
|
||||||
userIds,
|
userIds,
|
||||||
organizationId,
|
organizationId,
|
||||||
roles,
|
roles,
|
||||||
statuses,
|
statuses
|
||||||
}: {
|
}: {
|
||||||
userIds: string[];
|
userIds: string[];
|
||||||
organizationId: string;
|
organizationId: string;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
statuses: string[];
|
statuses: string[];
|
||||||
}) => {
|
}) => {
|
||||||
const operations = userIds.map((userId, idx) => {
|
const operations = userIds.map((userId, idx) => {
|
||||||
return {
|
return {
|
||||||
@@ -90,16 +86,16 @@ export const addMembershipsOrg = async ({
|
|||||||
user: userId,
|
user: userId,
|
||||||
organization: organizationId,
|
organization: organizationId,
|
||||||
role: roles[idx],
|
role: roles[idx],
|
||||||
status: statuses[idx],
|
status: statuses[idx]
|
||||||
},
|
},
|
||||||
update: {
|
update: {
|
||||||
user: userId,
|
user: userId,
|
||||||
organization: organizationId,
|
organization: organizationId,
|
||||||
role: roles[idx],
|
role: roles[idx],
|
||||||
status: statuses[idx],
|
status: statuses[idx]
|
||||||
},
|
},
|
||||||
upsert: true,
|
upsert: true
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,13 +107,9 @@ export const addMembershipsOrg = async ({
|
|||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.membershipOrgId - id of organization membership to delete
|
* @param {String} obj.membershipOrgId - id of organization membership to delete
|
||||||
*/
|
*/
|
||||||
export const deleteMembershipOrg = async ({
|
export const deleteMembershipOrg = async ({ membershipOrgId }: { membershipOrgId: string }) => {
|
||||||
membershipOrgId,
|
|
||||||
}: {
|
|
||||||
membershipOrgId: string;
|
|
||||||
}) => {
|
|
||||||
const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
|
const deletedMembershipOrg = await MembershipOrg.findOneAndDelete({
|
||||||
_id: membershipOrgId,
|
_id: membershipOrgId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!deletedMembershipOrg) throw new Error("Failed to delete organization membership");
|
if (!deletedMembershipOrg) throw new Error("Failed to delete organization membership");
|
||||||
@@ -128,24 +120,24 @@ export const deleteMembershipOrg = async ({
|
|||||||
|
|
||||||
const workspaces = (
|
const workspaces = (
|
||||||
await Workspace.find({
|
await Workspace.find({
|
||||||
organization: deletedMembershipOrg.organization,
|
organization: deletedMembershipOrg.organization
|
||||||
})
|
})
|
||||||
).map((w) => w._id.toString());
|
).map((w) => w._id.toString());
|
||||||
|
|
||||||
await Membership.deleteMany({
|
await Membership.deleteMany({
|
||||||
user: deletedMembershipOrg.user,
|
user: deletedMembershipOrg.user,
|
||||||
workspace: {
|
workspace: {
|
||||||
$in: workspaces,
|
$in: workspaces
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await Key.deleteMany({
|
await Key.deleteMany({
|
||||||
receiver: deletedMembershipOrg.user,
|
receiver: deletedMembershipOrg.user,
|
||||||
workspace: {
|
workspace: {
|
||||||
$in: workspaces,
|
$in: workspaces
|
||||||
},
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return deletedMembershipOrg;
|
return deletedMembershipOrg;
|
||||||
};
|
};
|
||||||
|
|||||||
17
backend/src/helpers/validation.ts
Normal file
17
backend/src/helpers/validation.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { Request } from "express";
|
||||||
|
import { AnyZodObject, ZodError, z } from "zod";
|
||||||
|
import { BadRequestError } from "../utils/errors";
|
||||||
|
|
||||||
|
export async function validateRequest<T extends AnyZodObject>(
|
||||||
|
schema: T,
|
||||||
|
req: Request
|
||||||
|
): Promise<z.infer<T>> {
|
||||||
|
try {
|
||||||
|
return schema.parseAsync(req);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ZodError) {
|
||||||
|
throw BadRequestError({ message: error.message });
|
||||||
|
}
|
||||||
|
return BadRequestError({ message: JSON.stringify(error) });
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
membership as v1MembershipRouter,
|
membership as v1MembershipRouter,
|
||||||
organization as v1OrganizationRouter,
|
organization as v1OrganizationRouter,
|
||||||
password as v1PasswordRouter,
|
password as v1PasswordRouter,
|
||||||
|
role as v1RoleRouter,
|
||||||
secretImport as v1SecretImportRouter,
|
secretImport as v1SecretImportRouter,
|
||||||
secret as v1SecretRouter,
|
secret as v1SecretRouter,
|
||||||
secretsFolder as v1SecretsFolder,
|
secretsFolder as v1SecretsFolder,
|
||||||
@@ -173,6 +174,7 @@ const main = async () => {
|
|||||||
app.use("/api/v1/secret-scanning", v1SecretScanningRouter);
|
app.use("/api/v1/secret-scanning", v1SecretScanningRouter);
|
||||||
app.use("/api/v1/webhooks", v1WebhooksRouter);
|
app.use("/api/v1/webhooks", v1WebhooksRouter);
|
||||||
app.use("/api/v1/secret-imports", v1SecretImportRouter);
|
app.use("/api/v1/secret-imports", v1SecretImportRouter);
|
||||||
|
app.use("/api/v1/roles", v1RoleRouter);
|
||||||
|
|
||||||
// v2 routes (improvements)
|
// v2 routes (improvements)
|
||||||
app.use("/api/v2/signup", v2SignupRouter);
|
app.use("/api/v2/signup", v2SignupRouter);
|
||||||
|
|||||||
@@ -1,55 +1,60 @@
|
|||||||
import { Schema, Types, model } from "mongoose";
|
import { Schema, Types, model } from "mongoose";
|
||||||
import { ADMIN, MEMBER } from "../variables";
|
import { ADMIN, CUSTOM, MEMBER } from "../variables";
|
||||||
|
|
||||||
export interface IMembershipPermission {
|
export interface IMembershipPermission {
|
||||||
environmentSlug: string,
|
environmentSlug: string;
|
||||||
ability: string
|
ability: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IMembership {
|
export interface IMembership {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
inviteEmail?: string;
|
inviteEmail?: string;
|
||||||
workspace: Types.ObjectId;
|
workspace: Types.ObjectId;
|
||||||
role: "admin" | "member";
|
role: "admin" | "member" | "custom";
|
||||||
deniedPermissions: IMembershipPermission[]
|
customRole: Types.ObjectId;
|
||||||
|
deniedPermissions: IMembershipPermission[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const membershipSchema = new Schema<IMembership>(
|
const membershipSchema = new Schema<IMembership>(
|
||||||
{
|
{
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "User",
|
ref: "User"
|
||||||
},
|
},
|
||||||
inviteEmail: {
|
inviteEmail: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
workspace: {
|
workspace: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "Workspace",
|
ref: "Workspace",
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
deniedPermissions: {
|
deniedPermissions: {
|
||||||
type: [
|
type: [
|
||||||
{
|
{
|
||||||
environmentSlug: String,
|
environmentSlug: String,
|
||||||
ability: {
|
ability: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: ["read", "write"],
|
enum: ["read", "write"]
|
||||||
},
|
}
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
default: [],
|
default: []
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [ADMIN, MEMBER],
|
enum: [ADMIN, MEMBER, CUSTOM],
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
},
|
customRole: {
|
||||||
{
|
type: Schema.Types.ObjectId,
|
||||||
timestamps: true,
|
ref: "Role"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const Membership = model<IMembership>("Membership", membershipSchema);
|
export const Membership = model<IMembership>("Membership", membershipSchema);
|
||||||
@@ -1,45 +1,49 @@
|
|||||||
import { Document, Schema, Types, model } from "mongoose";
|
import { Document, Schema, Types, model } from "mongoose";
|
||||||
import { ACCEPTED, ADMIN, INVITED, MEMBER, OWNER } from "../variables";
|
import { ACCEPTED, ADMIN, CUSTOM, INVITED, MEMBER, OWNER } from "../variables";
|
||||||
|
|
||||||
export interface IMembershipOrg extends Document {
|
export interface IMembershipOrg extends Document {
|
||||||
_id: Types.ObjectId;
|
_id: Types.ObjectId;
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
inviteEmail: string;
|
inviteEmail: string;
|
||||||
organization: Types.ObjectId;
|
organization: Types.ObjectId;
|
||||||
role: "owner" | "admin" | "member";
|
role: "owner" | "admin" | "member" | "custom";
|
||||||
status: "invited" | "accepted";
|
customRole: Types.ObjectId;
|
||||||
|
status: "invited" | "accepted";
|
||||||
}
|
}
|
||||||
|
|
||||||
const membershipOrgSchema = new Schema(
|
const membershipOrgSchema = new Schema(
|
||||||
{
|
{
|
||||||
user: {
|
user: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "User",
|
ref: "User"
|
||||||
},
|
},
|
||||||
inviteEmail: {
|
inviteEmail: {
|
||||||
type: String,
|
type: String
|
||||||
},
|
},
|
||||||
organization: {
|
organization: {
|
||||||
type: Schema.Types.ObjectId,
|
type: Schema.Types.ObjectId,
|
||||||
ref: "Organization",
|
ref: "Organization"
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [OWNER, ADMIN, MEMBER],
|
enum: [OWNER, ADMIN, MEMBER, CUSTOM],
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
enum: [INVITED, ACCEPTED],
|
enum: [INVITED, ACCEPTED],
|
||||||
required: true,
|
required: true
|
||||||
},
|
},
|
||||||
},
|
customRole: {
|
||||||
{
|
type: Schema.Types.ObjectId,
|
||||||
timestamps: true,
|
ref: "Role"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
export const MembershipOrg = model<IMembershipOrg>(
|
const MembershipOrg = model<IMembershipOrg>("MembershipOrg", membershipOrgSchema);
|
||||||
"MembershipOrg",
|
|
||||||
membershipOrgSchema
|
export default MembershipOrg;
|
||||||
);
|
|
||||||
|
|||||||
53
backend/src/models/role.ts
Normal file
53
backend/src/models/role.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { Schema, Types, model } from "mongoose";
|
||||||
|
|
||||||
|
export interface IRole {
|
||||||
|
_id: Types.ObjectId;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
slug: string;
|
||||||
|
permissions: Array<unknown>;
|
||||||
|
workspace: Types.ObjectId;
|
||||||
|
organization: Types.ObjectId;
|
||||||
|
isOrgRole: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleSchema = new Schema<IRole>(
|
||||||
|
{
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
organization: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Organization",
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
workspace: {
|
||||||
|
type: Schema.Types.ObjectId,
|
||||||
|
ref: "Workspace"
|
||||||
|
},
|
||||||
|
isOrgRole: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
select: false
|
||||||
|
},
|
||||||
|
description: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
slug: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: Array,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Role = model<IRole>("Role", roleSchema);
|
||||||
|
|
||||||
|
export default Role;
|
||||||
@@ -17,6 +17,7 @@ import integrationAuth from "./integrationAuth";
|
|||||||
import secretsFolder from "./secretsFolder";
|
import secretsFolder from "./secretsFolder";
|
||||||
import webhooks from "./webhook";
|
import webhooks from "./webhook";
|
||||||
import secretImport from "./secretImport";
|
import secretImport from "./secretImport";
|
||||||
|
import role from "./role";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
signup,
|
signup,
|
||||||
@@ -37,5 +38,6 @@ export {
|
|||||||
integrationAuth,
|
integrationAuth,
|
||||||
secretsFolder,
|
secretsFolder,
|
||||||
webhooks,
|
webhooks,
|
||||||
secretImport
|
secretImport,
|
||||||
|
role
|
||||||
};
|
};
|
||||||
|
|||||||
20
backend/src/routes/v1/role.ts
Normal file
20
backend/src/routes/v1/role.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import express from "express";
|
||||||
|
import { roleController } from "../../controllers/v1";
|
||||||
|
import { requireAuth } from "../../middleware";
|
||||||
|
import { AuthMode } from "../../variables";
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
router.post("/", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.createRole);
|
||||||
|
|
||||||
|
router.patch("/:id", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.updateRole);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
"/:id",
|
||||||
|
requireAuth({ acceptedAuthModes: [AuthMode.JWT] }),
|
||||||
|
roleController.deleteRole
|
||||||
|
);
|
||||||
|
|
||||||
|
router.get("/", requireAuth({ acceptedAuthModes: [AuthMode.JWT] }), roleController.getRoles);
|
||||||
|
|
||||||
|
export default router;
|
||||||
@@ -1,88 +1,82 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
import {
|
import {
|
||||||
requireAuth,
|
requireAuth,
|
||||||
requireMembershipOrgAuth,
|
requireMembershipOrgAuth,
|
||||||
requireOrganizationAuth,
|
requireOrganizationAuth,
|
||||||
validateRequest,
|
validateRequest
|
||||||
} from "../../middleware";
|
} from "../../middleware";
|
||||||
import { body, param } from "express-validator";
|
import { body, param } from "express-validator";
|
||||||
import {
|
import { ACCEPTED, ADMIN, AuthMode, MEMBER, OWNER } from "../../variables";
|
||||||
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(),
|
param("organizationId").exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
acceptedRoles: [OWNER, ADMIN, MEMBER],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.getOrganizationMemberships
|
organizationsController.getOrganizationMemberships
|
||||||
);
|
);
|
||||||
|
|
||||||
router.patch(
|
router.patch(
|
||||||
"/:organizationId/memberships/:membershipId",
|
"/:organizationId/memberships/:membershipId",
|
||||||
param("organizationId").exists().trim(),
|
param("organizationId").exists().trim(),
|
||||||
param("membershipId").exists().trim(),
|
param("membershipId").exists().trim(),
|
||||||
body("role").exists().isString().trim().isIn([OWNER, ADMIN, MEMBER]),
|
body("role").exists().isString().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
requireMembershipOrgAuth({
|
requireMembershipOrgAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.updateOrganizationMembership
|
organizationsController.updateOrganizationMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:organizationId/memberships/:membershipId",
|
"/:organizationId/memberships/:membershipId",
|
||||||
param("organizationId").exists().trim(),
|
param("organizationId").exists().trim(),
|
||||||
param("membershipId").exists().trim(),
|
param("membershipId").exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
requireMembershipOrgAuth({
|
requireMembershipOrgAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.deleteOrganizationMembership
|
organizationsController.deleteOrganizationMembership
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
"/:organizationId/workspaces",
|
"/:organizationId/workspaces",
|
||||||
param("organizationId").exists().trim(),
|
param("organizationId").exists().trim(),
|
||||||
validateRequest,
|
validateRequest,
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY],
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY]
|
||||||
}),
|
}),
|
||||||
requireOrganizationAuth({
|
requireOrganizationAuth({
|
||||||
acceptedRoles: [OWNER, ADMIN],
|
acceptedRoles: [OWNER, ADMIN],
|
||||||
acceptedStatuses: [ACCEPTED],
|
acceptedStatuses: [ACCEPTED]
|
||||||
}),
|
}),
|
||||||
organizationsController.getOrganizationWorkspaces
|
organizationsController.getOrganizationWorkspaces
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get( // TODO endpoint: deprecate service accounts
|
router.get( // TODO endpoint: deprecate service accounts
|
||||||
@@ -99,4 +93,4 @@ router.get( // TODO endpoint: deprecate service accounts
|
|||||||
organizationsController.getOrganizationServiceAccounts
|
organizationsController.getOrganizationServiceAccounts
|
||||||
);
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
74
backend/src/services/RoleService.ts
Normal file
74
backend/src/services/RoleService.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { AbilityBuilder, MongoAbility, RawRuleOf, createMongoAbility } from "@casl/ability";
|
||||||
|
import { MembershipOrg } from "../models";
|
||||||
|
import { IRole } from "../models/role";
|
||||||
|
import { BadRequestError, UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
|
||||||
|
export enum GeneralPermissionActions {
|
||||||
|
Read = "read",
|
||||||
|
Create = "create",
|
||||||
|
Edit = "edit",
|
||||||
|
Delete = "delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum OrgPermissionSubjects {
|
||||||
|
Workspace = "workspace",
|
||||||
|
Role = "role"
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrgPermissionSet =
|
||||||
|
| [GeneralPermissionActions, OrgPermissionSubjects.Workspace]
|
||||||
|
| [GeneralPermissionActions, OrgPermissionSubjects.Role];
|
||||||
|
|
||||||
|
const buildAdminPermission = () => {
|
||||||
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
|
// ws permissions
|
||||||
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||||
|
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Workspace);
|
||||||
|
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Workspace);
|
||||||
|
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.Workspace);
|
||||||
|
// role permission
|
||||||
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
|
can(GeneralPermissionActions.Create, OrgPermissionSubjects.Role);
|
||||||
|
can(GeneralPermissionActions.Edit, OrgPermissionSubjects.Role);
|
||||||
|
can(GeneralPermissionActions.Delete, OrgPermissionSubjects.Role);
|
||||||
|
|
||||||
|
return build();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const adminPermissions = buildAdminPermission();
|
||||||
|
|
||||||
|
const buildMemberPermission = () => {
|
||||||
|
const { can, build } = new AbilityBuilder<MongoAbility<OrgPermissionSet>>(createMongoAbility);
|
||||||
|
|
||||||
|
// ws permissions
|
||||||
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Workspace);
|
||||||
|
can(GeneralPermissionActions.Read, OrgPermissionSubjects.Role);
|
||||||
|
|
||||||
|
return build();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const memberPermissions = buildMemberPermission();
|
||||||
|
|
||||||
|
export const getUserOrgPermissions = async (userId: string, orgId: string) => {
|
||||||
|
// TODO(akhilmhdh): speed this up by pulling from cache later
|
||||||
|
const orgMembership = await MembershipOrg.findOne({ user: userId, organization: orgId })
|
||||||
|
.populate<{ customRole: IRole & { permissions: RawRuleOf<MongoAbility<OrgPermissionSet>>[] } }>(
|
||||||
|
"customRole"
|
||||||
|
)
|
||||||
|
.exec();
|
||||||
|
|
||||||
|
if (!orgMembership || (orgMembership.role === "custom" && !orgMembership.customRole)) {
|
||||||
|
throw UnauthorizedRequestError({ message: "User doesn't belong to organization" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orgMembership.role === "admin" || orgMembership.role === "owner") return adminPermissions;
|
||||||
|
|
||||||
|
if (orgMembership.role === "member") return memberPermissions;
|
||||||
|
|
||||||
|
if (orgMembership.role === "custom") {
|
||||||
|
const permission = createMongoAbility<OrgPermissionSet>(orgMembership.customRole.permissions);
|
||||||
|
return permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw BadRequestError({ message: "User role not found" });
|
||||||
|
};
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
export * from "./user";
|
export * from "./user";
|
||||||
|
export * from "./role";
|
||||||
export * from "./workspace";
|
export * from "./workspace";
|
||||||
export * from "./bot";
|
export * from "./bot";
|
||||||
export * from "./integration";
|
export * from "./integration";
|
||||||
@@ -8,4 +9,4 @@ export * from "./membershipOrg";
|
|||||||
export * from "./organization";
|
export * from "./organization";
|
||||||
export * from "./secrets";
|
export * from "./secrets";
|
||||||
export * from "./serviceAccount";
|
export * from "./serviceAccount";
|
||||||
export * from "./serviceTokenData";
|
export * from "./serviceTokenData";
|
||||||
|
|||||||
52
backend/src/validation/role.ts
Normal file
52
backend/src/validation/role.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const CreateRoleSchema = z.object({
|
||||||
|
body: z.object({
|
||||||
|
slug: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
workspaceId: z.string().optional(),
|
||||||
|
orgId: z.string(),
|
||||||
|
permissions: z
|
||||||
|
.object({
|
||||||
|
subject: z.string(),
|
||||||
|
action: z.string(),
|
||||||
|
condition: z.record(z.union([z.string(), z.number()]))
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateRoleSchema = z.object({
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
}),
|
||||||
|
body: z.object({
|
||||||
|
slug: z.string().optional(),
|
||||||
|
name: z.string().optional(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
workspaceId: z.string().optional(),
|
||||||
|
orgId: z.string(),
|
||||||
|
permissions: z
|
||||||
|
.object({
|
||||||
|
subject: z.string(),
|
||||||
|
action: z.string(),
|
||||||
|
condition: z.record(z.union([z.string(), z.number()]))
|
||||||
|
})
|
||||||
|
.array()
|
||||||
|
.optional()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const DeleteRoleSchema = z.object({
|
||||||
|
params: z.object({
|
||||||
|
id: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const GetRoleSchema = z.object({
|
||||||
|
query: z.object({
|
||||||
|
workspaceId: z.string().optional(),
|
||||||
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
export const OWNER = "owner";
|
export const OWNER = "owner";
|
||||||
export const ADMIN = "admin";
|
export const ADMIN = "admin";
|
||||||
export const MEMBER = "member";
|
export const MEMBER = "member";
|
||||||
|
export const CUSTOM = "custom";
|
||||||
|
|
||||||
// membership statuses
|
// membership statuses
|
||||||
export const INVITED = "invited";
|
export const INVITED = "invited";
|
||||||
|
|
||||||
// -- organization
|
// -- organization
|
||||||
export const ACCEPTED = "accepted";
|
export const ACCEPTED = "accepted";
|
||||||
|
|||||||
Reference in New Issue
Block a user