⟨/⟩ 40_Arch/Tech/CSharp/ADR_SRV_004_AppConfigAndCors.cs

146 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;

namespace Ban.Sdaid.Icd.Arch.Tech.CSharp
{
    /// <summary>
    /// ADR-SRV-004: Конфигурация бэка по средам и CORS. Параметры среды — в appsettings
    /// (подменяются деплоем), CORS-origins фронта — из конфига (allowlist по среде).
    /// </summary>
    public class ADR_SRV_004_AppConfigAndCors : IAdrDocument, IFolder<ArchCSharpFolder>
    {
        public string Name => "ADR-SRV-004. Конфигурация бэка и CORS";

        public string Description =>
            @"Бэк должен разворачиваться на стенде, где фронт и API — на разных origin. Фиксируем: параметры
среды берутся из appsettings (стандартный конфиг ASP.NET, подменяется деплоем), список разрешённых
origin фронта — из `Cors:AllowedOrigins` (allowlist по среде); в локальной разработке (список пуст) CORS
разрешает любой origin. Swagger UI — только вне прода.";

        public string Version => "1.0";
        public string Status => "accepted";
        public string[] Comments => new[]
        {
            "2026-08-13. Принято перед разворачиванием на стенде (фронт и API на разных origin).",
        };

        public Type? Supersedes => null;

        public static string S1_Context = """
            ## Контекст и постановка задачи

            Сервис разворачивается на стендах (dev/stage/prod). Фронт и API там — на **разных origin**,
            поэтому бэку нужен **CORS-allowlist**: браузер разрешит запрос со страницы фронта, только если
            его origin в списке разрешённых. Список различается по среде, а артефакт сборки — один, значит
            origin'ы нельзя «зашивать» в код.

            Нужно зафиксировать: откуда бэк берёт параметры среды и как настраивается CORS, чтобы
            разворачивание было предсказуемым, а локальная разработка не требовала ручной настройки.
            """;

        public static string S2_DecisionDrivers = """
            ## Драйверы решения

            1. **Один билд на все среды** — параметры подменяются конфигом, не пересборкой.
            2. **Безопасность CORS** — на стенде разрешаем только известные origin фронта, не «любой».
            3. **Ноль трения локально** — `ng serve` на :4200 ходит на бэк без ручной настройки CORS.
            4. **Стандартные механизмы ASP.NET** — `appsettings`, `IConfiguration`, без своих слоёв.
            """;

        public static string S3_ConsideredOptions = """
            ## Рассмотренные варианты

            - **A (выбран).** Параметры среды — в `appsettings`; `Cors:AllowedOrigins` — allowlist из конфига;
              пустой список = разрешить любой origin (локальная разработка).
            - **B.** Всегда `AllowAnyOrigin`. Просто, но на стенде небезопасно.
            - **C.** Origin'ы в переменных окружения/коде. Дробит конфиг, хуже читается, чем один `appsettings`.
            """;

        public static string S4_DecisionOutcome = """
            ## Решение

            Выбран **Вариант A**.

            ### Параметры среды — в appsettings

            `appsettings.json` — стандартный конфиг ASP.NET (`IConfiguration`). На стенде файл подменяется
            деплоем (генерится `devops/configs-generator` вместе с рантайм-конфигом фронта). В репозитории —
            значения для локальной разработки. Имя окружения задаётся `ASPNETCORE_ENVIRONMENT` при запуске.

            ### CORS — allowlist из конфига

            ```
            "Cors": { "AllowedOrigins": ["https://sok.dev.example"] }
            ```

            ```csharp
            var allowedOrigins = builder.Configuration.GetSection("Cors:AllowedOrigins").Get<string[]>() ?? [];
            builder.Services.AddCors(o => o.AddPolicy(policy, p =>
            {
                if (allowedOrigins.Length > 0)
                    p.WithOrigins(allowedOrigins).AllowAnyHeader().AllowAnyMethod();
                else
                    p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod(); // локальная разработка
            }));
            app.UseCors(policy);
            ```

            - **Список задан** (стенд) → разрешаем только эти origin.
            - **Список пуст** (локальная разработка) → разрешаем любой origin, чтобы `ng serve` работал без
              настройки.

            ### Swagger UI — только вне прода

            Браузерная страница `/swagger` включается только при `ASPNETCORE_ENVIRONMENT=Development`
            (на проде — выключена). Сам контракт `/openapi/v1.json` отдаётся всегда.

            ### Что не делаем

            - Не зашиваем origin'ы в код.
            - Не оставляем `AllowAnyOrigin` на стенде.
            - Не заводим свой слой конфигурации поверх `IConfiguration`.
            """;

        public static string S5_PositiveConsequences = """
            ## Положительные следствия

            - Один билд → все среды; CORS и параметры меняет только конфиг.
            - На стенде CORS ограничен известными origin.
            - Локальная разработка — без ручной настройки CORS.
            """;

        public static string S6_NegativeConsequences = """
            ## Отрицательные следствия и компромиссы

            - Пустой список = «любой origin» удобен локально, но опасен, если случайно уедет на стенд —
              поэтому на стендах список **обязателен** и проверяется при деплое.
            """;

        public static string S7_Validation = """
            ## Проверка

            - С заданным `Cors:AllowedOrigins` запрос с чужого origin блокируется браузером, с разрешённого — проходит.
            - С пустым списком локальный `ng serve` (:4200) ходит на бэк (:5080) без ошибок CORS.
            - На проде `/swagger` недоступен; `/openapi/v1.json` доступен.
            """;

        public static string S8_OpenQuestions = """
            ## Открытые вопросы / отложено

            - **Строки подключения к БД** — появятся в `appsettings` с введением персистентности (ADR-SRV-003).
            - **Секреты** — не в коммитимом `appsettings`: локально — `appsettings.Local.json` (в `.gitignore`,
              подключается в `Program.cs`, грузится в любой среде), на стендах — через devops-генератор /
              переменные окружения (напр. `Orthocover:ApiKey`, ADR-SRV-005). Полноценная аутентификация — с auth-срезом.
            - **Проверка непустого allowlist на не-Development** — можно добавить fail-fast при старте.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Tech.Angular.ADR_UI_014_RuntimeConfig)} — рантайм-конфиг фронта (`config.json`), парная схема.
            - {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_001_Cqrs)} — обёртка ответа и композиция хоста.
            """;
    }
}