Advertisement
djbob2000

Optimize preload

Dec 20th, 2024
50
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //server/fd/fd-toolbox-asp-net/permission-data-provider.ts
  2. /* eslint-disable max-lines */
  3. import { FdMemoryCacheOptions } from "@server/fd/fd-toolbox/cachings/fd-memory-cache-options";
  4. import { Permission } from "@/fd/fd-toolbox-asp-net-models/permissions/permission.resource";
  5. import { type Uuid } from "@server/fd/fd-toolbox/types/uuid";
  6. import { PermissionPackageToPermission } from "@/fd/fd-toolbox-asp-net-models/permissions/permission-package-to-permission.resource";
  7. import { PermissionDto } from "@server/auth/permission-dto";
  8. import {
  9.     PermissionLevel,
  10.     permissionType,
  11.     PermissionType,
  12. } from "@/fd/fd-toolbox-core/auth/permissions/permission-enums";
  13. import { setCoreCache, tryGetObjectFromCoreCache } from "@server/fd/fd-toolbox/cachings/core-memory-cache";
  14. import { addListValueToDictionary, addMapValueToMap } from "@server/fd/fd-toolbox/services/dictionaries";
  15. import { RoleToPermission } from "@server/auth/role-to-permission";
  16. import { getAllIncludingUserToRoles } from "@server/auth/role-db";
  17. import { resourceSchema, permissionSchema as permissionTable, roleToPermissionSchema } from "@plugin/schema";
  18. import { eq, getTableColumns } from "drizzle-orm";
  19. import { getQuery } from "@server/web/drizzle";
  20. import { UserRole } from "@/users/user-role.resource";
  21. import { getPermissionPackageToPermissions } from "../fd-toolbox-web/auth/permissions/permission-package-to-permission-db";
  22. import { getRoleToPermissionPackages } from "../fd-toolbox-web/models/roles/role-to-permission-package-db";
  23. import { getPermissionCacheKey, createShallowPermission } from "../fd-toolbox-web/permissions/permissions";
  24. import { fdCacheKeys } from "../fd-toolbox-web/constants/fd-cache-keys";
  25.  
  26. // Add permission cache
  27. const permissionCache = new Map<Uuid, Permission>();
  28.  
  29. export async function getUserRoleIdsToRoleLevels() {
  30.     const roles = await getAllIncludingUserToRoles();
  31.  
  32.     const userRoleIdsLevels = new Map<Uuid, Map<Uuid, PermissionLevel>>();
  33.  
  34.     for (const roleItem of roles) {
  35.         if (roleItem.userRoleList) {
  36.             for (const userRoleItem of roleItem.userRoleList.filter(
  37.                 (ur: UserRole) => ur.roleId === roleItem.id,
  38.             )) {
  39.                 addMapValueToMap(userRoleIdsLevels, userRoleItem.userId, userRoleItem.roleId, roleItem.level);
  40.             }
  41.         }
  42.     }
  43.  
  44.     return userRoleIdsLevels;
  45. }
  46.  
  47. export async function saveRoleToPermissionsToCache(
  48.     rolePackageToPermissions: Map<Uuid, Uuid[]>,
  49.     ids: Uuid[],
  50.     type: PermissionType,
  51. ) {
  52.     const cacheKeyPrefix = getCacheKeyPrefix(type);
  53.     const uncachedIds = [];
  54.  
  55.     // Check cache first
  56.     for (const id of ids) {
  57.         const cached = await tryGetFromCache(id, cacheKeyPrefix);
  58.         if (cached) {
  59.             rolePackageToPermissions.set(id, cached);
  60.         } else {
  61.             uncachedIds.push(id);
  62.         }
  63.     }
  64.  
  65.     // Batch fetch uncached data
  66.     if (uncachedIds.length) {
  67.         await saveRoleToPermissionsBatch(uncachedIds);
  68.  
  69.         for (const id of uncachedIds) {
  70.             const cached = await tryGetFromCache(id, cacheKeyPrefix);
  71.             if (cached) {
  72.                 rolePackageToPermissions.set(id, cached);
  73.             }
  74.         }
  75.     }
  76.  
  77.     return rolePackageToPermissions;
  78. }
  79.  
  80. export async function getRoleToPermissionList() {
  81.     return (await getQuery(roleToPermissionSchema)) as RoleToPermission[];
  82. }
  83.  
  84. export async function getUserPermissionCacheKey(type: PermissionType, userId: Uuid) {
  85.     return getPermissionCacheKey(type.toString(), userId);
  86. }
  87.  
  88. export async function getUserResourcePermissionCacheKey(
  89.     type: PermissionType,
  90.     resourceName: string,
  91.     userId: Uuid,
  92. ) {
  93.     return getPermissionCacheKey(await getPermissionCacheKey(type.toString(), resourceName), userId);
  94. }
  95.  
  96. export async function saveUserPermissionsToMemoryCache(
  97.     userPermissionKey: string,
  98.     type: PermissionType,
  99.     userPermissions: Map<string, PermissionDto>,
  100. ) {
  101.     const cacheKey = await getUserPermissionCacheKey(type, userPermissionKey);
  102.     await saveToMemoryCache(cacheKey, userPermissions);
  103. }
  104.  
  105. export async function getUserRoleIdsToRolePermissionLevels(userId: Uuid) {
  106.     const roleIdsLevels = new Map<Uuid, PermissionLevel>();
  107.     const memoryCacheKey = await getPermissionCacheKey(fdCacheKeys.userRolePermissionLevels, userId);
  108.  
  109.     const cachedUserRoleIdsLevels = tryGetObjectFromCoreCache<Map<Uuid, PermissionLevel>>(memoryCacheKey);
  110.  
  111.     if (cachedUserRoleIdsLevels) {
  112.         return cachedUserRoleIdsLevels;
  113.     } else {
  114.         await saveUserRoleIdsToRolePermissionLevelsToCache();
  115.         const rolesToLevels = tryGetObjectFromCoreCache<Map<Uuid, PermissionLevel>>(memoryCacheKey);
  116.         if (rolesToLevels) {
  117.             return rolesToLevels;
  118.         }
  119.     }
  120.  
  121.     return roleIdsLevels;
  122. }
  123.  
  124. export async function savePackageToPermissionsToCache(
  125.     rolePackageToPermissions: Map<Uuid, Uuid[]>,
  126.     ids: Uuid[],
  127.     type: PermissionType,
  128. ) {
  129.     let cacheKeyPrefix;
  130.  
  131.     switch (type) {
  132.         case permissionType.resource:
  133.             cacheKeyPrefix = fdCacheKeys.packagesToResourcePermissions;
  134.             break;
  135.         case permissionType.property:
  136.             cacheKeyPrefix = fdCacheKeys.packagesToPropertyPermissions;
  137.             break;
  138.         default:
  139.             cacheKeyPrefix = fdCacheKeys.packagesToDefaultPermissions;
  140.     }
  141.  
  142.     for (const id of ids) {
  143.         await addMapFromCache(rolePackageToPermissions, id, cacheKeyPrefix);
  144.         if (!rolePackageToPermissions.has(id)) {
  145.             await privateSavePackageToPermissionsToCache(id);
  146.  
  147.             await addMapFromCache(rolePackageToPermissions, id, cacheKeyPrefix);
  148.         }
  149.     }
  150.  
  151.     return rolePackageToPermissions;
  152. }
  153.  
  154. export async function getPermission(id: Uuid) {
  155.     // Check memory cache first
  156.     if (permissionCache.has(id)) {
  157.         return createShallowPermission(permissionCache.get(id)!);
  158.     }
  159.  
  160.     const cacheKey = await getPermissionCacheKey(fdCacheKeys.permission, id);
  161.     let permission = tryGetObjectFromCoreCache<Permission>(cacheKey);
  162.  
  163.     if (!permission) {
  164.         await savePermissionsToCache();
  165.         permission = tryGetObjectFromCoreCache(cacheKey);
  166.     }
  167.  
  168.     if (permission) {
  169.         permissionCache.set(id, permission);
  170.         return createShallowPermission(permission);
  171.     }
  172.  
  173.     return null;
  174. }
  175.  
  176. export async function addPackageToPermissions(
  177.     rolesToPermissions: Map<Uuid, Uuid[]>,
  178.     packageToPermissionList: PermissionPackageToPermission[],
  179.     type: PermissionType,
  180. ) {
  181.     for (const packageToPermission of packageToPermissionList) {
  182.         const permission = await getPermission(packageToPermission.permissionId);
  183.         if (permission?.type === type) {
  184.             addListValueToDictionary(
  185.                 rolesToPermissions,
  186.                 packageToPermission.permissionPackageId,
  187.                 packageToPermission.permissionId,
  188.             );
  189.         }
  190.     }
  191. }
  192.  
  193. export async function savePermissionsToCache() {
  194.     const query = getQuery(permissionTable, {
  195.         ...getTableColumns(permissionTable),
  196.         resource: getTableColumns(resourceSchema),
  197.     });
  198.  
  199.     const permissions = (await query.leftJoin(
  200.         resourceSchema,
  201.         eq(permissionTable.resourceId, resourceSchema.id),
  202.     )) as Permission[];
  203.  
  204.     for (const permission of permissions) {
  205.         const memoryKey = await getPermissionCacheKey(fdCacheKeys.permission, permission.id);
  206.         const newPermission = createShallowPermission(permission);
  207.  
  208.         saveToMemoryCache(memoryKey, newPermission);
  209.     }
  210. }
  211.  
  212. export async function getRoleToPackages(ids: Uuid[]) {
  213.     const roleToPackages = new Map<Uuid, Uuid[]>();
  214.  
  215.     for (const id of ids) {
  216.         await addMapFromCache(roleToPackages, id, fdCacheKeys.roleToPackages);
  217.         if (!roleToPackages.has(id)) {
  218.             await saveRoleToPermissionPackagesToCache();
  219.  
  220.             await addMapFromCache(roleToPackages, id, fdCacheKeys.roleToPackages);
  221.         }
  222.     }
  223.  
  224.     return roleToPackages;
  225. }
  226.  
  227. async function addMapFromCache<T>(map: Map<Uuid, T[]>, id: Uuid, cacheKeyPrefix: string) {
  228.     const memoryCacheKey = await getPermissionCacheKey(cacheKeyPrefix, id);
  229.     const cachedValue = tryGetObjectFromCoreCache<T[]>(memoryCacheKey);
  230.     if (cachedValue) {
  231.         map.set(id, cachedValue);
  232.     }
  233. }
  234.  
  235. export async function saveRoleToPermissionsToCacheById(id: Uuid) {
  236.     const rolesToResourcePermissions: Map<Uuid, Uuid[]> = new Map();
  237.     const rolesToDefaultPermissions: Map<Uuid, Uuid[]> = new Map();
  238.     const rolesToPropertyPermissions: Map<Uuid, Uuid[]> = new Map();
  239.  
  240.     const roleToPermissionList = await getRoleToPermissionList();
  241.     let roleIds = [id, ...roleToPermissionList.map((x) => x.roleId)];
  242.     roleIds = [...new Set(roleIds)];
  243.  
  244.     await addRoleToPermissions(rolesToResourcePermissions, roleToPermissionList, permissionType.resource);
  245.     await addRoleToPermissions(rolesToDefaultPermissions, roleToPermissionList, permissionType.default);
  246.     await addRoleToPermissions(rolesToPropertyPermissions, roleToPermissionList, permissionType.property);
  247.  
  248.     await saveToMemoryCacheByCacheKeyPrefix(
  249.         rolesToResourcePermissions,
  250.         fdCacheKeys.roleToResourcePermissions,
  251.     );
  252.     await saveToMemoryCacheByCacheKeyPrefix(rolesToDefaultPermissions, fdCacheKeys.roleToDefaultPermissions);
  253.     await saveToMemoryCacheByCacheKeyPrefix(
  254.         rolesToPropertyPermissions,
  255.         fdCacheKeys.roleToPropertyPermissions,
  256.     );
  257. }
  258.  
  259. async function addRoleToPermissions(
  260.     rolesToPermissions: Map<Uuid, Uuid[]>,
  261.     roleToPermissionList: RoleToPermission[],
  262.     type: PermissionType,
  263. ) {
  264.     for (const roleToPermissionItem of roleToPermissionList) {
  265.         const permission = await getPermission(roleToPermissionItem.permissionId);
  266.         if (permission?.type === type) {
  267.             addListValueToDictionary(
  268.                 rolesToPermissions,
  269.                 roleToPermissionItem.roleId,
  270.                 roleToPermissionItem.permissionId,
  271.             );
  272.         }
  273.     }
  274. }
  275.  
  276. async function applyPermissionsToPackages(
  277.     permissionMap: Map<Uuid, Uuid[]>,
  278.     permissionList: PermissionPackageToPermission[],
  279.     permissionTypeParam: PermissionType,
  280.     packageIds: Uuid[],
  281. ) {
  282.     await addPackageToPermissions(permissionMap, permissionList, permissionTypeParam);
  283.  
  284.     if (permissionMap.size === 0) {
  285.         for (const packId of packageIds) {
  286.             permissionMap.set(packId, []);
  287.         }
  288.     }
  289. }
  290.  
  291. async function privateSavePackageToPermissionsToCache(id: Uuid) {
  292.     const packagesToResourcePermissions = new Map<Uuid, Uuid[]>();
  293.     const packagesToDefaultPermissions = new Map<Uuid, Uuid[]>();
  294.     const packagesToPropertyPermissions = new Map<Uuid, Uuid[]>();
  295.  
  296.     const packageToPermissionList = await getPermissionPackageToPermissions();
  297.  
  298.     let permissionPackageIds = [id];
  299.  
  300.     permissionPackageIds = permissionPackageIds.concat(
  301.         packageToPermissionList.map((x) => x.permissionPackageId),
  302.     );
  303.     permissionPackageIds = Array.from(new Set(permissionPackageIds));
  304.  
  305.     await applyPermissionsToPackages(
  306.         packagesToResourcePermissions,
  307.         packageToPermissionList,
  308.         permissionType.resource,
  309.         permissionPackageIds,
  310.     );
  311.  
  312.     await applyPermissionsToPackages(
  313.         packagesToDefaultPermissions,
  314.         packageToPermissionList,
  315.         permissionType.default,
  316.         permissionPackageIds,
  317.     );
  318.  
  319.     await applyPermissionsToPackages(
  320.         packagesToPropertyPermissions,
  321.         packageToPermissionList,
  322.         permissionType.property,
  323.         permissionPackageIds,
  324.     );
  325.  
  326.     await saveToMemoryCacheByCacheKeyPrefix(
  327.         packagesToResourcePermissions,
  328.         fdCacheKeys.packagesToResourcePermissions,
  329.     );
  330.     await saveToMemoryCacheByCacheKeyPrefix(
  331.         packagesToDefaultPermissions,
  332.         fdCacheKeys.packagesToDefaultPermissions,
  333.     );
  334.     await saveToMemoryCacheByCacheKeyPrefix(
  335.         packagesToPropertyPermissions,
  336.         fdCacheKeys.packagesToPropertyPermissions,
  337.     );
  338. }
  339.  
  340. async function saveRoleToPermissionPackagesToCache() {
  341.     const rolesToPackages = new Map<Uuid, Uuid[]>();
  342.  
  343.     const roleToPermissionPackageList = await getRoleToPermissionPackages();
  344.  
  345.     for (const roleToPermissionPackage of roleToPermissionPackageList) {
  346.         addListValueToDictionary(
  347.             rolesToPackages,
  348.             roleToPermissionPackage.roleId,
  349.             roleToPermissionPackage.permissionPackageId,
  350.         );
  351.     }
  352.  
  353.     await saveToMemoryCacheByCacheKeyPrefix(rolesToPackages, fdCacheKeys.roleToPackages);
  354. }
  355.  
  356. async function saveToMemoryCacheByCacheKeyPrefix(
  357.     idsToPermissions: Map<Uuid, Uuid[]>,
  358.     cacheKeyPrefix: string,
  359. ) {
  360.     for (const [key, value] of idsToPermissions) {
  361.         const newKey = await getPermissionCacheKey(cacheKeyPrefix, key);
  362.         await saveToMemoryCache(newKey, value);
  363.     }
  364. }
  365.  
  366. async function saveUserRoleIdsToRolePermissionLevelsToCache() {
  367.     const userIdRoleIdsToRolePermissionLevels = await getUserRoleIdsToRoleLevels();
  368.  
  369.     for (const [userId, rolePermissionLevels] of userIdRoleIdsToRolePermissionLevels.entries()) {
  370.         const key = await getPermissionCacheKey(fdCacheKeys.userRolePermissionLevels, userId);
  371.         await saveToMemoryCache(key, rolePermissionLevels);
  372.     }
  373. }
  374.  
  375. async function saveToMemoryCache<T extends object>(key: string, value: T) {
  376.     const fdMemoryCacheOptions: FdMemoryCacheOptions = {
  377.         absoluteExpirationRelativeToNowSeconds: 24 * fdCacheKeys.secondsInAHour,
  378.     };
  379.  
  380.     setCoreCache(key, value, fdMemoryCacheOptions);
  381. }
  382.  
  383. // Helper functions
  384. function getCacheKeyPrefix(type: PermissionType) {
  385.     switch (type) {
  386.         case permissionType.resource:
  387.             return fdCacheKeys.roleToResourcePermissions;
  388.         case permissionType.property:
  389.             return fdCacheKeys.roleToPropertyPermissions;
  390.         default:
  391.             return fdCacheKeys.roleToDefaultPermissions;
  392.     }
  393. }
  394.  
  395. async function tryGetFromCache(id: Uuid, cacheKeyPrefix: string) {
  396.     const memoryCacheKey = await getPermissionCacheKey(cacheKeyPrefix, id);
  397.     return tryGetObjectFromCoreCache<Uuid[]>(memoryCacheKey);
  398. }
  399.  
  400. async function saveRoleToPermissionsBatch(roleIds: Uuid[]) {
  401.     const roleToPermissionList = await getRoleToPermissionList();
  402.     const rolesToResourcePermissions: Map<Uuid, Uuid[]> = new Map();
  403.     const rolesToDefaultPermissions: Map<Uuid, Uuid[]> = new Map();
  404.     const rolesToPropertyPermissions: Map<Uuid, Uuid[]> = new Map();
  405.  
  406.     // Process all roles in one pass
  407.     await Promise.all([
  408.         addRoleToPermissions(rolesToResourcePermissions, roleToPermissionList, permissionType.resource),
  409.         addRoleToPermissions(rolesToDefaultPermissions, roleToPermissionList, permissionType.default),
  410.         addRoleToPermissions(rolesToPropertyPermissions, roleToPermissionList, permissionType.property),
  411.     ]);
  412.  
  413.     // Save all to cache in parallel
  414.     await Promise.all([
  415.         saveToMemoryCacheByCacheKeyPrefix(rolesToResourcePermissions, fdCacheKeys.roleToResourcePermissions),
  416.         saveToMemoryCacheByCacheKeyPrefix(rolesToDefaultPermissions, fdCacheKeys.roleToDefaultPermissions),
  417.         saveToMemoryCacheByCacheKeyPrefix(rolesToPropertyPermissions, fdCacheKeys.roleToPropertyPermissions),
  418.     ]);
  419. }
  420.  
  421.  
  422. //server/fd/fd-toolbox-web/permissions/permission-checker.ts
  423. /* eslint-disable max-lines */
  424. import { PermissionCheckConfig } from "@server/fd/fd-toolbox/auth/permissions/permission-check-config";
  425. import { PermissionDto } from "@server/auth/permission-dto";
  426. import {
  427.     PermissionLevel,
  428.     permissionType,
  429.     PermissionType,
  430. } from "@/fd/fd-toolbox-core/auth/permissions/permission-enums";
  431. import { ResourceMeta } from "@/fd/fd-toolbox/fd-meta/resource-meta";
  432. import { getResourceMeta } from "@server/meta/fd-meta/services/metas";
  433. import { getResourcePermissionChecker } from "@server/meta/fd-meta/auth/permissions/resource-permission-checker-provider";
  434. import { crudAction, CrudAction } from "@/fd/fd-toolbox-core/enums/enums";
  435. import { createError } from "@/fd/fd-toolbox/errors/errors";
  436. import { getPermissionCheckConfig } from "@server/meta/fd-meta/services/permission-check-config-provider";
  437. import { type Uuid } from "@server/fd/fd-toolbox/types/uuid";
  438. import {
  439.     getPermission,
  440.     getRoleToPackages,
  441.     getUserPermissionCacheKey,
  442.     getUserResourcePermissionCacheKey,
  443.     getUserRoleIdsToRolePermissionLevels,
  444.     savePackageToPermissionsToCache,
  445.     saveRoleToPermissionsToCache,
  446.     saveUserPermissionsToMemoryCache,
  447. } from "@server/fd/fd-toolbox-asp-net/permission-data-provider";
  448. import { WithIndexer } from "@server/fd/fd-toolbox/types/with-indexer";
  449. import { Permission } from "@/fd/fd-toolbox-asp-net-models/permissions/permission.resource";
  450. import { getEmptyUuid, isValidUuid } from "@/fd/fd-toolbox/uuids";
  451. import { setCoreCache, tryGetObjectFromCoreCache } from "@server/fd/fd-toolbox/cachings/core-memory-cache";
  452. import { isNullOrUndefined } from "@/fd/fd-toolbox/functions/value-checking-functions";
  453. import { UserPermissionList } from "@server/users/user-permission";
  454. import { withUserIdProps } from "@server/fd/fd-toolbox-core/base-resources/props/with-user-id-props";
  455. import { WithId } from "@server/fd/fd-toolbox-core/base-resources/with-id";
  456. import { WithUserId } from "@server/fd/fd-toolbox-core/base-resources/with-user-id";
  457. import {
  458.     getPermissionNameFromCrud,
  459.     convertCrudActionToPermissionAction,
  460.     createPermissionDto,
  461. } from "./permissions";
  462.  
  463. const minPermissionLevel = 0;
  464.  
  465. // Add new cache for request-level data
  466. const requestCache = new Map<
  467.     string,
  468.     {
  469.         roleIdsLevels: Map<Uuid, PermissionLevel>;
  470.         rolesToPackages: Map<Uuid, Uuid[]>;
  471.     }
  472. >();
  473.  
  474. export async function hasPermissionByPermissionCheckConfig(permissionCheckConfig: PermissionCheckConfig) {
  475.     if (permissionCheckConfig.resourceName && !isNullOrUndefined(permissionCheckConfig.crudAction)) {
  476.         permissionCheckConfig.permissionName = getPermissionNameFromCrud(
  477.             permissionCheckConfig.resourceName,
  478.             permissionCheckConfig.crudAction!,
  479.         );
  480.     }
  481.  
  482.     if (!permissionCheckConfig.permissionName) {
  483.         throw createError(`${permissionCheckConfig.permissionName} is empty`);
  484.     }
  485.  
  486.     const userHasPermission = await checkUserPermission(permissionCheckConfig);
  487.  
  488.     if (permissionCheckConfig.throwIfNoPermission && !userHasPermission) {
  489.         throw createError(
  490.             `No permission ${permissionCheckConfig.permissionName} for ${permissionCheckConfig.userId}`,
  491.         );
  492.     }
  493.     return userHasPermission;
  494. }
  495.  
  496. export async function hasPermissionByUserId(resourceName: string, action: CrudAction, userId: string) {
  497.     const resourceMeta: ResourceMeta = await getResourceMeta(resourceName);
  498.  
  499.     const permissionCheckConfig: PermissionCheckConfig = getPermissionCheckConfig(
  500.         userId,
  501.         resourceMeta,
  502.         undefined,
  503.         action,
  504.     );
  505.  
  506.     return hasPermissionByPermissionCheckConfig(permissionCheckConfig);
  507. }
  508.  
  509. export async function hasPropertyPermission(propertyId: Uuid, permissionCheckConfig: PermissionCheckConfig) {
  510.     if (!permissionCheckConfig.resourceId || !permissionCheckConfig.crudAction) {
  511.         return false;
  512.     }
  513.  
  514.     const userPermission = await getUserPermission(
  515.         permissionCheckConfig.userId,
  516.         permissionType.property,
  517.         propertyId,
  518.     );
  519.  
  520.     if (
  521.         userPermission &&
  522.         userPermission.action &&
  523.         convertCrudActionToPermissionAction(userPermission.action).includes(permissionCheckConfig.crudAction)
  524.     ) {
  525.         return true;
  526.     }
  527.  
  528.     return true;
  529. }
  530.  
  531. async function checkUserPermission(permissionCheckConfig: PermissionCheckConfig) {
  532.     let userHasPermission = await hasDefaultOrResourcePermission(permissionCheckConfig);
  533.  
  534.     if (permissionCheckConfig.resource && permissionCheckConfig.resourceName) {
  535.         const resourceMeta: ResourceMeta = await getResourceMeta(permissionCheckConfig.resourceName);
  536.  
  537.         if (!userHasPermission) {
  538.             userHasPermission = checkBelowLevelAllowedCrudByUserId(resourceMeta, permissionCheckConfig);
  539.         }
  540.  
  541.         const resourcePermissionChecker = getResourcePermissionChecker(resourceMeta.resourceName);
  542.  
  543.         if (resourcePermissionChecker) {
  544.             const result = await resourcePermissionChecker(
  545.                 permissionCheckConfig.resource,
  546.                 permissionCheckConfig.userId,
  547.                 permissionCheckConfig.crudAction ?? crudAction.view,
  548.             );
  549.  
  550.             if (result) {
  551.                 userHasPermission = result;
  552.             }
  553.         }
  554.     }
  555.     return userHasPermission;
  556. }
  557.  
  558. function mapToWithIndexer<T>(map: Map<string, T>): WithIndexer<T> {
  559.     const result: WithIndexer<T> = {};
  560.  
  561.     map.forEach((value, key) => {
  562.         result[key] = value;
  563.     });
  564.  
  565.     return result;
  566. }
  567.  
  568. export async function getUserPermissions(userId: Uuid) {
  569.     // Cache role data at request level
  570.     const roleData = await cacheRoleData(userId);
  571.  
  572.     if (!roleData) {
  573.         throw createError("Role data not found");
  574.     }
  575.     const [resourcePermissions, propertyPermissions, defaultPermissions] = await Promise.all([
  576.         getUserPermissionsByPermissionType(userId, permissionType.resource, roleData),
  577.         getUserPermissionsByPermissionType(userId, permissionType.property, roleData),
  578.         getUserPermissionsByPermissionType(userId, permissionType.default, roleData),
  579.     ]);
  580.  
  581.     const userPermissions: UserPermissionList = {
  582.         resourcePermissions: mapToWithIndexer(resourcePermissions),
  583.         propertyPermissions: mapToWithIndexer(propertyPermissions),
  584.         defaultPermissions: mapToWithIndexer(defaultPermissions),
  585.     };
  586.  
  587.     // Clear request cache after use
  588.     requestCache.clear();
  589.     return userPermissions;
  590. }
  591.  
  592. async function getUserPermissionsByPermissionType(
  593.     userId: Uuid,
  594.     type: PermissionType,
  595.     roleData: {
  596.         roleIdsLevels: Map<Uuid, PermissionLevel>;
  597.         rolesToPackages: Map<Uuid, Uuid[]>;
  598.     },
  599. ) {
  600.     if (!roleData?.roleIdsLevels || !roleData?.rolesToPackages) {
  601.         throw createError("Invalid role data provided");
  602.     }
  603.  
  604.     const cacheKey = await getUserPermissionCacheKey(type, userId);
  605.     const cached = tryGetObjectFromCoreCache<Map<string, PermissionDto>>(cacheKey);
  606.     if (cached !== null) return cached;
  607.  
  608.     const { roleIdsLevels, rolesToPackages } = roleData;
  609.     const roleIds = [...roleIdsLevels.keys()];
  610.     const maxPermissionRoleLevel = Math.max(...[...roleIdsLevels.values()], minPermissionLevel);
  611.  
  612.     const [rolesToPermissions, packagesToPermissions] = await Promise.all([
  613.         getRolePermissions(roleIds, type),
  614.         getPackagePermissions(rolesToPackages, type),
  615.     ]);
  616.  
  617.     const userPermissions = await buildUserPermissions(
  618.         roleIds,
  619.         rolesToPermissions,
  620.         packagesToPermissions,
  621.         rolesToPackages,
  622.         maxPermissionRoleLevel,
  623.     );
  624.  
  625.     const userPermissionsMap = convertWithIndexerToMap(userPermissions);
  626.     await saveUserPermissionsToMemoryCache(userId, type, userPermissionsMap);
  627.  
  628.     return userPermissionsMap;
  629. }
  630.  
  631. // Cache role data at request level
  632. async function cacheRoleData(userId: Uuid) {
  633.     const cacheKey = `roleData_${userId}`;
  634.  
  635.     if (requestCache.has(cacheKey)) {
  636.         return requestCache.get(cacheKey);
  637.     }
  638.  
  639.     const roleIdsLevels = await getUserRoleIdsToRolePermissionLevels(userId);
  640.     const roleIds = [...roleIdsLevels.keys()];
  641.     const rolesToPackages = await getRoleToPackages(roleIds);
  642.  
  643.     const data = { roleIdsLevels, rolesToPackages };
  644.     requestCache.set(cacheKey, data);
  645.  
  646.     return data;
  647. }
  648.  
  649. // Optimize permission fetching with batch operations
  650. async function getRolePermissions(roleIds: Uuid[], type: PermissionType) {
  651.     const rolesToPermissions = new Map<Uuid, Uuid[]>();
  652.     await saveRoleToPermissionsToCache(rolesToPermissions, roleIds, type);
  653.     return rolesToPermissions;
  654. }
  655.  
  656. async function getPackagePermissions(rolesToPackages: Map<Uuid, Uuid[]>, type: PermissionType) {
  657.     const packageIds = [...new Set([...rolesToPackages.values()].flat())];
  658.     const packagesToPermissions = new Map<Uuid, Uuid[]>();
  659.     await savePackageToPermissionsToCache(packagesToPermissions, packageIds, type);
  660.     return packagesToPermissions;
  661. }
  662.  
  663. function checkBelowLevelAllowedCrudByUserId(
  664.     resourceMeta: ResourceMeta,
  665.     permissionCheckConfig: PermissionCheckConfig,
  666. ) {
  667.     if (!permissionCheckConfig.resource) return false;
  668.  
  669.     const resource: WithId = permissionCheckConfig.resource;
  670.     let withUserId;
  671.  
  672.     if (withUserIdProps.userId in resourceMeta.properties) {
  673.         withUserId = resource as unknown as WithUserId;
  674.     }
  675.  
  676.     if (
  677.         withUserId &&
  678.         permissionCheckConfig.crudAction !== undefined &&
  679.         resourceMeta.auth.belowLevelAllowedCrudByUserId.includes(permissionCheckConfig.crudAction)
  680.     ) {
  681.         if (withUserId && permissionCheckConfig.userId === withUserId.userId) {
  682.             return true;
  683.         }
  684.     }
  685.  
  686.     return false;
  687. }
  688.  
  689. async function addHigherPermission<TKey>(
  690.     permissions: Map<TKey, PermissionDto>,
  691.     key: TKey,
  692.     permission: Permission,
  693.     permissionRoleLevel: PermissionLevel,
  694. ) {
  695.     if (!permissions.has(key)) {
  696.         permissions.set(key, await createPermissionDto(permission, permissionRoleLevel));
  697.     } else {
  698.         const permissionDto = permissions.get(key)!;
  699.  
  700.         if (permissionDto.id !== permission.id) {
  701.             if (permissionDto.action && permission.action) {
  702.                 permission.id = getEmptyUuid();
  703.                 permission.action |= permissionDto.action;
  704.  
  705.                 const newPermissionDto = await createPermissionDto(permission, permissionRoleLevel);
  706.                 permissions.set(key, newPermissionDto);
  707.             } else {
  708.                 const newPermissionDto = await createPermissionDto(permission, permissionRoleLevel);
  709.                 permissions.set(key, newPermissionDto);
  710.             }
  711.         }
  712.     }
  713. }
  714.  
  715. async function hasDefaultOrResourcePermission(permissionCheckConfig: PermissionCheckConfig) {
  716.     let userHasPermission = false;
  717.  
  718.     if (permissionCheckConfig.resourceId) {
  719.         const userPermission = await getUserPermission(
  720.             permissionCheckConfig.userId,
  721.             permissionType.resource,
  722.             permissionCheckConfig.resourceId,
  723.         );
  724.  
  725.         if (
  726.             userPermission &&
  727.             userPermission.action &&
  728.             permissionCheckConfig.crudAction !== undefined &&
  729.             convertCrudActionToPermissionAction(userPermission.action).includes(
  730.                 permissionCheckConfig.crudAction,
  731.             )
  732.         ) {
  733.             userHasPermission = true;
  734.         }
  735.     } else {
  736.         if (permissionCheckConfig.permissionName) {
  737.             const userPermission = await getUserPermission(
  738.                 permissionCheckConfig.userId,
  739.                 permissionType.default,
  740.                 permissionCheckConfig.permissionName,
  741.             );
  742.  
  743.             if (userPermission && userPermission.name === permissionCheckConfig.permissionName) {
  744.                 userHasPermission = true;
  745.             }
  746.         }
  747.     }
  748.  
  749.     return userHasPermission;
  750. }
  751.  
  752. async function addUserPermissions(
  753.     roleIds: Uuid[],
  754.     rolesToPermissions: Map<Uuid, Uuid[]>,
  755.     packagesToPermissions: Map<Uuid, Uuid[]>,
  756.     rolesToPackages: Map<Uuid, Uuid[]>,
  757.     maxPermissionRoleLevel: PermissionLevel,
  758.     userPermissions: WithIndexer<PermissionDto>,
  759. ) {
  760.     for (const role of roleIds) {
  761.         if (rolesToPermissions.has(role)) {
  762.             for (const permissionId of rolesToPermissions.get(role)!) {
  763.                 await addPermissionIfNeeded(permissionId, userPermissions, maxPermissionRoleLevel);
  764.             }
  765.         }
  766.  
  767.         if (rolesToPackages.has(role)) {
  768.             for (const rolePackageName of rolesToPackages.get(role)!) {
  769.                 if (packagesToPermissions.has(rolePackageName)) {
  770.                     for (const permissionId of packagesToPermissions.get(rolePackageName)!) {
  771.                         await addPermissionIfNeeded(permissionId, userPermissions, maxPermissionRoleLevel);
  772.                     }
  773.                 }
  774.             }
  775.         }
  776.     }
  777. }
  778.  
  779. async function addPermissionIfNeeded(
  780.     permissionId: Uuid,
  781.     userPermissions: WithIndexer<PermissionDto>,
  782.     maxPermissionRoleLevel: PermissionLevel,
  783. ) {
  784.     const permission = await getPermission(permissionId);
  785.  
  786.     if (permission) {
  787.         const userPermissionsMap = convertWithIndexerToMap<PermissionDto>(userPermissions);
  788.  
  789.         if (permission.type === permissionType.resource || permission.type === permissionType.property) {
  790.             const key =
  791.                 permission.type === permissionType.resource
  792.                     ? permission.resourceId!
  793.                     : permission.resourcePropertyId!;
  794.             await addHigherPermission(userPermissionsMap, key, permission, maxPermissionRoleLevel);
  795.         } else if (permission.type === permissionType.default) {
  796.             await addHigherPermission(
  797.                 userPermissionsMap,
  798.                 permission.name!,
  799.                 permission,
  800.                 maxPermissionRoleLevel,
  801.             );
  802.         }
  803.         userPermissionsMap.forEach((value, key) => {
  804.             userPermissions[key] = value;
  805.         });
  806.     }
  807. }
  808.  
  809. function convertWithIndexerToMap<T>(obj: WithIndexer<T>) {
  810.     return new Map(Object.entries(obj));
  811. }
  812.  
  813. async function getUserPermission(userId: Uuid, type: PermissionType, resourceIdOrPropertyIdOrName: Uuid) {
  814.     const cacheKey = await getUserResourcePermissionCacheKey(type, resourceIdOrPropertyIdOrName, userId);
  815.     let userPermission = tryGetObjectFromCoreCache<PermissionDto>(cacheKey);
  816.  
  817.     if (userPermission !== null) {
  818.         return userPermission;
  819.     }
  820.     const roleData = await cacheRoleData(userId);
  821.     if (!roleData) {
  822.         throw createError("Role data not found");
  823.     }
  824.     const userPermissions = await getUserPermissionsByPermissionType(userId, type, roleData);
  825.  
  826.     userPermission = null;
  827.  
  828.     if (type === permissionType.resource || type === permissionType.property) {
  829.         if (isValidUuid(resourceIdOrPropertyIdOrName) && userPermissions.has(resourceIdOrPropertyIdOrName)) {
  830.             userPermission = userPermissions.get(resourceIdOrPropertyIdOrName)!;
  831.         }
  832.     } else if (type === permissionType.default) {
  833.         const permissionName = resourceIdOrPropertyIdOrName.toString();
  834.         if (userPermissions.has(permissionName)) {
  835.             userPermission = userPermissions.get(permissionName)!;
  836.         }
  837.     }
  838.  
  839.     if (userPermission) {
  840.         setCoreCache(cacheKey, userPermission);
  841.     }
  842.  
  843.     return userPermission;
  844. }
  845.  
  846. async function buildUserPermissions(
  847.     roleIds: Uuid[],
  848.     rolesToPermissions: Map<Uuid, Uuid[]>,
  849.     packagesToPermissions: Map<Uuid, Uuid[]>,
  850.     rolesToPackages: Map<Uuid, Uuid[]>,
  851.     maxPermissionRoleLevel: PermissionLevel,
  852. ): Promise<WithIndexer<PermissionDto>> {
  853.     const userPermissions: WithIndexer<PermissionDto> = {};
  854.  
  855.     await addUserPermissions(
  856.         roleIds,
  857.         rolesToPermissions,
  858.         packagesToPermissions,
  859.         rolesToPackages,
  860.         maxPermissionRoleLevel,
  861.         userPermissions,
  862.     );
  863.  
  864.     return userPermissions;
  865. }
  866.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement