using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using PrivaPub.ClientModels; using PrivaPub.ClientModels.Resources; using PrivaPub.ClientModels.Group; using PrivaPub.ClientModels.User; using PrivaPub.ClientModels.User.Avatar; using PrivaPub.Extensions; using PrivaPub.Models; using PrivaPub.Models.User; using PrivaPub.Resources; using PrivaPub.Services; using PrivaPub.Services.ClientToServer.Private; using PrivaPub.StaticServices; using System.ComponentModel.DataAnnotations; namespace PrivaPub.Controllers.ClientToServer { [ApiController, Route("clientapi/user")] public class RootUserController : ControllerBase { readonly IRootUsersService UsersService; readonly IGroupUsersService GroupUsersService; readonly IPrivateAvatarUsersService AvatarUsersService; readonly AuthTokenManager AuthTokenManager; readonly IOptionsMonitor AppConfiguration; readonly ILogger Logger; readonly IStringLocalizer Localizer; public RootUserController(IRootUsersService usersService, IGroupUsersService groupUsersService, IPrivateAvatarUsersService avatarUsersService, AuthTokenManager authTokenManager, IOptionsMonitor appConfiguration, IStringLocalizer localizer, ILogger logger) { UsersService = usersService; GroupUsersService = groupUsersService; AvatarUsersService = avatarUsersService; AuthTokenManager = authTokenManager; AppConfiguration = appConfiguration; Localizer = localizer; Logger = logger; } #region User endpoints [HttpPost, Route("/clientapi/user/signup"), Authorize(Policy = Policies.IsUser), AllowAnonymous] public async Task 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, ViewUserSettings))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 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, ViewUserSettings))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, Route("/clientapi/user/invitation/signup"), AllowAnonymous] public async Task InvitationSignUp(InvitationLoginForm signUpForm, CancellationToken token) { var result = new WebResult(); if (!ModelState.IsValid) return BadRequest(result.Invalidate(Localizer["Invalid model."])); try { var invitation = await GroupUsersService.GetInvitation(signUpForm.InvitationCode, signUpForm.InvitationPassword, token); if (!invitation.IsValid) return StatusCode(invitation.StatusCode, invitation); result = await UsersService.SignUpAsync(signUpForm, signUpForm.InvitationCode); if (!result.IsValid) return StatusCode(result.StatusCode, result); var (user, userSettings) = ((RootUser, ViewUserSettings))result.Data; var avatar = await AvatarUsersService.InsertAvatar(new InsertAvatarForm { RootId = user.ID, Name = user.UserName, UserName = user.UserName, Biography = string.Empty }); if (!avatar.IsValid) return StatusCode(avatar.StatusCode, avatar); var joined = await GroupUsersService.JoinGroup(user.ID, new JoinGroupForm { AvatarId = ((ViewAvatar)avatar.Data).Id, InvitationCode = signUpForm.InvitationCode, InvitationPassword = signUpForm.InvitationPassword }, token); if (!joined.IsValid) return StatusCode(joined.StatusCode, joined); Logger.LogInformation( $"{nameof(InvitationSignUp)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]"); return Ok(AuthTokenManager.GenerateToken(user, userSettings)); } catch (Exception ex) { Logger.LogError(ex, $"{nameof(User)}.{nameof(InvitationSignUp)}()"); return BadRequest(result.Invalidate(ex.Message)); } } [HttpPost, Route("/clientapi/user/invitation/login"), AllowAnonymous] public async Task InvitationLogin(InvitationLoginForm loginForm, CancellationToken token) { var result = new WebResult(); if (!ModelState.IsValid) return BadRequest(result.Invalidate(Localizer["Invalid model."])); try { var invitation = await GroupUsersService.GetInvitation(loginForm.InvitationCode, loginForm.InvitationPassword, token); if (!invitation.IsValid) return StatusCode(invitation.StatusCode, invitation); result = await UsersService.LoginAsync(loginForm, loginForm.InvitationCode); if (!result.IsValid) return StatusCode(result.StatusCode, result); var (user, userSettings) = ((RootUser, ViewUserSettings))result.Data; var avatars = await AvatarUsersService.GetRootAvatars(user.ID, token); var avatar = (avatars.Data as List)?.FirstOrDefault(); if (avatar == default) { var inserted = await AvatarUsersService.InsertAvatar(new InsertAvatarForm { RootId = user.ID, Name = user.UserName, UserName = user.UserName, Biography = string.Empty }); if (!inserted.IsValid) return StatusCode(inserted.StatusCode, inserted); avatar = (ViewAvatar)inserted.Data; } var joined = await GroupUsersService.JoinGroup(user.ID, new JoinGroupForm { AvatarId = avatar.Id, InvitationCode = loginForm.InvitationCode, InvitationPassword = loginForm.InvitationPassword }, token); if (!joined.IsValid) return StatusCode(joined.StatusCode, joined); Logger.LogInformation( $"{nameof(InvitationLogin)}();IP:[{HttpContext.Connection?.RemoteIpAddress}];\nUser-Agent:[{Request.Headers["User-Agent"]}];\nUserId:[{user.ID}]"); return Ok(AuthTokenManager.GenerateToken(user, userSettings)); } 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 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 UpdateUserSettings(ViewUserSettings 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 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 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 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 RecoverPassword(PasswordRecoveryForm passwordRecoveryForm) { var result = new WebResult(); if (!ModelState.IsValid) return BadRequest(result.Invalidate(Localizer["Invalid model."])); try { var host = string.IsNullOrEmpty(AppConfiguration.CurrentValue.FrontendBaseAddress) ? $"{Request.Scheme}://{Request.Host.Host}" : AppConfiguration.CurrentValue.FrontendBaseAddress.TrimEnd('/'); 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 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 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 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 SniffAgain() { return Ok(); } #endregion } }