у записи появился владелец: чужую больше не отдают
- колонка `owner` связью с `users` в обеих коллекциях новым шагом схемы `202608140001`; чтение задачи сужено владельцем, и чужая, ничья и несуществующая дают один ответ; правило просмотра файлов сужено им же - приём по HTTP берёт владельца из сессии, а предъявителя без учётной записи пользователя отвергает до чтения тела: позже пришлось бы убирать уложенный файл, а уборки файлов сервис не умеет. Выборка воркера владельцем не сужается - удаление учётной записи с записями отвергается стражем, и вешает его сама сборка хранилища: сборка, забывшая его позвать, теряла защиту молча
This commit is contained in:
@@ -2,6 +2,7 @@ package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/router"
|
||||
|
||||
"git.vakhrushev.me/av/transcriber/internal/adapter/repo/pocketbase/migrations"
|
||||
"git.vakhrushev.me/av/transcriber/internal/contract"
|
||||
"git.vakhrushev.me/av/transcriber/internal/entity"
|
||||
"git.vakhrushev.me/av/transcriber/internal/service"
|
||||
@@ -51,7 +53,13 @@ func (h *TranscribeHandler) Register(r *router.Router[*core.RequestEvent]) {
|
||||
// него не подпадает, часть её защищена ровно тем, что браузер заголовка сам
|
||||
// не шлёт.
|
||||
api.Bind(SessionFromCookie())
|
||||
api.Bind(apis.RequireAuth())
|
||||
// Коллекция названа поимённо, а не оставлена умолчанию. Без имени проверка
|
||||
// пускает всякую учётную запись хранилища, включая владельца панели, — а
|
||||
// записи в коллекции пользователей у него нет, и владельцем записи он стать
|
||||
// не может. Отказ такому предъявителю обязан наступить здесь, до чтения
|
||||
// тела: позже пришлось бы убирать уже уложенный файл, а уборки файлов
|
||||
// сервис не умеет вовсе.
|
||||
api.Bind(apis.RequireAuth(migrations.UsersCollection))
|
||||
|
||||
// Умолчание роутера хранилища — 32 МиБ на тело, и оно отсекало бы запись
|
||||
// раньше обработчика, без строки в журнале приёма. Приём размеру не судья,
|
||||
@@ -79,7 +87,11 @@ func (h *TranscribeHandler) CreateTranscribeJob(e *core.RequestEvent) error {
|
||||
// (журнал запроса, сессия) при этом сохраняются, теряется только отмена.
|
||||
ctx := context.WithoutCancel(e.Request.Context())
|
||||
|
||||
job, err := h.trsService.CreateJobFromApi(ctx, file, header.Filename)
|
||||
// Владелец берётся из предъявленной сессии и ниоткуда больше: владелец,
|
||||
// пришедший полем запроса, дал бы всякому вошедшему право завести запись на
|
||||
// чужое имя. Проверка предъявителя стоит слоем выше, поэтому здесь `e.Auth`
|
||||
// уже есть и принадлежит коллекции пользователей.
|
||||
job, err := h.trsService.CreateJobFromApi(ctx, file, header.Filename, e.Auth.Id)
|
||||
if err != nil {
|
||||
// Второй раз отказ не логируем: приём назван конвенцией логирующей
|
||||
// границей и уже написал о нём. Транспорт переводит ошибку в ответ.
|
||||
@@ -96,8 +108,22 @@ func (h *TranscribeHandler) CreateTranscribeJob(e *core.RequestEvent) error {
|
||||
func (h *TranscribeHandler) GetTranscribeJobStatus(e *core.RequestEvent) error {
|
||||
jobID := e.Request.PathValue("id")
|
||||
|
||||
job, err := h.jobRepo.GetByID(jobID)
|
||||
// Чужая задача, задача без владельца и несуществующая отвечают одним и тем
|
||||
// же: хранилище отдаёт на все три ту же ошибку, а транспорт — тот же код и
|
||||
// то же тело. Различать их наружу нельзя — по разнице ответов перебирается
|
||||
// список заведённых задач.
|
||||
job, err := h.jobRepo.GetByID(jobID, e.Auth.Id)
|
||||
if err != nil {
|
||||
// Наружу ответ один на все исходы, а в журнал они идут по-разному.
|
||||
// «Задачи нет» и «задача чужая» — штатная работа разграничения, о ней
|
||||
// писать нечего; всё прочее — отказ хранилища, и без этой строки он
|
||||
// приходит отправителю как «вашей записи нет», а владелец сервиса об
|
||||
// аварии не узнаёт ниоткуда. Журнал читает владелец, а не тот, кто
|
||||
// перебирает, поэтому различать их здесь можно.
|
||||
var notFound *contract.JobNotFoundError
|
||||
if !errors.As(err, ¬Found) {
|
||||
h.logger.Error("Failed to read transcribe job", "error", err, "job_id", jobID)
|
||||
}
|
||||
return e.JSON(http.StatusNotFound, map[string]string{"error": "Job not found"})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user