diff --git a/BLAZAM/BLAZAM.csproj b/BLAZAM/BLAZAM.csproj index 51f23921..63e64f34 100644 --- a/BLAZAM/BLAZAM.csproj +++ b/BLAZAM/BLAZAM.csproj @@ -6,7 +6,7 @@ enable false 1.2.1 - 2024.11.30.2133 + 2024.12.01.1549 false BLAZAM True diff --git a/BLAZAM/ProgramHelpers.cs b/BLAZAM/ProgramHelpers.cs index a796bd9b..5fecbe24 100644 --- a/BLAZAM/ProgramHelpers.cs +++ b/BLAZAM/ProgramHelpers.cs @@ -176,11 +176,12 @@ public static WebApplicationBuilder InjectServices(this WebApplicationBuilder bu options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, // Important: Validate the signing key - IssuerSigningKey = ApplicationInfo.tokenKey, + IssuerSigningKey = new SymmetricSecurityKey (Encryption.Instance.Key), ValidateIssuer = false, ValidateAudience = false, ValidateActor = false, - ValidateLifetime = true + ValidateLifetime = true, + }; options.Events = new JwtAuthenticationEventsHandler( builder.Services.BuildServiceProvider().GetRequiredService(), @@ -234,12 +235,12 @@ public static WebApplicationBuilder InjectServices(this WebApplicationBuilder bu .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Set lifetime to five minutes .AddPolicyHandler(GetWebhookRetryPolicy()); - builder.Services.AddHttpClient(HttpClientNames.WebHookHttpClientNoSSLCheckName) - .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Set lifetime to five minutes - .AddPolicyHandler(GetWebhookRetryPolicy()).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler - { - ServerCertificateCustomValidationCallback = (m, c, ch, e) => true - }); + builder.Services.AddHttpClient(HttpClientNames.WebHookHttpClientNoSSLCheckName) + .SetHandlerLifetime(TimeSpan.FromMinutes(5)) //Set lifetime to five minutes + .AddPolicyHandler(GetWebhookRetryPolicy()).ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (m, c, ch, e) => true + }); //Also keeping this here for a possible future API, though this would be for internal use //builder.Services.AddTransient(); @@ -351,32 +352,47 @@ public static WebApplicationBuilder InjectServices(this WebApplicationBuilder bu builder.Services.AddSwaggerGen(c => { - c.SwaggerDoc("v1", new OpenApiInfo { Title = "Blazam API", - Version = "v1" , - Description="The official Blazam API documentation. Authorization is required for API access.", - License=new OpenApiLicense() { Name="MIT License", Url= new Uri("https://github.com/Blazam-App/BLAZAM/blob/v1-Dev/LICENSE") }, - Contact=new() { Email = "support@blazam.org", - Name="Blazam Support", - Url=new("https://blazam.org/support") }, - TermsOfService=new Uri("https://blazam.org/tos") - }); - - // Add descriptions using XML comments - var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; - c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); - - // Configure Swagger to use JWT Bearer authorization - //c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme - //{ - // Description = "JWT Authorization header using the Bearer scheme.Example: \"Authorization: Bearer {token}\"", - // Name = "Authorization", - // In = ParameterLocation.Header, - // Type = SecuritySchemeType.Http, - // Scheme = "bearer", - // BearerFormat = "JWT" - //}); + c.SwaggerDoc("v1", new OpenApiInfo + { + Title = "Blazam API", + Version = "v1", + Description = "The official Blazam API documentation. Authorization is required for API access.", + License = new OpenApiLicense() { Name = "MIT License", Url = new Uri("https://github.com/Blazam-App/BLAZAM/blob/v1-Dev/LICENSE") }, + Contact = new() + { + Email = "support@blazam.org", + Name = "Blazam Support", + Url = new("https://blazam.org/support") + }, + TermsOfService = new Uri("https://blazam.org/tos") }); + // Add descriptions using XML comments + var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"; + c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename)); + + // Configure Swagger to use JWT Bearer authorization + var jwtSecurityScheme = new OpenApiSecurityScheme + { + Description = "JWT Authorization header using the Bearer scheme.Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.Http, + Scheme = JwtBearerDefaults.AuthenticationScheme, + BearerFormat = "JWT", + Reference = new OpenApiReference + { + Id = JwtBearerDefaults.AuthenticationScheme, + Type = ReferenceType.SecurityScheme + } + }; + + c.AddSecurityDefinition(jwtSecurityScheme.Reference.Id, jwtSecurityScheme); + c.AddSecurityRequirement(new OpenApiSecurityRequirement() { + { jwtSecurityScheme,Array.Empty() } + }); + }); + builder.Host.UseWindowsService(); @@ -384,84 +400,84 @@ public static WebApplicationBuilder InjectServices(this WebApplicationBuilder bu return builder; } - public static void PreRun(this WebApplication application) + public static void PreRun(this WebApplication application) + { + //Setup Seq logging if allowed by admin + try { - //Setup Seq logging if allowed by admin - try + var context = Program.AppInstance.Services.GetRequiredService().CreateDbContext(); + if (context != null && context.AppSettings.FirstOrDefault()?.SendLogsToDeveloper != null) { - var context = Program.AppInstance.Services.GetRequiredService().CreateDbContext(); - if (context != null && context.AppSettings.FirstOrDefault()?.SendLogsToDeveloper != null) - { - Loggers.SendToSeqServer = context.AppSettings.FirstOrDefault().SendLogsToDeveloper; - - } + Loggers.SendToSeqServer = context.AppSettings.FirstOrDefault().SendLogsToDeveloper; } - catch (Exception ex) - { - Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); - } - PreloadServices(); } - static IAsyncPolicy GetWebhookRetryPolicy() + catch (Exception ex) { - var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 5); + Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + } + PreloadServices(); + + } + static IAsyncPolicy GetWebhookRetryPolicy() + { + var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 5); - return HttpPolicyExtensions - .HandleTransientHttpError() - .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) - .WaitAndRetryAsync(delay); + return HttpPolicyExtensions + .HandleTransientHttpError() + .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) + .WaitAndRetryAsync(delay); + } + private static void PreloadServices() + { + try + { + var context = Program.AppInstance.Services.GetRequiredService(); } - private static void PreloadServices() + catch (Exception ex) { - try - { - var context = Program.AppInstance.Services.GetRequiredService(); - } - catch (Exception ex) + Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + } + try + { + if (ApplicationInfo.installationCompleted) { - Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + var context = Program.AppInstance.Services.GetRequiredService(); } - try - { - if (ApplicationInfo.installationCompleted) - { - var context = Program.AppInstance.Services.GetRequiredService(); - } - } - catch (Exception ex) + } + catch (Exception ex) + { + Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + } + try + { + if (ApplicationInfo.installationCompleted) { - Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + var context = Program.AppInstance.Services.GetRequiredService(); + context.Initialize(); } - try - { - if (ApplicationInfo.installationCompleted) - { - var context = Program.AppInstance.Services.GetRequiredService(); - context.Initialize(); - } - } - catch (Exception ex) - { - Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); - } - try + } + catch (Exception ex) + { + Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + } + try + { + if (ApplicationInfo.installationCompleted) { - if (ApplicationInfo.installationCompleted) - { - var context = Program.AppInstance.Services.GetRequiredService(); - - } + var context = Program.AppInstance.Services.GetRequiredService(); } - catch (Exception ex) - { - Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); - } } + catch (Exception ex) + { + Loggers.SystemLogger.Error(ex.Message + " {@Error}", ex); + } + } } +} diff --git a/BLAZAMCommon/Data/ApplicationInfo.cs b/BLAZAMCommon/Data/ApplicationInfo.cs index 10ad5712..4db9320d 100644 --- a/BLAZAMCommon/Data/ApplicationInfo.cs +++ b/BLAZAMCommon/Data/ApplicationInfo.cs @@ -78,19 +78,7 @@ public class ApplicationInfo /// public static IServiceProvider services; - /// - /// A symmetric key version of the encryption key - /// for use with API signing - /// - public static SymmetricSecurityKey tokenKey { get { - - var keyString = configuration.GetValue("EncryptionKey"); - var keyBytes = Encoding.ASCII.GetBytes(keyString); - return new SymmetricSecurityKey(keyBytes); - } } - - - + /// /// The running Blazam version /// @@ -173,11 +161,7 @@ public static bool installationCompleted /// Unique ID for this machine /// public Guid InstallationId { get => installationId; set => installationId = value; } - /// - /// A symmetric key version of the encryption key - /// for use with API signing - /// - public SymmetricSecurityKey TokenKey { get => tokenKey; } + /// /// Use only for UnitTests diff --git a/BLAZAMGui/Layouts/AppUserApiModalContent.razor b/BLAZAMGui/Layouts/AppUserApiModalContent.razor index bd8fd032..d4fa8cf3 100644 --- a/BLAZAMGui/Layouts/AppUserApiModalContent.razor +++ b/BLAZAMGui/Layouts/AppUserApiModalContent.razor @@ -1,6 +1,12 @@ @inherits AppModalContent - @AppLocalization["Click to view API documentation"]. + + @AppLocalization["Click to view API documentation"]. + @code{ diff --git a/BLAZAMGui/UI/Outputs/UserApiTokenDataGrid.razor b/BLAZAMGui/UI/Outputs/UserApiTokenDataGrid.razor index 9036acad..ffcf401d 100644 --- a/BLAZAMGui/UI/Outputs/UserApiTokenDataGrid.razor +++ b/BLAZAMGui/UI/Outputs/UserApiTokenDataGrid.razor @@ -23,7 +23,11 @@ @AppLocalization["Forever"] - @AppLocalization["Generate New API Token"] + + @AppLocalization["Generate New API Token"] + @code { List _tokens = new(); diff --git a/BLAZAMServices/JwtTokenService.cs b/BLAZAMServices/JwtTokenService.cs index 9f1a3a06..abd284f1 100644 --- a/BLAZAMServices/JwtTokenService.cs +++ b/BLAZAMServices/JwtTokenService.cs @@ -18,12 +18,14 @@ namespace BLAZAM.Services { public class JwtTokenService { + private readonly IEncryptionService _encryptionService; private readonly ICurrentUserStateService _currentUserStateService; private readonly ApplicationInfo _applicationInfo; - public JwtTokenService(ApplicationInfo applicationInfo, ICurrentUserStateService currentUserStateService) + public JwtTokenService(IEncryptionService encryptionService, ApplicationInfo applicationInfo, ICurrentUserStateService currentUserStateService) { + _encryptionService = encryptionService; _currentUserStateService = currentUserStateService; _applicationInfo = applicationInfo; } @@ -51,7 +53,7 @@ public string GenerateJwtToken(TimeSpan? lifetime=null) IssuedAt = DateTime.UtcNow, Issuer = DatabaseCache.ApplicationSettings.AppName, Expires = (DateTime.UtcNow+lifetime.Value), // Set expiration - SigningCredentials = new SigningCredentials(_applicationInfo.TokenKey, SecurityAlgorithms.HmacSha256Signature) + SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(Encryption.Instance.Key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); var jwtToken = tokenHandler.WriteToken(token); @@ -72,7 +74,7 @@ public ClaimsPrincipal DecodeJwtToken(string tokenHandler.ValidateToken(token, new TokenValidationParameters { ValidateIssuerSigningKey = true, - IssuerSigningKey = _applicationInfo.TokenKey, + IssuerSigningKey = new SymmetricSecurityKey(Encryption.Instance.Key), ValidateIssuer = false, ValidateAudience = false, ClockSkew = TimeSpan.Zero // Set clock skew to zero diff --git a/PlaywrightTests/UnitTest1.cs b/PlaywrightTests/UnitTest1.cs index 15c7d4a6..bd7f7aad 100644 --- a/PlaywrightTests/UnitTest1.cs +++ b/PlaywrightTests/UnitTest1.cs @@ -36,17 +36,17 @@ public async Task MainMenuTest() await OpenRecycleBin(); - await OpenConfigureSubMenu(); + //await OpenConfigureSubMenu(); - await OpenSettingsPages(); + //await OpenSettingsPages(); - await OpenManageNotifications(); + //await OpenManageNotifications(); - await OpenPermissions(); + //await OpenPermissions(); - await OpenFields(); + //await OpenFields(); - await OpenTemplates(); + //await OpenTemplates(); // Expects the URL to contain intro.