mirror of
https://github.com/streetwriters/notesnook-sync-server.git
synced 2026-02-12 11:12:44 +00:00
global: migrate to using ILogger
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user