global: migrate to using ILogger

This commit is contained in:
Abdullah Atta
2025-10-14 09:28:41 +05:00
parent 0cc3365e44
commit be432dfd24
14 changed files with 113 additions and 161 deletions

View File

@@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MongoDB.Driver;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
@@ -74,24 +75,24 @@ namespace Notesnook.API.Accessors
IMongoCollection<SyncItem> tags,
Repository<UserSettings> usersSettings, Repository<Monograph> monographs,
Repository<InboxApiKey> inboxApiKey, Repository<InboxSyncItem> inboxItems)
Repository<InboxApiKey> inboxApiKey, Repository<InboxSyncItem> inboxItems, ILogger<SyncItemsRepository> logger)
{
UsersSettings = usersSettings;
Monographs = monographs;
InboxApiKey = inboxApiKey;
InboxItems = inboxItems;
Notebooks = new SyncItemsRepository(dbContext, notebooks);
Notes = new SyncItemsRepository(dbContext, notes);
Contents = new SyncItemsRepository(dbContext, content);
Settings = new SyncItemsRepository(dbContext, settings);
LegacySettings = new SyncItemsRepository(dbContext, legacySettings);
Attachments = new SyncItemsRepository(dbContext, attachments);
Shortcuts = new SyncItemsRepository(dbContext, shortcuts);
Reminders = new SyncItemsRepository(dbContext, reminders);
Relations = new SyncItemsRepository(dbContext, relations);
Colors = new SyncItemsRepository(dbContext, colors);
Vaults = new SyncItemsRepository(dbContext, vaults);
Tags = new SyncItemsRepository(dbContext, tags);
Notebooks = new SyncItemsRepository(dbContext, notebooks, logger);
Notes = new SyncItemsRepository(dbContext, notes, logger);
Contents = new SyncItemsRepository(dbContext, content, logger);
Settings = new SyncItemsRepository(dbContext, settings, logger);
LegacySettings = new SyncItemsRepository(dbContext, legacySettings, logger);
Attachments = new SyncItemsRepository(dbContext, attachments, logger);
Shortcuts = new SyncItemsRepository(dbContext, shortcuts, logger);
Reminders = new SyncItemsRepository(dbContext, reminders, logger);
Relations = new SyncItemsRepository(dbContext, relations, logger);
Colors = new SyncItemsRepository(dbContext, colors, logger);
Vaults = new SyncItemsRepository(dbContext, vaults, logger);
Tags = new SyncItemsRepository(dbContext, tags, logger);
}
}
}

View File

@@ -23,6 +23,7 @@ using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using Notesnook.API.Authorization;
using Notesnook.API.Models;
@@ -35,21 +36,12 @@ namespace Notesnook.API.Controllers
{
[ApiController]
[Route("inbox")]
public class InboxController : ControllerBase
{
private readonly Repository<InboxApiKey> InboxApiKey;
private readonly Repository<UserSettings> UserSetting;
private Repository<InboxSyncItem> InboxItems;
public InboxController(
public class InboxController(
Repository<InboxApiKey> inboxApiKeysRepository,
Repository<UserSettings> userSettingsRepository,
Repository<InboxSyncItem> inboxItemsRepository)
{
InboxApiKey = inboxApiKeysRepository;
UserSetting = userSettingsRepository;
InboxItems = inboxItemsRepository;
}
Repository<InboxSyncItem> inboxItemsRepository,
ILogger<InboxController> logger) : ControllerBase
{
[HttpGet("api-keys")]
[Authorize(Policy = "Notesnook")]
@@ -58,12 +50,12 @@ namespace Notesnook.API.Controllers
var userId = User.FindFirstValue("sub");
try
{
var apiKeys = await InboxApiKey.FindAsync(t => t.UserId == userId);
var apiKeys = await inboxApiKeysRepository.FindAsync(t => t.UserId == userId);
return Ok(apiKeys);
}
catch (Exception ex)
{
await Slogger<InboxController>.Error(nameof(GetApiKeysAsync), "Couldn't get inbox api keys.", userId, ex.ToString());
logger.LogError(ex, "Couldn't get inbox api keys for user {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}
@@ -84,7 +76,7 @@ namespace Notesnook.API.Controllers
return BadRequest(new { error = "Valid expiry date is required." });
}
var count = await InboxApiKey.CountAsync(t => t.UserId == userId);
var count = await inboxApiKeysRepository.CountAsync(t => t.UserId == userId);
if (count >= 10)
{
return BadRequest(new { error = "Maximum of 10 inbox api keys allowed." });
@@ -98,12 +90,12 @@ namespace Notesnook.API.Controllers
ExpiryDate = request.ExpiryDate,
LastUsedAt = 0
};
await InboxApiKey.InsertAsync(inboxApiKey);
await inboxApiKeysRepository.InsertAsync(inboxApiKey);
return Ok(inboxApiKey);
}
catch (Exception ex)
{
await Slogger<InboxController>.Error(nameof(CreateApiKeyAsync), "Couldn't create inbox api key.", userId, ex.ToString());
logger.LogError(ex, "Couldn't create inbox api key for {UserId}.", userId);
return BadRequest(new { error = ex.Message });
}
}
@@ -120,12 +112,12 @@ namespace Notesnook.API.Controllers
return BadRequest(new { error = "Api key is required." });
}
await InboxApiKey.DeleteAsync(t => t.UserId == userId && t.Key == apiKey);
await inboxApiKeysRepository.DeleteAsync(t => t.UserId == userId && t.Key == apiKey);
return Ok(new { message = "Api key deleted successfully." });
}
catch (Exception ex)
{
await Slogger<InboxController>.Error(nameof(DeleteApiKeyAsync), "Couldn't delete inbox api key.", userId, ex.ToString());
logger.LogError(ex, "Couldn't delete inbox api key for user {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}
@@ -137,7 +129,7 @@ namespace Notesnook.API.Controllers
var userId = User.FindFirstValue("sub");
try
{
var userSetting = await UserSetting.FindOneAsync(u => u.UserId == userId);
var userSetting = await userSettingsRepository.FindOneAsync(u => u.UserId == userId);
if (string.IsNullOrWhiteSpace(userSetting?.InboxKeys?.Public))
{
return BadRequest(new { error = "Inbox public key is not configured." });
@@ -146,7 +138,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<InboxController>.Error(nameof(GetPublicKeyAsync), "Couldn't get user's inbox's public key.", userId, ex.ToString());
logger.LogError(ex, "Couldn't get user's inbox's public key for user {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}
@@ -189,7 +181,7 @@ namespace Notesnook.API.Controllers
request.UserId = userId;
request.ItemId = ObjectId.GenerateNewId().ToString();
await InboxItems.InsertAsync(request);
await inboxItemsRepository.InsertAsync(request);
new SyncDeviceService(new SyncDevice(userId, string.Empty))
.AddIdsToAllDevices([$"{request.ItemId}:inboxItems"]);
await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
@@ -206,7 +198,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<InboxController>.Error(nameof(CreateInboxItemAsync), "Couldn't create inbox item.", userId, ex.ToString());
logger.LogError(ex, "Couldn't create inbox item for user {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}

View File

@@ -27,6 +27,7 @@ using AngleSharp;
using AngleSharp.Dom;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Authorization;
@@ -43,7 +44,7 @@ namespace Notesnook.API.Controllers
[ApiController]
[Route("monographs")]
[Authorize("Sync")]
public class MonographsController(Repository<Monograph> monographs, IURLAnalyzer analyzer) : ControllerBase
public class MonographsController(Repository<Monograph> monographs, IURLAnalyzer analyzer, ILogger<MonographsController> logger) : ControllerBase
{
const string SVG_PIXEL = "<svg xmlns='http://www.w3.org/2000/svg' width='1' height='1'><circle r='9'/></svg>";
private const int MAX_DOC_SIZE = 15 * 1024 * 1024;
@@ -133,7 +134,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception e)
{
await Slogger<MonographsController>.Error(nameof(PublishAsync), e.ToString());
logger.LogError(e, "Failed to publish monograph");
return BadRequest();
}
}
@@ -184,7 +185,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception e)
{
await Slogger<MonographsController>.Error(nameof(UpdateAsync), e.ToString());
logger.LogError(e, "Failed to update monograph");
return BadRequest();
}
}
@@ -328,7 +329,7 @@ namespace Notesnook.API.Controllers
if (string.IsNullOrEmpty(href)) continue;
if (!await analyzer.IsURLSafeAsync(href))
{
await Slogger<MonographsController>.Info("CleanupContentAsync", "Malicious URL detected: " + href);
logger.LogInformation("Malicious URL detected: {Url}", href);
element.RemoveAttribute("href");
}
}
@@ -355,7 +356,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<MonographsController>.Error("CleanupContentAsync", ex.ToString());
logger.LogError(ex, "Failed to cleanup monograph content");
return content;
}
}

View File

@@ -24,6 +24,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Notesnook.API.Interfaces;
using Notesnook.API.Models.Responses;
using Notesnook.API.Services;
@@ -36,10 +37,10 @@ namespace Notesnook.API.Controllers
[ApiController]
[Authorize]
[Route("devices")]
public class SyncDeviceController : ControllerBase
public class SyncDeviceController(ILogger<SyncDeviceController> logger) : ControllerBase
{
[HttpPost]
public async Task<IActionResult> RegisterDevice([FromQuery] string deviceId)
public IActionResult RegisterDevice([FromQuery] string deviceId)
{
try
{
@@ -49,14 +50,14 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(UnregisterDevice), "Couldn't register device.", ex.ToString());
logger.LogError(ex, "Failed to register device: {DeviceId}", deviceId);
return BadRequest(new { error = ex.Message });
}
}
[HttpDelete]
public async Task<IActionResult> UnregisterDevice([FromQuery] string deviceId)
public IActionResult UnregisterDevice([FromQuery] string deviceId)
{
try
{
@@ -66,7 +67,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(UnregisterDevice), "Couldn't unregister device.", ex.ToString());
logger.LogError(ex, "Failed to unregister device: {DeviceId}", deviceId);
return BadRequest(new { error = ex.Message });
}
}

View File

@@ -23,6 +23,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http.Timeouts;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
@@ -33,7 +34,7 @@ namespace Notesnook.API.Controllers
[ApiController]
[Authorize]
[Route("users")]
public class UsersController(IUserService UserService) : ControllerBase
public class UsersController(IUserService UserService, ILogger<UsersController> logger) : ControllerBase
{
[HttpPost]
[AllowAnonymous]
@@ -46,7 +47,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(Signup), "Couldn't sign up.", ex.ToString());
logger.LogError(ex, "Failed to sign up user");
return BadRequest(new { error = ex.Message });
}
}
@@ -63,7 +64,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(GetUser), "Couldn't get user for id.", userId, ex.ToString());
logger.LogError(ex, "Failed to get user with id: {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}
@@ -79,7 +80,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(GetUser), "Couldn't update user with id.", userId, ex.ToString());
logger.LogError(ex, "Failed to update user with id: {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}
@@ -107,7 +108,7 @@ namespace Notesnook.API.Controllers
}
catch (Exception ex)
{
await Slogger<UsersController>.Error(nameof(GetUser), "Couldn't delete user with id.", userId, ex.ToString());
logger.LogError(ex, "Failed to delete user with id: {UserId}", userId);
return BadRequest(new { error = ex.Message });
}
}

View File

@@ -26,6 +26,7 @@ using System.Threading;
using System.Threading.Tasks;
using IdentityModel;
using Microsoft.VisualBasic;
using Microsoft.Extensions.Logging;
using MongoDB.Bson;
using MongoDB.Driver;
using Notesnook.API.Hubs;
@@ -41,9 +42,11 @@ namespace Notesnook.API.Repositories
public class SyncItemsRepository : Repository<SyncItem>
{
private readonly string collectionName;
public SyncItemsRepository(IDbContext dbContext, IMongoCollection<SyncItem> collection) : base(dbContext, collection)
private readonly ILogger<SyncItemsRepository> logger;
public SyncItemsRepository(IDbContext dbContext, IMongoCollection<SyncItem> collection, ILogger<SyncItemsRepository> logger) : base(dbContext, collection)
{
this.collectionName = collection.CollectionNamespace.CollectionName;
this.logger = logger;
}
private readonly List<string> ALGORITHMS = [Algorithms.Default, Algorithms.XSAL_X25519_7];
@@ -110,7 +113,8 @@ namespace Notesnook.API.Repositories
// Handle case where the cipher is corrupted.
if (!IsBase64String(item.Cipher))
{
Slogger<SyncHub>.Error("Upsert", "Corrupted", item.ItemId, item.Length.ToString(), item.Cipher);
logger.LogError("Corrupted item {ItemId} in collection {CollectionName}. Length: {Length}, Cipher: {Cipher}",
item.ItemId, this.collectionName, item.Length, item.Cipher);
throw new Exception($"Corrupted item \"{item.ItemId}\" in collection \"{this.collectionName}\". Please report this error to support@streetwriters.co.");
}
@@ -147,7 +151,8 @@ namespace Notesnook.API.Repositories
// Handle case where the cipher is corrupted.
if (!IsBase64String(item.Cipher))
{
Slogger<SyncHub>.Error("Upsert", "Corrupted", item.ItemId, item.Length.ToString(), item.Cipher);
logger.LogError("Corrupted item {ItemId} in collection {CollectionName}. Length: {Length}, Cipher: {Cipher}",
item.ItemId, this.collectionName, item.Length, item.Cipher);
throw new Exception($"Corrupted item \"{item.ItemId}\" in collection \"{this.collectionName}\". Please report this error to support@streetwriters.co.");
}

View File

@@ -23,6 +23,7 @@ using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Notesnook.API.Helpers;
using Notesnook.API.Interfaces;
using Notesnook.API.Models;
@@ -37,33 +38,23 @@ using Streetwriters.Data.Interfaces;
namespace Notesnook.API.Services
{
public class UserService : IUserService
public class UserService(IHttpContextAccessor accessor,
ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor,
IUnitOfWork unitOfWork, IS3Service s3Service, ILogger<UserService> logger) : IUserService
{
private static readonly System.Security.Cryptography.RandomNumberGenerator Rng = System.Security.Cryptography.RandomNumberGenerator.Create();
private readonly HttpClient httpClient;
private IHttpContextAccessor HttpContextAccessor { get; }
private ISyncItemsRepositoryAccessor Repositories { get; }
private IS3Service S3Service { get; set; }
private readonly IUnitOfWork unit;
public UserService(IHttpContextAccessor accessor,
ISyncItemsRepositoryAccessor syncItemsRepositoryAccessor,
IUnitOfWork unitOfWork, IS3Service s3Service)
{
httpClient = new HttpClient();
Repositories = syncItemsRepositoryAccessor;
HttpContextAccessor = accessor;
unit = unitOfWork;
S3Service = s3Service;
}
private readonly HttpClient httpClient = new();
private IHttpContextAccessor HttpContextAccessor { get; } = accessor;
private ISyncItemsRepositoryAccessor Repositories { get; } = syncItemsRepositoryAccessor;
private IS3Service S3Service { get; set; } = s3Service;
private readonly IUnitOfWork unit = unitOfWork;
public async Task CreateUserAsync()
{
SignupResponse response = await httpClient.ForwardAsync<SignupResponse>(this.HttpContextAccessor, $"{Servers.IdentityServer}/signup", HttpMethod.Post);
if (!response.Success || (response.Errors != null && response.Errors.Length > 0))
{
await Slogger<UserService>.Error(nameof(CreateUserAsync), "Couldn't sign up.", JsonSerializer.Serialize(response));
logger.LogError("Failed to sign up user: {Response}", JsonSerializer.Serialize(response));
if (response.Errors != null && response.Errors.Length > 0)
throw new Exception(string.Join(" ", response.Errors));
else throw new Exception("Could not create a new account. Error code: " + response.StatusCode);
@@ -91,7 +82,7 @@ namespace Notesnook.API.Services
});
}
await Slogger<UserService>.Info(nameof(CreateUserAsync), "New user created.", JsonSerializer.Serialize(response));
logger.LogInformation("New user created: {Response}", JsonSerializer.Serialize(response));
}
public async Task<UserResponse> GetUserAsync(string userId)
@@ -210,7 +201,7 @@ namespace Notesnook.API.Services
Repositories.InboxApiKey.DeleteMany((t) => t.UserId == userId);
var result = await unit.Commit();
await Slogger<UserService>.Info(nameof(DeleteUserAsync), "User data deleted", userId, result.ToString());
logger.LogInformation("User data deleted for user {UserId}: {Result}", userId, result);
if (!result) throw new Exception("Could not delete user data.");
if (!Constants.IS_SELF_HOSTED)
@@ -227,7 +218,7 @@ namespace Notesnook.API.Services
public async Task DeleteUserAsync(string userId, string jti, string password)
{
await Slogger<UserService>.Info(nameof(DeleteUserAsync), "Deleting user account", userId);
logger.LogInformation("Deleting user account: {UserId}", userId);
var userService = await WampServers.IdentityServer.GetServiceAsync<IUserAccountService>(IdentityServerTopics.UserAccountServiceTopic);
await userService.DeleteUserAsync(Clients.Notesnook.Id, userId, password);

View File

@@ -1,52 +0,0 @@
/*
This file is part of the Notesnook Sync Server project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the Affero GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Affero GNU General Public License for more details.
You should have received a copy of the Affero GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
using System.Threading.Tasks;
namespace Streetwriters.Common
{
public class Slogger<T>
{
public static Task Info(string scope, params string[] messages)
{
return Write(Format("info", scope, messages));
}
public static Task Error(string scope, params string[] messages)
{
return Write(Format("error", scope, messages));
}
private static string Format(string level, string scope, params string[] messages)
{
var date = DateTime.UtcNow.ToString("MM-dd-yyyy HH:mm:ss");
var messageText = string.Join(" ", messages);
return $"[{date}] | {level} | <{scope}> {messageText}";
}
private static Task Write(string line)
{
var logDirectory = Path.GetFullPath("./logs");
if (!Directory.Exists(logDirectory))
Directory.CreateDirectory(logDirectory);
var path = Path.Join(logDirectory, typeof(T).FullName + "-" + DateTime.UtcNow.ToString("MM-dd-yyyy") + ".log");
return File.AppendAllLinesAsync(path, new string[1] { line });
}
}
}

View File

@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using MailKit.Net.Smtp;
using MimeKit;
using MimeKit.Cryptography;
@@ -16,6 +17,12 @@ namespace Streetwriters.Common.Services
public class EmailSender : IEmailSender, IAsyncDisposable
{
private readonly SmtpClient mailClient = new();
private readonly ILogger<EmailSender> logger;
public EmailSender(ILogger<EmailSender> logger)
{
this.logger = logger;
}
public async Task SendEmailAsync(
string email,
@@ -67,7 +74,7 @@ namespace Streetwriters.Common.Services
await mailClient.SendAsync(message);
}
private static async Task<MimeEntity> GetEmailBodyAsync(
private async Task<MimeEntity> GetEmailBodyAsync(
EmailTemplate template,
IClient client,
MailboxAddress sender,
@@ -120,7 +127,7 @@ namespace Streetwriters.Common.Services
}
catch (Exception ex)
{
await Slogger<EmailSender>.Error("GetEmailBodyAsync", ex.ToString());
logger.LogError(ex, "Failed to get email body");
return builder.ToMessageBody();
}
}

View File

@@ -55,30 +55,14 @@ namespace Streetwriters.Common
public async Task PublishMessageAsync<V>(string topic, V message)
{
try
{
IWampRealmProxy channel = await GetChannelAsync(topic);
WampHelper.PublishMessage(channel, topic, message);
}
catch (Exception ex)
{
await Slogger<WampServer<T>>.Error(nameof(PublishMessageAsync), ex.ToString());
throw ex;
}
IWampRealmProxy channel = await GetChannelAsync(topic);
WampHelper.PublishMessage(channel, topic, message);
}
public async Task PublishMessagesAsync<V>(string topic, IEnumerable<V> messages)
{
try
{
IWampRealmProxy channel = await GetChannelAsync(topic);
WampHelper.PublishMessages(channel, topic, messages);
}
catch (Exception ex)
{
await Slogger<WampServer<T>>.Error(nameof(PublishMessagesAsync), ex.ToString());
throw ex;
}
IWampRealmProxy channel = await GetChannelAsync(topic);
WampHelper.PublishMessages(channel, topic, messages);
}
}

View File

@@ -32,6 +32,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
@@ -54,13 +55,15 @@ namespace Streetwriters.Identity.Controllers
private IPersistedGrantStore PersistedGrantStore { get; set; }
private ITokenGenerationService TokenGenerationService { get; set; }
private IUserAccountService UserAccountService { get; set; }
private readonly ILogger<AccountController> logger;
public AccountController(UserManager<User> _userManager, ITemplatedEmailSender _emailSender,
SignInManager<User> _signInManager, RoleManager<MongoRole> _roleManager, IPersistedGrantStore store,
ITokenGenerationService tokenGenerationService, IMFAService _mfaService, IUserAccountService userAccountService) : base(_userManager, _emailSender, _signInManager, _roleManager, _mfaService)
ITokenGenerationService tokenGenerationService, IMFAService _mfaService, IUserAccountService userAccountService, ILogger<AccountController> logger) : base(_userManager, _emailSender, _signInManager, _roleManager, _mfaService)
{
PersistedGrantStore = store;
TokenGenerationService = tokenGenerationService;
UserAccountService = userAccountService;
this.logger = logger;
}
[HttpGet("confirm")]
@@ -161,7 +164,7 @@ namespace Streetwriters.Identity.Controllers
#if (DEBUG || STAGING)
return Ok(callbackUrl);
#else
await Slogger<AccountController>.Info("ResetUserPassword", user.Email, callbackUrl);
logger.LogInformation("Password reset email sent to: {Email}, callback URL: {CallbackUrl}", user.Email, callbackUrl);
await EmailSender.SendPasswordResetEmailAsync(user.Email, callbackUrl, client);
return Ok();
#endif

View File

@@ -26,6 +26,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Models;
@@ -40,9 +41,16 @@ namespace Streetwriters.Identity.Controllers
[Route("signup")]
public class SignupController : IdentityControllerBase
{
private readonly ILogger<SignupController> logger;
private readonly EmailAddressValidator emailValidator;
public SignupController(UserManager<User> _userManager, ITemplatedEmailSender _emailSender,
SignInManager<User> _signInManager, RoleManager<MongoRole> _roleManager, IMFAService _mfaService) : base(_userManager, _emailSender, _signInManager, _roleManager, _mfaService)
{ }
SignInManager<User> _signInManager, RoleManager<MongoRole> _roleManager, IMFAService _mfaService,
ILogger<SignupController> logger, EmailAddressValidator emailValidator) : base(_userManager, _emailSender, _signInManager, _roleManager, _mfaService)
{
this.logger = logger;
this.emailValidator = emailValidator;
}
private async Task AddClientRoleAsync(string clientId)
{
@@ -68,7 +76,7 @@ namespace Streetwriters.Identity.Controllers
form.Email = form.Email.ToLowerInvariant();
form.Username = form.Username?.ToLowerInvariant();
if (!await EmailAddressValidator.IsEmailAddressValidAsync(form.Email)) return BadRequest(new string[] { "Invalid email address." });
if (!await emailValidator.IsEmailAddressValidAsync(form.Email)) return BadRequest(new string[] { "Invalid email address." });
var result = await UserManager.CreateAsync(new User
{
@@ -128,7 +136,7 @@ namespace Streetwriters.Identity.Controllers
}
catch (System.Exception ex)
{
await Slogger<SignupController>.Error("Signup", ex.ToString());
logger.LogError(ex, "Failed to create user account for email: {Email}", form.Email);
return BadRequest("Failed to create an account.");
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
using System.Linq;
@@ -11,8 +12,14 @@ namespace Streetwriters.Identity.Services
{
private static DateTimeOffset LAST_FETCH_TIME = DateTimeOffset.MinValue;
private static HashSet<string> BLACKLISTED_DOMAINS = new();
private readonly ILogger<EmailAddressValidator> logger;
public static async Task<bool> IsEmailAddressValidAsync(string email)
public EmailAddressValidator(ILogger<EmailAddressValidator> logger)
{
this.logger = logger;
}
public async Task<bool> IsEmailAddressValidAsync(string email)
{
var domain = email.ToLowerInvariant().Split("@")[1];
try
@@ -30,7 +37,7 @@ namespace Streetwriters.Identity.Services
}
catch (Exception ex)
{
await Slogger<EmailAddressValidator>.Error("IsEmailAddressValidAsync", ex.ToString());
logger.LogError(ex, "Failed to validate email address: {Email}", email);
return BLACKLISTED_DOMAINS.Count > 0 ? !BLACKLISTED_DOMAINS.Contains(domain) : true;
}
}

View File

@@ -24,6 +24,7 @@ using System.Text;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Interfaces;
@@ -42,11 +43,13 @@ namespace Streetwriters.Identity.Services
private UserManager<User> UserManager { get; set; }
private ITemplatedEmailSender EmailSender { get; set; }
private ISMSSender SMSSender { get; set; }
public MFAService(UserManager<User> _userManager, ITemplatedEmailSender emailSender, ISMSSender smsSender)
private readonly ILogger<MFAService> logger;
public MFAService(UserManager<User> _userManager, ITemplatedEmailSender emailSender, ISMSSender smsSender, ILogger<MFAService> logger)
{
UserManager = _userManager;
EmailSender = emailSender;
SMSSender = smsSender;
this.logger = logger;
}
public async Task EnableMFAAsync(User user, string primaryMethod)
@@ -186,7 +189,7 @@ namespace Streetwriters.Identity.Services
case "sms":
await UserManager.SetPhoneNumberAsync(user, form.PhoneNumber);
var id = await SMSSender.SendOTPAsync(form.PhoneNumber, client);
await Slogger<MFAService>.Info("SendOTPAsync", user.Id.ToString(), id);
logger.LogInformation("SMS OTP sent for user: {UserId}, SMS ID: {SmsId}", user.Id, id);
await this.ReplaceClaimAsync(user, MFAService.SMS_ID_CLAIM, id);
break;