From 8113c82da86db83785592386d319cf8c30a8741c Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 16 Dec 2025 14:33:42 -0500 Subject: [PATCH 01/15] module migration issues should not prevent the framework from starting up --- .../Infrastructure/DatabaseManager.cs | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index afad9465..585aa1a5 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -449,8 +449,6 @@ namespace Oqtane.Infrastructure private Installation MigrateModules(InstallConfig install) { - var result = new Installation { Success = false, Message = string.Empty }; - using (var scope = _serviceScopeFactory.CreateScope()) { var moduleDefinitions = scope.ServiceProvider.GetRequiredService(); @@ -464,6 +462,8 @@ namespace Oqtane.Infrastructure var versions = moduleDefinition.ReleaseVersions.Split(',', StringSplitOptions.RemoveEmptyEntries); using (var db = GetInstallationContext()) { + var message = ""; + if (!string.IsNullOrEmpty(moduleDefinition.ServerManagerType)) { var moduleType = Type.GetType(moduleDefinition.ServerManagerType); @@ -488,20 +488,23 @@ namespace Oqtane.Infrastructure var moduleObject = ActivatorUtilities.CreateInstance(scope.ServiceProvider, moduleType) as IInstallable; if (moduleObject == null || !moduleObject.Install(tenant, versions[i])) { - result.Message = "An Error Occurred Executing IInstallable Interface For " + moduleDefinition.ServerManagerType; + message = "An Error Occurred Executing IInstallable Interface For " + moduleDefinition.ServerManagerType + " On Tenant " + tenant.Name; + _filelogger.LogError(Utilities.LogMessage(this, message)); } } else { if (!sql.ExecuteScript(tenant, moduleType.Assembly, Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql")) { - result.Message = "An Error Occurred Executing Database Script " + Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql"; + message = "An Error Occurred Executing Database Script " + Utilities.GetTypeName(moduleDefinition.ModuleDefinitionName) + "." + versions[i] + ".sql On Tenant " + tenant.Name; + _filelogger.LogError(Utilities.LogMessage(this, message)); } } } catch (Exception ex) { - result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " On Tenant " + tenant.Name + " - " + ex.ToString(); + message = "An Error Occurred Installing " + moduleDefinition.Name + " Version " + versions[i] + " On Tenant " + tenant.Name + " - " + ex.ToString(); + _filelogger.LogError(Utilities.LogMessage(this, message)); } } } @@ -509,11 +512,13 @@ namespace Oqtane.Infrastructure } else { - result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " - ServerManagerType " + moduleDefinition.ServerManagerType + " Does Not Exist"; + message = "An Error Occurred Installing " + moduleDefinition.Name + " - ServerManagerType " + moduleDefinition.ServerManagerType + " Does Not Exist"; + _filelogger.LogError(Utilities.LogMessage(this, message)); } } - if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1]) + // update module if all migrations were successful and version is not current + if (string.IsNullOrEmpty(message) && moduleDefinition.Version != versions[versions.Length - 1]) { // get module definition from database to retain user customizable property values var moduledef = db.ModuleDefinition.AsNoTracking().FirstOrDefault(item => item.ModuleDefinitionId == moduleDefinition.ModuleDefinitionId); @@ -531,16 +536,8 @@ namespace Oqtane.Infrastructure } } - if (string.IsNullOrEmpty(result.Message)) - { - result.Success = true; - } - else - { - _filelogger.LogError(Utilities.LogMessage(this, result.Message)); - } - - return result; + // module migration issues are logged and should not prevent the framework from starting up + return new Installation { Success = true, Message = string.Empty }; } private Installation CreateSite(InstallConfig install) From f74eda274a2a2f55c1e8b23648959671f25482d9 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Wed, 17 Dec 2025 15:51:21 -0500 Subject: [PATCH 02/15] enable EnhancedNavigation by default --- Oqtane.Server/Infrastructure/DatabaseManager.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs index 585aa1a5..87d6aab0 100644 --- a/Oqtane.Server/Infrastructure/DatabaseManager.cs +++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs @@ -593,6 +593,7 @@ namespace Oqtane.Infrastructure Runtime = runtime, Prerender = (rendermode == RenderModes.Interactive), Hybrid = false, + EnhancedNavigation = true, TenantId = tenant.TenantId }; site = sites.AddSite(site); From 1682a123b4e0c21c60b1f661426e567914f17160 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Thu, 18 Dec 2025 16:00:46 -0500 Subject: [PATCH 03/15] login improvements --- Oqtane.Client/Modules/Admin/Login/Index.razor | 26 +++++++-------- .../Modules/Admin/UserProfile/Index.razor | 12 +++---- Oqtane.Client/Modules/Admin/Users/Edit.razor | 12 +++---- .../Resources/Modules/Admin/Login/Index.resx | 3 ++ Oqtane.Client/Services/UserService.cs | 6 ++-- Oqtane.Server/Controllers/UserController.cs | 8 ++--- Oqtane.Server/Managers/UserManager.cs | 6 ++-- Oqtane.Server/Pages/LoginLink.cshtml.cs | 33 +++++++++++-------- Oqtane.Server/Pages/Logout.cshtml.cs | 3 -- Oqtane.Server/Pages/Passkey.cshtml.cs | 4 ++- Oqtane.Shared/Shared/ExternalLoginStatus.cs | 1 + 11 files changed, 62 insertions(+), 52 deletions(-) diff --git a/Oqtane.Client/Modules/Admin/Login/Index.razor b/Oqtane.Client/Modules/Admin/Login/Index.razor index 4a6fee49..773f52a8 100644 --- a/Oqtane.Client/Modules/Admin/Login/Index.razor +++ b/Oqtane.Client/Modules/Admin/Login/Index.razor @@ -127,6 +127,7 @@ else private bool _allowsitelogin = true; private bool _allowloginlink = false; private bool _allowpasskeys = false; + private string _returnurl = string.Empty; private ElementReference login; private bool validated = false; @@ -169,6 +170,9 @@ else _registerurl = NavigateUrl("register"); } + // PageState.ReturnUrl is not specified if user navigated directly to login page + _returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path; + _togglepassword = SharedLocalizer["ShowPassword"]; if (PageState.QueryString.ContainsKey("name")) @@ -216,7 +220,7 @@ else { if (PageState.QueryString.ContainsKey("status")) { - AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Info); + AddModuleMessage(Localizer["ExternalLoginStatus." + PageState.QueryString["status"]], MessageType.Warning); } } } @@ -252,7 +256,7 @@ else private void ExternalLogin() { - NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(PageState.ReturnUrl)), true); + NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + WebUtility.UrlEncode(_returnurl)), true); } private void TogglePassword() @@ -294,20 +298,17 @@ else { await logger.LogInformation(LogFunction.Security, "Login Successful For {Username} From IP Address {IPAddress}", _username, SiteState.RemoteIPAddress); - // return url is not specified if user navigated directly to login page - var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path; - if (hybrid) { // hybrid apps utilize an interactive login var authstateprovider = (IdentityAuthenticationStateProvider)ServiceProvider.GetService(typeof(IdentityAuthenticationStateProvider)); authstateprovider.NotifyAuthenticationChanged(); - NavigationManager.NavigateTo(NavigateUrl(returnurl, true)); + NavigationManager.NavigateTo(NavigateUrl(_returnurl, true)); } else { // post back to the Login page so that the cookies are set correctly - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(returnurl) }; + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, username = _username, password = _password, remember = _remember, returnurl = WebUtility.UrlEncode(_returnurl) }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/login/"); await interop.SubmitForm(url, fields); } @@ -349,14 +350,14 @@ else private void CancelLogin() { - NavigationManager.NavigateTo(PageState.ReturnUrl); + NavigationManager.NavigateTo(_returnurl); } private async Task PasskeyLogin() { // 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 = "request", returnurl = NavigateUrl() }; + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "request", returnurl = _returnurl }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/"); await interop.SubmitForm(url, fields); } @@ -423,7 +424,7 @@ else { if (!string.IsNullOrEmpty(_email)) { - if (await UserService.SendLoginLinkAsync(_email)) + if (await UserService.SendLoginLinkAsync(_email, _returnurl)) { AddModuleMessage(Localizer["Message.SendLoginLink"], MessageType.Info); await logger.LogInformation(LogFunction.Security, "Login Link Sent To Email {Email}", _email); @@ -457,8 +458,7 @@ else if (!string.IsNullOrEmpty(credential)) { // post back to the Passkey page so that the cookies are set correctly - var returnurl = (!string.IsNullOrEmpty(PageState.ReturnUrl)) ? PageState.ReturnUrl : PageState.Alias.Path + "/"; - var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = returnurl }; + var fields = new { __RequestVerificationToken = SiteState.AntiForgeryToken, operation = "login", credential = credential, returnurl = _returnurl }; string url = Utilities.TenantUrl(PageState.Alias, "/pages/passkey/"); await interop.SubmitForm(url, fields); } @@ -497,7 +497,7 @@ else // redirect logged in user to specified page if (PageState.User != null && !UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) { - NavigationManager.NavigateTo(PageState.ReturnUrl); + NavigationManager.NavigateTo(_returnurl); } } } diff --git a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor index c8113d11..705de755 100644 --- a/Oqtane.Client/Modules/Admin/UserProfile/Index.razor +++ b/Oqtane.Client/Modules/Admin/UserProfile/Index.razor @@ -114,9 +114,9 @@ } @if (_allowpasskeys) { -
+
- @if (_passkeys != null && _passkeys.Count > 0) + @if (_passkeys.Count > 0) {
@@ -142,15 +142,15 @@ } else { -
@Localizer["Message.Passkeys.None"]
+
@Localizer["Message.Passkeys.None"]
}

} @if (_allowexternallogin) { -
- @if (_logins != null && _logins.Count > 0) +
+ @if (_logins.Count > 0) {
@@ -165,7 +165,7 @@ } else { -
@Localizer["Message.Logins.None"]
+
@Localizer["Message.Logins.None"]
}

diff --git a/Oqtane.Client/Modules/Admin/Users/Edit.razor b/Oqtane.Client/Modules/Admin/Users/Edit.razor index 09199202..db0ea36b 100644 --- a/Oqtane.Client/Modules/Admin/Users/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Users/Edit.razor @@ -106,8 +106,8 @@

@if (_allowpasskeys) { -
- @if (_passkeys != null && _passkeys.Count > 0) +
+ @if (_passkeys.Count > 0) {
@@ -122,15 +122,15 @@ } else { -
@Localizer["Message.Passkeys.None"]
+
@Localizer["Message.Passkeys.None"]
}

} @if (_allowexternallogin) { -
- @if (_logins != null && _logins.Count > 0) +
+ @if (_logins.Count > 0) {
@@ -145,7 +145,7 @@ } else { -
@Localizer["Message.Logins.None"]
+
@Localizer["Message.Logins.None"]
}

diff --git a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx index af2a52ba..bbb7bc5d 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Login/Index.resx @@ -246,6 +246,9 @@ Login Links Are Time Sensitive. Please Request Another Login Link To Complete The Login Process. + + Passkey Login Was Unsuccessful. Please Ensure You Selected The Correct Passkey For This Site. + Register as new user? diff --git a/Oqtane.Client/Services/UserService.cs b/Oqtane.Client/Services/UserService.cs index 76de84e0..049a93d5 100644 --- a/Oqtane.Client/Services/UserService.cs +++ b/Oqtane.Client/Services/UserService.cs @@ -224,7 +224,7 @@ namespace Oqtane.Services /// /// /// - Task SendLoginLinkAsync(string email); + Task SendLoginLinkAsync(string email, string returnurl); } [PrivateApi("Don't show in the documentation, as everything should use the Interface")] @@ -386,9 +386,9 @@ namespace Oqtane.Services await DeleteAsync($"{Apiurl}/login?id={userId}&provider={provider}&key={key}"); } - public async Task SendLoginLinkAsync(string email) + public async Task SendLoginLinkAsync(string email, string returnurl) { - return await GetJsonAsync($"{Apiurl}/loginlink/{WebUtility.UrlEncode(email)}"); + return await GetJsonAsync($"{Apiurl}/loginlink/{WebUtility.UrlEncode(email)}/{WebUtility.UrlEncode(returnurl)}"); } } } diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs index ece7d295..aa6aa909 100644 --- a/Oqtane.Server/Controllers/UserController.cs +++ b/Oqtane.Server/Controllers/UserController.cs @@ -563,11 +563,11 @@ namespace Oqtane.Controllers } } - // GET api//loginlink/x - [HttpGet("loginlink/{email}")] - public async Task SendLoginLink(string email) + // GET api//loginlink/x/y + [HttpGet("loginlink/{email}/{returnurl}")] + public async Task SendLoginLink(string email, string returnurl) { - return await _userManager.SendLoginLink(email); + return await _userManager.SendLoginLink(email, returnurl); } } } diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs index 3e032400..ea76e39a 100644 --- a/Oqtane.Server/Managers/UserManager.cs +++ b/Oqtane.Server/Managers/UserManager.cs @@ -41,7 +41,7 @@ namespace Oqtane.Managers Task> GetLogins(int userId, int siteId); Task AddLogin(User user, string token, string type, string key, string name); Task DeleteLogin(int userId, string provider, string key); - Task SendLoginLink(string email); + Task SendLoginLink(string email, string returnurl); } public class UserManager : IUserManager @@ -960,7 +960,7 @@ namespace Oqtane.Managers } } - public async Task SendLoginLink(string email) + public async Task SendLoginLink(string email, string returnurl) { try { @@ -973,7 +973,7 @@ namespace Oqtane.Managers var alias = _tenantManager.GetAlias(); var user = GetUser(identityuser.UserName, alias.SiteId); - string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token); + string url = alias.Protocol + alias.Name + "/pages/loginlink?name=" + user.Username + "&token=" + WebUtility.UrlEncode(token) + "&returnurl=" + WebUtility.UrlEncode(returnurl); string siteName = _sites.GetSite(alias.SiteId).Name; string subject = _localizer["LoginLinkEmailSubject"]; subject = subject.Replace("[SiteName]", siteName); diff --git a/Oqtane.Server/Pages/LoginLink.cshtml.cs b/Oqtane.Server/Pages/LoginLink.cshtml.cs index d090c7ed..16b34d0c 100644 --- a/Oqtane.Server/Pages/LoginLink.cshtml.cs +++ b/Oqtane.Server/Pages/LoginLink.cshtml.cs @@ -27,38 +27,45 @@ namespace Oqtane.Pages _logger = logger; } - public async Task OnGetAsync(string name, string token) + public async Task OnGetAsync(string name, string token, string returnurl) { - var returnurl = "/login"; + returnurl = (returnurl == null) ? "" : WebUtility.UrlDecode(returnurl); if (bool.Parse(HttpContext.GetSiteSettings().GetValue("LoginOptions:LoginLink", "false")) && - !User.Identity.IsAuthenticated && !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(token)) + !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(token)) { var validuser = false; - IdentityUser identityuser = await _identityUserManager.FindByNameAsync(name); - if (identityuser != null) + if (!User.Identity.IsAuthenticated) { - var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token); - if (result.Succeeded) + IdentityUser identityuser = await _identityUserManager.FindByNameAsync(name); + if (identityuser != null) { - await _identitySignInManager.SignInAsync(identityuser, false); - _logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Successful For User {Username}", name); - validuser = true; - returnurl = "/"; + var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token); + if (result.Succeeded) + { + await _identitySignInManager.SignInAsync(identityuser, false); + _logger.Log(LogLevel.Information, this, LogFunction.Security, "Login Link Successful For User {Username}", name); + validuser = true; + } } } if (!validuser) { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Login Link Failed For User {Username}", name); - returnurl += $"?status={ExternalLoginStatus.LoginLinkFailed}"; + returnurl = HttpContext.GetAlias().Path + $"/login?status={ExternalLoginStatus.LoginLinkFailed}"; } } else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Login Link Attempt For User {Username}", name); - returnurl = "/"; + returnurl = HttpContext.GetAlias().Path; + } + + if (!returnurl.StartsWith("/")) + { + returnurl = "/" + returnurl; } return LocalRedirect(Url.Content("~" + returnurl)); diff --git a/Oqtane.Server/Pages/Logout.cshtml.cs b/Oqtane.Server/Pages/Logout.cshtml.cs index 3d72d2ec..86263836 100644 --- a/Oqtane.Server/Pages/Logout.cshtml.cs +++ b/Oqtane.Server/Pages/Logout.cshtml.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; @@ -11,7 +9,6 @@ using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Managers; using Oqtane.Shared; -using Radzen.Blazor.Markdown; namespace Oqtane.Pages { diff --git a/Oqtane.Server/Pages/Passkey.cshtml.cs b/Oqtane.Server/Pages/Passkey.cshtml.cs index 8923d78d..89d3ee2e 100644 --- a/Oqtane.Server/Pages/Passkey.cshtml.cs +++ b/Oqtane.Server/Pages/Passkey.cshtml.cs @@ -10,6 +10,7 @@ using Oqtane.Infrastructure; using Oqtane.Managers; using Oqtane.Security; using Oqtane.Shared; +using Oqtane.UI; namespace Oqtane.Pages { @@ -103,7 +104,7 @@ namespace Oqtane.Pages { identityuser = null; var requestOptionsJson = await _identitySignInManager.MakePasskeyRequestOptionsAsync(identityuser); - returnurl += $"?options={WebUtility.UrlEncode(requestOptionsJson)}"; + returnurl = HttpContext.GetAlias().Path + $"/login?options={WebUtility.UrlEncode(requestOptionsJson)}&returnurl={WebUtility.UrlEncode(returnurl)}"; } else { @@ -129,6 +130,7 @@ namespace Oqtane.Pages else { _logger.Log(LogLevel.Error, this, LogFunction.Security, "Passkey Login Failed - Invalid Credential"); + returnurl = HttpContext.GetAlias().Path + $"/login?status={ExternalLoginStatus.PasskeyFailed}&returnurl={WebUtility.UrlEncode(returnurl)}"; } } else diff --git a/Oqtane.Shared/Shared/ExternalLoginStatus.cs b/Oqtane.Shared/Shared/ExternalLoginStatus.cs index 8423d799..8acd1694 100644 --- a/Oqtane.Shared/Shared/ExternalLoginStatus.cs +++ b/Oqtane.Shared/Shared/ExternalLoginStatus.cs @@ -11,5 +11,6 @@ namespace Oqtane.Shared { public const string RemoteFailure = "RemoteFailure"; public const string ReviewClaims = "ReviewClaims"; public const string LoginLinkFailed = "LoginLinkFailed"; + public const string PasskeyFailed = "PasskeyFailed"; } } From a10575bfc367bb9b124d95940800b7e7dc66a234 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 09:03:44 -0500 Subject: [PATCH 04/15] fix #5897 - allow SQLite to drop columns, remove deprecated columns, and handle upgrade logic --- .../Databases/Sqlite/SqliteDatabase.cs | 5 +- ...020201_RemoveFolderFileDeletableColumns.cs | 19 +++--- .../03030201_AddFolderFileIsDeletedColumns.cs | 2 +- .../Tenant/05020401_RemoveLanguageName.cs | 7 ++- .../Tenant/06000101_AddLanguageName.cs | 2 +- .../Tenant/10000102_RemoveSiteTenantId.cs | 5 +- .../10000201_RemoveDeprecatedColumns.cs | 60 +++++++++++++++++++ .../Admin/Files/Manager/FileManager.cs | 3 +- Oqtane.Server/Repository/FileRepository.cs | 1 - Oqtane.Server/Repository/FolderRepository.cs | 1 - Oqtane.Shared/Models/File.cs | 19 +++--- Oqtane.Shared/Models/Folder.cs | 12 ++-- Oqtane.Shared/Models/Language.cs | 3 +- 13 files changed, 105 insertions(+), 34 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs diff --git a/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs b/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs index e8cd6fd3..7d1226da 100644 --- a/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs +++ b/Oqtane.Server/Databases/Sqlite/SqliteDatabase.cs @@ -31,12 +31,15 @@ namespace Oqtane.Database.Sqlite public override void DropColumn(MigrationBuilder builder, string name, string table) { - // not implemented as SQLite does not support dropping columns + // SQLite supports dropping columns starting with version 3.35.0 but EF Core does not implement it yet + // note that a column cannot be dropped if it has a UNIQUE constraint, is part of a PRIMARY KEY, is indexed, or is referenced by other parts of the schema + builder.Sql($"ALTER TABLE {table} DROP COLUMN {name};"); } public override void AlterStringColumn(MigrationBuilder builder, string name, string table, int length, bool nullable, bool unicode, string index) { // not implemented as SQLite does not support altering columns + // note that column length does not need to be modified as SQLite uses a TEXT type which utilizes variable length strings } public override string ConcatenateSql(params string[] values) diff --git a/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs b/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs index f17cda4b..ec2fff10 100644 --- a/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs +++ b/Oqtane.Server/Migrations/Tenant/03020201_RemoveFolderFileDeletableColumns.cs @@ -16,15 +16,18 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); - folderEntityBuilder.DropColumn("DeletedBy"); - folderEntityBuilder.DropColumn("DeletedOn"); - folderEntityBuilder.DropColumn("IsDeleted"); + if (ActiveDatabase.Name != "Sqlite") + { + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.DropColumn("DeletedBy"); + folderEntityBuilder.DropColumn("DeletedOn"); + folderEntityBuilder.DropColumn("IsDeleted"); - var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); - fileEntityBuilder.DropColumn("DeletedBy"); - fileEntityBuilder.DropColumn("DeletedOn"); - fileEntityBuilder.DropColumn("IsDeleted"); + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + fileEntityBuilder.DropColumn("DeletedBy"); + fileEntityBuilder.DropColumn("DeletedOn"); + fileEntityBuilder.DropColumn("IsDeleted"); + } } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs index ad954a7d..ae62f197 100644 --- a/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs +++ b/Oqtane.Server/Migrations/Tenant/03030201_AddFolderFileIsDeletedColumns.cs @@ -16,7 +16,7 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - // IsDeleted columns were removed in 3.2.2 however SQLite does not support column removal so they had to be restored + // IsDeleted columns were removed in 3.2.2 however SQLite did not support column removal so they had to be restored if (ActiveDatabase.Name != "Sqlite") { var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); diff --git a/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs b/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs index 063b7027..b2f814d4 100644 --- a/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs +++ b/Oqtane.Server/Migrations/Tenant/05020401_RemoveLanguageName.cs @@ -16,8 +16,11 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); - languageEntityBuilder.DropColumn("Name"); + if (ActiveDatabase.Name != "Sqlite") + { + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.DropColumn("Name"); + } } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs b/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs index 76fedad6..69c48ddf 100644 --- a/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs +++ b/Oqtane.Server/Migrations/Tenant/06000101_AddLanguageName.cs @@ -16,7 +16,7 @@ namespace Oqtane.Migrations.Tenant protected override void Up(MigrationBuilder migrationBuilder) { - // Name column was removed in 5.2.4 however SQLite does not support column removal so it had to be restored + // Name column was removed in 5.2.4 however SQLite did not support column removal so it had to be restored if (ActiveDatabase.Name != "Sqlite") { var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); diff --git a/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs b/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs index 3ac75cbe..117f6af4 100644 --- a/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs +++ b/Oqtane.Server/Migrations/Tenant/10000102_RemoveSiteTenantId.cs @@ -18,7 +18,10 @@ namespace Oqtane.Migrations.Tenant { var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); siteEntityBuilder.DropIndex("IX_Site"); // TenantId, Name - siteEntityBuilder.DropColumn("TenantId"); + if (ActiveDatabase.Name != "Sqlite") + { + siteEntityBuilder.DropColumn("TenantId"); + } } protected override void Down(MigrationBuilder migrationBuilder) diff --git a/Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs b/Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs new file mode 100644 index 00000000..c719f88c --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/10000201_RemoveDeprecatedColumns.cs @@ -0,0 +1,60 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.10.00.02.01")] + public class RemoveDeprecatedColumns : MultiDatabaseMigration + { + public RemoveDeprecatedColumns(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + // Oqtane 10.0.2 includes support for column removal in SQLite, so we can now clean up deprecated columns + + // Folder columns were deprecated in Oqtane 3.2.2 + var folderEntityBuilder = new FolderEntityBuilder(migrationBuilder, ActiveDatabase); + folderEntityBuilder.DropColumn("IsDeleted"); + if (ActiveDatabase.Name == "Sqlite") + { + /// the following columns were not added back in 3.2.3 but they still exist in SQLite databases + folderEntityBuilder.DropColumn("DeletedBy"); + folderEntityBuilder.DropColumn("DeletedOn"); + } + + // File columns were deprecated in Oqtane 3.2.2 + var fileEntityBuilder = new FileEntityBuilder(migrationBuilder, ActiveDatabase); + // IsDeleted was added back in 3.2.3 for non-SQLLite databases + fileEntityBuilder.DropColumn("IsDeleted"); + if (ActiveDatabase.Name == "Sqlite") + { + /// the following columns were not added back in 3.2.3 but they still exist in SQLite databases + fileEntityBuilder.DropColumn("DeletedBy"); + fileEntityBuilder.DropColumn("DeletedOn"); + } + + // Language columns were deprecated in Oqtane 5.2.4 + var languageEntityBuilder = new LanguageEntityBuilder(migrationBuilder, ActiveDatabase); + languageEntityBuilder.DropColumn("Name"); + + // Site columns were deprecated in Oqtane 10.0.1 + var siteEntityBuilder = new SiteEntityBuilder(migrationBuilder, ActiveDatabase); + if (ActiveDatabase.Name == "Sqlite") + { + /// the following column was removed for non-SQLite databases in 10.0.1 + siteEntityBuilder.DropColumn("TenantId"); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs b/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs index 43dbda33..dd684d59 100644 --- a/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs +++ b/Oqtane.Server/Modules/Admin/Files/Manager/FileManager.cs @@ -34,7 +34,6 @@ namespace Oqtane.Modules.Admin.Files.Manager if (folder.ModifiedOn >= lastIndexedOn) { changed = true; - removed = folder.IsDeleted.Value; } var files = _fileRepository.GetFiles(folder.FolderId); @@ -78,7 +77,7 @@ namespace Oqtane.Modules.Admin.Files.Manager Permissions = $"{EntityNames.Folder}:{folder.FolderId}", ContentModifiedBy = file.ModifiedBy, ContentModifiedOn = file.ModifiedOn, - IsDeleted = (removed || file.IsDeleted.Value) + IsDeleted = (removed) }; searchContents.Add(searchContent); } diff --git a/Oqtane.Server/Repository/FileRepository.cs b/Oqtane.Server/Repository/FileRepository.cs index def51519..3cd3e2bf 100644 --- a/Oqtane.Server/Repository/FileRepository.cs +++ b/Oqtane.Server/Repository/FileRepository.cs @@ -72,7 +72,6 @@ namespace Oqtane.Repository public File AddFile(File file) { using var db = _dbContextFactory.CreateDbContext(); - file.IsDeleted = false; db.File.Add(file); db.SaveChanges(); file.Folder = _folderRepository.GetFolder(file.FolderId); diff --git a/Oqtane.Server/Repository/FolderRepository.cs b/Oqtane.Server/Repository/FolderRepository.cs index d00a6a7c..fb68033f 100644 --- a/Oqtane.Server/Repository/FolderRepository.cs +++ b/Oqtane.Server/Repository/FolderRepository.cs @@ -51,7 +51,6 @@ namespace Oqtane.Repository public Folder AddFolder(Folder folder) { using var db = _dbContextFactory.CreateDbContext(); - folder.IsDeleted = false; db.Folder.Add(folder); db.SaveChanges(); _permissions.UpdatePermissions(folder.SiteId, EntityNames.Folder, folder.FolderId, folder.PermissionList); diff --git a/Oqtane.Shared/Models/File.cs b/Oqtane.Shared/Models/File.cs index e395bdb5..18c320a7 100644 --- a/Oqtane.Shared/Models/File.cs +++ b/Oqtane.Shared/Models/File.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json.Serialization; using Oqtane.Shared; namespace Oqtane.Models @@ -55,13 +56,6 @@ namespace Oqtane.Models /// public string Description { get; set; } - /// - /// Deprecated - /// Note that this property still exists in the database because columns cannot be dropped in SQLite - /// Therefore the property must be retained/mapped even though the framework no longer uses it - /// - public bool? IsDeleted { get; set; } - /// /// Object reference to the object. /// Use this if you need to determine what the file belongs to. @@ -74,5 +68,16 @@ namespace Oqtane.Models /// [NotMapped] public string Url { get; set; } + + #region Deprecated Properties + + [Obsolete("The IsDeleted property is deprecated. Soft delete of files is not supported.", false)] + [NotMapped] + [JsonIgnore] // exclude from API payload + public bool? IsDeleted { get; set; } + + #endregion + + } } diff --git a/Oqtane.Shared/Models/Folder.cs b/Oqtane.Shared/Models/Folder.cs index 47177cdb..d8e3e1ce 100644 --- a/Oqtane.Shared/Models/Folder.cs +++ b/Oqtane.Shared/Models/Folder.cs @@ -67,13 +67,6 @@ namespace Oqtane.Models /// public string CacheControl { get; set; } - /// - /// Deprecated - /// Note that this property still exists in the database because columns cannot be dropped in SQLite - /// Therefore the property must be retained/mapped even though the framework no longer uses it - /// - public bool? IsDeleted { get; set; } - /// /// TODO: todoc what would this contain? /// @@ -110,6 +103,11 @@ namespace Oqtane.Models } } + [Obsolete("The IsDeleted property is deprecated. Soft delete of folders is not supported.", false)] + [NotMapped] + [JsonIgnore] // exclude from API payload + public bool? IsDeleted { get; set; } + #endregion } } diff --git a/Oqtane.Shared/Models/Language.cs b/Oqtane.Shared/Models/Language.cs index 7f226168..750d8583 100644 --- a/Oqtane.Shared/Models/Language.cs +++ b/Oqtane.Shared/Models/Language.cs @@ -31,9 +31,8 @@ namespace Oqtane.Models /// /// Language Name - corresponds to , _not_ - /// Note that this property still exists in the database because columns cannot be dropped in SQLite - /// Therefore the property must be retained/mapped even though the framework populates it from the Culture API /// + [NotMapped] public string Name { get; set; } [NotMapped] From 5c536aafc2b3f36215c503a9059bd83bd0483cdf Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 10:56:08 -0500 Subject: [PATCH 05/15] handle case sensitivity for entity names and permission names --- .../Controllers/SettingController.cs | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs index 0a2eb0f5..c7a6d2ac 100644 --- a/Oqtane.Server/Controllers/SettingController.cs +++ b/Oqtane.Server/Controllers/SettingController.cs @@ -90,7 +90,7 @@ namespace Oqtane.Controllers else { // suppress unauthorized visitor logging as it is usually caused by clients that do not support cookies or private browsing sessions - if (entityName != EntityNames.Visitor) + if (FormatName(entityName) != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access Settings For EntityName {EntityName} And EntityId {EntityId}", entityName, entityId); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -114,7 +114,7 @@ namespace Oqtane.Controllers } else { - if (setting != null && entityName != EntityNames.Visitor) + if (setting != null && FormatName(entityName) != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Read, "User Not Authorized To Access SettingId {SettingId} For EntityName {EntityName} ", id, entityName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -139,7 +139,7 @@ namespace Oqtane.Controllers } else { - if (setting.EntityName != EntityNames.Visitor) + if (FormatName(setting.EntityName) != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Create, "User Not Authorized To Add Setting {Setting}", setting); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -161,7 +161,7 @@ namespace Oqtane.Controllers } else { - if (setting.EntityName != EntityNames.Visitor) + if (FormatName(setting.EntityName) != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Update, "User Not Authorized To Update Setting {Setting}", setting); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -261,7 +261,7 @@ namespace Oqtane.Controllers } else { - if (entityName != EntityNames.Visitor) + if (FormatName(entityName) != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For EntityName {EntityName} EntityId {EntityId} SettingName {SettingName}", entityName, entityId, settingName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -282,7 +282,7 @@ namespace Oqtane.Controllers } else { - if (entityName != EntityNames.Visitor) + if (FormatName(entityName) != EntityNames.Visitor) { _logger.Log(LogLevel.Error, this, LogFunction.Delete, "Setting Does Not Exist Or User Not Authorized To Delete Setting For SettingId {SettingId} For EntityName {EntityName} ", id, entityName); HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; @@ -408,19 +408,21 @@ namespace Oqtane.Controllers private bool IsAuthorized(string entityName, int entityId, string permissionName) { bool authorized = false; + if (entityName == EntityNames.PageModule) { entityName = EntityNames.Module; entityId = _pageModules.GetPageModule(entityId).ModuleId; } - switch (entityName) + + switch (FormatName(entityName)) { case EntityNames.Tenant: case EntityNames.ModuleDefinition: case EntityNames.Host: case EntityNames.Job: case EntityNames.Theme: - if (permissionName == PermissionNames.Edit) + if (FormatName(permissionName) == PermissionNames.Edit) { authorized = User.IsInRole(RoleNames.Host); } @@ -431,7 +433,7 @@ namespace Oqtane.Controllers break; case EntityNames.Site: case EntityNames.Role: - if (permissionName == PermissionNames.Edit) + if (FormatName(permissionName) == PermissionNames.Edit) { authorized = User.IsInRole(RoleNames.Admin); } @@ -458,7 +460,7 @@ namespace Oqtane.Controllers break; default: // custom entity authorized = true; - if (permissionName == PermissionNames.Edit) + if (FormatName(permissionName) == PermissionNames.Edit) { if (entityId == -1) { @@ -477,7 +479,7 @@ namespace Oqtane.Controllers private bool FilterPrivate(string entityName, int entityId) { bool filter = false; - switch (entityName) + switch (FormatName(entityName)) { case EntityNames.Tenant: case EntityNames.ModuleDefinition: @@ -526,9 +528,9 @@ namespace Oqtane.Controllers private void AddSyncEvent(string EntityName, int EntityId, int SettingId, string Action) { - _syncManager.AddSyncEvent(_alias, EntityName + "Setting", SettingId, Action); + _syncManager.AddSyncEvent(_alias, FormatName(EntityName) + "Setting", SettingId, Action); - switch (EntityName) + switch (FormatName(EntityName)) { case EntityNames.Module: case EntityNames.Page: @@ -540,5 +542,15 @@ namespace Oqtane.Controllers break; } } + + private string FormatName(string name) + { + if (!string.IsNullOrEmpty(name)) + { + // entity names and permission names are case sensitive + name = name.Substring(0, 1).ToUpper() + name.Substring(1).ToLower(); + } + return name; + } } } From 417a6bf2268a708946eac8d9e5a0f783512aa458 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 14:25:44 -0500 Subject: [PATCH 06/15] expand size of page name --- Oqtane.Client/Modules/Admin/Pages/Add.razor | 91 ++++++++++--------- Oqtane.Client/Modules/Admin/Pages/Edit.razor | 90 +++++++++--------- .../Resources/Modules/Admin/Pages/Add.resx | 10 +- .../Resources/Modules/Admin/Pages/Edit.resx | 3 + .../Tenant/10000202_ExpandPageName.cs | 28 ++++++ 5 files changed, 130 insertions(+), 92 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/10000202_ExpandPageName.cs diff --git a/Oqtane.Client/Modules/Admin/Pages/Add.razor b/Oqtane.Client/Modules/Admin/Pages/Add.razor index 36385232..da5bb505 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Add.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Add.razor @@ -16,7 +16,7 @@
- +
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @@ -81,21 +81,9 @@ }
- +
- -
-
-
- -
- +
@@ -110,27 +98,6 @@
-
- -
- -
-
- -
-
-
- -
- -
-
-
- -
- -
-
@@ -141,15 +108,8 @@
- -
+
-
- -
- -
-
@@ -181,6 +141,49 @@
+
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
diff --git a/Oqtane.Client/Modules/Admin/Pages/Edit.razor b/Oqtane.Client/Modules/Admin/Pages/Edit.razor index 2811bd87..acf99d1e 100644 --- a/Oqtane.Client/Modules/Admin/Pages/Edit.razor +++ b/Oqtane.Client/Modules/Admin/Pages/Edit.razor @@ -22,7 +22,7 @@
- +
@if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Admin)) @@ -98,21 +98,9 @@
}
- +
- -
-
-
- -
- +
@@ -127,27 +115,6 @@
-
- -
- -
-
- -
-
-
- -
- -
-
-
- -
- -
-
@@ -158,14 +125,8 @@
-
+
-
- -
- -
-
@@ -200,6 +161,49 @@
+
+
+
+ +
+ +
+
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx index b818eacd..e16db76a 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Add.resx @@ -225,9 +225,6 @@ Personalizable? - - Appearance - Optionally enter content to be included in the page head (ie. meta, link, or script tags) @@ -253,7 +250,7 @@ Permissions - Theme Settings + Theme The date that this page is active @@ -267,4 +264,7 @@ Expiry Date: - + + Appearance + + \ No newline at end of file diff --git a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx index ded50502..0f6df7e5 100644 --- a/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx +++ b/Oqtane.Client/Resources/Modules/Admin/Pages/Edit.resx @@ -309,4 +309,7 @@ Specify if changes made to page permissions should be propagated to the modules on this page + + Theme + \ No newline at end of file diff --git a/Oqtane.Server/Migrations/Tenant/10000202_ExpandPageName.cs b/Oqtane.Server/Migrations/Tenant/10000202_ExpandPageName.cs new file mode 100644 index 00000000..c7111406 --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/10000202_ExpandPageName.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.10.00.02.02")] + public class ExpandPageName : MultiDatabaseMigration + { + public ExpandPageName(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var pageEntityBuilder = new PageEntityBuilder(migrationBuilder, ActiveDatabase); + pageEntityBuilder.AlterStringColumn("Name", 100); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} From 8120db84f45ff8b844aa47b75894da2e30d9abfb Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 15:06:06 -0500 Subject: [PATCH 07/15] add url mapping referrer --- .../Modules/Admin/UrlMappings/Index.razor | 4 ++- Oqtane.Server/Components/App.razor | 5 +++- .../Tenant/10000203_AddUrlMappingReferrer.cs | 28 +++++++++++++++++++ .../Repository/UrlMappingRepository.cs | 11 ++++++++ Oqtane.Shared/Models/UrlMapping.cs | 5 ++++ 5 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 Oqtane.Server/Migrations/Tenant/10000203_AddUrlMappingReferrer.cs diff --git a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor index 554213fa..ae12713b 100644 --- a/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor +++ b/Oqtane.Client/Modules/Admin/UrlMappings/Index.razor @@ -36,6 +36,7 @@ else @Localizer["Url"] @Localizer["Requests"] @Localizer["Requested"] + @Localizer["Referrer"] @@ -49,7 +50,8 @@ else @context.Requests @UtcToLocal(context.RequestedOn) - + @context.Referrer + diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor index 6276eea0..fe668866 100644 --- a/Oqtane.Server/Components/App.razor +++ b/Oqtane.Server/Components/App.razor @@ -294,8 +294,11 @@ private void HandlePageNotFound(Site site, Page page, Route route) { + // referrer will only be set if the link originated externally + string referrer = (Context.Request.Headers[HeaderNames.Referer] != StringValues.Empty) ? Context.Request.Headers[HeaderNames.Referer] : ""; + // page not found - look for url mapping - var urlMapping = UrlMappingRepository.GetUrlMapping(site.SiteId, route.PagePath); + var urlMapping = UrlMappingRepository.GetUrlMapping(site.SiteId, route.PagePath, referrer); if (urlMapping != null && !string.IsNullOrEmpty(urlMapping.MappedUrl)) { // redirect to mapped url diff --git a/Oqtane.Server/Migrations/Tenant/10000203_AddUrlMappingReferrer.cs b/Oqtane.Server/Migrations/Tenant/10000203_AddUrlMappingReferrer.cs new file mode 100644 index 00000000..217b0d4c --- /dev/null +++ b/Oqtane.Server/Migrations/Tenant/10000203_AddUrlMappingReferrer.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Oqtane.Databases.Interfaces; +using Oqtane.Migrations.EntityBuilders; +using Oqtane.Repository; + +namespace Oqtane.Migrations.Tenant +{ + [DbContext(typeof(TenantDBContext))] + [Migration("Tenant.10.00.02.03")] + public class AddUrlMappingReferrer : MultiDatabaseMigration + { + public AddUrlMappingReferrer(IDatabase database) : base(database) + { + } + + protected override void Up(MigrationBuilder migrationBuilder) + { + var urlMappingEntityBuilder = new UrlMappingEntityBuilder(migrationBuilder, ActiveDatabase); + urlMappingEntityBuilder.AddStringColumn("Referrer", 2048); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + // not implemented + } + } +} diff --git a/Oqtane.Server/Repository/UrlMappingRepository.cs b/Oqtane.Server/Repository/UrlMappingRepository.cs index 9c2efbb6..b86b68b8 100644 --- a/Oqtane.Server/Repository/UrlMappingRepository.cs +++ b/Oqtane.Server/Repository/UrlMappingRepository.cs @@ -14,6 +14,7 @@ namespace Oqtane.Repository UrlMapping GetUrlMapping(int urlMappingId); UrlMapping GetUrlMapping(int urlMappingId, bool tracking); UrlMapping GetUrlMapping(int siteId, string url); + UrlMapping GetUrlMapping(int siteId, string url, string referrer); void DeleteUrlMapping(int urlMappingId); int DeleteUrlMappings(int siteId, int age); } @@ -78,6 +79,11 @@ namespace Oqtane.Repository } public UrlMapping GetUrlMapping(int siteId, string url) + { + return GetUrlMapping(siteId, url, ""); + } + + public UrlMapping GetUrlMapping(int siteId, string url, string referrer) { using var db = _dbContextFactory.CreateDbContext(); url = (url.StartsWith("/")) ? url.Substring(1) : url; @@ -93,6 +99,7 @@ namespace Oqtane.Repository urlMapping.Url = url; urlMapping.MappedUrl = ""; urlMapping.Requests = 1; + urlMapping.Referrer = referrer; urlMapping.CreatedOn = DateTime.UtcNow; urlMapping.RequestedOn = DateTime.UtcNow; try @@ -109,6 +116,10 @@ namespace Oqtane.Repository { urlMapping.Requests += 1; urlMapping.RequestedOn = DateTime.UtcNow; + if (!string.IsNullOrEmpty(referrer)) + { + urlMapping.Referrer = referrer; + } urlMapping = UpdateUrlMapping(urlMapping); } return urlMapping; diff --git a/Oqtane.Shared/Models/UrlMapping.cs b/Oqtane.Shared/Models/UrlMapping.cs index b5a15384..fb00e709 100644 --- a/Oqtane.Shared/Models/UrlMapping.cs +++ b/Oqtane.Shared/Models/UrlMapping.cs @@ -33,6 +33,11 @@ namespace Oqtane.Models /// public int Requests { get; set; } + /// + /// Last referrer to the Url (only set if linked to externally) + /// + public string Referrer { get; set; } + /// /// Date when the url was first requested for the site /// From 96bea7842403958e7974be0752e26d5b0d0f4ee3 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 15:35:35 -0500 Subject: [PATCH 08/15] allow menu component to be extensible --- .../Themes/Controls/Theme/Menu.razor | 35 +++++++++++++------ .../Themes/OqtaneTheme/Themes/Default.razor | 3 +- 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/Menu.razor b/Oqtane.Client/Themes/Controls/Theme/Menu.razor index 0f2610cc..738ece1b 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Menu.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Menu.razor @@ -1,20 +1,33 @@ -@namespace Oqtane.Themes.Controls +@namespace Oqtane.Themes.Controls -@switch (Orientation) +@if (ComponentType != null) { - case "Horizontal": - - break; - default: // Vertical - { - - break; - } + } @code{ - [Parameter] public string Orientation { get; set; } + [Parameter] + public string MenuType { get; set; } + + public Type ComponentType { get; set; } + + protected override void OnInitialized() + { + if (string.IsNullOrEmpty(MenuType) && !string.IsNullOrEmpty(Orientation)) + { + if (Orientation == "Horizontal") + { + MenuType = "Oqtane.Themes.Controls.MenuHorizontal, Oqtane.Client"; + } + else + { + MenuType = "Oqtane.Themes.Controls.MenuVertical, Oqtane.Client"; + } + } + + ComponentType = Type.GetType(MenuType); + } } diff --git a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor index 5032499a..f4fe08d6 100644 --- a/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor +++ b/Oqtane.Client/Themes/OqtaneTheme/Themes/Default.razor @@ -4,7 +4,8 @@
- + +
From 15ee2c9bcb20bac0af93b58e85588f09d89b9488 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Fri, 19 Dec 2025 15:51:21 -0500 Subject: [PATCH 09/15] bump version to 10.0.2 --- Directory.Build.props | 4 ++-- Oqtane.Application/Client/Oqtane.Application.Client.csproj | 2 +- Oqtane.Application/Oqtane.Application.Template.nuspec | 2 +- Oqtane.Application/Server/Oqtane.Application.Server.csproj | 2 +- Oqtane.Application/Shared/Oqtane.Application.Shared.csproj | 2 +- Oqtane.Maui/Oqtane.Maui.csproj | 2 +- Oqtane.Package/Oqtane.Client.nuspec | 4 ++-- Oqtane.Package/Oqtane.Framework.nuspec | 6 +++--- Oqtane.Package/Oqtane.Server.nuspec | 4 ++-- Oqtane.Package/Oqtane.Shared.nuspec | 4 ++-- Oqtane.Package/Oqtane.Updater.nuspec | 4 ++-- Oqtane.Package/install.ps1 | 2 +- Oqtane.Package/upgrade.ps1 | 2 +- Oqtane.Shared/Shared/Constants.cs | 4 ++-- 14 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 28379aa7..343fde0e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net10.0 Debug;Release - 10.0.1 + 10.0.2 Oqtane Shaun Walker .NET Foundation @@ -10,7 +10,7 @@ .NET Foundation https://www.oqtane.org https://github.com/oqtane/oqtane.framework/blob/dev/LICENSE - https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2 https://github.com/oqtane/oqtane.framework Git diff --git a/Oqtane.Application/Client/Oqtane.Application.Client.csproj b/Oqtane.Application/Client/Oqtane.Application.Client.csproj index d932bbb6..af164953 100644 --- a/Oqtane.Application/Client/Oqtane.Application.Client.csproj +++ b/Oqtane.Application/Client/Oqtane.Application.Client.csproj @@ -23,7 +23,7 @@ - + diff --git a/Oqtane.Application/Oqtane.Application.Template.nuspec b/Oqtane.Application/Oqtane.Application.Template.nuspec index 1b9a140b..0c9c6d23 100644 --- a/Oqtane.Application/Oqtane.Application.Template.nuspec +++ b/Oqtane.Application/Oqtane.Application.Template.nuspec @@ -2,7 +2,7 @@ Oqtane.Application.Template - 10.0.1 + 10.0.2 Oqtane Application Template For Blazor Shaun Walker false diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj index c1ed4362..891449de 100644 --- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj +++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj @@ -33,7 +33,7 @@ - + diff --git a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj index a063b58e..07c058f8 100644 --- a/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj +++ b/Oqtane.Application/Shared/Oqtane.Application.Shared.csproj @@ -11,7 +11,7 @@ - + diff --git a/Oqtane.Maui/Oqtane.Maui.csproj b/Oqtane.Maui/Oqtane.Maui.csproj index 2314ac0a..f2273569 100644 --- a/Oqtane.Maui/Oqtane.Maui.csproj +++ b/Oqtane.Maui/Oqtane.Maui.csproj @@ -18,7 +18,7 @@ com.oqtane.maui - 10.0.1 + 10.0.2 1 diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec index de21e194..ef0fa733 100644 --- a/Oqtane.Package/Oqtane.Client.nuspec +++ b/Oqtane.Package/Oqtane.Client.nuspec @@ -2,7 +2,7 @@ Oqtane.Client - 10.0.1 + 10.0.2 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec index 952bb267..19cd8031 100644 --- a/Oqtane.Package/Oqtane.Framework.nuspec +++ b/Oqtane.Package/Oqtane.Framework.nuspec @@ -2,7 +2,7 @@ Oqtane.Framework - 10.0.1 + 10.0.2 Shaun Walker .NET Foundation Oqtane Framework @@ -11,8 +11,8 @@ .NET Foundation false MIT - https://github.com/oqtane/oqtane.framework/releases/download/v10.0.1/Oqtane.Framework.10.0.1.Upgrade.zip - https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1 + https://github.com/oqtane/oqtane.framework/releases/download/v10.0.2/Oqtane.Framework.10.0.2.Upgrade.zip + https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2 readme.md icon.png oqtane framework diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec index 47a0b13a..f24dc68f 100644 --- a/Oqtane.Package/Oqtane.Server.nuspec +++ b/Oqtane.Package/Oqtane.Server.nuspec @@ -2,7 +2,7 @@ Oqtane.Server - 10.0.1 + 10.0.2 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec index 78dfbf9d..07e816d0 100644 --- a/Oqtane.Package/Oqtane.Shared.nuspec +++ b/Oqtane.Package/Oqtane.Shared.nuspec @@ -2,7 +2,7 @@ Oqtane.Shared - 10.0.1 + 10.0.2 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2 readme.md icon.png oqtane diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec index 6873d041..3994a0d3 100644 --- a/Oqtane.Package/Oqtane.Updater.nuspec +++ b/Oqtane.Package/Oqtane.Updater.nuspec @@ -2,7 +2,7 @@ Oqtane.Updater - 10.0.1 + 10.0.2 Shaun Walker .NET Foundation Oqtane Framework @@ -12,7 +12,7 @@ false MIT https://github.com/oqtane/oqtane.framework - https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1 + https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2 readme.md icon.png oqtane diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1 index 6efebb86..bbe63104 100644 --- a/Oqtane.Package/install.ps1 +++ b/Oqtane.Package/install.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.1.Install.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.2.Install.zip" -Force diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1 index 4f20bec7..7d31b12f 100644 --- a/Oqtane.Package/upgrade.ps1 +++ b/Oqtane.Package/upgrade.ps1 @@ -1 +1 @@ -Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.1.Upgrade.zip" -Force +Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.2.Upgrade.zip" -Force diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs index 3189ccce..d642b176 100644 --- a/Oqtane.Shared/Shared/Constants.cs +++ b/Oqtane.Shared/Shared/Constants.cs @@ -4,8 +4,8 @@ namespace Oqtane.Shared { public class Constants { - public static readonly string Version = "10.0.1"; - public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1"; + public static readonly string Version = "10.0.2"; + public const string ReleaseVersions = "1.0.0,1.0.1,1.0.2,1.0.3,1.0.4,2.0.0,2.0.1,2.0.2,2.1.0,2.2.0,2.3.0,2.3.1,3.0.0,3.0.1,3.0.2,3.0.3,3.1.0,3.1.1,3.1.2,3.1.3,3.1.4,3.2.0,3.2.1,3.3.0,3.3.1,3.4.0,3.4.1,3.4.2,3.4.3,4.0.0,4.0.1,4.0.2,4.0.3,4.0.4,4.0.5,4.0.6,5.0.0,5.0.1,5.0.2,5.0.3,5.1.0,5.1.1,5.1.2,5.2.0,5.2.1,5.2.2,5.2.3,5.2.4,6.0.0,6.0.1,6.1.0,6.1.1,6.1.2,6.1.3,6.1.4,6.1.5,6.2.0,6.2.1,10.0.0,10.0.1,10.0.2"; public const string PackageId = "Oqtane.Framework"; public const string ClientId = "Oqtane.Client"; public const string UpdaterPackageId = "Oqtane.Updater"; From 7330d5b2a73a4e9bab67a0e09e98932a14048a03 Mon Sep 17 00:00:00 2001 From: Ben Date: Mon, 22 Dec 2025 15:17:40 +0800 Subject: [PATCH 10/15] Fix #5911: let redirect to mapped url works in load balance env. --- .../Extensions/OqtaneServiceCollectionExtensions.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 0452686c..8d0c3f75 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -44,7 +44,10 @@ namespace Microsoft.Extensions.DependencyInjection // process forwarded headers on load balancers and proxy servers services.Configure(options => { - options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto; + options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedHost; + + options.KnownIPNetworks.Clear(); + options.KnownProxies.Clear(); }); // register localization services From 0ad5bd23352cc78280228004e6c81c1d21629ebc Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 22 Dec 2025 08:37:45 -0500 Subject: [PATCH 11/15] add ability for menu component to support arbitrary attributes --- Oqtane.Client/Themes/Controls/Theme/Menu.razor | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/Oqtane.Client/Themes/Controls/Theme/Menu.razor b/Oqtane.Client/Themes/Controls/Theme/Menu.razor index 738ece1b..7af966c8 100644 --- a/Oqtane.Client/Themes/Controls/Theme/Menu.razor +++ b/Oqtane.Client/Themes/Controls/Theme/Menu.razor @@ -1,8 +1,8 @@ @namespace Oqtane.Themes.Controls -@if (ComponentType != null) +@if (_menuType != null) { - + } @code{ @@ -12,7 +12,10 @@ [Parameter] public string MenuType { get; set; } - public Type ComponentType { get; set; } + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary Attributes { get; set; } = new Dictionary(); + + private Type _menuType; protected override void OnInitialized() { @@ -28,6 +31,6 @@ } } - ComponentType = Type.GetType(MenuType); + _menuType = Type.GetType(MenuType); } } From 67332992904275c0bf8de96913519936f8d12875 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Mon, 22 Dec 2025 16:04:00 -0500 Subject: [PATCH 12/15] synchronize static assets with .NET MAUI --- Oqtane.Maui/wwwroot/css/app.css | 5 +++++ Oqtane.Maui/wwwroot/js/interop.js | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/Oqtane.Maui/wwwroot/css/app.css b/Oqtane.Maui/wwwroot/css/app.css index 13c7d539..013ef348 100644 --- a/Oqtane.Maui/wwwroot/css/app.css +++ b/Oqtane.Maui/wwwroot/css/app.css @@ -273,6 +273,11 @@ app { min-height: 250px; } +.app-editor-resizable { + resize: vertical; + overflow: auto; +} + .app-logo .navbar-brand { padding: 5px 20px 5px 20px; } diff --git a/Oqtane.Maui/wwwroot/js/interop.js b/Oqtane.Maui/wwwroot/js/interop.js index fecc4c99..944f9812 100644 --- a/Oqtane.Maui/wwwroot/js/interop.js +++ b/Oqtane.Maui/wwwroot/js/interop.js @@ -124,7 +124,7 @@ Oqtane.Interop = { } }, includeScript: function (id, src, integrity, crossorigin, type, content, location, dataAttributes) { - var script; + var script = null; if (src !== "") { script = document.querySelector("script[src=\"" + CSS.escape(src) + "\"]"); } @@ -140,7 +140,7 @@ Oqtane.Interop = { } } } - if (script !== null) { + if (script instanceof HTMLScriptElement) { script.remove(); script = null; } @@ -516,5 +516,17 @@ Oqtane.Interop = { } } } + }, + createCredential: async function (optionsResponse) { + const optionsJson = JSON.parse(optionsResponse); + const options = PublicKeyCredential.parseCreationOptionsFromJSON(optionsJson); + const credential = await navigator.credentials.create({ publicKey: options }); + return JSON.stringify(credential); + }, + requestCredential: async function (optionsResponse) { + const optionsJson = JSON.parse(optionsResponse); + const options = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJson); + const credential = await navigator.credentials.get({ publicKey: options, undefined }); + return JSON.stringify(credential); } }; From aca70dd6c765ea1e6c57f583c3b5fedf4dd84b7d Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 23 Dec 2025 12:09:16 -0500 Subject: [PATCH 13/15] fix #5916 - PostgreSQL failing to install on .NET 10 --- .../PostgreSQL/PostgreSQLDatabase.cs | 53 ++++++++++--------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/Oqtane.Server/Databases/PostgreSQL/PostgreSQLDatabase.cs b/Oqtane.Server/Databases/PostgreSQL/PostgreSQLDatabase.cs index c22908b2..96c7b65f 100644 --- a/Oqtane.Server/Databases/PostgreSQL/PostgreSQLDatabase.cs +++ b/Oqtane.Server/Databases/PostgreSQL/PostgreSQLDatabase.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Globalization; +using System.Linq; using EFCore.NamingConventions.Internal; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Migrations; @@ -108,36 +109,40 @@ namespace Oqtane.Database.PostgreSQL public override void UpdateIdentityStoreTableNames(ModelBuilder builder) { - foreach(var entity in builder.Model.GetEntityTypes()) + foreach (var entity in builder.Model.GetEntityTypes()) { - var tableName = entity.GetTableName(); - if (tableName.StartsWith("AspNetUser")) + // the IdentityPasskeyData entity was introduced in .NET 10 and is not mapped to a database table so should be ignored + if (entity.ClrType.Name != "IdentityPasskeyData") { - // replace table name - entity.SetTableName(RewriteName(entity.GetTableName())); - - // replace column names - foreach(var property in entity.GetProperties()) + var tableName = entity.GetTableName(); + if (tableName.StartsWith("AspNetUser")) { - property.SetColumnName(RewriteName(property.Name)); - } + // replace table name + entity.SetTableName(RewriteName(entity.GetTableName())); - // replace key names - foreach(var key in entity.GetKeys()) - { - key.SetName(RewriteName(key.GetName())); - } + // replace column names + foreach (var property in entity.GetProperties()) + { + property.SetColumnName(RewriteName(property.Name)); + } - // replace foreign key names - foreach (var key in entity.GetForeignKeys()) - { - key.PrincipalKey.SetName(RewriteName(key.PrincipalKey.GetName())); - } + // replace key names + foreach (var key in entity.GetKeys()) + { + key.SetName(RewriteName(key.GetName())); + } - // replace index names - foreach (var index in entity.GetIndexes()) - { - index.SetDatabaseName(RewriteName(index.GetDatabaseName())); + // replace foreign key names + foreach (var key in entity.GetForeignKeys()) + { + key.PrincipalKey.SetName(RewriteName(key.PrincipalKey.GetName())); + } + + // replace index names + foreach (var index in entity.GetIndexes()) + { + index.SetDatabaseName(RewriteName(index.GetDatabaseName())); + } } } } From fc2a8cb9dd6cad6f8f1275af2c3d5def886ee578 Mon Sep 17 00:00:00 2001 From: sbwalker Date: Tue, 23 Dec 2025 13:16:12 -0500 Subject: [PATCH 14/15] update azuredeploy version --- azuredeploy.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azuredeploy.json b/azuredeploy.json index 54ab2451..beb6ea0a 100644 --- a/azuredeploy.json +++ b/azuredeploy.json @@ -220,7 +220,7 @@ "apiVersion": "2024-04-01", "name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]", "properties": { - "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.1/Oqtane.Framework.10.0.1.Install.zip" + "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.2/Oqtane.Framework.10.0.2.Install.zip" }, "dependsOn": [ "[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]" From cae61ab7017c82b135030f59583e754b5e1a0170 Mon Sep 17 00:00:00 2001 From: Shaun Walker Date: Tue, 23 Dec 2025 13:20:09 -0500 Subject: [PATCH 15/15] Update README.md --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 03b1c488..2f481f7d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Oqtane is being developed based on some fundamental principles which are outline # Latest Release -[10.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1) was released on December 15, 2025 and is a major release including 38 pull requests by 5 different contributors, pushing the total number of project commits all-time over 7400. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. +[10.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2) was released on December 23, 2025 and is a maintenance release including 19 pull requests by 2 different contributors, pushing the total number of project commits all-time to nearly 7500. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers. # Try It Now! @@ -111,6 +111,9 @@ Connect with other developers, get support, and share ideas by joining the Oqtan # Roadmap This project is open source, and therefore is a work in progress... +[10.0.2](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.2) (Dec 23, 2025) +- [x] Stabilization improvements + [10.0.1](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.1) (Dec 15, 2025) - [x] Stabilization improvements