From ee682516c3f1bb4140a93ad041301721eceae7bb Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 4 Feb 2020 15:14:27 -0500 Subject: [PATCH] refactoring email verification --- Oqtane.Client/Modules/Admin/Login/Index.razor | 19 ++++-- Oqtane.Client/Modules/Admin/Reset/Index.razor | 1 - Oqtane.Client/Modules/HtmlText/Edit.razor | 3 +- Oqtane.Client/Modules/HtmlText/Index.razor | 1 + .../Services/Interfaces/IUserService.cs | 2 + Oqtane.Client/Services/UserService.cs | 5 ++ Oqtane.Server/Controllers/FolderController.cs | 2 +- Oqtane.Server/Controllers/UserController.cs | 40 ++++++++++-- Oqtane.Server/Pages/Login.cshtml.cs | 4 ++ Oqtane.Server/Pages/Logout.cshtml.cs | 4 ++ Oqtane.Server/Pages/Verify.cshtml | 3 - Oqtane.Server/Pages/Verify.cshtml.cs | 42 ------------- .../Repository/Context/TenantDBContext.cs | 1 + Oqtane.Server/Repository/FileRepository.cs | 62 +++++++++++++++++++ .../Repository/Interfaces/IFileRepository.cs | 14 +++++ Oqtane.Server/Scripts/00.00.00.sql | 23 +++++++ Oqtane.Shared/Models/File.cs | 21 +++++++ 17 files changed, 187 insertions(+), 60 deletions(-) delete mode 100644 Oqtane.Server/Pages/Verify.cshtml delete mode 100644 Oqtane.Server/Pages/Verify.cshtml.cs create mode 100644 Oqtane.Server/Repository/FileRepository.cs create mode 100644 Oqtane.Server/Repository/Interfaces/IFileRepository.cs create mode 100644 Oqtane.Shared/Models/File.cs diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 654f292a..f2ffa020 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -50,15 +50,24 @@ public string Password = ""; public bool Remember = false; - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { if (PageState.QueryString.ContainsKey("returnurl")) { ReturnUrl = PageState.QueryString["returnurl"]; } - if (PageState.QueryString.ContainsKey("verified")) + if (PageState.QueryString.ContainsKey("name")) { - if (PageState.QueryString["verified"] == "1") + Username = PageState.QueryString["name"]; + } + if (PageState.QueryString.ContainsKey("token")) + { + User user = new User(); + user.SiteId = PageState.Site.SiteId; + user.Username = Username; + user = await UserService.VerifyEmailAsync(user, PageState.QueryString["token"]); + + if (user != null) { Message = "User Account Verified Successfully. You Can Now Login With Your Username And Password Below."; } @@ -141,8 +150,8 @@ } else { - Message = "Please Enter The Username Related To Your Account And Then Click The Forgot Password Option"; + Message = "Please Enter The Username Related To Your Account And Then Select The Forgot Password Option Again"; } StateHasChanged(); } -} + } diff --git a/Oqtane.Client/Modules/Admin/Reset/Index.razor b/Oqtane.Client/Modules/Admin/Reset/Index.razor index 5117ca90..e7ed6bbe 100644 --- a/Oqtane.Client/Modules/Admin/Reset/Index.razor +++ b/Oqtane.Client/Modules/Admin/Reset/Index.razor @@ -50,7 +50,6 @@ User user = new User(); user.SiteId = PageState.Site.SiteId; user.Username = Username; - user.DisplayName = Username; user.Password = Password; user = await UserService.ResetPasswordAsync(user, PageState.QueryString["token"]); diff --git a/Oqtane.Client/Modules/HtmlText/Edit.razor b/Oqtane.Client/Modules/HtmlText/Edit.razor index 4618aa09..0fb17a96 100644 --- a/Oqtane.Client/Modules/HtmlText/Edit.razor +++ b/Oqtane.Client/Modules/HtmlText/Edit.razor @@ -78,8 +78,7 @@ else string modifiedby; DateTime modifiedon; - protected override async Task - OnAfterRenderAsync(bool firstRender) + protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { diff --git a/Oqtane.Client/Modules/HtmlText/Index.razor b/Oqtane.Client/Modules/HtmlText/Index.razor index 5ec302be..b1d679c2 100644 --- a/Oqtane.Client/Modules/HtmlText/Index.razor +++ b/Oqtane.Client/Modules/HtmlText/Index.razor @@ -28,6 +28,7 @@ } catch (Exception ex) { + await logger.LogError(ex, "An Error Occurred Loading Html/Text Content. " + ex.Message); AddModuleMessage(ex.Message, MessageType.Error); } } diff --git a/Oqtane.Client/Services/Interfaces/IUserService.cs b/Oqtane.Client/Services/Interfaces/IUserService.cs index 31589eee..e0ed49c9 100644 --- a/Oqtane.Client/Services/Interfaces/IUserService.cs +++ b/Oqtane.Client/Services/Interfaces/IUserService.cs @@ -24,6 +24,8 @@ namespace Oqtane.Services Task LogoutUserAsync(User User); + Task VerifyEmailAsync(User User, string Token); + Task ForgotPasswordAsync(User User); Task ResetPasswordAsync(User User, string Token); diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 16d9a62b..f52d75c6 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -87,6 +87,11 @@ namespace Oqtane.Services await http.PostJsonAsync(apiurl + "/logout", User); } + public async Task VerifyEmailAsync(User User, string Token) + { + return await http.PostJsonAsync(apiurl + "/verify?token=" + Token, User); + } + public async Task ForgotPasswordAsync(User User) { await http.PostJsonAsync(apiurl + "/forgot", User); diff --git a/Oqtane.Server/Controllers/FolderController.cs b/Oqtane.Server/Controllers/FolderController.cs index f04eda49..46bae326 100644 --- a/Oqtane.Server/Controllers/FolderController.cs +++ b/Oqtane.Server/Controllers/FolderController.cs @@ -38,7 +38,7 @@ namespace Oqtane.Controllers } } - // GET api//5?userid=x + // GET api//5 [HttpGet("{id}")] public Folder Get(int id) { diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index 5229ca54..37038c9a 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -108,8 +108,7 @@ namespace Oqtane.Controllers notification.ToEmail = ""; notification.Subject = "User Account Verification"; string token = await IdentityUserManager.GenerateEmailConfirmationTokenAsync(identityuser); - string alias = Tenants.GetAlias().Path; - string url = HttpContext.Request.Scheme + "://" + HttpContext.Request.Host + "/pages/verify?name=" + User.Username + "&token=" + WebUtility.UrlEncode(token) + "&returnurl=" + (alias == "" ? "/" : alias); + string url = HttpContext.Request.Scheme + "://" + Tenants.GetAlias().Name + "/login?name=" + User.Username + "&token=" + WebUtility.UrlEncode(token); notification.Body = "Dear " + User.DisplayName + ",\n\nIn Order To Complete The Registration Of Your User Account Please Click The Link Displayed Below:\n\n" + url + "\n\nThank You!"; notification.ParentId = null; notification.CreatedOn = DateTime.Now; @@ -254,6 +253,35 @@ namespace Oqtane.Controllers logger.Log(LogLevel.Information, this, LogFunction.Security, "User Logout {Username}", User.Username); } + // POST api//verify + [HttpPost("verify")] + public async Task Verify([FromBody] User User, string token) + { + if (ModelState.IsValid) + { + IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username); + if (identityuser != null) + { + var result = await IdentityUserManager.ConfirmEmailAsync(identityuser, token); + if (result.Succeeded) + { + logger.Log(LogLevel.Information, this, LogFunction.Security, "Email Verified For {Username}", User.Username); + } + else + { + logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}", User.Username); + User = null; + } + } + else + { + logger.Log(LogLevel.Error, this, LogFunction.Security, "Email Verification Failed For {Username}", User.Username); + User = null; + } + } + return User; + } + // POST api//forgot [HttpPost("forgot")] public async Task Forgot([FromBody] User User) @@ -290,7 +318,6 @@ namespace Oqtane.Controllers [HttpPost("reset")] public async Task Reset([FromBody] User User, string token) { - User user = null; if (ModelState.IsValid) { IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(User.Username); @@ -299,21 +326,22 @@ namespace Oqtane.Controllers var result = await IdentityUserManager.ResetPasswordAsync(identityuser, token, User.Password); if (result.Succeeded) { - user = User; - user.Password = ""; logger.Log(LogLevel.Information, this, LogFunction.Security, "Password Reset For {Username}", User.Username); + User.Password = ""; } else { logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", User.Username); + User = null; } } else { logger.Log(LogLevel.Error, this, LogFunction.Security, "Password Reset Failed For {Username}", User.Username); + User = null; } } - return user; + return User; } // GET api//current diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs index d28b543e..adf86ee4 100644 --- a/Oqtane.Server/Pages/Login.cshtml.cs +++ b/Oqtane.Server/Pages/Login.cshtml.cs @@ -37,6 +37,10 @@ namespace Oqtane.Pages await IdentitySignInManager.SignInAsync(identityuser, remember); } + if (returnurl == null) + { + returnurl = ""; + } if (!returnurl.StartsWith("/")) { returnurl = "/" + returnurl; diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs index ee6f0df2..35440769 100644 --- a/Oqtane.Server/Pages/Logout.cshtml.cs +++ b/Oqtane.Server/Pages/Logout.cshtml.cs @@ -14,6 +14,10 @@ namespace Oqtane.Pages { await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); + if (returnurl == null) + { + returnurl = ""; + } if (!returnurl.StartsWith("/")) { returnurl = "/" + returnurl; diff --git a/Oqtane.Server/Pages/Verify.cshtml b/Oqtane.Server/Pages/Verify.cshtml deleted file mode 100644 index 250c5543..00000000 --- a/Oqtane.Server/Pages/Verify.cshtml +++ /dev/null @@ -1,3 +0,0 @@ -@page "/pages/verify" -@namespace Oqtane.Pages -@model Oqtane.Pages.VerifyModel diff --git a/Oqtane.Server/Pages/Verify.cshtml.cs b/Oqtane.Server/Pages/Verify.cshtml.cs deleted file mode 100644 index 8875da8e..00000000 --- a/Oqtane.Server/Pages/Verify.cshtml.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Identity; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Oqtane.Models; -using Oqtane.Repository; -using System.Threading.Tasks; - -namespace Oqtane.Pages -{ - [AllowAnonymous] - public class VerifyModel : PageModel - { - private readonly IUserRepository Users; - private readonly UserManager IdentityUserManager; - - public VerifyModel(IUserRepository Users, UserManager IdentityUserManager) - { - this.Users = Users; - this.IdentityUserManager = IdentityUserManager; - } - - public async Task OnGet(string name, string token, string returnurl) - { - int verified = 0; - IdentityUser identityuser = await IdentityUserManager.FindByNameAsync(name); - if (identityuser != null) - { - var result = await IdentityUserManager.ConfirmEmailAsync(identityuser, token); - if (result.Succeeded) - { - verified = 1; - } - } - if (!returnurl.StartsWith("/")) - { - returnurl += "/" + returnurl; - } - return Redirect(HttpContext.Request.Scheme + "://" + HttpContext.Request.Host + returnurl + "login?verified=" + verified.ToString()); - } - } -} \ No newline at end of file diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs index b531ecb1..c90f8538 100644 --- a/Oqtane.Server/Repository/Context/TenantDBContext.cs +++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs @@ -19,6 +19,7 @@ namespace Oqtane.Repository public virtual DbSet Log { get; set; } public virtual DbSet Notification { get; set; } public virtual DbSet Folder { get; set; } + public virtual DbSet File { get; set; } public TenantDBContext(ITenantResolver TenantResolver, IHttpContextAccessor accessor) : base(TenantResolver, accessor) { diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs new file mode 100644 index 00000000..1b85840f --- /dev/null +++ b/Oqtane.Server/Repository/FileRepository.cs @@ -0,0 +1,62 @@ +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public class FileRepository : IFileRepository + { + private TenantDBContext db; + private readonly IPermissionRepository Permissions; + + public FileRepository(TenantDBContext context, IPermissionRepository Permissions) + { + db = context; + this.Permissions = Permissions; + } + + public IEnumerable GetFiles(int FolderId) + { + IEnumerable permissions = Permissions.GetPermissions("Folder", FolderId); + IEnumerable files = db.File.Where(item => item.FolderId == FolderId); + foreach (File file in files) + { + file.Folder.Permissions = Permissions.EncodePermissions(FolderId, permissions); + } + return files; + } + + public File AddFile(File File) + { + db.File.Add(File); + db.SaveChanges(); + return File; + } + + public File UpdateFile(File File) + { + db.Entry(File).State = EntityState.Modified; + db.SaveChanges(); + return File; + } + + public File GetFile(int FileId) + { + File file = db.File.Find(FileId); + if (file != null) + { + IEnumerable permissions = Permissions.GetPermissions("Folder", file.FolderId); + file.Folder.Permissions = Permissions.EncodePermissions(file.FolderId, permissions); + } + return file; + } + + public void DeleteFile(int FileId) + { + File File = db.File.Find(FileId); + db.File.Remove(File); + db.SaveChanges(); + } + } +} diff --git a/Oqtane.Server/Repository/Interfaces/IFileRepository.cs b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs new file mode 100644 index 00000000..3122898b --- /dev/null +++ b/Oqtane.Server/Repository/Interfaces/IFileRepository.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Oqtane.Models; + +namespace Oqtane.Repository +{ + public interface IFileRepository + { + IEnumerable GetFiles(int FolderId); + File AddFile(File File); + File UpdateFile(File File); + File GetFile(int FileId); + void DeleteFile(int FileId); + } +} diff --git a/Oqtane.Server/Scripts/00.00.00.sql b/Oqtane.Server/Scripts/00.00.00.sql index 7f820d2c..93dc05f7 100644 --- a/Oqtane.Server/Scripts/00.00.00.sql +++ b/Oqtane.Server/Scripts/00.00.00.sql @@ -275,6 +275,24 @@ CREATE TABLE [dbo].[Folder]( ) GO +CREATE TABLE [dbo].[File]( + [FileId] [int] IDENTITY(1,1) NOT NULL, + [FolderId] [int] NOT NULL, + [Name] [nvarchar](50) NOT NULL, + [CreatedBy] [nvarchar](256) NOT NULL, + [CreatedOn] [datetime] NOT NULL, + [ModifiedBy] [nvarchar](256) NOT NULL, + [ModifiedOn] [datetime] NOT NULL, + [DeletedBy] [nvarchar](256) NULL, + [DeletedOn] [datetime] NULL, + [IsDeleted][bit] NOT NULL, + CONSTRAINT [PK_File] PRIMARY KEY CLUSTERED + ( + [FileId] ASC + ) +) +GO + CREATE TABLE [dbo].[HtmlText]( [HtmlTextId] [int] IDENTITY(1,1) NOT NULL, [ModuleId] [int] NOT NULL, @@ -361,6 +379,11 @@ REFERENCES [dbo].[Site] ([SiteId]) ON DELETE CASCADE GO +ALTER TABLE [dbo].[File] WITH CHECK ADD CONSTRAINT [FK_File_Folder] FOREIGN KEY([FolderId]) +REFERENCES [dbo].[Folder] ([FolderId]) +ON DELETE CASCADE +GO + ALTER TABLE [dbo].[HtmlText] WITH CHECK ADD CONSTRAINT [FK_HtmlText_Module] FOREIGN KEY([ModuleId]) REFERENCES [dbo].[Module] ([ModuleId]) ON DELETE CASCADE diff --git a/Oqtane.Shared/Models/File.cs b/Oqtane.Shared/Models/File.cs new file mode 100644 index 00000000..55b8a637 --- /dev/null +++ b/Oqtane.Shared/Models/File.cs @@ -0,0 +1,21 @@ +using System; + +namespace Oqtane.Models +{ + public class File : IAuditable + { + public int FileId { get; set; } + public int FolderId { get; set; } + public string Name { get; set; } + + public string CreatedBy { get; set; } + public DateTime CreatedOn { get; set; } + public string ModifiedBy { get; set; } + public DateTime ModifiedOn { get; set; } + public string DeletedBy { get; set; } + public DateTime? DeletedOn { get; set; } + public bool IsDeleted { get; set; } + + public Folder Folder { get; set; } + } +}