A PostgreSQL extension that provides Role-Based Access Control (RBAC) with Row-Level Security (RLS) for enterprise applications. This extension pushes authorization logic to the database layer, ensuring consistent security across all application frameworks and direct database access.
Authorization rules enforced at the database level
Fine-grained access control on individual rows
Support for department, region, or any custom scope
Special global/all scope for administrative access
Works with Laravel, Rails, Django, and more
Includes indexes and efficient access checks
High-performance batch assignment for large user bases
Timestamp tracking and comprehensive reporting views
c77_rbac_bulk_assign_subjects()
for batch user assignmentsc77_rbac_revoke_subject_role()
and c77_rbac_revoke_feature()
c77_rbac_sync_admin_features()
and c77_rbac_sync_global_admin_features()
c77_rbac_user_permissions
and c77_rbac_summary
for reportingexternal_id
(typically your application's user ID)view_reports
, edit_users
, delete_records
department/engineering
, region/north
, global/all
-- Define features (permissions)
SELECT public.c77_rbac_grant_feature('manager', 'view_reports');
SELECT public.c77_rbac_grant_feature('manager', 'edit_reports');
SELECT public.c77_rbac_grant_feature('admin', 'manage_users');
-- Sync all features to admin role automatically
SELECT public.c77_rbac_sync_admin_features();
-- Single assignment
SELECT public.c77_rbac_assign_subject('123', 'manager', 'department', 'engineering');
-- Bulk assignment (NEW in v1.1)
SELECT * FROM public.c77_rbac_bulk_assign_subjects(
ARRAY['101','102','103','104','105'],
'employee',
'department',
'sales'
);
-- Global admin assignment
SELECT public.c77_rbac_assign_subject('1', 'admin', 'global', 'all');
-- Remove specific role assignment
SELECT public.c77_rbac_revoke_subject_role('123', 'manager', 'department', 'engineering');
-- Remove feature from role
SELECT public.c77_rbac_revoke_feature('temp_role', 'temporary_access');
-- Apply RLS policy to a table
SELECT public.c77_rbac_apply_policy(
'myschema.reports', -- table name (can be schema-qualified)
'view_reports', -- required feature
'department', -- scope type
'department_id' -- column containing scope value
);
-- Set the current user for RLS checks
SET "c77_rbac.external_id" TO '123';
-- Now queries automatically filter based on permissions
SELECT * FROM myschema.reports; -- Only shows reports for user's department
-- Get all roles for a user
SELECT * FROM public.c77_rbac_get_user_roles('123');
-- Get all features for a role
SELECT * FROM public.c77_rbac_get_role_features('manager');
-- View comprehensive permissions
SELECT * FROM public.c77_rbac_user_permissions
WHERE external_id = '123';
-- Get system statistics
SELECT * FROM public.c77_rbac_summary;
-- Check specific permission
SELECT public.c77_rbac_can_access('edit_reports', '123', 'department', 'engineering');
// Middleware to set user context
public function handle($request, Closure $next)
{
if (Auth::check()) {
DB::statement('SET "c77_rbac.external_id" TO ?', [Auth::id()]);
}
return $next($request);
}
// Bulk assign roles to users
$userIds = ['101', '102', '103', '104', '105'];
$results = DB::select("
SELECT * FROM public.c77_rbac_bulk_assign_subjects(?, ?, ?, ?)
", [json_encode($userIds), 'student', 'program', 'driver_education']);
// Check permissions
$canEdit = DB::selectOne("
SELECT public.c77_rbac_can_access(?, ?, ?, ?) AS allowed
", ['edit_reports', Auth::id(), 'department', 'engineering'])->allowed;
-- Works with any schema
CREATE SCHEMA finance;
CREATE TABLE finance.accounts (...);
-- Apply RLS with schema qualification
SELECT public.c77_rbac_apply_policy(
'finance.accounts',
'view_finance',
'department',
'dept_id'
);
-- Define court-specific roles and features
SELECT public.c77_rbac_grant_feature('court_admin', 'manage_all_programs');
SELECT public.c77_rbac_grant_feature('court_admin', 'view_all_participants');
SELECT public.c77_rbac_grant_feature('judge', 'approve_completions');
SELECT public.c77_rbac_grant_feature('counselor', 'update_progress');
SELECT public.c77_rbac_grant_feature('participant', 'view_own_progress');
-- Sync admin features
SELECT public.c77_rbac_sync_admin_features();
-- Enroll multiple participants in a DUI education program
SELECT * FROM public.c77_rbac_bulk_assign_subjects(
ARRAY['P001','P002','P003','P004','P005'],
'participant',
'program',
'dui_education_2025_q1'
);
-- Participants can only see their own data
SELECT public.c77_rbac_apply_policy(
'participant_progress',
'view_own_progress',
'participant',
'participant_id'
);
-- Court staff can see their court's participants
SELECT public.c77_rbac_apply_policy(
'participants',
'view_court_participants',
'court',
'assigned_court'
);
-- Use bulk operations for large datasets
SELECT * FROM public.c77_rbac_bulk_assign_subjects(
array_agg(user_id::text), 'role_name', 'scope_type', 'scope_id'
) FROM large_user_table;
-- Cache permission checks in your application
-- Check EXPLAIN ANALYZE for query performance
EXPLAIN ANALYZE SELECT * FROM protected_table;
c77_rbac_assign_subject(external_id, role, scope_type, scope_id)
- Assign role to userc77_rbac_grant_feature(role, feature)
- Grant feature to rolec77_rbac_can_access(feature, external_id, scope_type, scope_id)
- Check accessc77_rbac_apply_policy(table, feature, scope_type, column)
- Apply RLS policyc77_rbac_bulk_assign_subjects(external_ids[], role, scope_type, scope_id)
- Batch assign rolesc77_rbac_revoke_subject_role(external_id, role, scope_type, scope_id)
- Remove role assignmentc77_rbac_revoke_feature(role, feature)
- Remove feature from rolec77_rbac_sync_admin_features()
- Sync all features to admin rolec77_rbac_sync_global_admin_features()
- Sync features to all global/all rolesFramework-agnostic security that works everywhere!
This extension is designed to work with any application framework. Keep the core extension simple and focused while adding framework-specific features to companion extensions.