supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guide to implementing Role-Based Access Control for enterprise applications with hierarchical roles, custom permissions, and multi-level access
Full skill instructions
Role-Based Access Control (RBAC): Users → Roles → Permissions
Users: John, Jane, Bob
Roles: Admin, Manager, Member, Viewer
Permissions: create_project, delete_user, view_reports
John → Admin → {create_project, delete_user, view_reports, ...}
Jane → Manager → {create_project, view_reports}
Bob → Viewer → {view_reports}
ACL: Direct user permissions
John can create_project
John can delete_user
John can view_reports
(Doesn't scale!)
RBAC: Role-based permissions
John is Admin
Admin can create_project, delete_user, view_reports
(Scalable!)
ABAC: Policy-based decisions
IF user.department == "Engineering" AND resource.sensitivity == "low"
THEN allow access
When to Use:
Without RBAC:
New employee joins
→ Assign 50 individual permissions manually
→ Error-prone, time-consuming
With RBAC:
New employee joins
→ Assign "Member" role
→ Automatically gets all member permissions
Questions:
RBAC Provides:
Principle: Users should have minimum permissions needed
RBAC Enforces:
Principle: No single user should have too much power
Example:
Definition: People or service accounts
Examples:
Definition: Named collection of permissions
Examples:
Definition: Specific actions on resources
Format: resource:action
Examples:
project:createuser:deletereport:viewbilling:manageDefinition: Things users act on
Examples:
Permissions: Full control
- project:*
- user:*
- billing:*
- settings:*
Use Case: Founder, CTO, IT admin
Permissions: Create, edit, delete (but not billing/settings)
- project:create
- project:edit
- project:delete
- user:invite
- report:view
Use Case: Team lead, project manager
Permissions: Create, edit own
- project:create
- project:edit_own
- file:upload
- report:view
Use Case: Engineer, designer, analyst
Permissions: Read-only
- project:view
- file:view
- report:view
Use Case: Stakeholder, auditor, contractor
Permissions: Manage billing only
- billing:view
- billing:manage
- invoice:download
Use Case: Finance team
Permissions: Limited access for support
- user:view
- project:view
- logs:view
Use Case: Customer support team
project:create
project:edit
project:delete
project:view
user:create
user:edit
user:delete
user:view
Pros: Clear, consistent, easy to parse
create_project
edit_project
delete_project
view_project
Pros: Reads like English
Recommendation: Use resource:action (more common)
project:* (all project permissions)
*:view (view all resources)
*:* (all permissions, superuser)
Model:
Admin inherits all permissions of Manager
Manager inherits all permissions of Member
Member inherits all permissions of Viewer
Example:
Viewer:
- project:view
- file:view
Member (inherits Viewer):
- project:view
- file:view
- project:create
- file:upload
Manager (inherits Member):
- project:view
- file:view
- project:create
- file:upload
- project:delete
- user:invite
Admin (inherits Manager):
- project:view
- file:view
- project:create
- file:upload
- project:delete
- user:invite
- user:delete
- billing:manage
- settings:*
const roleHierarchy = {
viewer: [],
member: ['viewer'],
manager: ['member'],
admin: ['manager']
};
function getAllPermissions(role) {
const permissions = new Set(rolePermissions[role]);
// Add inherited permissions
for (const parentRole of roleHierarchy[role]) {
const parentPerms = getAllPermissions(parentRole);
parentPerms.forEach(p => permissions.add(p));
}
return Array.from(permissions);
}
Definition: Customer defines their own roles with specific permissions
Example:
Customer creates "Auditor" role:
- project:view
- file:view
- logs:view
- report:export
(Read-only + export)
CREATE TABLE custom_roles (
id UUID PRIMARY KEY,
tenant_id UUID NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE custom_role_permissions (
role_id UUID REFERENCES custom_roles(id),
permission VARCHAR(255) NOT NULL,
PRIMARY KEY (role_id, permission)
);
// Create custom role
app.post('/api/roles', async (req, res) => {
const { name, description, permissions } = req.body;
const role = await db.customRoles.create({
tenantId: req.user.tenantId,
name,
description
});
for (const permission of permissions) {
await db.customRolePermissions.create({
roleId: role.id,
permission
});
}
res.json(role);
});
// Assign custom role to user
app.post('/api/users/:userId/roles', async (req, res) => {
const { roleId } = req.body;
await db.userRoles.create({
userId: req.params.userId,
roleId
});
res.json({ success: true });
});
Scope: Entire organization
Roles:
Scope: Specific project or workspace
Roles:
Scope: Specific resource (document, file)
Roles:
John is:
- Org Member (organization level)
- Project Admin in "Website Redesign" (project level)
- Document Owner of "Design Spec" (resource level)
Permissions:
- Can view all org projects (org member)
- Can manage "Website Redesign" project (project admin)
- Can delete "Design Spec" document (document owner)
CREATE TABLE users (
id UUID PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
name VARCHAR(255)
);
CREATE TABLE roles (
id UUID PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL,
description TEXT
);
CREATE TABLE permissions (
id UUID PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL,
description TEXT
);
CREATE TABLE user_roles (
user_id UUID REFERENCES users(id),
role_id UUID REFERENCES roles(id),
PRIMARY KEY (user_id, role_id)
);
CREATE TABLE role_permissions (
role_id UUID REFERENCES roles(id),
permission_id UUID REFERENCES permissions(id),
PRIMARY KEY (role_id, permission_id)
);
async function hasPermission(userId, permission, resourceId = null) {
// Get user's roles
const userRoles = await db.userRoles
.where({ userId })
.join('roles', 'roles.id', 'user_roles.role_id')
.select('roles.name');
// Get permissions for those roles
const rolePermissions = await db.rolePermissions
.whereIn('role_id', userRoles.map(r => r.id))
.join('permissions', 'permissions.id', 'role_permissions.permission_id')
.select('permissions.name');
// Check if user has permission
const hasPermission = rolePermissions.some(p =>
p.name === permission || p.name === permission.split(':')[0] + ':*'
);
// Check resource-level permissions (if applicable)
if (resourceId) {
const isOwner = await db.resources.findOne({
id: resourceId,
ownerId: userId
});
if (isOwner) return true;
}
return hasPermission;
}
function requirePermission(permission) {
return async (req, res, next) => {
const hasPermission = await hasPermission(req.user.id, permission);
if (!hasPermission) {
return res.status(403).json({
error: 'Forbidden',
message: `You don't have permission: ${permission}`
});
}
next();
};
}
// Usage
app.delete('/api/projects/:id',
requirePermission('project:delete'),
async (req, res) => {
// Delete project
}
);
function CanAccess({ permission, children }) {
const { user } = useAuth();
const hasPermission = usePermission(user.id, permission);
if (!hasPermission) return null;
return children;
}
// Usage
<CanAccess permission="project:delete">
<button onClick={deleteProject}>Delete Project</button>
</CanAccess>
Users belong to Teams
Teams have Roles in Workspaces
Users inherit permissions from Team Roles
Team: Engineering
Members: John, Jane, Bob
Workspace: Website Redesign
Engineering team has "Editor" role
Result:
- John can edit Website Redesign (via Engineering team)
- Jane can edit Website Redesign (via Engineering team)
- Bob can edit Website Redesign (via Engineering team)
CREATE TABLE teams (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
CREATE TABLE team_members (
team_id UUID REFERENCES teams(id),
user_id UUID REFERENCES users(id),
PRIMARY KEY (team_id, user_id)
);
CREATE TABLE team_workspace_roles (
team_id UUID REFERENCES teams(id),
workspace_id UUID REFERENCES workspaces(id),
role_id UUID REFERENCES roles(id),
PRIMARY KEY (team_id, workspace_id)
);
async function hasPermission(userId, permission, workspaceId) {
// Check direct user roles
const directPermissions = await getUserPermissions(userId, workspaceId);
// Check team roles
const teams = await db.teamMembers.where({ userId });
const teamPermissions = await getTeamPermissions(teams, workspaceId);
const allPermissions = [...directPermissions, ...teamPermissions];
return allPermissions.includes(permission);
}
SAML Assertion:
<saml:Attribute Name="groups">
<saml:AttributeValue>Admins</saml:AttributeValue>
<saml:AttributeValue>Engineering</saml:AttributeValue>
</saml:Attribute>
Mapping Configuration:
const ssoGroupRoleMapping = {
'Admins': 'admin',
'Engineering': 'member',
'Sales': 'member',
'Executives': 'viewer'
};
async function handleSSOLogin(assertion) {
const ssoGroups = assertion.attributes.groups || [];
const roles = ssoGroups
.map(group => ssoGroupRoleMapping[group])
.filter(role => role !== undefined);
// Assign roles to user
await db.userRoles.deleteWhere({ userId: user.id });
for (const roleName of roles) {
const role = await db.roles.findOne({ name: roleName });
await db.userRoles.create({ userId: user.id, roleId: role.id });
}
}
Option 1: SSO roles are authoritative (can't override)
// Always sync roles from SSO
await syncRolesFromSSO(user, ssoGroups);
Option 2: SSO roles + manual roles
// Keep manual roles, add SSO roles
const manualRoles = await getManualRoles(user.id);
const ssoRoles = mapSSOGroups(ssoGroups);
const allRoles = [...manualRoles, ...ssoRoles];
Rule: Org admin automatically has admin access to all projects
Implementation:
async function hasPermission(userId, permission, projectId) {
// Check if user is org admin
const isOrgAdmin = await db.userRoles.findOne({
userId,
role: 'org_admin'
});
if (isOrgAdmin) return true; // Org admin can do anything
// Check project-level permissions
const projectRole = await db.projectRoles.findOne({
userId,
projectId
});
return projectRole && hasPermissionForRole(projectRole.role, permission);
}
Rule: Project member can view all resources in project
Implementation:
async function canViewResource(userId, resourceId) {
const resource = await db.resources.findOne({ id: resourceId });
const projectMember = await db.projectMembers.findOne({
userId,
projectId: resource.projectId
});
return projectMember !== null;
}
Rule: Can edit own documents (even if not editor role)
Implementation:
async function canEditDocument(userId, documentId) {
const document = await db.documents.findOne({ id: documentId });
// Owner can always edit
if (document.ownerId === userId) return true;
// Check role-based permissions
return await hasPermission(userId, 'document:edit');
}
Rule: Temporary access (expires after 7 days)
Implementation:
async function hasTemporaryAccess(userId, resourceId) {
const access = await db.temporaryAccess.findOne({
userId,
resourceId,
expiresAt: { $gt: new Date() } // Not expired
});
return access !== null;
}
Rule: Request access, admin approves
Implementation:
// Request access
app.post('/api/access-requests', async (req, res) => {
const { resourceId, reason } = req.body;
const request = await db.accessRequests.create({
userId: req.user.id,
resourceId,
reason,
status: 'pending'
});
// Notify admins
await notifyAdmins(request);
res.json(request);
});
// Approve access
app.post('/api/access-requests/:id/approve', async (req, res) => {
const request = await db.accessRequests.findOne({ id: req.params.id });
// Grant temporary access (7 days)
await db.temporaryAccess.create({
userId: request.userId,
resourceId: request.resourceId,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
request.status = 'approved';
await request.save();
res.json(request);
});
describe('RBAC', () => {
test('Admin can delete projects', async () => {
const admin = await createUser({ role: 'admin' });
const canDelete = await hasPermission(admin.id, 'project:delete');
expect(canDelete).toBe(true);
});
test('Member cannot delete projects', async () => {
const member = await createUser({ role: 'member' });
const canDelete = await hasPermission(member.id, 'project:delete');
expect(canDelete).toBe(false);
});
test('Viewer can view projects', async () => {
const viewer = await createUser({ role: 'viewer' });
const canView = await hasPermission(viewer.id, 'project:view');
expect(canView).toBe(true);
});
});
test('Manager inherits Member permissions', async () => {
const manager = await createUser({ role: 'manager' });
// Manager should have member permissions
expect(await hasPermission(manager.id, 'project:create')).toBe(true);
expect(await hasPermission(manager.id, 'project:edit')).toBe(true);
// Plus manager-specific permissions
expect(await hasPermission(manager.id, 'user:invite')).toBe(true);
});
test('DELETE /api/projects requires project:delete permission', async () => {
const member = await createUser({ role: 'member' });
const token = await generateToken(member);
const response = await request(app)
.delete('/api/projects/123')
.set('Authorization', `Bearer ${token}`);
expect(response.status).toBe(403);
expect(response.body.error).toBe('Forbidden');
});
test('Delete button hidden for members', () => {
const member = { role: 'member' };
const { queryByText } = render(<ProjectPage user={member} />);
expect(queryByText('Delete Project')).toBeNull();
});
test('Delete button visible for admins', () => {
const admin = { role: 'admin' };
const { getByText } = render(<ProjectPage user={admin} />);
expect(getByText('Delete Project')).toBeInTheDocument();
});
// Log role assignment
app.post('/api/users/:userId/roles', async (req, res) => {
const { roleId } = req.body;
await db.userRoles.create({
userId: req.params.userId,
roleId
});
// Audit log
await db.auditLogs.create({
action: 'role_assigned',
actorId: req.user.id,
targetUserId: req.params.userId,
roleId,
timestamp: new Date()
});
res.json({ success: true });
});
async function hasPermission(userId, permission, resourceId) {
const allowed = await checkPermission(userId, permission, resourceId);
// Log access attempt
await db.accessLogs.create({
userId,
permission,
resourceId,
allowed,
timestamp: new Date()
});
return allowed;
}
// Generate access review report
app.get('/api/reports/access-review', async (req, res) => {
const users = await db.users.all();
const report = [];
for (const user of users) {
const roles = await db.userRoles.where({ userId: user.id });
const permissions = await getAllPermissions(roles);
report.push({
user: user.email,
roles: roles.map(r => r.name),
permissions,
lastLogin: user.lastLoginAt
});
}
res.json(report);
});
Features:
Example:
const casbin = require('casbin');
const enforcer = await casbin.newEnforcer('model.conf', 'policy.csv');
// Check permission
const allowed = await enforcer.enforce('john', 'project', 'delete');
Features:
Example:
from oso import Oso
oso = Oso()
oso.load_files(["policy.polar"])
# Check permission
allowed = oso.is_allowed(user, "delete", project)
Concepts:
Learn From: AWS IAM design patterns
When to Use: Most applications (RBAC is simple enough to implement)
Benefits:
Roles:
Levels:
Roles:
Custom Roles: Yes (enterprise feature)
Roles:
Levels:
RBAC: Users → Roles → Permissions
Components:
Common Roles:
Permission Format: resource:action
Hierarchical Roles:
Multi-Level:
Database Schema:
Permission Checking:
hasPermission(userId, permission, resourceId)
Middleware:
requirePermission('project:delete')
UI Components:
<CanAccess permission="project:delete">
<button>Delete</button>
</CanAccess>
Testing:
Tools:
project:create, project:edit_own, project:edit_all) rather than coarse-grained ones (e.g., project:manage).<CanAccess>) that conditionally render based on user permissions. This provides a better user experience.resource:action formatHandles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content