feat(instance): админ-панель с лимитами медиа и действиями над пользователями
- лимиты медиа живут в настройках инстанса (миграция 00016): аватары, оформление сервера, эмодзи, звуки и галерея; все загрузки берут предел оттуда, значения по умолчанию — из AGENT.md 7.7 - действия администратора: временный пароль (показывается один раз, сессии отзываются), выход со всех устройств, мягкое удаление пользователя (сообщения и аудит остаются), переименование и удаление любого сервера - админ-панель разбита на вкладки: обзор и здоровье, лимиты, пользователи, серверы, аудит; смена администраторов подтверждается личностью (step-up) - тесты: Go (действия администратора, лимит эмодзи из настроек) и Vitest (вкладки, сброс пароля, выход, удаление, переименование сервера)
This commit is contained in:
@@ -28,8 +28,6 @@ const (
|
||||
maxMultipartOverhead = 1 << 20
|
||||
// maxMultipartMemory — сколько multipart держим в памяти, остальное — на диске.
|
||||
maxMultipartMemory = 8 << 20
|
||||
// maxAvatarSize — предел размера аватара (AGENT.md 7.2).
|
||||
maxAvatarSize = 8 << 20
|
||||
)
|
||||
|
||||
// uploadPayload — результат загрузки файла: метаданные для вложения.
|
||||
@@ -86,7 +84,8 @@ func (s *Server) handleAvatarUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxAvatarSize+maxMultipartOverhead)
|
||||
limit := s.mediaLimit(ctx, mediaAvatar)
|
||||
r.Body = http.MaxBytesReader(w, r.Body, limit+maxMultipartOverhead)
|
||||
if err := r.ParseMultipartForm(maxMultipartMemory); err != nil { //nolint:gosec // объём ограничен MaxBytesReader
|
||||
httpxWriteJSONError(w, http.StatusBadRequest, "request.bad", "malformed multipart body")
|
||||
return
|
||||
@@ -102,14 +101,14 @@ func (s *Server) handleAvatarUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
defer func() { _ = file.Close() }()
|
||||
if header.Size > maxAvatarSize {
|
||||
if header.Size > limit {
|
||||
httpxWriteJSONError(w, http.StatusRequestEntityTooLarge, "file.too_large", "avatar is too large")
|
||||
return
|
||||
}
|
||||
// Аватар читаем в память (не больше 8 МБ): нужно проверить, что это
|
||||
// действительно изображение, а не переименованный файл (AGENT.md 9.2).
|
||||
data, err := io.ReadAll(io.LimitReader(file, maxAvatarSize+1))
|
||||
if err != nil || int64(len(data)) > maxAvatarSize {
|
||||
data, err := io.ReadAll(io.LimitReader(file, limit+1))
|
||||
if err != nil || int64(len(data)) > limit {
|
||||
httpxWriteJSONError(w, http.StatusRequestEntityTooLarge, "file.too_large", "avatar is too large")
|
||||
return
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user