Skip to content

jacksonkasi1/FlexAuth

Repository files navigation

FlexAuth - Flexible Role-Based Access Control

A flexible, tenant-aware RBAC system designed to be plug-and-play for any project.

Features

  • 🔌 Plug-and-play - Import and configure in minutes
  • 💻 Developer-defined resources - Define resources/actions in code
  • 👨‍💼 Admin-controlled rules - Super admins create permission rules via UI
  • Blazing fast - Redis caching for instant permission checks
  • 🔧 Framework agnostic - Works with Express, Hono, Fastify, Next.js, etc.
  • 🏢 Multi-tenant - Built-in tenant support for SaaS applications
  • 🎯 Type-safe - Full TypeScript support

Packages

Package Description
@flexauth/core Core SDK with permission engine, cache adapters, and conditions
@flexauth/drizzle Drizzle ORM schema and migrations
@flexauth/express Express.js middleware and route guards
@flexauth/hono Hono.js middleware and route guards
@flexauth/react React provider, hooks, and components

Quick Start

Installation

# Using bun
bun add @flexauth/core @flexauth/drizzle

# For Express.js
bun add @flexauth/express

# For Hono
bun add @flexauth/hono

# For React
bun add @flexauth/react

Basic Setup

import { FlexAuth, MemoryAdapter } from '@flexauth/core';
import { db } from './db';

// Initialize FlexAuth
const flexAuth = new FlexAuth({
  db,
  cache: new MemoryAdapter(), // Use RedisAdapter for production
});

// Define resources and actions
flexAuth
  .defineResources([
    { key: 'document', displayName: 'Document', category: 'Content' },
    { key: 'case', displayName: 'Case', category: 'Medical' },
  ])
  .defineActions([
    { key: 'create', displayName: 'Create', category: 'crud' },
    { key: 'read', displayName: 'Read', category: 'crud' },
    { key: 'update', displayName: 'Update', category: 'crud' },
    { key: 'delete', displayName: 'Delete', category: 'crud' },
    { key: 'share', displayName: 'Share', category: 'workflow' },
  ])
  .mapResourceActions('document', ['create', 'read', 'update', 'delete', 'share'])
  .mapResourceActions('case', ['create', 'read', 'update', 'delete']);

// Sync to database
await flexAuth.sync();

Permission Checks

// Simple check
const canEdit = await flexAuth.can(userId, 'update', 'document');

// With context
const canShare = await flexAuth.can(userId, 'share', 'document', {
  tenantId: 'hospital_123',
  resourceId: 'doc_456',
});

// Detailed check with reason
const result = await flexAuth.check(userId, 'delete', 'case');
console.log(result.allowed, result.reason, result.resolvedBy);

// Bulk check (optimized for UI)
const permissions = await flexAuth.checkBulk(userId, [
  { action: 'read', resource: 'document' },
  { action: 'update', resource: 'document' },
  { action: 'delete', resource: 'document' },
]);
// { 'document:read': true, 'document:update': true, 'document:delete': false }

Express.js Integration

import express from 'express';
import { flexAuthMiddleware, requirePermission } from '@flexauth/express';

const app = express();

// Apply middleware
app.use(flexAuthMiddleware({
  flexAuth,
  getSubject: (req) => req.user ? { id: req.user.id } : null,
  getTenantId: (req) => req.user?.tenantId,
}));

// Protect routes
app.get('/documents/:id', 
  requirePermission('read', 'document'),
  async (req, res) => {
    // User has permission
    const doc = await getDocument(req.params.id);
    res.json(doc);
  }
);

// Inline checks
app.get('/cases/:id', async (req, res) => {
  const canRead = await req.flexAuth.can('read', 'case');
  if (!canRead) {
    return res.status(403).json({ error: 'Access denied' });
  }
  // ...
});

Hono.js Integration

import { Hono } from 'hono';
import { flexAuthMiddleware, requirePermission } from '@flexauth/hono';

const app = new Hono();

// Apply middleware
app.use('*', flexAuthMiddleware({
  flexAuth,
  getSubject: (c) => {
    const user = c.get('user'); // Assuming user is attached by auth middleware
    return user ? { id: user.id, type: 'user' } : null;
  },
  getTenantId: (c) => c.req.header('x-tenant-id') || null,
}));

// Protect routes
app.get('/documents/:id', 
  requirePermission('read', 'document'),
  async (c) => {
    // User has permission
    const id = c.req.param('id');
    const doc = await getDocument(id);
    return c.json(doc);
  }
);

// Inline checks (fully typed c.var.flexAuth)
app.get('/cases/:id', async (c) => {
  const canRead = await c.var.flexAuth.can('read', 'case');
  if (!canRead) {
    return c.json({ error: 'Access denied' }, 403);
  }
  // ...
});

React Integration

import { FlexAuthProvider, Can, useCan, ProtectedRoute } from '@flexauth/react';

// Wrap your app
function App() {
  return (
    <FlexAuthProvider permissionsEndpoint="/api/permissions/me">
      <Router>
        <Routes>
          <Route path="/dashboard" element={
            <ProtectedRoute action="access" resource="dashboard" redirectTo="/login">
              <Dashboard />
            </ProtectedRoute>
          } />
        </Routes>
      </Router>
    </FlexAuthProvider>
  );
}

// Use in components
function DocumentActions({ documentId }) {
  const canEdit = useCan('update', 'document');
  const canDelete = useCan('delete', 'document');

  return (
    <div>
      <Can action="update" resource="document">
        <EditButton id={documentId} />
      </Can>
      
      <Can action="delete" resource="document" fallback={<DisabledButton />}>
        <DeleteButton id={documentId} />
      </Can>
    </div>
  );
}

Built-in Conditions

FlexAuth includes several built-in conditions for common use cases:

import {
  approvalRequiredCondition,
  ownRecordsCondition,
  sameDepartmentCondition,
  businessHoursCondition,
  ipWhitelistCondition,
} from '@flexauth/core';

// Register built-in conditions
flexAuth.defineConditions([
  approvalRequiredCondition,
  ownRecordsCondition,
  sameDepartmentCondition,
  businessHoursCondition,
  ipWhitelistCondition,
]);

// Or define custom conditions
flexAuth.defineCondition({
  key: 'has_subscription',
  displayName: 'Requires Active Subscription',
  handler: async (context) => {
    const { subjectId } = context;
    const hasSubscription = await checkSubscription(subjectId);
    return {
      passed: hasSubscription,
      reason: hasSubscription ? undefined : 'Active subscription required',
    };
  },
});

Admin API

// Create roles
await flexAuth.admin.createRole(tenantId, {
  key: 'doctor',
  displayName: 'Doctor',
  description: 'Medical doctor role',
});

// Set role permissions
await flexAuth.admin.setRolePermission(roleId, 'case', 'update', {
  isAllowed: true,
  conditions: [{ conditionKey: 'same_department' }],
});

// Assign roles to users
await flexAuth.admin.assignRole(userId, roleId, tenantId, {
  assignedBy: adminId,
});

// Direct user permission overrides
await flexAuth.admin.setSubjectPermission(userId, tenantId, 'case', 'delete', {
  isAllowed: true,
  reason: 'Special access for project lead',
  grantedBy: adminId,
});

Cache Adapters

import { MemoryAdapter, RedisAdapter } from '@flexauth/core';
import Redis from 'ioredis';

// Memory adapter (development/testing)
const cache = new MemoryAdapter();

// Redis adapter (production)
const redis = new Redis(process.env.REDIS_URL);
const cache = new RedisAdapter({
  client: redis,
  keyPrefix: 'flexauth:',
  defaultTTL: 300,
});

// Initialize with cache
const flexAuth = new FlexAuth({
  db,
  cache,
  cacheEnabled: true,
  cacheTTL: 300, // 5 minutes
});

Database Setup (Drizzle)

import { drizzle } from 'drizzle-orm/node-postgres';
import * as schema from '@flexauth/drizzle/schema';

const db = drizzle(pool, { schema });

// Run migrations
// Use drizzle-kit to generate and run migrations

Project Structure

flexauth/
├── packages/
│   ├── core/                    # Core SDK
│   │   ├── src/
│   │   │   ├── types/           # Type definitions
│   │   │   ├── adapters/        # Cache adapters
│   │   │   ├── conditions/      # Built-in conditions
│   │   │   ├── engine/          # Permission engine
│   │   │   ├── admin/           # Admin services
│   │   │   ├── utils/           # Utilities and errors
│   │   │   ├── flexauth.ts      # Main class
│   │   │   └── index.ts         # Exports
│   │   └── package.json
│   │
│   ├── drizzle/                 # Drizzle ORM integration
│   │   ├── src/
│   │   │   └── schema/          # Database schema
│   │   └── package.json
│   │
│   ├── express/                 # Express middleware
│   │   ├── src/
│   │   │   ├── middleware.ts
│   │   │   ├── require-permission.ts
│   │   │   └── require-super-admin.ts
│   │   └── package.json
│   │
│   ├── hono/                    # Hono middleware
│   │   ├── src/
│   │   │   ├── middleware.ts
│   │   │   ├── require-permission.ts
│   │   │   └── require-super-admin.ts
│   │   └── package.json
│   │
│   └── react/                   # React integration
│       ├── src/
│       │   ├── hooks/           # React hooks
│       │   ├── components/      # UI components
│       │   └── provider.tsx     # Context provider
│       └── package.json
│
├── package.json                 # Root package.json
└── tsconfig.json                # Root TypeScript config

Development

# Install dependencies
bun install

# Build all packages
bun run build

# Run type checking
bun x tsc -b

# Run tests
bun test

License

MIT

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published