namespace foodmarket.Api.Infrastructure;
/// Опции «быстрого входа» на тестовом стенде. По умолчанию выключен.
/// Включается ТОЛЬКО переменной окружения QuickLogin__Enabled=true
/// в stage-compose. В прод-compose этой переменной нет — флаг остаётся
/// в false и ни эндпоинт списка аккаунтов, ни «мастер-пароль» не включаются.
///
/// Жёсткий предохранитель : даже если флаг
/// случайно пробросили в прод, но OpenIddict:Issuer указывает на
/// admin.food-market.kz — быстрый вход всё равно выключен. Это защищает от
/// ошибок конфигурации (ошибочный set-env на prod-env) — конфиг-опечатка
/// не должна открыть прод.
public sealed class QuickLoginOptions
{
/// Главный флаг. При false любой код, относящийся к quick-login,
/// должен вести себя как в проде (эндпоинт 404, мастер-пароль не работает,
/// плашка не рендерится).
public bool Enabled { get; set; } = false;
/// Единый пароль-ключ для стенда. При включённом флаге подходит
/// к ЛЮБОМУ активному аккаунту в /connect/token grant=password.
/// Собственный пароль пользователя тоже продолжает работать (чтобы не
/// ломать e2e, seeded admin и т.д.).
public string MasterPassword { get; set; } = "123456";
/// Эффективный флаг с учётом safety-override по Issuer. Используй
/// везде, где решается «пускать мастер-пароль / отдавать список». Чтение
/// сравнивает Issuer с prod-доменом admin.food-market.kz — если совпадает
/// (или Issuer пустой), быстрый вход считается выключенным и в лог
/// пишется Warning один раз на старте через .
public bool EffectivelyEnabled(string? issuer, out string reason)
{
if (!Enabled)
{
reason = "QuickLogin:Enabled=false";
return false;
}
// Issuer обязателен — если его нет, не рискуем: dev/stage без явного
// Issuer не должны открывать мастер-пароль, иначе любое неверно
// настроенное окружение превращается в стенд.
if (string.IsNullOrWhiteSpace(issuer))
{
reason = "OpenIddict:Issuer пустой — не могу подтвердить, что это стенд";
return false;
}
// Прод-домен в lower-case: Uri сам нормализует хост, но подстрахуем.
if (!Uri.TryCreate(issuer, UriKind.Absolute, out var uri))
{
reason = $"OpenIddict:Issuer '{issuer}' не парсится как Uri";
return false;
}
var host = uri.Host.ToLowerInvariant();
// ТОЛЬКО точное совпадение с прод-хостом. Любой поддомен (test.admin.*,
// dev.admin.*) — это стенд, его трогать не надо. Если в будущем
// появится второй прод-хост, сюда дописывается новое условие.
if (host == "admin.food-market.kz")
{
reason = $"OpenIddict:Issuer host={host} — это ПРОД, быстрый вход принудительно выключен";
return false;
}
reason = $"OpenIddict:Issuer host={host} — стенд, быстрый вход включён";
return true;
}
/// Одноразовый лог при старте приложения. Пишется в лог, чтобы
/// в проде всегда было видно: «флаг пришёл, но Issuer=prod, выключили».
/// Вызывается из Program.cs после Build().
public void LogSafetyDecision(ILogger logger, string? issuer)
{
var effective = EffectivelyEnabled(issuer, out var reason);
if (effective)
{
logger.LogWarning("QuickLogin включён для стенда: {Reason}. " +
"Это норма для test.admin.food-market.kz. В проде должно быть выключено.", reason);
}
else if (Enabled)
{
// Enabled=true, но effective=false — явный safety-переключатель. Это серьёзно.
logger.LogWarning("QuickLogin:Enabled=true, но быстрый вход НЕ активирован. {Reason}", reason);
}
// Если Enabled=false — молчим, это обычный прод.
}
}