@tmlmobilidade/types
Version:
152 lines (151 loc) • 6.69 kB
JavaScript
/* eslint-disable @typescript-eslint/no-extraneous-class */
/* * */
import { AgenciesPermissionSchema } from './agencies.js';
import { AlertsRealtimePermissionSchema, AlertsScheduledPermissionSchema } from './alerts.js';
import { RidesPermissionSchema, SamsPermissionSchema } from './controller.js';
import { DatesPermissionSchema } from './dates.js';
import { GtfsValidationsPermissionSchema } from './gtfs-validations.js';
import { HomePermissionSchema } from './home.js';
import { OrganizationsPermissionSchema } from './organizations.js';
import { PerformancePermissionSchema } from './performance.js';
import { PlansPermissionSchema } from './plans.js';
import { RolesPermissionSchema } from './roles.js';
import { StopsPermissionSchema } from './stops.js';
import { UsersPermissionSchema } from './users.js';
import { z } from 'zod';
/* * */
export const PermissionSchema = z.discriminatedUnion('scope', [
AgenciesPermissionSchema,
AlertsScheduledPermissionSchema,
AlertsRealtimePermissionSchema,
RidesPermissionSchema,
SamsPermissionSchema,
GtfsValidationsPermissionSchema,
HomePermissionSchema,
OrganizationsPermissionSchema,
PerformancePermissionSchema,
PlansPermissionSchema,
RolesPermissionSchema,
StopsPermissionSchema,
UsersPermissionSchema,
DatesPermissionSchema,
]);
/**
* PermissionCatalog provides a structured catalog of all available permissions
* in the system, categorized by scope and their respective actions.
* Use it to reference required permissions in components and services.
*/
export class PermissionCatalog {
//
static ALLOW_ALL_FLAG = 'allow_all';
/**
* Generates the complete permission catalog by extracting
* scopes and actions from the defined PermissionSchema.
* @return A catalog object mapping scopes to their actions.
*/
static get all() {
// Initialize catalog object
const catalog = {};
// Iterate over each schema option
for (const schemaOption of PermissionSchema.options) {
// Extract scope name and actions
const scopeName = schemaOption.shape.scope.value;
const actions = schemaOption.shape.action.options;
// Build catalog entry
catalog[scopeName] = {
actions: Object.fromEntries(actions.map((a) => [a, a])),
scope: scopeName,
};
}
// Return the completed catalog
return catalog;
}
/**
* Get a specific permission from a full list by scope and action.
* @param permissionEntries The full list of permissions of the user.
* @param scope The resource scope of the permission to filter by.
* @param action The action of the permission to filter by.
* @returns The filtered Permission object or undefined if not found.
*/
static get(permissionEntries, scope, action) {
return permissionEntries.find((p) => p.scope === scope && p.action === action);
}
/**
* Check if a list of permission entries has the requested scope/action pair.
* @param permissionEntries The list of permission entries to check against.
* @param scope The required scope to check.
* @param action The required action to check.
* @returns The permission object or undefined if not found.
*/
static hasPermission(permissionEntries, scope, action) {
return permissionEntries.find(p => p.scope === scope && p.action === action) !== undefined;
}
/**
* Check if a permission exists in a list of permissions, with additional check for a given resource value.
* If a `value` exists in a `resource` of a User `permissions` object that
* matches the given `action` and `scope`. For example, if you want to check if
* a user has access to a specific `agency_id`, you set `value=43` and `resource_key='agency_ids'`.
* If the provided `permissions` object contains the value `43` inside the `scope='plans'`,
* `action='create'` and `resource_key='agency_ids'` the function will return true.
* @param permissions The list of permissions (from a user or request).
* @param value The permission value to check against.
* @param resource_key The key of the resource.
* @param scope The scope of the permission.
* @param action The action of the permission.
* @returns The permission.
*/
static hasPermissionResource({ action, permissions, resource_key, scope, value }) {
//
//
// Return false if no permissions
if (!permissions)
return false;
//
// Find the permission with the given action and scope
const foundPermission = permissions.find(p => p.action === action && p.scope === scope);
if (!foundPermission)
return false;
//
// Check if value exists in the permission.resources[resource_key]
const resourceValues = foundPermission['resources']?.[resource_key];
if (!resourceValues)
return false;
//
// If resourceValues is an Array, check if value is in the array
// or if it contains the ALLOW_ALL_FLAG.
if (Array.isArray(resourceValues) && resourceValues.includes(this.ALLOW_ALL_FLAG))
return true;
if (Array.isArray(resourceValues) && resourceValues.includes(value))
return true;
//
// If resourceValues is not an Array, check if it is equal to the requested value
if (resourceValues === value)
return true;
//
// Otherwise, return false
return false;
//
}
/**
* Sanitizes a list of permissions by removing any entries
* that do not correspond to valid scopes and actions
* defined in the PermissionCatalog.
* @param existingEntries Array of Permission objects to sanitize.
* @return A cleaned array containing only valid permissions.
*/
static sanitize(existingEntries) {
// Create a new array to hold valid permissions
const cleanedPermissions = {};
// Iterate through each permission entry of the user
for (const permissionEntry of existingEntries) {
// Validate the permission entry
const validationResult = PermissionSchema.safeParse(permissionEntry);
if (!validationResult.success)
continue;
// Permission is valid; keep it
cleanedPermissions[`${permissionEntry.scope}:${permissionEntry.action}`] = permissionEntry;
}
// Return the cleaned permissions array
return Object.values(cleanedPermissions);
}
}