feat(security): проверка Origin, строгий CSP и лимиты загрузок
- middleware OriginCheck: state-changing запросы с чужого Origin отклоняются (AGENT.md 9.7); запросы без Origin пропускаются — cookie уже SameSite=Lax - CSP перечисляет директивы явно (script-src/worker-src/manifest-src 'self', style-src с inline для React, img-src с внешними https для аватаров вебхуков, connect-src с доменом файлов и LiveKit); добавлены Permissions-Policy, Cross-Origin-Opener-Policy и X-Permitted-Cross-Domain-Policies - лимиты по AGENT.md 8.6: загрузки 10/мин и 100/сутки (вложения и аватары), реакции 20/мин; администратор инстанса лимиты обходит - step-up: смена прав роли (в теле PATCH) и удаление сервера (перед вызовом /auth/step-up) требуют свежего подтверждения личности - тесты: Origin (свой/чужой/GET), состав CSP, лимит реакций, step-up на права роли; исправлен вызов NewRateLimiter (второй аргумент — burst, не окно)
This commit is contained in:
@@ -84,6 +84,10 @@ func (s *Server) handleAvatarUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
if err := s.checkUploadLimit(currentUser); err != nil {
|
||||
writeHumaAPIError(w, err)
|
||||
return
|
||||
}
|
||||
limit := s.mediaLimit(ctx, mediaAvatar)
|
||||
r.Body = http.MaxBytesReader(w, r.Body, limit+maxMultipartOverhead)
|
||||
if err := r.ParseMultipartForm(maxMultipartMemory); err != nil { //nolint:gosec // объём ограничен MaxBytesReader
|
||||
@@ -203,6 +207,11 @@ func (s *Server) handleFileUpload(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
ctx := r.Context()
|
||||
// Загрузки: 10 в минуту и 100 в сутки на пользователя (AGENT.md 8.6).
|
||||
if err := s.checkUploadLimit(currentUser); err != nil {
|
||||
writeHumaAPIError(w, err)
|
||||
return
|
||||
}
|
||||
channelID, _, channel, err := s.requireChannelPermission(ctx, chi.URLParam(r, "channel_id"), currentUser, permissions.AttachFiles)
|
||||
if err != nil {
|
||||
writeHumaAPIError(w, err)
|
||||
@@ -383,6 +392,22 @@ func (s *Server) requireFileAccess(ctx context.Context, rawFileID string, user *
|
||||
return file, nil
|
||||
}
|
||||
|
||||
// checkUploadLimit применяет лимиты загрузок: минута и сутки (AGENT.md 8.6).
|
||||
// Администратор инстанса лимит обходит — как и в отправке сообщений.
|
||||
func (s *Server) checkUploadLimit(user *store.User) error {
|
||||
if user.IsInstanceAdmin {
|
||||
return nil
|
||||
}
|
||||
key := formatSnowflake(user.ID)
|
||||
if allowed, retryAfter := s.uploadLimiter.Allow("upload:" + key); !allowed {
|
||||
return rateLimitedError(retryAfter)
|
||||
}
|
||||
if allowed, retryAfter := s.uploadDailyLimiter.Allow("upload-day:" + key); !allowed {
|
||||
return rateLimitedError(retryAfter)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// saveUpload пишет файл на диск и регистрирует его в БД.
|
||||
func (s *Server) saveUpload(ctx context.Context, file store.File, content io.Reader) (*store.File, error) {
|
||||
if s.cfg.DataDir == "" {
|
||||
|
||||
Reference in New Issue
Block a user