From ce069ed45bf0d06a423e0f3ee1cf864f748c8614 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Mon, 15 Jul 2019 08:30:03 -0400 Subject: [PATCH] Refactoring authentication to support server-side Blazor using a seamless login flow. --- Oqtane.Client/Modules/Admin/Login/Index.razor | 60 ++++++++++++++----- ...=> IdentityAuthenticationStateProvider.cs} | 7 +-- Oqtane.Client/Services/AliasService.cs | 3 +- Oqtane.Client/Services/IModuleService.cs | 1 + Oqtane.Client/Services/IPageService.cs | 1 + Oqtane.Client/Services/IUserService.cs | 2 + Oqtane.Client/Services/ModuleService.cs | 5 ++ Oqtane.Client/Services/PageService.cs | 5 ++ Oqtane.Client/Services/SiteService.cs | 12 +--- Oqtane.Client/Services/UserService.cs | 8 ++- Oqtane.Client/Shared/Interop.cs | 43 +++++++++++-- Oqtane.Client/Shared/SiteRouter.razor | 2 +- Oqtane.Client/Shared/Utilities.cs | 9 ++- Oqtane.Client/Startup.cs | 4 +- Oqtane.Client/Themes/Controls/Login.razor | 24 ++++++-- Oqtane.Client/wwwroot/js/interop.js | 18 ++++++ Oqtane.Server/Controllers/UserController.cs | 46 +++++++------- Oqtane.Server/Pages/Login.cshtml | 3 + Oqtane.Server/Pages/Login.cshtml.cs | 52 ++++++++++++++++ Oqtane.Server/Pages/Logout.cshtml | 3 + Oqtane.Server/Pages/Logout.cshtml.cs | 26 ++++++++ Oqtane.Server/Pages/_Host.cshtml | 1 + .../Repository/ModuleDefinitionRepository.cs | 1 + Oqtane.Server/Repository/TenantResolver.cs | 2 +- Oqtane.Server/Repository/ThemeRepository.cs | 6 +- Oqtane.Server/Startup.cs | 21 ++++--- Oqtane.Server/wwwroot/js/interop.js | 26 ++++++++ Oqtane.Shared/Models/User.cs | 2 + 28 files changed, 307 insertions(+), 86 deletions(-) rename Oqtane.Client/Providers/{ServerAuthenticationStateProvider.cs => IdentityAuthenticationStateProvider.cs} (84%) create mode 100644 Oqtane.Server/Pages/Login.cshtml create mode 100644 Oqtane.Server/Pages/Login.cshtml.cs create mode 100644 Oqtane.Server/Pages/Logout.cshtml create mode 100644 Oqtane.Server/Pages/Logout.cshtml.cs diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index f8766bab..3071d436 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -4,11 +4,12 @@ @using Oqtane.Models @using Oqtane.Services @using Oqtane.Providers +@using Oqtane.Shared @inherits ModuleBase @inject IUriHelper UriHelper @inject IJSRuntime jsRuntime @inject IUserService UserService -@inject ServerAuthenticationStateProvider AuthStateProvider +@inject IServiceProvider ServiceProvider @@ -28,33 +29,62 @@ +
+
+   + +
+
- Cancel +
@code { - public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } } +public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } } - public string Message { get; set; } = "
Use host/password For Demo Access
"; - public string Username { get; set; } = ""; - public string Password { get; set; } = ""; +public string Message { get; set; } = "
Use host/password For Demo Access
"; +public string Username { get; set; } = ""; +public string Password { get; set; } = ""; +public bool Remember { get; set; } = false; - private async Task Login() +private async Task Login() +{ + User user = new User(); + user.Username = Username; + user.Password = Password; + user.IsPersistent = Remember; + user = await UserService.LoginUserAsync(user); + if (user.IsAuthenticated) { - User user = new User(); - user.Username = Username; - user.Password = Password; - user = await UserService.LoginUserAsync(user); - if (user != null) + string ReturnUrl = PageState.QueryString["returnurl"]; + + var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); + if (authstateprovider == null) { - AuthStateProvider.NotifyAuthenticationChanged(); - UriHelper.NavigateTo(NavigateUrl("", true)); + // server-side Blazor + var interop = new Interop(jsRuntime); + string antiforgerytoken = await interop.GetElementByName("__RequestVerificationToken"); + var fields = new { __RequestVerificationToken = antiforgerytoken, username = Username, password = Password, remember = Remember, returnurl = ReturnUrl }; + await interop.SubmitForm("/login/", fields); } else { - Message = "
User Does Not Exist
"; + // client-side Blazor + authstateprovider.NotifyAuthenticationChanged(); + UriHelper.NavigateTo(NavigateUrl(ReturnUrl, true)); } } + else + { + Message = "
Login Failed. Please Remember That Passwords Are Case Sensitive.
"; + } +} + +private void Cancel() +{ + string ReturnUrl = PageState.QueryString["returnurl"]; + UriHelper.NavigateTo(NavigateUrl(ReturnUrl)); +} } diff --git a/Oqtane.Client/Providers/ServerAuthenticationStateProvider.cs b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs similarity index 84% rename from Oqtane.Client/Providers/ServerAuthenticationStateProvider.cs rename to Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs index 818ed0c7..9ad5b1c7 100644 --- a/Oqtane.Client/Providers/ServerAuthenticationStateProvider.cs +++ b/Oqtane.Client/Providers/IdentityAuthenticationStateProvider.cs @@ -7,14 +7,12 @@ using Oqtane.Models; namespace Oqtane.Providers { - public class ServerAuthenticationStateProvider : AuthenticationStateProvider + public class IdentityAuthenticationStateProvider : AuthenticationStateProvider { - //private readonly IUserService UserService; private readonly IUriHelper urihelper; - public ServerAuthenticationStateProvider(IUriHelper urihelper) + public IdentityAuthenticationStateProvider(IUriHelper urihelper) { - //this.UserService = UserService; this.urihelper = urihelper; } @@ -25,6 +23,7 @@ namespace Oqtane.Providers Uri uri = new Uri(urihelper.GetAbsoluteUri()); string apiurl = uri.Scheme + "://" + uri.Authority + "/~/api/User/authenticate"; User user = await http.GetJsonAsync(apiurl); + var identity = user.IsAuthenticated ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.Username) }, "Identity.Application") : new ClaimsIdentity(); diff --git a/Oqtane.Client/Services/AliasService.cs b/Oqtane.Client/Services/AliasService.cs index acc81b47..88aa9683 100644 --- a/Oqtane.Client/Services/AliasService.cs +++ b/Oqtane.Client/Services/AliasService.cs @@ -32,8 +32,7 @@ namespace Oqtane.Services public async Task GetAliasAsync(int AliasId) { - List aliases = await http.GetJsonAsync>(apiurl); - return aliases.Where(item => item.AliasId == AliasId).FirstOrDefault(); + return await http.GetJsonAsync(apiurl + "/" + AliasId.ToString()); } public async Task AddAliasAsync(Alias alias) diff --git a/Oqtane.Client/Services/IModuleService.cs b/Oqtane.Client/Services/IModuleService.cs index 73143fc2..4f800cf7 100644 --- a/Oqtane.Client/Services/IModuleService.cs +++ b/Oqtane.Client/Services/IModuleService.cs @@ -8,6 +8,7 @@ namespace Oqtane.Services { Task> GetModulesAsync(int PageId); Task> GetModulesAsync(int SiteId, string ModuleDefinitionName); + Task GetModuleAsync(int ModuleId); Task AddModuleAsync(Module module); Task UpdateModuleAsync(Module module); Task DeleteModuleAsync(int ModuleId); diff --git a/Oqtane.Client/Services/IPageService.cs b/Oqtane.Client/Services/IPageService.cs index 71e0ea0c..5388af7b 100644 --- a/Oqtane.Client/Services/IPageService.cs +++ b/Oqtane.Client/Services/IPageService.cs @@ -7,6 +7,7 @@ namespace Oqtane.Services public interface IPageService { Task> GetPagesAsync(int SiteId); + Task GetPageAsync(int PageId); Task AddPageAsync(Page page); Task UpdatePageAsync(Page page); Task DeletePageAsync(int PageId); diff --git a/Oqtane.Client/Services/IUserService.cs b/Oqtane.Client/Services/IUserService.cs index c11e8e80..e1b42bdd 100644 --- a/Oqtane.Client/Services/IUserService.cs +++ b/Oqtane.Client/Services/IUserService.cs @@ -10,6 +10,8 @@ namespace Oqtane.Services Task GetUserAsync(int UserId); + Task GetUserAsync(string Username); + Task AddUserAsync(User user); Task UpdateUserAsync(User user); diff --git a/Oqtane.Client/Services/ModuleService.cs b/Oqtane.Client/Services/ModuleService.cs index 3b4b6d74..ede01dc5 100644 --- a/Oqtane.Client/Services/ModuleService.cs +++ b/Oqtane.Client/Services/ModuleService.cs @@ -39,6 +39,11 @@ namespace Oqtane.Services return modules.ToList(); } + public async Task GetModuleAsync(int ModuleId) + { + return await http.GetJsonAsync(apiurl + "/" + ModuleId.ToString()); + } + public async Task AddModuleAsync(Module module) { await http.PostJsonAsync(apiurl, module); diff --git a/Oqtane.Client/Services/PageService.cs b/Oqtane.Client/Services/PageService.cs index 172d7950..fb54c32c 100644 --- a/Oqtane.Client/Services/PageService.cs +++ b/Oqtane.Client/Services/PageService.cs @@ -30,6 +30,11 @@ namespace Oqtane.Services return pages.OrderBy(item => item.Order).ToList(); } + public async Task GetPageAsync(int PageId) + { + return await http.GetJsonAsync(apiurl + "/" + PageId.ToString()); + } + public async Task AddPageAsync(Page page) { await http.PostJsonAsync(apiurl, page); diff --git a/Oqtane.Client/Services/SiteService.cs b/Oqtane.Client/Services/SiteService.cs index 7d7d135e..519155a5 100644 --- a/Oqtane.Client/Services/SiteService.cs +++ b/Oqtane.Client/Services/SiteService.cs @@ -32,17 +32,7 @@ namespace Oqtane.Services public async Task GetSiteAsync(int SiteId) { - List sites = await http.GetJsonAsync>(apiurl); - Site site; - if (sites.Count == 1) - { - site = sites.FirstOrDefault(); - } - else - { - site = sites.Where(item => item.SiteId == SiteId).FirstOrDefault(); - } - return site; + return await http.GetJsonAsync(apiurl + "/" + SiteId.ToString()); } public async Task AddSiteAsync(Site site) diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index fbe718e1..d83dc260 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -35,8 +35,12 @@ namespace Oqtane.Services public async Task GetUserAsync(int UserId) { - List users = await http.GetJsonAsync>(apiurl); - return users.Where(item => item.UserId == UserId).FirstOrDefault(); + return await http.GetJsonAsync(apiurl + "/" + UserId.ToString()); + } + + public async Task GetUserAsync(string Username) + { + return await http.GetJsonAsync(apiurl + "/name/" + Username); } public async Task AddUserAsync(User user) diff --git a/Oqtane.Client/Shared/Interop.cs b/Oqtane.Client/Shared/Interop.cs index 38acd4bf..3ece9f1d 100644 --- a/Oqtane.Client/Shared/Interop.cs +++ b/Oqtane.Client/Shared/Interop.cs @@ -13,17 +13,18 @@ namespace Oqtane.Shared this.jsRuntime = jsRuntime; } - public Task SetCookie(string name, string value, int days) + public Task SetCookie(string name, string value, int days) { try { - return jsRuntime.InvokeAsync( + jsRuntime.InvokeAsync( "interop.setCookie", name, value, days); + return Task.CompletedTask; } catch { - return Task.FromResult(string.Empty); + return Task.CompletedTask; } } @@ -41,18 +42,48 @@ namespace Oqtane.Shared } } - public Task AddCSS(string filename) + public Task AddCSS(string filename) + { + try + { + jsRuntime.InvokeAsync( + "interop.addCSS", + filename); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } + + public Task GetElementByName(string name) { try { return jsRuntime.InvokeAsync( - "interop.addCSS", - filename); + "interop.getElementByName", + name); } catch { return Task.FromResult(string.Empty); } } + + public Task SubmitForm(string path, object fields) + { + try + { + jsRuntime.InvokeAsync( + "interop.submitForm", + path, fields); + return Task.CompletedTask; + } + catch + { + return Task.CompletedTask; + } + } } } diff --git a/Oqtane.Client/Shared/SiteRouter.razor b/Oqtane.Client/Shared/SiteRouter.razor index b2cf5ab4..b8ec1bd2 100644 --- a/Oqtane.Client/Shared/SiteRouter.razor +++ b/Oqtane.Client/Shared/SiteRouter.razor @@ -127,7 +127,7 @@ private async Task Refresh() var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync(); if (authState.User.Identity.IsAuthenticated) { - user = await UserService.GetCurrentUserAsync(); + user = await UserService.GetUserAsync(authState.User.Identity.Name); } } else diff --git a/Oqtane.Client/Shared/Utilities.cs b/Oqtane.Client/Shared/Utilities.cs index 410e2b0e..9de11332 100644 --- a/Oqtane.Client/Shared/Utilities.cs +++ b/Oqtane.Client/Shared/Utilities.cs @@ -25,7 +25,14 @@ namespace Oqtane.Shared string url = pagestate.Alias.Path + "/" + path; if (reload) { - url += "?reload=true"; + if (url.Contains("?")) + { + url += "&reload=true"; + } + else + { + url += "?reload=true"; + } } return url; } diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs index 3ed914c5..b817e046 100644 --- a/Oqtane.Client/Startup.cs +++ b/Oqtane.Client/Startup.cs @@ -31,8 +31,8 @@ namespace Oqtane.Client { // register auth services services.AddAuthorizationCore(); - services.AddScoped(); - services.AddScoped(s => s.GetRequiredService()); + services.AddScoped(); + services.AddScoped(s => s.GetRequiredService()); // register scoped core services services.AddScoped(); diff --git a/Oqtane.Client/Themes/Controls/Login.razor b/Oqtane.Client/Themes/Controls/Login.razor index 898f9c0b..cae8a035 100644 --- a/Oqtane.Client/Themes/Controls/Login.razor +++ b/Oqtane.Client/Themes/Controls/Login.razor @@ -1,10 +1,14 @@ @using Oqtane.Themes @using Oqtane.Services @using Oqtane.Providers +@using Oqtane.Shared +@using Oqtane.Models +@using Microsoft.JSInterop @inherits ThemeObjectBase @inject IUriHelper UriHelper @inject IUserService UserService -@inject ServerAuthenticationStateProvider AuthStateProvider +@inject IJSRuntime jsRuntime +@inject IServiceProvider ServiceProvider @@ -22,13 +26,25 @@ @code { private void LoginUser() { - UriHelper.NavigateTo(NavigateUrl("login")); + UriHelper.NavigateTo(NavigateUrl("login?returnurl=" + PageState.Page.Path)); } private async Task LogoutUser() { await UserService.LogoutUserAsync(); - AuthStateProvider.NotifyAuthenticationChanged(); - UriHelper.NavigateTo(NavigateUrl("", true)); + + var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); + if (authstateprovider == null) + { + // server-side Blazor + var interop = new Interop(jsRuntime); + await interop.SubmitForm("/logout/", ""); + } + else + { + // client-side Blazor + authstateprovider.NotifyAuthenticationChanged(); + UriHelper.NavigateTo(NavigateUrl("login", true)); + } } } diff --git a/Oqtane.Client/wwwroot/js/interop.js b/Oqtane.Client/wwwroot/js/interop.js index b6beef83..a322ca40 100644 --- a/Oqtane.Client/wwwroot/js/interop.js +++ b/Oqtane.Client/wwwroot/js/interop.js @@ -29,5 +29,23 @@ window.interop = { link.href = fileName; head.appendChild(link); + }, + submitForm: function (path, fields) { + const form = document.createElement('form'); + form.method = 'post'; + form.action = path; + + for (const key in fields) { + if (fields.hasOwnProperty(key)) { + const hiddenField = document.createElement('input'); + hiddenField.type = 'hidden'; + hiddenField.name = key; + hiddenField.value = fields[key]; + form.appendChild(hiddenField); + } + } + + document.body.appendChild(form); + form.submit(); } }; diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index cfad0479..759515ee 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -4,6 +4,8 @@ using Oqtane.Repository; using Oqtane.Models; using Microsoft.AspNetCore.Identity; using System.Threading.Tasks; +using Microsoft.Extensions.Primitives; +using System.Security.Claims; namespace Oqtane.Controllers { @@ -34,7 +36,7 @@ namespace Oqtane.Controllers { return users.GetUser(id); } - + // POST api/ [HttpPost] public async Task Post([FromBody] User user) @@ -73,54 +75,48 @@ namespace Oqtane.Controllers users.DeleteUser(id); } - // GET api//current - [HttpGet("current")] - public User Current() + // GET api//name/x + [HttpGet("name/{name}")] + public User GetByName(string name) { - User user = null; - if (User.Identity.IsAuthenticated) - { - user = users.GetUser(User.Identity.Name); - user.IsAuthenticated = true; - } - return user; + return users.GetUser(name); } // POST api//login [HttpPost("login")] public async Task Login([FromBody] User user) { + // TODO: seed host user - this logic should be moved to installation + IdentityUser identityuser = await identityUserManager.FindByNameAsync("host"); + if (identityuser == null) + { + var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password"); + if (result.Succeeded) + { + users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" }); + } + } + if (ModelState.IsValid) { - // seed host user - this logic should be moved to installation - IdentityUser identityuser = await identityUserManager.FindByNameAsync("host"); - if (identityuser == null) - { - var result = await identityUserManager.CreateAsync(new IdentityUser { UserName = "host", Email = "host" }, "password"); - if (result.Succeeded) - { - users.AddUser(new Models.User { Username = "host", DisplayName = "host", IsSuperUser = true, Roles = "" }); - } - } - identityuser = await identityUserManager.FindByNameAsync(user.Username); if (identityuser != null) { var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, user.Password, false); if (result.Succeeded) { - await identitySignInManager.SignInAsync(identityuser, false); + await identitySignInManager.SignInAsync(identityuser, user.IsPersistent); user = users.GetUser(identityuser.UserName); user.IsAuthenticated = true; } else { - user = null; + user = new Models.User { Username = user.Username, IsAuthenticated = false }; } } else { - user = null; + user = new Models.User { Username = user.Username, IsAuthenticated = false }; } } return user; diff --git a/Oqtane.Server/Pages/Login.cshtml b/Oqtane.Server/Pages/Login.cshtml new file mode 100644 index 00000000..b0240ac6 --- /dev/null +++ b/Oqtane.Server/Pages/Login.cshtml @@ -0,0 +1,3 @@ +@page "/login" +@namespace Oqtane.Pages +@model Oqtane.Pages.LoginModel diff --git a/Oqtane.Server/Pages/Login.cshtml.cs b/Oqtane.Server/Pages/Login.cshtml.cs new file mode 100644 index 00000000..ed88df50 --- /dev/null +++ b/Oqtane.Server/Pages/Login.cshtml.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Oqtane.Pages +{ + [AllowAnonymous] + public class LoginModel : PageModel + { + + private readonly UserManager identityUserManager; + private readonly SignInManager identitySignInManager; + + public LoginModel(UserManager IdentityUserManager, SignInManager IdentitySignInManager) + { + identityUserManager = IdentityUserManager; + identitySignInManager = IdentitySignInManager; + } + + public async Task OnPostAsync(string username, string password, bool remember, string returnurl) + { + await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); + + bool validuser = false; + IdentityUser identityuser = await identityUserManager.FindByNameAsync(username); + if (identityuser != null) + { + var result = await identitySignInManager.CheckPasswordSignInAsync(identityuser, password, false); + if (result.Succeeded) + { + validuser = true; + } + } + + if (validuser) + { + var claims = new List{ new Claim(ClaimTypes.Name, username) }; + var claimsIdentity = new ClaimsIdentity(claims, IdentityConstants.ApplicationScheme); + var authProperties = new AuthenticationProperties{IsPersistent = remember}; + await HttpContext.SignInAsync(IdentityConstants.ApplicationScheme, new ClaimsPrincipal(claimsIdentity), authProperties); + } + + return LocalRedirect(Url.Content("~/" + returnurl)); + } + } +} \ No newline at end of file diff --git a/Oqtane.Server/Pages/Logout.cshtml b/Oqtane.Server/Pages/Logout.cshtml new file mode 100644 index 00000000..75a9fd7e --- /dev/null +++ b/Oqtane.Server/Pages/Logout.cshtml @@ -0,0 +1,3 @@ +@page "/logout" +@namespace Oqtane.Pages +@model Oqtane.Pages.LogoutModel diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs new file mode 100644 index 00000000..f66575c4 --- /dev/null +++ b/Oqtane.Server/Pages/Logout.cshtml.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Oqtane.Models; + +namespace Oqtane.Pages +{ + [IgnoreAntiforgeryToken(Order = 1001)] + [AllowAnonymous] + public class LogoutModel : PageModel + { + public async Task OnPostAsync() + { + await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); + + return LocalRedirect(Url.Content("~/")); + } + } +} \ No newline at end of file diff --git a/Oqtane.Server/Pages/_Host.cshtml b/Oqtane.Server/Pages/_Host.cshtml index af42db3b..30862539 100644 --- a/Oqtane.Server/Pages/_Host.cshtml +++ b/Oqtane.Server/Pages/_Host.cshtml @@ -14,6 +14,7 @@ + @(Html.AntiForgeryToken()) @(await Html.RenderComponentAsync()) diff --git a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs index 298bfd84..a16572fe 100644 --- a/Oqtane.Server/Repository/ModuleDefinitionRepository.cs +++ b/Oqtane.Server/Repository/ModuleDefinitionRepository.cs @@ -52,6 +52,7 @@ namespace Oqtane.Repository { /// determine if this module implements IModule Type moduletype = assembly.GetTypes() + .Where(item => item.Namespace != null) .Where(item => item.Namespace.StartsWith(ModuleType)) .Where(item => item.GetInterfaces().Contains(typeof(IModule))) .FirstOrDefault(); diff --git a/Oqtane.Server/Repository/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs index 4ba145ff..4685f9cd 100644 --- a/Oqtane.Server/Repository/TenantResolver.cs +++ b/Oqtane.Server/Repository/TenantResolver.cs @@ -24,7 +24,7 @@ namespace Oqtane.Repository aliasname = accessor.HttpContext.Request.Host.Value; string path = accessor.HttpContext.Request.Path.Value; string[] segments = path.Split('/'); - if (segments[1] != "~") + if (segments[0] == "api" && segments[1] != "~") { aliasname += "/" + segments[1]; } diff --git a/Oqtane.Server/Repository/ThemeRepository.cs b/Oqtane.Server/Repository/ThemeRepository.cs index 2c02cf2c..aec59868 100644 --- a/Oqtane.Server/Repository/ThemeRepository.cs +++ b/Oqtane.Server/Repository/ThemeRepository.cs @@ -52,6 +52,7 @@ namespace Oqtane.Repository { /// determine if this theme implements ITheme Type themeType = assembly.GetTypes() + .Where(item => item.Namespace != null) .Where(item => item.Namespace.StartsWith(Namespace)) .Where(item => item.GetInterfaces().Contains(typeof(ITheme))).FirstOrDefault(); if (themeType != null) @@ -105,7 +106,10 @@ namespace Oqtane.Repository theme.ThemeControls += (themeControlType.FullName + ", " + typename[1] + ";"); } // containers - Type[] containertypes = assembly.GetTypes().Where(item => item.Namespace.StartsWith(Namespace)).Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray(); + Type[] containertypes = assembly.GetTypes() + .Where(item => item.Namespace != null) + .Where(item => item.Namespace.StartsWith(Namespace)) + .Where(item => item.GetInterfaces().Contains(typeof(IContainerControl))).ToArray(); foreach (Type containertype in containertypes) { theme.ContainerControls += (containertype.FullName + ", " + typename[1] + ";"); diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index 0e29d5df..ab2de131 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -17,12 +17,9 @@ using System.Runtime.Loader; using Oqtane.Services; using System.Net.Http; using Microsoft.AspNetCore.Components; -using Oqtane.Client; using Oqtane.Shared; using Microsoft.AspNetCore.Identity; -using Oqtane.Providers; using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication.Cookies; namespace Oqtane.Server { @@ -47,25 +44,27 @@ namespace Oqtane.Server services.AddRazorPages(); services.AddServerSideBlazor(); - // server-side Blazor does not register HttpClient by default + // setup HttpClient for server side in a client side compatible fashion ( with auth cookie ) if (!services.Any(x => x.ServiceType == typeof(HttpClient))) { - // setup HttpClient for server side in a client side compatible fashion services.AddScoped(s => { // creating the URI helper needs to wait until the JS Runtime is initialized, so defer it. var uriHelper = s.GetRequiredService(); - return new HttpClient + var httpContextAccessor = s.GetRequiredService(); + var authToken = httpContextAccessor.HttpContext.Request.Cookies[".AspNetCore.Identity.Application"]; + var client = new HttpClient(new HttpClientHandler { UseCookies = false }); + if (authToken != null) { - BaseAddress = new Uri(uriHelper.GetBaseUri()) - }; + client.DefaultRequestHeaders.Add("Cookie", ".AspNetCore.Identity.Application=" + authToken); + } + client.BaseAddress = new Uri(uriHelper.GetBaseUri()); + return client; }); } - // register auth services + // register auth services services.AddAuthorizationCore(); - services.AddScoped(); - services.AddScoped(s => s.GetRequiredService()); // register scoped core services services.AddScoped(); diff --git a/Oqtane.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js index b6beef83..db61ecbe 100644 --- a/Oqtane.Server/wwwroot/js/interop.js +++ b/Oqtane.Server/wwwroot/js/interop.js @@ -20,6 +20,14 @@ window.interop = { } return ""; }, + getElementByName: function (name) { + var elements = document.getElementsByName(name); + if (elements.length) { + return elements[0].value; + } else { + return ""; + } + }, addCSS: function (fileName) { var head = document.head; var link = document.createElement("link"); @@ -29,5 +37,23 @@ window.interop = { link.href = fileName; head.appendChild(link); + }, + submitForm: function (path, fields) { + const form = document.createElement('form'); + form.method = 'post'; + form.action = path; + + for (const key in fields) { + if (fields.hasOwnProperty(key)) { + const hiddenField = document.createElement('input'); + hiddenField.type = 'hidden'; + hiddenField.name = key; + hiddenField.value = fields[key]; + form.appendChild(hiddenField); + } + } + + document.body.appendChild(form); + form.submit(); } }; diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs index d2bd5f6d..34b27776 100644 --- a/Oqtane.Shared/Models/User.cs +++ b/Oqtane.Shared/Models/User.cs @@ -14,5 +14,7 @@ namespace Oqtane.Models public string Password { get; set; } [NotMapped] public bool IsAuthenticated { get; set; } + [NotMapped] + public bool IsPersistent { get; set; } } }