Skip to content

User Management System

Comprehensive guide for implementing a complete user management system with profiles, roles, quotas, and administration.

Table of Contents

Overview

The User Management System provides:

  • User Profiles: Personalized user information and preferences
  • Role-Based Access Control (RBAC): Different permission levels
  • Usage Quotas: Rate limiting and usage tracking per user/tier
  • Admin Dashboard: User administration and analytics
  • Audit Logging: Track important user actions
  • Account Management: Self-service profile updates

Architecture

┌─────────────────────────────────────────────────────┐
│                 User Management System              │
├─────────────────────────────────────────────────────┤
│                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────┐ │
│  │   Profiles   │  │     Roles    │  │  Quotas  │ │
│  └──────────────┘  └──────────────┘  └──────────┘ │
│         │                  │                │      │
│         └──────────────────┼────────────────┘      │
│                           │                        │
│              ┌────────────▼────────────┐           │
│              │    Authorization        │           │
│              │    Middleware           │           │
│              └────────────┬────────────┘           │
│                           │                        │
│         ┌─────────────────┼─────────────────┐     │
│         │                 │                 │     │
│  ┌──────▼──────┐  ┌──────▼──────┐  ┌──────▼────┐│
│  │   API       │  │   Admin     │  │   Audit   ││
│  │   Routes    │  │   Panel     │  │   Logs    ││
│  └─────────────┘  └─────────────┘  └───────────┘│
│                                                     │
└─────────────────────────────────────────────────────┘

User Roles & Permissions

Role Hierarchy

┌──────────────┐
│    Admin     │  Full system access
└──────┬───────┘
┌──────▼───────┐
│  Moderator   │  User management, content moderation
└──────┬───────┘
┌──────▼───────┐
│   Premium    │  Enhanced features, higher quotas
└──────┬───────┘
┌──────▼───────┐
│     User     │  Standard access
└──────────────┘

Permission Matrix

Feature User Premium Moderator Admin
Send Messages
Upload Images
Advanced AI Model
Export Chat History
View User List
Manage Users
View Analytics
System Settings

Role Type Definition

// src/lib/types/roles.ts
export type UserRole = "user" | "premium" | "moderator" | "admin";

export interface RolePermissions {
  role: UserRole;
  permissions: Permission[];
  quotas: Quotas;
}

export type Permission =
  | "chat.send"
  | "chat.history.view"
  | "chat.history.export"
  | "image.upload"
  | "ai.advanced"
  | "users.view"
  | "users.manage"
  | "analytics.view"
  | "system.settings";

export const ROLE_PERMISSIONS: Record<UserRole, RolePermissions> = {
  user: {
    role: "user",
    permissions: ["chat.send", "chat.history.view", "image.upload"],
    quotas: {
      messagesPerDay: 100,
      imagesPerDay: 10,
      chatsMax: 10,
    },
  },
  premium: {
    role: "premium",
    permissions: [
      "chat.send",
      "chat.history.view",
      "chat.history.export",
      "image.upload",
      "ai.advanced",
    ],
    quotas: {
      messagesPerDay: 1000,
      imagesPerDay: 100,
      chatsMax: 100,
    },
  },
  moderator: {
    role: "moderator",
    permissions: [
      "chat.send",
      "chat.history.view",
      "chat.history.export",
      "image.upload",
      "ai.advanced",
      "users.view",
      "users.manage",
    ],
    quotas: {
      messagesPerDay: -1, // unlimited
      imagesPerDay: -1,
      chatsMax: -1,
    },
  },
  admin: {
    role: "admin",
    permissions: [
      "chat.send",
      "chat.history.view",
      "chat.history.export",
      "image.upload",
      "ai.advanced",
      "users.view",
      "users.manage",
      "analytics.view",
      "system.settings",
    ],
    quotas: {
      messagesPerDay: -1, // unlimited
      imagesPerDay: -1,
      chatsMax: -1,
    },
  },
};

User Profiles

Profile Schema

// src/lib/types/user.ts
export interface UserProfile {
  id: string;
  email: string;
  role: UserRole;
  displayName: string | null;
  avatarUrl: string | null;
  bio: string | null;
  preferences: UserPreferences;
  tier: "free" | "premium" | "enterprise";
  usage: UsageStats;
  createdAt: string;
  updatedAt: string;
  lastLoginAt: string | null;
}

export interface UserPreferences {
  theme: "light" | "dark" | "system";
  language: string;
  emailNotifications: boolean;
  aiModel: "standard" | "advanced";
  streamingEnabled: boolean;
}

export interface UsageStats {
  messagesToday: number;
  imagesToday: number;
  totalChats: number;
  totalMessages: number;
  lastResetAt: string;
}

Profile Service

// src/lib/services/user-service.ts
import type { UserProfile } from "@/lib/types/user";
import type { SupabaseClient } from "@supabase/supabase-js";

export class UserService {
  constructor(private supabase: SupabaseClient) {}

  /**
   * Get user profile by email
   */
  async getProfile(email: string): Promise<UserProfile | null> {
    const { data, error } = await this.supabase
      .from("user_profiles")
      .select("*")
      .eq("email", email)
      .single();

    if (error) return null;
    return data;
  }

  /**
   * Create user profile
   */
  async createProfile(profile: Partial<UserProfile>): Promise<UserProfile> {
    const { data, error } = await this.supabase
      .from("user_profiles")
      .insert({
        email: profile.email!,
        role: "user",
        tier: "free",
        preferences: {
          theme: "system",
          language: "en",
          emailNotifications: true,
          aiModel: "standard",
          streamingEnabled: true,
        },
        usage: {
          messagesToday: 0,
          imagesToday: 0,
          totalChats: 0,
          totalMessages: 0,
          lastResetAt: new Date().toISOString(),
        },
        ...profile,
      })
      .select()
      .single();

    if (error) throw error;
    return data;
  }

  /**
   * Update user profile
   */
  async updateProfile(
    email: string,
    updates: Partial<UserProfile>
  ): Promise<UserProfile> {
    const { data, error } = await this.supabase
      .from("user_profiles")
      .update(updates)
      .eq("email", email)
      .select()
      .single();

    if (error) throw error;
    return data;
  }

  /**
   * Update usage stats
   */
  async incrementUsage(
    email: string,
    field: "messagesToday" | "imagesToday" | "totalMessages" | "totalChats"
  ): Promise<void> {
    const profile = await this.getProfile(email);
    if (!profile) throw new Error("Profile not found");

    // Reset daily counters if needed
    const lastReset = new Date(profile.usage.lastResetAt);
    const now = new Date();
    const hoursSinceReset =
      (now.getTime() - lastReset.getTime()) / (1000 * 60 * 60);

    let usage = profile.usage;
    if (hoursSinceReset >= 24) {
      usage = {
        ...usage,
        messagesToday: 0,
        imagesToday: 0,
        lastResetAt: now.toISOString(),
      };
    }

    // Increment counter
    usage[field] = (usage[field] || 0) + 1;

    await this.updateProfile(email, { usage });
  }
}

Quotas & Limits

Quota Enforcement

// src/lib/middleware/quota-check.ts
import { UserService } from "@/lib/services/user-service";
import { ROLE_PERMISSIONS } from "@/lib/types/roles";

export async function checkQuota(
  email: string,
  quotaType: "messagesPerDay" | "imagesPerDay" | "chatsMax"
): Promise<{ allowed: boolean; remaining: number; limit: number }> {
  const userService = new UserService(supabase);
  const profile = await userService.getProfile(email);

  if (!profile) {
    throw new Error("User profile not found");
  }

  const rolePermissions = ROLE_PERMISSIONS[profile.role];
  const limit = rolePermissions.quotas[quotaType];

  // Unlimited quota
  if (limit === -1) {
    return { allowed: true, remaining: -1, limit: -1 };
  }

  // Check current usage
  let current = 0;
  switch (quotaType) {
    case "messagesPerDay":
      current = profile.usage.messagesToday;
      break;
    case "imagesPerDay":
      current = profile.usage.imagesToday;
      break;
    case "chatsMax":
      current = profile.usage.totalChats;
      break;
  }

  const remaining = Math.max(0, limit - current);
  const allowed = current < limit;

  return { allowed, remaining, limit };
}

/**
 * API Route middleware for quota enforcement
 */
export async function withQuotaCheck(
  email: string,
  quotaType: "messagesPerDay" | "imagesPerDay"
) {
  const { allowed, remaining, limit } = await checkQuota(email, quotaType);

  if (!allowed) {
    return {
      error: "Quota exceeded",
      quotaType,
      limit,
      remaining: 0,
    };
  }

  return { allowed: true, remaining, limit };
}

Usage in API Routes

// src/app/api/chat/route.ts
import { withQuotaCheck } from "@/lib/middleware/quota-check";
import { getServerSession } from "next-auth";

export async function POST(request: Request) {
  const session = await getServerSession();
  if (!session?.user?.email) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Check quota
  const quotaCheck = await withQuotaCheck(session.user.email, "messagesPerDay");
  if (!quotaCheck.allowed) {
    return Response.json(
      {
        error: "Daily message quota exceeded",
        limit: quotaCheck.limit,
        remaining: 0,
      },
      {
        status: 429,
        headers: {
          "X-RateLimit-Limit": String(quotaCheck.limit),
          "X-RateLimit-Remaining": "0",
        },
      }
    );
  }

  // Process request...
  const response = await processChat(request);

  // Increment usage
  await incrementUsage(session.user.email, "messagesToday");

  return Response.json(response, {
    headers: {
      "X-RateLimit-Limit": String(quotaCheck.limit),
      "X-RateLimit-Remaining": String(quotaCheck.remaining - 1),
    },
  });
}

Admin Panel

Dashboard Layout

// src/app/admin/page.tsx
import { getServerSession } from "next-auth";
import { redirect } from "next/navigation";
import { UserService } from "@/lib/services/user-service";
import { hasPermission } from "@/lib/auth/permissions";

export default async function AdminDashboard() {
  const session = await getServerSession();

  if (!session?.user?.email) {
    redirect("/login");
  }

  const userService = new UserService(supabase);
  const profile = await userService.getProfile(session.user.email);

  if (!profile || !hasPermission(profile.role, "analytics.view")) {
    redirect("/");
  }

  return (
    <div className="container mx-auto p-8">
      <h1 className="text-3xl font-bold mb-8">Admin Dashboard</h1>

      <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
        <StatCard title="Total Users" value={stats.totalUsers} />
        <StatCard title="Active Today" value={stats.activeToday} />
        <StatCard title="Messages Today" value={stats.messagesToday} />
      </div>

      <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
        <UserList />
        <RecentActivity />
      </div>
    </div>
  );
}

User Management Component

// src/components/admin/user-list.tsx
"use client";

import { useState, useEffect } from "react";
import type { UserProfile } from "@/lib/types/user";

export function UserList() {
  const [users, setUsers] = useState<UserProfile[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadUsers() {
      const response = await fetch("/api/admin/users");
      const data = await response.json();
      setUsers(data);
      setLoading(false);
    }
    loadUsers();
  }, []);

  async function updateUserRole(userId: string, newRole: UserRole) {
    await fetch(`/api/admin/users/${userId}/role`, {
      method: "PATCH",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ role: newRole }),
    });

    // Refresh list
    setUsers(users.map(u => u.id === userId ? { ...u, role: newRole } : u));
  }

  return (
    <div className="bg-white rounded-lg shadow p-6">
      <h2 className="text-xl font-bold mb-4">Users</h2>
      {loading ? (
        <div>Loading...</div>
      ) : (
        <table className="w-full">
          <thead>
            <tr>
              <th>Email</th>
              <th>Role</th>
              <th>Tier</th>
              <th>Actions</th>
            </tr>
          </thead>
          <tbody>
            {users.map((user) => (
              <tr key={user.id}>
                <td>{user.email}</td>
                <td>
                  <select
                    value={user.role}
                    onChange={(e) =>
                      updateUserRole(user.id, e.target.value as UserRole)
                    }
                  >
                    <option value="user">User</option>
                    <option value="premium">Premium</option>
                    <option value="moderator">Moderator</option>
                    <option value="admin">Admin</option>
                  </select>
                </td>
                <td>{user.tier}</td>
                <td>
                  <button onClick={() => viewUser(user.id)}>View</button>
                </td>
              </tr>
            ))}
          </tbody>
        </table>
      )}
    </div>
  );
}

Security

Permission Checking

// src/lib/auth/permissions.ts
import {
  ROLE_PERMISSIONS,
  type UserRole,
  type Permission,
} from "@/lib/types/roles";

export function hasPermission(role: UserRole, permission: Permission): boolean {
  const rolePerms = ROLE_PERMISSIONS[role];
  return rolePerms.permissions.includes(permission);
}

export function canAccessRoute(role: UserRole, route: string): boolean {
  const routePermissions: Record<string, Permission> = {
    "/admin": "analytics.view",
    "/admin/users": "users.manage",
    "/admin/settings": "system.settings",
  };

  const requiredPermission = routePermissions[route];
  if (!requiredPermission) return true; // Public route

  return hasPermission(role, requiredPermission);
}

Middleware Protection

// src/middleware.ts
import { getToken } from "next-auth/jwt";
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export async function middleware(request: NextRequest) {
  const token = await getToken({ req: request });

  // Protect admin routes
  if (request.nextUrl.pathname.startsWith("/admin")) {
    if (!token) {
      return NextResponse.redirect(new URL("/login", request.url));
    }

    // Check role
    const userRole = token.role as UserRole;
    if (!canAccessRoute(userRole, request.nextUrl.pathname)) {
      return NextResponse.redirect(new URL("/", request.url));
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: ["/admin/:path*"],
};

Testing

Unit Tests

// tests/unit/user-service.test.ts
import { describe, it, expect, vi } from "vitest";
import { UserService } from "@/lib/services/user-service";

describe("UserService", () => {
  describe("getProfile", () => {
    it("should return user profile", async () => {
      const mockSupabase = createMockSupabase();
      const service = new UserService(mockSupabase);

      const profile = await service.getProfile("test@example.com");

      expect(profile).toBeDefined();
      expect(profile?.email).toBe("test@example.com");
    });
  });

  describe("checkQuota", () => {
    it("should allow within quota", async () => {
      const result = await checkQuota("user@example.com", "messagesPerDay");

      expect(result.allowed).toBe(true);
      expect(result.remaining).toBeGreaterThan(0);
    });

    it("should deny when quota exceeded", async () => {
      // Mock user with exceeded quota
      const result = await checkQuota("user@example.com", "messagesPerDay");

      expect(result.allowed).toBe(false);
      expect(result.remaining).toBe(0);
    });
  });
});

Implementation Checklist

Phase 1: Core Setup

  • Create user profile schema in database
  • Implement UserService class
  • Add role definitions and permissions
  • Create quota checking middleware
  • Write unit tests for services

Phase 2: Profile Management

  • Create profile page UI
  • Add profile edit functionality
  • Implement avatar upload
  • Add preference settings
  • Create usage statistics display

Phase 3: Admin Panel

  • Build admin dashboard layout
  • Implement user list component
  • Add user role management
  • Create analytics views
  • Add audit logging

Phase 4: Integration

  • Integrate with feature flags
  • Add quota checks to API routes
  • Implement permission-based UI
  • Add usage tracking
  • Create admin middleware

Phase 5: Testing & Launch

  • Write comprehensive tests
  • Perform security audit
  • Load testing
  • Documentation
  • Staged rollout

Resources