Authorization & Access Control

Role-based access control, permissions, and Row Level Security implementation.

Authorization & Access Control

Smart Shelf implements a comprehensive authorization system using Role-Based Access Control (RBAC) combined with Row Level Security (RLS) to ensure users can only access data and perform actions they're explicitly authorized for.

Role-Based Access Control (RBAC)

User Roles Hierarchy

// types/auth.ts
export type UserRole = 'admin' | 'manager' | 'employee' | 'viewer'

export interface UserPermissions {
  products: {
    create: boolean
    read: boolean
    update: boolean
    delete: boolean
    export: boolean
    import: boolean
  }
  inventory: {
    view: boolean
    adjust: boolean
    transfer: boolean
    audit: boolean
    reports: boolean
  }
  orders: {
    create: boolean
    view: boolean
    approve: boolean
    cancel: boolean
    fulfill: boolean
  }
  analytics: {
    basic_reports: boolean
    advanced_analytics: boolean
    custom_queries: boolean
    export_data: boolean
  }
  administration: {
    user_management: boolean
    system_settings: boolean
    audit_logs: boolean
    security_settings: boolean
    backup_restore: boolean
  }
  warehouse: {
    manage_locations: boolean
    configure_zones: boolean
    assign_users: boolean
    view_operations: boolean
  }
}

export const ROLE_PERMISSIONS: Record<UserRole, UserPermissions> = {
  admin: {
    products: { create: true, read: true, update: true, delete: true, export: true, import: true },
    inventory: { view: true, adjust: true, transfer: true, audit: true, reports: true },
    orders: { create: true, view: true, approve: true, cancel: true, fulfill: true },
    analytics: { basic_reports: true, advanced_analytics: true, custom_queries: true, export_data: true },
    administration: { user_management: true, system_settings: true, audit_logs: true, security_settings: true, backup_restore: true },
    warehouse: { manage_locations: true, configure_zones: true, assign_users: true, view_operations: true }
  },
  manager: {
    products: { create: true, read: true, update: true, delete: false, export: true, import: true },
    inventory: { view: true, adjust: true, transfer: true, audit: true, reports: true },
    orders: { create: true, view: true, approve: true, cancel: true, fulfill: true },
    analytics: { basic_reports: true, advanced_analytics: true, custom_queries: false, export_data: true },
    administration: { user_management: false, system_settings: false, audit_logs: true, security_settings: false, backup_restore: false },
    warehouse: { manage_locations: true, configure_zones: false, assign_users: true, view_operations: true }
  },
  employee: {
    products: { create: true, read: true, update: true, delete: false, export: false, import: false },
    inventory: { view: true, adjust: true, transfer: true, audit: false, reports: false },
    orders: { create: true, view: true, approve: false, cancel: false, fulfill: true },
    analytics: { basic_reports: true, advanced_analytics: false, custom_queries: false, export_data: false },
    administration: { user_management: false, system_settings: false, audit_logs: false, security_settings: false, backup_restore: false },
    warehouse: { manage_locations: false, configure_zones: false, assign_users: false, view_operations: true }
  },
  viewer: {
    products: { create: false, read: true, update: false, delete: false, export: false, import: false },
    inventory: { view: true, adjust: false, transfer: false, audit: false, reports: false },
    orders: { create: false, view: true, approve: false, cancel: false, fulfill: false },
    analytics: { basic_reports: true, advanced_analytics: false, custom_queries: false, export_data: false },
    administration: { user_management: false, system_settings: false, audit_logs: false, security_settings: false, backup_restore: false },
    warehouse: { manage_locations: false, configure_zones: false, assign_users: false, view_operations: true }
  }
}

Permission Matrix

FeatureAdminManagerEmployeeViewer
Products
Create Products
View Products
Edit Products
Delete Products
Export Products
Import Products
Inventory
View Inventory
Adjust Stock
Transfer Stock
Inventory Audit
Inventory Reports
Orders
View Orders
Create Orders
Approve Orders
Cancel Orders
Fulfill Orders
Analytics
Basic Reports
Advanced Analytics
Custom Queries
Export Data
Administration
User Management
System Settings
Audit Logs
Security Settings
Backup/Restore

Permission Enforcement

Server-Side Permission Checks

// lib/auth/permissions.ts
import { User } from '@supabase/supabase-js'
import { createClient } from '@/lib/supabase/server'

export async function requirePermission(
  permission: string,
  user?: User
): Promise<User> {
  if (!user) {
    const supabase = createClient()
    const { data: { user: currentUser }, error } = await supabase.auth.getUser()
    
    if (!currentUser || error) {
      throw new Error('Authentication required')
    }
    
    user = currentUser
  }
  
  const userRole = user.user_metadata?.role as UserRole
  
  if (!userRole || !hasPermission(userRole, permission)) {
    throw new Error(`Insufficient permissions. Required: ${permission}`)
  }
  
  // Log permission check
  await logPermissionCheck({
    user_id: user.id,
    permission,
    granted: true,
    timestamp: new Date().toISOString()
  })
  
  return user
}

export function hasPermission(role: UserRole, permission: string): boolean {
  const permissions = ROLE_PERMISSIONS[role]
  
  // Parse permission string (e.g., "products.create")
  const [resource, action] = permission.split('.')
  
  if (!permissions[resource as keyof UserPermissions]) {
    return false
  }
  
  const resourcePermissions = permissions[resource as keyof UserPermissions] as any
  return resourcePermissions[action] === true
}

export async function requireRole(requiredRole: UserRole, user?: User): Promise<User> {
  if (!user) {
    const supabase = createClient()
    const { data: { user: currentUser }, error } = await supabase.auth.getUser()
    
    if (!currentUser || error) {
      throw new Error('Authentication required')
    }
    
    user = currentUser
  }
  
  const userRole = user.user_metadata?.role as UserRole
  const roleHierarchy: Record<UserRole, number> = {
    viewer: 1,
    employee: 2,
    manager: 3,
    admin: 4
  }
  
  if (!userRole || roleHierarchy[userRole] < roleHierarchy[requiredRole]) {
    throw new Error(`Insufficient role. Required: ${requiredRole}, Current: ${userRole}`)
  }
  
  return user
}

API Route Protection

// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { requirePermission } from '@/lib/auth/permissions'

export async function GET(request: NextRequest) {
  try {
    const user = await requirePermission('products.read')
    
    // User is authorized, proceed with request
    const products = await getProducts(user)
    
    return NextResponse.json({ data: products })
  } catch (error) {
    if (error.message === 'Authentication required') {
      return NextResponse.json(
        { error: 'Authentication required' },
        { status: 401 }
      )
    } else if (error.message.includes('Insufficient permissions')) {
      return NextResponse.json(
        { error: 'Access denied' },
        { status: 403 }
      )
    }
    
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}

export async function POST(request: NextRequest) {
  try {
    const user = await requirePermission('products.create')
    
    const body = await request.json()
    const product = await createProduct(body, user)
    
    return NextResponse.json({ data: product })
  } catch (error) {
    // Handle authorization errors
    return handleAuthorizationError(error)
  }
}

export async function DELETE(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  try {
    const user = await requirePermission('products.delete')
    
    await deleteProduct(params.id, user)
    
    return NextResponse.json({ success: true })
  } catch (error) {
    return handleAuthorizationError(error)
  }
}

function handleAuthorizationError(error: any): NextResponse {
  if (error.message === 'Authentication required') {
    return NextResponse.json(
      { error: 'Authentication required' },
      { status: 401 }
    )
  } else if (error.message.includes('Insufficient')) {
    return NextResponse.json(
      { error: 'Access denied' },
      { status: 403 }
    )
  }
  
  console.error('API Error:', error)
  return NextResponse.json(
    { error: 'Internal server error' },
    { status: 500 }
  )
}

Component-Level Authorization

// components/auth/with-permission.tsx
import { useAuth } from '@/hooks/use-auth'
import { hasPermission } from '@/lib/auth/permissions'

interface WithPermissionProps {
  permission: string
  fallback?: React.ReactNode
  children: React.ReactNode
}

export function WithPermission({ permission, fallback, children }: WithPermissionProps) {
  const { user } = useAuth()
  
  if (!user) {
    return fallback || <div>Authentication required</div>
  }
  
  const userRole = user.user_metadata?.role
  
  if (!hasPermission(userRole, permission)) {
    return fallback || <div>Access denied</div>
  }
  
  return <>{children}</>
}

// Usage example
export function ProductActions({ product }: { product: Product }) {
  return (
    <div className="flex gap-2">
      <WithPermission permission="products.update">
        <EditProductButton product={product} />
      </WithPermission>
      
      <WithPermission permission="products.delete">
        <DeleteProductButton product={product} />
      </WithPermission>
      
      <WithPermission permission="products.export">
        <ExportProductButton product={product} />
      </WithPermission>
    </div>
  )
}

Higher-Order Component for Protection

// components/auth/protected-component.tsx
import { ComponentType } from 'react'
import { useAuth } from '@/hooks/use-auth'
import { hasPermission } from '@/lib/auth/permissions'

export function withPermission<P extends object>(
  Component: ComponentType<P>,
  requiredPermission: string
) {
  return function PermissionWrapper(props: P) {
    const { user, loading } = useAuth()
    
    if (loading) {
      return <div>Loading...</div>
    }
    
    if (!user) {
      return <div>Authentication required</div>
    }
    
    const userRole = user.user_metadata?.role
    
    if (!hasPermission(userRole, requiredPermission)) {
      return (
        <div className="text-center p-8">
          <h2 className="text-xl font-semibold mb-2">Access Denied</h2>
          <p>You don't have permission to access this resource.</p>
        </div>
      )
    }
    
    return <Component {...props} />
  }
}

// Usage
const ProtectedProductManager = withPermission(ProductManager, 'products.create')
const ProtectedUserSettings = withPermission(UserSettings, 'administration.user_management')

Row Level Security (RLS)

Database Policies

Row Level Security ensures that users can only access data they're authorized to see based on their role and organizational context.

-- Enable RLS on all tables
ALTER TABLE products ENABLE ROW LEVEL SECURITY;
ALTER TABLE inventory ENABLE ROW LEVEL SECURITY;
ALTER TABLE purchase_orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE sales_orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE warehouses ENABLE ROW LEVEL SECURITY;
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Admin access - full access to all data
CREATE POLICY admin_full_access ON products
    FOR ALL TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role = 'admin'
        )
    );

-- Manager access - department/warehouse specific
CREATE POLICY manager_department_access ON products
    FOR ALL TO authenticated
    USING (
        category_id IN (
            SELECT category_id 
            FROM manager_category_assignments 
            WHERE user_id = auth.uid()
        )
        OR EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager')
        )
    );

-- Warehouse-specific inventory access
CREATE POLICY warehouse_inventory_access ON inventory
    FOR ALL TO authenticated
    USING (
        warehouse_id IN (
            SELECT warehouse_id 
            FROM user_warehouse_assignments 
            WHERE user_id = auth.uid()
        )
        OR EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager')
        )
    );

-- Employee access - limited write permissions
CREATE POLICY employee_products_read ON products
    FOR SELECT TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager', 'employee', 'viewer')
        )
    );

CREATE POLICY employee_products_write ON products
    FOR INSERT TO authenticated
    WITH CHECK (
        EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager', 'employee')
        )
    );

CREATE POLICY employee_products_update ON products
    FOR UPDATE TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager', 'employee')
        )
    )
    WITH CHECK (
        -- Prevent employees from updating critical fields
        CASE 
            WHEN EXISTS (
                SELECT 1 FROM users 
                WHERE users.id = auth.uid() 
                AND users.role = 'employee'
            ) THEN (
                OLD.cost_price = NEW.cost_price
                AND OLD.category_id = NEW.category_id
                AND OLD.supplier_id = NEW.supplier_id
            )
            ELSE true
        END
    );

-- Viewer access - read only
CREATE POLICY viewer_read_only ON products
    FOR SELECT TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid()
        )
    );

-- Delete permissions - only admin and managers
CREATE POLICY delete_products ON products
    FOR DELETE TO authenticated
    USING (
        EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager')
        )
    );

Multi-Tenant Security

-- Tenant-based access control for multi-tenant deployments
CREATE POLICY tenant_isolation ON products
    FOR ALL TO authenticated
    USING (
        tenant_id = (
            SELECT tenant_id 
            FROM users 
            WHERE users.id = auth.uid()
        )
    );

-- Warehouse-based isolation
CREATE POLICY warehouse_isolation ON inventory
    FOR ALL TO authenticated
    USING (
        warehouse_id IN (
            SELECT warehouse_id 
            FROM user_warehouse_access 
            WHERE user_id = auth.uid()
        )
    );

-- Order access based on creator or assigned warehouse
CREATE POLICY order_access ON sales_orders
    FOR ALL TO authenticated
    USING (
        created_by = auth.uid()
        OR warehouse_id IN (
            SELECT warehouse_id 
            FROM user_warehouse_access 
            WHERE user_id = auth.uid()
        )
        OR EXISTS (
            SELECT 1 FROM users 
            WHERE users.id = auth.uid() 
            AND users.role IN ('admin', 'manager')
        )
    );

Dynamic Policy Functions

-- Function to check user permissions dynamically
CREATE OR REPLACE FUNCTION check_user_permission(required_permission text)
RETURNS boolean AS $$
DECLARE
    user_role text;
    has_permission boolean := false;
BEGIN
    -- Get user role
    SELECT role INTO user_role
    FROM users
    WHERE id = auth.uid();
    
    -- Check permission based on role
    CASE user_role
        WHEN 'admin' THEN
            has_permission := true;
        WHEN 'manager' THEN
            has_permission := required_permission NOT IN ('administration.user_management', 'administration.system_settings');
        WHEN 'employee' THEN
            has_permission := required_permission IN ('products.read', 'products.create', 'products.update', 'inventory.view', 'inventory.adjust');
        WHEN 'viewer' THEN
            has_permission := required_permission LIKE '%.read' OR required_permission LIKE '%.view';
        ELSE
            has_permission := false;
    END CASE;
    
    RETURN has_permission;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Use the function in policies
CREATE POLICY dynamic_permission_check ON sensitive_operations
    FOR ALL TO authenticated
    USING (check_user_permission('sensitive_operations.execute'));

Context-Based Access Control

Warehouse-Based Access

// lib/auth/context-based-access.ts
export async function getWarehouseAccess(userId: string): Promise<string[]> {
  const { data: assignments } = await supabase
    .from('user_warehouse_assignments')
    .select('warehouse_id')
    .eq('user_id', userId)
    .eq('is_active', true)

  return assignments?.map(a => a.warehouse_id) || []
}

export async function hasWarehouseAccess(userId: string, warehouseId: string): Promise<boolean> {
  const accessibleWarehouses = await getWarehouseAccess(userId)
  return accessibleWarehouses.includes(warehouseId)
}

export async function filterByWarehouseAccess<T extends { warehouse_id: string }>(
  userId: string,
  items: T[]
): Promise<T[]> {
  const accessibleWarehouses = await getWarehouseAccess(userId)
  return items.filter(item => accessibleWarehouses.includes(item.warehouse_id))
}

Time-Based Access Control

// lib/auth/time-based-access.ts
export interface AccessSchedule {
  user_id: string
  day_of_week: number // 0-6 (Sunday-Saturday)
  start_time: string // HH:MM format
  end_time: string // HH:MM format
  timezone: string
}

export async function isWithinAccessHours(userId: string): Promise<boolean> {
  const now = new Date()
  const dayOfWeek = now.getDay()
  const currentTime = now.toTimeString().slice(0, 5) // HH:MM

  const { data: schedules } = await supabase
    .from('user_access_schedules')
    .select('*')
    .eq('user_id', userId)
    .eq('day_of_week', dayOfWeek)

  if (!schedules || schedules.length === 0) {
    // No schedule defined, allow access
    return true
  }

  return schedules.some(schedule => {
    return currentTime >= schedule.start_time && currentTime <= schedule.end_time
  })
}

export async function requireTimeBasedAccess(userId: string): Promise<void> {
  const hasAccess = await isWithinAccessHours(userId)
  
  if (!hasAccess) {
    throw new Error('Access denied: Outside of permitted hours')
  }
}

Permission Audit & Monitoring

Permission Usage Tracking

// lib/auth/audit.ts
export async function logPermissionCheck(details: {
  user_id: string
  permission: string
  granted: boolean
  resource_id?: string
  context?: any
  timestamp: string
}) {
  await supabase
    .from('permission_audit_log')
    .insert({
      ...details,
      session_id: await getCurrentSessionId(),
      ip_address: await getClientIP(),
      user_agent: await getUserAgent()
    })
}

export async function generatePermissionReport(userId: string, startDate: Date, endDate: Date) {
  const { data: auditLogs } = await supabase
    .from('permission_audit_log')
    .select('*')
    .eq('user_id', userId)
    .gte('timestamp', startDate.toISOString())
    .lte('timestamp', endDate.toISOString())
    .order('timestamp', { ascending: false })

  const permissionUsage = auditLogs?.reduce((acc, log) => {
    acc[log.permission] = (acc[log.permission] || 0) + 1
    return acc
  }, {} as Record<string, number>)

  return {
    total_checks: auditLogs?.length || 0,
    permission_usage: permissionUsage,
    denied_attempts: auditLogs?.filter(log => !log.granted).length || 0,
    most_used_permissions: Object.entries(permissionUsage || {})
      .sort(([,a], [,b]) => b - a)
      .slice(0, 10)
  }
}

This comprehensive authorization system ensures that Smart Shelf maintains strict access controls while providing the flexibility needed for different organizational roles and contexts.