Merge pull request #39 from sbwalker/master
Refactor authentication to support server-side Blazor
This commit is contained in:
		@ -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
 | 
			
		||||
 | 
			
		||||
<AuthorizeView>
 | 
			
		||||
    <Authorizing>
 | 
			
		||||
@ -28,33 +29,62 @@
 | 
			
		||||
                <label for="Password" class="control-label">Password: </label>
 | 
			
		||||
                <input type="password" name="Password" class="form-control" placeholder="Password" @bind="@Password" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div class="form-group">
 | 
			
		||||
                <div class="form-check form-check-inline">
 | 
			
		||||
                    <label class="form-check-label" for="Remember">Remember Me?</label> 
 | 
			
		||||
                    <input type="checkbox" class="form-check-input" name="Remember" @bind="@Remember" />
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
            <button type="button" class="btn btn-primary" @onclick="@Login">Login</button>
 | 
			
		||||
            <NavLink class="btn btn-secondary" href="/">Cancel</NavLink>
 | 
			
		||||
            <button type="button" class="btn btn-secondary" @onclick="@Cancel">Cancel</button>
 | 
			
		||||
        </div>
 | 
			
		||||
    </NotAuthorized>
 | 
			
		||||
</AuthorizeView>
 | 
			
		||||
 | 
			
		||||
@code {
 | 
			
		||||
    public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
 | 
			
		||||
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
 | 
			
		||||
 | 
			
		||||
    public string Message { get; set; } = "<div class=\"alert alert-info\" role=\"alert\">Use host/password For Demo Access</div>";
 | 
			
		||||
    public string Username { get; set; } = "";
 | 
			
		||||
    public string Password { get; set; } = "";
 | 
			
		||||
public string Message { get; set; } = "<div class=\"alert alert-info\" role=\"alert\">Use host/password For Demo Access</div>";
 | 
			
		||||
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 = "<div class=\"alert alert-danger\" role=\"alert\">User Does Not Exist</div>";
 | 
			
		||||
            // client-side Blazor
 | 
			
		||||
            authstateprovider.NotifyAuthenticationChanged();
 | 
			
		||||
            UriHelper.NavigateTo(NavigateUrl(ReturnUrl, true));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    else
 | 
			
		||||
    {
 | 
			
		||||
        Message = "<div class=\"alert alert-danger\" role=\"alert\">Login Failed. Please Remember That Passwords Are Case Sensitive.</div>";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private void Cancel()
 | 
			
		||||
{
 | 
			
		||||
    string ReturnUrl = PageState.QueryString["returnurl"];
 | 
			
		||||
    UriHelper.NavigateTo(NavigateUrl(ReturnUrl));
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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<User>(apiurl);
 | 
			
		||||
 | 
			
		||||
            var identity = user.IsAuthenticated
 | 
			
		||||
                ? new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, user.Username) }, "Identity.Application")
 | 
			
		||||
                : new ClaimsIdentity();
 | 
			
		||||
@ -32,8 +32,7 @@ namespace Oqtane.Services
 | 
			
		||||
 | 
			
		||||
        public async Task<Alias> GetAliasAsync(int AliasId)
 | 
			
		||||
        {
 | 
			
		||||
            List<Alias> aliases = await http.GetJsonAsync<List<Alias>>(apiurl);
 | 
			
		||||
            return aliases.Where(item => item.AliasId == AliasId).FirstOrDefault();
 | 
			
		||||
            return await http.GetJsonAsync<Alias>(apiurl + "/" + AliasId.ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task AddAliasAsync(Alias alias)
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ namespace Oqtane.Services
 | 
			
		||||
    {
 | 
			
		||||
        Task<List<Module>> GetModulesAsync(int PageId);
 | 
			
		||||
        Task<List<Module>> GetModulesAsync(int SiteId, string ModuleDefinitionName);
 | 
			
		||||
        Task<Module> GetModuleAsync(int ModuleId);
 | 
			
		||||
        Task AddModuleAsync(Module module);
 | 
			
		||||
        Task UpdateModuleAsync(Module module);
 | 
			
		||||
        Task DeleteModuleAsync(int ModuleId);
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ namespace Oqtane.Services
 | 
			
		||||
    public interface IPageService
 | 
			
		||||
    {
 | 
			
		||||
        Task<List<Page>> GetPagesAsync(int SiteId);
 | 
			
		||||
        Task<Page> GetPageAsync(int PageId);
 | 
			
		||||
        Task AddPageAsync(Page page);
 | 
			
		||||
        Task UpdatePageAsync(Page page);
 | 
			
		||||
        Task DeletePageAsync(int PageId);
 | 
			
		||||
 | 
			
		||||
@ -10,6 +10,8 @@ namespace Oqtane.Services
 | 
			
		||||
 | 
			
		||||
        Task<User> GetUserAsync(int UserId);
 | 
			
		||||
 | 
			
		||||
        Task<User> GetUserAsync(string Username);
 | 
			
		||||
 | 
			
		||||
        Task AddUserAsync(User user);
 | 
			
		||||
 | 
			
		||||
        Task UpdateUserAsync(User user);
 | 
			
		||||
 | 
			
		||||
@ -39,6 +39,11 @@ namespace Oqtane.Services
 | 
			
		||||
            return modules.ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Module> GetModuleAsync(int ModuleId)
 | 
			
		||||
        {
 | 
			
		||||
            return await http.GetJsonAsync<Module>(apiurl + "/" + ModuleId.ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task AddModuleAsync(Module module)
 | 
			
		||||
        {
 | 
			
		||||
            await http.PostJsonAsync(apiurl, module);
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,11 @@ namespace Oqtane.Services
 | 
			
		||||
            return pages.OrderBy(item => item.Order).ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<Page> GetPageAsync(int PageId)
 | 
			
		||||
        {
 | 
			
		||||
            return await http.GetJsonAsync<Page>(apiurl + "/" + PageId.ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task AddPageAsync(Page page)
 | 
			
		||||
        {
 | 
			
		||||
            await http.PostJsonAsync(apiurl, page);
 | 
			
		||||
 | 
			
		||||
@ -32,17 +32,7 @@ namespace Oqtane.Services
 | 
			
		||||
 | 
			
		||||
        public async Task<Site> GetSiteAsync(int SiteId)
 | 
			
		||||
        {
 | 
			
		||||
            List<Site> sites = await http.GetJsonAsync<List<Site>>(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<Site>(apiurl + "/" + SiteId.ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task AddSiteAsync(Site site)
 | 
			
		||||
 | 
			
		||||
@ -35,8 +35,12 @@ namespace Oqtane.Services
 | 
			
		||||
 | 
			
		||||
        public async Task<User> GetUserAsync(int UserId)
 | 
			
		||||
        {
 | 
			
		||||
            List<User> users = await http.GetJsonAsync<List<User>>(apiurl);
 | 
			
		||||
            return users.Where(item => item.UserId == UserId).FirstOrDefault();
 | 
			
		||||
            return await http.GetJsonAsync<User>(apiurl + "/" + UserId.ToString());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<User> GetUserAsync(string Username)
 | 
			
		||||
        {
 | 
			
		||||
            return await http.GetJsonAsync<User>(apiurl + "/name/" + Username);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task AddUserAsync(User user)
 | 
			
		||||
 | 
			
		||||
@ -13,17 +13,18 @@ namespace Oqtane.Shared
 | 
			
		||||
            this.jsRuntime = jsRuntime;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<string> SetCookie(string name, string value, int days)
 | 
			
		||||
        public Task SetCookie(string name, string value, int days)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return jsRuntime.InvokeAsync<string>(
 | 
			
		||||
                jsRuntime.InvokeAsync<string>(
 | 
			
		||||
                "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<string> AddCSS(string filename)
 | 
			
		||||
        public Task AddCSS(string filename)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                jsRuntime.InvokeAsync<string>(
 | 
			
		||||
                    "interop.addCSS",
 | 
			
		||||
                    filename);
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<string> GetElementByName(string name)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return jsRuntime.InvokeAsync<string>(
 | 
			
		||||
                    "interop.addCSS",
 | 
			
		||||
                    filename);
 | 
			
		||||
                    "interop.getElementByName",
 | 
			
		||||
                    name);
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return Task.FromResult(string.Empty);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task SubmitForm(string path, object fields)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                jsRuntime.InvokeAsync<string>(
 | 
			
		||||
                "interop.submitForm",
 | 
			
		||||
                path, fields);
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -31,8 +31,8 @@ namespace Oqtane.Client
 | 
			
		||||
        {
 | 
			
		||||
            // register auth services
 | 
			
		||||
            services.AddAuthorizationCore();
 | 
			
		||||
            services.AddScoped<ServerAuthenticationStateProvider>();
 | 
			
		||||
            services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>());
 | 
			
		||||
            services.AddScoped<IdentityAuthenticationStateProvider>();
 | 
			
		||||
            services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<IdentityAuthenticationStateProvider>());
 | 
			
		||||
 | 
			
		||||
            // register scoped core services
 | 
			
		||||
            services.AddScoped<SiteState>();
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
<AuthorizeView>
 | 
			
		||||
    <Authorizing>
 | 
			
		||||
@ -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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -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/<controller>
 | 
			
		||||
        [HttpPost]
 | 
			
		||||
        public async Task Post([FromBody] User user)
 | 
			
		||||
@ -73,54 +75,48 @@ namespace Oqtane.Controllers
 | 
			
		||||
            users.DeleteUser(id);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // GET api/<controller>/current
 | 
			
		||||
        [HttpGet("current")]
 | 
			
		||||
        public User Current()
 | 
			
		||||
        // GET api/<controller>/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/<controller>/login
 | 
			
		||||
        [HttpPost("login")]
 | 
			
		||||
        public async Task<User> 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;
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										3
									
								
								Oqtane.Server/Pages/Login.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Oqtane.Server/Pages/Login.cshtml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
@page "/login"
 | 
			
		||||
@namespace  Oqtane.Pages
 | 
			
		||||
@model Oqtane.Pages.LoginModel
 | 
			
		||||
							
								
								
									
										52
									
								
								Oqtane.Server/Pages/Login.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								Oqtane.Server/Pages/Login.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							@ -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<IdentityUser> identityUserManager;
 | 
			
		||||
        private readonly SignInManager<IdentityUser> identitySignInManager;
 | 
			
		||||
 | 
			
		||||
        public LoginModel(UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager)
 | 
			
		||||
        {
 | 
			
		||||
            identityUserManager = IdentityUserManager;
 | 
			
		||||
            identitySignInManager = IdentitySignInManager;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task<IActionResult> 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<Claim>{ 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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										3
									
								
								Oqtane.Server/Pages/Logout.cshtml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								Oqtane.Server/Pages/Logout.cshtml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
			
		||||
@page "/logout"
 | 
			
		||||
@namespace Oqtane.Pages
 | 
			
		||||
@model Oqtane.Pages.LogoutModel
 | 
			
		||||
							
								
								
									
										26
									
								
								Oqtane.Server/Pages/Logout.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Oqtane.Server/Pages/Logout.cshtml.cs
									
									
									
									
									
										Normal file
									
								
							@ -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<IActionResult> OnPostAsync()
 | 
			
		||||
        {
 | 
			
		||||
            await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme);
 | 
			
		||||
 | 
			
		||||
            return LocalRedirect(Url.Content("~/"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -14,6 +14,7 @@
 | 
			
		||||
    <link href="css/site.css" rel="stylesheet" />
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
    @(Html.AntiForgeryToken())
 | 
			
		||||
    <app>@(await Html.RenderComponentAsync<App>())</app>
 | 
			
		||||
 | 
			
		||||
    <script src="js/site.js"></script>
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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];
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -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] + ";");
 | 
			
		||||
 | 
			
		||||
@ -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<HttpClient>(s =>
 | 
			
		||||
                {
 | 
			
		||||
                    // creating the URI helper needs to wait until the JS Runtime is initialized, so defer it.
 | 
			
		||||
                    var uriHelper = s.GetRequiredService<IUriHelper>();
 | 
			
		||||
                    return new HttpClient
 | 
			
		||||
                    var httpContextAccessor = s.GetRequiredService<IHttpContextAccessor>();
 | 
			
		||||
                    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<ServerAuthenticationStateProvider>();
 | 
			
		||||
            services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>());
 | 
			
		||||
 | 
			
		||||
            // register scoped core services
 | 
			
		||||
            services.AddScoped<SiteState>();
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@ -14,5 +14,7 @@ namespace Oqtane.Models
 | 
			
		||||
        public string Password { get; set; }
 | 
			
		||||
        [NotMapped]
 | 
			
		||||
        public bool IsAuthenticated { get; set; }
 | 
			
		||||
        [NotMapped]
 | 
			
		||||
        public bool IsPersistent { get; set; }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user