diff --git a/Oqtane.Client/App.razor b/Oqtane.Client/App.razor
index 5a5698af..3446ccbf 100644
--- a/Oqtane.Client/App.razor
+++ b/Oqtane.Client/App.razor
@@ -1,9 +1,11 @@
@using Oqtane.Shared
@using Oqtane.Client.Shared
-
-
-
+
+
+
+
+
@code {
private PageState PageState { get; set; }
diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor
index bd06d715..9a76cb88 100644
--- a/Oqtane.Client/Modules/Admin/Login/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Login/Index.razor
@@ -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
-
- @((MarkupString)Message)
-
-
-
-
-
-
-
-
-
-
Cancel
-
+
+
+ ...
+
+
+ You are already logged in
+
+
+
+ @((MarkupString)Message)
+
+
+
+
+
+
+
+
+
+
Cancel
+
+
+
@code {
public override SecurityAccessLevelEnum SecurityAccessLevel { get { return SecurityAccessLevelEnum.Anonymous; } }
- public string Message { get; set; } = "Use host/host For Demo Access
";
+ public string Message { get; set; } = "Use host/password For Demo Access
";
public string Username { get; set; } = "";
public string Password { get; set; } = "";
private async Task Login()
{
- List 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
{
diff --git a/Oqtane.Client/Modules/Admin/Register/Index.razor b/Oqtane.Client/Modules/Admin/Register/Index.razor
index 67120c3d..d2380ba2 100644
--- a/Oqtane.Client/Modules/Admin/Register/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Register/Index.razor
@@ -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
@@ -17,13 +15,25 @@
-
+
Cancel
@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("");
+}
}
diff --git a/Oqtane.Client/Providers/ServerAuthenticationStateProvider.cs b/Oqtane.Client/Providers/ServerAuthenticationStateProvider.cs
new file mode 100644
index 00000000..818ed0c7
--- /dev/null
+++ b/Oqtane.Client/Providers/ServerAuthenticationStateProvider.cs
@@ -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 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(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());
+ }
+ }
+}
diff --git a/Oqtane.Client/Services/IUserService.cs b/Oqtane.Client/Services/IUserService.cs
index bce0e7b9..c11e8e80 100644
--- a/Oqtane.Client/Services/IUserService.cs
+++ b/Oqtane.Client/Services/IUserService.cs
@@ -16,6 +16,12 @@ namespace Oqtane.Services
Task DeleteUserAsync(int UserId);
+ Task GetCurrentUserAsync();
+
+ Task LoginUserAsync(User user);
+
+ Task LogoutUserAsync();
+
bool IsAuthorized(User user, string accesscontrollist);
}
}
diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs
index bc00f00a..fbe718e1 100644
--- a/Oqtane.Client/Services/UserService.cs
+++ b/Oqtane.Client/Services/UserService.cs
@@ -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 users = await http.GetJsonAsync>(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 GetCurrentUserAsync()
+ {
+ return await http.GetJsonAsync(apiurl + "/current");
+ }
+
+ public async Task LoginUserAsync(User user)
+ {
+ return await http.PostJsonAsync(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)
{
diff --git a/Oqtane.Client/Shared/SiteRouter.razor b/Oqtane.Client/Shared/SiteRouter.razor
index 65d15f27..fd30c9d8 100644
--- a/Oqtane.Client/Shared/SiteRouter.razor
+++ b/Oqtane.Client/Shared/SiteRouter.razor
@@ -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 != "")
diff --git a/Oqtane.Client/Startup.cs b/Oqtane.Client/Startup.cs
index 7f1b7078..3ed914c5 100644
--- a/Oqtane.Client/Startup.cs
+++ b/Oqtane.Client/Startup.cs
@@ -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();
+ services.AddScoped(s => s.GetRequiredService());
+
// register scoped core services
services.AddScoped();
services.AddScoped();
@@ -39,6 +46,7 @@ namespace Oqtane.Client
services.AddScoped();
services.AddScoped();
+
// 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");
}
#endif
diff --git a/Oqtane.Client/Themes/Controls/Login.razor b/Oqtane.Client/Themes/Controls/Login.razor
index a96ae9b9..92e006d1 100644
--- a/Oqtane.Client/Themes/Controls/Login.razor
+++ b/Oqtane.Client/Themes/Controls/Login.razor
@@ -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
+
+
+
+ ...
+
+
+
+
+
+
+
+
-
@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(""));
}
}
diff --git a/Oqtane.Client/Themes/Controls/Profile.razor b/Oqtane.Client/Themes/Controls/Profile.razor
index e6afd7de..0ff591a3 100644
--- a/Oqtane.Client/Themes/Controls/Profile.razor
+++ b/Oqtane.Client/Themes/Controls/Profile.razor
@@ -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
+
+
+
+ ...
+
+
+
+
+
+
+
+
-@name
@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"));
}
}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index c2ce2932..cfad0479 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -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 identityUserManager;
+ private readonly SignInManager identitySignInManager;
- public UserController(IUserRepository Users)
+ public UserController(IUserRepository Users, UserManager IdentityUserManager, SignInManager IdentitySignInManager)
{
users = Users;
+ identityUserManager = IdentityUserManager;
+ identitySignInManager = IdentitySignInManager;
}
// GET: api/
@@ -31,10 +37,23 @@ namespace Oqtane.Controllers
// POST api/
[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//5
@@ -42,7 +61,9 @@ namespace Oqtane.Controllers
public void Put(int id, [FromBody] User user)
{
if (ModelState.IsValid)
+ {
users.UpdateUser(user);
+ }
}
// DELETE api//5
@@ -51,5 +72,72 @@ namespace Oqtane.Controllers
{
users.DeleteUser(id);
}
+
+ // GET api//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//login
+ [HttpPost("login")]
+ public async Task 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//logout
+ [HttpPost("logout")]
+ public async Task Logout([FromBody] User user)
+ {
+ await identitySignInManager.SignOutAsync();
+ }
+
+ // GET api//current
+ [HttpGet("authenticate")]
+ public User Authenticate()
+ {
+ return new User { Username = User.Identity.Name, IsAuthenticated = User.Identity.IsAuthenticated };
+ }
}
}
diff --git a/Oqtane.Server/Filters/UpgradeFilter.cs b/Oqtane.Server/Filters/UpgradeFilter.cs
index 57b6ed28..b08203aa 100644
--- a/Oqtane.Server/Filters/UpgradeFilter.cs
+++ b/Oqtane.Server/Filters/UpgradeFilter.cs
@@ -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;
}
}
diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj
index c27a89be..efb5ecaf 100644
--- a/Oqtane.Server/Oqtane.Server.csproj
+++ b/Oqtane.Server/Oqtane.Server.csproj
@@ -26,11 +26,13 @@
+
+
@@ -38,8 +40,9 @@
-
-
+
+
+
diff --git a/Oqtane.Server/Repository/IUserRepository.cs b/Oqtane.Server/Repository/IUserRepository.cs
index 4e0c29cc..05ede0ad 100644
--- a/Oqtane.Server/Repository/IUserRepository.cs
+++ b/Oqtane.Server/Repository/IUserRepository.cs
@@ -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);
}
}
diff --git a/Oqtane.Server/Repository/TenantContext.cs b/Oqtane.Server/Repository/TenantContext.cs
index e5c2baf2..b74ce34d 100644
--- a/Oqtane.Server/Repository/TenantContext.cs
+++ b/Oqtane.Server/Repository/TenantContext.cs
@@ -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
{
public virtual DbSet Site { get; set; }
public virtual DbSet Page { get; set; }
diff --git a/Oqtane.Server/Repository/TenantResolver.cs b/Oqtane.Server/Repository/TenantResolver.cs
index 1e806b31..4ba145ff 100644
--- a/Oqtane.Server/Repository/TenantResolver.cs
+++ b/Oqtane.Server/Repository/TenantResolver.cs
@@ -28,6 +28,10 @@ namespace Oqtane.Repository
{
aliasname += "/" + segments[1];
}
+ if (aliasname.EndsWith("/"))
+ {
+ aliasname = aliasname.Substring(0, aliasname.Length - 1);
+ }
}
public Tenant GetTenant()
diff --git a/Oqtane.Server/Repository/UserRepository.cs b/Oqtane.Server/Repository/UserRepository.cs
index 1d0bd9ca..a750a405 100644
--- a/Oqtane.Server/Repository/UserRepository.cs
+++ b/Oqtane.Server/Repository/UserRepository.cs
@@ -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
diff --git a/Oqtane.Server/Scripts/Identity.sql b/Oqtane.Server/Scripts/Identity.sql
new file mode 100644
index 00000000..45f9cd66
--- /dev/null
+++ b/Oqtane.Server/Scripts/Identity.sql
@@ -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
diff --git a/Oqtane.Server/Scripts/Tenant.sql b/Oqtane.Server/Scripts/Tenant.sql
index b43bf28a..5719f48f 100644
--- a/Oqtane.Server/Scripts/Tenant.sql
+++ b/Oqtane.Server/Scripts/Tenant.sql
@@ -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
-
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
index 7849d5e7..0e29d5df 100644
--- a/Oqtane.Server/Startup.cs
+++ b/Oqtane.Server/Startup.cs
@@ -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();
+ services.AddScoped(s => s.GetRequiredService());
+
// register scoped core services
services.AddScoped();
services.AddScoped();
@@ -99,6 +108,38 @@ namespace Oqtane.Server
));
services.AddDbContext(options => { });
+ services.AddIdentity()
+ .AddEntityFrameworkStores()
+ .AddDefaultTokenProviders();
+
+ services.Configure(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(options => { });
+ services.AddIdentity()
+ .AddEntityFrameworkStores()
+ .AddDefaultTokenProviders();
+
+ services.Configure(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();
app.UseRouting();
+ app.UseAuthentication();
+ app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
diff --git a/Oqtane.Shared/Models/User.cs b/Oqtane.Shared/Models/User.cs
index 7b55104f..d2bd5f6d 100644
--- a/Oqtane.Shared/Models/User.cs
+++ b/Oqtane.Shared/Models/User.cs
@@ -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; }
}
}