Authentication & Authorization

User authentication, authorization patterns, and access control mechanisms for Smart Shelf.

Authentication & Authorization

Comprehensive authentication and authorization system for Smart Shelf, implementing secure user access patterns, role-based permissions, and multi-layer authorization mechanisms.

Authentication Architecture

Authentication Flow

┌─────────────┐    ┌─────────────┐    ┌─────────────┐
│   Client    │    │    Server   │    │  Supabase   │
│             │    │             │    │    Auth     │
├─────────────┤    ├─────────────┤    ├─────────────┤
│1. Login     │───▶│2. Validate  │───▶│3. Generate  │
│   Request   │    │   Creds     │    │   JWT Token │
│             │◄───│4. Return    │◄───│             │
│             │    │   Token     │    │             │
│5. Store     │    │             │    │             │
│   Token     │    │             │    │             │
└─────────────┘    └─────────────┘    └─────────────┘

JWT Token Structure

// JWT Payload Structure
interface JWTPayload {
  // Standard claims
  sub: string;          // User ID
  iss: string;          // Issuer (Supabase)
  aud: string;          // Audience
  exp: number;          // Expiration timestamp
  iat: number;          // Issued at timestamp
  
  // Custom claims
  email: string;        // User email
  role: string;         // User role
  warehouse_id?: string; // Default warehouse
  permissions: string[]; // User permissions
  session_id: string;   // Session identifier
}

Server-Side Authentication

Authentication Utilities

// lib/auth/server.ts
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';

export async function getCurrentUser() {
  const supabase = createServerComponentClient({ cookies });
  
  try {
    const { data: { session }, error } = await supabase.auth.getSession();
    
    if (error || !session) {
      return null;
    }
    
    // Get user profile with permissions
    const { data: profile } = await supabase
      .from('user_profiles')
      .select('*, user_roles(role, permissions)')
      .eq('id', session.user.id)
      .single();
    
    return {
      id: session.user.id,
      email: session.user.email,
      ...profile,
    };
  } catch (error) {
    console.error('Authentication error:', error);
    return null;
  }
}

export async function requireAuth() {
  const user = await getCurrentUser();
  
  if (!user) {
    redirect('/auth/login');
  }
  
  return user;
}

export async function validateAuth(req: Request): Promise<User> {
  const authHeader = req.headers.get('authorization');
  
  if (!authHeader?.startsWith('Bearer ')) {
    throw new Error('Invalid authorization header');
  }
  
  const token = authHeader.substring(7);
  
  try {
    const payload = await verifyJWT(token);
    const user = await getUserById(payload.sub);
    
    if (!user?.is_active) {
      throw new Error('User account is inactive');
    }
    
    return user;
  } catch (error) {
    throw new Error('Invalid or expired token');
  }
}

Session Management

// lib/auth/session.ts
export class SessionManager {
  private static readonly SESSION_DURATION = 24 * 60 * 60 * 1000; // 24 hours
  private static readonly REFRESH_THRESHOLD = 60 * 60 * 1000; // 1 hour
  
  static async refreshSession(user: User): Promise<Session | null> {
    const supabase = createServerComponentClient({ cookies });
    
    try {
      const { data, error } = await supabase.auth.refreshSession();
      
      if (error || !data.session) {
        throw new Error('Failed to refresh session');
      }
      
      return data.session;
    } catch (error) {
      console.error('Session refresh error:', error);
      return null;
    }
  }
  
  static shouldRefreshSession(session: Session): boolean {
    const expiresAt = session.expires_at * 1000;
    const now = Date.now();
    const timeUntilExpiry = expiresAt - now;
    
    return timeUntilExpiry <= this.REFRESH_THRESHOLD;
  }
  
  static async validateSession(sessionId: string): Promise<boolean> {
    const supabase = createServerComponentClient({ cookies });
    
    const { data, error } = await supabase
      .from('user_sessions')
      .select('id, expires_at, is_active')
      .eq('id', sessionId)
      .single();
    
    if (error || !data?.is_active) {
      return false;
    }
    
    return new Date(data.expires_at) > new Date();
  }
}

Client-Side Authentication

Authentication Hook

// lib/auth/client.ts
import { createClientComponentClient } from '@supabase/auth-helpers-nextjs';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';

export function useAuth() {
  const supabase = createClientComponentClient();
  const router = useRouter();
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    const getSession = async () => {
      const { data: { session } } = await supabase.auth.getSession();
      
      if (session?.user) {
        const { data: profile } = await supabase
          .from('user_profiles')
          .select('*')
          .eq('id', session.user.id)
          .single();
        
        setUser({
          id: session.user.id,
          email: session.user.email,
          ...profile,
        });
      }
      
      setLoading(false);
    };
    
    getSession();
    
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        if (event === 'SIGNED_OUT' || !session) {
          setUser(null);
        } else if (event === 'SIGNED_IN' && session) {
          const { data: profile } = await supabase
            .from('user_profiles')
            .select('*')
            .eq('id', session.user.id)
            .single();
          
          setUser({
            id: session.user.id,
            email: session.user.email,
            ...profile,
          });
        }
      }
    );
    
    return () => subscription.unsubscribe();
  }, [supabase]);
  
  const signIn = async (email: string, password: string) => {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password,
    });
    
    if (error) {
      throw new Error(error.message);
    }
    
    router.push('/dashboard');
    return data;
  };
  
  const signOut = async () => {
    await supabase.auth.signOut();
    setUser(null);
    router.push('/auth/login');
  };
  
  const signUp = async (email: string, password: string, userData: any) => {
    const { data, error } = await supabase.auth.signUp({
      email,
      password,
      options: {
        data: userData,
      },
    });
    
    if (error) {
      throw new Error(error.message);
    }
    
    return data;
  };
  
  return {
    user,
    loading,
    signIn,
    signOut,
    signUp,
  };
}

Role-Based Access Control (RBAC)

Role Definitions

// lib/auth/roles.ts
export enum UserRole {
  ADMIN = 'admin',
  MANAGER = 'manager',
  STAFF = 'staff',
  VIEWER = 'viewer',
}

export const ROLE_HIERARCHY = {
  [UserRole.ADMIN]: 4,
  [UserRole.MANAGER]: 3,
  [UserRole.STAFF]: 2,
  [UserRole.VIEWER]: 1,
};

export const PERMISSIONS = {
  // Product Management
  'products:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF, UserRole.VIEWER],
  'products:create': [UserRole.ADMIN, UserRole.MANAGER],
  'products:update': [UserRole.ADMIN, UserRole.MANAGER],
  'products:delete': [UserRole.ADMIN],
  
  // Inventory Management
  'inventory:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF, UserRole.VIEWER],
  'inventory:adjust': [UserRole.ADMIN, UserRole.MANAGER],
  'inventory:transfer': [UserRole.ADMIN, UserRole.MANAGER],
  'inventory:count': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF],
  
  // Order Management
  'orders:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF, UserRole.VIEWER],
  'orders:create': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF],
  'orders:update': [UserRole.ADMIN, UserRole.MANAGER, UserRole.STAFF],
  'orders:approve': [UserRole.ADMIN, UserRole.MANAGER],
  'orders:cancel': [UserRole.ADMIN, UserRole.MANAGER],
  
  // Financial
  'financial:read': [UserRole.ADMIN, UserRole.MANAGER, UserRole.VIEWER],
  'financial:reports': [UserRole.ADMIN, UserRole.MANAGER],
  
  // User Management
  'users:read': [UserRole.ADMIN],
  'users:create': [UserRole.ADMIN],
  'users:update': [UserRole.ADMIN],
  'users:delete': [UserRole.ADMIN],
  
  // System Administration
  'system:settings': [UserRole.ADMIN],
  'system:backup': [UserRole.ADMIN],
  'system:audit': [UserRole.ADMIN],
} as const;

export function hasPermission(userRole: UserRole, permission: string): boolean {
  const allowedRoles = PERMISSIONS[permission as keyof typeof PERMISSIONS];
  return allowedRoles?.includes(userRole) ?? false;
}

export function requirePermission(user: User, permission: string): void {
  if (!hasPermission(user.role, permission)) {
    throw new Error(`Access denied: ${permission} permission required`);
  }
}

export function hasHigherRole(userRole: UserRole, targetRole: UserRole): boolean {
  return ROLE_HIERARCHY[userRole] > ROLE_HIERARCHY[targetRole];
}

Authorization Guards

Component-Level Authorization

// components/auth/role-guard.tsx
interface RoleGuardProps {
  allowedRoles: UserRole[];
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

export function RoleGuard({ allowedRoles, children, fallback }: RoleGuardProps) {
  const { user } = useAuth();
  
  if (!user || !allowedRoles.includes(user.role)) {
    return fallback || <UnauthorizedMessage />;
  }
  
  return <>{children}</>;
}

// Permission-based guard
interface PermissionGuardProps {
  permission: string;
  children: React.ReactNode;
  fallback?: React.ReactNode;
}

export function PermissionGuard({ permission, children, fallback }: PermissionGuardProps) {
  const { user } = useAuth();
  
  if (!user || !hasPermission(user.role, permission)) {
    return fallback || null;
  }
  
  return <>{children}</>;
}

// Usage examples
<RoleGuard allowedRoles={[UserRole.ADMIN, UserRole.MANAGER]}>
  <AdminPanel />
</RoleGuard>

<PermissionGuard permission="products:create">
  <CreateProductButton />
</PermissionGuard>

API Route Authorization

// lib/auth/api-auth.ts
export function withAuth(
  handler: (req: NextRequest, user: User) => Promise<NextResponse>,
  requiredPermission?: string
) {
  return async function(req: NextRequest) {
    try {
      // Validate authentication
      const user = await validateAuth(req);
      
      // Check permission if required
      if (requiredPermission) {
        requirePermission(user, requiredPermission);
      }
      
      return handler(req, user);
      
    } catch (error) {
      if (error.message.includes('token')) {
        return NextResponse.json(
          { success: false, error: { code: 'UNAUTHORIZED', message: 'Invalid token' } },
          { status: 401 }
        );
      }
      
      if (error.message.includes('Access denied')) {
        return NextResponse.json(
          { success: false, error: { code: 'FORBIDDEN', message: error.message } },
          { status: 403 }
        );
      }
      
      throw error;
    }
  };
}

// Usage in API routes
export const GET = withAuth(async (req, user) => {
  // Handler with authenticated user
  const products = await getProducts(user);
  return NextResponse.json({ success: true, data: products });
}, 'products:read');

export const POST = withAuth(async (req, user) => {
  const body = await req.json();
  const product = await createProduct(body, user);
  return NextResponse.json({ success: true, data: product });
}, 'products:create');

Multi-Factor Authentication (MFA)

TOTP Implementation

// lib/auth/mfa.ts
export class MFAManager {
  static async enableTOTP(userId: string): Promise<{ secret: string; qrCode: string }> {
    const supabase = createServerComponentClient({ cookies });
    
    const { data, error } = await supabase.auth.mfa.enroll({
      factorType: 'totp',
    });
    
    if (error) {
      throw new Error('Failed to enable MFA');
    }
    
    return {
      secret: data.totp.secret,
      qrCode: data.totp.qr_code,
    };
  }
  
  static async verifyTOTP(factorId: string, token: string): Promise<boolean> {
    const supabase = createServerComponentClient({ cookies });
    
    const { data, error } = await supabase.auth.mfa.verify({
      factorId,
      code: token,
    });
    
    return !error && data.valid;
  }
  
  static async requireMFA(user: User): Promise<boolean> {
    // Check if user requires MFA based on role or settings
    return user.role === UserRole.ADMIN || user.mfa_enabled;
  }
}

This authentication and authorization system provides comprehensive security controls while maintaining usability and scalability for Smart Shelf.