allow admin to see all users even not self created, admin can delete admin accounts

This commit is contained in:
Yinyin Liu 2026-06-02 09:37:04 +02:00
parent edb30286fa
commit 3a5c203664
5 changed files with 61 additions and 14 deletions

View File

@ -498,6 +498,20 @@ public class Controller : ControllerBase
.ToList(); .ToList();
} }
[HttpGet(nameof(GetAllUsers))]
public ActionResult<IEnumerable<User>> GetAllUsers(Token authToken)
{
var user = Db.GetSession(authToken)?.User;
if (user == null)
return Unauthorized();
if (user.UserType != 2) // admins only
return Unauthorized();
return Db.Users
.Select(u => u.HidePassword())
.ToList();
}
[HttpGet(nameof(GetAllInstallationsFromProduct))] [HttpGet(nameof(GetAllInstallationsFromProduct))]
public ActionResult<IEnumerable<Installation>> GetAllInstallationsFromProduct(int product,Token authToken) public ActionResult<IEnumerable<Installation>> GetAllInstallationsFromProduct(int product,Token authToken)

View File

@ -367,12 +367,17 @@ public static class SessionMethods
{ {
var sessionUser = session?.User; var sessionUser = session?.User;
var originalUser = Db.GetUserById(editedUser?.Id); var originalUser = Db.GetUserById(editedUser?.Id);
return editedUser is not null if (editedUser is null || sessionUser is null || originalUser is null)
&& sessionUser is not null return false;
&& originalUser is not null
&& sessionUser.UserType !=0 // email must stay unique; pre-check to avoid hitting the DB [Unique] constraint (500)
&& sessionUser.HasAccessTo(originalUser) var emailOwner = Db.GetUserByEmail(editedUser.Email);
return sessionUser.UserType != 0
&& originalUser.Id != 0 // never edit the root user
&& (sessionUser.UserType == 2 || sessionUser.HasAccessTo(originalUser)) // admins may edit any user
&& (emailOwner is null || emailOwner.Id == editedUser.Id) // email not taken by another user
&& editedUser && editedUser
.WithParentOf(originalUser) // prevent moving .WithParentOf(originalUser) // prevent moving
.WithPasswordOf(originalUser) .WithPasswordOf(originalUser)
@ -397,7 +402,9 @@ public static class SessionMethods
return sessionUser is not null return sessionUser is not null
&& userToDelete is not null && userToDelete is not null
&& sessionUser.UserType !=0 && sessionUser.UserType !=0
&& sessionUser.HasAccessTo(userToDelete) && userToDelete.Id != 0 // never delete the root user
&& userToDelete.Id != sessionUser.Id // never self-delete (avoid lockout)
&& (sessionUser.UserType == 2 || sessionUser.HasAccessTo(userToDelete)) // admins may delete any user
&& Db.Delete(userToDelete); && Db.Delete(userToDelete);
} }

View File

@ -164,8 +164,18 @@ public static partial class Db
Boolean DeleteUserAndHisDependencies() Boolean DeleteUserAndHisDependencies()
{ {
// Re-parent the deleted user's children up to its own parent so no subtree is orphaned
// (a dangling ParentId would make the children invisible/unmanageable in the tree).
var children = Users.Where(u => u.ParentId == user.Id).ToList();
foreach (var child in children)
{
child.ParentId = user.ParentId;
Connection.Update(child);
}
FolderAccess .Delete(u => u.UserId == user.Id); FolderAccess .Delete(u => u.UserId == user.Id);
InstallationAccess.Delete(u => u.UserId == user.Id); InstallationAccess.Delete(u => u.UserId == user.Id);
Sessions .Delete(s => s.UserId == user.Id); // kill the deleted user's login sessions immediately
return Users.Delete(u => u.Id == user.Id) > 0; return Users.Delete(u => u.Id == user.Id) > 0;
} }
} }

View File

@ -20,8 +20,8 @@ import { UserType } from '../../../interfaces/UserTypes';
function UsersSearch() { function UsersSearch() {
const intl = useIntl(); const intl = useIntl();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const { availableUsers, fetchAvailableUsers } = useContext(AccessContext); const { allUsers, fetchAllUsers } = useContext(AccessContext);
const [filteredData, setFilteredData] = useState(availableUsers); const [filteredData, setFilteredData] = useState(allUsers);
const [openModal, setOpenModal] = useState(false); const [openModal, setOpenModal] = useState(false);
const context = useContext(UserContext); const context = useContext(UserContext);
const [userCreated, setUserCreated] = useState(false); const [userCreated, setUserCreated] = useState(false);
@ -29,19 +29,19 @@ function UsersSearch() {
const { currentUser } = context; const { currentUser } = context;
useEffect(() => { useEffect(() => {
fetchAvailableUsers(); fetchAllUsers();
}, []); }, []);
const fetchDataAgain = () => { const fetchDataAgain = () => {
fetchAvailableUsers(); fetchAllUsers();
}; };
useEffect(() => { useEffect(() => {
const filtered = availableUsers.filter((item) => const filtered = allUsers.filter((item) =>
item.name.toLowerCase().includes(searchTerm.toLowerCase()) item.name.toLowerCase().includes(searchTerm.toLowerCase())
); );
setFilteredData(filtered); setFilteredData(filtered);
}, [searchTerm, availableUsers]); }, [searchTerm, allUsers]);
const handleSubmit = () => { const handleSubmit = () => {
setOpenModal(true); setOpenModal(true);
@ -50,7 +50,7 @@ function UsersSearch() {
setOpenModal(false); setOpenModal(false);
setUserCreated(true); setUserCreated(true);
fetchAvailableUsers(); fetchAllUsers();
setTimeout(() => { setTimeout(() => {
setUserCreated(false); setUserCreated(false);

View File

@ -19,6 +19,8 @@ interface AccessContextProviderProps {
accessibleInstallationsForUser: I_Installation[]; accessibleInstallationsForUser: I_Installation[];
availableUsers: InnovEnergyUser[]; availableUsers: InnovEnergyUser[];
fetchAvailableUsers: () => Promise<void>; fetchAvailableUsers: () => Promise<void>;
allUsers: InnovEnergyUser[];
fetchAllUsers: () => Promise<void>;
usersWithDirectAccess: InnovEnergyUser[]; usersWithDirectAccess: InnovEnergyUser[];
fetchUsersWithDirectAccessForResource: ( fetchUsersWithDirectAccessForResource: (
tempresourceType: string, tempresourceType: string,
@ -53,6 +55,10 @@ export const AccessContext = createContext<AccessContextProviderProps>({
fetchAvailableUsers: () => { fetchAvailableUsers: () => {
return Promise.resolve(); return Promise.resolve();
}, },
allUsers: [],
fetchAllUsers: () => {
return Promise.resolve();
},
usersWithDirectAccess: [], usersWithDirectAccess: [],
fetchUsersWithDirectAccessForResource: () => Promise.resolve(), fetchUsersWithDirectAccessForResource: () => Promise.resolve(),
usersWithInheritedAccess: [], usersWithInheritedAccess: [],
@ -80,6 +86,7 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
InnovEnergyUser[] InnovEnergyUser[]
>([]); >([]);
const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]); const [availableUsers, setAvailableUsers] = useState<InnovEnergyUser[]>([]);
const [allUsers, setAllUsers] = useState<InnovEnergyUser[]>([]);
const [accessibleInstallationsForUser, setAccessibleInstallationsForUser] = const [accessibleInstallationsForUser, setAccessibleInstallationsForUser] =
useState<I_Installation[]>([]); useState<I_Installation[]>([]);
@ -141,6 +148,13 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
}); });
}; };
// Admin-only: every user in the system (for the Users management page).
const fetchAllUsers = async (): Promise<void> => {
return axiosConfig.get('/GetAllUsers').then((res) => {
setAllUsers(res.data);
});
};
const RevokeAccessFromResource = useCallback( const RevokeAccessFromResource = useCallback(
async ( async (
resourceType: string, resourceType: string,
@ -187,6 +201,8 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
accessibleInstallationsForUser, accessibleInstallationsForUser,
availableUsers, availableUsers,
fetchAvailableUsers, fetchAvailableUsers,
allUsers,
fetchAllUsers,
usersWithDirectAccess, usersWithDirectAccess,
fetchUsersWithDirectAccessForResource, fetchUsersWithDirectAccessForResource,
usersWithInheritedAccess, usersWithInheritedAccess,