T.ME/BIBIL_0DAY
CasperSecurity


Server : Apache/2
System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64
User : gositeme ( 1004)
PHP Version : 8.2.29
Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Directory :  /home/gositeme/domains/lavocat.ca/private_html/src/pages/admin/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /home/gositeme/domains/lavocat.ca/private_html/src/pages/admin/users.tsx
import { useState, useEffect } from 'react';
import { useSession } from 'next-auth/react';
import { useRouter } from 'next/router';
import LayoutWithSidebar from '@/components/LayoutWithSidebar';
import { canAccessAdmin, isSuperAdmin } from '@/lib/auth-utils';
import { useImpersonation } from '@/hooks/useImpersonation';

interface User {
  id: string;
  email: string;
  name: string | null;
  role: 'USER' | 'ADMIN';
  createdAt: string;
  updatedAt: string;
}

export default function AdminUsersPage() {
  const { data: session, status } = useSession();
  const router = useRouter();
  const [users, setUsers] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState('');
  const [showPasswordReset, setShowPasswordReset] = useState(false);
  const [selectedUser, setSelectedUser] = useState<User | null>(null);
  const [newPassword, setNewPassword] = useState('');
  const [confirmPassword, setConfirmPassword] = useState('');
  const [resetting, setResetting] = useState(false);
  const [success, setSuccess] = useState('');
  const [isMobile, setIsMobile] = useState(false);
  const { impersonateUser, stopImpersonation, isImpersonating, isCurrentlyImpersonating, originalUser } = useImpersonation();

  // Mobile detection
  useEffect(() => {
    const checkMobile = () => {
      setIsMobile(window.innerWidth < 768);
    };
    
    checkMobile();
    window.addEventListener('resize', checkMobile);
    return () => window.removeEventListener('resize', checkMobile);
  }, []);

  useEffect(() => {
    if (status === 'unauthenticated') {
      router.push('/auth/login');
      return;
    }

    if (!canAccessAdmin(session)) {
      router.push('/user/dashboard');
      return;
    }
  }, [status, router]);

  useEffect(() => {
    const fetchUsers = async () => {
      if (status !== 'authenticated' || !canAccessAdmin(session)) {
        return;
      }

      try {
        const response = await fetch('/api/admin/users');
        if (!response.ok) {
          throw new Error('Failed to fetch users');
        }
        const data = await response.json();
        setUsers(data.users || data); // Handle both new and old API format
      } catch (err) {
        setError('Error loading users');
        console.error(err);
      } finally {
        setLoading(false);
      }
    };

    fetchUsers();
  }, [status, session]);

  const handleRoleChange = async (userId: string, newRole: 'USER' | 'ADMIN') => {
    try {
      const response = await fetch(`/api/admin/users/${userId}`, {
        method: 'PATCH',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ role: newRole }),
      });

      if (!response.ok) {
        throw new Error('Failed to update user role');
      }

      const updatedUser = await response.json();
      setUsers(users.map(user => 
        user.id === updatedUser.id ? updatedUser : user
      ));
    } catch (err) {
      setError('Error updating user role');
      console.error(err);
    }
  };

  const handleDeleteUser = async (userId: string) => {
    if (!confirm('Are you sure you want to delete this user?')) {
      return;
    }

    try {
      const response = await fetch(`/api/admin/users/${userId}`, {
        method: 'DELETE',
      });

      if (!response.ok) {
        throw new Error('Failed to delete user');
      }

      setUsers(users.filter(user => user.id !== userId));
    } catch (err) {
      setError('Error deleting user');
      console.error(err);
    }
  };

  const handlePasswordReset = (user: User) => {
    setSelectedUser(user);
    setNewPassword('');
    setConfirmPassword('');
    setShowPasswordReset(true);
    setError('');
    setSuccess('');
  };

  const handlePasswordSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    
    if (!selectedUser) return;
    
    if (newPassword !== confirmPassword) {
      setError('Passwords do not match');
      return;
    }
    
    if (newPassword.length < 6) {
      setError('Password must be at least 6 characters long');
      return;
    }

    setResetting(true);
    setError('');

    try {
      const response = await fetch('/api/admin/reset-password', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          userId: selectedUser.id,
          newPassword: newPassword,
        }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.message || 'Failed to reset password');
      }

      setSuccess(`Password reset successfully for ${selectedUser.email}`);
      setNewPassword('');
      setConfirmPassword('');
      
      // Close modal after 2 seconds
      setTimeout(() => {
        setShowPasswordReset(false);
        setSuccess('');
      }, 2000);
      
    } catch (err) {
      setError(err instanceof Error ? err.message : 'Error resetting password');
    } finally {
      setResetting(false);
    }
  };

  if (status === 'loading') {
    return (
      <div className="flex items-center justify-center min-h-screen">
        <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
        <span className="ml-3 text-gray-600">Loading...</span>
      </div>
    );
  }

  if (!session || !canAccessAdmin(session)) {
    return null;
  }

  return (
    <LayoutWithSidebar>
      <div className={`mx-auto ${isMobile ? 'px-2 py-4 max-w-full' : 'max-w-6xl px-4 py-8'}`}>
        {/* Back Button */}
        <div className="mb-6">
          <button
            onClick={() => router.push('/admin')}
            className={`inline-flex items-center bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-2'}`}
          >
            <svg xmlns="http://www.w3.org/2000/svg" className={`mr-2 ${isMobile ? 'h-4 w-4' : 'h-5 w-5'}`} viewBox="0 0 20 20" fill="currentColor">
              <path fillRule="evenodd" d="M9.707 14.707a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 1.414L7.414 9H17a1 1 0 110 2H7.414l2.293 2.293a1 1 0 010 1.414z" clipRule="evenodd" />
            </svg>
            Back to Dashboard
          </button>
        </div>

        {/* Header */}
        <div className={`bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-xl shadow-xl mb-8 ${isMobile ? 'p-6' : 'p-8'}`}>
          <div className="flex justify-between items-start">
            <div>
              <h1 className={`font-bold ${isMobile ? 'text-2xl' : 'text-3xl'}`}>User Management</h1>
              <p className={`mt-2 ${isMobile ? 'text-sm' : ''}`}>Manage user accounts, roles, and permissions</p>
            </div>
            
            {/* Stop Impersonation Button */}
            {isCurrentlyImpersonating && originalUser && (
              <button
                onClick={stopImpersonation}
                className={`bg-red-500 text-white rounded-lg font-semibold hover:bg-red-600 transition-all duration-200 ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-2'}`}
                title={`Stop impersonating ${session?.user?.name || session?.user?.email}`}
              >
                🚫 Stop Impersonation
              </button>
            )}
          </div>
          
          {/* Impersonation Status */}
          {isCurrentlyImpersonating && originalUser && (
            <div className={`mt-4 p-3 bg-yellow-500 bg-opacity-20 rounded-lg ${isMobile ? 'text-sm' : ''}`}>
              <p className="font-semibold">🔄 Currently impersonating: {session?.user?.name || session?.user?.email}</p>
              <p className="text-yellow-100">Original user: {originalUser.name || originalUser.email}</p>
            </div>
          )}
        </div>
        
        {error && (
          <div className={`bg-red-100 border border-red-400 text-red-700 rounded mb-4 ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-3'}`}>
            {error}
          </div>
        )}

        {/* Users Grid */}
        <div className={`grid gap-6 ${isMobile ? 'grid-cols-1' : 'md:grid-cols-2 lg:grid-cols-3'}`}>
          {users.map(user => (
            <div key={user.id} className={`bg-white rounded-xl shadow-xl border border-gray-200 ${isMobile ? 'p-6' : 'p-8'}`}>
              {/* User Info */}
              <div className="mb-4">
                <div className={`flex items-center mb-2 ${isMobile ? 'flex-col text-center' : ''}`}>
                  <div className={`bg-blue-500 text-white rounded-full flex items-center justify-center ${isMobile ? 'w-12 h-12 mb-2' : 'w-10 h-10 mr-3'}`}>
                    {user.name ? user.name.charAt(0).toUpperCase() : user.email.charAt(0).toUpperCase()}
                  </div>
                  <div className={`flex-1 ${isMobile ? 'text-center' : ''}`}>
                    <h3 className={`font-bold text-gray-900 ${isMobile ? 'text-lg' : 'text-xl'}`}>
                      {user.name || 'No Name'}
                    </h3>
                    <p className={`text-gray-600 break-all ${isMobile ? 'text-sm' : ''}`}>{user.email}</p>
                  </div>
                </div>
              </div>

              {/* Role Badge */}
              <div className="mb-4">
                <span className={`px-3 py-1 rounded-full text-xs font-bold ${
                  user.role === 'ADMIN' 
                    ? 'bg-purple-100 text-purple-800' 
                    : 'bg-green-100 text-green-800'
                }`}>
                  {user.role}
                </span>
              </div>

              {/* User Stats */}
              <div className={`mb-4 text-gray-600 ${isMobile ? 'text-xs' : 'text-sm'}`}>
                <p>Created: {new Date(user.createdAt).toLocaleDateString()}</p>
                <p>Updated: {new Date(user.updatedAt).toLocaleDateString()}</p>
              </div>

              {/* Role Change */}
              <div className="mb-4">
                <label className={`block text-gray-700 font-semibold mb-2 ${isMobile ? 'text-sm' : ''}`}>
                  Change Role
                </label>
                <select
                  value={user.role}
                  onChange={(e) => handleRoleChange(user.id, e.target.value as 'USER' | 'ADMIN')}
                  className={`w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-2'}`}
                >
                  <option value="USER">User</option>
                  <option value="ADMIN">Admin</option>
                </select>
              </div>

              {/* Actions */}
              <div className={`space-y-2 ${isMobile ? '' : 'space-y-0 space-x-2 flex flex-wrap'}`}>
                {/* Sign In As Button - Only for SUPERADMIN */}
                {isSuperAdmin(session) && user.id !== session.user.id && (
                  <button
                    onClick={() => impersonateUser(user.id, user.name || user.email)}
                    disabled={isImpersonating || session.user.isImpersonating}
                    className={`bg-purple-500 text-white rounded-lg font-semibold hover:bg-purple-600 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ${isMobile ? 'w-full px-4 py-2 text-sm mb-2' : 'flex-1 px-3 py-2 text-sm'}`}
                    title={session.user.isImpersonating ? 'Stop current impersonation first' : 'Sign in as this user'}
                  >
                    {isImpersonating ? '🔄 Switching...' : session.user.isImpersonating ? '🔄 Currently Impersonating' : '🔄 Sign In As'}
                  </button>
                )}
                
                <button
                  onClick={() => handlePasswordReset(user)}
                  className={`bg-yellow-500 text-white rounded-lg font-semibold hover:bg-yellow-600 transition-all duration-200 ${isMobile ? 'w-full px-4 py-2 text-sm' : 'flex-1 px-4 py-2'}`}
                >
                  Reset Password
                </button>
                <button
                  onClick={() => handleDeleteUser(user.id)}
                  className={`bg-red-500 text-white rounded-lg font-semibold hover:bg-red-600 transition-all duration-200 ${isMobile ? 'w-full px-4 py-2 text-sm' : 'flex-1 px-4 py-2'}`}
                >
                  Delete User
                </button>
              </div>
            </div>
          ))}
        </div>

        {users.length === 0 && (
          <div className={`text-center ${isMobile ? 'py-8' : 'py-12'}`}>
            <div className={`text-gray-400 mb-4 ${isMobile ? 'text-4xl' : 'text-6xl'}`}>👥</div>
            <h3 className={`font-bold text-gray-700 mb-2 ${isMobile ? 'text-lg' : 'text-xl'}`}>No Users Found</h3>
            <p className={`text-gray-600 ${isMobile ? 'text-sm' : ''}`}>There are no users in the system yet.</p>
          </div>
        )}

        {/* Password Reset Modal */}
        {showPasswordReset && selectedUser && (
          <div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
            <div className={`bg-white rounded-xl shadow-xl ${isMobile ? 'w-full max-w-sm p-6' : 'max-w-md w-full p-8'}`}>
              <h2 className={`font-bold text-gray-900 mb-4 ${isMobile ? 'text-lg' : 'text-xl'}`}>
                Reset Password for {selectedUser.email}
              </h2>
              
              {success && (
                <div className={`bg-green-100 border border-green-400 text-green-700 rounded mb-4 ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-3'}`}>
                  {success}
                </div>
              )}
              
              {error && (
                <div className={`bg-red-100 border border-red-400 text-red-700 rounded mb-4 ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-3'}`}>
                  {error}
                </div>
              )}

              <form onSubmit={handlePasswordSubmit} className="space-y-4">
                <div>
                  <label className={`block text-gray-700 font-semibold mb-2 ${isMobile ? 'text-sm' : ''}`}>
                    New Password
                  </label>
                  <input
                    type="password"
                    value={newPassword}
                    onChange={(e) => setNewPassword(e.target.value)}
                    required
                    minLength={6}
                    className={`w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-2'}`}
                    placeholder="Enter new password"
                  />
                </div>
                
                <div>
                  <label className={`block text-gray-700 font-semibold mb-2 ${isMobile ? 'text-sm' : ''}`}>
                    Confirm Password
                  </label>
                  <input
                    type="password"
                    value={confirmPassword}
                    onChange={(e) => setConfirmPassword(e.target.value)}
                    required
                    minLength={6}
                    className={`w-full border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent ${isMobile ? 'px-3 py-2 text-sm' : 'px-4 py-2'}`}
                    placeholder="Confirm new password"
                  />
                </div>

                <div className={`space-y-2 ${isMobile ? '' : 'space-y-0 space-x-4 flex'}`}>
                  <button
                    type="submit"
                    disabled={resetting}
                    className={`bg-blue-600 text-white rounded-lg font-semibold hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-all duration-200 ${isMobile ? 'w-full px-4 py-2 text-sm' : 'flex-1 px-4 py-2'}`}
                  >
                    {resetting ? 'Resetting...' : 'Reset Password'}
                  </button>
                  <button
                    type="button"
                    onClick={() => setShowPasswordReset(false)}
                    className={`bg-gray-600 text-white rounded-lg font-semibold hover:bg-gray-700 transition-all duration-200 ${isMobile ? 'w-full px-4 py-2 text-sm' : 'flex-1 px-4 py-2'}`}
                  >
                    Cancel
                  </button>
                </div>
              </form>
            </div>
          </div>
        )}
      </div>
    </LayoutWithSidebar>
  );
} 

CasperSecurity Mini