2023-02-18 08:52:17 +01:00
|
|
|
|
using Microsoft.AspNetCore.Authentication;
|
|
|
|
|
using Microsoft.AspNetCore.Authorization;
|
|
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
using Microsoft.Extensions.Localization;
|
|
|
|
|
|
2023-02-19 00:43:43 +01:00
|
|
|
|
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;
|
2023-02-18 08:52:17 +01:00
|
|
|
|
|
|
|
|
|
using System.ComponentModel.DataAnnotations;
|
|
|
|
|
|
2023-02-19 00:43:43 +01:00
|
|
|
|
namespace PrivaPub.Controllers.ClientToServer
|
2023-02-18 08:52:17 +01:00
|
|
|
|
{
|
|
|
|
|
[ApiController,
|
|
|
|
|
Route("clientapi/user")]
|
2023-02-19 00:43:43 +01:00
|
|
|
|
public class RootUserController : ControllerBase
|
2023-02-18 08:52:17 +01:00
|
|
|
|
{
|
|
|
|
|
readonly IRootUsersService UsersService;
|
|
|
|
|
readonly AuthTokenManager AuthTokenManager;
|
2023-02-19 00:43:43 +01:00
|
|
|
|
readonly ILogger<RootUserController> Logger;
|
2023-02-18 08:52:17 +01:00
|
|
|
|
readonly IStringLocalizer Localizer;
|
|
|
|
|
|
2023-02-19 00:43:43 +01:00
|
|
|
|
public RootUserController(IRootUsersService usersService,
|
2023-02-18 08:52:17 +01:00
|
|
|
|
AuthTokenManager authTokenManager,
|
|
|
|
|
IStringLocalizer<GenericRes> localizer,
|
2023-02-19 00:43:43 +01:00
|
|
|
|
ILogger<RootUserController> logger)
|
2023-02-18 08:52:17 +01:00
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
|
2023-02-19 00:43:43 +01:00
|
|
|
|
(var user, var userSettings) = ((RootUser, ViewAvatarServer))result.Data;
|
2023-02-18 08:52:17 +01:00
|
|
|
|
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) =
|
2023-02-19 00:43:43 +01:00
|
|
|
|
((RootUser, ViewAvatarServer))result.Data;
|
2023-02-18 08:52:17 +01:00
|
|
|
|
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)]
|
2023-02-19 00:43:43 +01:00
|
|
|
|
public async Task<IActionResult> UpdateUserSettings(ViewAvatarServer userSettings)
|
2023-02-18 08:52:17 +01:00
|
|
|
|
{
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|