Fix jwt-token
This commit is contained in:
@@ -10,7 +10,9 @@ import {
|
||||
GlobeAltIcon,
|
||||
KeyIcon,
|
||||
EyeIcon,
|
||||
EyeSlashIcon
|
||||
EyeSlashIcon,
|
||||
TrashIcon,
|
||||
PencilIcon
|
||||
} from '@heroicons/react/24/outline';
|
||||
import { hasPermission, canAccessSettings } from '../utils/rbac';
|
||||
|
||||
@@ -1099,6 +1101,8 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
||||
const [showCreateUser, setShowCreateUser] = useState(false);
|
||||
const [showEditUser, setShowEditUser] = useState(false);
|
||||
const [editingUser, setEditingUser] = useState(null);
|
||||
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||
const [deletingUser, setDeletingUser] = useState(null);
|
||||
|
||||
const authProvider = tenantConfig?.auth_provider;
|
||||
const isLocalAuth = authProvider === 'local';
|
||||
@@ -1260,25 +1264,41 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
||||
</td>
|
||||
{(canEditUsers || canDeleteUsers) && (
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||
{canEditUsers && (
|
||||
<button
|
||||
onClick={() => handleEditUser(user)}
|
||||
className="text-primary-600 hover:text-primary-900 mr-4"
|
||||
>
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
{canDeleteUsers && (
|
||||
<button
|
||||
onClick={() => handleToggleUserStatus(user)}
|
||||
className={user.is_active
|
||||
? 'text-red-600 hover:text-red-900'
|
||||
: 'text-green-600 hover:text-green-900'
|
||||
}
|
||||
>
|
||||
{user.is_active ? 'Deactivate' : 'Activate'}
|
||||
</button>
|
||||
)}
|
||||
<div className="flex items-center space-x-2">
|
||||
{canEditUsers && (
|
||||
<button
|
||||
onClick={() => handleEditUser(user)}
|
||||
className="inline-flex items-center px-2 py-1 border border-transparent text-xs font-medium rounded text-blue-700 bg-blue-100 hover:bg-blue-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
|
||||
title="Edit user"
|
||||
>
|
||||
<PencilIcon className="h-3 w-3 mr-1" />
|
||||
Edit
|
||||
</button>
|
||||
)}
|
||||
{canDeleteUsers && (
|
||||
<button
|
||||
onClick={() => handleToggleUserStatus(user)}
|
||||
className={`inline-flex items-center px-2 py-1 border border-transparent text-xs font-medium rounded focus:outline-none focus:ring-2 focus:ring-offset-2 ${
|
||||
user.is_active
|
||||
? 'text-orange-700 bg-orange-100 hover:bg-orange-200 focus:ring-orange-500'
|
||||
: 'text-green-700 bg-green-100 hover:bg-green-200 focus:ring-green-500'
|
||||
}`}
|
||||
title={user.is_active ? 'Deactivate user' : 'Activate user'}
|
||||
>
|
||||
{user.is_active ? 'Deactivate' : 'Activate'}
|
||||
</button>
|
||||
)}
|
||||
{canDeleteUsers && (
|
||||
<button
|
||||
onClick={() => handleDeleteUser(user)}
|
||||
className="inline-flex items-center px-2 py-1 border border-transparent text-xs font-medium rounded text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500"
|
||||
title="Delete user permanently"
|
||||
>
|
||||
<TrashIcon className="h-3 w-3 mr-1" />
|
||||
Delete
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</td>
|
||||
)}
|
||||
</tr>
|
||||
@@ -1349,6 +1369,43 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Delete Confirmation Modal */}
|
||||
{showDeleteConfirm && deletingUser && (
|
||||
<div className="fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
|
||||
<div className="relative top-20 mx-auto p-5 border w-96 shadow-lg rounded-md bg-white">
|
||||
<div className="mt-3 text-center">
|
||||
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
|
||||
<TrashIcon className="h-6 w-6 text-red-600" />
|
||||
</div>
|
||||
<h3 className="text-lg font-medium text-gray-900 mt-4">Delete User</h3>
|
||||
<div className="mt-2 px-7 py-3">
|
||||
<p className="text-sm text-gray-500">
|
||||
Are you sure you want to delete <strong>{deletingUser.username}</strong>?
|
||||
This action cannot be undone and will permanently remove the user and all associated data.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center space-x-4 mt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingUser(null);
|
||||
}}
|
||||
className="px-4 py-2 bg-gray-300 text-gray-700 rounded-md hover:bg-gray-400 focus:outline-none focus:ring-2 focus:ring-gray-500"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={confirmDeleteUser}
|
||||
className="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500"
|
||||
>
|
||||
Delete User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1369,6 +1426,25 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
||||
toast.error('Failed to update user status');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUser = (user) => {
|
||||
setDeletingUser(user);
|
||||
setShowDeleteConfirm(true);
|
||||
};
|
||||
|
||||
const confirmDeleteUser = async () => {
|
||||
if (!deletingUser) return;
|
||||
|
||||
try {
|
||||
await api.delete(`/tenant/users/${deletingUser.id}`);
|
||||
toast.success('User deleted successfully');
|
||||
fetchUsers();
|
||||
setShowDeleteConfirm(false);
|
||||
setDeletingUser(null);
|
||||
} catch (error) {
|
||||
toast.error('Failed to delete user');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create User Modal Component (for local auth only)
|
||||
|
||||
@@ -673,6 +673,84 @@ router.put('/users/:userId', authenticateToken, requirePermissions(['users.edit'
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* DELETE /tenant/users/:userId
|
||||
* Delete user permanently (user admin or higher, local auth only)
|
||||
*/
|
||||
router.delete('/users/:userId', authenticateToken, requirePermissions(['users.delete']), async (req, res) => {
|
||||
try {
|
||||
// Determine tenant from request
|
||||
const tenantId = await multiAuth.determineTenant(req);
|
||||
if (!tenantId) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Unable to determine tenant'
|
||||
});
|
||||
}
|
||||
|
||||
const tenant = await Tenant.findOne({ where: { slug: tenantId } });
|
||||
if (!tenant) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Tenant not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Check if tenant uses local authentication
|
||||
if (tenant.auth_provider !== 'local') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `User management is only available for local authentication. This tenant uses ${tenant.auth_provider}.`
|
||||
});
|
||||
}
|
||||
|
||||
const { userId } = req.params;
|
||||
|
||||
// Find user in this tenant
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
id: userId,
|
||||
tenant_id: tenant.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found in this tenant'
|
||||
});
|
||||
}
|
||||
|
||||
// Prevent self-deletion
|
||||
if (user.id === req.user.id) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Cannot delete your own user account'
|
||||
});
|
||||
}
|
||||
|
||||
// Store username for logging before deletion
|
||||
const deletedUsername = user.username;
|
||||
|
||||
// Delete the user permanently
|
||||
await user.destroy();
|
||||
|
||||
console.log(`⚠️ User "${deletedUsername}" permanently deleted from tenant "${tenantId}" by admin "${req.user.username}"`);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'User deleted successfully'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error deleting user:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Failed to delete user'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* GET /tenant/auth
|
||||
* Get authentication configuration (auth admins or higher)
|
||||
|
||||
Reference in New Issue
Block a user