Gathering detailed insights and metrics for permission-sync-tool
Gathering detailed insights and metrics for permission-sync-tool
Gathering detailed insights and metrics for permission-sync-tool
Gathering detailed insights and metrics for permission-sync-tool
npm install permission-sync-tool
Typescript
Module System
Min. Node Version
Node Version
NPM Version
TypeScript (79.26%)
JavaScript (20.02%)
Shell (0.71%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
2 Stars
31 Commits
1 Forks
1 Branches
1 Contributors
Updated on Jun 04, 2025
Latest Version
2.0.2
Package Id
permission-sync-tool@2.0.2
Unpacked Size
66.38 kB
Size
17.75 kB
File Count
54
NPM Version
10.9.2
Node Version
23.6.0
Published on
Jun 03, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
5
5
24
A powerful CLI tool and utility library for seamlessly syncing roles and permissions to your Prisma database while generating type-safe CASL abilities for NestJS, React, and TypeScript applications.
🚀 Perfect for teams using Prisma, CASL, and TypeScript who need robust, type-safe permission management.
Get up and running in under 5 minutes:
1# 1. Install the package 2npm install permission-sync-tool 3 4# 2. Set up your Prisma schema (see Configuration section) 5 6# 3. Create your roles configuration 7echo "export const roles = { 8 Admin: [{ action: 'manage', subject: 'all' }], 9 User: [{ action: 'read', subject: 'Post' }] 10}" > config/roles.ts 11 12# 4. Sync permissions to database 13npx permission-sync-tool sync --config=./config/roles.ts
1npm install permission-sync-tool 2# or 3yarn add permission-sync-tool 4# or 5pnpm add permission-sync-tool
1npm install -g permission-sync-tool
1git clone https://github.com/kennywam/permission-sync-tool.git 2cd permission-sync-tool 3npm install 4npm run build
Add these models to your schema.prisma
:
1model User { 2 id String @id @default(cuid()) 3 email String @unique 4 name String? 5 roles Role[] @relation("UserRole") 6 // ... your other fields 7} 8 9model Role { 10 id String @id @default(cuid()) 11 name String @unique 12 description String? 13 permissions Permission[] @relation("RolePermission") 14 users User[] @relation("UserRole") 15 16 createdAt DateTime @default(now()) 17 updatedAt DateTime @updatedAt 18} 19 20model Permission { 21 id String @id @default(cuid()) 22 action String 23 subject String 24 roles Role[] @relation("RolePermission") 25 26 createdAt DateTime @default(now()) 27 updatedAt DateTime @updatedAt 28 29 @@unique([action, subject]) 30}
1npx prisma db push 2# or 3npx prisma migrate dev --name add-permissions
Create a configuration file (e.g., config/roles.ts
):
1// config/roles.ts 2 3export const roles = { 4 // Super Admin - can do everything 5 SuperAdmin: [ 6 { action: 'manage', subject: 'all' }, 7 ], 8 9 // Admin - can manage most resources 10 Admin: [ 11 { action: 'create', subject: 'User' }, 12 { action: 'read', subject: 'User' }, 13 { action: 'update', subject: 'User' }, 14 { action: 'delete', subject: 'User' }, 15 { action: 'manage', subject: 'Post' }, 16 { action: 'manage', subject: 'Comment' }, 17 ], 18 19 // Editor - can manage content 20 Editor: [ 21 { action: 'create', subject: 'Post' }, 22 { action: 'read', subject: 'Post' }, 23 { action: 'update', subject: 'Post' }, 24 { action: 'delete', subject: 'Post' }, 25 { action: 'manage', subject: 'Comment' }, 26 ], 27 28 // User - basic permissions 29 User: [ 30 { action: 'read', subject: 'Post' }, 31 { action: 'create', subject: 'Comment' }, 32 { action: 'update', subject: 'Comment', conditions: { authorId: '${user.id}' } }, 33 { action: 'delete', subject: 'Comment', conditions: { authorId: '${user.id}' } }, 34 ], 35 36 // Guest - read-only access 37 Guest: [ 38 { action: 'read', subject: 'Post' }, 39 ], 40} as const; 41 42export type RoleName = keyof typeof roles;
1# .env 2DATABASE_URL="postgresql://username:password@localhost:5432/mydb"
1# Sync with default config (./config/roles.ts) 2npx permission-sync-tool sync 3 4# Sync with custom config path 5npx permission-sync-tool sync --config=./path/to/roles.ts 6 7# Sync with environment-specific config 8npx permission-sync-tool sync --config=./config/roles.prod.ts
1# Dry run - see what would be synced without making changes 2npx permission-sync-tool sync --dry-run 3 4# Verbose output 5npx permission-sync-tool sync --verbose 6 7# Force sync (recreate all permissions) 8npx permission-sync-tool sync --force 9 10# Sync specific roles only 11npx permission-sync-tool sync --roles=Admin,User 12 13# Help 14npx permission-sync-tool --help
Add to your package.json
:
1{ 2 "scripts": { 3 "sync-permissions": "permission-sync-tool sync", 4 "sync-permissions:prod": "permission-sync-tool sync --config=./config/roles.prod.ts", 5 "build": "npm run sync-permissions && next build" 6 } 7}
1// guards/permissions.guard.ts 2import { 3 CanActivate, 4 ExecutionContext, 5 Injectable, 6 ForbiddenException, 7 UnauthorizedException, 8} from '@nestjs/common'; 9import { Reflector } from '@nestjs/core'; 10import { Request } from 'express'; 11import { defineAbilityFor } from 'permission-sync-tool'; 12import { AppActions, AppSubjects } from '../types/permissions'; 13 14interface AuthenticatedRequest extends Request { 15 user: { 16 id: string; 17 permissions: Array<{ action: string; subject: string; conditions?: any }>; 18 roles: Array<{ name: string }>; 19 }; 20} 21 22@Injectable() 23export class PermissionsGuard implements CanActivate { 24 constructor(private reflector: Reflector) {} 25 26 canActivate(context: ExecutionContext): boolean { 27 const requiredPermissions = this.reflector.get< 28 Array<{ action: AppActions; subject: AppSubjects }> 29 >('check_permissions', context.getHandler()); 30 31 if (!requiredPermissions) { 32 return true; // No permissions required 33 } 34 35 const request = context.switchToHttp().getRequest<AuthenticatedRequest>(); 36 const user = request.user; 37 38 if (!user) { 39 throw new UnauthorizedException('Authentication required'); 40 } 41 42 if (!user.permissions || user.permissions.length === 0) { 43 throw new ForbiddenException('No permissions assigned'); 44 } 45 46 const ability = defineAbilityFor(user.permissions); 47 48 for (const permission of requiredPermissions) { 49 if (!ability.can(permission.action, permission.subject)) { 50 throw new ForbiddenException( 51 `Missing permission: ${permission.action} on ${permission.subject}` 52 ); 53 } 54 } 55 56 return true; 57 } 58}
1// decorators/permissions.decorator.ts 2import { SetMetadata } from '@nestjs/common'; 3import { AppActions, AppSubjects } from '../types/permissions'; 4 5export interface RequiredPermission { 6 action: AppActions; 7 subject: AppSubjects; 8} 9 10export const CheckPermissions = (...permissions: RequiredPermission[]) => 11 SetMetadata('check_permissions', permissions);
1// controllers/posts.controller.ts 2import { Controller, Get, Post, Body, UseGuards } from '@nestjs/common'; 3import { CheckPermissions } from '../decorators/permissions.decorator'; 4import { PermissionsGuard } from '../guards/permissions.guard'; 5import { AuthGuard } from '../guards/auth.guard'; 6 7@Controller('posts') 8@UseGuards(AuthGuard, PermissionsGuard) 9export class PostsController { 10 @Get() 11 @CheckPermissions({ action: 'read', subject: 'Post' }) 12 findAll() { 13 return this.postsService.findAll(); 14 } 15 16 @Post() 17 @CheckPermissions({ action: 'create', subject: 'Post' }) 18 create(@Body() createPostDto: CreatePostDto) { 19 return this.postsService.create(createPostDto); 20 } 21}
1// services/permissions.service.ts 2import { Injectable } from '@nestjs/common'; 3import { PrismaService } from './prisma.service'; 4import { defineAbilityFor } from 'permission-sync-tool'; 5 6@Injectable() 7export class PermissionsService { 8 constructor(private prisma: PrismaService) {} 9 10 async getUserPermissions(userId: string) { 11 const user = await this.prisma.user.findUnique({ 12 where: { id: userId }, 13 include: { 14 roles: { 15 include: { 16 permissions: true, 17 }, 18 }, 19 }, 20 }); 21 22 const permissions = user?.roles.flatMap(role => 23 role.permissions.map(p => ({ 24 action: p.action, 25 subject: p.subject, 26 })) 27 ) || []; 28 29 return defineAbilityFor(permissions); 30 } 31}
1// hooks/usePermissions.ts 2import { useMemo, useContext } from 'react'; 3import { defineAbilityFor } from 'permission-sync-tool'; 4import { AuthContext } from '../contexts/AuthContext'; 5import { AppActions, AppSubjects } from '../types/permissions'; 6 7export interface Permission { 8 action: AppActions; 9 subject: AppSubjects; 10 conditions?: any; 11} 12 13export function usePermissions() { 14 const { user } = useContext(AuthContext); 15 16 const ability = useMemo(() => { 17 if (!user?.permissions) return null; 18 return defineAbilityFor(user.permissions); 19 }, [user?.permissions]); 20 21 const can = (action: AppActions, subject: AppSubjects, resource?: any) => { 22 return ability?.can(action, subject, resource) ?? false; 23 }; 24 25 const cannot = (action: AppActions, subject: AppSubjects, resource?: any) => { 26 return !can(action, subject, resource); 27 }; 28 29 return { can, cannot, ability }; 30}
1// components/PermissionGate.tsx 2import React from 'react'; 3import { usePermissions } from '../hooks/usePermissions'; 4import { AppActions, AppSubjects } from '../types/permissions'; 5 6interface PermissionGateProps { 7 action: AppActions; 8 subject: AppSubjects; 9 resource?: any; 10 children: React.ReactNode; 11 fallback?: React.ReactNode; 12} 13 14export function PermissionGate({ 15 action, 16 subject, 17 resource, 18 children, 19 fallback = null, 20}: PermissionGateProps) { 21 const { can } = usePermissions(); 22 23 if (can(action, subject, resource)) { 24 return <>{children}</>; 25 } 26 27 return <>{fallback}</>; 28}
1// components/PostList.tsx 2import React from 'react'; 3import { PermissionGate } from './PermissionGate'; 4import { usePermissions } from '../hooks/usePermissions'; 5 6export function PostList({ posts }) { 7 const { can } = usePermissions(); 8 9 return ( 10 <div> 11 <PermissionGate action="create" subject="Post"> 12 <button>Create New Post</button> 13 </PermissionGate> 14 15 {posts.map(post => ( 16 <div key={post.id}> 17 <h3>{post.title}</h3> 18 <p>{post.content}</p> 19 20 <PermissionGate 21 action="update" 22 subject="Post" 23 resource={post} 24 > 25 <button>Edit</button> 26 </PermissionGate> 27 28 {can('delete', 'Post', post) && ( 29 <button onClick={() => deletePost(post.id)}> 30 Delete 31 </button> 32 )} 33 </div> 34 ))} 35 </div> 36 ); 37}
defineAbilityFor(permissions)
Creates a CASL ability instance from an array of permissions.
1import { defineAbilityFor } from 'permission-sync-tool'; 2 3const permissions = [ 4 { action: 'read', subject: 'Post' }, 5 { action: 'create', subject: 'Comment' } 6]; 7 8const ability = defineAbilityFor(permissions); 9console.log(ability.can('read', 'Post')); // true
syncPermissions(config)
Syncs roles and permissions to the database.
1import { syncPermissions } from 'permission-sync-tool'; 2 3await syncPermissions({ 4 configPath: './config/roles.ts', 5 dryRun: false, 6 verbose: true 7});
1// Available action types 2export type AppActions = 3 | 'manage' 4 | 'create' 5 | 'read' 6 | 'update' 7 | 'delete'; 8 9// Available subject types (extend as needed) 10export type AppSubjects = 11 | 'all' 12 | 'User' 13 | 'Post' 14 | 'Comment' 15 | string; 16 17// Permission interface 18export interface Permission { 19 action: AppActions; 20 subject: AppSubjects; 21 conditions?: any; 22}
1// config/roles.ts 2export const roles = { 3 User: [ 4 { action: 'read', subject: 'Post' }, 5 { 6 action: 'update', 7 subject: 'Post', 8 conditions: { authorId: '${user.id}' } 9 }, 10 { 11 action: 'delete', 12 subject: 'Comment', 13 conditions: { authorId: '${user.id}' } 14 }, 15 ], 16};
1// types/permissions.ts 2export type AppSubjects = 3 | 'all' 4 | 'User' 5 | 'Post' 6 | 'Comment' 7 | 'Analytics' 8 | 'Settings' 9 | 'Billing';
1// config/tenant-roles.ts 2export const createTenantRoles = (tenantId: string) => ({ 3 TenantAdmin: [ 4 { 5 action: 'manage', 6 subject: 'all', 7 conditions: { tenantId } 8 }, 9 ], 10 TenantUser: [ 11 { 12 action: 'read', 13 subject: 'Post', 14 conditions: { tenantId } 15 }, 16 ], 17});
1// config/roles.prod.ts 2export const roles = { 3 Admin: [ 4 { action: 'read', subject: 'User' }, 5 { action: 'update', subject: 'User' }, 6 // No delete in production 7 ], 8}; 9 10// config/roles.dev.ts 11export const roles = { 12 Admin: [ 13 { action: 'manage', subject: 'all' }, // Full access in development 14 ], 15};
1# Ensure the package is installed 2npm list permission-sync-tool 3 4# Reinstall if necessary 5npm uninstall permission-sync-tool 6npm install permission-sync-tool
1# Check your DATABASE_URL 2echo $DATABASE_URL 3 4# Test Prisma connection 5npx prisma db pull
1// Ensure you have proper types 2import type { AppActions, AppSubjects } from 'permission-sync-tool';
1# Run with verbose output 2npx permission-sync-tool sync --verbose 3 4# Check your config file path 5npx permission-sync-tool sync --config=./config/roles.ts --dry-run
1# Enable debug logging 2DEBUG=permission-sync-tool npx permission-sync-tool sync
We welcome contributions! Please see our Contributing Guide for details.
1git clone https://github.com/kennywam/permission-sync-tool.git 2cd permission-sync-tool 3npm install 4npm run dev
This project follows the Conventional Commits specification. All commit messages must adhere to this format to ensure clear communication and automated versioning.
<type>(<scope>): <description>
To make it easier to follow this convention, you can use our commit script:
1npm run commit
This will guide you through creating a properly formatted commit message.
1npm test 2npm run test:watch 3npm run test:coverage
We use Prettier and ESLint:
1npm run lint 2npm run format
This project is licensed under the ISC License.
Built with ❤️ by Kenny and contributors
No vulnerabilities found.
No security vulnerabilities found.