Saving
This commit is contained in:
129
PrivaPub/Controllers/ClientToServer/AdminController.cs
Normal file
129
PrivaPub/Controllers/ClientToServer/AdminController.cs
Normal file
@ -0,0 +1,129 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.User;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Resources;
|
||||
using PrivaPub.Services;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PrivaPub.Controllers.ClientToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("clientapi/admin")]
|
||||
public class AdminController : ControllerBase
|
||||
{
|
||||
readonly IRootUsersService RootUsersService;
|
||||
readonly ILogger<RootUserController> Logger;
|
||||
readonly IStringLocalizer Localizer;
|
||||
|
||||
public AdminController(ILogger<RootUserController> logger,
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
IRootUsersService rootUsersService)
|
||||
{
|
||||
Logger = logger;
|
||||
Localizer = localizer;
|
||||
RootUsersService = rootUsersService;
|
||||
}
|
||||
|
||||
[HttpDelete, Route("/clientapi/admin/remove/users"), Authorize(Policy = Policies.IsAdmin)]
|
||||
public async Task<IActionResult> RemoveUsers([Required] UsersIds usersIds)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
usersIds.UserIdList.Remove(User.GetUserId());
|
||||
result = await RootUsersService.RemoveUserAsync(usersIds);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(RemoveUsers)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Authorize(Policy = Policies.IsAdmin), Route("/clientapi/admin/ban/users")]
|
||||
public async Task<IActionResult> BanUsers([Required] UsersIds usersIds)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(usersIds);
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
//var isUserResult = await UsersService.UserIsAdminAsync(User.GetUserId());
|
||||
//if (isUserResult.IsValid && !(bool)isUserResult.Data)
|
||||
//if (isUserResult is { IsValid: true } and not { Data: bool })
|
||||
// return Unauthorized();
|
||||
|
||||
usersIds.UserIdList.Remove(User.GetUserId());
|
||||
result = await RootUsersService.BanUserAsync(usersIds);
|
||||
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(BanUsers)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Authorize(Policy = Policies.IsAdmin), Route("/clientapi/admin/unban/users")]
|
||||
public async Task<IActionResult> UnbanUsers([Required] UsersIds usersIds)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(usersIds);
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
//var isUserResult = await UsersService.UserIsAdminAsync(User.GetUserId());
|
||||
//if (isUserResult.IsValid && !(bool)isUserResult.Data)
|
||||
// return Unauthorized();
|
||||
|
||||
usersIds.UserIdList.Remove(User.GetUserId());
|
||||
result = await RootUsersService.UnbanUserAsync(usersIds);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(UnbanUsers)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
//[HttpGet, Authorize(Policy = Policies.IsAdmin)]
|
||||
//public async Task<IActionResult> GetUsers()
|
||||
//{
|
||||
// var result = new WebResult();
|
||||
// try
|
||||
// {
|
||||
// var isUserResult = await UsersService.UserIsAdminAsync(User.GetUserId());
|
||||
// if (isUserResult.IsValid && !(bool)isUserResult.Data)
|
||||
// return Unauthorized();
|
||||
|
||||
// result = await UsersService.GetUsersAsync();
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// return Ok(result.Data);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger.LogError(ex, $"{nameof(User)}.{nameof(GetUsers)}()");
|
||||
// return BadRequest(result.Invalidate(ex.Message));
|
||||
// }
|
||||
//}
|
||||
}
|
||||
}
|
55
PrivaPub/Controllers/ClientToServer/DataController.cs
Normal file
55
PrivaPub/Controllers/ClientToServer/DataController.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.Data;
|
||||
using PrivaPub.Services.ClientToServer.Public;
|
||||
|
||||
namespace PrivaPub.Controllers.ClientToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("clientapi/data")]
|
||||
public class DataController : ControllerBase
|
||||
{
|
||||
readonly IDataService DataService;
|
||||
readonly ILogger<DataController> Logger;
|
||||
|
||||
public DataController(IDataService dataService,
|
||||
ILogger<DataController> logger)
|
||||
{
|
||||
DataService = dataService;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet, Route("/clientapi/data/ping"), AllowAnonymous]
|
||||
public async ValueTask<IActionResult> Ping() => NoContent();
|
||||
|
||||
[HttpGet, Route("/clientapi/data/current-version"), AllowAnonymous]
|
||||
public IActionResult CurrentVersion() =>
|
||||
Ok(DataService.GetCurrentVersion());
|
||||
|
||||
[HttpGet, Route("/clientapi/data/languages"), AllowAnonymous]
|
||||
public async Task<IActionResult> Languages(CancellationToken cancellationToken)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
var languages = await DataService.GetLanguages(cancellationToken);
|
||||
var viewLanguages = new List<ViewLanguage>();
|
||||
|
||||
viewLanguages.AddRange(languages.Select(l => new ViewLanguage
|
||||
{
|
||||
Name = l.NativeName,
|
||||
International2Code = l.International2Code
|
||||
}).ToArray());
|
||||
|
||||
return Ok(viewLanguages);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(DataController)}.{nameof(Languages)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message, exception: ex));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
10
PrivaPub/Controllers/ClientToServer/ModeratorController.cs
Normal file
10
PrivaPub/Controllers/ClientToServer/ModeratorController.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace PrivaPub.Controllers.ClientToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("clientapi/moderator")]
|
||||
public class ModeratorController : ControllerBase
|
||||
{
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Resources;
|
||||
using PrivaPub.Services.ClientToServer.Private;
|
||||
using PrivaPub.ClientModels.User.Avatar;
|
||||
|
||||
namespace PrivaPub.Controllers.ClientToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("clientapi/avatar/private"),
|
||||
Authorize(Policy = Policies.IsUser)]
|
||||
public class PrivateAvatarController : ControllerBase
|
||||
{
|
||||
readonly ILogger<PrivateAvatarController> _logger;
|
||||
readonly IPrivateAvatarUsersService _privateAvatarUsersService;
|
||||
readonly IStringLocalizer _localizer;
|
||||
|
||||
public PrivateAvatarController(IPrivateAvatarUsersService privateAvatarUsersService,
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
ILogger<PrivateAvatarController> logger)
|
||||
{
|
||||
_privateAvatarUsersService = privateAvatarUsersService;
|
||||
_localizer = localizer;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/avatar/private/insert")]
|
||||
public async Task<IActionResult> InsertAvatar(InsertAvatarForm model)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(_localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
model.RootId = User.GetUserId();
|
||||
result = await _privateAvatarUsersService.InsertAvatar(model);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PrivateAvatarController)}.{nameof(InsertAvatar)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/avatar/private/update")]
|
||||
public async Task<IActionResult> UpdateAvatar(UpdateAvatarForm model)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(_localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
model.RootId = User.GetUserId();
|
||||
result = await _privateAvatarUsersService.UpdateAvatar(model);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, $"{nameof(PrivateAvatarController)}.{nameof(UpdateAvatar)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
404
PrivaPub/Controllers/ClientToServer/RootUserController.cs
Normal file
404
PrivaPub/Controllers/ClientToServer/RootUserController.cs
Normal file
@ -0,0 +1,404 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Localization;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.Resources;
|
||||
using PrivaPub.ClientModels.User;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Models.User;
|
||||
using PrivaPub.Resources;
|
||||
using PrivaPub.Services;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PrivaPub.Controllers.ClientToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("clientapi/user")]
|
||||
public class RootUserController : ControllerBase
|
||||
{
|
||||
readonly IRootUsersService UsersService;
|
||||
readonly AuthTokenManager AuthTokenManager;
|
||||
readonly ILogger<RootUserController> Logger;
|
||||
readonly IStringLocalizer Localizer;
|
||||
|
||||
public RootUserController(IRootUsersService usersService,
|
||||
AuthTokenManager authTokenManager,
|
||||
IStringLocalizer<GenericRes> localizer,
|
||||
ILogger<RootUserController> logger)
|
||||
{
|
||||
UsersService = usersService;
|
||||
AuthTokenManager = authTokenManager;
|
||||
Localizer = localizer;
|
||||
Logger = logger;
|
||||
}
|
||||
|
||||
#region User endpoints
|
||||
|
||||
[HttpPost, Route("/clientapi/user/signup"), Authorize(Policy = Policies.IsUser), AllowAnonymous]
|
||||
public async Task<IActionResult> SignUp(LoginForm signUpForm)
|
||||
{
|
||||
if (User.Identity?.IsAuthenticated ?? false) return Redirect("/");
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
result = await UsersService.SignUpAsync(signUpForm);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
(var user, var userSettings) = ((RootUser, ViewAvatarServer))result.Data;
|
||||
var jwtUser = AuthTokenManager.GenerateToken(user, userSettings);
|
||||
Logger.LogInformation(
|
||||
$"{nameof(SignUp)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]");
|
||||
return Ok(jwtUser);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(SignUp)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/login"), Authorize(Policy = Policies.IsUser), AllowAnonymous]
|
||||
public async Task<IActionResult> Login(LoginForm loginForm)
|
||||
{
|
||||
if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions");
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
result = await UsersService.LoginAsync(loginForm);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
var (user, userSettings) =
|
||||
((RootUser, ViewAvatarServer))result.Data;
|
||||
var jwtUser = AuthTokenManager.GenerateToken(user, userSettings);
|
||||
Logger.LogInformation(
|
||||
$"{nameof(Login)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]");
|
||||
|
||||
return Ok(jwtUser);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(Login)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
//[HttpPost, Authorize(Policy = Policies.IsUser), AllowAnonymous]
|
||||
//public async Task<IActionResult> InvitationSignUp(InvitationLoginForm signUpForm)
|
||||
//{
|
||||
// if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions");
|
||||
// var result = new WebResult();
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
// try
|
||||
// {
|
||||
// result = await DiscussionsService.GetInvitationConfiguration(signUpForm.InvitationCode, includePassword: true);
|
||||
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// var configuration = result.Data as InvitationConfiguration;
|
||||
// if (configuration.IsPasswordRequired && signUpForm.InvitationPassword != configuration.Password)
|
||||
// {
|
||||
// result.Invalidate(Localizer["Invalid password."], StatusCodes.Status406NotAcceptable);
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
// }
|
||||
|
||||
// result = await UsersService.SignUpAsync(new LoginForm
|
||||
// {
|
||||
// Username = signUpForm.Username,
|
||||
// Password = signUpForm.Password,
|
||||
// LightThemeIndexColour = signUpForm.ThemeIndexColour,
|
||||
// ThemeIsDarkMode = signUpForm.ThemeIsDarkMode,
|
||||
// InvitationPassword = signUpForm.InvitationPassword
|
||||
// }, signUpForm.InvitationCode, configuration.IsPasswordRequired);
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// (var user, var userSettings) = ((User, ViewUserSettings))result.Data;
|
||||
// var jwtUser = AuthTokenManager.GenerateToken(user, userSettings);
|
||||
// Logger.LogInformation(
|
||||
// $"{nameof(InvitationSignUp)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]");
|
||||
|
||||
// return Ok(jwtUser);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger.LogError(ex, $"{nameof(User)}.{nameof(InvitationSignUp)}()");
|
||||
// return BadRequest(result.Invalidate(ex.Message));
|
||||
// }
|
||||
//}
|
||||
|
||||
//[HttpPost, Authorize(Policy = Policies.IsUser), AllowAnonymous]
|
||||
//public async Task<IActionResult> InvitationLogin(InvitationLoginForm loginForm)
|
||||
//{
|
||||
// if (User.Identity?.IsAuthenticated ?? false) return Redirect("/discussions" + loginForm.InvitationCode);
|
||||
// var result = new WebResult();
|
||||
// if (!ModelState.IsValid)
|
||||
// return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
// try
|
||||
// {
|
||||
// result = await DiscussionsService.GetInvitationConfiguration(loginForm.InvitationCode, includePassword: true);
|
||||
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// var configuration = result.Data as InvitationConfiguration;
|
||||
// if (configuration.IsPasswordRequired && loginForm.InvitationPassword != configuration.Password)
|
||||
// {
|
||||
// result.Invalidate(Localizer["Invalid password."], StatusCodes.Status406NotAcceptable);
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
// }
|
||||
|
||||
// result = await UsersService.LoginAsync(new LoginForm
|
||||
// {
|
||||
// Username = loginForm.Username,
|
||||
// Password = loginForm.Password,
|
||||
// ThemeIsDarkMode = loginForm.ThemeIsDarkMode,
|
||||
// LightThemeIndexColour = loginForm.ThemeIndexColour,
|
||||
// InvitationPassword = loginForm.InvitationPassword
|
||||
// }, loginForm.InvitationCode);
|
||||
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// var (user, userSettings/*, userCurrentTier*/) = ((User, ViewUserSettings/*, UserCurrentTier*/))result.Data;
|
||||
// var jwtUser = AuthTokenManager.GenerateToken(user, userSettings, 0
|
||||
// /*userCurrentTier == null ? default : (userCurrentTier.EndPeriod - DateTime.UtcNow).Days*/);
|
||||
// Logger.LogInformation(
|
||||
// $"{nameof(InvitationLogin)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]");
|
||||
|
||||
// return Ok(jwtUser);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger.LogError(ex, $"{nameof(User)}.{nameof(InvitationLogin)}()");
|
||||
// return BadRequest(result.Invalidate(ex.Message));
|
||||
// }
|
||||
//}
|
||||
|
||||
[HttpGet, Route("/clientapi/user/logout"), Authorize(Policy = Policies.IsUser)]
|
||||
public IActionResult Logout()
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(Logout)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/update"), Authorize(Policy = Policies.IsUser)]
|
||||
public async Task<IActionResult> UpdateUser(UserForm userEmailForm)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
result = await UsersService.UpdateUserAsync(userEmailForm, User.GetUserId());
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(UpdateUser)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/update/settings"), Authorize(Policy = Policies.IsUser)]
|
||||
public async Task<IActionResult> UpdateUserSettings(ViewAvatarServer userSettings)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
result = await UsersService.UpdateUserSettingsAsync(userSettings, User.GetUserId());
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(UpdateUserSettings)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/update/password"), Authorize(Policy = Policies.IsUser)]
|
||||
public async Task<IActionResult> UpdatePassword(UserPasswordForm userPasswordForm)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
result = await UsersService.UpdateUserPasswordAsync(userPasswordForm, User.GetUserId());
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(UpdatePassword)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
//[HttpGet, Authorize(Policy = Policies.IsUser)]
|
||||
//public async Task<IActionResult> GetUser()
|
||||
//{
|
||||
// var result = new WebResult();
|
||||
// try
|
||||
// {
|
||||
// result = await UsersService.GetUserAsync(User.GetUserId());
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// return Ok(result.Data);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger.LogError(ex, $"{nameof(User)}.{nameof(GetUser)}()");
|
||||
// return BadRequest(result.Invalidate(ex.Message));
|
||||
// }
|
||||
//}
|
||||
|
||||
[HttpGet, Route("/clientapi/user/settings"), Authorize(Policy = Policies.IsUser)]
|
||||
public async Task<IActionResult> GetUserSettings()
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
result = await UsersService.GetUserSettingsAsync(User.GetUserId());
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
return Ok(result.Data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(GetUserSettings)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/recover/password"), AllowAnonymous]
|
||||
public async Task<IActionResult> RecoverPassword(PasswordRecoveryForm passwordRecoveryForm)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
var host = $"{Request.Scheme}://{Request.Host.Host}";
|
||||
result = await UsersService.SetupAndSendRecoveryEmail(passwordRecoveryForm, host);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(RecoverPassword)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/recover/valid"), AllowAnonymous]
|
||||
public async Task<IActionResult> IsValidRecoveryCode(
|
||||
[FromBody,
|
||||
Required(ErrorMessageResourceName = "Required", ErrorMessageResourceType = typeof(ErrorsResource)),
|
||||
Display(Name = "RecoveryCode", ResourceType = typeof(FieldsNameResource))]
|
||||
string recoveryCode)
|
||||
{
|
||||
var result = new WebResult();
|
||||
try
|
||||
{
|
||||
result = await UsersService.IsValidRecoveryCode(recoveryCode);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(IsValidRecoveryCode)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost, Route("/clientapi/user/recover/update/password"), AllowAnonymous]
|
||||
public async Task<IActionResult> ChangePassword(NewPasswordForm newPasswordForm)
|
||||
{
|
||||
var result = new WebResult();
|
||||
if (!ModelState.IsValid)
|
||||
return BadRequest(result.Invalidate(Localizer["Invalid model."]));
|
||||
try
|
||||
{
|
||||
result = await UsersService.ChangePassword(newPasswordForm);
|
||||
if (!result.IsValid)
|
||||
return StatusCode(result.StatusCode, result);
|
||||
|
||||
return Ok(result.Data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError(ex, $"{nameof(User)}.{nameof(ChangePassword)}()");
|
||||
return BadRequest(result.Invalidate(ex.Message));
|
||||
}
|
||||
}
|
||||
|
||||
//[HttpDelete, Route("delete"), Authorize(Policy = Policies.IsUser)]
|
||||
//public async Task<IActionResult> RemoveSelf()
|
||||
//{
|
||||
// var result = new WebResult();
|
||||
// try
|
||||
// {
|
||||
// //result = await UsersService.RemoveUserAsync(User.GetUserId());
|
||||
// if (!result.IsValid)
|
||||
// return StatusCode(result.StatusCode, result);
|
||||
|
||||
// await HttpContext.SignOutAsync();
|
||||
|
||||
// return Ok();
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Logger.LogError(ex, $"{nameof(User)}.{nameof(RemoveSelf)}()");
|
||||
// return BadRequest(result.Invalidate(ex.Message));
|
||||
// }
|
||||
//}
|
||||
|
||||
#endregion User endpoints
|
||||
|
||||
#region Auth refresh
|
||||
|
||||
[HttpGet, Route("/clientapi/user/sniff/again"), Authorize(Policy = Policies.IsUser)]
|
||||
public async Task<IActionResult> SniffAgain()
|
||||
{
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
111
PrivaPub/Controllers/ServerToServer/PeasantsController.cs
Normal file
111
PrivaPub/Controllers/ServerToServer/PeasantsController.cs
Normal file
@ -0,0 +1,111 @@
|
||||
using Markdig;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using PrivaPub.ClientModels.Resources;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Models.ActivityPub;
|
||||
using PrivaPub.Models.Group;
|
||||
using PrivaPub.Services;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PrivaPub.Controllers.ServerToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("peasants"), Produces("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"; charset=utf-8")]
|
||||
public class PeasantsController : ControllerBase
|
||||
{
|
||||
readonly IGroupUsersService _groupUsersService;
|
||||
|
||||
public PeasantsController(IGroupUsersService groupUsersService)
|
||||
{
|
||||
_groupUsersService = groupUsersService;
|
||||
}
|
||||
|
||||
[HttpGet, Route("{actor}")]
|
||||
public async Task<IActionResult> GetActor(
|
||||
[Required(ErrorMessageResourceName = "Required",
|
||||
ErrorMessageResourceType = typeof(FieldsNameResource))] string actor,
|
||||
CancellationToken cancellation)
|
||||
{
|
||||
var getResult = await _groupUsersService.GetGroup(actor, cancellation);
|
||||
if (!getResult.IsValid)
|
||||
return StatusCode(getResult.StatusCode, getResult);
|
||||
|
||||
var actorResponse = new ActivityPubActor();
|
||||
actorResponse.Context[1] = string.Format(actorResponse.Context[1].ToString(), HttpContext.Request.PathBase);
|
||||
actorResponse.Id = HttpContext.GetHostWithPath();
|
||||
actorResponse.ProfileURL = HttpContext.GetHostWithPath();
|
||||
actorResponse.Inbox = $"{HttpContext.GetHostWithPath()}/mouth";
|
||||
actorResponse.Outbox = $"{HttpContext.GetHostWithPath()}/anus";
|
||||
actorResponse.Endpoints = new()
|
||||
{
|
||||
SharedInboxURL = $"{HttpContext.GetHostWithPath()}/human-centipede",
|
||||
OAuthAuthorizationEndpoint = $"{HttpContext.GetHost()}/sniff/again",
|
||||
};
|
||||
actorResponse.PreferredUsername = actor;
|
||||
actorResponse.Name = actor;
|
||||
|
||||
if (getResult.Data is DmGroup dmGroup)
|
||||
{
|
||||
actorResponse.Summary = Markdown.ToHtml(dmGroup.Description);
|
||||
actorResponse.Published = dmGroup.CreationDate;
|
||||
}
|
||||
else if (getResult.Data is Group dbGroup)
|
||||
{
|
||||
actorResponse.Summary = Markdown.ToHtml(dbGroup.Description);
|
||||
actorResponse.Published = dbGroup.CreationDate;
|
||||
}
|
||||
|
||||
return Ok(actorResponse);
|
||||
}
|
||||
|
||||
[HttpGet, Route("{actor}/anus")]
|
||||
public async Task<IActionResult> Anus(
|
||||
[FromQuery] bool? page,
|
||||
[Required(ErrorMessageResourceName = "Required",
|
||||
ErrorMessageResourceType = typeof(FieldsNameResource))] string actor)
|
||||
{
|
||||
if (!page.HasValue)
|
||||
return Ok(new ActivityPubOrderedCollection
|
||||
{
|
||||
Id = HttpContext.Request.Path,
|
||||
FirstItem = $"{HttpContext.GetHostWithPath()}?page=true",
|
||||
});
|
||||
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost, Route("{actor}/mouth")]
|
||||
public async Task<IActionResult> Month(
|
||||
[Required(ErrorMessageResourceName = "Required",
|
||||
ErrorMessageResourceType = typeof(FieldsNameResource))] string actor,
|
||||
[FromBody] JsonDocument json)
|
||||
{
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost, Route("{actor}/human-centipede")]
|
||||
public async Task<IActionResult> HumanCentipede(
|
||||
[Required(ErrorMessageResourceName = "Required",
|
||||
ErrorMessageResourceType = typeof(FieldsNameResource))] string actor,
|
||||
[FromBody] JsonDocument json)
|
||||
{
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[HttpPost, Route("/human-centipede")]
|
||||
public async Task<IActionResult> PublicHumanCentipede(
|
||||
[FromBody] JsonDocument json)
|
||||
{
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
69
PrivaPub/Controllers/ServerToServer/UsersController.cs
Normal file
69
PrivaPub/Controllers/ServerToServer/UsersController.cs
Normal file
@ -0,0 +1,69 @@
|
||||
using Markdig;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PrivaPub.ClientModels.Resources;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Models.ActivityPub;
|
||||
using PrivaPub.Models.Group;
|
||||
using PrivaPub.Services;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PrivaPub.Controllers.ServerToServer
|
||||
{
|
||||
[ApiController,
|
||||
Route("users"), Produces("application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"; charset=utf-8")]
|
||||
public class UsersController : ControllerBase
|
||||
{
|
||||
readonly IRootUsersService _rootUsersService;
|
||||
|
||||
public UsersController(IRootUsersService rootUsersService)
|
||||
{
|
||||
_rootUsersService = rootUsersService;
|
||||
}
|
||||
|
||||
[HttpGet, Route("{actor}")]
|
||||
public async Task<IActionResult> GetActor(
|
||||
[Required(ErrorMessageResourceName = "Required",
|
||||
ErrorMessageResourceType = typeof(FieldsNameResource))] string actor,
|
||||
CancellationToken cancellation)
|
||||
{
|
||||
var getResult = await _groupUsersService.GetGroup(actor, cancellation);
|
||||
if (!getResult.IsValid)
|
||||
return StatusCode(getResult.StatusCode, getResult);
|
||||
|
||||
var actorResponse = new ActivityPubActor();
|
||||
actorResponse.Context[1] = string.Format(actorResponse.Context[1].ToString(), HttpContext.Request.PathBase);
|
||||
actorResponse.Id = $"{HttpContext.GetHost()}/peasants/{actor}";
|
||||
actorResponse.ProfileURL = $"{HttpContext.GetHost()}/peasants/{actor}";
|
||||
actorResponse.Inbox = $"{HttpContext.GetHost()}/peasants/{actor}/mouth";
|
||||
actorResponse.Outbox = $"{HttpContext.GetHost()}/peasants/{actor}/anus";
|
||||
actorResponse.Endpoints = new()
|
||||
{
|
||||
SharedInboxURL = $"{HttpContext.GetHost()}/peasants/{actor}/human-centipede",
|
||||
OAuthAuthorizationEndpoint = $"{HttpContext.GetHost()}/sniff/again",
|
||||
};
|
||||
actorResponse.PreferredUsername = actor;
|
||||
actorResponse.Name = actor;
|
||||
|
||||
if (getResult.Data is DmGroup dmGroup)
|
||||
{
|
||||
actorResponse.Summary = Markdown.ToHtml(dmGroup.Description);
|
||||
actorResponse.Published = dmGroup.CreationDate;
|
||||
}
|
||||
else if (getResult.Data is Group dbGroup)
|
||||
{
|
||||
actorResponse.Summary = Markdown.ToHtml(dbGroup.Description);
|
||||
actorResponse.Published = dbGroup.CreationDate;
|
||||
}
|
||||
|
||||
return Ok(actorResponse);
|
||||
}
|
||||
|
||||
#region Admin
|
||||
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
213
PrivaPub/Data/InitDb.cs
Normal file
213
PrivaPub/Data/InitDb.cs
Normal file
@ -0,0 +1,213 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Models.Data;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace PrivaPub.Data
|
||||
{
|
||||
public static class InitDb
|
||||
{
|
||||
public static async Task Init(this DbEntities dbClient, IPasswordHasher passwordHasher)
|
||||
{
|
||||
await SyncLanguages(dbClient);
|
||||
await UpdateNewLanguages(dbClient);
|
||||
//await SyncUserSettings(dbClient);
|
||||
//await ClearUsers(dbClient);
|
||||
//var botsInsertionAwaiter = InsertBots(dbClient, passwordHasher);
|
||||
//var updateAnswersAwaiter = UpdateAnswersLikes(dbClient);
|
||||
|
||||
//Task.WaitAll(botsInsertionAwaiter);
|
||||
//await SyncShareDiscussions(dbClient);
|
||||
}
|
||||
|
||||
//static async Task UpdateAnswersLikes(DbCollections dbClient)
|
||||
//{
|
||||
// if (await dbClient.AnswersLikes.Find(al => al.AnswerId != null).AnyAsync()) return;
|
||||
|
||||
// foreach (var answerLikes in await dbClient.AnswersLikes.Find(al => true).ToListAsync())
|
||||
// {
|
||||
// answerLikes.AnswerId = answerLikes.Id;
|
||||
// answerLikes.Id = ObjectId.GenerateNewId();
|
||||
// await dbClient.AnswersLikes.InsertOneAsync(answerLikes);
|
||||
// _ = await dbClient.AnswersLikes.DeleteOneAsync(al => al.Id == answerLikes.AnswerId);
|
||||
// }
|
||||
//}
|
||||
|
||||
//static async Task ClearUsers(DbColl dbClient)
|
||||
//{
|
||||
// await dbClient.Users.DeleteManyAsync(u => true);
|
||||
// await dbClient.UsersSettings.DeleteManyAsync(u => true);
|
||||
// await dbClient.Answers.DeleteManyAsync(u => true);
|
||||
// await dbClient.AnswersLikes.DeleteManyAsync(u => true);
|
||||
// await dbClient.Comments.DeleteManyAsync(u => true);
|
||||
// await dbClient.EmailRecoveries.DeleteManyAsync(u => true);
|
||||
// await dbClient.Threads.DeleteManyAsync(u => true);
|
||||
// await dbClient.EDiscussions.DeleteManyAsync(u => true);
|
||||
// await dbClient.Logs.DeleteManyAsync(u => true);
|
||||
// await dbClient.ShareDiscussions.De(u => true);
|
||||
// await dbClient.DiscussionFiles.DeleteManyAsync(u => true);
|
||||
// await dbClient.UsersGroups.DeleteManyAsync(u => true);
|
||||
//}
|
||||
|
||||
// static async Task SyncShareDiscussions(DbColl dbClient)
|
||||
// {
|
||||
// var anyThread = await dbClient.Discussions.Match(t => true).ExecuteAnyAsync();
|
||||
// var anyShareDiscussion = await dbClient.ShareDiscussions.Match(st => true).ExecuteAnyAsync();
|
||||
//
|
||||
// if (anyThread == anyShareDiscussion) return;
|
||||
//
|
||||
// var discussions = await dbClient.Discussions.ManyAsync(t => true);
|
||||
//
|
||||
// foreach (var discussion in discussions)
|
||||
// {
|
||||
// var newShareDiscussion = new ShareDiscussion
|
||||
// {
|
||||
// DiscussionId = discussion.ID,
|
||||
// InvitationCode = $"{Guid.NewGuid():N}{Guid.NewGuid():N}"
|
||||
// };
|
||||
// await newShareDiscussion.SaveAsync();
|
||||
// }
|
||||
// }
|
||||
|
||||
static async Task SyncLanguages(DbEntities dbClient)
|
||||
{
|
||||
if (await dbClient.Languages.ExecuteAnyAsync()) return;
|
||||
|
||||
var languagesJson = await File.ReadAllTextAsync(Path.Combine(Directory.GetCurrentDirectory(), "Data", "languagesNative.json"), Encoding.UTF8);
|
||||
var languagesRows = JsonSerializer.Deserialize<IEnumerable<LanguagesRow>>(languagesJson);
|
||||
|
||||
var languages = new List<Language>();
|
||||
foreach (var languageRow in languagesRows ?? new LanguagesRow[]
|
||||
{
|
||||
})
|
||||
languages.Add(new Language
|
||||
{
|
||||
EnglishName = languageRow.EnglishName,
|
||||
NativeName = languageRow.NativeName,
|
||||
International2Code = languageRow.International2Code
|
||||
});
|
||||
|
||||
await DB.SaveAsync(languages);
|
||||
}
|
||||
|
||||
static async Task UpdateNewLanguages(DbEntities dbClient)
|
||||
{
|
||||
var bulgarianLanguage = await dbClient.Languages.Match(l => l.International2Code == "bg").ExecuteFirstAsync();
|
||||
if (bulgarianLanguage.NativeName == "Български") return;
|
||||
|
||||
var languagesToUpdate = new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
"bg", "Български"
|
||||
},
|
||||
{
|
||||
"cs", "Česky"
|
||||
},
|
||||
{
|
||||
"da", "Dansk"
|
||||
},
|
||||
{
|
||||
"nl", "Nederlands"
|
||||
},
|
||||
{
|
||||
"et", "Eesti"
|
||||
},
|
||||
{
|
||||
"fi", "Suomalainen"
|
||||
},
|
||||
{
|
||||
"el", "Ελληνική"
|
||||
},
|
||||
{
|
||||
"hu", "Magyar"
|
||||
},
|
||||
{
|
||||
"lv", "Latviešu"
|
||||
},
|
||||
{
|
||||
"lt", "Lietuvių kalba"
|
||||
},
|
||||
{
|
||||
"pl", "Polski"
|
||||
},
|
||||
{
|
||||
"pt", "Português"
|
||||
},
|
||||
{
|
||||
"ro", "Românesc"
|
||||
},
|
||||
{
|
||||
"sk", "Slovenská"
|
||||
},
|
||||
{
|
||||
"sl", "Slovenski"
|
||||
},
|
||||
{
|
||||
"sv", "Svenska"
|
||||
}
|
||||
};
|
||||
|
||||
foreach (var languageToUpdate in languagesToUpdate)
|
||||
{
|
||||
var bgLang = await dbClient.Languages.Match(l => l.International2Code == languageToUpdate.Key).ExecuteFirstAsync();
|
||||
bgLang.NativeName = languageToUpdate.Value;
|
||||
await bgLang.SaveAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// static async Task SyncUserSettings(DbColl dbClient)
|
||||
// {
|
||||
// if (!await dbClient.Users.ExecuteAnyAsync()) return;
|
||||
//
|
||||
// var users = await dbClient.Users.ManyAsync(u => true);
|
||||
// var usersId = users.Select(u => u.ID).ToList();
|
||||
// var usersSettings = await dbClient.UsersSettings
|
||||
// .ManyAsync(us => true);
|
||||
//
|
||||
// var usersWithoutSettings = users.Where(u => !usersSettings.Any(us => us.UserId == u.ID)).Select(u => u.ID).ToList();
|
||||
// if (usersWithoutSettings.Count == 0) return;
|
||||
//
|
||||
// var defaultLanguage = await dbClient.Languages
|
||||
// .Match(l => l.International2Code == "en")
|
||||
// .ExecuteFirstAsync();
|
||||
// foreach (var userId in usersWithoutSettings)
|
||||
// await DB.SaveAsync(new UserSettings
|
||||
// {
|
||||
// UserId = userId,
|
||||
// LanguageId = defaultLanguage.International2Code
|
||||
// });
|
||||
// }
|
||||
|
||||
//static async Task InsertBots(DbEntities dbClient, IPasswordHasher passwordHasher)
|
||||
//{
|
||||
// if (await dbClient.Users.Match(u => u.UserName == "bot0").ExecuteAnyAsync()) return;
|
||||
|
||||
// var newBots = new List<User>();
|
||||
// var newBotsUserSettings = new List<UserSettings>();
|
||||
|
||||
// for (int i = 0; i < 100; i++)
|
||||
// newBots.Add(new User
|
||||
// {
|
||||
// HashedPassword = passwordHasher.Hash("Asdfmov13!!!"),
|
||||
// UserName = $"bot{i}"
|
||||
// });
|
||||
// await newBots.SaveAsync();
|
||||
|
||||
// var defaultLanguage = await dbClient.Languages
|
||||
// .Match(l => l.International2Code == "en")
|
||||
// .ExecuteFirstAsync();
|
||||
|
||||
// foreach (var newBot in newBots)
|
||||
// newBotsUserSettings.Add(new()
|
||||
// {
|
||||
// UserId = newBot.ID,
|
||||
// LanguageId = defaultLanguage.International2Code,
|
||||
// });
|
||||
|
||||
// await newBotsUserSettings.SaveAsync();
|
||||
//}
|
||||
}
|
||||
}
|
9
PrivaPub/Data/LanguagesRow.cs
Normal file
9
PrivaPub/Data/LanguagesRow.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace PrivaPub.Data
|
||||
{
|
||||
public class LanguagesRow
|
||||
{
|
||||
public string EnglishName { get; set; }
|
||||
public string NativeName { get; set; }
|
||||
public string International2Code { get; set; }
|
||||
}
|
||||
}
|
2210
PrivaPub/Data/languagesNative.json
Normal file
2210
PrivaPub/Data/languagesNative.json
Normal file
File diff suppressed because it is too large
Load Diff
33
PrivaPub/Extensions/AddAuthExtension.cs
Normal file
33
PrivaPub/Extensions/AddAuthExtension.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using PrivaPub.Services;
|
||||
|
||||
using System.Text;
|
||||
|
||||
namespace PrivaPub.Extensions
|
||||
{
|
||||
public static class AddAuthExtension
|
||||
{
|
||||
public static AuthenticationBuilder AddPrivaPubAuth(this AuthenticationBuilder builder, IConfiguration configuration)
|
||||
{
|
||||
builder.AddJwtBearer(options => {
|
||||
#if DEBUG
|
||||
options.RequireHttpsMetadata = false;
|
||||
#endif
|
||||
options.TokenValidationParameters = new()
|
||||
{
|
||||
ValidateIssuer = true,
|
||||
ValidateAudience = true,
|
||||
ValidateLifetime = true,
|
||||
ValidateIssuerSigningKey = true,
|
||||
ValidIssuer = configuration["AppConfiguration:Jwt:Issuer"],
|
||||
ValidAudience = configuration["AppConfiguration:Jwt:Audience"],
|
||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AppConfiguration:Jwt:Key"]))
|
||||
};
|
||||
options.Events = new JwtEvents();
|
||||
});
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
}
|
93
PrivaPub/Extensions/Extensions.cs
Normal file
93
PrivaPub/Extensions/Extensions.cs
Normal file
@ -0,0 +1,93 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
using Org.BouncyCastle.Crypto.Generators;
|
||||
using Org.BouncyCastle.Crypto.Prng;
|
||||
using Org.BouncyCastle.Crypto;
|
||||
using Org.BouncyCastle.Security;
|
||||
using Org.BouncyCastle.X509;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
|
||||
using System.Security.Cryptography.X509Certificates;
|
||||
using Org.BouncyCastle.Math;
|
||||
using PrivaPub.Models.User;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace PrivaPub.Extensions
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static string GetLogsConnectionString(this IConfiguration configuration) =>
|
||||
configuration.GetSection("Serilog")
|
||||
?.GetSection("WriteTo")
|
||||
?.GetChildren()
|
||||
?.First()
|
||||
?.GetSection("Args")
|
||||
?.GetSection("databaseUrl")
|
||||
?.Value;
|
||||
|
||||
public static AuthorizationPolicy IsAdminPolicy() =>
|
||||
new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
|
||||
.RequireClaim(Policies.IsAdmin, true.ToString().ToLower())
|
||||
.RequireClaim(Policies.IsUser, true.ToString().ToLower())
|
||||
.RequireClaim(Policies.IsModerator, true.ToString().ToLower())
|
||||
.Build();
|
||||
|
||||
public static AuthorizationPolicy IsUserPolicy() =>
|
||||
new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
|
||||
.RequireClaim(Policies.IsUser, true.ToString().ToLower())
|
||||
.Build();
|
||||
|
||||
public static AuthorizationPolicy IsModeratorPolicy() =>
|
||||
new AuthorizationPolicyBuilder().RequireAuthenticatedUser()
|
||||
.RequireClaim(Policies.IsUser, true.ToString().ToLower())
|
||||
.RequireClaim(Policies.IsModerator, true.ToString().ToLower())
|
||||
.Build();
|
||||
|
||||
public static string GetHostWithPath(this HttpContext httpContext) =>
|
||||
$"https://{httpContext.Request.Host}{httpContext.Request.Path}";
|
||||
|
||||
public static string GetHost(this HttpContext httpContext) =>
|
||||
$"https://{httpContext.Request.Host}";
|
||||
|
||||
public static X509Certificate2 GetX509Certificate2(string certName)
|
||||
{
|
||||
var keypairgen = new RsaKeyPairGenerator();
|
||||
keypairgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 512));
|
||||
|
||||
var keypair = keypairgen.GenerateKeyPair();
|
||||
|
||||
var gen = new X509V3CertificateGenerator();
|
||||
|
||||
var CN = new X509Name("CN=" + certName);
|
||||
var SN = BigInteger.ProbablePrime(120, new Random());
|
||||
|
||||
gen.SetSerialNumber(SN);
|
||||
gen.SetSubjectDN(CN);
|
||||
gen.SetIssuerDN(CN);
|
||||
gen.SetNotAfter(DateTime.MaxValue);
|
||||
gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
|
||||
gen.SetSignatureAlgorithm("MD5WithRSA");
|
||||
gen.SetPublicKey(keypair.Public);
|
||||
|
||||
var newCert = gen.Generate(keypair.Private);
|
||||
|
||||
return new X509Certificate2(DotNetUtilities.ToX509Certificate((Org.BouncyCastle.X509.X509Certificate)newCert));
|
||||
}
|
||||
|
||||
public static UserPolicyType GetHighestPolicy(this ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
if (bool.Parse(claimsPrincipal.FindFirstValue(Policies.IsAdmin)))
|
||||
return UserPolicyType.IsAdmin;
|
||||
|
||||
if (bool.Parse(claimsPrincipal.FindFirstValue(Policies.IsModerator)))
|
||||
return UserPolicyType.IsModerator;
|
||||
|
||||
if (bool.Parse(claimsPrincipal.FindFirstValue(Policies.IsUser)))
|
||||
return UserPolicyType.IsUser;
|
||||
|
||||
return UserPolicyType.IsUser;
|
||||
}
|
||||
}
|
||||
}
|
23
PrivaPub/Extensions/OperationCancelledExceptionFilter.cs
Normal file
23
PrivaPub/Extensions/OperationCancelledExceptionFilter.cs
Normal file
@ -0,0 +1,23 @@
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace PrivaPub.Extensions
|
||||
{
|
||||
public class OperationCancelledExceptionFilter : ExceptionFilterAttribute
|
||||
{
|
||||
readonly Serilog.ILogger Logger;
|
||||
|
||||
public OperationCancelledExceptionFilter(Serilog.ILogger logger) =>
|
||||
Logger = logger.ForContext<OperationCancelledExceptionFilter>();
|
||||
|
||||
public override void OnException(ExceptionContext context)
|
||||
{
|
||||
if (context.Exception is not OperationCanceledException) return;
|
||||
|
||||
Logger.Information($"Request for {context.HttpContext.Request.Path} was cancelled.");
|
||||
context.ExceptionHandled = true;
|
||||
context.Result = new StatusCodeResult(499);
|
||||
}
|
||||
}
|
||||
}
|
36
PrivaPub/Extensions/StringExtensions.cs
Normal file
36
PrivaPub/Extensions/StringExtensions.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using PrivaPub.ClientModels;
|
||||
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace PrivaPub.Extensions
|
||||
{
|
||||
public static class StringExtensions
|
||||
{
|
||||
public static string GetUserId(this ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
return claimsPrincipal?.Claims?.FirstOrDefault(c => c.Type == ClaimTypes.UserData)?.Value;
|
||||
}
|
||||
|
||||
public static string GetUserName(this ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var userId = claimsPrincipal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value;
|
||||
return string.IsNullOrEmpty(userId) ? default : userId;
|
||||
}
|
||||
|
||||
public static List<string> GetUserPolicies(this ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var policies = new List<string>();
|
||||
|
||||
if (claimsPrincipal.Claims.Any(c => c.Type == Policies.IsAdmin && bool.Parse(c.Value)))
|
||||
policies.Add(Policies.IsAdmin);
|
||||
|
||||
if (claimsPrincipal.Claims.Any(c => c.Type == Policies.IsUser && bool.Parse(c.Value)))
|
||||
policies.Add(Policies.IsUser);
|
||||
|
||||
if (claimsPrincipal.Claims.Any(c => c.Type == Policies.IsModerator && bool.Parse(c.Value)))
|
||||
policies.Add(Policies.IsModerator);
|
||||
|
||||
return policies;
|
||||
}
|
||||
}
|
||||
}
|
216
PrivaPub/Middleware/SocialPubConfigurations.cs
Normal file
216
PrivaPub/Middleware/SocialPubConfigurations.cs
Normal file
@ -0,0 +1,216 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.ResponseCompression;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Models;
|
||||
using PrivaPub.Services;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
using NSign.Providers;
|
||||
using NSign;
|
||||
using NSign.Signatures;
|
||||
using NSign.Client;
|
||||
using System.Text;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using PrivaPub.Services.ClientToServer.Public;
|
||||
|
||||
namespace PrivaPub.Middleware
|
||||
{
|
||||
public static class PrivaPubConfigurations
|
||||
{
|
||||
public static IServiceCollection PrivaPubAppSettingsConfiguration(this IServiceCollection service, IConfiguration configuration)
|
||||
{
|
||||
return service
|
||||
.Configure<MongoSettings>(configuration.GetSection(nameof(MongoSettings)))
|
||||
.Configure<AppConfiguration>(configuration.GetSection(nameof(AppConfiguration)));
|
||||
}
|
||||
public static IServiceCollection PrivaPubWorkersConfiguration(this IServiceCollection service)
|
||||
{
|
||||
return service;
|
||||
//.AddHostedService<DiscussionsWorker>()
|
||||
//.AddHostedService<GroupsCleanerWorker>()
|
||||
//.AddHostedService<PoliciesCleanerWorker>();
|
||||
}
|
||||
public static IServiceCollection PrivaPubHTTPSignature(this IServiceCollection service, IConfiguration configuration)
|
||||
{
|
||||
//HTTP CLIENT
|
||||
service.Configure<AddDigestOptions>(options => options.WithHash(AddDigestOptions.Hash.Sha256))
|
||||
.ConfigureMessageSigningOptions(options =>
|
||||
{
|
||||
options.SignatureName = "PrivaPub";
|
||||
options
|
||||
.WithMandatoryComponent(SignatureComponent.Path)
|
||||
.WithMandatoryComponent(SignatureComponent.RequestTarget)
|
||||
.SetParameters = signatureParams => signatureParams.WithKeyId("keyId");
|
||||
})
|
||||
.Services.Configure<SignatureVerificationOptions>(options =>
|
||||
{
|
||||
|
||||
})
|
||||
.AddHttpClient<ActivityPubClient>(nameof(ActivityPubClient))
|
||||
.ConfigureHttpClient(httpClient =>
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Accept.Add(new("application/ld+json"));
|
||||
})
|
||||
.AddDigestAndSigningHandlers()
|
||||
//.AddSignatureVerifiationHandler()
|
||||
.Services
|
||||
.AddSingleton<ISigner>(new HmacSha256SignatureProvider(Encoding.UTF8.GetBytes(configuration["AppConfiguration:Jwt:Key"])));
|
||||
|
||||
//MESSAGE RESPONSE
|
||||
|
||||
|
||||
return service;
|
||||
//.Configure<RequestSignatureVerificationOptions>(options =>
|
||||
//{
|
||||
// options.SignaturesToVerify.Add("sample");
|
||||
// options.RequiredSignatureComponents.Add(SignatureComponent.Path);
|
||||
// options.RequiredSignatureComponents.Add(SignatureComponent.Method);
|
||||
// options.RequiredSignatureComponents.Add(SignatureComponent.Digest);
|
||||
//})
|
||||
//.AddSignatureVerification(serviceProvider =>
|
||||
//{
|
||||
// var memoryCache = serviceProvider.GetRequiredService<IMemoryCache>();
|
||||
// //var httpContextAccessor = serviceProvider.GetRequiredService<IHttpContextAccessor>();
|
||||
|
||||
// //httpContextAccessor.HttpContext.Request.
|
||||
|
||||
// var cert = memoryCache.GetOrCreate("PrivaPub", (cacheEntry) => Extensions.Extensions.GetX509Certificate2("PrivaPubCert"));
|
||||
// return new RsaPkcs15Sha256SignatureProvider(cert, "anon");
|
||||
//})
|
||||
//.ConfigureMessageSigningOptions(options =>
|
||||
//{
|
||||
// options.WithMandatoryComponent(SignatureComponent.Path)
|
||||
// .WithMandatoryComponent(SignatureComponent.Method)
|
||||
// .WithMandatoryComponent(SignatureComponent.Digest)
|
||||
// .WithOptionalComponent(new HttpHeaderDictionaryStructuredComponent(NSign.Constants.Headers.Signature, "sample", bindRequest: true));
|
||||
// options.SignatureName = "resp";
|
||||
// options.SetParameters = (sigParams) =>
|
||||
// {
|
||||
// sigParams.WithCreatedNow();
|
||||
// };
|
||||
//})
|
||||
//.ValidateOnStart()
|
||||
//.Services
|
||||
//.AddHttpClient("ActivityPub", (serviceProvider, client) =>
|
||||
//{
|
||||
// client.DefaultRequestHeaders.UserAgent.Add(new ProductInfoHeaderValue("NSignSample", "0.1-beta"));
|
||||
//}).Services;
|
||||
//.AddSingleton<ISigner>(new RsaPssSha512SignatureProvider(
|
||||
// new X509Certificate2(@"path\to\certificate.pfx", "PasswordForPfx"),
|
||||
// "my-cert"));
|
||||
}
|
||||
public static IServiceCollection PrivaPubAuthServicesConfiguration(this IServiceCollection service, IConfiguration configuration)
|
||||
{
|
||||
return service
|
||||
.AddAuthorization(options =>
|
||||
{
|
||||
options.AddPolicy(Policies.IsUser, Extensions.Extensions.IsUserPolicy());
|
||||
options.AddPolicy(Policies.IsAdmin, Extensions.Extensions.IsAdminPolicy());
|
||||
options.AddPolicy(Policies.IsModerator, Extensions.Extensions.IsModeratorPolicy());
|
||||
})
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddPrivaPubAuth(configuration)
|
||||
.Services
|
||||
.AddSingleton<AuthTokenManager>()
|
||||
.AddSingleton<IPasswordHasher, PasswordHasher>();
|
||||
}
|
||||
public static IServiceCollection PrivaPubInternalizationConfiguration(this IServiceCollection service, IConfiguration configuration)
|
||||
{
|
||||
return service
|
||||
.AddLocalization()
|
||||
.AddSingleton<RequestLocalizationOptionsService>();
|
||||
}
|
||||
|
||||
public static IServiceCollection PrivaPubOptimizationConfiguration(this IServiceCollection service)
|
||||
{
|
||||
return service.AddResponseCompression(opts =>
|
||||
{
|
||||
opts.Providers.Add<BrotliCompressionProvider>();
|
||||
opts.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/octet-stream" });
|
||||
});
|
||||
}
|
||||
|
||||
public static IServiceCollection PrivaPubDataBaseConfiguration(this IServiceCollection service)
|
||||
{
|
||||
return service.AddSingleton<DbEntities>();
|
||||
}
|
||||
|
||||
public static IServiceCollection PrivaPubServicesConfiguration(this IServiceCollection service)
|
||||
{
|
||||
return service
|
||||
.AddTransient<IDataService, DataService>()
|
||||
.AddTransient<IRootUsersService, RootUsersService>()
|
||||
.AddTransient<IPublicAvatarUsersService, PublicAvatarUsersService>()
|
||||
.AddSingleton<AppConfigurationService>()
|
||||
.AddHttpContextAccessor()
|
||||
.AddMemoryCache()
|
||||
.AddSingleton<IPasswordHasher, PasswordHasher>();
|
||||
}
|
||||
|
||||
public static IServiceCollection PrivaPubMiddlewareConfiguration(this IServiceCollection service)
|
||||
{
|
||||
return service
|
||||
.AddEndpointsApiExplorer()
|
||||
.AddSwaggerGen(c =>
|
||||
{
|
||||
c.AddSecurityDefinition("Bearer", new()
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Description = "Please enter a valid token",
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.Http,
|
||||
BearerFormat = "JWT",
|
||||
Scheme = "Bearer"
|
||||
});
|
||||
c.AddSecurityRequirement(new()
|
||||
{
|
||||
{
|
||||
new()
|
||||
{
|
||||
Reference = new()
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
},
|
||||
new string[]{}
|
||||
}
|
||||
});
|
||||
})
|
||||
.AddControllers(options => { options.Filters.Add<OperationCancelledExceptionFilter>(); })
|
||||
.AddJsonOptions(options =>
|
||||
{
|
||||
options.JsonSerializerOptions.IgnoreReadOnlyFields = false;
|
||||
options.JsonSerializerOptions.IgnoreReadOnlyProperties = false;
|
||||
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
|
||||
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
|
||||
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
|
||||
}).Services;
|
||||
}
|
||||
|
||||
public static IServiceCollection PrivaPubCORSConfiguration(this IServiceCollection service)
|
||||
{
|
||||
return service.AddCors(options =>
|
||||
{
|
||||
options.DefaultPolicyName = "DefaultCORS";
|
||||
options.AddDefaultPolicy(configure =>
|
||||
{
|
||||
configure.AllowAnyMethod()
|
||||
.AllowAnyHeader()
|
||||
.AllowAnyOrigin()
|
||||
.AllowAnyMethod()
|
||||
.DisallowCredentials();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
44
PrivaPub/Models/ActivityPub/ActivityPubActivity.cs
Normal file
44
PrivaPub/Models/ActivityPub/ActivityPubActivity.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using PrivaPub.Models.ActivityPub.Extra;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubActivity))]
|
||||
public class ActivityPubActivity : ActivityPubObject
|
||||
{
|
||||
[JsonPropertyName("actor")]
|
||||
public ActivityPubActor Actor { get; set; }
|
||||
|
||||
[JsonPropertyName("object")]
|
||||
public ActivityPubObject Object { get; set; }
|
||||
|
||||
[JsonPropertyName("target")]
|
||||
public ActivityPubObject Target { get; set; }
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public ActivityPubResult Result { get; set; }
|
||||
|
||||
[JsonPropertyName("origin")]
|
||||
public ActivityPubOrigin Origin { get; set; }
|
||||
|
||||
[JsonPropertyName("instrument")]
|
||||
public ActivityPubInstrument Instrument { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public bool NeedsObjectForInbox => Type is
|
||||
ObjectType.Create or
|
||||
ObjectType.Update or
|
||||
ObjectType.Delete or
|
||||
ObjectType.Follow or
|
||||
ObjectType.Add or
|
||||
ObjectType.Remove or
|
||||
ObjectType.Like or
|
||||
ObjectType.Block or
|
||||
ObjectType.Undo;
|
||||
[JsonIgnore]
|
||||
public bool NeedsTargetForInbox => Type is
|
||||
ObjectType.Add or
|
||||
ObjectType.Remove;
|
||||
}
|
||||
}
|
79
PrivaPub/Models/ActivityPub/ActivityPubActor.cs
Normal file
79
PrivaPub/Models/ActivityPub/ActivityPubActor.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using PrivaPub.Models.ActivityPub.Extra;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubActor))]
|
||||
public class ActivityPubActor : ActivityPubObject
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public new ObjectType? Type => ObjectType.Person;
|
||||
|
||||
[JsonPropertyName("inbox")]
|
||||
public string Inbox { get; set; }
|
||||
[JsonPropertyName("outbox")]
|
||||
public string Outbox { get; set; }
|
||||
|
||||
[JsonPropertyName("url")]
|
||||
public string ProfileURL { get; set; }
|
||||
[JsonPropertyName("preferredUsername")]
|
||||
public string PreferredUsername { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("summary")]
|
||||
public string Summary { get; set; }
|
||||
[JsonPropertyName("icon")]
|
||||
public ActivityPubIcon Icon { get; set; }
|
||||
[JsonPropertyName("image")]
|
||||
public ActivityPubIcon Thumbnail { get; set; }
|
||||
|
||||
[JsonPropertyName("manuallyApprovesFollowers")]
|
||||
public bool ManuallyApprovesFollowers { get; set; } = false;
|
||||
[JsonPropertyName("discoverable")]
|
||||
public bool Discoverable { get; set; } = true;
|
||||
|
||||
[JsonPropertyName("publicKey")]
|
||||
public ActivityPubPublicKey PublicKey { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("endpoints")]
|
||||
public ActivityPubActorEndpoints Endpoints { get; set; } = new();
|
||||
|
||||
[JsonPropertyName("attachment")]
|
||||
public IEnumerable<ActivityPubAttachment> Attachment { get; set; } = Enumerable.Empty<ActivityPubAttachment>();
|
||||
|
||||
[JsonPropertyName("tag")]
|
||||
public IEnumerable<string> Tags => Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
public class ActivityPubAttachment
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("value")]
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
public class ActivityPubActorEndpoints
|
||||
{
|
||||
[JsonPropertyName("sharedInbox")]
|
||||
public string SharedInboxURL { get; set; }
|
||||
[JsonPropertyName("oauthAuthorizationEndpoint")]
|
||||
public string OAuthAuthorizationEndpoint { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, object> OtherData { get; set; }
|
||||
}
|
||||
|
||||
public class ActivityPubPublicKey
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
[JsonPropertyName("owner")]
|
||||
public string Owner { get; set; }
|
||||
[JsonPropertyName("publicKeyPem")]
|
||||
public string PublicKeyPem { get; set; }
|
||||
}
|
||||
}
|
24
PrivaPub/Models/ActivityPub/ActivityPubCollection.cs
Normal file
24
PrivaPub/Models/ActivityPub/ActivityPubCollection.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubCollection))]
|
||||
public class ActivityPubCollection : ActivityPubObject
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public new ObjectType Type => ObjectType.Collection;
|
||||
|
||||
[JsonPropertyName("current")]
|
||||
public string RecentlyUpdatedItem { get; set; }
|
||||
[JsonPropertyName("first")]
|
||||
public string FirstItem { get; set; }
|
||||
[JsonPropertyName("last")]
|
||||
public string LastItem { get; set; }
|
||||
|
||||
[JsonPropertyName("items")]
|
||||
public List<ActivityPubLink> Items { get; set; }
|
||||
|
||||
[JsonPropertyName("totalItems")]
|
||||
public int TotalItems => Items.Count;
|
||||
}
|
||||
}
|
18
PrivaPub/Models/ActivityPub/ActivityPubCollectionPage.cs
Normal file
18
PrivaPub/Models/ActivityPub/ActivityPubCollectionPage.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubCollectionPage))]
|
||||
public class ActivityPubCollectionPage : ActivityPubCollection
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public new ObjectType Type => ObjectType.CollectionPage;
|
||||
|
||||
[JsonPropertyName("partOf")]
|
||||
public string BaseCollectionLink { get; set; }
|
||||
[JsonPropertyName("next")]
|
||||
public string NextCollectionLink { get; set; }
|
||||
[JsonPropertyName("prev")]
|
||||
public string PreviousCollectionLink { get; set; }
|
||||
}
|
||||
}
|
28
PrivaPub/Models/ActivityPub/ActivityPubLink.cs
Normal file
28
PrivaPub/Models/ActivityPub/ActivityPubLink.cs
Normal file
@ -0,0 +1,28 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubLink))]
|
||||
public class ActivityPubLink : ActivityPubObject
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public new ObjectType Type { get; set; } = ObjectType.Mention;
|
||||
|
||||
[JsonPropertyName("href")]
|
||||
public string Href { get; set; }
|
||||
[JsonPropertyName("preview")]
|
||||
public string Preview { get; set; }
|
||||
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("rel")]
|
||||
public string Relation { get; set; }
|
||||
[JsonPropertyName("hreflang")]
|
||||
public string HrefLang { get; set; }
|
||||
|
||||
[JsonPropertyName("height")]
|
||||
public int Height { get; set; }
|
||||
[JsonPropertyName("width")]
|
||||
public int Width { get; set; }
|
||||
}
|
||||
}
|
126
PrivaPub/Models/ActivityPub/ActivityPubObject.cs
Normal file
126
PrivaPub/Models/ActivityPub/ActivityPubObject.cs
Normal file
@ -0,0 +1,126 @@
|
||||
using PrivaPub.Models.ActivityPub.Extra;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubObject))]
|
||||
public partial class ActivityPubObject
|
||||
{
|
||||
[JsonPropertyName("@context")]
|
||||
public List<object> Context => new()
|
||||
{
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://{0}/schemas/litepub-0.1.jsonld",
|
||||
new ActivityPubContextLanguage()
|
||||
};
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
[JsonPropertyName("type")]
|
||||
public ObjectType? Type { get; set; }
|
||||
|
||||
[JsonPropertyName("mediaType")]
|
||||
public string MediaType { get; set; }
|
||||
|
||||
[JsonPropertyName("published")]
|
||||
public DateTime? Published { get; set; }
|
||||
|
||||
[JsonPropertyName("updated")]
|
||||
public DateTime? Updated { get; set; }
|
||||
|
||||
[JsonExtensionData]
|
||||
public Dictionary<string, object> OtherData { get; set; }
|
||||
|
||||
[JsonPropertyName("source")]
|
||||
public ActivityPubSource Source { get; set; }
|
||||
|
||||
//and part of link
|
||||
[JsonPropertyName("to")]
|
||||
public string[] To { get; set; }
|
||||
[JsonPropertyName("cc")]
|
||||
public string[] Cc { get; set; }
|
||||
[JsonPropertyName("bcc")]
|
||||
public string[] Bcc { get; set; }
|
||||
[JsonPropertyName("bto")]
|
||||
public string[] Bto { get; set; }
|
||||
|
||||
[JsonPropertyName("audience")]
|
||||
public ActivityPubAudience Audience { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
public MacroType MacroType => Type switch
|
||||
{
|
||||
ObjectType.Article or
|
||||
ObjectType.Audio or
|
||||
ObjectType.Document or
|
||||
ObjectType.Event or
|
||||
ObjectType.Image or
|
||||
ObjectType.Note or
|
||||
ObjectType.Page or
|
||||
ObjectType.Place or
|
||||
ObjectType.Profile or
|
||||
ObjectType.Relationship or
|
||||
ObjectType.Tombstone or
|
||||
ObjectType.Video => MacroType.Object,
|
||||
|
||||
ObjectType.Mention => MacroType.Link,
|
||||
|
||||
ObjectType.Application or
|
||||
ObjectType.Group or
|
||||
ObjectType.Organization or
|
||||
ObjectType.Person or
|
||||
ObjectType.Service => MacroType.Actor,
|
||||
|
||||
ObjectType.Accept or
|
||||
ObjectType.Add or
|
||||
ObjectType.Announce or
|
||||
ObjectType.Arrive or
|
||||
ObjectType.Block or
|
||||
ObjectType.Create or
|
||||
ObjectType.Delete or
|
||||
ObjectType.Dislike or
|
||||
ObjectType.Flag or
|
||||
ObjectType.Follow or
|
||||
ObjectType.Ignore or
|
||||
ObjectType.Invite or
|
||||
ObjectType.Join or
|
||||
ObjectType.Leave or
|
||||
ObjectType.Like or
|
||||
ObjectType.Listen or
|
||||
ObjectType.Move or
|
||||
ObjectType.Offer or
|
||||
ObjectType.Question or
|
||||
ObjectType.Reject or
|
||||
ObjectType.Read or
|
||||
ObjectType.Remove or
|
||||
ObjectType.TentativeReject or
|
||||
ObjectType.TentativeAccept or
|
||||
ObjectType.Travel or
|
||||
ObjectType.Undo or
|
||||
ObjectType.Update or
|
||||
ObjectType.View => MacroType.Activity,
|
||||
|
||||
ObjectType.Collection => MacroType.Collection,
|
||||
ObjectType.CollectionPage => MacroType.CollectionPage,
|
||||
ObjectType.OrderedCollection => MacroType.OrderedCollection,
|
||||
ObjectType.OrderedCollectionPage => MacroType.OrderedCollectionPage,
|
||||
|
||||
_ => MacroType.Unknown
|
||||
};
|
||||
}
|
||||
|
||||
public class ActivityPubContextLanguage
|
||||
{
|
||||
[JsonPropertyName("@language")]
|
||||
public string Language { get; set; } = "und";
|
||||
}
|
||||
|
||||
public class ActivityPubSource
|
||||
{
|
||||
[JsonPropertyName("content")]
|
||||
public string Content { get; set; }
|
||||
[JsonPropertyName("mediaType")]
|
||||
public string MediaType { get; set; }
|
||||
}
|
||||
}
|
17
PrivaPub/Models/ActivityPub/ActivityPubOrderedCollection.cs
Normal file
17
PrivaPub/Models/ActivityPub/ActivityPubOrderedCollection.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubOrderedCollection))]
|
||||
public class ActivityPubOrderedCollection : ActivityPubCollection
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public new ObjectType Type => ObjectType.OrderedCollection;
|
||||
|
||||
[JsonPropertyName("orderedItems")]
|
||||
public List<ActivityPubLink> OrderedItems { get; set; }
|
||||
|
||||
[JsonPropertyName("totalItems")]
|
||||
public new int TotalItems => OrderedItems?.Count ?? default;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubOrderedCollectionPage))]
|
||||
public class ActivityPubOrderedCollectionPage : ActivityPubCollection
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public new ObjectType Type => ObjectType.OrderedCollectionPage;
|
||||
|
||||
[JsonPropertyName("partOf")]
|
||||
public string BaseCollectionLink { get; set; }
|
||||
[JsonPropertyName("next")]
|
||||
public string NextCollectionLink { get; set; }
|
||||
[JsonPropertyName("prev")]
|
||||
public string PreviousCollectionLink { get; set; }
|
||||
|
||||
[JsonPropertyName("startIndex")]
|
||||
public uint? StartIndex { get; set; }
|
||||
|
||||
[JsonPropertyName("orderedItems")]
|
||||
public List<ActivityPubLink> OrderedItems { get; set; }
|
||||
|
||||
[JsonPropertyName("totalItems")]
|
||||
public new int TotalItems => OrderedItems?.Count ?? default;
|
||||
}
|
||||
}
|
14
PrivaPub/Models/ActivityPub/ActivityPubTombstone.cs
Normal file
14
PrivaPub/Models/ActivityPub/ActivityPubTombstone.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubTombstone))]
|
||||
public class ActivityPubTombstone : ActivityPubObject
|
||||
{
|
||||
[JsonPropertyName("formerType")]
|
||||
public ObjectType FormerType { get; set; } = ObjectType.Unknown;
|
||||
|
||||
[JsonPropertyName("deleted")]
|
||||
public DateTime Deleted { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubAudience.cs
Normal file
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubAudience.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub.Extra
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubPublicKey))]
|
||||
public class ActivityPubAudience
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
19
PrivaPub/Models/ActivityPub/Extra/ActivityPubIcon.cs
Normal file
19
PrivaPub/Models/ActivityPub/Extra/ActivityPubIcon.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub.Extra
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubPublicKey))]
|
||||
public class ActivityPubIcon
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
[JsonPropertyName("url")]
|
||||
public string URL { get; set; }
|
||||
[JsonPropertyName("width")]
|
||||
public int Width { get; set; }
|
||||
[JsonPropertyName("height")]
|
||||
public int Height { get; set; }
|
||||
}
|
||||
}
|
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs
Normal file
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubInstrument.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub.Extra
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubPublicKey))]
|
||||
public class ActivityPubInstrument
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs
Normal file
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubOrigin.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub.Extra
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubPublicKey))]
|
||||
public class ActivityPubOrigin
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubResult.cs
Normal file
13
PrivaPub/Models/ActivityPub/Extra/ActivityPubResult.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace PrivaPub.Models.ActivityPub.Extra
|
||||
{
|
||||
[JsonSerializable(typeof(ActivityPubPublicKey))]
|
||||
public class ActivityPubResult
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; }
|
||||
[JsonPropertyName("name")]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
17
PrivaPub/Models/ActivityPub/MacroType.cs
Normal file
17
PrivaPub/Models/ActivityPub/MacroType.cs
Normal file
@ -0,0 +1,17 @@
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
public enum MacroType
|
||||
{
|
||||
Object,
|
||||
Link,
|
||||
Actor,
|
||||
Activity,
|
||||
|
||||
Collection,
|
||||
OrderedCollection,
|
||||
CollectionPage,
|
||||
OrderedCollectionPage,
|
||||
|
||||
Unknown
|
||||
}
|
||||
}
|
63
PrivaPub/Models/ActivityPub/ObjectType.cs
Normal file
63
PrivaPub/Models/ActivityPub/ObjectType.cs
Normal file
@ -0,0 +1,63 @@
|
||||
namespace PrivaPub.Models.ActivityPub
|
||||
{
|
||||
public enum ObjectType
|
||||
{
|
||||
//Object types
|
||||
Article,
|
||||
Audio,
|
||||
Document,
|
||||
Event,
|
||||
Image,
|
||||
Note,
|
||||
Page,
|
||||
Place,
|
||||
Profile,
|
||||
Relationship,
|
||||
Tombstone,
|
||||
Video,
|
||||
//Link types
|
||||
Mention,
|
||||
//Actor
|
||||
Application,
|
||||
Group,
|
||||
Organization,
|
||||
Person,
|
||||
Service,
|
||||
//Activity
|
||||
Accept,
|
||||
Add,
|
||||
Announce,
|
||||
Arrive,
|
||||
Block,
|
||||
Create,
|
||||
Delete,
|
||||
Dislike,
|
||||
Flag,
|
||||
Follow,
|
||||
Ignore,
|
||||
Invite,
|
||||
Join,
|
||||
Leave,
|
||||
Like,
|
||||
Listen,
|
||||
Move,
|
||||
Offer,
|
||||
Question,
|
||||
Reject,
|
||||
Read,
|
||||
Remove,
|
||||
TentativeReject,
|
||||
TentativeAccept,
|
||||
Travel,
|
||||
Undo,
|
||||
Update,
|
||||
View,
|
||||
//Collections
|
||||
Collection,
|
||||
OrderedCollection,
|
||||
CollectionPage,
|
||||
OrderedCollectionPage,
|
||||
//NULL or default
|
||||
Unknown
|
||||
}
|
||||
}
|
21
PrivaPub/Models/AppConfiguration.cs
Normal file
21
PrivaPub/Models/AppConfiguration.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Models.Email;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
namespace PrivaPub.Models
|
||||
{
|
||||
public class AppConfiguration : Entity
|
||||
{
|
||||
public bool RequiresFirstTimeSetup { get; set; } = true;
|
||||
public string Version { get; set; }
|
||||
public int MaxAllowedUploadFiles { get; set; }
|
||||
public int MaxAllowedFileSize { get; set; }
|
||||
public string ValidDiscussionFilesTypes { get; set; }
|
||||
public List<string> SupportedLanguages { get; set; } = new();
|
||||
public string BackendBaseAddress { get; set; }
|
||||
public EmailConfiguration EmailConfiguration { get; set; }
|
||||
public HashingOptions HashingOptions { get; set; }
|
||||
public DateTime LastUpdateDate { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
11
PrivaPub/Models/Data/Language.cs
Normal file
11
PrivaPub/Models/Data/Language.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Data
|
||||
{
|
||||
public class Language : Entity
|
||||
{
|
||||
public string EnglishName { get; set; }
|
||||
public string NativeName { get; set; }
|
||||
public string International2Code { get; set; }
|
||||
}
|
||||
}
|
11
PrivaPub/Models/Email/EmailConfiguration.cs
Normal file
11
PrivaPub/Models/Email/EmailConfiguration.cs
Normal file
@ -0,0 +1,11 @@
|
||||
namespace PrivaPub.Models.Email
|
||||
{
|
||||
public class EmailConfiguration
|
||||
{
|
||||
public string SmtpServer { get; set; }
|
||||
public int SmtpPort { get; set; }
|
||||
public bool UseSSL { get; set; }
|
||||
public string SmtpUsername { get; set; }
|
||||
public string SmtpPassword { get; set; }
|
||||
}
|
||||
}
|
9
PrivaPub/Models/MongoSettings.cs
Normal file
9
PrivaPub/Models/MongoSettings.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace PrivaPub.Models
|
||||
{
|
||||
public class MongoSettings
|
||||
{
|
||||
public string Database { get; set; }
|
||||
public string LogsDatabase { get; set; }
|
||||
public string ConnectionString { get; set; }
|
||||
}
|
||||
}
|
22
PrivaPub/Models/Post/DmPost.cs
Normal file
22
PrivaPub/Models/Post/DmPost.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Post
|
||||
{
|
||||
public class DmPost : Entity
|
||||
{
|
||||
public string GroupUserId { get; set; }
|
||||
public string AnsweringToPostId { get; set; }
|
||||
public string GroupId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Text { get; set; }
|
||||
public List<PostMedia> Media { get; set; } = new();
|
||||
public List<float> Location { get; set; } = new();
|
||||
public bool HasContentWarning { get; set; } = false;
|
||||
|
||||
public bool IsFederatedCopy { get; set; }
|
||||
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? UpdateDate { get; set; } = DateTime.UtcNow;
|
||||
public string UpdateReason { get; set; }
|
||||
}
|
||||
}
|
23
PrivaPub/Models/Post/Post.cs
Normal file
23
PrivaPub/Models/Post/Post.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Post
|
||||
{
|
||||
public class Post : Entity
|
||||
{
|
||||
public string GroupUserId { get; set; }
|
||||
public string AnsweringToPostId { get; set; }
|
||||
public string GroupId { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Text { get; set; }
|
||||
public List<PostMedia> Media { get; set; } = new();
|
||||
public List<float> Location { get; set; } = new();
|
||||
public float RangeKm { get; set; } = 5.0f;
|
||||
public bool HasContentWarning { get; set; } = false;
|
||||
|
||||
public bool IsFederatedCopy { get; set; }
|
||||
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? UpdateDate { get; set; } = DateTime.UtcNow;
|
||||
public string UpdateReason { get; set; }
|
||||
}
|
||||
}
|
12
PrivaPub/Models/Post/PostBoost.cs
Normal file
12
PrivaPub/Models/Post/PostBoost.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace PrivaPub.Models.Post
|
||||
{
|
||||
public class PostBoost
|
||||
{
|
||||
public string PostId { get; set; }
|
||||
public string GroupUserId { get; set; }
|
||||
|
||||
public bool IsFederatedCopy { get; set; }
|
||||
|
||||
public DateTime CreationDate { get; set; }
|
||||
}
|
||||
}
|
12
PrivaPub/Models/Post/PostMedia.cs
Normal file
12
PrivaPub/Models/Post/PostMedia.cs
Normal file
@ -0,0 +1,12 @@
|
||||
namespace PrivaPub.Models.Post
|
||||
{
|
||||
public class PostMedia
|
||||
{
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
public string ContentType { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string Extension { get; set; }
|
||||
public string Path { get; set; }
|
||||
public string URL { get; set; }
|
||||
}
|
||||
}
|
116
PrivaPub/Models/User/Avatar.cs
Normal file
116
PrivaPub/Models/User/Avatar.cs
Normal file
@ -0,0 +1,116 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.User
|
||||
{
|
||||
public class Avatar : Entity
|
||||
{
|
||||
public string Url { get; set; }//url
|
||||
public string Name { get; set; }//name
|
||||
public string UserName { get; set; }//preferredUsername
|
||||
public string Biography { get; set; }//summary
|
||||
|
||||
public Dictionary<string, string> SharedPersonalContacts { get; set; } = new();
|
||||
public string PrivateKey { get; set; }
|
||||
public string PublicKey { get; set; }
|
||||
public AvatarAccountState AccountState { get; set; } = AvatarAccountState.Normal;
|
||||
public AvatarSettings Settings { get; set; } = new();
|
||||
public Dictionary<string, string> Fields { get; set; } = new();
|
||||
|
||||
public string Domain { get; set; }
|
||||
public string PersonalNote { get; set; }
|
||||
public string ModerationNote { get; set; }
|
||||
|
||||
public string InboxURL { get; set; }
|
||||
public string OutboxURL { get; set; }
|
||||
public string MovedToURL { get; set; }
|
||||
public string PictureURL { get; set; }//icon
|
||||
public string ThumbnailURL { get; set; }//image
|
||||
public AvatarType AvatarType { get; set; } = AvatarType.Person;
|
||||
public AvatarServer AvatarServer { get; set; } = AvatarServer.PrivaPub;
|
||||
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? SilencedAt { get; set; }
|
||||
public DateTime? SuspendedAt { get; set; }
|
||||
public DateTime? BannedAt { get; set; }
|
||||
public DateTime? DeletionAt { get; set; }
|
||||
}
|
||||
|
||||
public class ForeignAvatar : Entity
|
||||
{
|
||||
public string Url { get; set; }//url
|
||||
public string Name { get; set; }//name
|
||||
public string UserName { get; set; }//preferredUsername
|
||||
public string Biography { get; set; }//summary
|
||||
|
||||
public Dictionary<string, string> SharedPersonalContacts { get; set; } = new();
|
||||
public string PrivateKey { get; set; }
|
||||
public string PublicKey { get; set; }
|
||||
public AvatarAccountState AccountState { get; set; } = AvatarAccountState.Normal;
|
||||
public AvatarSettings Settings { get; set; } = new();
|
||||
public Dictionary<string, string> Fields { get; set; } = new();
|
||||
|
||||
public string Domain { get; set; }
|
||||
public string PersonalNote { get; set; }
|
||||
public string ModerationNote { get; set; }
|
||||
public bool IsDiscoverable { get; set; } = true;
|
||||
|
||||
public string InboxURL { get; set; }
|
||||
public string OutboxURL { get; set; }
|
||||
public string MovedToURL { get; set; }
|
||||
public string PictureURL { get; set; }//icon
|
||||
public string ThumbnailURL { get; set; }//image
|
||||
public AvatarType AvatarType { get; set; } = AvatarType.Person;
|
||||
public AvatarServer AvatarServer { get; set; } = AvatarServer.Unknown;
|
||||
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? SilencedAt { get; set; }
|
||||
public DateTime? SuspendedAt { get; set; }
|
||||
public DateTime? BannedAt { get; set; }
|
||||
public DateTime? DeletionAt { get; set; }
|
||||
}
|
||||
|
||||
public class AvatarSettings
|
||||
{
|
||||
public string LanguageCode { get; set; } = "en-GB";
|
||||
|
||||
public bool IsDefault { get; set; } = true;
|
||||
|
||||
public short IconsThemeIndexColour { get; set; } = 25;
|
||||
public short LightThemeIndexColour { get; set; } = 25;
|
||||
public short DarkThemeIndexColour { get; set; } = 215;
|
||||
public bool PreferSystemTheming { get; set; } = true;
|
||||
public bool ThemeIsDarkMode { get; set; } = false;
|
||||
public bool ThemeIsDarkGray { get; set; } = false;
|
||||
public bool ThemeIsLightGray { get; set; } = true;
|
||||
}
|
||||
|
||||
public enum AvatarServer
|
||||
{
|
||||
Unknown,
|
||||
Pleroma,
|
||||
Mastodon,
|
||||
Akkoma,
|
||||
Misskey,
|
||||
PrivaPub
|
||||
}
|
||||
|
||||
public enum AvatarType
|
||||
{
|
||||
Application,
|
||||
Group,
|
||||
Organization,
|
||||
Person,
|
||||
Service
|
||||
}
|
||||
|
||||
public enum AvatarAccountState
|
||||
{
|
||||
Normal,
|
||||
Silenced,
|
||||
Suspended,
|
||||
Banned,
|
||||
Deleted
|
||||
}
|
||||
}
|
12
PrivaPub/Models/User/EmailRecovery.cs
Normal file
12
PrivaPub/Models/User/EmailRecovery.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.User
|
||||
{
|
||||
public class EmailRecovery : Entity
|
||||
{
|
||||
public string RootUserId { get; set; }
|
||||
public string RecoveryCode { get; set; }
|
||||
public string RequestIP { get; set; }
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
10
PrivaPub/Models/User/RootToAvatar.cs
Normal file
10
PrivaPub/Models/User/RootToAvatar.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.User
|
||||
{
|
||||
public class RootToAvatar : Entity
|
||||
{
|
||||
public string RootId { get; set; }
|
||||
public string AvatarId { get; set; }
|
||||
}
|
||||
}
|
48
PrivaPub/Models/User/RootUser.cs
Normal file
48
PrivaPub/Models/User/RootUser.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace PrivaPub.Models.User
|
||||
{
|
||||
public class RootUser : Entity
|
||||
{
|
||||
[StringLength(32)]
|
||||
public string UserName { get; set; }
|
||||
[EmailAddress]
|
||||
public string Email { get; set; }
|
||||
public bool IsEmailValidated { get; set; } = false;
|
||||
public bool IsBanned { get; set; } = false;
|
||||
public string HashedPassword { get; set; }
|
||||
public List<string> Policies { get; set; } = new() { ClientModels.Policies.IsUser };
|
||||
public List<ContactItem> Contacts { get; set; } = new();
|
||||
public RootUserSettings Settings { get; set; } = new();
|
||||
|
||||
public string ResetPasswordToken { get; set; }
|
||||
public DateTime? ResetPasswordTokenSentAt { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime? DeletedAt { get; set; }
|
||||
}
|
||||
|
||||
public class ContactItem
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Contact { get; set; }
|
||||
public string Note { get; set; }
|
||||
public DateTime CreationDate { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
public class RootUserSettings
|
||||
{
|
||||
public string LanguageCode { get; set; } = "en-GB";
|
||||
|
||||
public short IconsThemeIndexColour { get; set; } = 25;
|
||||
public short LightThemeIndexColour { get; set; } = 25;
|
||||
public short DarkThemeIndexColour { get; set; } = 215;
|
||||
public bool PreferSystemTheming { get; set; } = true;
|
||||
public bool ThemeIsDarkMode { get; set; } = false;
|
||||
public bool ThemeIsDarkGray { get; set; } = false;
|
||||
public bool ThemeIsLightGray { get; set; } = true;
|
||||
}
|
||||
}
|
14
PrivaPub/Models/User/RootUserNote.cs
Normal file
14
PrivaPub/Models/User/RootUserNote.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.User
|
||||
{
|
||||
public class RootUserNote : Entity
|
||||
{
|
||||
public string RootUserId { get; set; }
|
||||
public string AvatarUserId { get; set; }
|
||||
public string Note { get; set; }
|
||||
|
||||
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
}
|
||||
}
|
9
PrivaPub/Models/User/UserPolicyType.cs
Normal file
9
PrivaPub/Models/User/UserPolicyType.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace PrivaPub.Models.User
|
||||
{
|
||||
public enum UserPolicyType
|
||||
{
|
||||
IsUser,
|
||||
IsModerator,
|
||||
IsAdmin
|
||||
}
|
||||
}
|
24
PrivaPub/Models/socialpub.cs
Normal file
24
PrivaPub/Models/socialpub.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using MongoDB.Bson.Serialization.Attributes;
|
||||
using MongoDB.Entities;
|
||||
|
||||
namespace PrivaPub.Models.Data
|
||||
{
|
||||
[BsonIgnoreExtraElements]
|
||||
public class PrivaPub : Entity
|
||||
{
|
||||
public DateTime Timestamp { get; set; }
|
||||
public string Level { get; set; }
|
||||
public string RenderedMessage { get; set; }
|
||||
|
||||
[BsonIgnoreIfDefault, BsonIgnoreIfNull]
|
||||
public DbLogProperties Properties { get; set; }
|
||||
[BsonIgnoreIfDefault, BsonIgnoreIfNull]
|
||||
public string Exception { get; set; }
|
||||
}
|
||||
|
||||
[BsonIgnoreExtraElements]
|
||||
public class DbLogProperties
|
||||
{
|
||||
public string SourceContext { get; set; }
|
||||
}
|
||||
}
|
56
PrivaPub/PrivaPub.csproj
Normal file
56
PrivaPub/PrivaPub.csproj
Normal file
@ -0,0 +1,56 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<!--<Nullable>enable</Nullable>-->
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MailKit" Version="3.5.0" />
|
||||
<PackageReference Include="Markdig" Version="0.30.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Cors" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Options" Version="7.0.1" />
|
||||
<PackageReference Include="MongoDB.Entities" Version="21.0.2" />
|
||||
<PackageReference Include="NSign.Abstractions" Version="0.16.0" />
|
||||
<PackageReference Include="NSign.AspNetCore" Version="0.16.0" />
|
||||
<PackageReference Include="NSign.Client" Version="0.16.0" />
|
||||
<PackageReference Include="NSign.SignatureProviders" Version="0.16.0" />
|
||||
<PackageReference Include="PasswordGenerator" Version="2.1.0" />
|
||||
<PackageReference Include="Serilog.Extensions.Hosting" Version="5.0.1" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
|
||||
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
|
||||
<PackageReference Include="Serilog.Sinks.MongoDB" Version="5.3.1" />
|
||||
<PackageReference Include="SshKeyGenerator" Version="1.1.51" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Helpers\" />
|
||||
<Folder Include="Services\ServerToServer\" />
|
||||
<Folder Include="Workers\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\SocialPub.ClientModels\PrivaPub.ClientModels.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources\GenericRes.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>GenericRes.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources\GenericRes.resx">
|
||||
<Generator>PublicResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>GenericRes.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
142
PrivaPub/Program.cs
Normal file
142
PrivaPub/Program.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using MongoDB.Driver;
|
||||
using MongoDB.Entities;
|
||||
|
||||
using Serilog;
|
||||
|
||||
using PrivaPub.Data;
|
||||
using PrivaPub.Extensions;
|
||||
using PrivaPub.Middleware;
|
||||
using PrivaPub.Models;
|
||||
using PrivaPub.Models.Data;
|
||||
using PrivaPub.Services;
|
||||
using PrivaPub.StaticServices;
|
||||
|
||||
try
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.WebHost.ConfigureKestrel(serverOptions =>
|
||||
{
|
||||
if (builder.Environment.IsProduction())
|
||||
{
|
||||
serverOptions.ListenLocalhost(6970
|
||||
//, options =>
|
||||
//{
|
||||
// options.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
|
||||
//}
|
||||
);
|
||||
serverOptions.UseSystemd();
|
||||
serverOptions.AddServerHeader = false;
|
||||
}
|
||||
});
|
||||
builder.Host.UseSerilog((context, config) =>
|
||||
{
|
||||
config.ReadFrom.Configuration(context.Configuration);
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
builder.Services.PrivaPubAppSettingsConfiguration(builder.Configuration)
|
||||
.PrivaPubWorkersConfiguration()
|
||||
.PrivaPubAuthServicesConfiguration(builder.Configuration)
|
||||
.PrivaPubInternalizationConfiguration(builder.Configuration)
|
||||
.PrivaPubOptimizationConfiguration()
|
||||
.PrivaPubDataBaseConfiguration()
|
||||
.PrivaPubServicesConfiguration()
|
||||
.PrivaPubHTTPSignature(builder.Configuration)
|
||||
.PrivaPubCORSConfiguration()
|
||||
.PrivaPubMiddlewareConfiguration();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal(ex, "{0}.{1}()", nameof(Program), "ConfigureServices");
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var mongoSettings = builder.Configuration.GetSection(nameof(MongoSettings)).Get<MongoSettings>();
|
||||
await DB.InitAsync(mongoSettings.Database, MongoClientSettings.FromConnectionString(mongoSettings.ConnectionString));
|
||||
var logsConnectionString = builder.Configuration.GetLogsConnectionString();
|
||||
await DB.InitAsync(mongoSettings.LogsDatabase, MongoClientSettings.FromConnectionString(logsConnectionString));
|
||||
DB.DatabaseFor<PrivaPub>(mongoSettings.LogsDatabase);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal(ex, $"{nameof(Program)}.{nameof(Program)}() DB Instantiation");
|
||||
throw;
|
||||
}
|
||||
|
||||
var app = default(WebApplication);
|
||||
try
|
||||
{
|
||||
app = builder.Build();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal(ex, "{0}.{1}()", nameof(Program), "Build");
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var localizationService = app.Services.GetService<RequestLocalizationOptionsService>();
|
||||
if (app.Environment.IsProduction())
|
||||
{
|
||||
app.UseResponseCompression();
|
||||
app.UseForwardedHeaders(new()
|
||||
{
|
||||
ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
|
||||
});
|
||||
}
|
||||
else if(app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseSwagger();
|
||||
app.UseSwaggerUI();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseCors("DefaultCORS");
|
||||
|
||||
app.UseStaticFiles();
|
||||
|
||||
app.UseRequestLocalization(await localizationService.Get());
|
||||
|
||||
app.UseRouting();
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
//app.UseWhen(context => context.Request.Path.StartsWithSegments("/peasants") ||
|
||||
// context.Request.Path.StartsWithSegments("/users"),
|
||||
// app => app.UseSignatureVerification().UseDigestVerification());
|
||||
|
||||
app.MapControllers();
|
||||
//app.MapFallbackToFile("index.html");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal(ex, "{0}.{1}()", nameof(Program), "Use");
|
||||
throw;
|
||||
}
|
||||
|
||||
Log.ForContext<Program>().Information($"Starting collAnon at {nameof(Program)}()");
|
||||
try
|
||||
{
|
||||
var dbClient = app.Services.GetService(typeof(DbEntities)) as DbEntities;
|
||||
var passwordHasher = app.Services.GetService(typeof(IPasswordHasher)) as IPasswordHasher;
|
||||
await dbClient.Init(passwordHasher);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<Program>().Warning(ex, $"{nameof(Program)}.{nameof(Program)}() DB Init");
|
||||
}
|
||||
|
||||
await app.RunAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.ForContext<Program>().Fatal(ex, $"{nameof(Program)}.{nameof(Program)}()");
|
||||
}
|
||||
|
41
PrivaPub/Properties/launchSettings.json
Normal file
41
PrivaPub/Properties/launchSettings.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:55406",
|
||||
"sslPort": 44347
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "http://localhost:5293",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"applicationUrl": "https://localhost:7195;http://localhost:5293",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "swagger",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
81
PrivaPub/Resources/GenericRes.Designer.cs
generated
Normal file
81
PrivaPub/Resources/GenericRes.Designer.cs
generated
Normal file
@ -0,0 +1,81 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace PrivaPub.Resources {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
public class GenericRes {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal GenericRes() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("PrivaPub.Resources.GenericRes", typeof(GenericRes).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
public static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Forbidden: {0}.
|
||||
/// </summary>
|
||||
public static string Forbidden___0_ {
|
||||
get {
|
||||
return ResourceManager.GetString("Forbidden: {0}", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Unauthorized: {0}.
|
||||
/// </summary>
|
||||
public static string Unauthorized___0_ {
|
||||
get {
|
||||
return ResourceManager.GetString("Unauthorized: {0}", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
126
PrivaPub/Resources/GenericRes.resx
Normal file
126
PrivaPub/Resources/GenericRes.resx
Normal file
@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Forbidden: {0}" xml:space="preserve">
|
||||
<value>Forbidden: {0}</value>
|
||||
</data>
|
||||
<data name="Unauthorized: {0}" xml:space="preserve">
|
||||
<value>Unauthorized: {0}</value>
|
||||
</data>
|
||||
</root>
|
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
55
PrivaPub/StaticServices/AuthTokenManager.cs
Normal file
55
PrivaPub/StaticServices/AuthTokenManager.cs
Normal file
@ -0,0 +1,55 @@
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using PrivaPub.ClientModels;
|
||||
using PrivaPub.ClientModels.User;
|
||||
using PrivaPub.Models.User;
|
||||
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
|
||||
namespace PrivaPub.StaticServices
|
||||
{
|
||||
public class AuthTokenManager
|
||||
{
|
||||
readonly IConfiguration Configuration;
|
||||
|
||||
public AuthTokenManager(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public JwtUser GenerateToken(RootUser user, ViewAvatarServer userSettings)
|
||||
{
|
||||
var expiration = DateTime.UtcNow.AddHours(int.Parse(Configuration["AppConfiguration:Jwt:HoursTimeout"]));
|
||||
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["AppConfiguration:Jwt:Key"]));
|
||||
var jwtUser = new JwtUser
|
||||
{
|
||||
UserId = user.ID,
|
||||
Email = user.Email,
|
||||
Username = user.UserName,
|
||||
Expiration = expiration.Ticks,
|
||||
Policies = user.Policies,
|
||||
UserSettings = userSettings
|
||||
};
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ClaimTypes.UserData, user.ID),
|
||||
new(ClaimTypes.Name, user.UserName)
|
||||
};
|
||||
|
||||
claims.Add(new(Policies.IsUser, $"{user.Policies.Contains(Policies.IsUser)}".ToLower()));
|
||||
claims.Add(new(Policies.IsModerator, $"{user.Policies.Contains(Policies.IsModerator)}".ToLower()));
|
||||
claims.Add(new(Policies.IsAdmin, $"{user.Policies.Contains(Policies.IsAdmin)}".ToLower()));
|
||||
|
||||
var token = new JwtSecurityToken(issuer: Configuration["AppConfiguration:Jwt:Issuer"], audience: Configuration["AppConfiguration:Jwt:Audience"],
|
||||
claims: claims,
|
||||
expires: expiration,
|
||||
signingCredentials: new(securityKey, SecurityAlgorithms.HmacSha512)
|
||||
);
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
jwtUser.Token = tokenHandler.WriteToken(token);
|
||||
return jwtUser;
|
||||
}
|
||||
}
|
||||
}
|
27
PrivaPub/StaticServices/DbEntities.cs
Normal file
27
PrivaPub/StaticServices/DbEntities.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using MongoDB.Entities;
|
||||
|
||||
using PrivaPub.Models;
|
||||
using PrivaPub.Models.Data;
|
||||
using PrivaPub.Models.Post;
|
||||
using PrivaPub.Models.User;
|
||||
|
||||
namespace PrivaPub.StaticServices
|
||||
{
|
||||
public class DbEntities
|
||||
{
|
||||
public Find<RootUser> RootUsers { get { return DB.Find<RootUser>(); } }
|
||||
public Find<RootUserNote> RootUserNotes { get { return DB.Find<RootUserNote>(); } }
|
||||
|
||||
public Find<AppConfiguration> AppConfiguration { get { return DB.Find<AppConfiguration>(); } }
|
||||
public Find<Language> Languages { get { return DB.Find<Language>(); } }
|
||||
public Find<EmailRecovery> EmailRecoveries { get { return DB.Find<EmailRecovery>(); } }
|
||||
|
||||
public Find<Post> Posts { get { return DB.Find<Post>(); } }
|
||||
public Find<DmPost> DmPosts { get { return DB.Find<DmPost>(); } }
|
||||
|
||||
public Find<Avatar> Avatars { get { return DB.Find<Avatar>(); } }
|
||||
public Find<ForeignAvatar> ForeignAvatars { get { return DB.Find<ForeignAvatar>(); } }
|
||||
|
||||
public Find<RootToAvatar> RootToAvatars { get { return DB.Find<RootToAvatar>(); } }
|
||||
}
|
||||
}
|
9
PrivaPub/StaticServices/IPasswordHasher.cs
Normal file
9
PrivaPub/StaticServices/IPasswordHasher.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace PrivaPub.StaticServices
|
||||
{
|
||||
public interface IPasswordHasher
|
||||
{
|
||||
string Hash(string password);
|
||||
|
||||
(bool verified, bool needsUpgrade) Check(string hash, string password);
|
||||
}
|
||||
}
|
54
PrivaPub/StaticServices/PasswordHasher.cs
Normal file
54
PrivaPub/StaticServices/PasswordHasher.cs
Normal file
@ -0,0 +1,54 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
using System.Security.Cryptography;
|
||||
|
||||
namespace PrivaPub.StaticServices
|
||||
{
|
||||
public sealed class PasswordHasher : IPasswordHasher
|
||||
{
|
||||
private const int SaltSize = 16;
|
||||
private const int KeySize = 32;
|
||||
private HashingOptions Options { get; }
|
||||
|
||||
public PasswordHasher(IOptionsMonitor<HashingOptions> options)
|
||||
{
|
||||
Options = options.CurrentValue;
|
||||
}
|
||||
|
||||
public (bool verified, bool needsUpgrade) Check(string hash, string password)
|
||||
{
|
||||
var parts = hash.Split('~', 3);
|
||||
|
||||
if (parts.Length != 3)
|
||||
throw new FormatException("Unexpected hash format.");
|
||||
|
||||
var iterations = Convert.ToInt32(parts[2]);
|
||||
var salt = Convert.FromBase64String(parts[1]);
|
||||
var key = Convert.FromBase64String(parts[0]);
|
||||
|
||||
var needsUpgrade = iterations != Options.Iterations;
|
||||
|
||||
using (var algorithm = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA512))
|
||||
{
|
||||
var keyToCheck = algorithm.GetBytes(KeySize);
|
||||
var verified = keyToCheck.SequenceEqual(key);
|
||||
return (verified, needsUpgrade);
|
||||
}
|
||||
}
|
||||
|
||||
public string Hash(string password)
|
||||
{
|
||||
using (var algorithm = new Rfc2898DeriveBytes(password, SaltSize, Options.Iterations, HashAlgorithmName.SHA512))
|
||||
{
|
||||
var key = Convert.ToBase64String(algorithm.GetBytes(KeySize));
|
||||
var salt = Convert.ToBase64String(algorithm.Salt);
|
||||
return $"{key}~{salt}~{Options.Iterations}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class HashingOptions
|
||||
{
|
||||
public int Iterations { get; set; } = 9789;
|
||||
}
|
||||
}
|
97
PrivaPub/appsettings.Development.json
Normal file
97
PrivaPub/appsettings.Development.json
Normal file
@ -0,0 +1,97 @@
|
||||
{
|
||||
"MongoSettings": {
|
||||
"Database": "PrivaPub",
|
||||
"LogsDatabase": "logs",
|
||||
"ConnectionString": "mongodb://localhost:27017"
|
||||
},
|
||||
"AppConfiguration": {
|
||||
"RequiresFirstTimeSetup": null,
|
||||
"Version": "0.0.0",
|
||||
"MaxAllowedUploadFiles": 3,
|
||||
"MaxAllowedFileSize": 2097152,
|
||||
"ValidDiscussionFilesTypes": ".odt,.docx,.pdf,.xlsx,.ods,.odp,.pptx,.png,.jpg,.jpeg",
|
||||
"SupportedLanguages": [
|
||||
"en",
|
||||
"it",
|
||||
"de",
|
||||
"fr",
|
||||
"es",
|
||||
"ja",
|
||||
"ru",
|
||||
"zh",
|
||||
"bg",
|
||||
"cs",
|
||||
"da",
|
||||
"nl",
|
||||
"et",
|
||||
"fi",
|
||||
"el",
|
||||
"hu",
|
||||
"lv",
|
||||
"lt",
|
||||
"pl",
|
||||
"pt",
|
||||
"ro",
|
||||
"sk",
|
||||
"sl",
|
||||
"sv"
|
||||
],
|
||||
"BackendBaseAddress": "https://localhost:7195",
|
||||
"EmailConfiguration": {
|
||||
"SmtpServer": "mail.privateemail.com",
|
||||
"SmtpPort": 465,
|
||||
"UseSSL": true,
|
||||
"SmtpUsername": "support@collanon.app",
|
||||
"SmtpPassword": "c4kXUJFQeKC2dVQbZqxZ"
|
||||
},
|
||||
"HashingOptions": {
|
||||
"Iterations": 10101
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "ITNN8mPfS2ivOqr1eRWK0Rac3sRAchQdG8BUy0pK4vQ3",
|
||||
"Issuer": "https://localhost",
|
||||
"Audience": "https://localhost",
|
||||
"HoursTimeout": 24
|
||||
}
|
||||
},
|
||||
"Kestrel": {
|
||||
"Endpoints": {
|
||||
"Http": {
|
||||
"Url": "https://0.0.0.0:7195",
|
||||
"Protocols": "Http2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Debug",
|
||||
"Override": {
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "MongoDB",
|
||||
"Args": {
|
||||
"databaseUrl": "mongodb://localhost:27017/logs",
|
||||
"collectionName": "collanonquick",
|
||||
"cappedMaxSizeMb": "1024",
|
||||
"cappedMaxDocuments": "10000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
|
||||
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
65
PrivaPub/appsettings.Production.json
Normal file
65
PrivaPub/appsettings.Production.json
Normal file
@ -0,0 +1,65 @@
|
||||
{
|
||||
"MongoSettings": {
|
||||
"Database": "PrivaPub",
|
||||
"LogsDatabase": "logs",
|
||||
"ConnectionString": "mongodb://localhost:27017"
|
||||
},
|
||||
"AppConfiguration": {
|
||||
"Version": "0.0.0",
|
||||
"MaxAllowedUploadFiles": 3,
|
||||
"MaxAllowedFileSize": 2097152,
|
||||
"ValidDiscussionFilesTypes": ".odt,.docx,.pdf,.xlsx,.ods,.odp,.pptx,.png,.jpg,.jpeg",
|
||||
"SupportedLanguages": [
|
||||
"en", "it", "de", "fr", "es", "ja", "ru", "zh", "bg", "cs", "da", "nl", "et", "fi", "el", "hu", "lv", "lt", "pl", "pt", "ro", "sk", "sl", "sv"
|
||||
],
|
||||
"EmailConfiguration": {
|
||||
"SmtpServer": "mail.privateemail.com",
|
||||
"SmtpPort": 465,
|
||||
"UseSSL": true,
|
||||
"SmtpUsername": "support@collanon.app",
|
||||
"SmtpPassword": "c4kXUJFQeKC2dVQbZqxZ"
|
||||
},
|
||||
"HashingOptions": {
|
||||
"Iterations": 10101
|
||||
},
|
||||
"Jwt": {
|
||||
"Key": "ITNN8mPfS2ivOqr1eRWK0Rac3sRAchQdG8BUy0pK4vQ3",
|
||||
"Issuer": "https://localhost",
|
||||
"Audience": "https://localhost",
|
||||
"HoursTimeout": 365
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Serilog": {
|
||||
"MinimumLevel": {
|
||||
"Default": "Information",
|
||||
"Override": {
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"WriteTo": [
|
||||
{
|
||||
"Name": "MongoDB",
|
||||
"Args": {
|
||||
"databaseUrl": "mongodb://localhost:27017/logs",
|
||||
"collectionName": "PrivaPub",
|
||||
"cappedMaxSizeMb": "1024",
|
||||
"cappedMaxDocuments": "10000"
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "Console",
|
||||
"Args": {
|
||||
"theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
|
||||
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} <s:{SourceContext}>{NewLine}{Exception}"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
9
PrivaPub/appsettings.json
Normal file
9
PrivaPub/appsettings.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
Reference in New Issue
Block a user