Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ csharp_style_prefer_top_level_statements = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent

# CA1848: Use the LoggerMessage delegates
dotnet_diagnostic.CA1848.severity = suggestion
# Test files - allow underscore-separated test method names (CA1707)
[{EssentialCSharp.Web.Tests,EssentialCSharp.Chat.Tests}/**]
dotnet_diagnostic.CA1707.severity = none
7 changes: 5 additions & 2 deletions EssentialCSharp.Chat.Shared/Services/AISearchService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace EssentialCSharp.Chat.Common.Services;

public class AISearchService(
public partial class AISearchService(
VectorStore vectorStore,
EmbeddingService embeddingService,
ILogger<AISearchService> logger)
Expand Down Expand Up @@ -49,10 +49,13 @@ public async Task<IReadOnlyList<VectorSearchResult<BookContentChunk>>> ExecuteVe
// needed (clearing would evict all healthy connections, hurting concurrent users).
// The retry opens a fresh physical connection, which calls UsePasswordProvider
// and gets a new token from DefaultAzureCredential.
logger.LogWarning(ex, "Entra ID token expired on pooled connection (SqlState 28000); retrying once.");
LogEntraIdTokenExpired(logger, ex);
}
}

throw new UnreachableException("Retry loop exited without returning or throwing.");
}

[LoggerMessage(Level = LogLevel.Warning, Message = "Entra ID token expired on pooled connection (SqlState 28000); retrying once.")]
private static partial void LogEntraIdTokenExpired(ILogger<AISearchService> logger, Exception exception);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public async Task<List<FileChunkingResult>> ProcessMarkdownFilesAsync(
// Validate input parameters
if (!directory.Exists)
{
logger.LogError("Error: Directory {DirectoryName} does not exist.", directory.FullName);
LogDirectoryDoesNotExist(logger, directory.FullName);
throw new InvalidOperationException($"Error: Directory '{directory.FullName}' does not exist.");
}

Expand Down Expand Up @@ -177,4 +177,7 @@ public FileChunkingResult ProcessSingleMarkdownFile(

[GeneratedRegex(@"^(#{1,6}) +(.+)$")]
private static partial Regex HeadingRegex();

[LoggerMessage(Level = LogLevel.Error, Message = "Directory {directoryName} does not exist.")]
private static partial void LogDirectoryDoesNotExist(ILogger<MarkdownChunkingService> logger, string directoryName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
namespace EssentialCSharp.Web.Areas.Identity.Pages.Account;

[AllowAnonymous]
public class ExternalLoginModel(
public partial class ExternalLoginModel(
SignInManager<EssentialCSharpWebUser> signInManager,
UserManager<EssentialCSharpWebUser> userManager,
IUserStore<EssentialCSharpWebUser> userStore,
Expand Down Expand Up @@ -78,7 +78,7 @@ public async Task<IActionResult> OnGetCallbackAsync(string? returnUrl = null, st
Microsoft.AspNetCore.Identity.SignInResult result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false, bypassTwoFactor: true);
if (result.Succeeded)
{
logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity?.Name, info.LoginProvider);
LogUserLoggedInWithProvider(logger, info.Principal.Identity?.Name, info.LoginProvider);
// Ensure referral ID is set for the user
var user = await userManager.FindByLoginAsync(info.LoginProvider, info.ProviderKey);
if (user != null)
Expand Down Expand Up @@ -140,7 +140,7 @@ public async Task<IActionResult> OnPostConfirmationAsync(string? returnUrl = nul
result = await userManager.AddLoginAsync(user, info);
if (result.Succeeded)
{
logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider);
LogUserCreatedWithProvider(logger, info.LoginProvider);
return await SendConfirmationEmail(returnUrl, info, user);
}
}
Expand Down Expand Up @@ -214,4 +214,10 @@ private EssentialCSharpWebUser CreateUser()
$"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml", innerException);
}
}

[LoggerMessage(Level = LogLevel.Information, Message = "{Name} logged in with {LoginProvider} provider.")]
private static partial void LogUserLoggedInWithProvider(ILogger<ExternalLoginModel> logger, string? name, string loginProvider);

[LoggerMessage(Level = LogLevel.Information, Message = "User created an account using {Name} provider.")]
private static partial void LogUserCreatedWithProvider(ILogger<ExternalLoginModel> logger, string name);
}
12 changes: 9 additions & 3 deletions EssentialCSharp.Web/Areas/Identity/Pages/Account/Login.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account;

public class LoginModel(SignInManager<EssentialCSharpWebUser> signInManager, UserManager<EssentialCSharpWebUser> userManager, ILogger<LoginModel> logger, IReferralService referralService, ICaptchaService captchaService, IOptions<CaptchaOptions> optionsAccessor) : PageModel
public partial class LoginModel(SignInManager<EssentialCSharpWebUser> signInManager, UserManager<EssentialCSharpWebUser> userManager, ILogger<LoginModel> logger, IReferralService referralService, ICaptchaService captchaService, IOptions<CaptchaOptions> optionsAccessor) : PageModel
{
private InputModel? _Input;
[BindProperty]
Expand Down Expand Up @@ -102,7 +102,7 @@ public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
}
if (result.Succeeded)
{
logger.LogInformation("User logged in.");
LogUserLoggedIn(logger);
return LocalRedirect(returnUrl);
}
if (result.RequiresTwoFactor)
Expand All @@ -111,7 +111,7 @@ public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
}
if (result.IsLockedOut)
{
logger.LogWarning("User account locked out.");
LogUserAccountLockedOut(logger);
return RedirectToPage("./Lockout");
}
else
Expand All @@ -124,4 +124,10 @@ public async Task<IActionResult> OnPostAsync(string? returnUrl = null)
// If we got this far, something failed, redisplay form
return Page();
}

[LoggerMessage(Level = LogLevel.Information, Message = "User logged in.")]
private static partial void LogUserLoggedIn(ILogger<LoginModel> logger);

[LoggerMessage(Level = LogLevel.Warning, Message = "User account locked out.")]
private static partial void LogUserAccountLockedOut(ILogger<LoginModel> logger);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account;

public class LoginWith2faModel(
public partial class LoginWith2faModel(
SignInManager<EssentialCSharpWebUser> signInManager,
UserManager<EssentialCSharpWebUser> userManager,
ILogger<LoginWith2faModel> logger) : PageModel
Expand Down Expand Up @@ -68,19 +68,28 @@ public async Task<IActionResult> OnPostAsync(bool rememberMe, string? returnUrl

if (result.Succeeded)
{
logger.LogInformation("User with ID '{UserId}' logged in with 2fa.", user.Id);
LogUserLoggedInWith2fa(logger, user.Id);
return LocalRedirect(returnUrl);
}
else if (result.IsLockedOut)
{
logger.LogWarning("User with ID '{UserId}' account locked out.", user.Id);
LogUserAccountLockedOut2fa(logger, user.Id);
return RedirectToPage("./Lockout");
}
else
{
logger.LogWarning("Invalid authenticator code entered for user with ID '{UserId}'.", user.Id);
LogInvalidAuthenticatorCode(logger, user.Id);
ModelState.AddModelError(string.Empty, "Invalid authenticator code.");
return Page();
}
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' logged in with 2fa.")]
private static partial void LogUserLoggedInWith2fa(ILogger<LoginWith2faModel> logger, string userId);

[LoggerMessage(Level = LogLevel.Warning, Message = "User with ID '{UserId}' account locked out.")]
private static partial void LogUserAccountLockedOut2fa(ILogger<LoginWith2faModel> logger, string userId);

[LoggerMessage(Level = LogLevel.Warning, Message = "Invalid authenticator code entered for user with ID '{UserId}'.")]
private static partial void LogInvalidAuthenticatorCode(ILogger<LoginWith2faModel> logger, string userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace EssentialCSharp.Web.Areas.Identity.Pages.Account;

public class LoginWithRecoveryCodeModel(
public partial class LoginWithRecoveryCodeModel(
SignInManager<EssentialCSharpWebUser> signInManager,
UserManager<EssentialCSharpWebUser> userManager,
ILogger<LoginWithRecoveryCodeModel> logger) : PageModel
Expand Down Expand Up @@ -59,19 +59,28 @@ public async Task<IActionResult> OnPostAsync(string? returnUrl = null)

if (result.Succeeded)
{
logger.LogInformation("User with ID '{UserId}' logged in with a recovery code.", user.Id);
LogUserLoggedInWithRecoveryCode(logger, user.Id);
return LocalRedirect(returnUrl ?? Url.Content("~/"));
}
if (result.IsLockedOut)
{
logger.LogWarning("User account locked out.");
LogUserAccountLockedOutRecovery(logger);
return RedirectToPage("./Lockout");
}
else
{
logger.LogWarning("Invalid recovery code entered for user with ID '{UserId}' ", user.Id);
LogInvalidRecoveryCode(logger, user.Id);
ModelState.AddModelError(string.Empty, "Invalid recovery code entered.");
return Page();
}
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' logged in with a recovery code.")]
private static partial void LogUserLoggedInWithRecoveryCode(ILogger<LoginWithRecoveryCodeModel> logger, string userId);

[LoggerMessage(Level = LogLevel.Warning, Message = "User account locked out.")]
private static partial void LogUserAccountLockedOutRecovery(ILogger<LoginWithRecoveryCodeModel> logger);

[LoggerMessage(Level = LogLevel.Warning, Message = "Invalid recovery code entered for user with ID '{UserId}' ")]
private static partial void LogInvalidRecoveryCode(ILogger<LoginWithRecoveryCodeModel> logger, string userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account;

public class LogoutModel(SignInManager<EssentialCSharpWebUser> signInManager, ILogger<LogoutModel> logger) : PageModel
public partial class LogoutModel(SignInManager<EssentialCSharpWebUser> signInManager, ILogger<LogoutModel> logger) : PageModel
{
public async Task<IActionResult> OnPost(string? returnUrl = null)
{
await signInManager.SignOutAsync();
logger.LogInformation("User logged out.");
LogUserLoggedOut(logger);
// This needs to be a redirect so that the browser performs a new
// request and the identity for the user gets updated.
return returnUrl is not null ? LocalRedirect(returnUrl) : RedirectToPage();
}

[LoggerMessage(Level = LogLevel.Information, Message = "User logged out.")]
private static partial void LogUserLoggedOut(ILogger<LogoutModel> logger);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class ChangePasswordModel(
public partial class ChangePasswordModel(
UserManager<EssentialCSharpWebUser> userManager,
SignInManager<EssentialCSharpWebUser> signInManager,
ILogger<ChangePasswordModel> logger) : PageModel
Expand Down Expand Up @@ -106,9 +106,12 @@ public async Task<IActionResult> OnPostAsync()
}

await signInManager.RefreshSignInAsync(user);
logger.LogInformation("User changed their password successfully.");
LogUserChangedPassword(logger);
StatusMessage = "Your password has been changed.";

return RedirectToPage();
}

[LoggerMessage(Level = LogLevel.Information, Message = "User changed their password successfully.")]
private static partial void LogUserChangedPassword(ILogger<ChangePasswordModel> logger);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class DeletePersonalDataModel(
public partial class DeletePersonalDataModel(
UserManager<EssentialCSharpWebUser> userManager,
SignInManager<EssentialCSharpWebUser> signInManager,
ILogger<DeletePersonalDataModel> logger) : PageModel
Expand Down Expand Up @@ -79,8 +79,11 @@ public async Task<IActionResult> OnPostAsync()

await signInManager.SignOutAsync();

logger.LogInformation("User with ID '{UserId}' deleted themselves.", userId);
LogUserDeletedThemselves(logger, userId);

return Redirect("~/");
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' deleted themselves.")]
private static partial void LogUserDeletedThemselves(ILogger<DeletePersonalDataModel> logger, string userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class Disable2faModel(
public partial class Disable2faModel(
UserManager<EssentialCSharpWebUser> userManager,
ILogger<Disable2faModel> logger) : PageModel
{
Expand Down Expand Up @@ -42,8 +42,11 @@ public async Task<IActionResult> OnPostAsync()
throw new InvalidOperationException($"Unexpected error occurred disabling 2FA.");
}

logger.LogInformation("User with ID '{UserId}' has disabled 2fa.", userManager.GetUserId(User));
LogUserDisabled2fa(logger, userManager.GetUserId(User));
StatusMessage = "2fa has been disabled. You can reenable 2fa when you setup an authenticator app";
return RedirectToPage("./TwoFactorAuthentication");
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' has disabled 2fa.")]
private static partial void LogUserDisabled2fa(ILogger<Disable2faModel> logger, string? userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class DownloadPersonalDataModel(
public partial class DownloadPersonalDataModel(
UserManager<EssentialCSharpWebUser> userManager,
ILogger<DownloadPersonalDataModel> logger) : PageModel
{
Expand All @@ -23,7 +23,7 @@ public async Task<IActionResult> OnPostAsync()
return NotFound($"Unable to load user with ID '{userManager.GetUserId(User)}'.");
}

logger.LogInformation("User with ID '{UserId}' asked for their personal data.", userManager.GetUserId(User));
LogUserAskedForPersonalData(logger, userManager.GetUserId(User));

// Only include personal data for download
var personalData = new Dictionary<string, string>();
Expand All @@ -48,4 +48,7 @@ public async Task<IActionResult> OnPostAsync()
Response.Headers.Append("Content-Disposition", "attachment; filename=PersonalData.json");
return new FileContentResult(JsonSerializer.SerializeToUtf8Bytes(personalData), "application/json");
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' asked for their personal data.")]
private static partial void LogUserAskedForPersonalData(ILogger<DownloadPersonalDataModel> logger, string? userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class EnableAuthenticatorModel(
public partial class EnableAuthenticatorModel(
UserManager<EssentialCSharpWebUser> userManager,
ILogger<EnableAuthenticatorModel> logger,
UrlEncoder urlEncoder) : PageModel
Expand Down Expand Up @@ -90,7 +90,7 @@ public async Task<IActionResult> OnPostAsync()

await userManager.SetTwoFactorEnabledAsync(user, true);
string userId = await userManager.GetUserIdAsync(user);
logger.LogInformation("User with ID '{UserId}' has enabled 2FA with an authenticator app.", userId);
LogUserEnabled2fa(logger, userId);

StatusMessage = "Your authenticator app has been verified.";

Expand Down Expand Up @@ -159,4 +159,7 @@ private string GenerateQrCodeUri(string email, string unformattedKey)
urlEncoder.Encode(email),
unformattedKey);
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' has enabled 2FA with an authenticator app.")]
private static partial void LogUserEnabled2fa(ILogger<EnableAuthenticatorModel> logger, string userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class GenerateRecoveryCodesModel(
public partial class GenerateRecoveryCodesModel(
UserManager<EssentialCSharpWebUser> userManager,
ILogger<GenerateRecoveryCodesModel> logger) : PageModel
{
Expand Down Expand Up @@ -50,8 +50,11 @@ public async Task<IActionResult> OnPostAsync()
IEnumerable<string>? recoveryCodes = await userManager.GenerateNewTwoFactorRecoveryCodesAsync(user, 10);
RecoveryCodes = recoveryCodes?.ToArray();

logger.LogInformation("User with ID '{UserId}' has generated new 2FA recovery codes.", userId);
LogUserGeneratedRecoveryCodes(logger, userId);
StatusMessage = "You have generated new recovery codes.";
return RedirectToPage("./ShowRecoveryCodes");
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' has generated new 2FA recovery codes.")]
private static partial void LogUserGeneratedRecoveryCodes(ILogger<GenerateRecoveryCodesModel> logger, string userId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

namespace EssentialCSharp.Web.Areas.Identity.Pages.Account.Manage;

public class ResetAuthenticatorModel(
public partial class ResetAuthenticatorModel(
UserManager<EssentialCSharpWebUser> userManager,
SignInManager<EssentialCSharpWebUser> signInManager,
ILogger<ResetAuthenticatorModel> logger) : PageModel
Expand Down Expand Up @@ -35,11 +35,14 @@ public async Task<IActionResult> OnPostAsync()
await userManager.SetTwoFactorEnabledAsync(user, false);
await userManager.ResetAuthenticatorKeyAsync(user);
_ = await userManager.GetUserIdAsync(user);
logger.LogInformation("User with ID '{UserId}' has reset their authentication app key.", user.Id);
LogUserResetAuthenticatorKey(logger, user.Id);

await signInManager.RefreshSignInAsync(user);
StatusMessage = "Your authenticator app key has been reset, you will need to configure your authenticator app using the new key.";

return RedirectToPage("./EnableAuthenticator");
}

[LoggerMessage(Level = LogLevel.Information, Message = "User with ID '{UserId}' has reset their authentication app key.")]
private static partial void LogUserResetAuthenticatorKey(ILogger<ResetAuthenticatorModel> logger, string userId);
}
Loading
Loading