Merge pull request #37 from sbwalker/master

Integrated AuthenticatedStateProvider using .NET Core Identity. Solution works fine when running in client-side mode ( Wasm build profile ) but does not set cookie in server-side mode.
This commit is contained in:
Shaun Walker 2019-07-08 12:55:03 -04:00 committed by GitHub
commit 61171e4844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 565 additions and 137 deletions

View File

@ -1,9 +1,11 @@
@using Oqtane.Shared
@using Oqtane.Client.Shared
<CascadingValue Value="@PageState">
<SiteRouter OnStateChange="@ChangeState" />
</CascadingValue>
<CascadingAuthenticationState>
<CascadingValue Value="@PageState">
<SiteRouter OnStateChange="@ChangeState" />
</CascadingValue>
</CascadingAuthenticationState>
@code {
private PageState PageState { get; set; }

View File

@ -1,45 +1,56 @@
@using Microsoft.AspNetCore.Components.Routing
@using Oqtane.Shared
@using Oqtane.Modules
@using Microsoft.JSInterop
@using Oqtane.Models
@using Oqtane.Services
@using Oqtane.Client.Modules.Controls
@using Oqtane.Providers
@inherits ModuleBase
@inject IUriHelper UriHelper
@inject IJSRuntime jsRuntime
@inject IUserService UserService
@inject IUserService UserService
@inject ServerAuthenticationStateProvider AuthStateProvider
<div class="container">
@((MarkupString)Message)
<div class="form-group">
<label for="Username" class="control-label">Username: </label>
<input type="text" name="Username" class="form-control" placeholder="Username" @bind="@Username" />
</div>
<div class="form-group">
<label for="Password" class="control-label">Password: </label>
<input type="password" name="Password" class="form-control" placeholder="Password" @bind="@Password" />
</div>
<button type="button" class="btn btn-primary" @onclick="@Login">Login</button>
<NavLink class="btn btn-secondary" href="/">Cancel</NavLink>
</div>
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
You are already logged in
</Authorized>
<NotAuthorized>
<div class="container">
@((MarkupString)Message)
<div class="form-group">
<label for="Username" class="control-label">Username: </label>
<input type="text" name="Username" class="form-control" placeholder="Username" @bind="@Username" />
</div>
<div class="form-group">
<label for="Password" class="control-label">Password: </label>
<input type="password" name="Password" class="form-control" placeholder="Password" @bind="@Password" />
</div>
<button type="button" class="btn btn-primary" @onclick="@Login">Login</button>
<NavLink class="btn btn-secondary" href="/">Cancel</NavLink>
</div>
</NotAuthorized>
</AuthorizeView>
@code {
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
public string Message { get; set; } = "<div class=\"alert alert-info\" role=\"alert\">Use host/host For Demo Access</div>";
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; } = "";
private async Task Login()
{
List<User> users = await UserService.GetUsersAsync();
User user = users.Where(item => item.Username == Username).FirstOrDefault();
User user = new User();
user.Username = Username;
user.Password = Password;
user = await UserService.LoginUserAsync(user);
if (user != null)
{
var interop = new Interop(jsRuntime);
await interop.SetCookie("user", user.UserId.ToString(), 7);
UriHelper.NavigateTo(NavigateUrl(""), true);
AuthStateProvider.NotifyAuthenticationChanged();
UriHelper.NavigateTo(NavigateUrl(""));
}
else
{

View File

@ -1,12 +1,10 @@
@using Microsoft.AspNetCore.Components.Routing
@using Oqtane.Shared
@using Oqtane.Modules
@using Microsoft.JSInterop
@using Oqtane.Client.Modules.Controls
@using Oqtane.Models
@using Oqtane.Services
@inherits ModuleBase
@inject IUriHelper UriHelper
@inject IJSRuntime jsRuntime
@inject IUserService UserService
<div class="container">
<div class="form-group">
@ -17,13 +15,25 @@
<label for="Password" class="control-label">Password: </label>
<input type="password" name="Password" class="form-control" placeholder="Password" @bind="@Password" />
</div>
<button type="button" class="btn btn-primary">Register</button>
<button type="button" class="btn btn-primary" @onclick="@RegisterUser">Register</button>
<NavLink class="btn btn-secondary" href="/">Cancel</NavLink>
</div>
@code {
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
public string Username { get; set; } = "";
public string Password { get; set; } = "";
public string Username { get; set; } = "";
public string Password { get; set; } = "";
private async Task RegisterUser()
{
User user = new User();
user.Username = Username;
user.DisplayName = Username;
user.Roles = "Administrators;";
user.IsSuperUser = false;
user.Password = Password;
await UserService.AddUserAsync(user);
UriHelper.NavigateTo("");
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Net.Http;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components;
using Oqtane.Models;
namespace Oqtane.Providers
{
public class ServerAuthenticationStateProvider : AuthenticationStateProvider
{
//private readonly IUserService UserService;
private readonly IUriHelper urihelper;
public ServerAuthenticationStateProvider(IUriHelper urihelper)
{
//this.UserService = UserService;
this.urihelper = urihelper;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
// hack: create a new HttpClient rather than relying on the registered service as the AuthenticationStateProvider is initialized prior to IUriHelper ( https://github.com/aspnet/AspNetCore/issues/11867 )
HttpClient http = new HttpClient();
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();
return new AuthenticationState(new ClaimsPrincipal(identity));
}
public void NotifyAuthenticationChanged()
{
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
}
}

View File

@ -16,6 +16,12 @@ namespace Oqtane.Services
Task DeleteUserAsync(int UserId);
Task<User> GetCurrentUserAsync();
Task<User> LoginUserAsync(User user);
Task LogoutUserAsync();
bool IsAuthorized(User user, string accesscontrollist);
}
}

View File

@ -13,11 +13,13 @@ namespace Oqtane.Services
{
private readonly HttpClient http;
private readonly SiteState sitestate;
private readonly IUriHelper urihelper;
public UserService(HttpClient http, SiteState sitestate)
public UserService(HttpClient http, SiteState sitestate, IUriHelper urihelper)
{
this.http = http;
this.sitestate = sitestate;
this.urihelper = urihelper;
}
private string apiurl
@ -35,7 +37,7 @@ namespace Oqtane.Services
{
List<User> users = await http.GetJsonAsync<List<User>>(apiurl);
return users.Where(item => item.UserId == UserId).FirstOrDefault();
}
}
public async Task AddUserAsync(User user)
{
@ -51,6 +53,22 @@ namespace Oqtane.Services
await http.DeleteAsync(apiurl + "/" + UserId.ToString());
}
public async Task<User> GetCurrentUserAsync()
{
return await http.GetJsonAsync<User>(apiurl + "/current");
}
public async Task<User> LoginUserAsync(User user)
{
return await http.PostJsonAsync<User>(apiurl + "/login", user);
}
public async Task LogoutUserAsync()
{
// best practices recommend post is preferrable to get for logout
await http.PostJsonAsync(apiurl + "/logout", null);
}
// ACLs are stored in the format "!rolename1;![userid1];rolename2;rolename3;[userid2];[userid3]" where "!" designates Deny permissions
public bool IsAuthorized(User user, string accesscontrollist)
{

View File

@ -6,6 +6,7 @@
@using Oqtane.Shared
@using Microsoft.JSInterop
@using Microsoft.AspNetCore.Components.Routing
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject SiteState SiteState
@inject IUriHelper UriHelper
@inject INavigationInterception NavigationInterception
@ -106,15 +107,13 @@ private async Task Refresh()
}
if (site != null || reload == true)
{
var interop = new Interop(jsRuntime);
string userid = await interop.GetCookie("user");
user = null;
if (PageState == null || reload == true)
{
if (!string.IsNullOrEmpty(userid))
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
if (authState.User.Identity.IsAuthenticated)
{
user = await UserService.GetUserAsync(int.Parse(userid));
user = await UserService.GetCurrentUserAsync();
}
}
else
@ -122,23 +121,6 @@ private async Task Refresh()
user = PageState.User;
}
if (!string.IsNullOrEmpty(userid))
{
if (user != null && user.UserId != int.Parse(userid))
{
user = await UserService.GetUserAsync(int.Parse(userid));
}
// this is a hack for server-side Blazor where JSInterop is not working OnInit() which means the userid is not being retrieved from the cookie on the initial render and is not being loaded into PageState
if (user == null)
{
user = await UserService.GetUserAsync(int.Parse(userid));
}
}
else
{
user = null;
}
string path = new Uri(_absoluteUri).PathAndQuery.Substring(1);
if (path.EndsWith("/")) { path = path.Substring(0, path.Length - 1); }
if (alias.Path != "")

View File

@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Components;
using System.Reflection;
using Oqtane.Modules;
using Oqtane.Shared;
using Oqtane.Providers;
using Microsoft.AspNetCore.Blazor.Http;
namespace Oqtane.Client
{
@ -27,6 +29,11 @@ namespace Oqtane.Client
#if WASM
public void ConfigureServices(IServiceCollection services)
{
// register auth services
services.AddAuthorizationCore();
services.AddScoped<ServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>());
// register scoped core services
services.AddScoped<SiteState>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
@ -39,6 +46,7 @@ namespace Oqtane.Client
services.AddScoped<IPageModuleService, PageModuleService>();
services.AddScoped<IUserService, UserService>();
// dynamically register module contexts and repository services
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies)
@ -63,6 +71,7 @@ namespace Oqtane.Client
public void Configure(IComponentsApplicationBuilder app)
{
WebAssemblyHttpMessageHandler.DefaultCredentials = FetchCredentialsOption.Include;
app.AddComponent<App>("app");
}
#endif

View File

@ -1,41 +1,34 @@
@using Oqtane.Themes
@using Oqtane.Shared
@using Microsoft.JSInterop
@using Oqtane.Services
@using Oqtane.Providers
@inherits ThemeObjectBase
@inject IUriHelper UriHelper
@inject IJSRuntime jsRuntime
@inject IUserService UserService
@inject ServerAuthenticationStateProvider AuthStateProvider
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="@LogoutUser">Logout</button>
</Authorized>
<NotAuthorized>
<button type="button" class="btn btn-primary" @onclick="@LoginUser">Login</button>
</NotAuthorized>
</AuthorizeView>
<button type="button" class="btn btn-primary" @onclick="@Click">@name</button>
@code {
string name = "";
protected override async Task OnInitAsync()
private void LoginUser()
{
var interop = new Interop(jsRuntime);
string user = await interop.GetCookie("user");
if (user == "")
{
name = "Login";
}
else
{
name = "Logout";
}
UriHelper.NavigateTo(NavigateUrl("login"));
}
private async Task Click()
private async Task LogoutUser()
{
if (name == "Login")
{
UriHelper.NavigateTo(NavigateUrl("login"));
}
else
{
var interop = new Interop(jsRuntime);
await interop.SetCookie("user", "", 7);
UriHelper.NavigateTo(NavigateUrl(""), true);
}
await UserService.LogoutUserAsync();
AuthStateProvider.NotifyAuthenticationChanged();
UriHelper.NavigateTo(NavigateUrl(""));
}
}

View File

@ -1,35 +1,25 @@
@using Microsoft.AspNetCore.Components.Routing
@using Oqtane.Themes
@using Oqtane.Shared
@using Oqtane.Services;
@using Oqtane.Models;
@using Microsoft.JSInterop
@inject IJSRuntime jsRuntime
@using Oqtane.Themes
@inherits ThemeObjectBase
@inject IUriHelper UriHelper
<AuthorizeView>
<Authorizing>
<text>...</text>
</Authorizing>
<Authorized>
<button type="button" class="btn btn-primary" @onclick="@RegisterUser">@context.User.Identity.Name</button>
</Authorized>
<NotAuthorized>
<button type="button" class="btn btn-primary" @onclick="@RegisterUser">Register</button>
</NotAuthorized>
</AuthorizeView>
<NavLink class="btn btn-primary" href="@url">@name</NavLink>
@code {
string name = "";
string url = "";
protected override async Task OnInitAsync()
private void RegisterUser()
{
var interop = new Interop(jsRuntime);
string userid = await interop.GetCookie("user");
if (userid == "")
{
name = "Register";
url = "register";
}
else
{
if (PageState.User != null)
{
name = PageState.User.DisplayName;
}
}
UriHelper.NavigateTo(NavigateUrl("register"));
}
}

View File

@ -2,6 +2,8 @@
using Microsoft.AspNetCore.Mvc;
using Oqtane.Repository;
using Oqtane.Models;
using Microsoft.AspNetCore.Identity;
using System.Threading.Tasks;
namespace Oqtane.Controllers
{
@ -9,10 +11,14 @@ namespace Oqtane.Controllers
public class UserController : Controller
{
private readonly IUserRepository users;
private readonly UserManager<IdentityUser> identityUserManager;
private readonly SignInManager<IdentityUser> identitySignInManager;
public UserController(IUserRepository Users)
public UserController(IUserRepository Users, UserManager<IdentityUser> IdentityUserManager, SignInManager<IdentityUser> IdentitySignInManager)
{
users = Users;
identityUserManager = IdentityUserManager;
identitySignInManager = IdentitySignInManager;
}
// GET: api/<controller>
@ -31,10 +37,23 @@ namespace Oqtane.Controllers
// POST api/<controller>
[HttpPost]
public void Post([FromBody] User user)
public async Task Post([FromBody] User user)
{
if (ModelState.IsValid)
users.AddUser(user);
{
IdentityUser identityuser = await identityUserManager.FindByNameAsync(user.Username);
if (identityuser == null)
{
identityuser = new IdentityUser();
identityuser.UserName = user.Username;
identityuser.Email = user.Username;
var result = await identityUserManager.CreateAsync(identityuser, user.Password);
if (result.Succeeded)
{
users.AddUser(user);
}
}
}
}
// PUT api/<controller>/5
@ -42,7 +61,9 @@ namespace Oqtane.Controllers
public void Put(int id, [FromBody] User user)
{
if (ModelState.IsValid)
{
users.UpdateUser(user);
}
}
// DELETE api/<controller>/5
@ -51,5 +72,72 @@ namespace Oqtane.Controllers
{
users.DeleteUser(id);
}
// GET api/<controller>/current
[HttpGet("current")]
public User Current()
{
User user = null;
if (User.Identity.IsAuthenticated)
{
user = users.GetUser(User.Identity.Name);
user.IsAuthenticated = true;
}
return user;
}
// POST api/<controller>/login
[HttpPost("login")]
public async Task<User> Login([FromBody] User user)
{
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);
user = users.GetUser(identityuser.UserName);
user.IsAuthenticated = true;
}
else
{
user = null;
}
}
else
{
user = null;
}
}
return user;
}
// POST api/<controller>/logout
[HttpPost("logout")]
public async Task Logout([FromBody] User user)
{
await identitySignInManager.SignOutAsync();
}
// GET api/<controller>/current
[HttpGet("authenticate")]
public User Authenticate()
{
return new User { Username = User.Identity.Name, IsAuthenticated = User.Identity.IsAuthenticated };
}
}
}

View File

@ -7,6 +7,7 @@ using DbUp;
using System.Data.SqlClient;
using System.Threading;
using System.IO;
using Microsoft.AspNetCore.Identity;
namespace Oqtane.Filters
{
@ -109,6 +110,7 @@ namespace Oqtane.Filters
throw new Exception();
}
}
return next;
}
}

View File

@ -26,11 +26,13 @@
</PropertyGroup>
<ItemGroup>
<None Remove="Scripts\Identity.sql" />
<None Remove="Scripts\Master.sql" />
</ItemGroup>
<ItemGroup>
<Content Include="Scripts\Master.sql" />
<EmbeddedResource Include="Scripts\Identity.sql" />
<EmbeddedResource Include="Scripts\Tenant.sql" />
</ItemGroup>
@ -38,8 +40,9 @@
<PackageReference Include="dbup" Version="4.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Blazor.Server" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.3" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.3" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0-preview6.19307.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0-preview6.19304.10" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0-preview6.19304.10" />
</ItemGroup>
<ItemGroup>

View File

@ -9,6 +9,7 @@ namespace Oqtane.Repository
void AddUser(User User);
void UpdateUser(User User);
User GetUser(int UserId);
User GetUser(string Username);
void DeleteUser(int UserId);
}
}

View File

@ -1,10 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
using System;
namespace Oqtane.Repository
{
public class TenantContext : DbContext
public class TenantContext : IdentityDbContext<IdentityUser>
{
public virtual DbSet<Site> Site { get; set; }
public virtual DbSet<Page> Page { get; set; }

View File

@ -28,6 +28,10 @@ namespace Oqtane.Repository
{
aliasname += "/" + segments[1];
}
if (aliasname.EndsWith("/"))
{
aliasname = aliasname.Substring(0, aliasname.Length - 1);
}
}
public Tenant GetTenant()

View File

@ -65,6 +65,19 @@ namespace Oqtane.Repository
}
}
public User GetUser(string Username)
{
try
{
User user = db.User.Where(item => item.Username == Username).FirstOrDefault();
return user;
}
catch
{
throw;
}
}
public void DeleteUser(int userId)
{
try

View File

@ -0,0 +1,190 @@
CREATE TABLE [dbo].[AspNetRoleClaims](
[Id] [int] IDENTITY(1,1) NOT NULL,
[RoleId] [nvarchar](450) NOT NULL,
[ClaimType] [nvarchar](max) NULL,
[ClaimValue] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetRoleClaims] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetRoles](
[Id] [nvarchar](450) NOT NULL,
[Name] [nvarchar](256) NULL,
[NormalizedName] [nvarchar](256) NULL,
[ConcurrencyStamp] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetRoles] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserClaims](
[Id] [int] IDENTITY(1,1) NOT NULL,
[UserId] [nvarchar](450) NOT NULL,
[ClaimType] [nvarchar](max) NULL,
[ClaimValue] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetUserClaims] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserLogins](
[LoginProvider] [nvarchar](128) NOT NULL,
[ProviderKey] [nvarchar](128) NOT NULL,
[ProviderDisplayName] [nvarchar](max) NULL,
[UserId] [nvarchar](450) NOT NULL,
CONSTRAINT [PK_AspNetUserLogins] PRIMARY KEY CLUSTERED
(
[LoginProvider] ASC,
[ProviderKey] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserRoles](
[UserId] [nvarchar](450) NOT NULL,
[RoleId] [nvarchar](450) NOT NULL,
CONSTRAINT [PK_AspNetUserRoles] PRIMARY KEY CLUSTERED
(
[UserId] ASC,
[RoleId] ASC
) ON [PRIMARY]
) ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUsers](
[Id] [nvarchar](450) NOT NULL,
[UserName] [nvarchar](256) NULL,
[NormalizedUserName] [nvarchar](256) NULL,
[Email] [nvarchar](256) NULL,
[NormalizedEmail] [nvarchar](256) NULL,
[EmailConfirmed] [bit] NOT NULL,
[PasswordHash] [nvarchar](max) NULL,
[SecurityStamp] [nvarchar](max) NULL,
[ConcurrencyStamp] [nvarchar](max) NULL,
[PhoneNumber] [nvarchar](max) NULL,
[PhoneNumberConfirmed] [bit] NOT NULL,
[TwoFactorEnabled] [bit] NOT NULL,
[LockoutEnd] [datetimeoffset](7) NULL,
[LockoutEnabled] [bit] NOT NULL,
[AccessFailedCount] [int] NOT NULL,
CONSTRAINT [PK_AspNetUsers] PRIMARY KEY CLUSTERED
(
[Id] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE TABLE [dbo].[AspNetUserTokens](
[UserId] [nvarchar](450) NOT NULL,
[LoginProvider] [nvarchar](128) NOT NULL,
[Name] [nvarchar](128) NOT NULL,
[Value] [nvarchar](max) NULL,
CONSTRAINT [PK_AspNetUserTokens] PRIMARY KEY CLUSTERED
(
[UserId] ASC,
[LoginProvider] ASC,
[Name] ASC
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetRoleClaims_RoleId] ON [dbo].[AspNetRoleClaims]
(
[RoleId] ASC
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [RoleNameIndex] ON [dbo].[AspNetRoles]
(
[NormalizedName] ASC
)
WHERE ([NormalizedName] IS NOT NULL)
ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetUserClaims_UserId] ON [dbo].[AspNetUserClaims]
(
[UserId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetUserLogins_UserId] ON [dbo].[AspNetUserLogins]
(
[UserId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_AspNetUserRoles_RoleId] ON [dbo].[AspNetUserRoles]
(
[RoleId] ASC
) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [EmailIndex] ON [dbo].[AspNetUsers]
(
[NormalizedEmail] ASC
) ON [PRIMARY]
GO
CREATE UNIQUE NONCLUSTERED INDEX [UserNameIndex] ON [dbo].[AspNetUsers]
(
[NormalizedUserName] ASC
)
WHERE ([NormalizedUserName] IS NOT NULL)
ON [PRIMARY]
GO
ALTER TABLE [dbo].[AspNetRoleClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId] FOREIGN KEY([RoleId])
REFERENCES [dbo].[AspNetRoles] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetRoleClaims] CHECK CONSTRAINT [FK_AspNetRoleClaims_AspNetRoles_RoleId]
GO
ALTER TABLE [dbo].[AspNetUserClaims] WITH CHECK ADD CONSTRAINT [FK_AspNetUserClaims_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserClaims] CHECK CONSTRAINT [FK_AspNetUserClaims_AspNetUsers_UserId]
GO
ALTER TABLE [dbo].[AspNetUserLogins] WITH CHECK ADD CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserLogins] CHECK CONSTRAINT [FK_AspNetUserLogins_AspNetUsers_UserId]
GO
ALTER TABLE [dbo].[AspNetUserRoles] WITH CHECK ADD CONSTRAINT [FK_AspNetUserRoles_AspNetRoles_RoleId] FOREIGN KEY([RoleId])
REFERENCES [dbo].[AspNetRoles] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserRoles] CHECK CONSTRAINT [FK_AspNetUserRoles_AspNetRoles_RoleId]
GO
ALTER TABLE [dbo].[AspNetUserRoles] WITH CHECK ADD CONSTRAINT [FK_AspNetUserRoles_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserRoles] CHECK CONSTRAINT [FK_AspNetUserRoles_AspNetUsers_UserId]
GO
ALTER TABLE [dbo].[AspNetUserTokens] WITH CHECK ADD CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId] FOREIGN KEY([UserId])
REFERENCES [dbo].[AspNetUsers] ([Id])
ON DELETE CASCADE
GO
ALTER TABLE [dbo].[AspNetUserTokens] CHECK CONSTRAINT [FK_AspNetUserTokens_AspNetUsers_UserId]
GO

View File

@ -313,21 +313,4 @@ GO
SET IDENTITY_INSERT [dbo].[HtmlText] OFF
GO
SET IDENTITY_INSERT [dbo].[User] ON
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (1, N'host', N'Host', N'', 1)
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (2, N'admin', N'Administrator', N'Administrators;', 0)
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (3, N'editor', N'Editor', N'Editors;', 0)
GO
INSERT [dbo].[User] ([UserId], [Username], [DisplayName], [Roles], [IsSuperUser])
VALUES (4, N'member', N'Member', N'Members;', 0)
GO
SET IDENTITY_INSERT [dbo].[User] OFF
GO

View File

@ -19,6 +19,10 @@ 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
{
@ -58,6 +62,11 @@ namespace Oqtane.Server
});
}
// register auth services
services.AddAuthorizationCore();
services.AddScoped<ServerAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(s => s.GetRequiredService<ServerAuthenticationStateProvider>());
// register scoped core services
services.AddScoped<SiteState>();
services.AddScoped<IModuleDefinitionService, ModuleDefinitionService>();
@ -99,6 +108,38 @@ namespace Oqtane.Server
));
services.AddDbContext<TenantContext>(options => { });
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<TenantContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMemoryCache();
services.AddMvc().AddNewtonsoftJson();
@ -177,6 +218,8 @@ namespace Oqtane.Server
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
@ -201,6 +244,38 @@ namespace Oqtane.Server
));
services.AddDbContext<TenantContext>(options => { });
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<TenantContext>()
.AddDefaultTokenProviders();
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = false;
options.Password.RequiredLength = 6;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = false;
options.Password.RequireLowercase = false;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = false;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.HttpOnly = false;
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = 401;
return Task.CompletedTask;
};
});
services.AddMemoryCache();
services.AddMvc().AddNewtonsoftJson();
@ -281,6 +356,8 @@ namespace Oqtane.Server
app.UseClientSideBlazorFiles<Client.Startup>();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{

View File

@ -9,5 +9,10 @@ namespace Oqtane.Models
public string DisplayName { get; set; }
public string Roles { get; set; }
public bool IsSuperUser { get; set; }
[NotMapped]
public string Password { get; set; }
[NotMapped]
public bool IsAuthenticated { get; set; }
}
}