Fix jwt-token
This commit is contained in:
@@ -10,7 +10,9 @@ import {
|
|||||||
GlobeAltIcon,
|
GlobeAltIcon,
|
||||||
KeyIcon,
|
KeyIcon,
|
||||||
EyeIcon,
|
EyeIcon,
|
||||||
EyeSlashIcon
|
EyeSlashIcon,
|
||||||
|
TrashIcon,
|
||||||
|
PencilIcon
|
||||||
} from '@heroicons/react/24/outline';
|
} from '@heroicons/react/24/outline';
|
||||||
import { hasPermission, canAccessSettings } from '../utils/rbac';
|
import { hasPermission, canAccessSettings } from '../utils/rbac';
|
||||||
|
|
||||||
@@ -1099,6 +1101,8 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
const [showCreateUser, setShowCreateUser] = useState(false);
|
const [showCreateUser, setShowCreateUser] = useState(false);
|
||||||
const [showEditUser, setShowEditUser] = useState(false);
|
const [showEditUser, setShowEditUser] = useState(false);
|
||||||
const [editingUser, setEditingUser] = useState(null);
|
const [editingUser, setEditingUser] = useState(null);
|
||||||
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
||||||
|
const [deletingUser, setDeletingUser] = useState(null);
|
||||||
|
|
||||||
const authProvider = tenantConfig?.auth_provider;
|
const authProvider = tenantConfig?.auth_provider;
|
||||||
const isLocalAuth = authProvider === 'local';
|
const isLocalAuth = authProvider === 'local';
|
||||||
@@ -1260,25 +1264,41 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
</td>
|
</td>
|
||||||
{(canEditUsers || canDeleteUsers) && (
|
{(canEditUsers || canDeleteUsers) && (
|
||||||
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium">
|
||||||
{canEditUsers && (
|
<div className="flex items-center space-x-2">
|
||||||
<button
|
{canEditUsers && (
|
||||||
onClick={() => handleEditUser(user)}
|
<button
|
||||||
className="text-primary-600 hover:text-primary-900 mr-4"
|
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"
|
||||||
Edit
|
title="Edit user"
|
||||||
</button>
|
>
|
||||||
)}
|
<PencilIcon className="h-3 w-3 mr-1" />
|
||||||
{canDeleteUsers && (
|
Edit
|
||||||
<button
|
</button>
|
||||||
onClick={() => handleToggleUserStatus(user)}
|
)}
|
||||||
className={user.is_active
|
{canDeleteUsers && (
|
||||||
? 'text-red-600 hover:text-red-900'
|
<button
|
||||||
: 'text-green-600 hover:text-green-900'
|
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
|
||||||
{user.is_active ? 'Deactivate' : 'Activate'}
|
? 'text-orange-700 bg-orange-100 hover:bg-orange-200 focus:ring-orange-500'
|
||||||
</button>
|
: '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>
|
</td>
|
||||||
)}
|
)}
|
||||||
</tr>
|
</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>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1369,6 +1426,25 @@ const UsersSettings = ({ tenantConfig, onRefresh }) => {
|
|||||||
toast.error('Failed to update user status');
|
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)
|
// 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 /tenant/auth
|
||||||
* Get authentication configuration (auth admins or higher)
|
* Get authentication configuration (auth admins or higher)
|
||||||
|
|||||||
Reference in New Issue
Block a user