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.