fix(settings): require re-2FA confirmation for sensitive setting changes (#5610)

* fix(settings): require server-side 2fa for sensitive changes

* fix(lint): group third-party imports separately from local (goimports)

golangci-lint goimports flagged setting.go and setting_security_test.go because xlzd/gotp and gorm.io/gorm were mixed into the github.com/mhsanaei/3x-ui local-prefix group. Move them into the third-party group so the local imports stand alone.
This commit is contained in:
n0ctal
2026-06-28 18:17:15 +05:00
committed by GitHub
parent 25a86b9ee2
commit 2b10808fbd
7 changed files with 97 additions and 23 deletions
+9 -4
View File
@@ -7,6 +7,8 @@ import { AllSetting } from '@/models/setting';
import { AllSettingSchema, type AllSettingInput } from '@/schemas/setting';
import { keys } from '@/api/queryKeys';
type SettingSavePayload = Partial<AllSetting> & Record<string, unknown>;
async function fetchAllSetting(): Promise<AllSettingInput | null> {
const msg = await HttpUtil.post('/panel/api/setting/all', undefined, { silent: true });
if (!msg?.success) throw new Error(msg?.msg || 'Failed to fetch settings');
@@ -42,19 +44,21 @@ export function useAllSettings() {
}, []);
const saveMut = useMutation({
mutationFn: async (next: AllSetting): Promise<Msg<unknown>> => {
const body = AllSettingSchema.partial().safeParse(next);
mutationFn: async (next: SettingSavePayload): Promise<Msg<unknown>> => {
const payload = { ...next };
const body = AllSettingSchema.partial().safeParse(payload);
if (!body.success) {
console.warn('[zod] setting/update body failed validation', body.error.issues);
}
return HttpUtil.post('/panel/api/setting/update', body.success ? body.data : next);
return HttpUtil.post('/panel/api/setting/update', body.success ? { ...payload, ...body.data } : payload);
},
onSuccess: (msg) => {
if (msg?.success) queryClient.invalidateQueries({ queryKey: keys.settings.all() });
},
});
const saveAll = useCallback(() => saveMut.mutateAsync(draft), [saveMut, draft]);
const saveAll = useCallback(() => saveMut.mutateAsync({ ...draft }), [saveMut, draft]);
const savePayload = useCallback((payload: SettingSavePayload) => saveMut.mutateAsync(payload), [saveMut]);
const saveDisabled = useMemo(() => server.equals(draft), [server, draft]);
return {
@@ -65,5 +69,6 @@ export function useAllSettings() {
setSpinning: setExtraSpinning,
saveDisabled,
saveAll,
savePayload,
};
}