fix: address coderabbit review on #6214

- Remove invalid null return from session callback (must return session object)
- Prevent admin from disabling their own account (guard with isSelf check)
- Hide disable UI when viewing own profile
- Add user existence check to changeDisabled endpoint
- Add OpenAPI metadata to changeDisabled mutation
This commit is contained in:
ajnart committed 2026-07-06 06:31:42 +02:00
1 parent fbac0c8752
commit 39a1a3695e
4 files changed
+35 -12

No files matched your search

@@ -10,9 +10,10 @@ import { useI18n } from "@homarr/translation/client";
interface DisableUserButtonProps {
user: RouterOutputs["user"]["getById"];
isSelf?: boolean;
}
export const DisableUserButton = ({ user }: DisableUserButtonProps) => {
export const DisableUserButton = ({ user, isSelf = false }: DisableUserButtonProps) => {
const t = useI18n();
const [isPending, startTransition] = useTransition();
const changeDisabledMutation = clientApi.user.changeDisabled.useMutation();
@@ -32,6 +33,8 @@ export const DisableUserButton = ({ user }: DisableUserButtonProps) => {
};
const handleToggle = () => {
if (isSelf) return;
startTransition(async () => {
try {
await changeDisabledMutation.mutateAsync({
@@ -65,12 +68,13 @@ export const DisableUserButton = ({ user }: DisableUserButtonProps) => {
const getButtonColor = () => (isDisabled ? "green" : "yellow");
return (
<Tooltip label={getTooltipLabel()} position="top">
<Tooltip label={isSelf ? t("user.action.disableSelf.disabled", { defaultValue: "Cannot disable your own account" }) : getTooltipLabel()} position="top">
<Button
onClick={handleToggle}
loading={isPending || changeDisabledMutation.isPending}
color={getButtonColor()}
variant="light"
disabled={isSelf}
>
{getButtonLabel()}
</Button>
@@ -110,11 +110,13 @@ export default async function EditUserPage(props: Props) {
)}
<DangerZoneRoot>
<DangerZoneItem
label={getDisableItemLabel()}
description={getDisableItemDescription()}
action={<DisableUserButton user={user} />}
/>
{!isSelf && (
<DangerZoneItem
label={getDisableItemLabel()}
description={getDisableItemDescription()}
action={<DisableUserButton user={user} isSelf={isSelf} />}
/>
)}
<DangerZoneItem
label={t("user.action.delete.label")}
description={t("user.action.delete.description")}
+22
View File
@@ -790,7 +790,29 @@ export const userRouter = createTRPCRouter({
}),
)
.output(z.void())
.meta({
openapi: {
method: "PATCH",
path: "/api/users/disabled",
tags: ["users"],
protect: true,
},
})
.mutation(async ({ input, ctx }) => {
const dbUser = await ctx.db.query.users.findFirst({
columns: {
id: true,
},
where: eq(users.id, input.userId),
});
if (!dbUser) {
throw new TRPCError({
code: "NOT_FOUND",
message: "User not found",
});
}
await ctx.db
.update(users)
.set({
-5
View File
@@ -47,14 +47,9 @@ export const createSessionCallback = (db: Database): NextAuthCallbackOf<"session
where: eq(users.id, user.id),
columns: {
colorScheme: true,
disabled: true,
},
});
if (additionalProperties?.disabled) {
return null;
}
return {
...session,
user: {