@noony-serverless/core
Version:
A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript
248 lines • 9.74 kB
JavaScript
;
/**
* Plain Permission Resolver
*
* Fastest permission resolver using direct O(1) set membership checks.
* Optimized for high-performance scenarios where sub-millisecond permission
* checks are critical. No pattern matching or complex logic - just pure
* set-based lookups for maximum speed.
*
* Use Cases:
* - High-traffic API endpoints requiring maximum performance
* - Simple permission models without wildcards or expressions
* - Scenarios where all required permissions are known at compile time
* - Serverless functions with strict latency requirements
*
* Performance Characteristics:
* - Time Complexity: O(1) for permission lookup
* - Space Complexity: O(n) where n is the number of user permissions
* - Cache Utilization: None (no caching needed due to speed)
* - Memory Footprint: Minimal (uses JavaScript Set)
*
* @author Noony Framework Team
* @version 1.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PlainPermissionResolver = void 0;
const PermissionResolver_1 = require("./PermissionResolver");
/**
* Plain permission resolver for direct O(1) permission checks
*
* This resolver performs the simplest and fastest permission checks by
* using JavaScript Set's has() method for O(1) membership testing.
* It checks if the user has ANY of the required permissions (OR logic).
*/
class PlainPermissionResolver extends PermissionResolver_1.PermissionResolver {
checkCount = 0;
totalResolutionTimeUs = 0;
/**
* Check if user has any of the required permissions
*
* Uses OR logic: user needs only ONE of the required permissions
* to pass the check. This is the most common permission pattern.
*
* @param userPermissions - Set of user's permissions for O(1) lookup
* @param requiredPermissions - Array of required permissions (OR logic)
* @returns Promise resolving to true if user has any required permission
*/
async check(userPermissions, requiredPermissions) {
const startTime = process.hrtime.bigint();
try {
// Validate inputs
if (!userPermissions || userPermissions.size === 0) {
return false;
}
if (!requiredPermissions || requiredPermissions.length === 0) {
return false; // No permissions required means access denied
}
// Validate permission format
for (const permission of requiredPermissions) {
if (!PermissionResolver_1.PermissionUtils.isValidPermission(permission)) {
throw new Error(`Invalid permission format: ${permission}`);
}
}
// O(1) set membership check for each required permission
// Return true as soon as we find a match (short-circuit OR)
for (const requiredPermission of requiredPermissions) {
if (userPermissions.has(requiredPermission)) {
return true;
}
}
return false;
}
finally {
// Track performance metrics
const endTime = process.hrtime.bigint();
const resolutionTimeUs = Number(endTime - startTime) / 1000; // Convert to microseconds
this.checkCount++;
this.totalResolutionTimeUs += resolutionTimeUs;
}
}
/**
* Check permissions with detailed result information
*
* Provides additional metadata about the permission check for
* debugging, monitoring, and audit purposes.
*
* @param userPermissions - Set of user's permissions
* @param requiredPermissions - Array of required permissions
* @returns Detailed permission check result
*/
async checkWithResult(userPermissions, requiredPermissions) {
const startTime = process.hrtime.bigint();
const matchedPermissions = [];
try {
// Validate inputs
if (!userPermissions || userPermissions.size === 0) {
return {
allowed: false,
resolverType: this.getType(),
resolutionTimeUs: 0,
cached: false,
reason: 'User has no permissions',
};
}
if (!requiredPermissions || requiredPermissions.length === 0) {
return {
allowed: false,
resolverType: this.getType(),
resolutionTimeUs: 0,
cached: false,
reason: 'No permissions specified',
};
}
// Find all matching permissions (not just the first one)
for (const requiredPermission of requiredPermissions) {
if (!PermissionResolver_1.PermissionUtils.isValidPermission(requiredPermission)) {
throw new Error(`Invalid permission format: ${requiredPermission}`);
}
if (userPermissions.has(requiredPermission)) {
matchedPermissions.push(requiredPermission);
}
}
const allowed = matchedPermissions.length > 0;
const endTime = process.hrtime.bigint();
const resolutionTimeUs = Number(endTime - startTime) / 1000;
return {
allowed,
resolverType: this.getType(),
resolutionTimeUs,
cached: false, // Plain resolver doesn't use caching
reason: allowed
? undefined
: `Missing required permissions: ${requiredPermissions.join(', ')}`,
matchedPermissions: allowed ? matchedPermissions : undefined,
};
}
catch (error) {
const endTime = process.hrtime.bigint();
const resolutionTimeUs = Number(endTime - startTime) / 1000;
return {
allowed: false,
resolverType: this.getType(),
resolutionTimeUs,
cached: false,
reason: error instanceof Error ? error.message : 'Unknown error',
};
}
}
/**
* Check if user has ALL required permissions (AND logic)
*
* Alternative method for scenarios requiring ALL permissions
* instead of ANY permission. Less commonly used but available
* for completeness.
*
* @param userPermissions - Set of user's permissions
* @param requiredPermissions - Array of ALL required permissions
* @returns Promise resolving to true if user has ALL permissions
*/
async checkAllRequired(userPermissions, requiredPermissions) {
const startTime = process.hrtime.bigint();
try {
// Validate inputs
if (!userPermissions || userPermissions.size === 0) {
return false;
}
if (!requiredPermissions || requiredPermissions.length === 0) {
return true; // No permissions required means access allowed
}
// Check that user has ALL required permissions
for (const requiredPermission of requiredPermissions) {
if (!PermissionResolver_1.PermissionUtils.isValidPermission(requiredPermission)) {
throw new Error(`Invalid permission format: ${requiredPermission}`);
}
if (!userPermissions.has(requiredPermission)) {
return false; // Missing at least one required permission
}
}
return true;
}
finally {
// Track performance metrics
const endTime = process.hrtime.bigint();
const resolutionTimeUs = Number(endTime - startTime) / 1000;
this.checkCount++;
this.totalResolutionTimeUs += resolutionTimeUs;
}
}
/**
* Get resolver type for identification
*/
getType() {
return PermissionResolver_1.PermissionResolverType.PLAIN;
}
/**
* Get performance characteristics for monitoring
*/
getPerformanceCharacteristics() {
return {
timeComplexity: 'O(1) per permission check',
memoryUsage: 'low',
cacheUtilization: 'none',
recommendedFor: [
'High-traffic API endpoints',
'Sub-millisecond permission checks',
'Simple permission models',
'Serverless functions',
'Performance-critical paths',
],
};
}
/**
* Get performance statistics for monitoring
*/
getStats() {
return {
checkCount: this.checkCount,
averageResolutionTimeUs: this.checkCount > 0 ? this.totalResolutionTimeUs / this.checkCount : 0,
totalResolutionTimeUs: this.totalResolutionTimeUs,
};
}
/**
* Reset performance statistics
*/
resetStats() {
this.checkCount = 0;
this.totalResolutionTimeUs = 0;
}
/**
* Get resolver name for debugging
*/
getName() {
return 'PlainPermissionResolver';
}
/**
* Check if this resolver can handle the given requirement type
*
* @param requirement - The requirement to check
* @returns true if this resolver can handle the requirement
*/
canHandle(requirement) {
return (Array.isArray(requirement) &&
requirement.length > 0 &&
requirement.every((item) => typeof item === 'string'));
}
}
exports.PlainPermissionResolver = PlainPermissionResolver;
//# sourceMappingURL=PlainPermissionResolver.js.map