rbac-validator
Validates role-based access control (RBAC) implementation for four-tier permissions in the NABIP AMS (Member, Chapter Admin, State Admin, National Admin). Use when implementing permission checks, RLS policies, UI access controls, or audit logging for multi-tenant association management.
When & Why to Use This Skill
The RBAC Validator is a specialized Claude skill designed to implement and verify complex four-tier permission structures (Member, Chapter, State, and National) for multi-tenant association management systems. It streamlines the creation of Supabase Row Level Security (RLS) policies, React permission hooks, and secure API middleware, ensuring robust data isolation and comprehensive audit trails for sensitive operations.
Use Cases
- Case 1: Designing hierarchical access control for organizations with multi-level administrative structures like Chapters, States, and National offices.
- Case 2: Generating and validating Supabase Row Level Security (RLS) policies to enforce strict data privacy at the database level.
- Case 3: Implementing conditional UI rendering in React applications to show or hide features based on specific user roles and data scopes.
- Case 4: Securing API endpoints with RBAC middleware to prevent unauthorized resource access and ensure multi-tenant security.
- Case 5: Establishing automated audit logging for privileged administrative actions to maintain a secure and compliant system history.
| name | rbac-validator |
|---|---|
| description | Validates role-based access control (RBAC) implementation for four-tier permissions in the NABIP AMS (Member, Chapter Admin, State Admin, National Admin). Use when implementing permission checks, RLS policies, UI access controls, or audit logging for multi-tenant association management. |
RBAC Validator
Establish comprehensive access control rules to ensure secure, scalable permission management across the NABIP association's hierarchical structure.
When to Use
Activate this skill when:
- Implementing role-based permissions (Member, Chapter Admin, State Admin, National Admin)
- Creating Supabase Row Level Security (RLS) policies
- Designing permission middleware for API routes
- Building conditional UI rendering based on roles
- Implementing audit logging for privileged actions
- Working on chapter hierarchy access control
- Creating permission management interfaces
- Validating data access patterns
NABIP RBAC Structure
Four-Tier Role Hierarchy
type UserRole = "member" | "chapter_admin" | "state_admin" | "national_admin"
interface RolePermissions {
role: UserRole
permissions: Permission[]
dataScope: DataScope
}
interface Permission {
resource: string // "members", "chapters", "events", "finances", etc.
actions: Action[] // "create", "read", "update", "delete"
}
type Action = "create" | "read" | "update" | "delete" | "approve" | "export"
interface DataScope {
level: "self" | "chapter" | "state" | "national"
chapterIds?: string[]
stateIds?: string[]
}
Permission Matrix
| Resource | Member | Chapter Admin | State Admin | National Admin |
|---|---|---|---|---|
| Own Profile | Read, Update | Read, Update | Read, Update | Read, Update |
| Other Members | Read (public) | Read, Update (chapter) | Read, Update (state) | Full Access |
| Chapters | Read (own) | Read, Update (own) | Read, Update (state) | Full Access |
| Events | Read, Register | Create, Manage (chapter) | Create, Manage (state) | Full Access |
| Finances | Read (own transactions) | Read (chapter) | Read, Export (state) | Full Access |
| Reports | None | Chapter reports | State reports | Full Access |
| System Settings | None | None | None | Full Access |
Role Definitions
1. Member (Base Role)
- Data Scope: Self only
- Permissions:
- Read own profile and membership details
- Update own contact information and preferences
- Register for events
- View own transaction history
- Enroll in courses
- View chapter public information
2. Chapter Admin
- Data Scope: Specific chapter(s)
- Permissions:
- All Member permissions
- Manage chapter members (view, edit, approve)
- Create and manage chapter events
- View chapter financial reports
- Send communications to chapter members
- Approve/deny chapter membership requests
- Assign chapter roles
3. State Admin
- Data Scope: All chapters within state(s)
- Permissions:
- All Chapter Admin permissions (across state)
- Manage state-level chapters
- Create state-wide events
- View/export state financial reports
- Approve chapter creation requests
- Manage chapter admins within state
- Access state-wide analytics
4. National Admin
- Data Scope: All data (unrestricted)
- Permissions:
- All State Admin permissions (across all states)
- Manage national organization settings
- Create/modify roles and permissions
- Access all financial data
- Manage system configurations
- View audit logs
- Export all data
Implementation Patterns
Supabase RLS Policies
-- Example: Members table RLS policies
-- 1. Members can view own data
CREATE POLICY "members_select_own"
ON members FOR SELECT
USING (auth.uid() = id);
-- 2. Chapter admins can view chapter members
CREATE POLICY "members_select_chapter_admin"
ON members FOR SELECT
USING (
EXISTS (
SELECT 1 FROM user_roles ur
WHERE ur.user_id = auth.uid()
AND ur.role = 'chapter_admin'
AND members.chapter_id = ANY(ur.chapter_ids)
)
);
-- 3. State admins can view state members
CREATE POLICY "members_select_state_admin"
ON members FOR SELECT
USING (
EXISTS (
SELECT 1 FROM user_roles ur
JOIN chapters c ON c.id = members.chapter_id
WHERE ur.user_id = auth.uid()
AND ur.role = 'state_admin'
AND c.state = ANY(ur.state_ids)
)
);
-- 4. National admins can view all members
CREATE POLICY "members_select_national_admin"
ON members FOR SELECT
USING (
EXISTS (
SELECT 1 FROM user_roles ur
WHERE ur.user_id = auth.uid()
AND ur.role = 'national_admin'
)
);
-- Similar patterns for UPDATE, INSERT, DELETE
CREATE POLICY "members_update_own"
ON members FOR UPDATE
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
CREATE POLICY "members_update_chapter_admin"
ON members FOR UPDATE
USING (
EXISTS (
SELECT 1 FROM user_roles ur
WHERE ur.user_id = auth.uid()
AND ur.role IN ('chapter_admin', 'state_admin', 'national_admin')
AND members.chapter_id = ANY(ur.chapter_ids)
)
);
React Permission Hooks
// usePermissions.ts
import { useKV } from "@github/spark"
interface UserRole {
role: UserRole
chapterIds?: string[]
stateIds?: string[]
}
export function usePermissions() {
const [currentUser] = useKV("currentUser")
const hasPermission = (
resource: string,
action: Action,
targetData?: { chapterId?: string; stateId?: string; ownerId?: string }
): boolean => {
if (!currentUser?.role) return false
// National admin has full access
if (currentUser.role === "national_admin") return true
// Check based on role and data scope
switch (currentUser.role) {
case "state_admin":
if (targetData?.stateId && currentUser.stateIds?.includes(targetData.stateId)) {
return hasStateAdminPermission(resource, action)
}
break
case "chapter_admin":
if (targetData?.chapterId && currentUser.chapterIds?.includes(targetData.chapterId)) {
return hasChapterAdminPermission(resource, action)
}
break
case "member":
if (targetData?.ownerId === currentUser.id) {
return hasMemberPermission(resource, action)
}
break
}
return false
}
const canAccessChapter = (chapterId: string): boolean => {
if (currentUser.role === "national_admin") return true
if (currentUser.role === "state_admin") {
// Check if chapter belongs to admin's state(s)
return checkChapterInStates(chapterId, currentUser.stateIds)
}
if (currentUser.role === "chapter_admin") {
return currentUser.chapterIds?.includes(chapterId) || false
}
return false
}
const canAccessState = (stateId: string): boolean => {
if (currentUser.role === "national_admin") return true
if (currentUser.role === "state_admin") {
return currentUser.stateIds?.includes(stateId) || false
}
return false
}
return {
hasPermission,
canAccessChapter,
canAccessState,
role: currentUser.role,
isNationalAdmin: currentUser.role === "national_admin",
isStateAdmin: currentUser.role === "state_admin",
isChapterAdmin: currentUser.role === "chapter_admin",
isMember: currentUser.role === "member"
}
}
// Usage in components
export function MemberListPage() {
const permissions = usePermissions()
if (!permissions.hasPermission("members", "read")) {
return <UnauthorizedPage />
}
return (
<div>
<h1>Member Directory</h1>
{permissions.hasPermission("members", "create") && (
<Button>Add New Member</Button>
)}
{/* ... */}
</div>
)
}
API Route Protection
// middleware/rbac.ts
import { createClient } from "@/lib/supabase/server"
interface ProtectedRouteOptions {
resource: string
action: Action
requiredRole?: UserRole | UserRole[]
}
export async function withRBAC(
handler: (req: Request, context: any) => Promise<Response>,
options: ProtectedRouteOptions
) {
return async (req: Request, context: any) => {
const supabase = createClient()
// Get authenticated user
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
return new Response("Unauthorized", { status: 401 })
}
// Get user roles
const { data: userRoles, error: roleError } = await supabase
.from("user_roles")
.select("*")
.eq("user_id", user.id)
.single()
if (roleError || !userRoles) {
return new Response("No role assigned", { status: 403 })
}
// Check required role
if (options.requiredRole) {
const requiredRoles = Array.isArray(options.requiredRole)
? options.requiredRole
: [options.requiredRole]
if (!requiredRoles.includes(userRoles.role)) {
return new Response("Insufficient permissions", { status: 403 })
}
}
// Check resource-action permission
const hasPermission = checkPermission(
userRoles.role,
options.resource,
options.action
)
if (!hasPermission) {
// Log unauthorized access attempt
await logAuditEvent({
userId: user.id,
action: "access_denied",
resource: options.resource,
requestedAction: options.action,
timestamp: new Date()
})
return new Response("Forbidden", { status: 403 })
}
// Add user context to request
context.user = user
context.role = userRoles.role
context.permissions = userRoles
return handler(req, context)
}
}
// Usage in API routes
export const POST = withRBAC(
async (req, context) => {
// Handler has access to context.user and context.role
const member = await createMember(await req.json())
return Response.json(member)
},
{
resource: "members",
action: "create",
requiredRole: ["chapter_admin", "state_admin", "national_admin"]
}
)
Conditional UI Rendering
// PermissionGate component
interface PermissionGateProps {
resource: string
action: Action
fallback?: React.ReactNode
children: React.ReactNode
}
export function PermissionGate({
resource,
action,
fallback = null,
children
}: PermissionGateProps) {
const permissions = usePermissions()
if (!permissions.hasPermission(resource, action)) {
return <>{fallback}</>
}
return <>{children}</>
}
// Usage
<PermissionGate resource="events" action="create">
<Button>Create Event</Button>
</PermissionGate>
<PermissionGate
resource="finances"
action="export"
fallback={<p className="text-muted-foreground">Export restricted to admins</p>}
>
<ExportButton />
</PermissionGate>
Audit Logging
Comprehensive Audit Trail
interface AuditLog {
id: string
userId: string
userRole: UserRole
action: string
resource: string
resourceId?: string
oldValues?: Record<string, any>
newValues?: Record<string, any>
ipAddress?: string
userAgent?: string
timestamp: Date
success: boolean
errorMessage?: string
}
async function logAuditEvent(event: Omit<AuditLog, "id" | "timestamp">) {
await db.insert(auditLogs).values({
...event,
timestamp: new Date()
})
}
// Automatic audit logging for sensitive operations
async function updateMemberWithAudit(
memberId: string,
updates: Partial<Member>,
actorId: string,
actorRole: UserRole
) {
// Get current state
const currentMember = await getMember(memberId)
try {
// Perform update
const updatedMember = await db
.update(members)
.set(updates)
.where(eq(members.id, memberId))
.returning()
// Log successful update
await logAuditEvent({
userId: actorId,
userRole: actorRole,
action: "update",
resource: "member",
resourceId: memberId,
oldValues: currentMember,
newValues: updatedMember[0],
success: true
})
return updatedMember[0]
} catch (error) {
// Log failed update attempt
await logAuditEvent({
userId: actorId,
userRole: actorRole,
action: "update",
resource: "member",
resourceId: memberId,
success: false,
errorMessage: error.message
})
throw error
}
}
// Audit log viewer (National Admin only)
export function AuditLogViewer() {
const permissions = usePermissions()
if (!permissions.isNationalAdmin) {
return <UnauthorizedPage />
}
return (
<div>
<h1>Audit Logs</h1>
{/* Display filterable audit log table */}
</div>
)
}
Permission Testing Checklist
✅ RLS Policies: Every table has appropriate policies for all roles ✅ API Routes: All endpoints protected with RBAC middleware ✅ UI Components: Conditional rendering based on permissions ✅ Audit Logging: All privileged actions logged ✅ Role Assignment: Secure role management (admin-only) ✅ Data Scope: Queries respect hierarchical boundaries ✅ Error Handling: Clear messages for unauthorized access
Common RBAC Patterns
Hierarchical Data Access
// Get members accessible by current user
async function getAccessibleMembers(userId: string) {
const userRole = await getUserRole(userId)
if (userRole.role === "national_admin") {
return await db.select().from(members)
}
if (userRole.role === "state_admin") {
return await db
.select()
.from(members)
.innerJoin(chapters, eq(members.chapterId, chapters.id))
.where(inArray(chapters.state, userRole.stateIds))
}
if (userRole.role === "chapter_admin") {
return await db
.select()
.from(members)
.where(inArray(members.chapterId, userRole.chapterIds))
}
// Member role: only own data
return await db
.select()
.from(members)
.where(eq(members.id, userId))
}
Integration with Other Skills
- Use with
supabase-schema-validatorfor RLS policy design - Combine with
member-workflowfor role-based actions - Works with
event-managementfor event access control - Supports
analytics-helperfor role-based reporting
Best for: Developers implementing secure, scalable access control across the NABIP AMS's multi-tier organizational structure with comprehensive audit trails.