fix(security): update dependencies to avoid RCE and other exploits

Обновлены зависимости Node.js, которые были уязвимы с разной степенью критичности.
Обновлен Next.js, так как его предыдущая используемая версия привнесла в production-среду постоянную борьбу с майнерами.
К сожалению, в этом коммите парсер расписания сломан.
This commit is contained in:
kilyabin
2026-02-11 02:45:44 +04:00
parent 47b8bc7dad
commit b9ae52681e
13 changed files with 607 additions and 1354 deletions

View File

@@ -364,8 +364,8 @@ export default function AdminPage({ groups: initialGroups, settings: initialSett
try {
const res = await fetch('/api/admin/logs')
const data = await res.json()
if (data.success && data.logs) {
setLogs(data.logs)
if (data.success) {
setLogs(data.logs ?? '')
} else {
setLogs(data.error || 'Не удалось загрузить логи')
}
@@ -533,6 +533,19 @@ export default function AdminPage({ groups: initialGroups, settings: initialSett
disabled={loading}
/>
</div>
<div className="flex items-center justify-between p-4 border rounded-lg">
<div>
<div className="font-semibold">Кнопка "Преподаватели"</div>
<div className="text-sm text-muted-foreground">
Отображать кнопку перехода к расписанию преподавателей на главной странице
</div>
</div>
<ToggleSwitch
checked={settings.showTeachersButton ?? true}
onChange={(checked) => handleUpdateSettings({ ...settings, showTeachersButton: checked })}
disabled={loading}
/>
</div>
<div className="p-4 border rounded-lg">
<div className="flex items-center justify-between mb-3">
<div>
@@ -892,7 +905,7 @@ export default function AdminPage({ groups: initialGroups, settings: initialSett
<DialogHeader>
<DialogTitle>Логи ошибок</DialogTitle>
<DialogDescription>
Содержимое файла error.log
Ошибки парсинга записываются в error.log. Если записей пока нет здесь будет пусто.
</DialogDescription>
</DialogHeader>
<div className="mt-4">

View File

@@ -14,18 +14,18 @@ async function handler(
// Путь к файлу логов (в корне проекта)
const logPath = path.join(process.cwd(), 'error.log')
// Проверяем существование файла
// Проверяем существование файла (если нет — возвращаем пустую строку, UI покажет «Логи пусты»)
if (!fs.existsSync(logPath)) {
res.status(200).json({ success: true, logs: 'Файл логов пуст или не существует.' })
res.status(200).json({ success: true, logs: '' })
return
}
// Читаем файл
const logs = fs.readFileSync(logPath, 'utf8')
// Если файл пуст
// Если файл пуст — возвращаем пустую строку
if (!logs || logs.trim().length === 0) {
res.status(200).json({ success: true, logs: 'Файл логов пуст.' })
res.status(200).json({ success: true, logs: '' })
return
}

View File

@@ -24,7 +24,7 @@ async function handler(
const currentSettings = loadSettings(true)
// Обновление настроек
const { weekNavigationEnabled, showAddGroupButton, vacationModeEnabled, vacationModeContent, debug } = req.body
const { weekNavigationEnabled, showAddGroupButton, showTeachersButton, vacationModeEnabled, vacationModeContent, debug } = req.body
if (typeof weekNavigationEnabled !== 'boolean') {
res.status(400).json({ error: 'weekNavigationEnabled must be a boolean' })
@@ -36,6 +36,11 @@ async function handler(
return
}
if (showTeachersButton !== undefined && typeof showTeachersButton !== 'boolean') {
res.status(400).json({ error: 'showTeachersButton must be a boolean' })
return
}
if (vacationModeEnabled !== undefined && typeof vacationModeEnabled !== 'boolean') {
res.status(400).json({ error: 'vacationModeEnabled must be a boolean' })
return
@@ -77,6 +82,7 @@ async function handler(
...currentSettings,
weekNavigationEnabled,
showAddGroupButton: showAddGroupButton !== undefined ? showAddGroupButton : (currentSettings.showAddGroupButton ?? true),
showTeachersButton: showTeachersButton !== undefined ? showTeachersButton : (currentSettings.showTeachersButton ?? true),
vacationModeEnabled: vacationModeEnabled !== undefined ? vacationModeEnabled : (currentSettings.vacationModeEnabled ?? false),
vacationModeContent: vacationModeContent !== undefined ? vacationModeContent : (currentSettings.vacationModeContent || ''),
...(validatedDebug !== undefined && { debug: validatedDebug })

View File

@@ -32,6 +32,7 @@ type NormalModeProps = {
groups: GroupsData
groupsByCourse: { [course: number]: Array<{ id: string; name: string }> }
showAddGroupButton: boolean
showTeachersButton: boolean
}
type HomePageProps = VacationModeProps | NormalModeProps
@@ -113,7 +114,7 @@ export default function HomePage(props: HomePageProps) {
}
// Обычный режим - список групп
const { groups, groupsByCourse, showAddGroupButton } = props
const { groups, groupsByCourse, showAddGroupButton, showTeachersButton } = props
const [openCourses, setOpenCourses] = React.useState<Set<number>>(new Set())
const [addGroupDialogOpen, setAddGroupDialogOpen] = React.useState(false)
@@ -235,16 +236,18 @@ export default function HomePage(props: HomePageProps) {
)}
{/* Кнопка перехода к расписанию преподавателей */}
<div
className="stagger-card mt-6"
style={{ animationDelay: `${0.15 + courseOffsets.totalGroups * 0.04 + 0.05}s` } as React.CSSProperties}
>
<Link href="/teachers" className="block">
<Button variant="default" className="w-full h-auto py-4 text-base font-semibold">
Расписание преподавателей
</Button>
</Link>
</div>
{showTeachersButton && (
<div
className="stagger-card mt-6"
style={{ animationDelay: `${0.15 + courseOffsets.totalGroups * 0.04 + 0.05}s` } as React.CSSProperties}
>
<Link href="/teachers" className="block">
<Button variant="default" className="w-full h-auto py-4 text-base font-semibold">
Расписание преподавателей
</Button>
</Link>
</div>
)}
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 mt-8">
{showAddGroupButton && (
@@ -349,7 +352,8 @@ export const getServerSideProps: GetServerSideProps<HomePageProps> = async () =>
vacationModeEnabled: false,
groups,
groupsByCourse,
showAddGroupButton: settings.showAddGroupButton ?? true
showAddGroupButton: settings.showAddGroupButton ?? true,
showTeachersButton: settings.showTeachersButton ?? true
} as NormalModeProps
}
}