Saving
This commit is contained in:
12
PrivaPub/Services/ActivityPubClient.cs
Normal file
12
PrivaPub/Services/ActivityPubClient.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace PrivaPub.Services
|
||||
{
|
||||
public class ActivityPubClient : HttpClient
|
||||
{
|
||||
//readonly HttpClient client;
|
||||
|
||||
//public ActivityPubClient(HttpClient client)
|
||||
//{
|
||||
// this.client = client;
|
||||
//}
|
||||
}
|
||||
}
|
36
PrivaPub/Services/AppConfigurationService.cs
Normal file
36
PrivaPub/Services/AppConfigurationService.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Models;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
namespace PrivaPub.Services
|
||||
{
|
||||
public class AppConfigurationService
|
||||
{
|
||||
public AppConfiguration AppConfiguration;
|
||||
readonly DbEntities _dbEntities;
|
||||
AppConfiguration _appConfigurationFromFile;
|
||||
public AppConfigurationService(DbEntities dbEntities, IOptionsMonitor<AppConfiguration> appConfiguration)
|
||||
{
|
||||
_dbEntities = dbEntities;
|
||||
_appConfigurationFromFile = appConfiguration.CurrentValue;
|
||||
}
|
||||
|
||||
public async Task Init()
|
||||
{
|
||||
var dbAppConfiguration = await _dbEntities.AppConfiguration.ExecuteFirstAsync();
|
||||
if (dbAppConfiguration != default)
|
||||
{
|
||||
if (AppConfiguration == default || AppConfiguration.LastUpdateDate != dbAppConfiguration.LastUpdateDate)
|
||||
AppConfiguration = dbAppConfiguration;
|
||||
return;
|
||||
}
|
||||
|
||||
dbAppConfiguration = _appConfigurationFromFile;
|
||||
await dbAppConfiguration.SaveAsync();
|
||||
AppConfiguration = dbAppConfiguration;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.User.Avatar;
|
||||
using PrivaPub.Models;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.Resources;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PrivaPub.Services.ClientToServer.Private
|
||||
{
|
||||
public interface IPrivateAvatarUsersService
|
||||
{
|
||||
Task<WebResult> UpdateAvatar(UpdateAvatarForm form);
|
||||
Task<WebResult> InsertAvatar(InsertAvatarForm form);
|
||||
}
|
||||
|
||||
public class PrivateAvatarUsersService : IPrivateAvatarUsersService
|
||||
{
|
||||
readonly AppConfigurationService _appConfiguration;
|
||||
readonly DbEntities _dbEntities;
|
||||
readonly IStringLocalizer<GenericRes> _localizer;
|
||||
readonly ILogger<PrivateAvatarUsersService> _logger;
|
||||
|
||||
public PrivateAvatarUsersService(AppConfigurationService appConfiguration,
|
||||
DbEntities dbEntities,
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
ILogger<PrivateAvatarUsersService> logger)
|
||||
{
|
||||
_appConfiguration = appConfiguration;
|
||||
_dbEntities = dbEntities;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<WebResult> InsertAvatar(InsertAvatarForm form)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
if (await _dbEntities.Avatars.Match(a => a.UserName == form.UserName).ExecuteAnyAsync())
|
||||
return result.Invalidate(_localizer["The username '{0}' is already take.", form.UserName]);
|
||||
|
||||
if (!await IsValidRootUser(form.RootId))
|
||||
return result.Invalidate(_localizer["You can't do this action because of your account status."]);
|
||||
|
||||
var newAvatar = new Avatar
|
||||
{
|
||||
Name = form.Name,
|
||||
UserName = form.UserName,
|
||||
Biography = form.Biography,
|
||||
Fields = form.Fields,
|
||||
PersonalNote = form.PersonalNote,
|
||||
};
|
||||
if (!form.Settings.IsDefault)
|
||||
newAvatar.Settings = new()
|
||||
{
|
||||
IsDefault = false,
|
||||
DarkThemeIndexColour = form.Settings.DarkThemeIndexColour,
|
||||
IconsThemeIndexColour = form.Settings.IconsThemeIndexColour,
|
||||
LanguageCode = form.Settings.LanguageCode,
|
||||
LightThemeIndexColour = form.Settings.LightThemeIndexColour,
|
||||
PreferSystemTheming = form.Settings.PreferSystemTheming,
|
||||
ThemeIsDarkGray = form.Settings.ThemeIsDarkGray,
|
||||
ThemeIsDarkMode = form.Settings.ThemeIsDarkMode,
|
||||
ThemeIsLightGray = form.Settings.ThemeIsLightGray
|
||||
};
|
||||
|
||||
if (_appConfiguration.AppConfiguration == default)
|
||||
await _appConfiguration.Init();
|
||||
|
||||
newAvatar.Url = _appConfiguration.AppConfiguration.BackendBaseAddress + $"/peasants/{form.UserName}";
|
||||
newAvatar.Domain = _appConfiguration.AppConfiguration.BackendBaseAddress;
|
||||
newAvatar.InboxURL = _appConfiguration.AppConfiguration.BackendBaseAddress + $"/peasants/{form.UserName}/mouth";
|
||||
newAvatar.OutboxURL = _appConfiguration.AppConfiguration.BackendBaseAddress + $"/peasants/{form.UserName}/anus";
|
||||
var rsa = RSA.Create();
|
||||
newAvatar.PrivateKey = rsa.ExportRSAPrivateKeyPem();
|
||||
newAvatar.PublicKey = rsa.ExportRSAPublicKeyPem();
|
||||
|
||||
await newAvatar.SaveAsync();
|
||||
result.Data = newAvatar;
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PrivateAvatarUsersService)}.{nameof(InsertAvatar)}");
|
||||
return result.Invalidate(_localizer["Error: {0}", ex.ToString()], exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> UpdateAvatar(UpdateAvatarForm form)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PrivateAvatarUsersService)}.{nameof(UpdateAvatar)}");
|
||||
return result.Invalidate(_localizer["Error: {0}", ex.ToString()], exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
async ValueTask<bool> IsValidRootUser(string rootUserId) =>
|
||||
await _dbEntities.RootUsers.MatchID(rootUserId)
|
||||
.Match(ru => !ru.DeletedAt.HasValue)
|
||||
.ExecuteAnyAsync();
|
||||
}
|
||||
}
|
37
PrivaPub/Services/ClientToServer/Public/DataService.cs
Normal file
37
PrivaPub/Services/ClientToServer/Public/DataService.cs
Normal file
@ -0,0 +1,37 @@
|
||||
using PrivaPub.Models.Data;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
namespace PrivaPub.Services.ClientToServer.Public
|
||||
{
|
||||
public class DataService : IDataService
|
||||
{
|
||||
readonly DbEntities DbEntities;
|
||||
readonly AppConfigurationService AppConfigurationService;
|
||||
|
||||
public DataService(DbEntities dbCollections, AppConfigurationService appConfigurationService)
|
||||
{
|
||||
DbEntities = dbCollections;
|
||||
AppConfigurationService = appConfigurationService;
|
||||
}
|
||||
|
||||
public string GetCurrentVersion() =>
|
||||
AppConfigurationService.AppConfiguration.Version;
|
||||
|
||||
public async Task<IEnumerable<Language>> GetLanguages(CancellationToken cancellationToken)
|
||||
{
|
||||
return (await DbEntities.Languages
|
||||
.Match(l => AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(l.International2Code))
|
||||
.ExecuteAsync(cancellationToken)).OrderBy(l => l.NativeName).ToArray();
|
||||
}
|
||||
|
||||
public async Task<bool> IsValidLanguageId(string languageId)
|
||||
{
|
||||
var anyLanguageWithId = await DbEntities.Languages.Match(l => l.ID == languageId).ExecuteAnyAsync();
|
||||
if (!anyLanguageWithId)
|
||||
return false;
|
||||
var language2Code = (await DbEntities.Languages
|
||||
.Match(l => l.ID == languageId).ExecuteFirstAsync())?.International2Code;
|
||||
return AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(language2Code);
|
||||
}
|
||||
}
|
||||
}
|
13
PrivaPub/Services/ClientToServer/Public/IDataService.cs
Normal file
13
PrivaPub/Services/ClientToServer/Public/IDataService.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using PrivaPub.Models.Data;
|
||||
|
||||
namespace PrivaPub.Services.ClientToServer.Public
|
||||
{
|
||||
public interface IDataService
|
||||
{
|
||||
string GetCurrentVersion();
|
||||
|
||||
Task<IEnumerable<Language>> GetLanguages(CancellationToken cancellationToken);
|
||||
|
||||
Task<bool> IsValidLanguageId(string languageId);
|
||||
}
|
||||
}
|
@ -0,0 +1,174 @@
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.Resources;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
namespace PrivaPub.Services.ClientToServer.Public
|
||||
{
|
||||
public interface IPublicAvatarUsersService
|
||||
{
|
||||
Task<WebResult> GetLocalAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token);
|
||||
Task<IEnumerable<Avatar>> GetLocalAvatars(UserPolicyType userPolicyType, CancellationToken token);
|
||||
|
||||
Task<WebResult> GetForeignAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token);
|
||||
Task<IEnumerable<ForeignAvatar>> GetForeignAvatars(UserPolicyType userPolicyType, CancellationToken token);
|
||||
|
||||
Task<IEnumerable<Avatar>> GetRootAvatars(string rootUserId, CancellationToken token);
|
||||
}
|
||||
|
||||
public class PublicAvatarUsersService : IPublicAvatarUsersService
|
||||
{
|
||||
readonly DbEntities _dbEntities;
|
||||
readonly IStringLocalizer<GenericRes> _localizer;
|
||||
readonly ILogger<PublicAvatarUsersService> _logger;
|
||||
|
||||
public PublicAvatarUsersService(DbEntities dbEntities,
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
ILogger<PublicAvatarUsersService> logger)
|
||||
{
|
||||
_dbEntities = dbEntities;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<WebResult> GetLocalAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var query = _dbEntities.Avatars;
|
||||
switch (userPolicyType)
|
||||
{
|
||||
case UserPolicyType.IsUser:
|
||||
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
|
||||
break;
|
||||
case UserPolicyType.IsModerator:
|
||||
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
|
||||
break;
|
||||
case UserPolicyType.IsAdmin:
|
||||
query.Match(a => a.UserName == actor);
|
||||
break;
|
||||
}
|
||||
var avatar = await query.ExecuteFirstAsync(token);
|
||||
if (avatar == default)
|
||||
return result.Invalidate(_localizer["User '{0}' not found.", actor], StatusCodes.Status404NotFound);
|
||||
result.Data = avatar;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetLocalAvatar)}");
|
||||
return result.Invalidate(_localizer["Error: {0}", ex.ToString()], exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Avatar>> GetLocalAvatars(UserPolicyType userPolicyType, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _dbEntities.Avatars;
|
||||
switch (userPolicyType)
|
||||
{
|
||||
case UserPolicyType.IsUser:
|
||||
query.Match(a => !a.DeletionAt.HasValue);
|
||||
break;
|
||||
case UserPolicyType.IsModerator:
|
||||
query.Match(a => !a.DeletionAt.HasValue);
|
||||
break;
|
||||
case UserPolicyType.IsAdmin:
|
||||
break;
|
||||
}
|
||||
var avatars = await query.ExecuteAsync(token);
|
||||
return avatars;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetLocalAvatars)}");
|
||||
return Enumerable.Empty<Avatar>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> GetForeignAvatar(UserPolicyType userPolicyType, string actor, CancellationToken token)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var query = _dbEntities.ForeignAvatars;
|
||||
switch (userPolicyType)
|
||||
{
|
||||
case UserPolicyType.IsUser:
|
||||
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
|
||||
break;
|
||||
case UserPolicyType.IsModerator:
|
||||
query.Match(a => !a.DeletionAt.HasValue && a.UserName == actor);
|
||||
break;
|
||||
case UserPolicyType.IsAdmin:
|
||||
query.Match(a => a.UserName == actor);
|
||||
break;
|
||||
}
|
||||
var avatar = await query.ExecuteFirstAsync(token);
|
||||
if (avatar == default)
|
||||
return result.Invalidate(_localizer["User '{0}' not found.", actor], StatusCodes.Status404NotFound);
|
||||
result.Data = avatar;
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetForeignAvatar)}");
|
||||
return result.Invalidate(_localizer["Error: {0}", ex.ToString()], exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ForeignAvatar>> GetForeignAvatars(UserPolicyType userPolicyType, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var query = _dbEntities.ForeignAvatars;
|
||||
switch (userPolicyType)
|
||||
{
|
||||
case UserPolicyType.IsUser:
|
||||
query.Match(a => !a.DeletionAt.HasValue);
|
||||
break;
|
||||
case UserPolicyType.IsModerator:
|
||||
query.Match(a => !a.DeletionAt.HasValue);
|
||||
break;
|
||||
case UserPolicyType.IsAdmin:
|
||||
break;
|
||||
}
|
||||
var foreignAvatars = await query.ExecuteAsync(token);
|
||||
return foreignAvatars;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetForeignAvatars)}");
|
||||
return Enumerable.Empty<ForeignAvatar>();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Avatar>> GetRootAvatars(string rootUserId, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
var rootUser = _dbEntities.RootUsers.MatchID(rootUserId).ExecuteFirstAsync(token);
|
||||
if (rootUser == default)
|
||||
return Enumerable.Empty<Avatar>();
|
||||
var rootToAvatars = await _dbEntities.RootToAvatars.Match(ra => ra.RootId == rootUserId).ExecuteAsync(token);
|
||||
if (rootToAvatars.Count == 0)
|
||||
return Enumerable.Empty<Avatar>();
|
||||
var avatarIds = rootToAvatars.Select(ra => ra.AvatarId).ToArray();
|
||||
var avatars = await _dbEntities.Avatars
|
||||
.Match(a => avatarIds.Contains(a.ID))
|
||||
.ProjectExcluding(a => a.Settings)
|
||||
.ExecuteAsync(token);
|
||||
return avatars;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PublicAvatarUsersService)}.{nameof(GetRootAvatars)}");
|
||||
return Enumerable.Empty<Avatar>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
PrivaPub/Services/IRootUsersService.cs
Normal file
34
PrivaPub/Services/IRootUsersService.cs
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma warning disable 8625
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.User;
|
||||
|
||||
namespace PrivaPub.Services
|
||||
{
|
||||
public interface IRootUsersService
|
||||
{
|
||||
Task<WebResult> LoginAsync(LoginForm loginForm, string invitationCode = default, bool isPasswordRequired = false);
|
||||
|
||||
Task<WebResult> SignUpAsync(LoginForm signUpForm, string invitationCode = default, bool isPasswordRequired = false);
|
||||
|
||||
Task<WebResult> BanUserAsync(UsersIds usersIds);
|
||||
|
||||
Task<WebResult> UnbanUserAsync(UsersIds usersIds);
|
||||
|
||||
Task<WebResult> RemoveUserAsync(UsersIds usersIds);
|
||||
|
||||
Task<WebResult> UpdateUserAsync(UserForm userEmailForm, string userId);
|
||||
|
||||
Task<WebResult> UpdateUserPasswordAsync(UserPasswordForm userPasswordForm, string userId);
|
||||
|
||||
Task<WebResult> GetUserSettingsAsync(string userId, LoginForm loginForm = default);
|
||||
|
||||
Task<WebResult> UpdateUserSettingsAsync(ViewAvatarServer userSettings, string userId);
|
||||
|
||||
Task<WebResult> SetupAndSendRecoveryEmail(PasswordRecoveryForm passwordRecoveryForm, string host);
|
||||
|
||||
Task<WebResult> IsValidRecoveryCode(string recoveryCode);
|
||||
|
||||
Task<WebResult> ChangePassword(NewPasswordForm newPasswordForm);
|
||||
}
|
||||
}
|
49
PrivaPub/Services/JwtEvents.cs
Normal file
49
PrivaPub/Services/JwtEvents.cs
Normal file
@ -0,0 +1,49 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.Resources;
|
||||
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PrivaPub.Services
|
||||
{
|
||||
public class JwtEvents : JwtBearerEvents
|
||||
{
|
||||
ILogger<JwtEvents> _logger { get; set; }
|
||||
const string contentType = "application/json";
|
||||
|
||||
public override async Task AuthenticationFailed(AuthenticationFailedContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var localizer = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizer<GenericRes>>();
|
||||
var webResult = new WebResult().Invalidate(localizer["Unauthorized: {0}", context.Exception], StatusCodes.Status401Unauthorized);
|
||||
context.Response.ContentType = contentType;
|
||||
await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtEvents>>();
|
||||
_logger.LogError(ex, "Error at AuthenticationFailed()");
|
||||
}
|
||||
}
|
||||
|
||||
public override async Task Forbidden(ForbiddenContext context)
|
||||
{
|
||||
try
|
||||
{
|
||||
var localizer = context.HttpContext.RequestServices.GetRequiredService<IStringLocalizer<GenericRes>>();
|
||||
var webResult = new WebResult().Invalidate(localizer["Forbidden: {0}", context.Result.None ? "N/A" : context.Result.Failure?.Message ?? "N/A"], StatusCodes.Status403Forbidden);
|
||||
context.Response.ContentType = contentType;
|
||||
await context.Response.BodyWriter.WriteAsync(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(webResult)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtEvents>>();
|
||||
_logger.LogError(ex, "Error at AuthenticationFailed()");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
42
PrivaPub/Services/RequestLocalizationOptionsService.cs
Normal file
42
PrivaPub/Services/RequestLocalizationOptionsService.cs
Normal file
@ -0,0 +1,42 @@
|
||||
using Microsoft.AspNetCore.Localization;
|
||||
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
namespace PrivaPub.Services
|
||||
{
|
||||
public class RequestLocalizationOptionsService
|
||||
{
|
||||
RequestLocalizationOptions RequestLocalizationOptions { get; set; }
|
||||
readonly AppConfigurationService AppConfigurationService;
|
||||
|
||||
public RequestLocalizationOptionsService(AppConfigurationService appConfigurationService)
|
||||
{
|
||||
AppConfigurationService = appConfigurationService;
|
||||
}
|
||||
|
||||
public async Task<RequestLocalizationOptions> Get()
|
||||
{
|
||||
if (RequestLocalizationOptions != default) return RequestLocalizationOptions;
|
||||
if (AppConfigurationService.AppConfiguration == default)
|
||||
await AppConfigurationService.Init();
|
||||
|
||||
RequestLocalizationOptions = new RequestLocalizationOptions();
|
||||
var cultures = AppConfigurationService
|
||||
.AppConfiguration
|
||||
.SupportedLanguages
|
||||
.Select(sl => new CultureInfo(sl))
|
||||
.ToArray();
|
||||
RequestLocalizationOptions.DefaultRequestCulture = new(cultures.First(), cultures.First());
|
||||
RequestLocalizationOptions.SupportedCultures = cultures;
|
||||
RequestLocalizationOptions.SupportedUICultures = cultures;
|
||||
RequestLocalizationOptions.RequestCultureProviders = new IRequestCultureProvider[] {
|
||||
new AcceptLanguageHeaderRequestCultureProvider { Options = RequestLocalizationOptions }
|
||||
};
|
||||
RequestLocalizationOptions.ApplyCurrentCultureToResponseHeaders = true;
|
||||
|
||||
return RequestLocalizationOptions;
|
||||
}
|
||||
}
|
||||
}
|
577
PrivaPub/Services/RootUsersService.cs
Normal file
577
PrivaPub/Services/RootUsersService.cs
Normal file
@ -0,0 +1,577 @@
|
||||
using MailKit.Net.Smtp;
|
||||
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using MimeKit;
|
||||
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PasswordGenerator;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.User;
|
||||
using PrivaPub.Models;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.Resources;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Globalization;
|
||||
|
||||
#pragma warning disable 8603
|
||||
#pragma warning disable 8625
|
||||
|
||||
namespace PrivaPub.Services
|
||||
{
|
||||
public class RootUsersService : IRootUsersService
|
||||
{
|
||||
readonly DbEntities DbEntities;
|
||||
readonly IPasswordHasher PasswordHasher;
|
||||
readonly IStringLocalizer Localizer;
|
||||
readonly ILogger<RootUsersService> Logger;
|
||||
readonly AppConfigurationService AppConfigurationService;
|
||||
readonly AuthTokenManager AuthTokenManager;
|
||||
|
||||
public RootUsersService(
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
ILogger<RootUsersService> logger,
|
||||
IPasswordHasher passwordHasher,
|
||||
DbEntities dbEntities,
|
||||
AppConfigurationService appConfigurationService,
|
||||
AuthTokenManager authTokenManager)
|
||||
{
|
||||
DbEntities = dbEntities;
|
||||
AuthTokenManager = authTokenManager;
|
||||
PasswordHasher = passwordHasher;
|
||||
Localizer = localizer;
|
||||
Logger = logger;
|
||||
AppConfigurationService = appConfigurationService;
|
||||
}
|
||||
|
||||
public async Task<WebResult> SignUpAsync(LoginForm signUpForm, string invitationCode = default,
|
||||
bool isPasswordRequired = false)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
signUpForm.UserName = signUpForm.UserName.ToLower();
|
||||
if (await DbEntities.RootUsers.Match(u => u.UserName == signUpForm.UserName).ExecuteAnyAsync())
|
||||
return result.Invalidate(Localizer["Username '{0}' already taken.", signUpForm.UserName]);
|
||||
|
||||
var signUpPasswordHashed = PasswordHasher.Hash(signUpForm.Password);
|
||||
var newUser = new RootUser
|
||||
{
|
||||
UserName = signUpForm.UserName,
|
||||
HashedPassword = signUpPasswordHashed
|
||||
};
|
||||
if (signUpForm.UserName == "admin")
|
||||
{
|
||||
newUser.Policies.Clear();
|
||||
newUser.Policies.Add(Policies.IsAdmin);
|
||||
newUser.Policies.Add(Policies.IsUser);
|
||||
newUser.Policies.Add(Policies.IsModerator);
|
||||
}
|
||||
|
||||
await newUser.SaveAsync();
|
||||
var cultureLanguage = CultureInfo.CurrentCulture.TwoLetterISOLanguageName;
|
||||
var language = await DbEntities.Languages.Match(l => l.International2Code == cultureLanguage).ExecuteFirstAsync();
|
||||
var newRootUserSettings = new RootUserSettings
|
||||
{
|
||||
RootUserId = newUser.ID,
|
||||
LanguageCode = language?.International2Code ?? "en",
|
||||
LightThemeIndexColour = signUpForm.LightThemeIndexColour,
|
||||
DarkThemeIndexColour = signUpForm.DarkThemeIndexColour,
|
||||
IconsThemeIndexColour = signUpForm.IconsThemeIndexColour,
|
||||
ThemeIsDarkGray = signUpForm.ThemeIsDarkGray,
|
||||
ThemeIsLightGray = signUpForm.ThemeIsLightGray,
|
||||
PreferSystemTheming = signUpForm.PreferSystemTheming,
|
||||
ThemeIsDarkMode = signUpForm.ThemeIsDarkMode
|
||||
};
|
||||
await newRootUserSettings.SaveAsync();
|
||||
|
||||
//if (!string.IsNullOrEmpty(invitationCode))
|
||||
//{
|
||||
// if (isPasswordRequired)
|
||||
// result = await DiscussionService.InviteUserToDiscussion(new PwDiscussionPreviewForm
|
||||
// {
|
||||
// InvitationCode = invitationCode,
|
||||
// Password = signUpForm.InvitationPassword
|
||||
// }, newUser.ID);
|
||||
// else
|
||||
// result = await DiscussionService.InviteUserToDiscussion(new NoPwDiscussionPreviewForm
|
||||
// {
|
||||
// InvitationCode = invitationCode,
|
||||
// }, newUser.ID);
|
||||
// if (!result.IsValid)
|
||||
// return result;
|
||||
//}
|
||||
|
||||
result.Data = (newUser, new ViewAvatarServer
|
||||
{
|
||||
LanguageCode = newRootUserSettings.LanguageCode,
|
||||
LightThemeIndexColour = newRootUserSettings.LightThemeIndexColour,
|
||||
ThemeIsDarkMode = newRootUserSettings.ThemeIsDarkMode,
|
||||
IconsThemeIndexColour = newRootUserSettings.IconsThemeIndexColour,
|
||||
ThemeIsDarkGray = newRootUserSettings.ThemeIsDarkGray,
|
||||
ThemeIsLightGray = newRootUserSettings.ThemeIsLightGray,
|
||||
DarkThemeIndexColour = newRootUserSettings.DarkThemeIndexColour,
|
||||
PreferSystemTheming = newRootUserSettings.PreferSystemTheming
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(SignUpAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> LoginAsync(LoginForm loginForm, string invitationCode = default,
|
||||
bool isPasswordRequired = false)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
loginForm.UserName = loginForm.UserName.ToLower();
|
||||
if (!await DbEntities.RootUsers.Match(u => u.UserName == loginForm.UserName && u.DeletionDate == null)
|
||||
.ExecuteAnyAsync())
|
||||
return result.Invalidate(Localizer["Username '{0}' not found.", loginForm.UserName]);
|
||||
|
||||
var user = await DbEntities.RootUsers.Match(u => u.UserName == loginForm.UserName).ExecuteFirstAsync();
|
||||
if (user.IsBanned)
|
||||
return result.Invalidate(Localizer["User '{0}' banned.", user.UserName]);
|
||||
|
||||
var (verified, needsUpgrade) = PasswordHasher.Check(user.HashedPassword, loginForm.Password);
|
||||
|
||||
if (!verified)
|
||||
return result.Invalidate(Localizer["Wrong password."]);
|
||||
|
||||
if (needsUpgrade)
|
||||
result.ErrorMessage = Localizer["Needs upgrade!"];
|
||||
|
||||
var userSettingsResult = await GetUserSettingsAsync(user.ID, loginForm);
|
||||
var userSettings = (ViewAvatarServer)userSettingsResult.Data;
|
||||
|
||||
//if (!string.IsNullOrEmpty(invitationCode))
|
||||
//{
|
||||
// if (isPasswordRequired)
|
||||
// result = await DiscussionService.InviteUserToDiscussion(new PwDiscussionPreviewForm
|
||||
// {
|
||||
// InvitationCode = invitationCode,
|
||||
// Password = loginForm.InvitationPassword
|
||||
// }, user.ID);
|
||||
// else
|
||||
// result = await DiscussionService.InviteUserToDiscussion(new NoPwDiscussionPreviewForm
|
||||
// {
|
||||
// InvitationCode = invitationCode,
|
||||
// }, user.ID);
|
||||
// if (!result.IsValid)
|
||||
// return result;
|
||||
//}
|
||||
|
||||
result.Data = (user, userSettings);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(LoginAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> UpdateUserAsync(UserForm userForm, string userId)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var currentUser = await DbEntities.RootUsers.Match(u => u.ID == userId).ExecuteFirstAsync();
|
||||
if (!string.IsNullOrEmpty(userForm.Email) && currentUser.Email != userForm.Email)
|
||||
{
|
||||
var emailAlreadyUsed = await DbEntities.RootUsers.Match(u => u.Email == userForm.Email).ExecuteAnyAsync();
|
||||
if (emailAlreadyUsed)
|
||||
return result.Invalidate(Localizer["Email '{0}' already taken.", userForm.Email]);
|
||||
}
|
||||
|
||||
await DB.Update<RootUser>()
|
||||
.Match(u => u.ID == userId)
|
||||
.Modify(u => u.Email, userForm.Email)
|
||||
.ExecuteAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UpdateUserAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> UpdateUserSettingsAsync(ViewAvatarServer userSettings, string userId)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var isSupportedLanguage = AppConfigurationService.AppConfiguration.SupportedLanguages.Contains(userSettings.LanguageCode);
|
||||
if (!isSupportedLanguage)
|
||||
return result.Invalidate(Localizer["Language code '{0}' unsupported.", userSettings.LanguageCode]);
|
||||
|
||||
var languageCodeExists = await DbEntities.Languages
|
||||
.Match(l => l.International2Code == userSettings.LanguageCode).ExecuteAnyAsync();
|
||||
if (!languageCodeExists)
|
||||
return result.Invalidate(Localizer["Language code '{0}' doesn't exist.", userSettings.LanguageCode]);
|
||||
|
||||
var language = await DbEntities.Languages.Match(l => l.International2Code == userSettings.LanguageCode)
|
||||
.ExecuteFirstAsync();
|
||||
_ = await DB.Update<RootUserSettings>()
|
||||
.Match(u => u.RootUserId == userId)
|
||||
.Modify(us => us.LanguageCode, language.International2Code)
|
||||
.Modify(us => us.LightThemeIndexColour, userSettings.LightThemeIndexColour)
|
||||
.Modify(us => us.DarkThemeIndexColour, userSettings.DarkThemeIndexColour)
|
||||
.Modify(us => us.PreferSystemTheming, userSettings.PreferSystemTheming)
|
||||
.Modify(us => us.ThemeIsDarkMode, userSettings.ThemeIsDarkMode)
|
||||
.Modify(us => us.ThemeIsDarkGray, userSettings.ThemeIsDarkGray)
|
||||
.Modify(us => us.ThemeIsLightGray, userSettings.ThemeIsLightGray)
|
||||
.Modify(us => us.IconsThemeIndexColour, userSettings.IconsThemeIndexColour)
|
||||
.ExecuteAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UpdateUserSettingsAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> UpdateUserPasswordAsync(UserPasswordForm userPasswordForm, string userId)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var user = await DbEntities.RootUsers.Match(u => u.ID == userId && u.DeletionDate == null).ExecuteFirstAsync();
|
||||
|
||||
if (user == null)
|
||||
return result.Invalidate(Localizer["Username '{0}' not found.", userId]);
|
||||
|
||||
var (verified, needsUpgrade) = PasswordHasher.Check(user.HashedPassword, userPasswordForm.OldPassword);
|
||||
|
||||
if (!verified)
|
||||
return result.Invalidate(Localizer["Wrong password."]);
|
||||
|
||||
var newPasswordHashed = PasswordHasher.Hash(userPasswordForm.NewPassword);
|
||||
user.HashedPassword = newPasswordHashed;
|
||||
await user.SaveAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UpdateUserPasswordAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> RemoveUserAsync(UsersIds usersIds)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var users = await DbEntities.RootUsers.Match(u => usersIds.UserIdList.Contains(u.ID) && u.DeletionDate == default).ExecuteAsync();
|
||||
if (users == null || users.Count == 0)
|
||||
return result.Invalidate(Localizer["User already deleted."]);
|
||||
|
||||
foreach (var user in users)
|
||||
{
|
||||
user.Email = Localizer["Deleted user"];
|
||||
user.HashedPassword = null;
|
||||
user.Policies.Clear();
|
||||
user.IsBanned = false;
|
||||
user.IsEmailValidated = false;
|
||||
//user.TempSecret = null;
|
||||
user.UserName = Localizer["Deleted user"];
|
||||
user.DeletionDate = DateTime.UtcNow;
|
||||
|
||||
await user.SaveAsync();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(RemoveUserAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> BanUserAsync(UsersIds usersIds)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
await DB.Update<RootUser>()
|
||||
.Match(u => usersIds.UserIdList.Contains(u.ID))
|
||||
.Modify(u => u.IsBanned, true)
|
||||
.ExecuteAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(BanUserAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> UnbanUserAsync(UsersIds usersIds)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
await DB.Update<RootUser>()
|
||||
.Match(u => usersIds.UserIdList.Contains(u.ID))
|
||||
.Modify(u => u.IsBanned, false)
|
||||
.ExecuteAsync();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(UnbanUserAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> GetUserSettingsAsync(string userId, LoginForm loginForm = default)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var userSettings = await DbEntities.RootUsersSettings.Match(u => u.RootUserId == userId).ExecuteFirstAsync();
|
||||
if (loginForm != default && (loginForm.ThemeIsDarkMode != userSettings.ThemeIsDarkMode))
|
||||
{
|
||||
userSettings.ThemeIsDarkMode = loginForm.ThemeIsDarkMode;
|
||||
await userSettings.SaveAsync();
|
||||
}
|
||||
|
||||
result.Data = new ViewAvatarServer
|
||||
{
|
||||
LanguageCode = userSettings.LanguageCode,
|
||||
LightThemeIndexColour = userSettings.LightThemeIndexColour,
|
||||
DarkThemeIndexColour = userSettings.DarkThemeIndexColour,
|
||||
PreferSystemTheming = userSettings.PreferSystemTheming,
|
||||
ThemeIsDarkMode = userSettings.ThemeIsDarkMode,
|
||||
IconsThemeIndexColour = userSettings.IconsThemeIndexColour,
|
||||
ThemeIsDarkGray = userSettings.ThemeIsDarkGray,
|
||||
ThemeIsLightGray = userSettings.ThemeIsLightGray
|
||||
};
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(GetUserSettingsAsync)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> SetupAndSendRecoveryEmail(PasswordRecoveryForm passwordRecoveryForm, string host)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var usernameExists = false;
|
||||
if (passwordRecoveryForm.IsEmailDisabled)
|
||||
{
|
||||
if (!await DbEntities.RootUsers.Match(u => u.UserName == passwordRecoveryForm.UserName && u.DeletionDate == null)
|
||||
.ExecuteAnyAsync())
|
||||
return result.Invalidate(Localizer["Username '{0}' not found.", passwordRecoveryForm.UserName],
|
||||
StatusCodes.Status404NotFound);
|
||||
usernameExists = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!await DbEntities.RootUsers.Match(u => u.Email == passwordRecoveryForm.Email && u.DeletionDate == null)
|
||||
.ExecuteAnyAsync())
|
||||
return result.Invalidate(Localizer["Username '{0}' not found.", passwordRecoveryForm.UserName],
|
||||
StatusCodes.Status404NotFound);
|
||||
}
|
||||
|
||||
var user = default(RootUser);
|
||||
if (usernameExists)
|
||||
user = await DbEntities.RootUsers.Match(u => u.UserName == passwordRecoveryForm.UserName).ExecuteFirstAsync();
|
||||
else
|
||||
user = await DbEntities.RootUsers.Match(u => u.Email == passwordRecoveryForm.Email).ExecuteFirstAsync();
|
||||
|
||||
if (string.IsNullOrEmpty(user.Email))
|
||||
return result.Invalidate(Localizer["This User doesn't have an email, no way to recover."],
|
||||
StatusCodes.Status423Locked);
|
||||
|
||||
var emailRecovery = await DbEntities.EmailRecoveries
|
||||
.Match(er => er.RootUserId == user.ID).ExecuteFirstAsync();
|
||||
|
||||
if (emailRecovery is null)
|
||||
{
|
||||
emailRecovery = new()
|
||||
{
|
||||
RootUserId = user.ID
|
||||
};
|
||||
await emailRecovery.SaveAsync();
|
||||
}
|
||||
|
||||
var recoveryCodeGenerator = new Password(true, true, true, false, 127);
|
||||
emailRecovery.RecoveryCode = recoveryCodeGenerator.Next();
|
||||
await emailRecovery.SaveAsync();
|
||||
|
||||
using (var smtpClient = new SmtpClient())
|
||||
{
|
||||
try
|
||||
{
|
||||
await smtpClient.ConnectAsync(AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpServer, AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpPort,
|
||||
AppConfigurationService.AppConfiguration.EmailConfiguration.UseSSL);
|
||||
if (!smtpClient.IsConnected)
|
||||
{
|
||||
Logger.LogError($"Failed to connect to the SMTP server({AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpServer}).");
|
||||
return result.Invalidate(Localizer["Failed to send email."], (int)SmtpStatusCode.ServiceNotAvailable);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"Error at connection to the SMTP server({AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpServer}).");
|
||||
return result.Invalidate(Localizer["Failed to send email."], (int)SmtpStatusCode.ServiceNotAvailable, exception: ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await smtpClient.AuthenticateAsync(AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername, AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpPassword);
|
||||
if (!smtpClient.IsAuthenticated)
|
||||
{
|
||||
Logger.LogError($"Failed SMTP authentication of {AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername}.");
|
||||
return result.Invalidate(Localizer["Failed to send email."],
|
||||
(int)SmtpStatusCode.TemporaryAuthenticationFailure);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"Failed SMTP authentication of {AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername}.");
|
||||
return result.Invalidate(Localizer["Failed to send email."],
|
||||
(int)SmtpStatusCode.TemporaryAuthenticationFailure, exception: ex);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var toParsed = await smtpClient.VerifyAsync(user.Email);
|
||||
if (toParsed == null)
|
||||
return result.Invalidate($"Invalid email of {user.Email}.", (int)SmtpStatusCode.MailboxUnavailable);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
Logger.LogWarning(
|
||||
$"SMTP operation canceled exception at email verification of {user.Email}. Exception=[{ex.Message}]");
|
||||
}
|
||||
catch (SmtpCommandException ex)
|
||||
{
|
||||
Logger.LogWarning(
|
||||
$"SMTP command exception at email verification of {user.Email}. Exception=[{ex.Message}]");
|
||||
}
|
||||
catch (SmtpProtocolException ex)
|
||||
{
|
||||
Logger.LogWarning(
|
||||
$"SMTP protocol exception at email verification of {user.Email}. Exception=[{ex.Message}]");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning($"General exception at email verification of {user.Email}. Exception=[{ex.Message}]");
|
||||
}
|
||||
|
||||
var message = new MimeMessage();
|
||||
message.From.Add(new MailboxAddress(Localizer["Eugene - collAnon support"], AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername));
|
||||
message.To.Add(MailboxAddress.Parse(user.Email));
|
||||
message.Subject = Localizer["PrivaPub - Password recovery link"];
|
||||
message.Body = new TextPart("plain")
|
||||
{
|
||||
Text = string.Format(Localizer[@"Hey {0},
|
||||
|
||||
Eugene from collAnon, following is the password recovery link:
|
||||
{1}
|
||||
|
||||
-- Eugene"], user.UserName, $"{host}/password-recovery?rc={emailRecovery.RecoveryCode}")
|
||||
};
|
||||
try
|
||||
{
|
||||
await smtpClient.SendAsync(message);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"Error at email sending to {user.Email} from {AppConfigurationService.AppConfiguration.EmailConfiguration.SmtpUsername}.");
|
||||
return result.Invalidate(Localizer["Failed to send email."], (int)SmtpStatusCode.TransactionFailed, exception: ex);
|
||||
}
|
||||
|
||||
await smtpClient.DisconnectAsync(quit: true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(SetupAndSendRecoveryEmail)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> IsValidRecoveryCode(string recoveryCode)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var isValidRecoveryCode = await DbEntities.EmailRecoveries
|
||||
.Match(er => er.RecoveryCode == recoveryCode).ExecuteAnyAsync();
|
||||
|
||||
result.Data = isValidRecoveryCode;
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<WebResult> ChangePassword(NewPasswordForm newPasswordForm)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var isValidRecoveryCode = await DbEntities.EmailRecoveries
|
||||
.Match(er => er.RecoveryCode == newPasswordForm.RecoveryCode).ExecuteAnyAsync();
|
||||
if (!isValidRecoveryCode)
|
||||
return result.Invalidate(Localizer["Invalid recovery code."], StatusCodes.Status404NotFound);
|
||||
|
||||
var emailRecovery = await DbEntities.EmailRecoveries
|
||||
.Match(er => er.RecoveryCode == newPasswordForm.RecoveryCode)
|
||||
.Project(er => er.Include(nameof(EmailRecovery.RootUserId)))
|
||||
.ExecuteFirstAsync();
|
||||
|
||||
if (emailRecovery is null || emailRecovery.RootUserId is null)
|
||||
return result.Invalidate(Localizer["User not found."]);
|
||||
|
||||
var newHashedPassword = PasswordHasher.Hash(newPasswordForm.NewPassword);
|
||||
|
||||
_ = await DB.Update<RootUser>()
|
||||
.Match(u => u.ID == emailRecovery.RootUserId && u.DeletionDate == null)
|
||||
.Modify(u => u.HashedPassword, newHashedPassword)
|
||||
.ExecuteAsync();
|
||||
|
||||
_ = await DB.DeleteAsync<EmailRecovery>(er => er.RecoveryCode == newPasswordForm.RecoveryCode);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(RootUsersService)}.{nameof(ChangePassword)}()");
|
||||
return result.Invalidate(ex.Message, exception: ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user