HTTP API закрыт за вход через OIDC у Authelia

- шаг схемы закрывает поверхность, которую хранилище приносит открытой:
  собственную регистрацию, вход по паролю и одноразовый код — без этого
  закрытие приёма обходилось двумя запросами
- продление сессии выключено, срок семь суток: иначе отзыв доступа у
  провайдера до сервиса не доходит никогда
- файл записи отдаётся вошедшему по токену файла — пересмотр
  ADR-2026-08-12-file-link-open-but-not-logged
This commit is contained in:
av
2026-08-12 17:44:22 +03:00
parent d676df8a27
commit c44f0e7582
37 changed files with 3997 additions and 65 deletions
+68 -15
View File
@@ -70,6 +70,50 @@ type testEnv struct {
handler *TranscribeHandler
app core.App
journal *journalBuffer
// session — значение сессии вошедшего. Приём и опрос закрыты за
// аутентификацией, и проверка, судящая их по существу, обязана предъявить
// сессию ровно так же, как это делает браузер.
session string
// account — учётная запись, которой выдана сессия. Нужна проверкам выхода.
account *core.Record
}
// serve шлёт запрос от имени вошедшего: сессия предъявляется кукой — тем же
// способом, каким её предъявляет браузер. Заголовок проверки не ставят: куку в
// него перекладывает слой предъявления, и подмена его здесь означала бы проверку
// не той цепочки.
//
// Проверки, судящие отказ без сессии, зовут `mux` напрямую.
func (e *testEnv) serve(w http.ResponseWriter, req *http.Request) {
req.AddCookie(&http.Cookie{Name: SessionCookieName, Value: e.session})
e.mux.ServeHTTP(w, req)
}
// newTestAccount заводит учётную запись и выдаёт ей сессию.
//
// Запись создаётся прямым сохранением, а не запросом к API: заводить её
// запросом больше нельзя — создание закрыто шагом схемы, и в этом весь смысл
// изменения. Прямое сохранение идёт мимо правил доступа так же, как идёт вход,
// когда учётную запись заводит само хранилище.
func newTestAccount(t *testing.T, app core.App) (*core.Record, string) {
t.Helper()
users, err := app.FindCollectionByNameOrId("users")
require.NoError(t, err)
record := core.NewRecord(users)
record.Set("email", "person@example.com")
record.Set("verified", true)
// Случайный пароль ставит и само хранилище, когда заводит запись по входу у
// провайдера: запись auth-коллекции без пароля не сохраняется, а войти по
// нему всё равно нельзя — парольный вход выключен шагом схемы.
record.SetRandomPassword()
require.NoError(t, app.Save(record))
token, err := record.NewAuthToken()
require.NoError(t, err)
return record, token
}
// journalBuffer — перехваченный журнал одной проверки. Свой на случай: общий на
@@ -147,7 +191,16 @@ func setupTestEnv(t *testing.T, metaviewer contract.AudioMetaViewer) *testEnv {
mux, err := r.BuildMux()
require.NoError(t, err)
return &testEnv{mux: mux, handler: handler, app: app, journal: journal}
account, session := newTestAccount(t, app)
return &testEnv{
mux: mux,
handler: handler,
app: app,
journal: journal,
session: session,
account: account,
}
}
// createMultipartRequest собирает запрос из имени и содержимого. Файла на диске
@@ -241,7 +294,7 @@ func TestCreateTranscribeJob_Success(t *testing.T) {
req := createMultipartRequest(t, "sample.m4a", content)
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -304,7 +357,7 @@ func TestCreateTranscribeJob_NoFile(t *testing.T) {
env := setupTestEnv(t, readableMetaViewer())
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, tc.req(t))
env.serve(w, tc.req(t))
require.Equal(t, http.StatusBadRequest, w.Code)
@@ -327,7 +380,7 @@ func TestCreateTranscribeJob_EmptyFile(t *testing.T) {
req := createMultipartRequest(t, "empty.m4a", nil)
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -374,7 +427,7 @@ func TestCreateTranscribeJob_DifferentFileExtensions(t *testing.T) {
req := createMultipartRequest(t, tc.fileName, []byte("запись"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -400,7 +453,7 @@ func TestCreateTranscribeJob_SenderFileNameNotStored(t *testing.T) {
req := createMultipartRequest(t, "секретное-слово.mp3", []byte("запись"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -419,7 +472,7 @@ func TestCreateTranscribeJob_MetaViewerFailure(t *testing.T) {
req := createMultipartRequest(t, "broken.m4a", []byte("не запись вовсе"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusInternalServerError, w.Code)
@@ -459,7 +512,7 @@ func TestCreateTranscribeJob_SenderFileNameNotLogged(t *testing.T) {
req := createMultipartRequest(t, senderNameMarker+".mp3", []byte("запись"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -482,7 +535,7 @@ func TestCreateTranscribeJob_SenderFileNameNotLoggedOnFailure(t *testing.T) {
req := createMultipartRequest(t, senderNameMarker+".mp3", []byte("не запись вовсе"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusInternalServerError, w.Code)
@@ -507,7 +560,7 @@ func TestCreateTranscribeJob_StorageFileNameNotLogged(t *testing.T) {
req := createMultipartRequest(t, "sample.mp3", []byte("запись"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -528,7 +581,7 @@ func TestCreateTranscribeJob_JournalTracesRecord(t *testing.T) {
req := createMultipartRequest(t, "sample.mp3", content)
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -593,7 +646,7 @@ func TestCreateTranscribeJob_MetricLabelCarriesNoSenderName(t *testing.T) {
req := createMultipartRequest(t, "sample."+senderNameMarker, []byte("запись"))
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusCreated, w.Code)
@@ -622,7 +675,7 @@ func TestGetTranscribeJobStatus_Success(t *testing.T) {
req := httptest.NewRequest("GET", "/api/status/"+job.Id, http.NoBody)
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusOK, w.Code)
@@ -642,7 +695,7 @@ func TestGetTranscribeJobStatus_NoTranscriptionText(t *testing.T) {
req := httptest.NewRequest("GET", "/api/status/"+job.Id, http.NoBody)
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusOK, w.Code)
@@ -664,7 +717,7 @@ func TestGetTranscribeJobStatus_NotFound(t *testing.T) {
req := httptest.NewRequest("GET", "/api/status/non-existent-id", http.NoBody)
w := httptest.NewRecorder()
env.mux.ServeHTTP(w, req)
env.serve(w, req)
require.Equal(t, http.StatusNotFound, w.Code)