Socket
Book a DemoInstallSign in
Socket

@levante-framework/permissions-core

Package Overview
Dependencies
Maintainers
4
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@levante-framework/permissions-core

Shared permissions service for front-end and back-end

latest
npmnpm
Version
1.1.2
Version published
Weekly downloads
43
-6.52%
Maintainers
4
Weekly downloads
 
Created
Source

Permissions Service

A TypeScript package implementing a resource-based access control system for multi-site platforms. Designed for use in both frontend (Vue SPA) and backend (Firebase Cloud Functions) environments.

Features

  • Multi-site Support: Site-scoped permissions with super admin global access
  • Role Hierarchy: Five-tier role system from participant to super admin
  • Resource-based Access Control: Granular permissions with nested sub-resources for groups and admins
  • Caching: TTL-based caching with user-specific clearing and automatic cleanup
  • Decision Logging (opt-in): Configurable modes with pluggable sinks for observability
  • Version Management: Document validation and migration framework
  • TypeScript: Full type safety with comprehensive interfaces
  • ESM: Modern ES module support with source maps

Installation

npm install permissions-service

Quick Start

Basic Usage

import { PermissionService, CacheService } from 'permissions-service';

const cache = new CacheService();

const loggingConfig = { mode: 'off' as const }; // 'off' | 'baseline' | 'debug'
const sink = {
  isEnabled: () => loggingConfig.mode !== 'off',
  emit: (event) => {
    // no-op by default; plug in Firestore, console, etc.
  }
};

const permissions = new PermissionService(cache, loggingConfig, sink);

// Check if user can perform action on a nested resource
const canEdit = permissions.canPerformSiteAction(
  user,
  'site456',
  'groups',
  'update',
  'schools' // sub-resource required for groups
);

if (canEdit) {
  // User can edit schools
}

Cloud Functions Integration

// functions/src/permissions.ts
import { onCall, HttpsError } from 'firebase-functions/v2/https';
import { getFirestore } from 'firebase-admin/firestore';
import { PermissionService, CacheService } from 'permissions-service';

// Module-level cache for container persistence
const cache = new CacheService();

const loggingConfig = {
  mode: process.env.PERM_LOG_MODE ?? 'baseline'
};

const firestoreSink = {
  isEnabled: () => loggingConfig.mode !== 'off',
  emit: (event) => {
    setImmediate(async () => {
      await getFirestore()
        .collection('permission_events')
        .add({ ...event, expireAt: Date.now() + 1000 * 60 * 60 * 24 * 90 }); // 90-day TTL
    });
  }
};

export const updateGroup = onCall(async (request) => {
  const permissions = new PermissionService(cache, loggingConfig, firestoreSink);
  const { userId, siteId } = request.auth;
  
  // Check permission
  const canUpdate = await permissions.hasPermission(
    userId,
    siteId,
    'groups',
    'update'
  );
  
  if (!canUpdate) {
    throw new HttpsError('permission-denied', 'Insufficient permissions');
  }
  
  // Proceed with update
});

Vue SPA Integration

// composables/usePermissions.ts
import { PermissionService, CacheService } from 'permissions-service';
import { ref, computed } from 'vue';

// Session-level cache
const cache = new CacheService();
const permissions = new PermissionService(cache);

export function usePermissions() {
  const currentUser = ref(null);
  const currentSite = ref(null);
  
  const canCreateGroups = computed(async () => {
    if (!currentUser.value || !currentSite.value) return false;
    
    return await permissions.hasPermission(
      currentUser.value.id,
      currentSite.value.id,
      'groups',
      'create'
    );
  });
  
  return {
    canCreateGroups,
    hasPermission: permissions.hasPermission.bind(permissions)
  };
}

Logging & Observability

Permission decisions remain boolean for callers, but you can enable structured logging by supplying a LoggingModeConfig and sink:

import { PermissionService, CacheService } from 'permissions-service';

const cache = new CacheService();
const loggingConfig = { mode: 'baseline' as const };

const sink = {
  isEnabled: () => loggingConfig.mode !== 'off',
  emit: (event) => {
    // Persist to Firestore, enqueue to Pub/Sub, etc.
    // Keep payloads de-identified (avoid IP / user agent).
  }
};

const permissions = new PermissionService(cache, loggingConfig, sink);

Recommended sink patterns:

  • Firestore (backend) — write each event with a TTL:

    const FirestoreSink = {
      isEnabled: () => true,
      emit: (event) => {
        setImmediate(async () => {
          await db.collection('permission_events').add({
            ...event,
            expireAt: Date.now() + 1000 * 60 * 60 * 24 * 60 // 60 days
          });
        });
      }
    };
    
  • Beacon (frontend) — forward sampled events to an HTTPS endpoint:

    const BrowserSink = {
      isEnabled: () => true,
      emit: (event) => {
        const { userId, ...sanitized } = event; // strip identifiers if required
        navigator.sendBeacon('/api/permission-log', JSON.stringify(sanitized));
      }
    };
    

Toggle logging modes via environment variables or Remote Config ('off' → no emission, 'baseline' → minimal denies, 'debug' → full capture for investigations). Return to 'off' once debugging is complete to avoid unnecessary overhead.

Role Hierarchy

The system implements a five-tier role hierarchy:

  • participant - No admin dashboard access
  • research_assistant - Read access + user creation
  • admin - Subset of actions within their site
  • site_admin - Full control over their site's resources
  • super_admin - Full system access across all sites

Nested Permissions Structure

The permission system uses nested sub-resources for groups and admins:

Group Sub-Resources:

  • sites - Site-level groups
  • schools - School-level groups
  • classes - Class-level groups
  • cohorts - Cohort-level groups

Admin Sub-Resources:

  • site_admin - Site administrator accounts
  • admin - Admin accounts
  • research_assistant - Research assistant accounts

Flat Resources:

  • assignments - Task assignments
  • users - User accounts
  • tasks - System tasks

Permission Matrix Example

{
  "admin": {
    "groups": {
      "sites": ["read", "update"],
      "schools": ["read", "update", "delete"],
      "classes": ["read", "update", "delete"],
      "cohorts": ["read", "update", "delete"]
    },
    "admins": {
      "site_admin": ["read"],
      "admin": ["read"],
      "research_assistant": ["create", "read"]
    },
    "assignments": ["create", "read", "update", "delete"],
    "users": ["create", "read", "update"],
    "tasks": ["read"]
  }
}

API Reference

PermissionService

Constructor

new PermissionService(
  cache?: CacheService,
  loggingConfig?: LoggingModeConfig,
  sink?: PermEventSink
)
  • loggingConfig defaults to { mode: 'off' }.
  • sink defaults to the internal no-op sink; callers can supply Firestore/beacon/etc.

Methods

canPerformSiteAction(user, siteId, resource, action, subResource?)

Check if a user has permission to perform an action on a resource within a site.

// Nested resource (requires sub-resource)
const canEditSchools = permissions.canPerformSiteAction(
  user,
  'site456', 
  'groups',
  'update',
  'schools' // required for nested resources
);

// Flat resource (no sub-resource needed)
const canEditUsers = permissions.canPerformSiteAction(
  user,
  'site456',
  'users',
  'update'
);
canPerformGlobalAction(user, resource, action, subResource?)

Check if a super admin can perform a global action.

const canManageAdmins = permissions.canPerformGlobalAction(
  superAdminUser,
  'admins',
  'delete',
  'admin'
);
bulkPermissionCheck(user, siteId, checks)

Bulk permission checking for multiple resource/action combinations.

const results = permissions.bulkPermissionCheck(user, 'site456', [
  { resource: 'groups', action: 'create', subResource: 'schools' },
  { resource: 'users', action: 'read' }
]);
// Returns: [{ resource: 'groups', action: 'create', subResource: 'schools', allowed: true }, ...]
getAccessibleResources(user, siteId, action)

Get flat resources the user can perform an action on.

const resources = permissions.getAccessibleResources(user, 'site456', 'create');
// Returns: ['assignments', 'users'] (only flat resources)
getAccessibleGroupSubResources(user, siteId, action)

Get group sub-resources the user can perform an action on.

const groupTypes = permissions.getAccessibleGroupSubResources(user, 'site456', 'create');
// Returns: ['schools', 'classes', 'cohorts']
getAccessibleAdminSubResources(user, siteId, action)

Get admin sub-resources the user can perform an action on.

const adminTypes = permissions.getAccessibleAdminSubResources(user, 'site456', 'create');
// Returns: ['research_assistant']
getUserRole(userId, siteId)

Get the user's role for a specific site.

const role = await permissions.getUserRole('user123', 'site456');
// Returns: 'admin' | 'site_admin' | etc.
clearUserCache(userId)

Clear cached data for a specific user.

await permissions.clearUserCache('user123');

CacheService

Constructor

new CacheService(defaultTtl?: number) // Default: 5 minutes

Methods

get(key)

Retrieve cached value.

const value = cache.get('user:123:permissions');
set(key, value, ttl?)

Store value in cache with optional TTL.

cache.set('user:123:permissions', permissions, 300000); // 5 minutes
delete(key) / clear()

Remove specific key or clear entire cache.

cache.delete('user:123:permissions');
cache.clear();

User Data Structure

Users must have the following structure in Firestore:

interface User {
  id: string;
  roles: Array<{
    siteId: string;
    role: 'participant' | 'research_assistant' | 'admin' | 'site_admin' | 'super_admin';
  }>;
  userType?: 'admin' | 'student' | 'teacher' | 'caregiver';
}

Permission Matrix Document

The system expects a permission matrix document with nested structure:

interface PermissionMatrix {
  [role: string]: {
    groups: {
      sites: Action[];
      schools: Action[];
      classes: Action[];
      cohorts: Action[];
    };
    admins: {
      site_admin: Action[];
      admin: Action[];
      research_assistant: Action[];
    };
    assignments: Action[];
    users: Action[];
    tasks: Action[];
  };
}

interface PermissionDocument {
  permissions: PermissionMatrix;
  version: string;
  updatedAt: string;
}

Example Document (stored at system/permissions):

{
  "permissions": {
    "site_admin": {
      "groups": {
        "sites": ["read", "update"],
        "schools": ["create", "read", "update", "delete", "exclude"],
        "classes": ["create", "read", "update", "delete", "exclude"],
        "cohorts": ["create", "read", "update", "delete", "exclude"]
      },
      "assignments": ["create", "read", "update", "delete", "exclude"],
      "users": ["create", "read", "update", "delete", "exclude"],
      "admins": {
        "site_admin": ["create", "read"],
        "admin": ["create", "read", "update", "delete", "exclude"],
        "research_assistant": ["create", "read", "update", "delete"]
      },
      "tasks": ["create", "read", "update", "delete", "exclude"]
    }
  },
  "version": "1.1.0",
  "updatedAt": "2025-09-29T00:00:00Z"
}

Error Handling

The service throws specific errors for different scenarios:

try {
  const canEdit = await permissions.hasPermission(userId, siteId, 'groups', 'update');
} catch (error) {
  if (error.message.includes('User not found')) {
    // Handle missing user
  } else if (error.message.includes('Permission matrix not found')) {
    // Handle missing configuration
  }
}

Performance Considerations

Caching Strategy

  • Frontend: Session-level cache, cleared on user/site changes
  • Backend: Module-level cache for container persistence
  • TTL: Default 5 minutes, configurable per cache instance
  • Bulk Operations: Use hasPermissions() for multiple checks

Best Practices

  • Reuse Cache Instances: Create once per session/container
  • Bulk Checks: Use hasPermissions() for multiple permission checks
  • Clear Cache: Clear user cache after role changes
  • Error Handling: Always handle permission check failures gracefully

Development

Build

npm run build    # Compile TypeScript
npm run dev      # Watch mode
npm run clean    # Remove dist directory

Testing

npm test         # Run tests in watch mode
npm run test:run # Run tests once

Package Testing

npm pack         # Create tarball for local testing

Migration from Organization-based Permissions

This package replaces organization-based permissions with resource-based permissions. Key changes:

  • Roles are now site-scoped instead of organization-scoped
  • Permissions are defined per resource/action combination
  • Super admin role provides global access across all sites
  • No permission management UI (roles are backend-managed)

License

TBD

Keywords

permissions

FAQs

Package last updated on 14 Oct 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts