优化前后端权限一致性

This commit is contained in:
Cheng Zhou
2026-05-26 14:53:17 +08:00
parent 41bd423be0
commit c909fc9387
15 changed files with 184 additions and 26 deletions
+6
View File
@@ -13,10 +13,16 @@ describe("permission project role model", () => {
expect(source).toContain("study.currentStudyRole");
expect(source).toContain("study.currentPermissions");
expect(source).toContain('"faq.edit": "faq:update"');
expect(source).toContain('"fees.contract.create": "fees_contracts:create"');
expect(source).toContain('"fees.contract.update": "fees_contracts:update"');
expect(source).toContain('"fees.contract.delete": "fees_contracts:delete"');
expect(source).toContain('"documents.create": "documents:create"');
expect(source).toContain('"documents.delete": "documents:delete"');
expect(source).toContain("isApiPermissionAllowed");
expect(source).toContain("projectPermissions.value?.[projectRole.value]?.[operationKey]");
expect(source).not.toContain("projectPermissions.value?.roles");
expect(source).not.toContain('"faq.edit": "etmf');
expect(source).not.toContain('"ADMIN", "PM"');
expect(source).not.toContain('"fees.contract.write": "fees_contracts:create"');
});
});
+15 -3
View File
@@ -18,7 +18,11 @@ const PERMISSIONS: Record<string, string[]> = {
"project.members.manage": ["ADMIN"],
"site.manage": ["ADMIN"],
"site.cra.bind": ["ADMIN"],
"fees.contract.write": ["ADMIN"],
"fees.contract.create": ["ADMIN"],
"fees.contract.update": ["ADMIN"],
"fees.contract.delete": ["ADMIN"],
"documents.create": ["ADMIN"],
"documents.delete": ["ADMIN"],
};
const REASONS: Record<string, string> = {
@@ -32,7 +36,11 @@ const REASONS: Record<string, string> = {
"project.members.manage": TEXT.modules.permissions.projectMembersManage,
"site.manage": TEXT.modules.permissions.siteManage,
"site.cra.bind": TEXT.modules.permissions.siteCraBind,
"fees.contract.write": TEXT.modules.permissions.feeContractsWrite,
"fees.contract.create": TEXT.modules.permissions.feeContractsWrite,
"fees.contract.update": TEXT.modules.permissions.feeContractsWrite,
"fees.contract.delete": TEXT.modules.permissions.feeContractsWrite,
"documents.create": TEXT.modules.permissions.default,
"documents.delete": TEXT.modules.permissions.default,
};
export const usePermission = () => {
@@ -63,7 +71,11 @@ export const usePermission = () => {
"project.members.manage": "project_members:update",
"site.manage": "sites:update",
"site.cra.bind": "sites:update",
"fees.contract.write": "fees_contracts:create",
"fees.contract.create": "fees_contracts:create",
"fees.contract.update": "fees_contracts:update",
"fees.contract.delete": "fees_contracts:delete",
"documents.create": "documents:create",
"documents.delete": "documents:delete",
"fees.attachment.delete": "fees_attachments:delete",
"file.attachment.delete": "attachments:delete",
"audit.export.read": "audit_logs:read",
@@ -6,11 +6,20 @@ describe("project route permissions", () => {
expect(getProjectRoutePermission("/project/overview")).toEqual({ operationKey: "project_overview:read" });
expect(getProjectRoutePermission("/project/milestones")).toEqual({ operationKey: "project_milestones:read" });
expect(getProjectRoutePermission("/finance/contracts")).toEqual({ operationKey: "finance_contracts:read" });
expect(getProjectRoutePermission("/finance/contracts/new")).toEqual({ operationKey: "finance_contracts:create" });
expect(getProjectRoutePermission("/finance/contracts/abc/edit")).toEqual({ operationKey: "finance_contracts:update" });
expect(getProjectRoutePermission("/drug/shipments")).toEqual({ operationKey: "drug_shipments:read" });
expect(getProjectRoutePermission("/startup/feasibility/new")).toEqual({ operationKey: "ethics:read" });
expect(getProjectRoutePermission("/startup/kickoff/new")).toEqual({ operationKey: "startup_auth:read" });
expect(getProjectRoutePermission("/drug/shipments/new")).toEqual({ operationKey: "drug_shipments:create" });
expect(getProjectRoutePermission("/drug/shipments/abc/edit")).toEqual({ operationKey: "drug_shipments:update" });
expect(getProjectRoutePermission("/subjects/new")).toEqual({ operationKey: "subjects:create" });
expect(getProjectRoutePermission("/subjects/abc/edit")).toEqual({ operationKey: "subjects:update" });
expect(getProjectRoutePermission("/startup/feasibility/new")).toEqual({ operationKey: "ethics:create" });
expect(getProjectRoutePermission("/startup/feasibility/abc/edit")).toEqual({ operationKey: "ethics:update" });
expect(getProjectRoutePermission("/startup/kickoff/new")).toEqual({ operationKey: "startup_auth:create" });
expect(getProjectRoutePermission("/startup/kickoff/abc/edit")).toEqual({ operationKey: "startup_auth:update" });
expect(getProjectRoutePermission("/knowledge/medical-consult/abc")).toEqual({ operationKey: "faq:read" });
expect(getProjectRoutePermission("/knowledge/notes/new")).toEqual({ operationKey: "knowledge_notes:read" });
expect(getProjectRoutePermission("/knowledge/notes/new")).toEqual({ operationKey: "knowledge_notes:create" });
expect(getProjectRoutePermission("/knowledge/notes/abc/edit")).toEqual({ operationKey: "knowledge_notes:update" });
});
it("rejects project routes when the role lacks operation permission", () => {
@@ -6,6 +6,13 @@ export type ProjectRoutePermission = {
};
const routePermissions: Array<{ prefixes: string[]; permission: ProjectRoutePermission }> = [
{ prefixes: ["/fees/contracts/new"], permission: { operationKey: "fees_contracts:create" } },
{ prefixes: ["/finance/contracts/new"], permission: { operationKey: "finance_contracts:create" } },
{ prefixes: ["/drug/shipments/new"], permission: { operationKey: "drug_shipments:create" } },
{ prefixes: ["/startup/feasibility/new", "/startup/ethics/new"], permission: { operationKey: "ethics:create" } },
{ prefixes: ["/startup/kickoff/new", "/startup/training/new"], permission: { operationKey: "startup_auth:create" } },
{ prefixes: ["/subjects/new"], permission: { operationKey: "subjects:create" } },
{ prefixes: ["/knowledge/notes/new"], permission: { operationKey: "knowledge_notes:create" } },
{ prefixes: ["/project/overview"], permission: { operationKey: "project_overview:read" } },
{ prefixes: ["/project/milestones"], permission: { operationKey: "project_milestones:read" } },
{ prefixes: ["/fees/contracts"], permission: { operationKey: "fees_contracts:read" } },
@@ -23,6 +30,18 @@ const routePermissions: Array<{ prefixes: string[]; permission: ProjectRoutePerm
{ prefixes: ["/knowledge/notes", "/knowledge/support-files", "/knowledge/instruction-files"], permission: { operationKey: "knowledge_notes:read" } },
];
const editRoutePermissions: Array<{ pattern: RegExp; permission: ProjectRoutePermission }> = [
{ pattern: /^\/fees\/contracts\/[^/]+\/edit$/, permission: { operationKey: "fees_contracts:update" } },
{ pattern: /^\/finance\/contracts\/[^/]+\/edit$/, permission: { operationKey: "finance_contracts:update" } },
{ pattern: /^\/drug\/shipments\/[^/]+\/edit$/, permission: { operationKey: "drug_shipments:update" } },
{ pattern: /^\/startup\/feasibility\/[^/]+\/edit$/, permission: { operationKey: "ethics:update" } },
{ pattern: /^\/startup\/ethics\/[^/]+\/edit$/, permission: { operationKey: "ethics:update" } },
{ pattern: /^\/startup\/kickoff\/[^/]+\/edit$/, permission: { operationKey: "startup_auth:update" } },
{ pattern: /^\/startup\/training\/[^/]+\/edit$/, permission: { operationKey: "startup_auth:update" } },
{ pattern: /^\/subjects\/[^/]+\/edit$/, permission: { operationKey: "subjects:update" } },
{ pattern: /^\/knowledge\/notes\/[^/]+\/edit$/, permission: { operationKey: "knowledge_notes:update" } },
];
export const projectRouteLandingPaths = [
"/project/overview",
"/project/milestones",
@@ -44,6 +63,11 @@ export const projectRouteLandingPaths = [
export const getProjectRoutePermission = (path: string): ProjectRoutePermission | null => {
const normalized = path || "";
for (const item of editRoutePermissions) {
if (item.pattern.test(normalized)) {
return item.permission;
}
}
for (const item of routePermissions) {
if (item.prefixes.some((prefix) => normalized === prefix || normalized.startsWith(prefix.endsWith("/") ? prefix : `${prefix}/`))) {
return item.permission;