diff --git a/Notesnook.API/Controllers/UsersController.cs b/Notesnook.API/Controllers/UsersController.cs
index 647e110..a4df0f8 100644
--- a/Notesnook.API/Controllers/UsersController.cs
+++ b/Notesnook.API/Controllers/UsersController.cs
@@ -18,35 +18,23 @@ along with this program. If not, see .
*/
using System;
-using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Http.Timeouts;
using Microsoft.AspNetCore.Mvc;
using Notesnook.API.Interfaces;
+using Notesnook.API.Models;
using Notesnook.API.Models.Responses;
using Streetwriters.Common;
-using Streetwriters.Common.Extensions;
-using Streetwriters.Common.Models;
namespace Notesnook.API.Controllers
{
[ApiController]
[Authorize]
[Route("users")]
- public class UsersController : ControllerBase
+ public class UsersController(IUserService UserService) : ControllerBase
{
- private readonly HttpClient httpClient;
- private readonly IHttpContextAccessor HttpContextAccessor;
- private IUserService UserService { get; set; }
- public UsersController(IUserService userService, IHttpContextAccessor accessor)
- {
- httpClient = new HttpClient();
- HttpContextAccessor = accessor;
- UserService = userService;
- }
-
[HttpPost]
[AllowAnonymous]
public async Task Signup()
@@ -66,20 +54,35 @@ namespace Notesnook.API.Controllers
[HttpGet]
public async Task GetUser()
{
- UserResponse response = await UserService.GetUserAsync();
- if (!response.Success) return BadRequest(response);
- return Ok(response);
+ var userId = User.FindFirstValue("sub");
+ try
+ {
+ UserResponse response = await UserService.GetUserAsync(userId);
+ if (!response.Success) return BadRequest(response);
+ return Ok(response);
+ }
+ catch (Exception ex)
+ {
+ await Slogger.Error(nameof(GetUser), "Couldn't get user for id.", userId, ex.ToString());
+ return BadRequest(new { error = ex.Message });
+ }
}
[HttpPatch]
public async Task UpdateUser([FromBody] UserResponse user)
{
- UserResponse response = await UserService.GetUserAsync(false);
-
- if (user.AttachmentsKey != null)
- await UserService.SetUserAttachmentsKeyAsync(response.UserId, user.AttachmentsKey);
-
- return Ok();
+ var userId = User.FindFirstValue("sub");
+ try
+ {
+ if (user.AttachmentsKey != null)
+ await UserService.SetUserAttachmentsKeyAsync(userId, user.AttachmentsKey);
+ return Ok();
+ }
+ catch (Exception ex)
+ {
+ await Slogger.Error(nameof(GetUser), "Couldn't update user with id.", userId, ex.ToString());
+ return BadRequest(new { error = ex.Message });
+ }
}
[HttpPost("reset")]
@@ -93,24 +96,20 @@ namespace Notesnook.API.Controllers
}
[HttpPost("delete")]
- public async Task Delete()
+ [RequestTimeout(5 * 60 * 1000)]
+ public async Task Delete([FromForm] DeleteAccountForm form)
{
+ var userId = this.User.FindFirstValue("sub");
+ var jti = User.FindFirstValue("jti");
try
{
- var userId = this.User.FindFirstValue("sub");
-
- if (await UserService.DeleteUserAsync(userId, User.FindFirstValue("jti")))
- {
- Response response = await this.httpClient.ForwardAsync(HttpContextAccessor, $"{Servers.IdentityServer}/account/unregister", HttpMethod.Post);
- if (!response.Success) return BadRequest();
-
- return Ok();
- }
- return BadRequest();
+ await UserService.DeleteUserAsync(userId, jti, form.Password);
+ return Ok();
}
catch (Exception ex)
{
- return BadRequest(ex.Message);
+ await Slogger.Error(nameof(GetUser), "Couldn't delete user with id.", userId, ex.ToString());
+ return BadRequest(new { error = ex.Message });
}
}
}
diff --git a/Notesnook.API/Interfaces/IUserService.cs b/Notesnook.API/Interfaces/IUserService.cs
index dc84d08..04498f6 100644
--- a/Notesnook.API/Interfaces/IUserService.cs
+++ b/Notesnook.API/Interfaces/IUserService.cs
@@ -27,9 +27,10 @@ namespace Notesnook.API.Interfaces
public interface IUserService
{
Task CreateUserAsync();
- Task DeleteUserAsync(string userId, string jti);
+ Task DeleteUserAsync(string userId);
+ Task DeleteUserAsync(string userId, string jti, string password);
Task ResetUserAsync(string userId, bool removeAttachments);
- Task GetUserAsync(bool repair = true);
+ Task GetUserAsync(string userId);
Task SetUserAttachmentsKeyAsync(string userId, IEncrypted key);
}
}
\ No newline at end of file
diff --git a/Notesnook.API/Models/DeleteAccountForm.cs b/Notesnook.API/Models/DeleteAccountForm.cs
new file mode 100644
index 0000000..a504fda
--- /dev/null
+++ b/Notesnook.API/Models/DeleteAccountForm.cs
@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace Notesnook.API.Models
+{
+ public class DeleteAccountForm
+ {
+ [Required]
+ public string Password
+ {
+ get; set;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Notesnook.API/Services/UserService.cs b/Notesnook.API/Services/UserService.cs
index a122540..d52b973 100644
--- a/Notesnook.API/Services/UserService.cs
+++ b/Notesnook.API/Services/UserService.cs
@@ -92,10 +92,11 @@ namespace Notesnook.API.Services
await Slogger.Info(nameof(CreateUserAsync), "New user created.", JsonSerializer.Serialize(response));
}
- public async Task GetUserAsync(bool repair = true)
+ public async Task GetUserAsync(string userId)
{
- UserResponse response = await httpClient.ForwardAsync(this.HttpContextAccessor, $"{Servers.IdentityServer.ToString()}/account", HttpMethod.Get);
- if (!response.Success) return response;
+ var userService = await WampServers.IdentityServer.GetServiceAsync(IdentityServerTopics.UserAccountServiceTopic);
+
+ var user = await userService.GetUserAsync(Clients.Notesnook.Id, userId) ?? throw new Exception("User not found.");
ISubscription subscription = null;
if (Constants.IS_SELF_HOSTED)
@@ -105,7 +106,7 @@ namespace Notesnook.API.Services
AppId = ApplicationType.NOTESNOOK,
Provider = SubscriptionProvider.STREETWRITERS,
Type = SubscriptionType.PREMIUM,
- UserId = response.UserId,
+ UserId = user.UserId,
StartDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
// this date doesn't matter as the subscription is static.
ExpiryDate = DateTimeOffset.UtcNow.AddYears(1).ToUnixTimeMilliseconds()
@@ -113,117 +114,92 @@ namespace Notesnook.API.Services
}
else
{
- SubscriptionResponse subscriptionResponse = await httpClient.ForwardAsync(this.HttpContextAccessor, $"{Servers.SubscriptionServer}/subscriptions", HttpMethod.Get);
- if (repair && subscriptionResponse.StatusCode == 404)
- {
- await Slogger.Error(nameof(GetUserAsync), "Repairing user subscription.", JsonSerializer.Serialize(response));
- // user was partially created. We should continue the process here.
- await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.CreateSubscriptionTopic, new CreateSubscriptionMessage
- {
- AppId = ApplicationType.NOTESNOOK,
- Provider = SubscriptionProvider.STREETWRITERS,
- Type = SubscriptionType.TRIAL,
- UserId = response.UserId,
- StartTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
- ExpiryTime = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
- });
- // just a dummy object
- subscriptionResponse.Subscription = new Subscription
- {
- AppId = ApplicationType.NOTESNOOK,
- Provider = SubscriptionProvider.STREETWRITERS,
- Type = SubscriptionType.TRIAL,
- UserId = response.UserId,
- StartDate = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
- ExpiryDate = DateTimeOffset.UtcNow.AddDays(7).ToUnixTimeMilliseconds()
- };
- }
- subscription = subscriptionResponse.Subscription;
+ var subscriptionService = await WampServers.SubscriptionServer.GetServiceAsync(SubscriptionServerTopics.UserSubscriptionServiceTopic);
+ subscription = await subscriptionService.GetUserSubscriptionAsync(Clients.Notesnook.Id, userId);
}
- var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == response.UserId);
- if (repair && userSettings == null)
+ var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == user.UserId) ?? throw new Exception("User settings not found.");
+ return new UserResponse
{
- await Slogger.Error(nameof(GetUserAsync), "Repairing user settings.", JsonSerializer.Serialize(response));
- userSettings = new UserSettings
- {
- UserId = response.UserId,
- LastSynced = 0,
- Salt = GetSalt()
- };
- await Repositories.UsersSettings.InsertAsync(userSettings);
- }
- response.AttachmentsKey = userSettings.AttachmentsKey;
- response.Salt = userSettings.Salt;
- response.Subscription = subscription;
- return response;
+ UserId = user.UserId,
+ Email = user.Email,
+ IsEmailConfirmed = user.IsEmailConfirmed,
+ MarketingConsent = user.MarketingConsent,
+ MFA = user.MFA,
+ PhoneNumber = user.PhoneNumber,
+ AttachmentsKey = userSettings.AttachmentsKey,
+ Salt = userSettings.Salt,
+ Subscription = subscription,
+ Success = true,
+ StatusCode = 200
+ };
}
public async Task SetUserAttachmentsKeyAsync(string userId, IEncrypted key)
{
- var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId);
+ var userSettings = await Repositories.UsersSettings.FindOneAsync((u) => u.UserId == userId) ?? throw new Exception("User not found.");
userSettings.AttachmentsKey = (EncryptedData)key;
await Repositories.UsersSettings.UpdateAsync(userSettings.Id, userSettings);
}
- public async Task DeleteUserAsync(string userId, string jti)
+ public async Task DeleteUserAsync(string userId)
{
- try
+ new SyncDeviceService(new SyncDevice(ref userId, ref userId)).ResetDevices();
+
+ var cc = new CancellationTokenSource();
+
+ Repositories.Notes.DeleteByUserId(userId);
+ Repositories.Notebooks.DeleteByUserId(userId);
+ Repositories.Shortcuts.DeleteByUserId(userId);
+ Repositories.Contents.DeleteByUserId(userId);
+ Repositories.Settings.DeleteByUserId(userId);
+ Repositories.LegacySettings.DeleteByUserId(userId);
+ Repositories.Attachments.DeleteByUserId(userId);
+ Repositories.Reminders.DeleteByUserId(userId);
+ Repositories.Relations.DeleteByUserId(userId);
+ Repositories.Colors.DeleteByUserId(userId);
+ Repositories.Tags.DeleteByUserId(userId);
+ Repositories.Vaults.DeleteByUserId(userId);
+ Repositories.UsersSettings.Delete((u) => u.UserId == userId);
+ Repositories.Monographs.DeleteMany((m) => m.UserId == userId);
+
+ var result = await unit.Commit();
+ await Slogger.Info(nameof(DeleteUserAsync), "User data deleted", userId, result.ToString());
+ if (!result) throw new Exception("Could not delete user data.");
+
+ if (!Constants.IS_SELF_HOSTED)
{
- await Slogger.Info(nameof(DeleteUserAsync), "Deleting user account", userId);
-
- new SyncDeviceService(new SyncDevice(ref userId, ref userId)).ResetDevices();
-
- var cc = new CancellationTokenSource();
-
- Repositories.Notes.DeleteByUserId(userId);
- Repositories.Notebooks.DeleteByUserId(userId);
- Repositories.Shortcuts.DeleteByUserId(userId);
- Repositories.Contents.DeleteByUserId(userId);
- Repositories.Settings.DeleteByUserId(userId);
- Repositories.LegacySettings.DeleteByUserId(userId);
- Repositories.Attachments.DeleteByUserId(userId);
- Repositories.Reminders.DeleteByUserId(userId);
- Repositories.Relations.DeleteByUserId(userId);
- Repositories.Colors.DeleteByUserId(userId);
- Repositories.Tags.DeleteByUserId(userId);
- Repositories.Vaults.DeleteByUserId(userId);
- Repositories.UsersSettings.Delete((u) => u.UserId == userId);
- Repositories.Monographs.DeleteMany((m) => m.UserId == userId);
-
- var result = await unit.Commit();
- await Slogger.Info(nameof(DeleteUserAsync), "User account deleted", userId, result.ToString());
- if (!result) return false;
-
- if (!Constants.IS_SELF_HOSTED)
+ await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
{
- await WampServers.SubscriptionServer.PublishMessageAsync(SubscriptionServerTopics.DeleteSubscriptionTopic, new DeleteSubscriptionMessage
- {
- AppId = ApplicationType.NOTESNOOK,
- UserId = userId
- });
- }
-
- await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
- {
- SendToAll = false,
- OriginTokenId = jti,
- UserId = userId,
- Message = new Message
- {
- Type = "logout",
- Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
- }
+ AppId = ApplicationType.NOTESNOOK,
+ UserId = userId
});
+ }
- await S3Service.DeleteDirectoryAsync(userId);
- return result;
- }
- catch (Exception ex)
+ await S3Service.DeleteDirectoryAsync(userId);
+ }
+
+ public async Task DeleteUserAsync(string userId, string jti, string password)
+ {
+ await Slogger.Info(nameof(DeleteUserAsync), "Deleting user account", userId);
+
+ var userService = await WampServers.IdentityServer.GetServiceAsync(IdentityServerTopics.UserAccountServiceTopic);
+ await userService.DeleteUserAsync(Clients.Notesnook.Id, userId, password);
+
+ await DeleteUserAsync(userId);
+
+ await WampServers.MessengerServer.PublishMessageAsync(MessengerServerTopics.SendSSETopic, new SendSSEMessage
{
- await Slogger.Error(nameof(DeleteUserAsync), "User account not deleted", userId, ex.ToString());
- }
- return false;
+ SendToAll = false,
+ OriginTokenId = jti,
+ UserId = userId,
+ Message = new Message
+ {
+ Type = "logout",
+ Data = JsonSerializer.Serialize(new { reason = "Account deleted." })
+ }
+ });
+
}
public async Task ResetUserAsync(string userId, bool removeAttachments)
diff --git a/Streetwriters.Common/Interfaces/IUserAccountService.cs b/Streetwriters.Common/Interfaces/IUserAccountService.cs
new file mode 100644
index 0000000..facb452
--- /dev/null
+++ b/Streetwriters.Common/Interfaces/IUserAccountService.cs
@@ -0,0 +1,16 @@
+using System.Threading.Tasks;
+using Streetwriters.Common.Models;
+using WampSharp.V2.Rpc;
+
+namespace Streetwriters.Common.Interfaces
+{
+ public interface IUserAccountService
+ {
+ [WampProcedure("co.streetwriters.identity.users.get_user")]
+ Task GetUserAsync(string clientId, string userId);
+ [WampProcedure("co.streetwriters.identity.users.delete_user")]
+ Task DeleteUserAsync(string clientId, string userId, string password);
+ // [WampProcedure("co.streetwriters.identity.users.create_user")]
+ // Task CreateUserAsync();
+ }
+}
\ No newline at end of file
diff --git a/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs b/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs
new file mode 100644
index 0000000..2ed2abd
--- /dev/null
+++ b/Streetwriters.Common/Interfaces/IUserSubscriptionService.cs
@@ -0,0 +1,13 @@
+using System.Threading.Tasks;
+using Streetwriters.Common.Helpers;
+using Streetwriters.Common.Models;
+using WampSharp.V2.Rpc;
+
+namespace Streetwriters.Common.Interfaces
+{
+ public interface IUserSubscriptionService
+ {
+ [WampProcedure("co.streetwriters.subscriptions.subscriptions.get_user_subscription")]
+ Task GetUserSubscriptionAsync(string clientId, string userId);
+ }
+}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Controllers/AccountController.cs b/Streetwriters.Identity/Controllers/AccountController.cs
index b6f0019..3e20de9 100644
--- a/Streetwriters.Identity/Controllers/AccountController.cs
+++ b/Streetwriters.Identity/Controllers/AccountController.cs
@@ -33,11 +33,13 @@ using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Streetwriters.Common;
using Streetwriters.Common.Enums;
+using Streetwriters.Common.Interfaces;
using Streetwriters.Common.Messages;
using Streetwriters.Common.Models;
using Streetwriters.Identity.Enums;
using Streetwriters.Identity.Interfaces;
using Streetwriters.Identity.Models;
+using Streetwriters.Identity.Services;
using static IdentityServer4.IdentityServerConstants;
namespace Streetwriters.Identity.Controllers
@@ -52,12 +54,14 @@ namespace Streetwriters.Identity.Controllers
private ITokenGenerationService TokenGenerationService { get; set; }
private IUserClaimsPrincipalFactory PrincipalFactory { get; set; }
private IdentityServerOptions ISOptions { get; set; }
+ private IUserAccountService UserAccountService { get; set; }
public AccountController(UserManager _userManager, IEmailSender _emailSender,
SignInManager _signInManager, RoleManager _roleManager, IPersistedGrantStore store,
- ITokenGenerationService tokenGenerationService, IMFAService _mfaService) : base(_userManager, _emailSender, _signInManager, _roleManager, _mfaService)
+ ITokenGenerationService tokenGenerationService, IMFAService _mfaService, IUserAccountService userAccountService) : base(_userManager, _emailSender, _signInManager, _roleManager, _mfaService)
{
PersistedGrantStore = store;
TokenGenerationService = tokenGenerationService;
+ UserAccountService = userAccountService;
}
[HttpGet("confirm")]
@@ -69,7 +73,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.FindByIdAsync(userId);
- if (!await IsUserValidAsync(user, clientId)) return BadRequest($"Unable to find user with ID '{userId}'.");
+ if (!await UserService.IsUserValidAsync(UserManager, user, clientId)) return BadRequest($"Unable to find user with ID '{userId}'.");
switch (type)
{
@@ -116,7 +120,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
+ if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
if (string.IsNullOrEmpty(newEmail))
{
@@ -132,63 +136,13 @@ namespace Streetwriters.Identity.Controllers
return Ok();
}
- [HttpPost("unregister")]
- public async Task UnregisterAccountAync([FromForm] DeleteAccountForm form)
- {
- var client = Clients.FindClientById(User.FindFirstValue("client_id"));
- if (client == null) return BadRequest("Invalid client_id.");
-
- var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
-
- if (!await UserManager.CheckPasswordAsync(user, form.Password))
- {
- return Unauthorized();
- }
-
- await UserManager.DeleteAsync(user);
-
- // await UserManager.RemoveFromRoleAsync(user, client.Id);
- // await MFAService.DisableMFAAsync(user);
-
- // IdentityUserClaim statusClaim = user.Claims.FirstOrDefault((c) => c.ClaimType == $"{client.Id}:status");
- // await UserManager.RemoveClaimAsync(user, statusClaim.ToClaim());
- return Ok();
- }
-
[HttpGet]
public async Task GetUserAccount()
{
var client = Clients.FindClientById(User.FindFirstValue("client_id"));
if (client == null) return BadRequest("Invalid client_id.");
-
var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id))
- return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
-
- var claims = await UserManager.GetClaimsAsync(user);
- var marketingConsentClaim = claims.FirstOrDefault((claim) => claim.Type == $"{client.Id}:marketing_consent");
-
- if (await UserManager.IsEmailConfirmedAsync(user) && !await UserManager.GetTwoFactorEnabledAsync(user))
- {
- await MFAService.EnableMFAAsync(user, MFAMethods.Email);
- user = await UserManager.GetUserAsync(User);
- }
-
- return Ok(new UserModel
- {
- UserId = user.Id.ToString(),
- Email = user.Email,
- IsEmailConfirmed = user.EmailConfirmed,
- MarketingConsent = marketingConsentClaim == null,
- MFA = new MFAConfig
- {
- IsEnabled = user.TwoFactorEnabled,
- PrimaryMethod = MFAService.GetPrimaryMethod(user),
- SecondaryMethod = MFAService.GetSecondaryMethod(user),
- RemainingValidCodes = await MFAService.GetRemainingValidCodesAsync(user)
- }
- });
+ return Ok(UserAccountService.GetUserAsync(client.Id, user.Id.ToString()));
}
[HttpPost("recover")]
@@ -199,7 +153,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.FindByEmailAsync(form.Email);
- if (!await IsUserValidAsync(user, form.ClientId)) return Ok();
+ if (!await UserService.IsUserValidAsync(UserManager, user, form.ClientId)) return Ok();
var code = await UserManager.GenerateUserTokenAsync(user, TokenOptions.DefaultProvider, "ResetPassword");
var callbackUrl = Url.TokenLink(user.Id.ToString(), code, client.Id, TokenType.RESET_PASSWORD, Request.Scheme);
@@ -219,7 +173,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
+ if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
var subjectId = User.FindFirstValue("sub");
var jti = User.FindFirstValue("jti");
@@ -246,7 +200,7 @@ namespace Streetwriters.Identity.Controllers
{
if (!Clients.IsValidClient(form.ClientId)) return BadRequest("Invalid clientId.");
var user = await UserManager.FindByIdAsync(form.UserId);
- if (!await IsUserValidAsync(user, form.ClientId))
+ if (!await UserService.IsUserValidAsync(UserManager, user, form.ClientId))
return BadRequest($"Unable to find user with ID '{form.UserId}'.");
if (!await UserManager.VerifyUserTokenAsync(user, TokenOptions.DefaultProvider, "PasswordResetAuthorizationCode", form.Code))
@@ -267,7 +221,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id))
+ if (!await UserService.IsUserValidAsync(UserManager, user, client.Id))
return BadRequest($"Unable to find user with ID '{UserManager.GetUserId(User)}'.");
switch (form.Type)
@@ -338,7 +292,7 @@ namespace Streetwriters.Identity.Controllers
if (client == null) return BadRequest("Invalid client_id.");
var user = await UserManager.GetUserAsync(User);
- if (!await IsUserValidAsync(user, client.Id)) return BadRequest($"Unable to find user with ID '{user.Id}'.");
+ if (!await UserService.IsUserValidAsync(UserManager, user, client.Id)) return BadRequest($"Unable to find user with ID '{user.Id}'.");
var jti = User.FindFirstValue("jti");
@@ -386,10 +340,5 @@ namespace Streetwriters.Identity.Controllers
Message = message
});
}
-
- public async Task IsUserValidAsync(User user, string clientId)
- {
- return user != null && await UserManager.IsInRoleAsync(user, clientId);
- }
}
}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Models/DeleteAccountForm.cs b/Streetwriters.Identity/Models/DeleteAccountForm.cs
deleted file mode 100644
index 83bec8e..0000000
--- a/Streetwriters.Identity/Models/DeleteAccountForm.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
-This file is part of the Notesnook Sync Server project (https://notesnook.com/)
-
-Copyright (C) 2023 Streetwriters (Private) Limited
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the Affero GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-Affero GNU General Public License for more details.
-
-You should have received a copy of the Affero GNU General Public License
-along with this program. If not, see .
-*/
-
-using System.ComponentModel.DataAnnotations;
-using System.Runtime.Serialization;
-
-namespace Streetwriters.Identity.Models
-{
- public class DeleteAccountForm
- {
- [Required]
- public string Password
- {
- get; set;
- }
- }
-}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Services/UserAccountService.cs b/Streetwriters.Identity/Services/UserAccountService.cs
new file mode 100644
index 0000000..37ae44b
--- /dev/null
+++ b/Streetwriters.Identity/Services/UserAccountService.cs
@@ -0,0 +1,56 @@
+using System;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
+using Streetwriters.Common.Enums;
+using Streetwriters.Common.Interfaces;
+using Streetwriters.Common.Models;
+using Streetwriters.Identity.Interfaces;
+using Streetwriters.Identity.Models;
+
+namespace Streetwriters.Identity.Services
+{
+ public class UserAccountService(UserManager userManager, IMFAService mfaService) : IUserAccountService
+ {
+ public async Task GetUserAsync(string clientId, string userId)
+ {
+ var user = await userManager.FindByIdAsync(userId);
+ if (!await UserService.IsUserValidAsync(userManager, user, clientId))
+ throw new Exception($"Unable to find user with ID '{userId}'.");
+
+ var claims = await userManager.GetClaimsAsync(user);
+ var marketingConsentClaim = claims.FirstOrDefault((claim) => claim.Type == $"{clientId}:marketing_consent");
+
+ if (await userManager.IsEmailConfirmedAsync(user) && !await userManager.GetTwoFactorEnabledAsync(user))
+ {
+ await mfaService.EnableMFAAsync(user, MFAMethods.Email);
+ user = await userManager.FindByIdAsync(userId);
+ }
+
+ return new UserModel
+ {
+ UserId = user.Id.ToString(),
+ Email = user.Email,
+ IsEmailConfirmed = user.EmailConfirmed,
+ MarketingConsent = marketingConsentClaim == null,
+ MFA = new MFAConfig
+ {
+ IsEnabled = user.TwoFactorEnabled,
+ PrimaryMethod = mfaService.GetPrimaryMethod(user),
+ SecondaryMethod = mfaService.GetSecondaryMethod(user),
+ RemainingValidCodes = await mfaService.GetRemainingValidCodesAsync(user)
+ }
+ };
+ }
+
+ public async Task DeleteUserAsync(string clientId, string userId, string password)
+ {
+ var user = await userManager.FindByIdAsync(userId);
+ if (!await UserService.IsUserValidAsync(userManager, user, clientId)) throw new Exception($"User not found.");
+
+ if (!await userManager.CheckPasswordAsync(user, password)) throw new Exception("Wrong password.");
+
+ await userManager.DeleteAsync(user);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Streetwriters.Identity/Services/UserService.cs b/Streetwriters.Identity/Services/UserService.cs
index 8067985..a60a46b 100644
--- a/Streetwriters.Identity/Services/UserService.cs
+++ b/Streetwriters.Identity/Services/UserService.cs
@@ -19,6 +19,8 @@ along with this program. If not, see .
using System.Linq;
using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Identity;
using Streetwriters.Common.Enums;
using Streetwriters.Common.Models;
@@ -78,5 +80,10 @@ namespace Streetwriters.Identity.Services
{
return $"{clientId}:status";
}
+
+ public static async Task IsUserValidAsync(UserManager userManager, User user, string clientId)
+ {
+ return user != null && await userManager.IsInRoleAsync(user, clientId);
+ }
}
}
\ No newline at end of file