diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor
index 14bf6f8c..f05dc1d3 100644
--- a/Oqtane.Client/Modules/Admin/Login/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Login/Index.razor
@@ -95,9 +95,9 @@
{
_togglepassword = Localizer["ShowPassword"];
- if (PageState.Site.Settings.ContainsKey("ExternalLogin:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:AllowSiteLogin"]))
+ if (PageState.Site.Settings.ContainsKey("LoginOptions:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]))
{
- _allowsitelogin = bool.Parse(PageState.Site.Settings["ExternalLogin:AllowSiteLogin"]);
+ _allowsitelogin = bool.Parse(PageState.Site.Settings["LoginOptions:AllowSiteLogin"]);
}
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor
index 2df480a4..6bcf5f4a 100644
--- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor
+++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor
@@ -41,15 +41,18 @@ else
-
-
-
-
-
-
+ @if (allowtwofactor)
+ {
+
+
+
+
+
+
+ }
@@ -216,6 +219,7 @@ else
private string username = string.Empty;
private string password = string.Empty;
private string confirm = string.Empty;
+ private bool allowtwofactor = false;
private string twofactor = "False";
private string email = string.Empty;
private string displayname = string.Empty;
@@ -235,6 +239,11 @@ else
{
try
{
+ if (PageState.Site.Settings.ContainsKey("LoginOptions:TwoFactor") && !string.IsNullOrEmpty(PageState.Site.Settings["LoginOptions:TwoFactor"]))
+ {
+ allowtwofactor = bool.Parse(PageState.Site.Settings["LoginOptions:TwoFactor"]);
+ }
+
if (PageState.User != null)
{
username = PageState.User.Username;
diff --git a/Oqtane.Client/Modules/Admin/Users/Index.razor b/Oqtane.Client/Modules/Admin/Users/Index.razor
index a9b16b4b..21154914 100644
--- a/Oqtane.Client/Modules/Admin/Users/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Users/Index.razor
@@ -55,16 +55,56 @@ else
-
-
-
-
+
+
+
+
+
+
-
-
+ @if (_providertype != "")
+ {
+
+
+
+
+
+
+ }
+ else
+ {
+
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -129,17 +169,6 @@ else
-
-
-
-
-
-
-
-
@@ -255,15 +284,6 @@ else
-
-
-
-
-
-
}
@@ -314,6 +334,10 @@ else
private string _search;
private string _allowregistration;
+ private string _allowsitelogin;
+ private string _twofactor;
+ private string _cookietype;
+
private string _minimumlength;
private string _uniquecharacters;
private string _requiredigit;
@@ -323,8 +347,6 @@ else
private string _maximumfailures;
private string _lockoutduration;
- private string _cookietype;
-
private string _providertype;
private string _providername;
private string _authority;
@@ -340,7 +362,6 @@ else
private string _emailclaimtype;
private string _domainfilter;
private string _createusers;
- private string _allowsitelogin;
private string _secret;
private string _issuer;
@@ -356,8 +377,11 @@ else
await LoadSettingsAsync();
userroles = Search(_search);
- _allowregistration = PageState.Site.AllowRegistration.ToString();
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
+ _allowregistration = PageState.Site.AllowRegistration.ToString();
+ _allowsitelogin = SettingService.GetSetting(settings, "LoginOptions:AllowSiteLogin", "true");
+ _twofactor = SettingService.GetSetting(settings, "LoginOptions:TwoFactor", "false");
+ _cookietype = SettingService.GetSetting(settings, "LoginOptions:CookieType", "domain");
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
@@ -369,8 +393,6 @@ else
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
- _cookietype = SettingService.GetSetting(settings, "CookieOptions:CookieType", "domain");
-
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
@@ -386,7 +408,6 @@ else
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
- _allowsitelogin = SettingService.GetSetting(settings, "ExternalLogin:AllowSiteLogin", "true");
_secret = SettingService.GetSetting(settings, "JwtOptions:Secret", "");
_issuer = SettingService.GetSetting(settings, "JwtOptions:Issuer", PageState.Uri.Scheme + "://" + PageState.Alias.Name);
@@ -462,6 +483,10 @@ else
await SiteService.UpdateSiteAsync(site);
var settings = await SettingService.GetSiteSettingsAsync(site.SiteId);
+ settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false);
+ settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false);
+ settings = SettingService.SetSetting(settings, "LoginOptions:CookieType", _cookietype, true);
+
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true);
@@ -472,8 +497,6 @@ else
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
- settings = SettingService.SetSetting(settings, "CookieOptions:CookieType", _cookietype, true);
-
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
@@ -488,7 +511,6 @@ else
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
- settings = SettingService.SetSetting(settings, "ExternalLogin:AllowSiteLogin", _allowsitelogin, false);
if (!string.IsNullOrEmpty(_secret) && _secret.Length < 16) _secret = (_secret + "????????????????").Substring(0, 16);
settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true);
@@ -497,7 +519,7 @@ else
settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true);
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
- await SettingService.ClearSiteSettingsCacheAsync(site.SiteId);
+ await SettingService.ClearSiteSettingsCacheAsync();
AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);
}
diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs
index 3e972be9..80c9fbab 100644
--- a/Oqtane.Client/Program.cs
+++ b/Oqtane.Client/Program.cs
@@ -28,9 +28,9 @@ namespace Oqtane.Client
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)};
+ builder.Services.AddSingleton(httpClient);
+ builder.Services.AddHttpClient(); // IHttpClientFactory for calling remote services via RemoteServiceBase
- builder.Services.AddSingleton(httpClient);
- builder.Services.AddHttpClient("Remote");
builder.Services.AddOptions();
// Register localization services
diff --git a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx
index d23c130b..01cb2c95 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Users/Index.resx
@@ -208,7 +208,7 @@
Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site.
- Allow Site Login?
+ Allow Login?
The Authority Url or Issuer Url associated with the OpenID Connect provider
@@ -315,14 +315,14 @@
Audience:
-
- Cookie Settings
+
+ User Settings
- Cookies are usually managed per domain. However you can also choose to have distinct cookies for each site.
+ Login cookies are usually managed per domain. However you can also choose to have distinct cookies for each site.
- Cookie Type:
+ Login Cookie Type:
Create Token
@@ -354,4 +354,10 @@
Token Settings
+
+ Do you want to allow users to use two factor authentication? Note that the Notification Job in Scheduled Jobs needs to be enabled and your SMTP options need to be configured in Site Settings for this option to work properly.
+
+
+ Allow Two Factor?
+
\ No newline at end of file
diff --git a/Oqtane.Client/Services/Interfaces/ISettingService.cs b/Oqtane.Client/Services/Interfaces/ISettingService.cs
index 98f733d1..8e795e80 100644
--- a/Oqtane.Client/Services/Interfaces/ISettingService.cs
+++ b/Oqtane.Client/Services/Interfaces/ISettingService.cs
@@ -42,7 +42,7 @@ namespace Oqtane.Services
/// Clears site option cache
///
///
- Task ClearSiteSettingsCacheAsync(int siteId);
+ Task ClearSiteSettingsCacheAsync();
///
/// Returns a key-value dictionary of all page settings for the given page
diff --git a/Oqtane.Client/Services/SettingService.cs b/Oqtane.Client/Services/SettingService.cs
index 343920d2..b87c7dfe 100644
--- a/Oqtane.Client/Services/SettingService.cs
+++ b/Oqtane.Client/Services/SettingService.cs
@@ -42,9 +42,9 @@ namespace Oqtane.Services
await UpdateSettingsAsync(siteSettings, EntityNames.Site, siteId);
}
- public async Task ClearSiteSettingsCacheAsync(int siteId)
+ public async Task ClearSiteSettingsCacheAsync()
{
- await DeleteAsync($"{Apiurl}/clear/{siteId}");
+ await DeleteAsync($"{Apiurl}/clear");
}
public async Task> GetPageSettingsAsync(int pageId)
diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs
index 1d2ed70c..6fb6a443 100644
--- a/Oqtane.Server/Controllers/SettingController.cs
+++ b/Oqtane.Server/Controllers/SettingController.cs
@@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Authentication.Cookies;
+using Microsoft.Extensions.Options;
namespace Oqtane.Controllers
{
@@ -23,18 +24,26 @@ namespace Oqtane.Controllers
private readonly IPageModuleRepository _pageModules;
private readonly IUserPermissions _userPermissions;
private readonly ISyncManager _syncManager;
+ private readonly IAliasAccessor _aliasAccessor;
+ private readonly IOptionsMonitorCache _cookieCache;
+ private readonly IOptionsMonitorCache _oidcCache;
+ private readonly IOptionsMonitorCache _oauthCache;
+ private readonly IOptionsMonitorCache _identityCache;
private readonly ILogManager _logger;
private readonly Alias _alias;
- private readonly IAliasAccessor _aliasAccessor;
private readonly string _visitorCookie;
- public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, ILogManager logger)
+ public SettingController(ISettingRepository settings, IPageModuleRepository pageModules, IUserPermissions userPermissions, ITenantManager tenantManager, ISyncManager syncManager, IAliasAccessor aliasAccessor, IOptionsMonitorCache cookieCache, IOptionsMonitorCache oidcCache, IOptionsMonitorCache oauthCache, IOptionsMonitorCache identityCache, ILogManager logger)
{
_settings = settings;
_pageModules = pageModules;
_userPermissions = userPermissions;
_syncManager = syncManager;
_aliasAccessor = aliasAccessor;
+ _cookieCache = cookieCache;
+ _oidcCache = oidcCache;
+ _oauthCache = oauthCache;
+ _identityCache = identityCache;
_logger = logger;
_alias = tenantManager.GetAlias();
_visitorCookie = "APP_VISITOR_" + _alias.SiteId.ToString();
@@ -139,18 +148,26 @@ namespace Oqtane.Controllers
}
// DELETE api//clear
- [HttpDelete("clear/{id}")]
+ [HttpDelete("clear")]
[Authorize(Roles = RoleNames.Admin)]
- public void Clear(int id)
+ public void Clear()
{
- var cookieAuthenticationOptionsCache = new SiteOptionsCache(_aliasAccessor);
- cookieAuthenticationOptionsCache.Clear();
- var openIdConnectOptionsCache = new SiteOptionsCache(_aliasAccessor);
- openIdConnectOptionsCache.Clear();
- var oAuthOptionsCache = new SiteOptionsCache(_aliasAccessor);
- oAuthOptionsCache.Clear();
- var identityOptionsCache = new SiteOptionsCache(_aliasAccessor);
- identityOptionsCache.Clear();
+ // clear SiteOptionsCache for each option type
+ var cookieCache = new SiteOptionsCache(_aliasAccessor);
+ cookieCache.Clear();
+ var oidcCache = new SiteOptionsCache(_aliasAccessor);
+ oidcCache.Clear();
+ var oauthCache = new SiteOptionsCache(_aliasAccessor);
+ oauthCache.Clear();
+ var identityCache = new SiteOptionsCache(_aliasAccessor);
+ identityCache.Clear();
+
+ // clear IOptionsMonitorCache for each option type
+ _cookieCache.Clear();
+ _oidcCache.Clear();
+ _oauthCache.Clear();
+ _identityCache.Clear();
+
_logger.Log(LogLevel.Information, this, LogFunction.Other, "Site Options Cache Cleared");
}
diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs
index d233f554..0e1f525d 100644
--- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs
@@ -28,7 +28,7 @@ namespace Oqtane.Extensions
// site cookie authentication options
builder.AddSiteOptions((options, alias, sitesettings) =>
{
- if (sitesettings.GetValue("CookieOptions:CookieType", "domain") == "domain")
+ if (sitesettings.GetValue("LoginOptions:CookieType", "domain") == "domain")
{
options.Cookie.Name = ".AspNetCore.Identity.Application";
}
diff --git a/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs
index 0787c354..4297981c 100644
--- a/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs
+++ b/Oqtane.Server/Infrastructure/Middleware/JwtMiddleware.cs
@@ -36,7 +36,7 @@ namespace Oqtane.Infrastructure
var user = jwtManager.ValidateToken(token, secret, sitesettings.GetValue("JwtOptions:Issuer", ""), sitesettings.GetValue("JwtOptions:Audience", ""));
if (user != null)
{
- // populate principal (reload user roles to ensure most accurate permission assigments)
+ // populate principal (reload user roles to ensure most accurate permissions)
var _userRoles = context.RequestServices.GetService(typeof(IUserRoleRepository)) as IUserRoleRepository;
var principal = (ClaimsIdentity)context.User.Identity;
UserSecurity.ResetClaimsIdentity(principal);
@@ -52,7 +52,8 @@ namespace Oqtane.Infrastructure
}
}
- await _next(context);
+ // continue processing
+ if (_next != null) await _next(context);
}
}
}
diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs
index 540df02a..87dc89c8 100644
--- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs
+++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs
@@ -9,11 +9,11 @@ namespace Oqtane.Infrastructure
{
internal class TenantMiddleware
{
- private readonly RequestDelegate next;
+ private readonly RequestDelegate _next;
public TenantMiddleware(RequestDelegate next)
{
- this.next = next;
+ _next = next;
}
public async Task Invoke(HttpContext context)
@@ -55,7 +55,7 @@ namespace Oqtane.Infrastructure
}
// continue processing
- if (next != null) await next(context);
+ if (_next != null) await _next(context);
}
}
}
diff --git a/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs b/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs
index 164ab7bd..e4737d9a 100644
--- a/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs
+++ b/Oqtane.Server/Infrastructure/Options/SiteOptionsCache.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
-using Oqtane.Models;
namespace Oqtane.Infrastructure
{
@@ -20,21 +19,7 @@ namespace Oqtane.Infrastructure
{
var cache = map.GetOrAdd(GetKey(), new OptionsCache());
cache.Clear();
- }
- public void Clear(Alias alias)
- {
- var cache = map.GetOrAdd(alias.SiteKey, new OptionsCache());
-
- cache.Clear();
- }
-
- public void ClearAll()
- {
- foreach (var cache in map.Values)
- {
- cache.Clear();
- }
}
public TOptions GetOrAdd(string name, Func createOptions)