diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor
index 162196ff..02d45a3d 100644
--- a/Oqtane.Client/Modules/Admin/Login/Index.razor
+++ b/Oqtane.Client/Modules/Admin/Login/Index.razor
@@ -52,7 +52,7 @@ else
- @if (_allowpasskeys && PageState.Route.Scheme == "https")
+ @if (_allowpasskeys)
{
@@ -116,7 +116,7 @@ else
{
_allowexternallogin = (SettingService.GetSetting(PageState.Site.Settings, "ExternalLogin:ProviderType", "") != "") ? true : false;
_allowsitelogin = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowSiteLogin", "true"));
- _allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AllowPasskeys", "false"));
+ _allowpasskeys = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:Passkeys", "false"));
_alwaysremember = bool.Parse(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:AlwaysRemember", "false"));
if (!string.IsNullOrEmpty(SettingService.GetSetting(PageState.Site.Settings, "LoginOptions:RegisterUrl", "")))
@@ -365,14 +365,14 @@ else
}
else
{
- await logger.LogError("Error Logging In With Passkey");
- AddModuleMessage(Localizer["Error.Passkey"], MessageType.Error);
+ await logger.LogError("Passkey Login Was Not Successful");
+ AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
}
catch (Exception ex)
{
- await logger.LogError(ex, "Error Logging In With Passkey");
- AddModuleMessage(Localizer["Error.Passkey"], MessageType.Error);
+ await logger.LogError(ex, "Passkey Login Was Not Successful");
+ AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
return;
}
diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor
index 1bf585f6..6e9a9efe 100644
--- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor
+++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor
@@ -115,14 +115,7 @@
@if (_allowpasskeys)
{
- @if (PageState.Route.Scheme == "https")
- {
-
- }
- else
- {
-
- }
+
@if (_passkeys != null && _passkeys.Count > 0)
{
@@ -669,7 +662,7 @@
{
// post back to the Passkey page so that the cookies are set correctly
var interop = new Interop(JSRuntime);
- var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "create", returnurl = NavigateUrl() };
+ var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "create", returnurl = NavigateUrl(PageState.Page.Path, "tab=Security") };
string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/");
await interop.SubmitForm(url, fields);
}
@@ -694,14 +687,14 @@
}
else
{
- await logger.LogError("Error Adding Passkey");
- AddModuleMessage(Localizer["Error.Passkey"], MessageType.Error);
+ await logger.LogError("Passkey Could Not Be Created");
+ AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
}
catch (Exception ex)
{
- await logger.LogError(ex, "Error Adding Passkey");
- AddModuleMessage(Localizer["Error.Passkey"], MessageType.Error);
+ await logger.LogError(ex, "Passkey Could Not Be Created");
+ AddModuleMessage(Localizer["Error.Passkey.Fail"], MessageType.Warning);
}
}
}
diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx
index 84a53b4a..de17269f 100644
--- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx
@@ -234,4 +234,7 @@
Use Passkey
+
+ Passkey Login Was Not Successful
+
\ No newline at end of file
diff --git a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx
index 5c2a5261..9433b281 100644
--- a/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx
+++ b/Oqtane.Client/Resources/Modules/Admin/UserProfile/Index.resx
@@ -279,13 +279,13 @@
Are You Sure You Wish To Delete {0}?
-
- Passkeys Can Only Be Created Using a Secure Browser Connection
-
You Have Not Created Any Passkeys
You Do Not Have Any External Logins For This Site
+
+ Passkey Could Not Be Created
+
\ No newline at end of file
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index 741b9118..875e6e12 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -471,7 +471,7 @@ namespace Oqtane.Controllers
[Authorize]
public async Task> GetPasskeys()
{
- return await _userManager.GetPasskeys(_userPermissions.GetUser(User).UserId);
+ return await _userManager.GetPasskeys(_userPermissions.GetUser(User).UserId, _tenantManager.GetAlias().SiteId);
}
// PUT api//passkey
@@ -481,6 +481,8 @@ namespace Oqtane.Controllers
{
if (ModelState.IsValid)
{
+ // passkey name is prefixed with SiteId for multi-tenancy
+ passkey.Name = $"{_tenantManager.GetAlias().SiteId}:" + passkey.Name;
passkey.UserId = _userPermissions.GetUser(User).UserId;
await _userManager.UpdatePasskey(passkey);
}
diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs
index 0ce7fd39..b56a606d 100644
--- a/Oqtane.Server/Managers/UserManager.cs
+++ b/Oqtane.Server/Managers/UserManager.cs
@@ -37,7 +37,7 @@ namespace Oqtane.Managers
Task ValidateUser(string username, string email, string password);
Task ValidatePassword(string password);
Task> ImportUsers(int siteId, string filePath, bool notify);
- Task> GetPasskeys(int userId);
+ Task> GetPasskeys(int userId, int siteId);
Task UpdatePasskey(UserPasskey passkey);
Task DeletePasskey(int userId, byte[] credentialId);
Task> GetLogins(int userId, int siteId);
@@ -826,7 +826,7 @@ namespace Oqtane.Managers
return result;
}
- public async Task> GetPasskeys(int userId)
+ public async Task> GetPasskeys(int userId, int siteId)
{
var passkeys = new List();
var user = _users.GetUser(userId);
@@ -838,7 +838,11 @@ namespace Oqtane.Managers
var userpasskeys = await _identityUserManager.GetPasskeysAsync(identityuser);
foreach (var userpasskey in userpasskeys)
{
- passkeys.Add(new UserPasskey { CredentialId = userpasskey.CredentialId, Name = userpasskey.Name, UserId = userId });
+ // passkey name is prefixed with SiteId for multi-tenancy
+ if (userpasskey.Name.StartsWith($"{siteId}:"))
+ {
+ passkeys.Add(new UserPasskey { CredentialId = userpasskey.CredentialId, Name = userpasskey.Name.Split(':')[1], UserId = userId });
+ }
}
}
}
diff --git a/Oqtane.Server/Pages/Passkey.cshtml.cs b/Oqtane.Server/Pages/Passkey.cshtml.cs
index ad6a35d3..8923d78d 100644
--- a/Oqtane.Server/Pages/Passkey.cshtml.cs
+++ b/Oqtane.Server/Pages/Passkey.cshtml.cs
@@ -49,7 +49,7 @@ namespace Oqtane.Pages
Name = identityuser.UserName,
DisplayName = identityuser.UserName
});
- returnurl += $"?options={WebUtility.UrlEncode(creationOptionsJson)}";
+ returnurl += (!returnurl.Contains("?") ? "?" : "&") + $"options={WebUtility.UrlEncode(creationOptionsJson)}";
}
else
{
@@ -70,8 +70,18 @@ namespace Oqtane.Pages
var attestationResult = await _identitySignInManager.PerformPasskeyAttestationAsync(credential);
if (attestationResult.Succeeded)
{
- attestationResult.Passkey.Name = identityuser.UserName + "'s Passkey";
- var addPasskeyResult = await _identityUserManager.AddOrUpdatePasskeyAsync(identityuser, attestationResult.Passkey);
+ var user = _userManager.GetUser(User.Identity.Name, HttpContext.GetAlias().SiteId);
+ if (user != null && !user.IsDeleted && UserSecurity.ContainsRole(user.Roles, RoleNames.Registered))
+ {
+ // setting a default name and including a SiteId prefix for multi-tenancy
+ var name = (!string.IsNullOrEmpty(user.DisplayName)) ? user.DisplayName : user.Username;
+ attestationResult.Passkey.Name = HttpContext.GetAlias().SiteId + ":" + name + "'s Passkey";
+ var addPasskeyResult = await _identityUserManager.AddOrUpdatePasskeyAsync(identityuser, attestationResult.Passkey);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Information, this, LogFunction.Security, "Passkey Validation Failed - User {Username} Is Deleted Or Is Not A Registered User For The Site", User.Identity.Name);
+ }
}
else
{
@@ -113,7 +123,7 @@ namespace Oqtane.Pages
}
else
{
- _logger.Log(LogLevel.Information, this, LogFunction.Security, "Passkey Login Failed For User {Username}", User.Identity.Name);
+ _logger.Log(LogLevel.Information, this, LogFunction.Security, "Passkey Login Failed - User {Username} Is Deleted Or Is Not A Registered User For The Site", User.Identity.Name);
}
}
else