diff --git a/Directory.Build.props b/Directory.Build.props
index 223c5f9b..aff5bd3e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -1,8 +1,8 @@
- net9.0
+ net10.0
Debug;Release
- 6.2.1
+ 10.0.0
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/v6.2.1
+ https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0
https://github.com/oqtane/oqtane.framework
Git
diff --git a/Oqtane.Application/.template.config/template.json b/Oqtane.Application/.template.config/template.json
index ebcd1b03..7f0e8ba9 100644
--- a/Oqtane.Application/.template.config/template.json
+++ b/Oqtane.Application/.template.config/template.json
@@ -18,11 +18,6 @@
},
"sourceName": "Oqtane.Application",
"preferNameDirectory": true,
- "guids": [
- "04B05448-788F-433D-92C0-FED35122D45A",
- "AA8E58A1-CD09-4208-BF66-A8BB341FD669",
- "18D73F73-D7BE-4388-85BA-FBD9AC96FCA2"
- ],
"symbols": {
"Framework": {
"type": "parameter",
@@ -30,12 +25,12 @@
"datatype": "choice",
"choices": [
{
- "choice": "net9.0",
- "description": "Target net9.0"
+ "choice": "net10.0",
+ "description": "Target net10.0"
}
],
- "replaces": "net9.0",
- "defaultValue": "net9.0"
+ "replaces": "net10.0",
+ "defaultValue": "net10.0"
},
"HttpPort": {
"type": "parameter",
@@ -80,7 +75,7 @@
},
"primaryOutputs": [
{
- "path": "Oqtane.Application.sln"
+ "path": "Oqtane.Application.slnx"
}
]
}
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Modules/MyModule/ModuleInfo.cs b/Oqtane.Application/Client/Modules/MyModule/ModuleInfo.cs
deleted file mode 100644
index 4b87c85f..00000000
--- a/Oqtane.Application/Client/Modules/MyModule/ModuleInfo.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using Oqtane.Models;
-using Oqtane.Modules;
-
-namespace Oqtane.Application.MyModule
-{
- public class ModuleInfo : IModule
- {
- public ModuleDefinition ModuleDefinition => new ModuleDefinition
- {
- Name = "MyModule",
- Description = "Example module",
- Version = "1.0.0",
- ServerManagerType = "Oqtane.Application.Manager.MyModuleManager, Oqtane.Application.Server.Oqtane",
- ReleaseVersions = "1.0.0",
- Dependencies = "Oqtane.Application.Shared.Oqtane",
- PackageName = "Oqtane.Application"
- };
- }
-}
diff --git a/Oqtane.Application/Client/Oqtane.Application.Client.csproj b/Oqtane.Application/Client/Oqtane.Application.Client.csproj
index c7bd2e5c..fdbfd650 100644
--- a/Oqtane.Application/Client/Oqtane.Application.Client.csproj
+++ b/Oqtane.Application/Client/Oqtane.Application.Client.csproj
@@ -1,22 +1,21 @@
- net9.0
- Exe
+ net10.0
1.0.0
Oqtane.Application.Client.Oqtane
Default
true
false
false
+ true
-
-
-
-
-
+
+
+
+
@@ -24,7 +23,7 @@
-
+
diff --git a/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Edit.resx b/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Edit.resx
deleted file mode 100644
index 168fb569..00000000
--- a/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Edit.resx
+++ /dev/null
@@ -1,141 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- Name:
-
-
- Enter the name
-
-
- Save
-
-
- Cancel
-
-
- Please Provide All Required Information
-
-
- Error Saving MyModule
-
-
- Error Loading MyModule
-
-
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Index.resx b/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Index.resx
deleted file mode 100644
index 89fab54a..00000000
--- a/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Index.resx
+++ /dev/null
@@ -1,147 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- Name
-
-
- Add MyModule
-
-
- Edit
-
-
- Delete
-
-
- Delete MyModule
-
-
- Are You Sure You Wish To Delete This MyModule?
-
-
- No MyModules To Display
-
-
- Error Loading MyModule
-
-
- Error Deleting MyModule
-
-
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Services/MyModuleService.cs b/Oqtane.Application/Client/Services/MyModuleService.cs
deleted file mode 100644
index 3caf5bba..00000000
--- a/Oqtane.Application/Client/Services/MyModuleService.cs
+++ /dev/null
@@ -1,55 +0,0 @@
-using System.Collections.Generic;
-using System.Linq;
-using System.Net.Http;
-using System.Threading.Tasks;
-using Oqtane.Services;
-using Oqtane.Shared;
-
-namespace Oqtane.Application.Services
-{
- public interface IMyModuleService
- {
- Task> GetMyModulesAsync(int ModuleId);
-
- Task GetMyModuleAsync(int MyModuleId, int ModuleId);
-
- Task AddMyModuleAsync(Models.MyModule MyModule);
-
- Task UpdateMyModuleAsync(Models.MyModule MyModule);
-
- Task DeleteMyModuleAsync(int MyModuleId, int ModuleId);
- }
-
- public class MyModuleService : ServiceBase, IMyModuleService
- {
- public MyModuleService(HttpClient http, SiteState siteState) : base(http, siteState) { }
-
- private string Apiurl => CreateApiUrl("MyModule");
-
- public async Task> GetMyModulesAsync(int ModuleId)
- {
- List Tasks = await GetJsonAsync>(CreateAuthorizationPolicyUrl($"{Apiurl}?moduleid={ModuleId}", EntityNames.Module, ModuleId), Enumerable.Empty().ToList());
- return Tasks.OrderBy(item => item.Name).ToList();
- }
-
- public async Task GetMyModuleAsync(int MyModuleId, int ModuleId)
- {
- return await GetJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
- }
-
- public async Task AddMyModuleAsync(Models.MyModule MyModule)
- {
- return await PostJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}", EntityNames.Module, MyModule.ModuleId), MyModule);
- }
-
- public async Task UpdateMyModuleAsync(Models.MyModule MyModule)
- {
- return await PutJsonAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModule.MyModuleId}", EntityNames.Module, MyModule.ModuleId), MyModule);
- }
-
- public async Task DeleteMyModuleAsync(int MyModuleId, int ModuleId)
- {
- await DeleteAsync(CreateAuthorizationPolicyUrl($"{Apiurl}/{MyModuleId}/{ModuleId}", EntityNames.Module, ModuleId));
- }
- }
-}
diff --git a/Oqtane.Application/Oqtane.Application.Template.nuspec b/Oqtane.Application/Oqtane.Application.Template.nuspec
index 47d1af9d..e7097801 100644
--- a/Oqtane.Application/Oqtane.Application.Template.nuspec
+++ b/Oqtane.Application/Oqtane.Application.Template.nuspec
@@ -2,7 +2,7 @@
Oqtane.Application.Template
- 6.2.1
+ 10.0.0
Oqtane Application Template For Blazor
Shaun Walker
false
diff --git a/Oqtane.Application/Oqtane.Application.sln b/Oqtane.Application/Oqtane.Application.sln
deleted file mode 100644
index ea06ef57..00000000
--- a/Oqtane.Application/Oqtane.Application.sln
+++ /dev/null
@@ -1,33 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.12.35506.116 d17.12
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Server", "Server\Oqtane.Application.Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Client", "Client\Oqtane.Application.Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Application.Shared", "Shared\Oqtane.Application.Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = Release|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/Oqtane.Application/Oqtane.Application.slnx b/Oqtane.Application/Oqtane.Application.slnx
new file mode 100644
index 00000000..fb193c28
--- /dev/null
+++ b/Oqtane.Application/Oqtane.Application.slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Oqtane.Application/Server/Manager/MyModuleManager.cs b/Oqtane.Application/Server/Manager/MyModuleManager.cs
deleted file mode 100644
index f2d8278e..00000000
--- a/Oqtane.Application/Server/Manager/MyModuleManager.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.Json;
-using Oqtane.Modules;
-using Oqtane.Models;
-using Oqtane.Infrastructure;
-using Oqtane.Interfaces;
-using Oqtane.Enums;
-using Oqtane.Repository;
-using Oqtane.Application.Repository;
-using System.Threading.Tasks;
-
-namespace Oqtane.Application.Manager
-{
- public class MyModuleManager : MigratableModuleBase, IInstallable, IPortable, ISearchable
- {
- private readonly IMyModuleRepository _MyModuleRepository;
- private readonly IDBContextDependencies _DBContextDependencies;
-
- public MyModuleManager(IMyModuleRepository MyModuleRepository, IDBContextDependencies DBContextDependencies)
- {
- _MyModuleRepository = MyModuleRepository;
- _DBContextDependencies = DBContextDependencies;
- }
-
- public bool Install(Tenant tenant, string version)
- {
- return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Up);
- }
-
- public bool Uninstall(Tenant tenant)
- {
- return Migrate(new Context(_DBContextDependencies), tenant, MigrationType.Down);
- }
-
- public string ExportModule(Module module)
- {
- string content = "";
- List MyModules = _MyModuleRepository.GetMyModules(module.ModuleId).ToList();
- if (MyModules != null)
- {
- content = JsonSerializer.Serialize(MyModules);
- }
- return content;
- }
-
- public void ImportModule(Module module, string content, string version)
- {
- List MyModules = null;
- if (!string.IsNullOrEmpty(content))
- {
- MyModules = JsonSerializer.Deserialize>(content);
- }
- if (MyModules != null)
- {
- foreach(var Task in MyModules)
- {
- _MyModuleRepository.AddMyModule(new Models.MyModule { ModuleId = module.ModuleId, Name = Task.Name });
- }
- }
- }
-
- public Task> GetSearchContentsAsync(PageModule pageModule, DateTime lastIndexedOn)
- {
- var searchContentList = new List();
-
- foreach (var MyModule in _MyModuleRepository.GetMyModules(pageModule.ModuleId))
- {
- if (MyModule.ModifiedOn >= lastIndexedOn)
- {
- searchContentList.Add(new SearchContent
- {
- EntityName = "MyModule",
- EntityId = MyModule.MyModuleId.ToString(),
- Title = MyModule.Name,
- Body = MyModule.Name,
- ContentModifiedBy = MyModule.ModifiedBy,
- ContentModifiedOn = MyModule.ModifiedOn
- });
- }
- }
-
- return Task.FromResult(searchContentList);
- }
- }
-}
diff --git a/Oqtane.Application/Server/Migrations/01000000_InitializeModule.cs b/Oqtane.Application/Server/Migrations/01000000_InitializeModule.cs
deleted file mode 100644
index 67bf16dd..00000000
--- a/Oqtane.Application/Server/Migrations/01000000_InitializeModule.cs
+++ /dev/null
@@ -1,30 +0,0 @@
-using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Migrations;
-using Oqtane.Databases.Interfaces;
-using Oqtane.Migrations;
-using Oqtane.Application.Migrations.EntityBuilders;
-using Oqtane.Application.Repository;
-
-namespace Oqtane.Application.Migrations
-{
- [DbContext(typeof(Context))]
- [Migration("Oqtane.Application.01.00.00.00")]
- public class InitializeModule : MultiDatabaseMigration
- {
- public InitializeModule(IDatabase database) : base(database)
- {
- }
-
- protected override void Up(MigrationBuilder migrationBuilder)
- {
- var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
- myModuleEntityBuilder.Create();
- }
-
- protected override void Down(MigrationBuilder migrationBuilder)
- {
- var myModuleEntityBuilder = new MyModuleEntityBuilder(migrationBuilder, ActiveDatabase);
- myModuleEntityBuilder.Drop();
- }
- }
-}
diff --git a/Oqtane.Application/Server/Oqtane.Application.Server.csproj b/Oqtane.Application/Server/Oqtane.Application.Server.csproj
index aaf443fa..4d6ad43b 100644
--- a/Oqtane.Application/Server/Oqtane.Application.Server.csproj
+++ b/Oqtane.Application/Server/Oqtane.Application.Server.csproj
@@ -1,20 +1,30 @@
- net9.0
+ net10.0
1.0.0
Oqtane.Application.Server.Oqtane
true
none
false
false
+ true
+ true
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -23,7 +33,7 @@
-
+
diff --git a/Oqtane.Application/Server/Program.cs b/Oqtane.Application/Server/Program.cs
index 55a18e50..a4eaab1b 100644
--- a/Oqtane.Application/Server/Program.cs
+++ b/Oqtane.Application/Server/Program.cs
@@ -1,9 +1,13 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.AspNetCore;
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.Logging;
-using Oqtane.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Logging;
+using Oqtane.Extensions;
+using Oqtane.Infrastructure;
+using Oqtane.Shared;
namespace Oqtane.Application.Server
{
@@ -11,32 +15,41 @@ namespace Oqtane.Application.Server
{
public static void Main(string[] args)
{
- // defer server startup to Oqtane - do not modify
- var host = BuildWebHost(args);
- var databaseManager = host.Services.GetService();
+ var builder = WebApplication.CreateBuilder(args);
+
+ AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(builder.Environment.ContentRootPath, "Data"));
+
+ var configurationBuilder = new ConfigurationBuilder()
+ .SetBasePath(builder.Environment.ContentRootPath)
+ .AddJsonFile("appsettings.json", false, true)
+ .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
+ .AddEnvironmentVariables();
+ var configuration = configurationBuilder.Build();
+
+ builder.Services.AddOqtane(configuration, builder.Environment);
+
+ var app = builder.Build();
+
+ var corsService = app.Services.GetRequiredService();
+ var corsPolicyProvider = app.Services.GetRequiredService();
+ var syncManager = app.Services.GetRequiredService();
+
+ app.UseOqtane(configuration, builder.Environment, corsService, corsPolicyProvider, syncManager);
+
+ var databaseManager = app.Services.GetService();
var install = databaseManager.Install();
if (!string.IsNullOrEmpty(install.Message))
{
- var filelogger = host.Services.GetRequiredService>();
+ var filelogger = app.Services.GetRequiredService>();
if (filelogger != null)
{
- filelogger.LogError($"[Oqtane.Application.Server.Program.Main] {install.Message}");
+ filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
}
}
else
{
- host.Run();
+ app.Run();
}
}
-
- public static IWebHost BuildWebHost(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseConfiguration(new ConfigurationBuilder()
- .AddCommandLine(args)
- .AddEnvironmentVariables()
- .Build())
- .UseStartup()
- .ConfigureLocalizationSettings()
- .Build();
}
}
diff --git a/Oqtane.Application/Server/Repository/Context.cs b/Oqtane.Application/Server/Repository/Context.cs
deleted file mode 100644
index adbcfe95..00000000
--- a/Oqtane.Application/Server/Repository/Context.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using Oqtane.Modules;
-using Oqtane.Repository;
-using Oqtane.Repository.Databases.Interfaces;
-
-namespace Oqtane.Application.Repository
-{
- public class Context : DBContextBase, ITransientService, IMultiDatabase
- {
- public virtual DbSet MyModule { get; set; }
-
- public Context(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies)
- {
- // ContextBase handles multi-tenant database connections
- }
-
- protected override void OnModelCreating(ModelBuilder builder)
- {
- base.OnModelCreating(builder);
-
- builder.Entity().ToTable(ActiveDatabase.RewriteName("MyModule"));
- }
- }
-}
diff --git a/Oqtane.Application/Server/Repository/MyModuleRepository.cs b/Oqtane.Application/Server/Repository/MyModuleRepository.cs
deleted file mode 100644
index 0912fad3..00000000
--- a/Oqtane.Application/Server/Repository/MyModuleRepository.cs
+++ /dev/null
@@ -1,75 +0,0 @@
-using Microsoft.EntityFrameworkCore;
-using System.Linq;
-using System.Collections.Generic;
-using Oqtane.Modules;
-
-namespace Oqtane.Application.Repository
-{
- public interface IMyModuleRepository
- {
- IEnumerable GetMyModules(int ModuleId);
- Models.MyModule GetMyModule(int MyModuleId);
- Models.MyModule GetMyModule(int MyModuleId, bool tracking);
- Models.MyModule AddMyModule(Models.MyModule MyModule);
- Models.MyModule UpdateMyModule(Models.MyModule MyModule);
- void DeleteMyModule(int MyModuleId);
- }
-
- public class MyModuleRepository : IMyModuleRepository, ITransientService
- {
- private readonly IDbContextFactory _factory;
-
- public MyModuleRepository(IDbContextFactory factory)
- {
- _factory = factory;
- }
-
- public IEnumerable GetMyModules(int ModuleId)
- {
- using var db = _factory.CreateDbContext();
- return db.MyModule.Where(item => item.ModuleId == ModuleId).ToList();
- }
-
- public Models.MyModule GetMyModule(int MyModuleId)
- {
- return GetMyModule(MyModuleId, true);
- }
-
- public Models.MyModule GetMyModule(int MyModuleId, bool tracking)
- {
- using var db = _factory.CreateDbContext();
- if (tracking)
- {
- return db.MyModule.Find(MyModuleId);
- }
- else
- {
- return db.MyModule.AsNoTracking().FirstOrDefault(item => item.MyModuleId == MyModuleId);
- }
- }
-
- public Models.MyModule AddMyModule(Models.MyModule MyModule)
- {
- using var db = _factory.CreateDbContext();
- db.MyModule.Add(MyModule);
- db.SaveChanges();
- return MyModule;
- }
-
- public Models.MyModule UpdateMyModule(Models.MyModule MyModule)
- {
- using var db = _factory.CreateDbContext();
- db.Entry(MyModule).State = EntityState.Modified;
- db.SaveChanges();
- return MyModule;
- }
-
- public void DeleteMyModule(int MyModuleId)
- {
- using var db = _factory.CreateDbContext();
- Models.MyModule MyModule = db.MyModule.Find(MyModuleId);
- db.MyModule.Remove(MyModule);
- db.SaveChanges();
- }
- }
-}
diff --git a/Oqtane.Application/Server/Startup.cs b/Oqtane.Application/Server/Startup.cs
deleted file mode 100644
index 71f5bd19..00000000
--- a/Oqtane.Application/Server/Startup.cs
+++ /dev/null
@@ -1,45 +0,0 @@
-using System;
-using System.IO;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Oqtane.Extensions;
-using Oqtane.Infrastructure;
-using Oqtane.Shared;
-using Microsoft.AspNetCore.Cors.Infrastructure;
-
-namespace Oqtane.Application.Server
-{
- public class Startup
- {
- private readonly IConfigurationRoot _configuration;
- private readonly IWebHostEnvironment _environment;
-
- public Startup(IWebHostEnvironment environment)
- {
- AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data"));
-
- var builder = new ConfigurationBuilder()
- .SetBasePath(environment.ContentRootPath)
- .AddJsonFile("appsettings.json", false, true)
- .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
- .AddEnvironmentVariables();
-
- _configuration = builder.Build();
- _environment = environment;
- }
-
- public void ConfigureServices(IServiceCollection services)
- {
- // defer server startup to Oqtane - do not modify
- services.AddOqtane(_configuration, _environment);
- }
-
- public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync)
- {
- // defer server startup to Oqtane - do not modify
- app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync);
- }
- }
-}
diff --git a/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Application.MyModule/Module.js b/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Application.MyModule/Module.js
deleted file mode 100644
index ec30da43..00000000
--- a/Oqtane.Application/Server/wwwroot/Modules/Oqtane.Application.MyModule/Module.js
+++ /dev/null
@@ -1,5 +0,0 @@
-/* Module Script */
-var App = App || {};
-
-App.MyModule = {
-};
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Modules/MyModule/Edit.razor b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Edit.razor
similarity index 61%
rename from Oqtane.Application/Client/Modules/MyModule/Edit.razor
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Edit.razor
index 3e9caa2a..59badca4 100644
--- a/Oqtane.Application/Client/Modules/MyModule/Edit.razor
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Edit.razor
@@ -1,10 +1,10 @@
@using Oqtane.Modules.Controls
-@using Oqtane.Application.Services
-@using Oqtane.Application.Models
+@using [Owner].Module.[Module].Services
+@using [Owner].Module.[Module].Models
-@namespace Oqtane.Application.MyModule
+@namespace [Owner].Module.[Module]
@inherits ModuleBase
-@inject IMyModuleService MyModuleService
+@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
@inject IStringLocalizer Localizer
@@ -31,12 +31,7 @@
public override string Actions => "Add,Edit";
- public override string Title => "Manage MyModule";
-
- public override List Resources => new List()
- {
- new Stylesheet(ModulePath() + "Module.css")
- };
+ public override string Title => "Manage [Module]";
private ElementReference form;
private bool validated = false;
@@ -55,20 +50,20 @@
if (PageState.Action == "Edit")
{
_id = Int32.Parse(PageState.QueryString["id"]);
- MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
- if (MyModule != null)
+ [Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
+ if ([Module] != null)
{
- _name = MyModule.Name;
- _createdby = MyModule.CreatedBy;
- _createdon = MyModule.CreatedOn;
- _modifiedby = MyModule.ModifiedBy;
- _modifiedon = MyModule.ModifiedOn;
+ _name = [Module].Name;
+ _createdby = [Module].CreatedBy;
+ _createdon = [Module].CreatedOn;
+ _modifiedby = [Module].ModifiedBy;
+ _modifiedon = [Module].ModifiedOn;
}
}
}
catch (Exception ex)
{
- await logger.LogError(ex, "Error Loading MyModule {MyModuleId} {Error}", _id, ex.Message);
+ await logger.LogError(ex, "Error Loading [Module] {[Module]Id} {Error}", _id, ex.Message);
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
}
@@ -83,18 +78,18 @@
{
if (PageState.Action == "Add")
{
- MyModule MyModule = new MyModule();
- MyModule.ModuleId = ModuleState.ModuleId;
- MyModule.Name = _name;
- MyModule = await MyModuleService.AddMyModuleAsync(MyModule);
- await logger.LogInformation("MyModule Added {MyModule}", MyModule);
+ [Module] [Module] = new [Module]();
+ [Module].ModuleId = ModuleState.ModuleId;
+ [Module].Name = _name;
+ [Module] = await [Module]Service.Add[Module]Async([Module]);
+ await logger.LogInformation("[Module] Added {[Module]}", [Module]);
}
else
{
- MyModule MyModule = await MyModuleService.GetMyModuleAsync(_id, ModuleState.ModuleId);
- MyModule.Name = _name;
- await MyModuleService.UpdateMyModuleAsync(MyModule);
- await logger.LogInformation("MyModule Updated {MyModule}", MyModule);
+ [Module] [Module] = await [Module]Service.Get[Module]Async(_id, ModuleState.ModuleId);
+ [Module].Name = _name;
+ await [Module]Service.Update[Module]Async([Module]);
+ await logger.LogInformation("[Module] Updated {[Module]}", [Module]);
}
NavigationManager.NavigateTo(NavigateUrl());
}
@@ -105,7 +100,7 @@
}
catch (Exception ex)
{
- await logger.LogError(ex, "Error Saving MyModule {Error}", ex.Message);
+ await logger.LogError(ex, "Error Saving [Module] {Error}", ex.Message);
AddModuleMessage(Localizer["Message.SaveError"], MessageType.Error);
}
}
diff --git a/Oqtane.Application/Client/Modules/MyModule/Index.razor b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Index.razor
similarity index 56%
rename from Oqtane.Application/Client/Modules/MyModule/Index.razor
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Index.razor
index fc3744d5..af8a4839 100644
--- a/Oqtane.Application/Client/Modules/MyModule/Index.razor
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Index.razor
@@ -1,32 +1,32 @@
-@using Oqtane.Application.Services
-@using Oqtane.Application.Models
+@using [Owner].Module.[Module].Services
+@using [Owner].Module.[Module].Models
-@namespace Oqtane.Application.MyModule
+@namespace [Owner].Module.[Module]
@inherits ModuleBase
-@inject IMyModuleService MyModuleService
+@inject I[Module]Service [Module]Service
@inject NavigationManager NavigationManager
@inject IStringLocalizer Localizer
-@if (_MyModules == null)
+@if (_[Module]s == null)
{
Loading...
}
else
{
-
+
- @if (@_MyModules.Count != 0)
+ @if (@_[Module]s.Count != 0)
{
-
+
- |
- |
+ |
+ |
@context.Name |
@@ -38,39 +38,41 @@ else
}
@code {
+ public override string RenderMode => RenderModes.Static;
+
public override List Resources => new List()
{
new Stylesheet(ModulePath() + "Module.css"),
new Script(ModulePath() + "Module.js")
};
- List _MyModules;
+ List<[Module]> _[Module]s;
protected override async Task OnInitializedAsync()
{
try
{
- _MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
+ _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
}
catch (Exception ex)
{
- await logger.LogError(ex, "Error Loading MyModule {Error}", ex.Message);
+ await logger.LogError(ex, "Error Loading [Module] {Error}", ex.Message);
AddModuleMessage(Localizer["Message.LoadError"], MessageType.Error);
}
}
- private async Task Delete(MyModule MyModule)
+ private async Task Delete([Module] [Module])
{
try
{
- await MyModuleService.DeleteMyModuleAsync(MyModule.MyModuleId, ModuleState.ModuleId);
- await logger.LogInformation("MyModule Deleted {MyModule}", MyModule);
- _MyModules = await MyModuleService.GetMyModulesAsync(ModuleState.ModuleId);
+ await [Module]Service.Delete[Module]Async([Module].[Module]Id, ModuleState.ModuleId);
+ await logger.LogInformation("[Module] Deleted {[Module]}", [Module]);
+ _[Module]s = await [Module]Service.Get[Module]sAsync(ModuleState.ModuleId);
StateHasChanged();
}
catch (Exception ex)
{
- await logger.LogError(ex, "Error Deleting MyModule {MyModule} {Error}", MyModule, ex.Message);
+ await logger.LogError(ex, "Error Deleting [Module] {[Module]} {Error}", [Module], ex.Message);
AddModuleMessage(Localizer["Message.DeleteError"], MessageType.Error);
}
}
diff --git a/Oqtane.Application/Client/Interop.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Interop.cs
similarity index 87%
rename from Oqtane.Application/Client/Interop.cs
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Interop.cs
index 5edf3aa5..bcb7d729 100644
--- a/Oqtane.Application/Client/Interop.cs
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Interop.cs
@@ -1,7 +1,7 @@
using Microsoft.JSInterop;
using System.Threading.Tasks;
-namespace Oqtane.Application
+namespace [Owner].Module.[Module]
{
public class Interop
{
diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/ModuleInfo.cs b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/ModuleInfo.cs
new file mode 100644
index 00000000..15c96b86
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/ModuleInfo.cs
@@ -0,0 +1,16 @@
+using Oqtane.Models;
+using Oqtane.Modules;
+
+namespace [Owner].Module.[Module]
+{
+ public class ModuleInfo : IModule
+ {
+ public ModuleDefinition ModuleDefinition => new ModuleDefinition
+ {
+ Name = "[Module]",
+ Description = "[Description]",
+ Version = "1.0.0",
+ ServerManagerType = "[ServerManagerType]"
+ };
+ }
+}
diff --git a/Oqtane.Application/Client/Modules/MyModule/Settings.razor b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Settings.razor
similarity index 74%
rename from Oqtane.Application/Client/Modules/MyModule/Settings.razor
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Settings.razor
index facaecae..624dee2a 100644
--- a/Oqtane.Application/Client/Modules/MyModule/Settings.razor
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Modules/[Owner].Module.[Module]/Settings.razor
@@ -1,4 +1,4 @@
-@namespace Oqtane.Application.MyModule
+@namespace [Owner].Module.[Module]
@inherits ModuleBase
@inject ISettingService SettingService
@inject IStringLocalizer Localizer
@@ -13,8 +13,8 @@
@code {
- private string resourceType = "Oqtane.Application.MyModule.Settings, Oqtane.Application.Client.Oqtane"; // for localization
- public override string Title => "MyModdule Settings";
+ private string resourceType = "[Owner].Module.[Module].Settings, [Owner].Module.[Module].Client.Oqtane"; // for localization
+ public override string Title => "[Module] Settings";
string _value;
@@ -35,8 +35,8 @@
{
try
{
- Dictionary settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
- SettingService.SetSetting(settings, "SettingName", _value);
+ var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
+ settings = SettingService.SetSetting(settings, "SettingName", _value);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Edit.resx b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Edit.resx
new file mode 100644
index 00000000..eebd66cd
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Edit.resx
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Name:
+
+
+ Enter the name
+
+
+ Save
+
+
+ Cancel
+
+
+ Error Loading [Module]
+
+
+ Please Provide All Required Information
+
+
+ Error Saving [Module]
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Index.resx b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Index.resx
new file mode 100644
index 00000000..721a853a
--- /dev/null
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Index.resx
@@ -0,0 +1,147 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Name
+
+
+ Add [Module]
+
+
+ Edit
+
+
+ Delete
+
+
+ Delete [Module]
+
+
+ Are You Sure You Wish To Delete This [Module]?
+
+
+ No [Module]s To Display
+
+
+ Error Loading [Module]
+
+
+ Error Deleting [Module]
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Settings.resx b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Settings.resx
similarity index 99%
rename from Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Settings.resx
rename to Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Settings.resx
index 83dc88f9..ba0390d8 100644
--- a/Oqtane.Application/Client/Resources/Oqtane.Application.MyModule/Settings.resx
+++ b/Oqtane.Application/Server/wwwroot/Modules/Templates/Internal/Client/Resources/[Owner].Module.[Module]/Settings.resx
@@ -1,4 +1,4 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Resources/Oqtane.Application.MyTheme/ContainerSettings.resx b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ContainerSettings.resx
similarity index 99%
rename from Oqtane.Application/Client/Resources/Oqtane.Application.MyTheme/ContainerSettings.resx
rename to Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ContainerSettings.resx
index aea7d1ba..9c3b2d80 100644
--- a/Oqtane.Application/Client/Resources/Oqtane.Application.MyTheme/ContainerSettings.resx
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ContainerSettings.resx
@@ -1,4 +1,4 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 1.3
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
\ No newline at end of file
diff --git a/Oqtane.Application/Client/Resources/Oqtane.Application.MyTheme/ThemeSettings.resx b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ThemeSettings.resx
similarity index 99%
rename from Oqtane.Application/Client/Resources/Oqtane.Application.MyTheme/ThemeSettings.resx
rename to Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ThemeSettings.resx
index fe0965fd..1359d4be 100644
--- a/Oqtane.Application/Client/Resources/Oqtane.Application.MyTheme/ThemeSettings.resx
+++ b/Oqtane.Application/Server/wwwroot/Themes/Templates/Internal/Client/Resources/[Owner].Theme.[Theme]/ThemeSettings.resx
@@ -1,4 +1,4 @@
-
+
-
-
+ $(TargetFrameworks);net10.0-windows10.0.19041.0
+
+
Exe
- Oqtane.Maui
+ Oqtane.Maui
true
true
enable
@@ -19,32 +18,31 @@
com.oqtane.maui
- 6.2.1
+ 10.0.0
1
-
- None
+
+ None
- 15.0
- 15.0
- 24.0
- 10.0.17763.0
- 10.0.17763.0
- 6.5
-
+ 15.0
+ 15.0
+ 24.0
+ 10.0.17763.0
+ 10.0.17763.0
+
-
+
-
+
-
+
-
+
@@ -55,24 +53,24 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
- ..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Client.dll
-
-
- ..\Oqtane.Server\bin\Debug\net9.0\Oqtane.Shared.dll
-
-
+
+
+ ..\Oqtane.Server\bin\Debug\net10.0\Oqtane.Client.dll
+
+
+ ..\Oqtane.Server\bin\Debug\net10.0\Oqtane.Shared.dll
+
+
diff --git a/Oqtane.Package/FixProps.exe b/Oqtane.Package/FixProps.exe
index 58d79183..e2c449ec 100644
Binary files a/Oqtane.Package/FixProps.exe and b/Oqtane.Package/FixProps.exe differ
diff --git a/Oqtane.Package/Oqtane.Client.nuspec b/Oqtane.Package/Oqtane.Client.nuspec
index e719802b..6fea68b2 100644
--- a/Oqtane.Package/Oqtane.Client.nuspec
+++ b/Oqtane.Package/Oqtane.Client.nuspec
@@ -2,7 +2,7 @@
Oqtane.Client
- 6.2.1
+ 10.0.0
Shaun Walker
.NET Foundation
Oqtane Framework
@@ -12,24 +12,24 @@
false
MIT
https://github.com/oqtane/oqtane.framework
- https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1
+ https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0
readme.md
icon.png
oqtane
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
+
+
diff --git a/Oqtane.Package/Oqtane.Framework.nuspec b/Oqtane.Package/Oqtane.Framework.nuspec
index c4d8b054..2a1dabff 100644
--- a/Oqtane.Package/Oqtane.Framework.nuspec
+++ b/Oqtane.Package/Oqtane.Framework.nuspec
@@ -2,7 +2,7 @@
Oqtane.Framework
- 6.2.1
+ 10.0.0
Shaun Walker
.NET Foundation
Oqtane Framework
@@ -11,8 +11,8 @@
.NET Foundation
false
MIT
- https://github.com/oqtane/oqtane.framework/releases/download/v6.2.1/Oqtane.Framework.6.2.1.Upgrade.zip
- https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1
+ https://github.com/oqtane/oqtane.framework/releases/download/v10.0.0/Oqtane.Framework.10.0.0.Upgrade.zip
+ https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0
readme.md
icon.png
oqtane framework
diff --git a/Oqtane.Package/Oqtane.Server.nuspec b/Oqtane.Package/Oqtane.Server.nuspec
index 7a436eae..789f9215 100644
--- a/Oqtane.Package/Oqtane.Server.nuspec
+++ b/Oqtane.Package/Oqtane.Server.nuspec
@@ -2,7 +2,7 @@
Oqtane.Server
- 6.2.1
+ 10.0.0
Shaun Walker
.NET Foundation
Oqtane Framework
@@ -12,49 +12,45 @@
false
MIT
https://github.com/oqtane/oqtane.framework
- https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1
+ https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0
readme.md
icon.png
oqtane
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/Oqtane.Package/Oqtane.Shared.nuspec b/Oqtane.Package/Oqtane.Shared.nuspec
index cb148f19..9e68e8ca 100644
--- a/Oqtane.Package/Oqtane.Shared.nuspec
+++ b/Oqtane.Package/Oqtane.Shared.nuspec
@@ -2,7 +2,7 @@
Oqtane.Shared
- 6.2.1
+ 10.0.0
Shaun Walker
.NET Foundation
Oqtane Framework
@@ -12,24 +12,21 @@
false
MIT
https://github.com/oqtane/oqtane.framework
- https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1
+ https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0
readme.md
icon.png
oqtane
-
-
-
-
+
+
+
-
-
-
-
+
+
diff --git a/Oqtane.Package/Oqtane.Updater.nuspec b/Oqtane.Package/Oqtane.Updater.nuspec
index 0acd762e..597f5617 100644
--- a/Oqtane.Package/Oqtane.Updater.nuspec
+++ b/Oqtane.Package/Oqtane.Updater.nuspec
@@ -2,7 +2,7 @@
Oqtane.Updater
- 6.2.1
+ 10.0.0
Shaun Walker
.NET Foundation
Oqtane Framework
@@ -12,13 +12,13 @@
false
MIT
https://github.com/oqtane/oqtane.framework
- https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1
+ https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0
readme.md
icon.png
oqtane
-
+
diff --git a/Oqtane.Package/install.ps1 b/Oqtane.Package/install.ps1
index 70f1001b..a59e4412 100644
--- a/Oqtane.Package/install.ps1
+++ b/Oqtane.Package/install.ps1
@@ -1 +1 @@
-Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.1.Install.zip" -Force
+Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.0.Install.zip" -Force
diff --git a/Oqtane.Package/release.cmd b/Oqtane.Package/release.cmd
index f32f8178..cc12b123 100644
--- a/Oqtane.Package/release.cmd
+++ b/Oqtane.Package/release.cmd
@@ -1,29 +1,30 @@
-dotnet build -c Release ..\Oqtane.sln
+dotnet build -c Release ..\Oqtane.slnx
+FixProps.exe
nuget.exe pack Oqtane.Client.nuspec
nuget.exe pack Oqtane.Server.nuspec
nuget.exe pack Oqtane.Shared.nuspec
nuget.exe pack Oqtane.Framework.nuspec
dotnet publish ..\Oqtane.Server\Oqtane.Server.csproj /p:Configuration=Release
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\Content"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Content"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-arm64"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x64"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\android-x86"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\ios-arm64"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-arm64"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x64"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\runtimes\iossimulator-x86"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Modules\Templates"
-rmdir /Q /S "..\Oqtane.Server\bin\Release\net9.0\publish\wwwroot\Themes\Templates"
-del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
-ren "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.release.json" "appsettings.json"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\Content"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\wwwroot\Content"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-arm"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-arm64"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-x64"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\android-x86"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\ios-arm"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\ios-arm64"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\iossimulator-arm64"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\iossimulator-x64"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\runtimes\iossimulator-x86"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\wwwroot\Modules\Templates"
+rmdir /Q /S "..\Oqtane.Server\bin\Release\net10.0\publish\wwwroot\Themes\Templates"
+del "..\Oqtane.Server\bin\Release\net10.0\publish\appsettings.json"
+ren "..\Oqtane.Server\bin\Release\net10.0\publish\appsettings.release.json" "appsettings.json"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\install.ps1"
-del "..\Oqtane.Server\bin\Release\net9.0\publish\appsettings.json"
-del "..\Oqtane.Server\bin\Release\net9.0\publish\web.config"
+del "..\Oqtane.Server\bin\Release\net10.0\publish\appsettings.json"
+del "..\Oqtane.Server\bin\Release\net10.0\publish\web.config"
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe ".\upgrade.ps1"
-dotnet build -c Release ..\Oqtane.Updater.sln
+dotnet build -c Release ..\Oqtane.Updater.slnx
dotnet publish ..\Oqtane.Updater\Oqtane.Updater.csproj /p:Configuration=Release
nuget.exe pack Oqtane.Updater.nuspec
nuget.exe pack ..\Oqtane.Application\Oqtane.Application.Template.nuspec -NoDefaultExcludes
diff --git a/Oqtane.Package/upgrade.ps1 b/Oqtane.Package/upgrade.ps1
index cc87a261..5979f4d7 100644
--- a/Oqtane.Package/upgrade.ps1
+++ b/Oqtane.Package/upgrade.ps1
@@ -1 +1 @@
-Compress-Archive -Path "..\Oqtane.Server\bin\Release\net9.0\publish\*" -DestinationPath "Oqtane.Framework.6.2.1.Upgrade.zip" -Force
+Compress-Archive -Path "..\Oqtane.Server\bin\Release\net10.0\publish\*" -DestinationPath "Oqtane.Framework.10.0.0.Upgrade.zip" -Force
diff --git a/Oqtane.Server/Components/App.razor b/Oqtane.Server/Components/App.razor
index a4e1f4ab..7382976b 100644
--- a/Oqtane.Server/Components/App.razor
+++ b/Oqtane.Server/Components/App.razor
@@ -197,10 +197,6 @@
{
_scripts += CreatePWAScript(alias, site, route);
}
- @if (_renderMode == RenderModes.Static)
- {
- _scripts += CreateScrollPositionScript();
- }
// set culture if not specified
string cultureCookie = Context.Request.Cookies[Shared.CookieRequestCultureProvider.DefaultCookieName];
@@ -510,25 +506,6 @@
"" + Environment.NewLine;
}
- private string CreateScrollPositionScript()
- {
- return Environment.NewLine +
- "" + Environment.NewLine;
- }
-
private void AddScript(Resource resource, Alias alias)
{
var script = CreateScript(resource, alias);
diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
index 486af9fc..139da8e2 100644
--- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs
+++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs
@@ -1,20 +1,20 @@
+using System;
using System.Collections.Generic;
-using Microsoft.AspNetCore.Mvc;
-using Oqtane.Models;
-using Oqtane.Shared;
-using Microsoft.AspNetCore.Authorization;
using System.IO;
-using System.Reflection;
using System.Linq;
+using System.Net;
+using System.Reflection;
+using System.Text.Json;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.DependencyInjection;
using Oqtane.Enums;
using Oqtane.Infrastructure;
+using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Security;
-using System;
-using Microsoft.Extensions.DependencyInjection;
-using System.Text.Json;
-using System.Net;
+using Oqtane.Shared;
namespace Oqtane.Controllers
{
@@ -132,8 +132,8 @@ namespace Oqtane.Controllers
if (moduleDefinition.Template.ToLower().Contains("internal"))
{
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
- moduleDefinition.ServerManagerType = moduleDefinition.ModuleDefinitionName + ".Manager." + moduleDefinition.Name + "Manager, Oqtane.Server";
- moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", Oqtane.Client";
+ moduleDefinition.ServerManagerType = moduleDefinition.ModuleDefinitionName + ".Manager." + moduleDefinition.Name + "Manager, " + moduleDefinition.Owner + ".Server.Oqtane";
+ moduleDefinition.ModuleDefinitionName = moduleDefinition.ModuleDefinitionName + ", " + moduleDefinition.Owner + ".Client.Oqtane";
}
else
{
@@ -270,9 +270,10 @@ namespace Oqtane.Controllers
foreach (string directory in Directory.GetDirectories(templatePath))
{
string name = directory.Replace(templatePath, "");
- if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
+ var manifest = Directory.GetFiles(directory, "*.json");
+ if (manifest.Any())
{
- var template = JsonSerializer.Deserialize(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
+ var template = JsonSerializer.Deserialize(System.IO.File.ReadAllText(manifest[0]));
template.Name = name;
template.Location = "";
if (template.Type.ToLower() != "internal")
@@ -350,9 +351,9 @@ namespace Oqtane.Controllers
return new Dictionary()
{
{ "FrameworkVersion", Constants.Version },
- { "ClientReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll" },
- { "ServerReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Server.dll" },
- { "SharedReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll" },
+ { "ClientReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Client.dll" },
+ { "ServerReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Server.dll" },
+ { "SharedReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Shared.dll" },
};
});
}
diff --git a/Oqtane.Server/Controllers/PageController.cs b/Oqtane.Server/Controllers/PageController.cs
index 6086bd0f..cb7b8ee5 100644
--- a/Oqtane.Server/Controllers/PageController.cs
+++ b/Oqtane.Server/Controllers/PageController.cs
@@ -216,6 +216,13 @@ namespace Oqtane.Controllers
page.UserId = int.Parse(userid);
page = _pages.AddPage(page);
+ // copy parent page settings
+ var settings = _settings.GetSettings(EntityNames.Page, parent.PageId);
+ foreach (var setting in settings)
+ {
+ _settings.AddSetting(new Setting { EntityName = EntityNames.Page, EntityId = page.PageId, SettingName = setting.SettingName, SettingValue = setting.SettingValue, IsPrivate = setting.IsPrivate });
+ }
+
// copy modules
List pagemodules = _pageModules.GetPageModules(page.SiteId).ToList();
foreach (PageModule pm in pagemodules.Where(item => item.PageId == parent.PageId && !item.IsDeleted))
@@ -258,8 +265,7 @@ namespace Oqtane.Controllers
_syncManager.AddSyncEvent(_alias, EntityNames.Site, page.SiteId, SyncEventActions.Refresh);
// set user personalized page path
- var setting = new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false };
- _settings.AddSetting(setting);
+ _settings.AddSetting(new Setting { EntityName = EntityNames.User, EntityId = page.UserId.Value, SettingName = $"PersonalizedPagePath:{page.SiteId}:{parent.PageId}", SettingValue = path, IsPrivate = false });
_syncManager.AddSyncEvent(_alias, EntityNames.User, user.UserId, SyncEventActions.Update);
}
}
diff --git a/Oqtane.Server/Controllers/SettingController.cs b/Oqtane.Server/Controllers/SettingController.cs
index 041c70cf..0a2eb0f5 100644
--- a/Oqtane.Server/Controllers/SettingController.cs
+++ b/Oqtane.Server/Controllers/SettingController.cs
@@ -16,8 +16,6 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Options;
using System.IO;
using System.Text.RegularExpressions;
-using Oqtane.Migrations.Tenant;
-using Google.Protobuf.WellKnownTypes;
using System;
namespace Oqtane.Controllers
@@ -220,39 +218,25 @@ namespace Oqtane.Controllers
var existingSettings = _settings.GetSettings(entityName, entityId).ToList();
foreach (Setting setting in settings)
{
- bool modified = false;
-
- // manage settings modified with SetSetting method
- if (setting.SettingValue.StartsWith("[Private]"))
+ Setting existing = existingSettings.FirstOrDefault(item => item.SettingName.Equals(setting.SettingName, StringComparison.OrdinalIgnoreCase));
+ if (existing == null)
{
- modified = true;
- setting.IsPrivate = true;
- setting.SettingValue = setting.SettingValue.Substring(9);
- }
- if (setting.SettingValue.StartsWith("[Public]"))
- {
- modified = true;
- setting.IsPrivate = false;
- setting.SettingValue = setting.SettingValue.Substring(8);
- }
-
- Setting existingSetting = existingSettings.FirstOrDefault(item => item.SettingName.Equals(setting.SettingName, StringComparison.OrdinalIgnoreCase));
- if (existingSetting == null)
- {
- _settings.AddSetting(setting);
- AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
- _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", setting);
+ setting.SettingId = 0; // initialize
+ existing = _settings.AddSetting(setting);
+ AddSyncEvent(existing.EntityName, existing.EntityId, existing.SettingId, SyncEventActions.Create);
+ _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Created {Setting}", existing);
}
else
{
- if (existingSetting.SettingValue != setting.SettingValue || (modified && existingSetting.IsPrivate != setting.IsPrivate))
+ // note that setting.SettingId will be 0 or -1 if the Settings were converted from a Dictionary in the SettingService
+ if (existing.SettingValue != setting.SettingValue || (existing.IsPrivate != setting.IsPrivate && setting.SettingId != 0))
{
- existingSetting.SettingValue = setting.SettingValue;
- existingSetting.IsPrivate = setting.IsPrivate;
- _settings.UpdateSetting(existingSetting);
- AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
- _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", setting);
+ existing.SettingValue = setting.SettingValue;
+ existing.IsPrivate = setting.IsPrivate;
+ existing = _settings.UpdateSetting(existing);
+ AddSyncEvent(existing.EntityName, existing.EntityId, existing.SettingId, SyncEventActions.Update);
+ _logger.Log(LogLevel.Information, this, LogFunction.Update, "Setting Updated {Setting}", existing);
}
}
}
@@ -366,13 +350,16 @@ namespace Oqtane.Controllers
var setting = _settings.GetSetting(cols[0], entityId, cols[2]);
if (setting == null)
{
- _settings.AddSetting(new Setting { EntityName = cols[0], EntityId = entityId, SettingName = cols[2], SettingValue = cols[3], IsPrivate = isPrivate });
+ setting = new Setting { EntityName = cols[0], EntityId = entityId, SettingName = cols[2], SettingValue = cols[3], IsPrivate = isPrivate };
+ _settings.AddSetting(setting);
+ AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Create);
}
else
{
setting.SettingValue = cols[3];
setting.IsPrivate = isPrivate;
_settings.UpdateSetting(setting);
+ AddSyncEvent(setting.EntityName, setting.EntityId, setting.SettingId, SyncEventActions.Update);
}
rows++;
}
diff --git a/Oqtane.Server/Controllers/ThemeController.cs b/Oqtane.Server/Controllers/ThemeController.cs
index eb250069..3f025b40 100644
--- a/Oqtane.Server/Controllers/ThemeController.cs
+++ b/Oqtane.Server/Controllers/ThemeController.cs
@@ -127,7 +127,7 @@ namespace Oqtane.Controllers
}
// DELETE api//5?siteid=x
- [HttpDelete("{themename}")]
+ [HttpDelete("{id}")]
[Authorize(Roles = RoleNames.Host)]
public void Delete(int id, int siteid)
{
@@ -183,9 +183,10 @@ namespace Oqtane.Controllers
foreach (string directory in Directory.GetDirectories(templatePath))
{
string name = directory.Replace(templatePath, "");
- if (System.IO.File.Exists(Path.Combine(directory, "template.json")))
+ var manifest = Directory.GetFiles(directory, "*.json");
+ if (manifest.Any())
{
- var template = JsonSerializer.Deserialize(System.IO.File.ReadAllText(Path.Combine(directory, "template.json")));
+ var template = JsonSerializer.Deserialize(System.IO.File.ReadAllText(manifest[0]));
template.Name = name;
template.Location = "";
if (template.Type.ToLower() != "internal")
@@ -226,7 +227,7 @@ namespace Oqtane.Controllers
if (theme.Template.ToLower().Contains("internal"))
{
rootPath = Utilities.PathCombine(rootFolder.FullName, Path.DirectorySeparatorChar.ToString());
- theme.ThemeName = theme.ThemeName + ", Oqtane.Client";
+ theme.ThemeName = theme.ThemeName + ", " + theme.Owner + ".Client.Oqtane";
}
else
{
@@ -304,8 +305,8 @@ namespace Oqtane.Controllers
return new Dictionary()
{
{ "FrameworkVersion", Constants.Version },
- { "ClientReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Client.dll" },
- { "SharedReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net9.0\\Oqtane.Shared.dll" },
+ { "ClientReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Client.dll" },
+ { "SharedReference", $"..\\..\\{rootFolder}\\Oqtane.Server\\bin\\Debug\\net10.0\\Oqtane.Shared.dll" },
};
});
}
diff --git a/Oqtane.Server/Controllers/UserController.cs b/Oqtane.Server/Controllers/UserController.cs
index 33a934f1..018e2695 100644
--- a/Oqtane.Server/Controllers/UserController.cs
+++ b/Oqtane.Server/Controllers/UserController.cs
@@ -1,19 +1,22 @@
-using Microsoft.AspNetCore.Mvc;
+using System.Buffers.Text;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Security.Claims;
+using System.Security.Policy;
+using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
-using Oqtane.Models;
-using System.Threading.Tasks;
-using System.Linq;
-using System.Security.Claims;
-using Oqtane.Shared;
-using System.Net;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
using Oqtane.Enums;
+using Oqtane.Extensions;
using Oqtane.Infrastructure;
+using Oqtane.Managers;
+using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Security;
-using Oqtane.Extensions;
-using Oqtane.Managers;
-using System.Collections.Generic;
+using Oqtane.Shared;
namespace Oqtane.Controllers
{
@@ -238,8 +241,8 @@ namespace Oqtane.Controllers
}
}
- // POST api//login
- [HttpPost("login")]
+ // POST api//signin
+ [HttpPost("signin")]
public async Task Login([FromBody] User user, bool setCookie, bool isPersistent)
{
if (ModelState.IsValid)
@@ -328,22 +331,6 @@ namespace Oqtane.Controllers
return user;
}
- // POST api//link
- [HttpPost("link")]
- public async Task Link([FromBody] User user, string token, string type, string key, string name)
- {
- if (ModelState.IsValid)
- {
- user = await _userManager.LinkExternalAccount(user, token, type, key, name);
- }
- else
- {
- _logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} And Token {Token}", user.Username, token);
- user = null;
- }
- return user;
- }
-
// GET api//validate/x
[HttpGet("validateuser")]
public async Task ValidateUser(string username, string email, string password)
@@ -463,5 +450,114 @@ namespace Oqtane.Controllers
return null;
}
}
+
+ // GET: api//passkey?id=x
+ [HttpGet("passkey")]
+ [Authorize]
+ public async Task> GetPasskeys(int id)
+ {
+ if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
+ {
+ return await _userManager.GetPasskeys(id, _tenantManager.GetAlias().SiteId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Get Attempt {UserId} {SiteId}", id, _tenantManager.GetAlias().SiteId);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ return null;
+ }
+ }
+
+ // PUT api//passkey
+ [HttpPut("passkey")]
+ [Authorize]
+ public async Task UpdatePasskey([FromBody] UserPasskey passkey)
+ {
+ if (ModelState.IsValid)
+ {
+ if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == passkey.UserId)
+ {
+ // passkey name is prefixed with SiteId for multi-tenancy
+ passkey.Name = $"{_tenantManager.GetAlias().SiteId}:" + passkey.Name;
+ await _userManager.UpdatePasskey(passkey);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Put Attempt {PassKey}", passkey);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Put Attempt {PassKey}", passkey);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ }
+ }
+
+ // DELETE api//passkey?id=x&credential=y
+ [HttpDelete("passkey")]
+ [Authorize]
+ public async Task DeletePasskey(int id, string credential)
+ {
+ if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
+ {
+ await _userManager.DeletePasskey(id, Base64Url.DecodeFromChars(credential));
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized User Passkey Delete Attempt {UserId} {Credential}", id, credential);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ }
+ }
+
+ // GET: api//login?id=x
+ [HttpGet("login")]
+ [Authorize]
+ public async Task> GetLogins(int id)
+ {
+ if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
+ {
+ return await _userManager.GetLogins(id, _tenantManager.GetAlias().SiteId);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized External Login Get Attempt {UserId} {SiteId}", id, _tenantManager.GetAlias().SiteId);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ return null;
+ }
+ }
+
+ // PUT api//login
+ [HttpPost("login")]
+ public async Task AddLogin([FromBody] User user, string token, string type, string key, string name)
+ {
+ if (ModelState.IsValid)
+ {
+ user = await _userManager.AddLogin(user, token, type, key, name);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized External Login Post Attempt {Username} {Token}", user.Username, token);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ user = null;
+ }
+ return user;
+ }
+
+ // DELETE api//login?id=x&provider=y&key=z
+ [HttpDelete("login")]
+ [Authorize]
+ public async Task DeleteLogin(int id, string provider, string key)
+ {
+ if (_userPermissions.IsAuthorized(User, _tenantManager.GetAlias().SiteId, EntityNames.User, -1, PermissionNames.Write, RoleNames.Admin) || _userPermissions.GetUser(User).UserId == id)
+ {
+ await _userManager.DeleteLogin(id, provider, key);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized External Login Delete Attempt {UserId} {Provider} {Key}", id, provider, key);
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
+ }
+ }
}
}
diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
index 115abee0..0452686c 100644
--- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs
@@ -22,7 +22,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
-using Microsoft.OpenApi.Models;
+using Microsoft.OpenApi;
using Oqtane.Extensions;
using Oqtane.Infrastructure;
using Oqtane.Interfaces;
@@ -102,7 +102,8 @@ namespace Microsoft.Extensions.DependencyInjection
})
.AddCookie(Constants.AuthenticationScheme)
.AddOpenIdConnect(AuthenticationProviderTypes.OpenIDConnect, options => { })
- .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { });
+ .AddOAuth(AuthenticationProviderTypes.OAuth2, options => { })
+ .AddTwoFactorUserIdCookie();
services.ConfigureOqtaneCookieOptions();
services.ConfigureOqtaneAuthenticationOptions(configuration);
diff --git a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs
index 8c8f1990..26a041ee 100644
--- a/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs
+++ b/Oqtane.Server/Extensions/OqtaneSiteAuthenticationBuilderExtensions.cs
@@ -676,12 +676,12 @@ namespace Oqtane.Extensions
var _syncManager = httpContext.RequestServices.GetRequiredService();
_syncManager.AddSyncEvent(alias, EntityNames.User, user.UserId, "Login");
- _logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External User Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
+ _logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External Login Successful For {Username} From IP Address {IPAddress} Using Provider {Provider}", user.Username, httpContext.Connection.RemoteIpAddress.ToString(), providerName);
}
else
{
identity.Label = ExternalLoginStatus.AccessDenied;
- _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External User Login Denied For {Username}. User Account Is Deleted Or Not An Active Member Of Site {SiteId}.", user.Username, user.SiteId);
+ _logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External Login Denied For {Username}. User Account Is Deleted Or Not An Active Member Of Site {SiteId}.", user.Username, user.SiteId);
}
}
}
diff --git a/Oqtane.Server/Infrastructure/DatabaseManager.cs b/Oqtane.Server/Infrastructure/DatabaseManager.cs
index 4dd4ea52..1d4debe7 100644
--- a/Oqtane.Server/Infrastructure/DatabaseManager.cs
+++ b/Oqtane.Server/Infrastructure/DatabaseManager.cs
@@ -395,7 +395,7 @@ namespace Oqtane.Infrastructure
var connectionString = _configManager.GetSetting($"{SettingKeys.ConnectionStringsSection}:{tenant.DBConnectionString}", "");
if (!string.IsNullOrEmpty(connectionString))
{
- using (var tenantDbContext = new TenantDBContext(DBContextDependencies))
+ using (var tenantDbContext = new TenantDBContext(new DbContextOptions(), DBContextDependencies))
{
AddEFMigrationsHistory(sql, connectionString, tenant.DBType, tenant.Version, false);
// push latest model into database
@@ -507,6 +507,10 @@ namespace Oqtane.Infrastructure
}
}
}
+ else
+ {
+ result.Message = "An Error Occurred Installing " + moduleDefinition.Name + " - ServerManagerType " + moduleDefinition.ServerManagerType + " Does Not Exist";
+ }
}
if (string.IsNullOrEmpty(result.Message) && moduleDefinition.Version != versions[versions.Length - 1])
diff --git a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs
index 7771d8c1..9d320bd9 100644
--- a/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs
+++ b/Oqtane.Server/Infrastructure/Jobs/NotificationJob.cs
@@ -42,220 +42,237 @@ namespace Oqtane.Infrastructure
{
log += "Processing Notifications For Site: " + site.Name + "
";
- // get site settings
- var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1);
-
- if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
+ List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
+ if (notifications.Count > 0)
{
- bool valid = true;
- if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
+ // get site settings
+ var settings = settingRepository.GetSettings(EntityNames.Site, site.SiteId, EntityNames.Host, -1);
+
+ if (!site.IsDeleted && settingRepository.GetSettingValue(settings, "SMTPEnabled", "True") == "True")
{
- // basic
- if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
- {
- log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "
";
- valid = false;
- }
- }
- else
- {
- // oauth
- if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPAuthority", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPClientId", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPClientSecret", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPScopes", "") == "" ||
- settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
- {
- log += "SMTP Not Configured Properly In Site Settings - Host, Port, Authority, Client ID, Client Secret, Scopes, And Sender Are All Required" + "
";
- valid = false;
- }
- }
-
-
- if (valid)
- {
- // construct SMTP Client
- using var client = new SmtpClient();
-
- var secureSocketOptions = SecureSocketOptions.Auto;
- switch (settingRepository.GetSettingValue(settings, "SMTPSSL", "Auto"))
- {
- case "None":
- secureSocketOptions = SecureSocketOptions.None;
- break;
- case "Auto":
- secureSocketOptions = SecureSocketOptions.Auto;
- break;
- case "StartTls":
- secureSocketOptions = SecureSocketOptions.StartTls;
- break;
- case "SslOnConnect":
- case "True": // legacy setting value
- secureSocketOptions = SecureSocketOptions.SslOnConnect;
- break;
- case "StartTlsWhenAvailable":
- case "False": // legacy setting value
- secureSocketOptions = SecureSocketOptions.StartTlsWhenAvailable;
- break;
- }
-
- await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""),
- int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")),
- secureSocketOptions);
-
+ bool valid = true;
if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
{
- // it is possible to use basic without any authentication (not recommended)
- if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
+ // basic
+ if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
{
- await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
- settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
+ log += "SMTP Not Configured Properly In Site Settings - Host, Port, And Sender Are All Required" + "
";
+ valid = false;
}
}
else
{
- // oauth authentication
- var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(settingRepository.GetSettingValue(settings, "SMTPClientId", ""))
- .WithAuthority(settingRepository.GetSettingValue(settings, "SMTPAuthority", ""))
- .WithClientSecret(settingRepository.GetSettingValue(settings, "SMTPClientSecret", ""))
- .Build();
- try
+ // oauth
+ if (settingRepository.GetSettingValue(settings, "SMTPHost", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPPort", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPAuthority", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPClientId", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPClientSecret", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPScopes", "") == "" ||
+ settingRepository.GetSettingValue(settings, "SMTPSender", "") == "")
{
- var result = await confidentialClientApplication.AcquireTokenForClient(settingRepository.GetSettingValue(settings, "SMTPScopes", "").Split(',')).ExecuteAsync();
- var oauth2 = new SaslMechanismOAuth2(settingRepository.GetSettingValue(settings, "SMTPSender", ""), result.AccessToken);
- await client.AuthenticateAsync(oauth2);
- }
- catch (Exception ex)
- {
- log += "SMTP Not Configured Properly In Site Settings - OAuth Token Could Not Be Retrieved From Authority - " + ex.Message + "
";
+ log += "SMTP Not Configured Properly In Site Settings - Host, Port, Authority, Client ID, Client Secret, Scopes, And Sender Are All Required" + "
";
valid = false;
}
}
if (valid)
{
- // iterate through undelivered notifications
- int sent = 0;
- List notifications = notificationRepository.GetNotifications(site.SiteId, -1, -1).ToList();
- foreach (Notification notification in notifications)
+ // construct SMTP Client
+ using var client = new SmtpClient();
+
+ try
{
- var fromEmail = notification.FromEmail ?? "";
- var fromName = notification.FromDisplayName ?? "";
- var toEmail = notification.ToEmail ?? "";
- var toName = notification.ToDisplayName ?? "";
-
- // get sender and receiver information from user information if available
- if ((string.IsNullOrEmpty(fromEmail) || string.IsNullOrEmpty(fromName)) && notification.FromUserId != null)
+ var secureSocketOptions = SecureSocketOptions.Auto;
+ switch (settingRepository.GetSettingValue(settings, "SMTPSSL", "Auto"))
{
- var user = userRepository.GetUser(notification.FromUserId.Value);
- if (user != null)
+ case "None":
+ secureSocketOptions = SecureSocketOptions.None;
+ break;
+ case "Auto":
+ secureSocketOptions = SecureSocketOptions.Auto;
+ break;
+ case "StartTls":
+ secureSocketOptions = SecureSocketOptions.StartTls;
+ break;
+ case "SslOnConnect":
+ case "True": // legacy setting value
+ secureSocketOptions = SecureSocketOptions.SslOnConnect;
+ break;
+ case "StartTlsWhenAvailable":
+ case "False": // legacy setting value
+ secureSocketOptions = SecureSocketOptions.StartTlsWhenAvailable;
+ break;
+ }
+
+ await client.ConnectAsync(settingRepository.GetSettingValue(settings, "SMTPHost", ""),
+ int.Parse(settingRepository.GetSettingValue(settings, "SMTPPort", "")),
+ secureSocketOptions);
+ }
+ catch (Exception ex)
+ {
+ log += "SMTP Not Configured Properly In Site Settings - Could Not Connect To SMTP Server - " + ex.Message + "
";
+ valid = false;
+ }
+
+ if (valid)
+ {
+ if (settingRepository.GetSettingValue(settings, "SMTPAuthentication", "Basic") == "Basic")
+ {
+ // it is possible to use basic without any authentication (not recommended)
+ if (settingRepository.GetSettingValue(settings, "SMTPUsername", "") != "" && settingRepository.GetSettingValue(settings, "SMTPPassword", "") != "")
{
- fromEmail = string.IsNullOrEmpty(fromEmail) ? user.Email ?? "" : fromEmail;
- fromName = string.IsNullOrEmpty(fromName) ? user.DisplayName ?? "" : fromName;
+ await client.AuthenticateAsync(settingRepository.GetSettingValue(settings, "SMTPUsername", ""),
+ settingRepository.GetSettingValue(settings, "SMTPPassword", ""));
}
}
- if ((string.IsNullOrEmpty(toEmail) || string.IsNullOrEmpty(toName)) && notification.ToUserId != null)
- {
- var user = userRepository.GetUser(notification.ToUserId.Value);
- if (user != null)
- {
- toEmail = string.IsNullOrEmpty(toEmail) ? user.Email ?? "" : toEmail;
- toName = string.IsNullOrEmpty(toName) ? user.DisplayName ?? "" : toName;
- }
- }
-
- // create mailbox addresses
- MailboxAddress to = null;
- MailboxAddress from = null;
- var mailboxAddressValidationError = "";
-
- // sender
- if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True")
- {
- fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", "");
- fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName;
- }
- if (MailboxAddress.TryParse(fromEmail, out from))
- {
- from.Name = fromName;
- }
else
{
-
- mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>";
- }
-
- // recipient
- if (MailboxAddress.TryParse(toEmail, out to))
- {
- to.Name = toName;
- }
- else
- {
- mailboxAddressValidationError += $" Invalid Recipient: {toName} <{toEmail}>";
- }
-
- // if mailbox addresses are valid
- if (from != null && to != null)
- {
- // create mail message
- MimeMessage mailMessage = new MimeMessage();
- mailMessage.From.Add(from);
- mailMessage.To.Add(to);
-
- // subject
- mailMessage.Subject = notification.Subject;
-
- // body
- var bodyText = notification.Body;
-
- if (!bodyText.Contains('<') || !bodyText.Contains('>'))
- {
- // plain text messages should convert line breaks to HTML tags to preserve formatting
- bodyText = bodyText.Replace("\n", "
");
- }
-
- mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8)
- {
- Text = bodyText
- };
-
- // send mail
+ // oauth authentication
+ var confidentialClientApplication = ConfidentialClientApplicationBuilder.Create(settingRepository.GetSettingValue(settings, "SMTPClientId", ""))
+ .WithAuthority(settingRepository.GetSettingValue(settings, "SMTPAuthority", ""))
+ .WithClientSecret(settingRepository.GetSettingValue(settings, "SMTPClientSecret", ""))
+ .Build();
try
{
- await client.SendAsync(mailMessage);
- sent++;
- notification.IsDelivered = true;
- notification.DeliveredOn = DateTime.UtcNow;
- notificationRepository.UpdateNotification(notification);
+ var result = await confidentialClientApplication.AcquireTokenForClient(settingRepository.GetSettingValue(settings, "SMTPScopes", "").Split(',')).ExecuteAsync();
+ var oauth2 = new SaslMechanismOAuth2(settingRepository.GetSettingValue(settings, "SMTPSender", ""), result.AccessToken);
+ await client.AuthenticateAsync(oauth2);
}
catch (Exception ex)
{
- log += $"Error Sending Notification Id: {notification.NotificationId} - {ex.Message}
";
+ log += "SMTP Not Configured Properly In Site Settings - OAuth Token Could Not Be Retrieved From Authority - " + ex.Message + "
";
+ valid = false;
}
}
- else
- {
- // invalid mailbox address
- log += $"Notification Id: {notification.NotificationId} Has An {mailboxAddressValidationError} And Has Been Deleted
";
- notification.IsDeleted = true;
- notificationRepository.UpdateNotification(notification);
- }
}
- log += "Notifications Delivered: " + sent + "
";
- }
+ if (valid)
+ {
+ // iterate through undelivered notifications
+ int sent = 0;
+ foreach (Notification notification in notifications)
+ {
+ var fromEmail = notification.FromEmail ?? "";
+ var fromName = notification.FromDisplayName ?? "";
+ var toEmail = notification.ToEmail ?? "";
+ var toName = notification.ToDisplayName ?? "";
- await client.DisconnectAsync(true);
+ // get sender and receiver information from user information if available
+ if ((string.IsNullOrEmpty(fromEmail) || string.IsNullOrEmpty(fromName)) && notification.FromUserId != null)
+ {
+ var user = userRepository.GetUser(notification.FromUserId.Value);
+ if (user != null)
+ {
+ fromEmail = string.IsNullOrEmpty(fromEmail) ? user.Email ?? "" : fromEmail;
+ fromName = string.IsNullOrEmpty(fromName) ? user.DisplayName ?? "" : fromName;
+ }
+ }
+ if ((string.IsNullOrEmpty(toEmail) || string.IsNullOrEmpty(toName)) && notification.ToUserId != null)
+ {
+ var user = userRepository.GetUser(notification.ToUserId.Value);
+ if (user != null)
+ {
+ toEmail = string.IsNullOrEmpty(toEmail) ? user.Email ?? "" : toEmail;
+ toName = string.IsNullOrEmpty(toName) ? user.DisplayName ?? "" : toName;
+ }
+ }
+
+ // create mailbox addresses
+ MailboxAddress to = null;
+ MailboxAddress from = null;
+ var mailboxAddressValidationError = "";
+
+ // sender
+ if (settingRepository.GetSettingValue(settings, "SMTPRelay", "False") != "True")
+ {
+ fromEmail = settingRepository.GetSettingValue(settings, "SMTPSender", "");
+ fromName = string.IsNullOrEmpty(fromName) ? site.Name : fromName;
+ }
+ if (MailboxAddress.TryParse(fromEmail, out from))
+ {
+ from.Name = fromName;
+ }
+ else
+ {
+
+ mailboxAddressValidationError += $" Invalid Sender: {fromName} <{fromEmail}>";
+ }
+
+ // recipient
+ if (MailboxAddress.TryParse(toEmail, out to))
+ {
+ to.Name = toName;
+ }
+ else
+ {
+ mailboxAddressValidationError += $" Invalid Recipient: {toName} <{toEmail}>";
+ }
+
+ // if mailbox addresses are valid
+ if (from != null && to != null)
+ {
+ // create mail message
+ MimeMessage mailMessage = new MimeMessage();
+ mailMessage.From.Add(from);
+ mailMessage.To.Add(to);
+
+ // subject
+ mailMessage.Subject = notification.Subject;
+
+ // body
+ var bodyText = notification.Body;
+
+ if (!bodyText.Contains('<') || !bodyText.Contains('>'))
+ {
+ // plain text messages should convert line breaks to HTML tags to preserve formatting
+ bodyText = bodyText.Replace("\n", "
");
+ }
+
+ mailMessage.Body = new TextPart("html", System.Text.Encoding.UTF8)
+ {
+ Text = bodyText
+ };
+
+ // send mail
+ try
+ {
+ await client.SendAsync(mailMessage);
+ sent++;
+ notification.IsDelivered = true;
+ notification.DeliveredOn = DateTime.UtcNow;
+ notificationRepository.UpdateNotification(notification);
+ }
+ catch (Exception ex)
+ {
+ log += $"Error Sending Notification Id: {notification.NotificationId} - {ex.Message}
";
+ }
+ }
+ else
+ {
+ // invalid mailbox address
+ log += $"Notification Id: {notification.NotificationId} Has An {mailboxAddressValidationError} And Has Been Deleted
";
+ notification.IsDeleted = true;
+ notificationRepository.UpdateNotification(notification);
+ }
+ }
+
+ log += "Notifications Delivered: " + sent + "
";
+ }
+
+ await client.DisconnectAsync(true);
+ }
+ }
+ else
+ {
+ log += "Site Deleted Or SMTP Disabled In Site Settings
";
}
}
else
{
- log += "Site Deleted Or SMTP Disabled In Site Settings" + "
";
+ log += "No Notifications To Deliver
";
}
}
diff --git a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs
index 13b74176..733c6bf1 100644
--- a/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs
+++ b/Oqtane.Server/Infrastructure/Middleware/TenantMiddleware.cs
@@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Caching.Memory;
using Oqtane.Repository;
using Oqtane.Shared;
-using SixLabors.ImageSharp.Metadata.Profiles.Exif;
namespace Oqtane.Infrastructure
{
diff --git a/Oqtane.Server/Infrastructure/UpgradeManager.cs b/Oqtane.Server/Infrastructure/UpgradeManager.cs
index 532bc3d0..d57e2eb7 100644
--- a/Oqtane.Server/Infrastructure/UpgradeManager.cs
+++ b/Oqtane.Server/Infrastructure/UpgradeManager.cs
@@ -7,15 +7,11 @@ using Oqtane.Infrastructure.SiteTemplates;
using Oqtane.Models;
using Oqtane.Repository;
using Oqtane.Shared;
-using Oqtane.UI;
-using Org.BouncyCastle.Pqc.Crypto.Lms;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Reflection.Metadata;
-using System.Runtime.Serialization;
namespace Oqtane.Infrastructure
{
@@ -103,24 +99,6 @@ namespace Oqtane.Infrastructure
private void Upgrade_2_0_2(Tenant tenant, IServiceScope scope)
{
- if (tenant.Name == TenantNames.Master)
- {
- // remove Internal module template files as they are no longer supported
- var internalTemplatePath = Utilities.PathCombine(_environment.WebRootPath, "Modules", "Templates", "Internal", Path.DirectorySeparatorChar.ToString());
- if (Directory.Exists(internalTemplatePath))
- {
- try
- {
- Directory.Delete(internalTemplatePath, true);
- }
- catch (Exception ex)
- {
- // error deleting directory
- _filelogger.LogError(Utilities.LogMessage(this, $"Oqtane Error: Error In 2.0.2 Upgrade Logic - {ex}"));
- }
- }
- }
-
// initialize SiteGuid
try
{
diff --git a/Oqtane.Server/Managers/UserManager.cs b/Oqtane.Server/Managers/UserManager.cs
index d6d4afa0..4e95d5f6 100644
--- a/Oqtane.Server/Managers/UserManager.cs
+++ b/Oqtane.Server/Managers/UserManager.cs
@@ -4,8 +4,10 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
+using System.Security.Policy;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
+using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Localization;
using Oqtane.Enums;
@@ -31,10 +33,15 @@ namespace Oqtane.Managers
Task ForgotPassword(User user);
Task ResetPassword(User user, string token);
User VerifyTwoFactor(User user, string token);
- Task LinkExternalAccount(User user, string token, string type, string key, string name);
Task ValidateUser(string username, string email, string password);
Task ValidatePassword(string password);
Task> ImportUsers(int siteId, string filePath, bool notify);
+ Task> GetPasskeys(int userId, int siteId);
+ Task UpdatePasskey(UserPasskey passkey);
+ Task DeletePasskey(int userId, byte[] credentialId);
+ 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);
}
public class UserManager : IUserManager
@@ -581,29 +588,6 @@ namespace Oqtane.Managers
}
return user;
}
-
- public async Task LinkExternalAccount(User user, string token, string type, string key, string name)
- {
- IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
- if (identityuser != null && !string.IsNullOrEmpty(token))
- {
- var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
- if (result.Succeeded)
- {
- // make LoginProvider multi-tenant aware
- type += ":" + user.SiteId.ToString();
- await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(type, key, name));
- _logger.Log(LogLevel.Information, this, LogFunction.Security, "External Login Linkage Successful For {Username} And Provider {Provider}", user.Username, type);
- }
- else
- {
- _logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} - Error {Error}", user.Username, string.Join(" ", result.Errors.ToList().Select(e => e.Description)));
- user = null;
- }
- }
- return user;
- }
-
public async Task ValidateUser(string username, string email, string password)
{
var validateResult = new UserValidateResult { Succeeded = true };
@@ -818,5 +802,117 @@ namespace Oqtane.Managers
return result;
}
+
+ public async Task> GetPasskeys(int userId, int siteId)
+ {
+ var passkeys = new List();
+ var user = _users.GetUser(userId);
+ if (user != null)
+ {
+ var identityuser = await _identityUserManager.FindByNameAsync(user.Username);
+ if (identityuser != null)
+ {
+ var userpasskeys = await _identityUserManager.GetPasskeysAsync(identityuser);
+ foreach (var userpasskey in userpasskeys)
+ {
+ // 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 });
+ }
+ }
+ }
+ }
+ return passkeys;
+ }
+
+ public async Task UpdatePasskey(UserPasskey passkey)
+ {
+ var user = _users.GetUser(passkey.UserId);
+ if (user != null)
+ {
+ var identityuser = await _identityUserManager.FindByNameAsync(user.Username);
+ if (identityuser != null)
+ {
+ var userPasskeyInfo = await _identityUserManager.GetPasskeyAsync(identityuser, passkey.CredentialId);
+ if (userPasskeyInfo != null)
+ {
+ userPasskeyInfo.Name = passkey.Name;
+ await _identityUserManager.AddOrUpdatePasskeyAsync(identityuser, userPasskeyInfo);
+ }
+ }
+ }
+ }
+
+ public async Task DeletePasskey(int userId, byte[] credentialId)
+ {
+ var user = _users.GetUser(userId);
+ if (user != null)
+ {
+ var identityuser = await _identityUserManager.FindByNameAsync(user.Username);
+ if (identityuser != null)
+ {
+ await _identityUserManager.RemovePasskeyAsync(identityuser, credentialId);
+ }
+ }
+ }
+
+ public async Task> GetLogins(int userId, int siteId)
+ {
+ var logins = new List();
+ var user = _users.GetUser(userId);
+ if (user != null)
+ {
+ var identityuser = await _identityUserManager.FindByNameAsync(user.Username);
+ if (identityuser != null)
+ {
+ var userlogins = await _identityUserManager.GetLoginsAsync(identityuser);
+ foreach (var userlogin in userlogins)
+ {
+ if (userlogin.LoginProvider.EndsWith(":" + siteId.ToString()))
+ {
+ logins.Add(new UserLogin { Provider = userlogin.LoginProvider, Key = userlogin.ProviderKey, Name = userlogin.ProviderDisplayName });
+ }
+ }
+ }
+ }
+ return logins;
+ }
+
+ public async Task AddLogin(User user, string token, string type, string key, string name)
+ {
+ IdentityUser identityuser = await _identityUserManager.FindByNameAsync(user.Username);
+ if (identityuser != null && !string.IsNullOrEmpty(token))
+ {
+ var result = await _identityUserManager.ConfirmEmailAsync(identityuser, token);
+ if (result.Succeeded)
+ {
+ // make LoginProvider multi-tenant aware
+ type += ":" + user.SiteId.ToString();
+ await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(type, key, name));
+ _logger.Log(LogLevel.Information, this, LogFunction.Security, "External Login Linkage Successful For {Username} And Provider {Provider}", user.Username, type);
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "External Login Linkage Failed For {Username} - Error {Error}", user.Username, string.Join(" ", result.Errors.ToList().Select(e => e.Description)));
+ user = null;
+ }
+ }
+ return user;
+ }
+
+
+ public async Task DeleteLogin(int userId, string provider, string key)
+ {
+ var user = _users.GetUser(userId);
+ if (user != null)
+ {
+ var identityuser = await _identityUserManager.FindByNameAsync(user.Username);
+ if (identityuser != null)
+ {
+ await _identityUserManager.RemoveLoginAsync(identityuser, provider, key);
+ }
+ }
+ }
}
}
diff --git a/Oqtane.Server/Migrations/EntityBuilders/AspNetUserPasskeysEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserPasskeysEntityBuilder.cs
new file mode 100644
index 00000000..32219746
--- /dev/null
+++ b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserPasskeysEntityBuilder.cs
@@ -0,0 +1,39 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations.Operations;
+using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
+using Oqtane.Databases.Interfaces;
+
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
+namespace Oqtane.Migrations.EntityBuilders
+{
+ public class AspNetUserPasskeysEntityBuilder : BaseEntityBuilder
+ {
+ private const string _entityTableName = "AspNetUserPasskeys";
+ private readonly PrimaryKey _primaryKey = new("PK_AspNetUserPasskeys", x => x.CredentialId);
+ private readonly ForeignKey _aspNetUsersForeignKey = new("FK_AspNetUserPasskeys_AspNetUsers_UserId", x => x.UserId, "AspNetUsers", "Id", ReferentialAction.Cascade);
+
+ public AspNetUserPasskeysEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
+ {
+ EntityTableName = _entityTableName;
+ PrimaryKey = _primaryKey;
+ ForeignKeys.Add(_aspNetUsersForeignKey);
+ }
+
+ protected override AspNetUserPasskeysEntityBuilder BuildTable(ColumnsBuilder table)
+ {
+ CredentialId = AddBinaryColumn(table, "CredentialId", 1024);
+ UserId = AddStringColumn(table, "UserId", 450);
+ Data = AddMaxStringColumn(table, "Data");
+
+ return this;
+ }
+
+ public OperationBuilder CredentialId { get; set; }
+
+ public OperationBuilder UserId { get; set; }
+
+ public OperationBuilder Data { get; set; }
+ }
+}
diff --git a/Oqtane.Server/Migrations/EntityBuilders/AspNetUserTokensEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserTokensEntityBuilder.cs
new file mode 100644
index 00000000..16bf640b
--- /dev/null
+++ b/Oqtane.Server/Migrations/EntityBuilders/AspNetUserTokensEntityBuilder.cs
@@ -0,0 +1,42 @@
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Migrations.Operations;
+using Microsoft.EntityFrameworkCore.Migrations.Operations.Builders;
+using Oqtane.Databases.Interfaces;
+
+// ReSharper disable MemberCanBePrivate.Global
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+
+namespace Oqtane.Migrations.EntityBuilders
+{
+ public class AspNetUserTokensEntityBuilder : BaseEntityBuilder
+ {
+ private const string _entityTableName = "AspNetUserTokens";
+ private readonly PrimaryKey _primaryKey = new("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name });
+ private readonly ForeignKey _aspNetUsersForeignKey = new("FK_AspNetUserTokens_AspNetUsers_UserId", x => x.UserId, "AspNetUsers", "Id", ReferentialAction.Cascade);
+
+ public AspNetUserTokensEntityBuilder(MigrationBuilder migrationBuilder, IDatabase database) : base(migrationBuilder, database)
+ {
+ EntityTableName = _entityTableName;
+ PrimaryKey = _primaryKey;
+ ForeignKeys.Add(_aspNetUsersForeignKey);
+ }
+
+ protected override AspNetUserTokensEntityBuilder BuildTable(ColumnsBuilder table)
+ {
+ UserId = AddStringColumn(table, "UserId", 450);
+ LoginProvider = AddStringColumn(table, "LoginProvider", 128);
+ Name = AddStringColumn(table, "Name", 128);
+ Value = AddMaxStringColumn(table, "Value", true);
+
+ return this;
+ }
+
+ public OperationBuilder UserId { get; set; }
+
+ public OperationBuilder LoginProvider { get; set; }
+
+ public OperationBuilder Name { get; set; }
+
+ public OperationBuilder Value { get; set; }
+ }
+}
diff --git a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs
index e5ac664b..42342138 100644
--- a/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs
+++ b/Oqtane.Server/Migrations/EntityBuilders/BaseEntityBuilder.cs
@@ -299,6 +299,27 @@ namespace Oqtane.Migrations.EntityBuilders
return table.Column(name: RewriteName(name), nullable: nullable, defaultValue: defaultValue);
}
+ // binary
+ public void AddBinaryColumn(string name, int length, bool nullable = false, bool unicode = true)
+ {
+ _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode, schema: Schema);
+ }
+
+ public void AddBinaryColumn(string name, int length, bool nullable, bool unicode, string defaultValue)
+ {
+ _migrationBuilder.AddColumn(RewriteName(name), RewriteName(EntityTableName), maxLength: length, nullable: nullable, unicode: unicode, defaultValue: defaultValue, schema: Schema);
+ }
+
+ protected OperationBuilder AddBinaryColumn(ColumnsBuilder table, string name, int length, bool nullable = false, bool unicode = true)
+ {
+ return table.Column(name: RewriteName(name), maxLength: length, nullable: nullable, unicode: unicode);
+ }
+
+ protected OperationBuilder AddBinaryColumn(ColumnsBuilder table, string name, int length, bool nullable, bool unicode, string defaultValue)
+ {
+ return table.Column(name: RewriteName(name), maxLength: length, nullable: nullable, unicode: unicode, defaultValue: defaultValue);
+ }
+
// alter string
public void AlterStringColumn(string name, int length, bool nullable = false, bool unicode = true, string index = "")
{
diff --git a/Oqtane.Server/Migrations/Tenant/10000001_AddAspNetUserPasskeys.cs b/Oqtane.Server/Migrations/Tenant/10000001_AddAspNetUserPasskeys.cs
new file mode 100644
index 00000000..3ed8dfcd
--- /dev/null
+++ b/Oqtane.Server/Migrations/Tenant/10000001_AddAspNetUserPasskeys.cs
@@ -0,0 +1,29 @@
+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.00.01")]
+ public class AddAspNetUserPasskeys : MultiDatabaseMigration
+ {
+ public AddAspNetUserPasskeys(IDatabase database) : base(database)
+ {
+ }
+
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ var aspNetUserPasskeysEntityBuilder = new AspNetUserPasskeysEntityBuilder(migrationBuilder, ActiveDatabase);
+ aspNetUserPasskeysEntityBuilder.Create();
+ aspNetUserPasskeysEntityBuilder.AddIndex("IX_AspNetUserPasskeys_UserId", "UserId");
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ // not implemented
+ }
+ }
+}
diff --git a/Oqtane.Server/Migrations/Tenant/10000002_AddAspNetUserTokens.cs b/Oqtane.Server/Migrations/Tenant/10000002_AddAspNetUserTokens.cs
new file mode 100644
index 00000000..4a66848e
--- /dev/null
+++ b/Oqtane.Server/Migrations/Tenant/10000002_AddAspNetUserTokens.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.00.02")]
+ public class AddAspNetUserTokens : MultiDatabaseMigration
+ {
+ public AddAspNetUserTokens(IDatabase database) : base(database)
+ {
+ }
+
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ var aspNetUserTokensEntityBuilder = new AspNetUserTokensEntityBuilder(migrationBuilder, ActiveDatabase);
+ aspNetUserTokensEntityBuilder.Create();
+ }
+
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ // not implemented
+ }
+ }
+}
diff --git a/Oqtane.Server/Oqtane.Server.csproj b/Oqtane.Server/Oqtane.Server.csproj
index 6dde2259..1236dde0 100644
--- a/Oqtane.Server/Oqtane.Server.csproj
+++ b/Oqtane.Server/Oqtane.Server.csproj
@@ -9,12 +9,15 @@
false
false
/
+ true
+
+
@@ -24,36 +27,28 @@
-
-
-
-
-
- all
- runtime; build; native; contentfiles; analyzers; buildtransitive
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
-
-
-
+
+
-
-
-
+
+
-
+
diff --git a/Oqtane.Server/Pages/Passkey.cshtml b/Oqtane.Server/Pages/Passkey.cshtml
new file mode 100644
index 00000000..43bac572
--- /dev/null
+++ b/Oqtane.Server/Pages/Passkey.cshtml
@@ -0,0 +1,3 @@
+@page "/pages/passkey"
+@namespace Oqtane.Pages
+@model Oqtane.Pages.PasskeyModel
diff --git a/Oqtane.Server/Pages/Passkey.cshtml.cs b/Oqtane.Server/Pages/Passkey.cshtml.cs
new file mode 100644
index 00000000..8923d78d
--- /dev/null
+++ b/Oqtane.Server/Pages/Passkey.cshtml.cs
@@ -0,0 +1,154 @@
+using System.Net;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Oqtane.Enums;
+using Oqtane.Extensions;
+using Oqtane.Infrastructure;
+using Oqtane.Managers;
+using Oqtane.Security;
+using Oqtane.Shared;
+
+namespace Oqtane.Pages
+{
+ [AllowAnonymous]
+ public class PasskeyModel : PageModel
+ {
+ private readonly UserManager _identityUserManager;
+ private readonly SignInManager _identitySignInManager;
+ private readonly IUserManager _userManager;
+ private readonly ILogManager _logger;
+
+ public PasskeyModel(UserManager identityUserManager, SignInManager identitySignInManager, IUserManager userManager, ILogManager logger)
+ {
+ _identityUserManager = identityUserManager;
+ _identitySignInManager = identitySignInManager;
+ _userManager = userManager;
+ _logger = logger;
+ }
+
+ public async Task OnPostAsync(string operation, string credential, string returnurl)
+ {
+ if (HttpContext.GetSiteSettings().GetValue("LoginOptions:Passkeys", "false") == "true")
+ {
+ IdentityUser identityuser;
+
+ switch (operation.ToLower())
+ {
+ case "create":
+ if (User.Identity.IsAuthenticated)
+ {
+ identityuser = await _identityUserManager.FindByNameAsync(User.Identity.Name);
+ if (identityuser != null)
+ {
+ var creationOptionsJson = await _identitySignInManager.MakePasskeyCreationOptionsAsync(new()
+ {
+ Id = identityuser.Id,
+ Name = identityuser.UserName,
+ DisplayName = identityuser.UserName
+ });
+ returnurl += (!returnurl.Contains("?") ? "?" : "&") + $"options={WebUtility.UrlEncode(creationOptionsJson)}";
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Create Attempt - User {User} Does Not Exist", User.Identity.Name);
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Create Attempt - User Not Authenticated");
+ }
+ break;
+ case "validate":
+ if (User.Identity.IsAuthenticated && !string.IsNullOrEmpty(credential))
+ {
+ identityuser = await _identityUserManager.FindByNameAsync(User.Identity.Name);
+ if (identityuser != null)
+ {
+ var attestationResult = await _identitySignInManager.PerformPasskeyAttestationAsync(credential);
+ if (attestationResult.Succeeded)
+ {
+ 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
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Passkey Validation Failed For User {Username} - {Message}", User.Identity.Name, attestationResult.Failure.Message);
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Validation Attempt - User {User} Does Not Exist", User.Identity.Name);
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Validation Attempt - User Not Authenticated Or Credential Not Provided");
+ }
+ break;
+ case "request":
+ if (!User.Identity.IsAuthenticated)
+ {
+ identityuser = null;
+ var requestOptionsJson = await _identitySignInManager.MakePasskeyRequestOptionsAsync(identityuser);
+ returnurl += $"?options={WebUtility.UrlEncode(requestOptionsJson)}";
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Request Attempt - User Is Already Authenticated");
+ }
+ break;
+ case "login":
+ if (!User.Identity.IsAuthenticated && !string.IsNullOrEmpty(credential))
+ {
+ var result = await _identitySignInManager.PasskeySignInAsync(credential);
+ if (result.Succeeded)
+ {
+ var user = _userManager.GetUser(User.Identity.Name, HttpContext.GetAlias().SiteId);
+ if (user != null && !user.IsDeleted && UserSecurity.ContainsRole(user.Roles, RoleNames.Registered))
+ {
+ _logger.Log(LogLevel.Information, this, LogFunction.Security, "Passkey Login Successful For User {Username}", User.Identity.Name);
+ }
+ else
+ {
+ _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
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Passkey Login Failed - Invalid Credential");
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Login Attempt");
+ }
+ break;
+ }
+ }
+ else
+ {
+ _logger.Log(LogLevel.Error, this, LogFunction.Security, "Unauthorized Passkey Request - Passkeys Are Not Enabled For Site");
+ }
+
+ if (!returnurl.StartsWith("/"))
+ {
+ returnurl = "/" + returnurl;
+ }
+
+ return LocalRedirect(Url.Content("~" + returnurl));
+ }
+ }
+}
diff --git a/Oqtane.Server/Program.cs b/Oqtane.Server/Program.cs
index 24eeee11..5fb2fad2 100644
--- a/Oqtane.Server/Program.cs
+++ b/Oqtane.Server/Program.cs
@@ -1,25 +1,46 @@
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Hosting;
+using System;
+using System.IO;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.Extensions.Configuration;
-using Microsoft.AspNetCore;
using Microsoft.Extensions.DependencyInjection;
-using Oqtane.Infrastructure;
using Microsoft.Extensions.Logging;
-using Oqtane.Documentation;
+using Oqtane.Extensions;
+using Oqtane.Infrastructure;
+using Oqtane.Shared;
namespace Oqtane.Server
{
- [PrivateApi("Mark Entry-Program as private, since it's not very useful in the public docs")]
public class Program
{
public static void Main(string[] args)
{
- var host = BuildWebHost(args);
- var databaseManager = host.Services.GetService();
+ var builder = WebApplication.CreateBuilder(args);
+
+ AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(builder.Environment.ContentRootPath, "Data"));
+
+ var configurationBuilder = new ConfigurationBuilder()
+ .SetBasePath(builder.Environment.ContentRootPath)
+ .AddJsonFile("appsettings.json", false, true)
+ .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", true, true)
+ .AddEnvironmentVariables();
+ var configuration = configurationBuilder.Build();
+
+ builder.Services.AddOqtane(configuration, builder.Environment);
+
+ var app = builder.Build();
+
+ var corsService = app.Services.GetRequiredService();
+ var corsPolicyProvider = app.Services.GetRequiredService();
+ var syncManager = app.Services.GetRequiredService();
+
+ app.UseOqtane(configuration, builder.Environment, corsService, corsPolicyProvider, syncManager);
+
+ var databaseManager = app.Services.GetService();
var install = databaseManager.Install();
if (!string.IsNullOrEmpty(install.Message))
{
- var filelogger = host.Services.GetRequiredService>();
+ var filelogger = app.Services.GetRequiredService>();
if (filelogger != null)
{
filelogger.LogError($"[Oqtane.Server.Program.Main] {install.Message}");
@@ -27,18 +48,8 @@ namespace Oqtane.Server
}
else
{
- host.Run();
+ app.Run();
}
}
-
- public static IWebHost BuildWebHost(string[] args) =>
- WebHost.CreateDefaultBuilder(args)
- .UseConfiguration(new ConfigurationBuilder()
- .AddCommandLine(args)
- .AddEnvironmentVariables()
- .Build())
- .UseStartup()
- .ConfigureLocalizationSettings()
- .Build();
}
}
diff --git a/Oqtane.Server/Repository/Context/DBContextBase.cs b/Oqtane.Server/Repository/Context/DBContextBase.cs
index fc02291d..aedbf412 100644
--- a/Oqtane.Server/Repository/Context/DBContextBase.cs
+++ b/Oqtane.Server/Repository/Context/DBContextBase.cs
@@ -4,7 +4,6 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Configuration;
@@ -19,7 +18,7 @@ using Oqtane.Shared;
namespace Oqtane.Repository
{
- public class DBContextBase : IdentityUserContext
+ public class DBContextBase : DbContext
{
private readonly ITenantManager _tenantManager;
private readonly IHttpContextAccessor _accessor;
@@ -75,8 +74,6 @@ namespace Oqtane.Repository
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
-
- ActiveDatabase.UpdateIdentityStoreTableNames(builder);
}
public override int SaveChanges()
diff --git a/Oqtane.Server/Repository/Context/DBContextDependencies.cs b/Oqtane.Server/Repository/Context/DBContextDependencies.cs
index f48d4fcd..26f6c156 100644
--- a/Oqtane.Server/Repository/Context/DBContextDependencies.cs
+++ b/Oqtane.Server/Repository/Context/DBContextDependencies.cs
@@ -4,6 +4,13 @@ using Oqtane.Infrastructure;
namespace Oqtane.Repository
{
+ public interface IDBContextDependencies
+ {
+ ITenantManager TenantManager { get; }
+ IHttpContextAccessor Accessor { get; }
+ IConfigurationRoot Config { get; }
+ }
+
public class DBContextDependencies : IDBContextDependencies
{
public DBContextDependencies(ITenantManager tenantManager, IHttpContextAccessor httpContextAccessor, IConfigurationRoot config)
diff --git a/Oqtane.Server/Repository/Context/IDBContextDependencies.cs b/Oqtane.Server/Repository/Context/IDBContextDependencies.cs
deleted file mode 100644
index 5e93f0e1..00000000
--- a/Oqtane.Server/Repository/Context/IDBContextDependencies.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Configuration;
-using Oqtane.Infrastructure;
-
-namespace Oqtane.Repository
-{
- public interface IDBContextDependencies
- {
- ITenantManager TenantManager { get; }
- IHttpContextAccessor Accessor { get; }
- IConfigurationRoot Config { get; }
- }
-}
diff --git a/Oqtane.Server/Repository/Context/TenantDBContext.cs b/Oqtane.Server/Repository/Context/TenantDBContext.cs
index bb0e1c72..e8b1bb3d 100644
--- a/Oqtane.Server/Repository/Context/TenantDBContext.cs
+++ b/Oqtane.Server/Repository/Context/TenantDBContext.cs
@@ -1,6 +1,20 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Oqtane.Databases.Interfaces;
+using Oqtane.Extensions;
+using Oqtane.Infrastructure;
+using Oqtane.Migrations.Framework;
using Oqtane.Models;
using Oqtane.Repository.Databases.Interfaces;
+using Oqtane.Shared;
// ReSharper disable CheckNamespace
// ReSharper disable MemberCanBePrivate.Global
@@ -8,9 +22,87 @@ using Oqtane.Repository.Databases.Interfaces;
namespace Oqtane.Repository
{
- public class TenantDBContext : DBContextBase, IMultiDatabase
+ public class TenantDBContext : IdentityUserContext, IMultiDatabase
{
- public TenantDBContext(IDBContextDependencies DBContextDependencies) : base(DBContextDependencies) { }
+ private readonly ITenantManager _tenantManager;
+ private readonly IHttpContextAccessor _accessor;
+ private readonly IConfigurationRoot _config;
+ private string _connectionString = "";
+ private string _databaseType = "";
+
+ public TenantDBContext(DbContextOptions options, IDBContextDependencies DBContextDependencies) : base(options)
+ {
+ _tenantManager = DBContextDependencies.TenantManager;
+ _accessor = DBContextDependencies.Accessor;
+ _config = DBContextDependencies.Config;
+ }
+
+ public IDatabase ActiveDatabase { get; set; }
+
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.ReplaceService();
+
+ // specify the SchemaVersion for .NET Identity as it is not being persisted when using AddIdentityCore()
+ var services = new ServiceCollection();
+ services.AddIdentityCore(options =>
+ {
+ options.Stores.SchemaVersion = IdentitySchemaVersions.Version3;
+ });
+ optionsBuilder.UseApplicationServiceProvider(services.BuildServiceProvider());
+
+ if (string.IsNullOrEmpty(_connectionString))
+ {
+ Tenant tenant = _tenantManager.GetTenant();
+ if (tenant != null)
+ {
+ _connectionString = _config.GetConnectionString(tenant.DBConnectionString);
+ if (_connectionString != null)
+ {
+ _connectionString = _connectionString.Replace($"|{Constants.DataDirectory}|", AppDomain.CurrentDomain.GetData(Constants.DataDirectory)?.ToString());
+ _databaseType = tenant.DBType;
+ }
+ else
+ {
+ // tenant connection string does not exist in appsettings.json
+ }
+ }
+ }
+
+ if (!string.IsNullOrEmpty(_databaseType))
+ {
+ var type = Type.GetType(_databaseType);
+ ActiveDatabase = Activator.CreateInstance(type) as IDatabase;
+ }
+
+ if (!string.IsNullOrEmpty(_connectionString) && ActiveDatabase != null)
+ {
+ optionsBuilder.UseOqtaneDatabase(ActiveDatabase, _connectionString);
+ }
+
+ base.OnConfiguring(optionsBuilder);
+ }
+
+ protected override void OnModelCreating(ModelBuilder builder)
+ {
+ base.OnModelCreating(builder);
+
+ ActiveDatabase.UpdateIdentityStoreTableNames(builder);
+ }
+
+ public override int SaveChanges()
+ {
+ DbContextUtils.SaveChanges(this, _accessor);
+
+ return base.SaveChanges();
+ }
+
+ public override Task SaveChangesAsync(CancellationToken cancellationToken = default)
+ {
+ DbContextUtils.SaveChanges(this, _accessor);
+
+ return base.SaveChangesAsync(cancellationToken);
+ }
public virtual DbSet Site { get; set; }
public virtual DbSet Page { get; set; }
diff --git a/Oqtane.Server/Repository/ModuleRepository.cs b/Oqtane.Server/Repository/ModuleRepository.cs
index 18cbd05f..94291d5b 100644
--- a/Oqtane.Server/Repository/ModuleRepository.cs
+++ b/Oqtane.Server/Repository/ModuleRepository.cs
@@ -110,9 +110,12 @@ namespace Oqtane.Repository
ModuleDefinition moduledefinition = moduledefinitions.FirstOrDefault(item => item.ModuleDefinitionName == module.ModuleDefinitionName);
if (moduledefinition != null)
{
+ var settings = _settings.GetSettings(EntityNames.Module, moduleId);
+
ModuleContent modulecontent = new ModuleContent();
modulecontent.ModuleDefinitionName = moduledefinition.ModuleDefinitionName;
modulecontent.Version = moduledefinition.Version;
+ modulecontent.Settings = settings.Where(item => !item.IsPrivate).ToDictionary(x => x.SettingName, x => x.SettingValue);
modulecontent.Content = "";
if (moduledefinition.ServerManagerType != "")
@@ -122,7 +125,7 @@ namespace Oqtane.Repository
{
try
{
- module.Settings = _settings.GetSettings(EntityNames.Module, moduleId).ToDictionary(x => x.SettingName, x => x.SettingValue);
+ module.Settings = settings.ToDictionary(x => x.SettingName, x => x.SettingValue);
var moduleobject = ActivatorUtilities.CreateInstance(_serviceProvider, moduletype);
modulecontent.Content = ((IPortable)moduleobject).ExportModule(module);
}
@@ -160,6 +163,29 @@ namespace Oqtane.Repository
ModuleContent modulecontent = JsonSerializer.Deserialize(content.Replace("\n", ""));
if (modulecontent.ModuleDefinitionName == moduledefinition.ModuleDefinitionName)
{
+ var settings = _settings.GetSettings(EntityNames.Module, moduleId);
+
+ if (modulecontent.Settings != null)
+ {
+ foreach (var kvp in modulecontent.Settings)
+ {
+ var setting = settings.FirstOrDefault(item => item.SettingName == kvp.Key);
+ if (setting == null)
+ {
+ setting = new Setting { EntityName = EntityNames.Module, EntityId = moduleId, SettingName = kvp.Key, SettingValue = kvp.Value, IsPrivate = false };
+ _settings.AddSetting(setting);
+ }
+ else
+ {
+ if (setting.SettingValue != kvp.Value)
+ {
+ setting.SettingValue = kvp.Value;
+ _settings.UpdateSetting(setting);
+ }
+ }
+ }
+ }
+
if (moduledefinition.ServerManagerType != "")
{
Type moduletype = Type.GetType(moduledefinition.ServerManagerType);
diff --git a/Oqtane.Server/Repository/RoleRepository.cs b/Oqtane.Server/Repository/RoleRepository.cs
index 05de9047..9239155a 100644
--- a/Oqtane.Server/Repository/RoleRepository.cs
+++ b/Oqtane.Server/Repository/RoleRepository.cs
@@ -2,6 +2,7 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Oqtane.Models;
+using Oqtane.Shared;
namespace Oqtane.Repository
{
@@ -19,10 +20,12 @@ namespace Oqtane.Repository
public class RoleRepository : IRoleRepository
{
private readonly IDbContextFactory _dbContextFactory;
+ private readonly ISettingRepository _settings;
- public RoleRepository(IDbContextFactory dbContextFactory)
+ public RoleRepository(IDbContextFactory dbContextFactory, ISettingRepository settings)
{
_dbContextFactory = dbContextFactory;
+ _settings = settings;
}
public IEnumerable GetRoles(int siteId)
@@ -95,6 +98,9 @@ namespace Oqtane.Repository
db.Permission.Remove(permission);
}
+ //remove settings for role
+ _settings.DeleteSettings(EntityNames.Role, roleId);
+
Role role = db.Role.Find(roleId);
db.Role.Remove(role);
db.SaveChanges();
diff --git a/Oqtane.Server/Repository/SiteRepository.cs b/Oqtane.Server/Repository/SiteRepository.cs
index bd84354e..7a94737c 100644
--- a/Oqtane.Server/Repository/SiteRepository.cs
+++ b/Oqtane.Server/Repository/SiteRepository.cs
@@ -168,7 +168,7 @@ namespace Oqtane.Repository
var attribute = (SiteMigrationAttribute)Attribute.GetCustomAttribute(type, typeof(SiteMigrationAttribute));
if (attribute.AliasName == "*" || attribute.AliasName == alias.Name)
{
- if (string.IsNullOrEmpty(site.Version) || Version.Parse(attribute.Version) > Version.Parse(site.Version))
+ if (string.IsNullOrEmpty(site.Version) || attribute.Version == "*" || Version.Parse(attribute.Version) > Version.Parse(site.Version))
{
try
{
@@ -176,14 +176,14 @@ namespace Oqtane.Repository
if (obj != null)
{
obj.Up(site, alias);
- _logger.Log(LogLevel.Information, "Site Migration", LogFunction.Other, "Site Migrated Successfully To Version {version} For {Alias}", version, alias.Name);
+ _logger.Log(LogLevel.Information, "Site Migration", LogFunction.Other, "Site Migrated Successfully For {Alias} And Version {version}", alias.Name, attribute.Version);
}
}
catch (Exception ex)
{
- _logger.Log(LogLevel.Error, "Site Migration", LogFunction.Other, ex, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version}", type, alias.Name, version);
+ _logger.Log(LogLevel.Error, "Site Migration", LogFunction.Other, ex, "An Error Occurred Executing Site Migration {Type} For {Alias} And Version {Version}", type, alias.Name, attribute.Version);
}
- if (string.IsNullOrEmpty(version) || Version.Parse(attribute.Version) > Version.Parse(version))
+ if (attribute.Version != "*" && (string.IsNullOrEmpty(version) || Version.Parse(attribute.Version) > Version.Parse(version)))
{
version = attribute.Version;
}
@@ -251,21 +251,21 @@ namespace Oqtane.Repository
_roleRepository.AddRole(new Role {SiteId = site.SiteId, Name = RoleNames.Admin, Description = RoleNames.Admin, IsAutoAssigned = false, IsSystem = true});
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "FirstName", Title = "First Name", Description = "Your First Or Given Name", Category = "Name", ViewOrder = 1, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "FirstName", Title = "First Name", Description = "Your First Or Given Name", Category = "Name", ViewOrder = 10, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "LastName", Title = "Last Name", Description = "Your Last Or Family Name", Category = "Name", ViewOrder = 2, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "LastName", Title = "Last Name", Description = "Your Last Or Family Name", Category = "Name", ViewOrder = 20, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "Street", Title = "Street", Description = "Street Or Building Address", Category = "Address", ViewOrder = 3, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "Street", Title = "Street", Description = "Street Or Building Address", Category = "Address", ViewOrder = 30, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "City", Title = "City", Description = "City", Category = "Address", ViewOrder = 4, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "City", Title = "City", Description = "City", Category = "Address", ViewOrder = 40, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "Region", Title = "Region", Description = "State Or Province", Category = "Address", ViewOrder = 5, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "Region", Title = "Region", Description = "State Or Province", Category = "Address", ViewOrder = 50, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "Country", Title = "Country", Description = "Country", Category = "Address", ViewOrder = 6, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "Country", Title = "Country", Description = "Country", Category = "Address", ViewOrder = 60, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "PostalCode", Title = "Postal Code", Description = "Postal Code Or Zip Code", Category = "Address", ViewOrder = 7, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "PostalCode", Title = "Postal Code", Description = "Postal Code Or Zip Code", Category = "Address", ViewOrder = 70, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
_profileRepository.AddProfile(new Profile
- { SiteId = site.SiteId, Name = "Phone", Title = "Phone Number", Description = "Phone Number", Category = "Contact", ViewOrder = 8, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
+ { SiteId = site.SiteId, Name = "Phone", Title = "Phone Number", Description = "Phone Number", Category = "Contact", ViewOrder = 80, MaxLength = 50, DefaultValue = "", IsRequired = false, IsPrivate = false, Options = "", Rows = 1 });
Folder folder = _folderRepository.AddFolder(new Folder
{
@@ -490,7 +490,27 @@ namespace Oqtane.Repository
}
else
{
- var module = _moduleRepository.AddModule(pageModule.Module);
+ Module module = null;
+ if (pageTemplateModule.FromPagePath != "")
+ {
+ // used for modules shared across pages
+ var pagePath = pageTemplateModule.FromPagePath;
+ pagePath = (pagePath.ToLower() == "home") ? "" : pagePath;
+ pagePath = (pagePath == "/") ? "" : pagePath;
+ if (pages.Any(item => item.Path.ToLower() == pagePath.ToLower()))
+ {
+ var pageId = pages.First(item => item.Path.ToLower() == pagePath.ToLower()).PageId;
+ if (pageModules.Any(item => item.PageId == pageId && item.Module.ModuleDefinitionName == pageTemplateModule.ModuleDefinitionName && item.Title.ToLower() == pageTemplateModule.Title.ToLower()))
+ {
+ module = pageModules.FirstOrDefault(item => item.PageId == pageId && item.Module.ModuleDefinitionName == pageTemplateModule.ModuleDefinitionName && item.Title.ToLower() == pageTemplateModule.Title.ToLower()).Module;
+ }
+ }
+ }
+ if (module == null)
+ {
+ module = _moduleRepository.AddModule(pageModule.Module);
+ }
+
pageModule.ModuleId = module.ModuleId;
pageModule.Module = null; // remove tracking
_pageModuleRepository.AddPageModule(pageModule);
diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs
deleted file mode 100644
index 979c57e4..00000000
--- a/Oqtane.Server/Startup.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-using System;
-using System.IO;
-using Microsoft.AspNetCore.Builder;
-using Microsoft.AspNetCore.Hosting;
-using Microsoft.Extensions.Configuration;
-using Microsoft.Extensions.DependencyInjection;
-using Oqtane.Extensions;
-using Oqtane.Infrastructure;
-using Oqtane.Shared;
-using Microsoft.AspNetCore.Cors.Infrastructure;
-
-namespace Oqtane
-{
- public class Startup
- {
- private readonly IConfigurationRoot _configuration;
- private readonly IWebHostEnvironment _environment;
-
- public Startup(IWebHostEnvironment environment)
- {
- AppDomain.CurrentDomain.SetData(Constants.DataDirectory, Path.Combine(environment.ContentRootPath, "Data"));
-
- var builder = new ConfigurationBuilder()
- .SetBasePath(environment.ContentRootPath)
- .AddJsonFile("appsettings.json", false, true)
- .AddJsonFile($"appsettings.{environment.EnvironmentName}.json", true, true)
- .AddEnvironmentVariables();
-
- _configuration = builder.Build();
- _environment = environment;
- }
-
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddOqtane(_configuration, _environment);
- }
-
- public void Configure(IApplicationBuilder app, IConfigurationRoot configuration, IWebHostEnvironment environment, ICorsService corsService, ICorsPolicyProvider corsPolicyProvider, ISyncManager sync)
- {
- app.UseOqtane(configuration, environment, corsService, corsPolicyProvider, sync);
- }
- }
-}
diff --git a/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css b/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css
index 086b246b..38c1a2c9 100644
--- a/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css
+++ b/Oqtane.Server/wwwroot/Modules/Oqtane.Modules.Admin.Login/Module.css
@@ -1,5 +1,5 @@
/* Login Module Custom Styles */
.Oqtane-Modules-Admin-Login {
- width: 200px;
+ width: 220px;
}
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Settings.razor b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Settings.razor
index 3c4cae69..624dee2a 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Settings.razor
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/Modules/[Owner].Module.[Module]/Settings.razor
@@ -35,8 +35,8 @@
{
try
{
- Dictionary settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
- SettingService.SetSetting(settings, "SettingName", _value);
+ var settings = await SettingService.GetModuleSettingsAsync(ModuleState.ModuleId);
+ settings = SettingService.SetSetting(settings, "SettingName", _value);
await SettingService.UpdateModuleSettingsAsync(settings, ModuleState.ModuleId);
}
catch (Exception ex)
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj
index 501cec31..607195bc 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Client/[Owner].Module.[Module].Client.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
1.0.0
[Owner]
[Owner]
@@ -13,11 +13,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj
index 211d1eee..32e1baca 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].Package.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
false
false
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec
index bf919a3d..7c719852 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Package/[Owner].Module.[Module].nuspec
@@ -27,11 +27,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj
index 3d047143..4e70b5b0 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Server/[Owner].Module.[Module].Server.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
true
1.0.0
[Owner].Module.[Module]
@@ -20,10 +20,10 @@
-
-
-
-
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/Models/[Module].cs b/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/Models/[Module].cs
index ddba10ad..7236fb25 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/Models/[Module].cs
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/Models/[Module].cs
@@ -1,4 +1,3 @@
-using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Oqtane.Models;
@@ -6,16 +5,11 @@ using Oqtane.Models;
namespace [Owner].Module.[Module].Models
{
[Table("[Owner][Module]")]
- public class [Module] : IAuditable
+ public class [Module] : ModelBase
{
[Key]
public int [Module]Id { get; set; }
public int ModuleId { get; set; }
public string Name { get; set; }
-
- public string CreatedBy { get; set; }
- public DateTime CreatedOn { get; set; }
- public string ModifiedBy { get; set; }
- public DateTime ModifiedOn { get; set; }
}
}
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj b/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj
index dd2e3d40..63175d61 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/Shared/[Owner].Module.[Module].Shared.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
1.0.0
[Owner].Module.[Module]
[Owner]
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].Module.[Module].sln b/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].Module.[Module].sln
deleted file mode 100644
index e8622beb..00000000
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].Module.[Module].sln
+++ /dev/null
@@ -1,47 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28621.142
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "..\[RootFolder]\Oqtane.Server\Oqtane.Server.csproj", "{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].Module.[Module].Client", "Client\[Owner].Module.[Module].Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].Module.[Module].Server", "Server\[Owner].Module.[Module].Server.csproj", "{04B05448-788F-433D-92C0-FED35122D45A}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].Module.[Module].Shared", "Shared\[Owner].Module.[Module].Shared.csproj", "{18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].Module.[Module].Package", "Package\[Owner].Module.[Module].Package.csproj", "{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {04B05448-788F-433D-92C0-FED35122D45A}.Release|Any CPU.Build.0 = Release|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {18D73F73-D7BE-4388-85BA-FBD9AC96FCA2}.Release|Any CPU.Build.0 = Release|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {1D016F15-46FE-4726-8DFD-2E4FD4DC7668}
- EndGlobalSection
-EndGlobal
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].Module.[Module].slnx b/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].Module.[Module].slnx
new file mode 100644
index 00000000..74f9bd8a
--- /dev/null
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/[Owner].Module.[Module].slnx
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/Modules/Templates/External/template.json b/Oqtane.Server/wwwroot/Modules/Templates/External/exteneral.module.template.json
similarity index 82%
rename from Oqtane.Server/wwwroot/Modules/Templates/External/template.json
rename to Oqtane.Server/wwwroot/Modules/Templates/External/exteneral.module.template.json
index 0579d8e6..06073057 100644
--- a/Oqtane.Server/wwwroot/Modules/Templates/External/template.json
+++ b/Oqtane.Server/wwwroot/Modules/Templates/External/exteneral.module.template.json
@@ -1,6 +1,6 @@
{
"Title": "Default Module Template",
"Type": "External",
- "Version": "5.2.0",
+ "Version": "10.0.0",
"Namespace": "[Owner].Module.[Module]"
}
diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj
index de8fd5cc..8333d87e 100644
--- a/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj
+++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Client/[Owner].Theme.[Theme].Client.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
1.0.0
[Owner]
[Owner]
@@ -14,9 +14,9 @@
-
-
-
+
+
+
diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj
index 0e454dec..0eb08fe0 100644
--- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj
+++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].Package.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
false
false
diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec
index 363ebaa9..f9fc035a 100644
--- a/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec
+++ b/Oqtane.Server/wwwroot/Themes/Templates/External/Package/[Owner].Theme.[Theme].nuspec
@@ -23,11 +23,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].Theme.[Theme].sln b/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].Theme.[Theme].sln
deleted file mode 100644
index cd9d50da..00000000
--- a/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].Theme.[Theme].sln
+++ /dev/null
@@ -1,35 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28621.142
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "..\[RootFolder]\Oqtane.Server\Oqtane.Server.csproj", "{3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].Theme.[Theme].Client", "Client\[Owner].Theme.[Theme].Client.csproj", "{AA8E58A1-CD09-4208-BF66-A8BB341FD669}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "[Owner].Theme.[Theme].Package", "Package\[Owner].Theme.[Theme].Package.csproj", "{C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3AB6FCC9-EFEB-4C0E-A2CF-8103914C5196}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {AA8E58A1-CD09-4208-BF66-A8BB341FD669}.Release|Any CPU.Build.0 = Release|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C5CE512D-CBB7-4545-AF0F-9B6591A0C3A7}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {1D016F15-46FE-4726-8DFD-2E4FD4DC7668}
- EndGlobalSection
-EndGlobal
diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].Theme.[Theme].slnx b/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].Theme.[Theme].slnx
new file mode 100644
index 00000000..1b1f9c26
--- /dev/null
+++ b/Oqtane.Server/wwwroot/Themes/Templates/External/[Owner].Theme.[Theme].slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/Oqtane.Server/wwwroot/Themes/Templates/External/template.json b/Oqtane.Server/wwwroot/Themes/Templates/External/exteneral.theme.template.json
similarity index 81%
rename from Oqtane.Server/wwwroot/Themes/Templates/External/template.json
rename to Oqtane.Server/wwwroot/Themes/Templates/External/exteneral.theme.template.json
index bf1ec318..1c2e132e 100644
--- a/Oqtane.Server/wwwroot/Themes/Templates/External/template.json
+++ b/Oqtane.Server/wwwroot/Themes/Templates/External/exteneral.theme.template.json
@@ -1,6 +1,6 @@
{
"Title": "Default Theme Template",
"Type": "External",
- "Version": "5.2.0",
+ "Version": "10.0.0",
"Namespace": "[Owner].Theme.[Theme]"
}
diff --git a/Oqtane.Server/wwwroot/css/app.css b/Oqtane.Server/wwwroot/css/app.css
index 13c7d539..013ef348 100644
--- a/Oqtane.Server/wwwroot/css/app.css
+++ b/Oqtane.Server/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.Server/wwwroot/js/interop.js b/Oqtane.Server/wwwroot/js/interop.js
index fecc4c99..944f9812 100644
--- a/Oqtane.Server/wwwroot/js/interop.js
+++ b/Oqtane.Server/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);
}
};
diff --git a/Oqtane.Shared/Models/ModuleContent.cs b/Oqtane.Shared/Models/ModuleContent.cs
index c1f5abf7..8c1269f3 100644
--- a/Oqtane.Shared/Models/ModuleContent.cs
+++ b/Oqtane.Shared/Models/ModuleContent.cs
@@ -1,3 +1,5 @@
+using System.Collections.Generic;
+
namespace Oqtane.Models
{
///
@@ -15,6 +17,11 @@ namespace Oqtane.Models
///
public string Version { get; set; }
+ ///
+ /// Serialized Settings of the module for import/export.
+ ///
+ public Dictionary Settings { get; set; }
+
///
/// Serialized Content of the module for import/export.
///
diff --git a/Oqtane.Shared/Models/SiteTemplate.cs b/Oqtane.Shared/Models/SiteTemplate.cs
index 42cbffee..9cbade09 100644
--- a/Oqtane.Shared/Models/SiteTemplate.cs
+++ b/Oqtane.Shared/Models/SiteTemplate.cs
@@ -105,6 +105,7 @@ namespace Oqtane.Models
};
Settings = new List();
Content = "";
+ FromPagePath = "";
}
public string ModuleDefinitionName { get; set; }
@@ -118,6 +119,7 @@ namespace Oqtane.Models
public List PermissionList { get; set; }
public List Settings { get; set; }
public string Content { get; set; }
+ public string FromPagePath { get; set; } // for modules shared across pages
[Obsolete("The ModulePermissions property is deprecated. Use PermissionList instead", false)]
public string ModulePermissions
diff --git a/Oqtane.Shared/Models/UserLogin.cs b/Oqtane.Shared/Models/UserLogin.cs
new file mode 100644
index 00000000..d22311df
--- /dev/null
+++ b/Oqtane.Shared/Models/UserLogin.cs
@@ -0,0 +1,23 @@
+namespace Oqtane.Models
+{
+ ///
+ /// Passkey properties
+ ///
+ public class UserLogin
+ {
+ ///
+ /// the login provider for this login
+ ///
+ public string Provider { get; set; }
+
+ ///
+ /// The key for this login
+ ///
+ public string Key { get; set; }
+
+ ///
+ /// The friendly name for the login provider
+ ///
+ public string Name { get; set; }
+ }
+}
diff --git a/Oqtane.Shared/Models/UserPasskey.cs b/Oqtane.Shared/Models/UserPasskey.cs
new file mode 100644
index 00000000..9ee69221
--- /dev/null
+++ b/Oqtane.Shared/Models/UserPasskey.cs
@@ -0,0 +1,28 @@
+namespace Oqtane.Models
+{
+ ///
+ /// Passkey properties
+ ///
+ public class UserPasskey
+ {
+ ///
+ /// the credential ID for this passkey
+ ///
+ public byte[] CredentialId { get; set; }
+
+ ///
+ /// The friendly name of the passkey
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// The User which this passkey belongs to
+ ///
+ public int UserId { get; set; }
+
+ ///
+ /// A serialized JSON object from the navigator.credentials.create() JavaScript API - only populated during Add
+ ///
+ public string CredentialJson { get; set; }
+ }
+}
diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj
index 78b6ba46..c1bdff46 100644
--- a/Oqtane.Shared/Oqtane.Shared.csproj
+++ b/Oqtane.Shared/Oqtane.Shared.csproj
@@ -5,12 +5,9 @@
-
-
-
+
+
-
-
diff --git a/Oqtane.Shared/Shared/Constants.cs b/Oqtane.Shared/Shared/Constants.cs
index 9fc43717..d6740658 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 = "6.2.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";
+ public static readonly string Version = "10.0.0";
+ 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";
public const string PackageId = "Oqtane.Framework";
public const string ClientId = "Oqtane.Client";
public const string UpdaterPackageId = "Oqtane.Updater";
@@ -89,10 +89,10 @@ namespace Oqtane.Shared
public const string DefaultTextEditor = "Oqtane.Modules.Controls.RadzenTextEditor, Oqtane.Client";
// obtained from https://cdnjs.com/libraries/bootstrap
- public const string BootstrapScriptUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/js/bootstrap.bundle.min.js";
- public const string BootstrapScriptIntegrity = "sha512-Tc0i+vRogmX4NN7tuLbQfBxa8JkfUSAxSFVzmU31nVdHyiHElPPy2cWfFacmCJKw0VqovrzKhdd2TSTMdAxp2g==";
- public const string BootstrapStylesheetUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.7/css/bootstrap.min.css";
- public const string BootstrapStylesheetIntegrity = "sha512-fw7f+TcMjTb7bpbLJZlP8g2Y4XcCyFZW8uy8HsRZsH/SwbMw0plKHFHr99DN3l04VsYNwvzicUX/6qurvIxbxw==";
+ public const string BootstrapScriptUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/js/bootstrap.bundle.min.js";
+ public const string BootstrapScriptIntegrity = "sha512-HvOjJrdwNpDbkGJIG2ZNqDlVqMo77qbs4Me4cah0HoDrfhrbA+8SBlZn1KrvAQw7cILLPFJvdwIgphzQmMm+Pw==";
+ public const string BootstrapStylesheetUrl = "https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.8/css/bootstrap.min.css";
+ public const string BootstrapStylesheetIntegrity = "sha512-2bBQCjcnw658Lho4nlXJcc6WkV/UxpE/sAokbXPxQNGqmNdQrWqtw26Ns9kFF/yG792pKR1Sx8/Y1Lf1XN4GKA==";
public const string CookieConsentCookieName = "Oqtane.CookieConsent";
public const string CookieConsentCookieValue = "yes";
diff --git a/Oqtane.Updater.sln b/Oqtane.Updater.sln
deleted file mode 100644
index f79c8fed..00000000
--- a/Oqtane.Updater.sln
+++ /dev/null
@@ -1,38 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.28822.285
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{77EECA8C-B58E-469E-B8C5-D543AFC9A654}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .gitignore = .gitignore
- README.md = README.md
- EndProjectSection
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Updater", "Oqtane.Updater\Oqtane.Updater.csproj", "{2E8C6889-37CF-4C8D-88B1-505547F25098}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Shared", "Oqtane.Shared\Oqtane.Shared.csproj", "{E2512C17-291F-460A-A6D1-741C301DA184}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {2E8C6889-37CF-4C8D-88B1-505547F25098}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {2E8C6889-37CF-4C8D-88B1-505547F25098}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {2E8C6889-37CF-4C8D-88B1-505547F25098}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {2E8C6889-37CF-4C8D-88B1-505547F25098}.Release|Any CPU.Build.0 = Release|Any CPU
- {E2512C17-291F-460A-A6D1-741C301DA184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E2512C17-291F-460A-A6D1-741C301DA184}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E2512C17-291F-460A-A6D1-741C301DA184}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E2512C17-291F-460A-A6D1-741C301DA184}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {1FB11796-35DE-4AED-9A52-17733557FCC4}
- EndGlobalSection
-EndGlobal
diff --git a/Oqtane.Updater.slnx b/Oqtane.Updater.slnx
new file mode 100644
index 00000000..56bc84b7
--- /dev/null
+++ b/Oqtane.Updater.slnx
@@ -0,0 +1,3 @@
+
+
+
diff --git a/Oqtane.sln b/Oqtane.sln
deleted file mode 100644
index f6d07ccb..00000000
--- a/Oqtane.sln
+++ /dev/null
@@ -1,44 +0,0 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.3.32611.2
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Server", "Oqtane.Server\Oqtane.Server.csproj", "{083BB22D-DF24-43A2-95E5-8F385CCB3318}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Client", "Oqtane.Client\Oqtane.Client.csproj", "{FD15B24A-7F6A-4830-9CA2-9C621771C330}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Oqtane.Shared", "Oqtane.Shared\Oqtane.Shared.csproj", "{19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{77EECA8C-B58E-469E-B8C5-D543AFC9A654}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- .gitignore = .gitignore
- README.md = README.md
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {083BB22D-DF24-43A2-95E5-8F385CCB3318}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {083BB22D-DF24-43A2-95E5-8F385CCB3318}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {083BB22D-DF24-43A2-95E5-8F385CCB3318}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {083BB22D-DF24-43A2-95E5-8F385CCB3318}.Release|Any CPU.Build.0 = Release|Any CPU
- {FD15B24A-7F6A-4830-9CA2-9C621771C330}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {FD15B24A-7F6A-4830-9CA2-9C621771C330}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {FD15B24A-7F6A-4830-9CA2-9C621771C330}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {FD15B24A-7F6A-4830-9CA2-9C621771C330}.Release|Any CPU.Build.0 = Release|Any CPU
- {19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {19D67A9D-3F2E-41BD-80E6-0B50CA83C3AE}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {1FB11796-35DE-4AED-9A52-17733557FCC4}
- EndGlobalSection
-EndGlobal
diff --git a/Oqtane.slnx b/Oqtane.slnx
new file mode 100644
index 00000000..13e0328f
--- /dev/null
+++ b/Oqtane.slnx
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 024dd4cb..6d7709a3 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
-[6.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1) was released on September 29, 2025 and is a maintenance release including 65 pull requests by 6 different contributors, pushing the total number of project commits all-time over 7100. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
+[10.0.0](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0) was released on November 14, 2025 and is a maintenance release including 77 pull requests by 6 different contributors, pushing the total number of project commits all-time over 7300. The Oqtane framework continues to evolve at a rapid pace to meet the needs of .NET developers.
# Try It Now!
@@ -22,10 +22,12 @@ Microsoft's Public Cloud (requires an Azure account)
A free ASP.NET hosting account. No hidden fees. No credit card required.
[](https://www.monsterasp.net/)
-# Getting Started (Version 6.2+)
+# Getting Started (Version 10.0.0+)
**Installing using the Oqtane Application Template:**
+(Note that "MyCompany.MyProject" can be replaced with your own unique company and project name)
+
```
dotnet new install Oqtane.Application.Template
dotnet new oqtane-app -o MyCompany.MyProject
@@ -35,20 +37,18 @@ cd Server
dotnet run
```
-- Browse to http://localhost:5001 to run the application (an Installation Wizard screen will be displayed the first time you run the application)
-- To develop/debug the application, open the MyCompany.MyProject.sln file in the root folder and hit F5
+- Browse to the Url specified to run the application (an Installation Wizard screen will be displayed the first time you run the application)
+- To develop/debug the application in an IDE, open the *.sln file in the root folder and hit F5
**Installing using source code from the Dev/Master branch:**
-- Install Latest **[.NET 9.0 SDK](https://dotnet.microsoft.com/en-us/download)**.
+- Install Latest **[.NET 10.0 SDK](https://dotnet.microsoft.com/en-us/download)**.
-- Install the latest edition (v17.12 or higher) of [Visual Studio 2022](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
+- Install the latest edition of [Visual Studio 2026](https://visualstudio.microsoft.com/downloads) with the **ASP.NET and web development** workload enabled. Oqtane works with ALL editions of Visual Studio from Community to Enterprise. If you wish to use LocalDB for development ( not a requirement as Oqtane supports SQLite, mySQL, and PostgreSQL ) you must also install the **Data storage and processing**.
- Clone (or download) the Oqtane Master or Dev branch source code to your local system.
-- Open the **Oqtane.sln** solution file.
-
-- Make sure you specify Oqtane.Server as the Startup Project.
+- Open the **Oqtane.slnx** solution file (make sure you specify Oqtane.Server as the Startup Project)
- Run the application... an Installation Wizard screen will be displayed which will allow you to configure your preferred database and create a host user account.
@@ -104,6 +104,10 @@ 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.0](https://github.com/oqtane/oqtane.framework/releases/tag/v10.0.0) (Nov 14, 2025)
+- [x] Migration to .NET 10
+- [x] Passkey Authentication
+
[6.2.1](https://github.com/oqtane/oqtane.framework/releases/tag/v6.2.1) (Sep 29, 2025)
- [x] Stabilization improvements
diff --git a/azuredeploy.json b/azuredeploy.json
index 49a1e1f2..4b3df91b 100644
--- a/azuredeploy.json
+++ b/azuredeploy.json
@@ -205,7 +205,7 @@
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('hostingPlanName'))]",
"siteConfig": {
"webSocketsEnabled": true,
- "netFrameworkVersion": "v9.0"
+ "netFrameworkVersion": "v10.0"
}
},
"dependsOn": [
@@ -220,7 +220,7 @@
"apiVersion": "2024-04-01",
"name": "[concat(parameters('BlazorWebsiteName'), '/ZipDeploy')]",
"properties": {
- "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v6.2.1/Oqtane.Framework.6.2.1.Install.zip"
+ "packageUri": "https://github.com/oqtane/oqtane.framework/releases/download/v10.0.0/Oqtane.Framework.10.0.0.Install.zip"
},
"dependsOn": [
"[resourceId('Microsoft.Web/sites', parameters('BlazorWebsiteName'))]"
diff --git a/oqtane.png b/oqtane.png
index d62a37dc..19e9e4d6 100644
Binary files a/oqtane.png and b/oqtane.png differ