From 0e7ee28791835849e073b50f43f2bb8b88e5dfec Mon Sep 17 00:00:00 2001 From: Sergey Karmanov Date: Fri, 14 Nov 2025 01:50:40 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BrowserScripts/MicrosoftLoginHelper.cs | 41 +++++++++++++++++++--- src/Program.cs | 2 ++ src/Services/MicrosoftAuthService.cs | 16 ++++++++- src/appsettings.Development.json | 2 +- 4 files changed, 54 insertions(+), 7 deletions(-) diff --git a/src/BrowserScripts/MicrosoftLoginHelper.cs b/src/BrowserScripts/MicrosoftLoginHelper.cs index 9a1625d..bdfdd8b 100644 --- a/src/BrowserScripts/MicrosoftLoginHelper.cs +++ b/src/BrowserScripts/MicrosoftLoginHelper.cs @@ -1,61 +1,92 @@ using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; using Microsoft.Playwright; namespace ModeusSchedule.MSAuth.BrowserScripts; 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 }); + logger.LogDebug("Страница логина загружена, ожидаем переход на домен Microsoft."); + 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; try { await Assertions.Expect(useAnotherAccount).ToBeVisibleAsync(new() { Timeout = 2000 }); + logger.LogDebug("Обнаружена кнопка 'Использовать другой аккаунт'. Нажимаем."); await useAnotherAccount.ClickAsync(); } - catch (PlaywrightException) + catch (PlaywrightException ex) { - // Кнопка не появилась — пропускаем + logger.LogDebug(ex, "Кнопка 'Использовать другой аккаунт' не появилась — пропускаем шаг."); } var emailInput = page.Locator("input[name='loginfmt'], input#i0116"); await emailInput.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 30_000 }); + logger.LogDebug("Поле ввода email найдено, вводим логин."); await emailInput.FillAsync(username); var nextButton = page.Locator("#idSIButton9, input#idSIButton9"); await nextButton.ClickAsync(); + logger.LogDebug("Нажата кнопка 'Далее' после ввода логина."); + var passwordInput = page.Locator("input[name='passwd'], input#i0118"); await passwordInput.WaitForAsync(new LocatorWaitForOptions { State = WaitForSelectorState.Visible, Timeout = 30_000 }); + logger.LogDebug("Поле ввода пароля найдено, вводим пароль."); await passwordInput.FillAsync(password); await nextButton.ClickAsync(); + logger.LogDebug("Нажата кнопка входа после ввода пароля."); + await page.WaitForSelectorAsync("button, input[type='submit'], a", new PageWaitForSelectorOptions { Timeout = 8000 }); var locator = page.Locator("#idSIButton9, #idBtn_Back").First; try { await Assertions.Expect(locator).ToBeVisibleAsync(new() { Timeout = 3000 }); + logger.LogDebug("Обнаружен экран 'Остаться в системе?'."); var noBtn = page.Locator("#idBtn_Back"); if (await noBtn.IsVisibleAsync()) + { + logger.LogDebug("Нажимаем кнопку 'Нет'."); await noBtn.ClickAsync(); + } else + { + logger.LogDebug("Кнопка 'Нет' не найдена, нажимаем 'Да'/'Далее'."); 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.WaitForLoadStateAsync(LoadState.NetworkIdle); var currentHost = new Uri(page.Url).Host; if (Regex.IsMatch(currentHost, "login\\.(microsoftonline|live)\\.com", RegexOptions.IgnoreCase)) + { + logger.LogError("Авторизация не завершена: остались на странице Microsoft Login. Текущий URL: {Url}", page.Url); throw new Exception("Авторизация не завершена: остались на странице Microsoft Login"); + } + + logger.LogInformation("Успешный вход в Microsoft. Текущий URL: {Url}", page.Url); } } diff --git a/src/Program.cs b/src/Program.cs index 62ee756..c596b31 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -8,6 +8,8 @@ if (string.IsNullOrWhiteSpace(builder.Configuration["MODEUS_URL"])) Environment.Exit(1); } +Console.WriteLine($"Используется URL Modeus: {builder.Configuration["MODEUS_URL"]}"); + if (string.IsNullOrWhiteSpace(builder.Configuration["MS_USERNAME"]) || string.IsNullOrWhiteSpace(builder.Configuration["MS_PASSWORD"])) { Console.Error.WriteLine("Ошибка: не заданы учетные данные для MicrosoftAuth. Пожалуйста, укажите MS_USERNAME и MS_PASSWORD в файле конфигурации или переменных окружения."); diff --git a/src/Services/MicrosoftAuthService.cs b/src/Services/MicrosoftAuthService.cs index f4ea435..11d48fc 100644 --- a/src/Services/MicrosoftAuthService.cs +++ b/src/Services/MicrosoftAuthService.cs @@ -18,11 +18,15 @@ public class MicrosoftAuthService(ILogger logger, IConfigu public async Task GetJwtAsync(CancellationToken ct = default) { + logger.LogDebug("Запрошен JWT токен. HasFreshToken=" + HasFreshToken); await EnsureBrowsersAsync(); // Если кэш актуален — вернуть сразу if (HasFreshToken) + { + logger.LogInformation("Возвращаем закэшированный JWT токен, возраст={AgeSeconds} сек", (DateTime.UtcNow - _cachedAtUtc).TotalSeconds); return _cachedToken!; + } // Пытаемся единолично выполнить авторизацию if (!await FetchLock.WaitAsync(0, ct)) @@ -45,19 +49,29 @@ public class MicrosoftAuthService(ILogger logger, IConfigu try { 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("JSON.stringify(sessionStorage)"); + logger.LogDebug("Пробуем извлечь id_token из sessionStorage"); string? idToken = ExtractIdToken(sessionStorageJson); if (string.IsNullOrWhiteSpace(idToken)) + { + logger.LogError("Не удалось извлечь id_token из sessionStorage"); throw new Exception("Не удалось извлечь id_token из sessionStorage"); + } // Сохраняем в кэш _cachedToken = idToken; _cachedAtUtc = DateTime.UtcNow; + logger.LogInformation("Успешно получили и закэшировали id_token"); return idToken; } + catch (Exception ex) when (ex is not MicrosoftAuthInProgressException) + { + logger.LogError(ex, "Ошибка при получении JWT через Microsoft авторизацию"); + throw; + } finally { await context.CloseAsync(); diff --git a/src/appsettings.Development.json b/src/appsettings.Development.json index d838ee7..e88d3dc 100644 --- a/src/appsettings.Development.json +++ b/src/appsettings.Development.json @@ -1,7 +1,7 @@ { "Logging": { "LogLevel": { - "Default": "Information", + "Default": "Debug", "Microsoft.AspNetCore": "Warning" } },