feat: Добавил больше логирования

This commit is contained in:
2025-11-14 01:50:40 +03:00
parent 8a469625c5
commit 0e7ee28791
4 changed files with 54 additions and 7 deletions

View File

@@ -1,61 +1,92 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;
using Microsoft.Playwright; using Microsoft.Playwright;
namespace ModeusSchedule.MSAuth.BrowserScripts; namespace ModeusSchedule.MSAuth.BrowserScripts;
public static class MicrosoftLoginHelper public static class MicrosoftLoginHelper
{ {
public static async Task LoginMicrosoftAsync(IPage page, string username, string password, string loginUrl) public static async Task LoginMicrosoftAsync(
IPage page,
ILogger logger,
string username,
string password,
string loginUrl
)
{ {
logger.LogInformation("Начало входа в Microsoft. Url: {LoginUrl}, пользователь: {Username}", loginUrl, username);
await page.GotoAsync(loginUrl, new PageGotoOptions { WaitUntil = WaitUntilState.DOMContentLoaded }); await page.GotoAsync(loginUrl, new PageGotoOptions { WaitUntil = WaitUntilState.DOMContentLoaded });
logger.LogDebug("Страница логина загружена, ожидаем переход на домен Microsoft.");
await page.WaitForURLAsync(new Regex("login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase), new PageWaitForURLOptions { Timeout = 60_000 }); await page.WaitForURLAsync(new Regex("login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase), new PageWaitForURLOptions { Timeout = 60_000 });
var useAnotherAccount = page.Locator("div#otherTile, #otherTileText, div[data-test-id='useAnotherAccount']").First; var useAnotherAccount = page.Locator("div#otherTile, #otherTileText, div[data-test-id='useAnotherAccount']").First;
try try
{ {
await Assertions.Expect(useAnotherAccount).ToBeVisibleAsync(new() { Timeout = 2000 }); await Assertions.Expect(useAnotherAccount).ToBeVisibleAsync(new() { Timeout = 2000 });
logger.LogDebug("Обнаружена кнопка 'Использовать другой аккаунт'. Нажимаем.");
await useAnotherAccount.ClickAsync(); await useAnotherAccount.ClickAsync();
} }
catch (PlaywrightException) catch (PlaywrightException ex)
{ {
// Кнопка не появилась — пропускаем logger.LogDebug(ex, "Кнопка 'Использовать другой аккаунт' не появилась — пропускаем шаг.");
} }
var emailInput = page.Locator("input[name='loginfmt'], input#i0116"); var emailInput = page.Locator("input[name='loginfmt'], input#i0116");
await emailInput.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 30_000 }); await emailInput.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 30_000 });
logger.LogDebug("Поле ввода email найдено, вводим логин.");
await emailInput.FillAsync(username); await emailInput.FillAsync(username);
var nextButton = page.Locator("#idSIButton9, input#idSIButton9"); var nextButton = page.Locator("#idSIButton9, input#idSIButton9");
await nextButton.ClickAsync(); await nextButton.ClickAsync();
logger.LogDebug("Нажата кнопка 'Далее' после ввода логина.");
var passwordInput = page.Locator("input[name='passwd'], input#i0118"); var passwordInput = page.Locator("input[name='passwd'], input#i0118");
await passwordInput.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 30_000 }); await passwordInput.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 30_000 });
logger.LogDebug("Поле ввода пароля найдено, вводим пароль.");
await passwordInput.FillAsync(password); await passwordInput.FillAsync(password);
await nextButton.ClickAsync(); await nextButton.ClickAsync();
logger.LogDebug("Нажата кнопка входа после ввода пароля.");
await page.WaitForSelectorAsync("button, input[type='submit'], a", new PageWaitForSelectorOptions { Timeout = 8000 }); await page.WaitForSelectorAsync("button, input[type='submit'], a", new PageWaitForSelectorOptions { Timeout = 8000 });
var locator = page.Locator("#idSIButton9, #idBtn_Back").First; var locator = page.Locator("#idSIButton9, #idBtn_Back").First;
try try
{ {
await Assertions.Expect(locator).ToBeVisibleAsync(new() { Timeout = 3000 }); await Assertions.Expect(locator).ToBeVisibleAsync(new() { Timeout = 3000 });
logger.LogDebug("Обнаружен экран 'Остаться в системе?'.");
var noBtn = page.Locator("#idBtn_Back"); var noBtn = page.Locator("#idBtn_Back");
if (await noBtn.IsVisibleAsync()) if (await noBtn.IsVisibleAsync())
{
logger.LogDebug("Нажимаем кнопку 'Нет'.");
await noBtn.ClickAsync(); await noBtn.ClickAsync();
}
else else
{
logger.LogDebug("Кнопка 'Нет' не найдена, нажимаем 'Да'/'Далее'.");
await page.Locator("#idSIButton9").ClickAsync(); await page.Locator("#idSIButton9").ClickAsync();
} }
catch (PlaywrightException) }
catch (PlaywrightException ex)
{ {
// Кнопки не появились — пропускаем этот шаг logger.LogDebug(ex, "Экран 'Остаться в системе?' не появился — пропускаем шаг.");
} }
logger.LogInformation("Ожидаем завершения редиректов после логина.");
await page.WaitForURLAsync(url => !Regex.IsMatch(new Uri(url).Host, "login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase), new PageWaitForURLOptions { Timeout = 60_000 }); await page.WaitForURLAsync(url => !Regex.IsMatch(new Uri(url).Host, "login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase), new PageWaitForURLOptions { Timeout = 60_000 });
await page.WaitForLoadStateAsync(LoadState.NetworkIdle); await page.WaitForLoadStateAsync(LoadState.NetworkIdle);
var currentHost = new Uri(page.Url).Host; var currentHost = new Uri(page.Url).Host;
if (Regex.IsMatch(currentHost, "login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase)) if (Regex.IsMatch(currentHost, "login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase))
{
logger.LogError("Авторизация не завершена: остались на странице Microsoft Login. Текущий URL: {Url}", page.Url);
throw new Exception("Авторизация не завершена: остались на странице Microsoft Login"); throw new Exception("Авторизация не завершена: остались на странице Microsoft Login");
} }
logger.LogInformation("Успешный вход в Microsoft. Текущий URL: {Url}", page.Url);
}
} }

View File

@@ -8,6 +8,8 @@ if (string.IsNullOrWhiteSpace(builder.Configuration["MODEUS_URL"]))
Environment.Exit(1); Environment.Exit(1);
} }
Console.WriteLine($"Используется URL Modeus: {builder.Configuration["MODEUS_URL"]}");
if (string.IsNullOrWhiteSpace(builder.Configuration["MS_USERNAME"]) || string.IsNullOrWhiteSpace(builder.Configuration["MS_PASSWORD"])) if (string.IsNullOrWhiteSpace(builder.Configuration["MS_USERNAME"]) || string.IsNullOrWhiteSpace(builder.Configuration["MS_PASSWORD"]))
{ {
Console.Error.WriteLine("Ошибка: не заданы учетные данные для MicrosoftAuth. Пожалуйста, укажите MS_USERNAME и MS_PASSWORD в файле конфигурации или переменных окружения."); Console.Error.WriteLine("Ошибка: не заданы учетные данные для MicrosoftAuth. Пожалуйста, укажите MS_USERNAME и MS_PASSWORD в файле конфигурации или переменных окружения.");

View File

@@ -18,11 +18,15 @@ public class MicrosoftAuthService(ILogger<MicrosoftAuthService> logger, IConfigu
public async Task<string> GetJwtAsync(CancellationToken ct = default) public async Task<string> GetJwtAsync(CancellationToken ct = default)
{ {
logger.LogDebug("Запрошен JWT токен. HasFreshToken=" + HasFreshToken);
await EnsureBrowsersAsync(); await EnsureBrowsersAsync();
// Если кэш актуален — вернуть сразу // Если кэш актуален — вернуть сразу
if (HasFreshToken) if (HasFreshToken)
{
logger.LogInformation("Возвращаем закэшированный JWT токен, возраст={AgeSeconds} сек", (DateTime.UtcNow - _cachedAtUtc).TotalSeconds);
return _cachedToken!; return _cachedToken!;
}
// Пытаемся единолично выполнить авторизацию // Пытаемся единолично выполнить авторизацию
if (!await FetchLock.WaitAsync(0, ct)) if (!await FetchLock.WaitAsync(0, ct))
@@ -45,19 +49,29 @@ public class MicrosoftAuthService(ILogger<MicrosoftAuthService> logger, IConfigu
try try
{ {
logger.LogInformation("Старт авторизации через Microsoft"); logger.LogInformation("Старт авторизации через Microsoft");
await MicrosoftLoginHelper.LoginMicrosoftAsync(page, configuration["MS_USERNAME"]!, configuration["MS_PASSWORD"]!, configuration["MODEUS_URL"]!); await MicrosoftLoginHelper.LoginMicrosoftAsync(page, logger, configuration["MS_USERNAME"]!, configuration["MS_PASSWORD"]!, configuration["MODEUS_URL"]!);
var sessionStorageJson = await page.EvaluateAsync<string>("JSON.stringify(sessionStorage)"); var sessionStorageJson = await page.EvaluateAsync<string>("JSON.stringify(sessionStorage)");
logger.LogDebug("Пробуем извлечь id_token из sessionStorage");
string? idToken = ExtractIdToken(sessionStorageJson); string? idToken = ExtractIdToken(sessionStorageJson);
if (string.IsNullOrWhiteSpace(idToken)) if (string.IsNullOrWhiteSpace(idToken))
{
logger.LogError("Не удалось извлечь id_token из sessionStorage");
throw new Exception("Не удалось извлечь id_token из sessionStorage"); throw new Exception("Не удалось извлечь id_token из sessionStorage");
}
// Сохраняем в кэш // Сохраняем в кэш
_cachedToken = idToken; _cachedToken = idToken;
_cachedAtUtc = DateTime.UtcNow; _cachedAtUtc = DateTime.UtcNow;
logger.LogInformation("Успешно получили и закэшировали id_token");
return idToken; return idToken;
} }
catch (Exception ex) when (ex is not MicrosoftAuthInProgressException)
{
logger.LogError(ex, "Ошибка при получении JWT через Microsoft авторизацию");
throw;
}
finally finally
{ {
await context.CloseAsync(); await context.CloseAsync();

View File

@@ -1,7 +1,7 @@
{ {
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Debug",
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },