diff --git a/src/Managing.Api/Managing.Api.csproj b/src/Managing.Api/Managing.Api.csproj index afc1e2cc..4dd45482 100644 --- a/src/Managing.Api/Managing.Api.csproj +++ b/src/Managing.Api/Managing.Api.csproj @@ -39,7 +39,6 @@ - diff --git a/src/Managing.Api/Program.cs b/src/Managing.Api/Program.cs index ad0bbc0c..9c9b06ba 100644 --- a/src/Managing.Api/Program.cs +++ b/src/Managing.Api/Program.cs @@ -70,8 +70,6 @@ SentrySdk.Init(options => options.Environment = builder.Environment.EnvironmentName; }); -// Add Service Defaults - using extension methods directly -builder.Services.AddServiceDiscovery(); builder.Services.AddHealthChecks() .AddCheck("self", () => HealthCheckResult.Healthy(), ["api"]); @@ -167,7 +165,9 @@ builder.Host.UseSerilog((hostBuilder, loggerConfiguration) => }; loggerConfiguration - .MinimumLevel.Override("Microsoft.EntityFrameworkCore.Database.Command", LogEventLevel.Warning) // Filter out EF Core SQL query logs + .MinimumLevel + .Override("Microsoft.EntityFrameworkCore.Database.Command", + LogEventLevel.Warning) // Filter out EF Core SQL query logs .WriteTo.Console() .WriteTo.Elasticsearch(es); }); @@ -225,9 +225,9 @@ var validAudiences = builder.Configuration.GetSection("Authentication:Schemes:Be .Get() ?? Array.Empty(); // Determine if validation should be enabled (enable in production, allow override via config) -var enableIssuerValidation = builder.Configuration.GetValue("Jwt:ValidateIssuer", +var enableIssuerValidation = builder.Configuration.GetValue("Jwt:ValidateIssuer", !builder.Environment.IsDevelopment()); -var enableAudienceValidation = builder.Configuration.GetValue("Jwt:ValidateAudience", +var enableAudienceValidation = builder.Configuration.GetValue("Jwt:ValidateAudience", !builder.Environment.IsDevelopment()); // Configure clock skew (tolerance for time differences between servers) @@ -268,7 +268,7 @@ builder.Services context.Token = null; return Task.CompletedTask; } - + // Handle tokens sent without "Bearer " prefix for authenticated endpoints // The standard middleware expects "Bearer " but some clients send just the token if (string.IsNullOrEmpty(context.Token)) @@ -284,7 +284,7 @@ builder.Services // Otherwise, let the default middleware extract it (it will strip "Bearer " automatically) } } - + // If you want to get the token from a custom header or query string // var accessToken = context.Request.Query["access_token"]; // if (!string.IsNullOrEmpty(accessToken) && @@ -298,30 +298,30 @@ builder.Services { var logger = context.HttpContext.RequestServices .GetService>(); - + // Check if the endpoint allows anonymous access var endpoint = context.HttpContext.GetEndpoint(); var allowAnonymous = endpoint?.Metadata.GetMetadata() != null; - + // For anonymous endpoints with malformed tokens, skip authentication instead of failing if (allowAnonymous && context.Exception is SecurityTokenMalformedException) { - logger?.LogDebug("Skipping malformed token validation for anonymous endpoint: {Path}", + logger?.LogDebug("Skipping malformed token validation for anonymous endpoint: {Path}", context.Request.Path); context.NoResult(); // Skip authentication, don't fail return Task.CompletedTask; } - + if (context.Exception is SecurityTokenExpiredException) { context.Response.Headers["Token-Expired"] = "true"; - logger?.LogWarning("JWT token expired for request: {Path}", + logger?.LogWarning("JWT token expired for request: {Path}", context.Request.Path); } else { - logger?.LogError(context.Exception, - "JWT authentication failed for request: {Path}", + logger?.LogError(context.Exception, + "JWT authentication failed for request: {Path}", context.Request.Path); } @@ -332,12 +332,12 @@ builder.Services { var logger = context.HttpContext.RequestServices .GetService>(); - + try { var userService = context.HttpContext.RequestServices .GetRequiredService(); - + // JWT token contains 'address' claim (not NameIdentifier) var address = context.Principal.FindFirst("address")?.Value; @@ -354,7 +354,7 @@ builder.Services else { logger?.LogWarning( - "JWT token validated but user not found for address: {Address}", + "JWT token validated but user not found for address: {Address}", address); context.Fail("User not found"); } @@ -367,7 +367,7 @@ builder.Services } catch (Exception ex) { - logger?.LogError(ex, + logger?.LogError(ex, "Error during JWT token validation - user lookup failed"); context.Fail("Authentication failed: user lookup error"); } @@ -482,19 +482,19 @@ app.Use(async (context, next) => context.Response.Headers.Append("X-XSS-Protection", "1; mode=block"); context.Response.Headers.Append("Referrer-Policy", "strict-origin-when-cross-origin"); context.Response.Headers.Append("Permissions-Policy", "geolocation=(), microphone=(), camera=()"); - + // Content Security Policy - only for non-Swagger endpoints - if (!context.Request.Path.StartsWithSegments("/swagger") && + if (!context.Request.Path.StartsWithSegments("/swagger") && !context.Request.Path.StartsWithSegments("/health") && !context.Request.Path.StartsWithSegments("/alive")) { - context.Response.Headers.Append("Content-Security-Policy", + context.Response.Headers.Append("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:;"); } - + // Remove server header (optional - Kestrel can be configured separately) context.Response.Headers.Remove("Server"); - + await next(); }); diff --git a/src/Managing.Nswag/Program.cs b/src/Managing.Nswag/Program.cs index 3796ba53..dbbef41b 100644 --- a/src/Managing.Nswag/Program.cs +++ b/src/Managing.Nswag/Program.cs @@ -29,6 +29,9 @@ Directory.CreateDirectory(targetWebAppDirectory); // Ensure the directory exists var targetWeb3ProxyDirectory = Path.Combine(solutionDirectory, "src", "Managing.Web3Proxy", "src", "generated"); Directory.CreateDirectory(targetWeb3ProxyDirectory); +var targetKaigenWebDirectory = "/Users/oda/Desktop/Projects/kaigen-web/webapp/src/generated"; +Directory.CreateDirectory(targetKaigenWebDirectory); + var settings = new TypeScriptClientGeneratorSettings { ClassName = "{controller}Client", @@ -53,13 +56,38 @@ var settings = new TypeScriptClientGeneratorSettings var generatorApiClient = new TypeScriptClientGenerator(document, settings); var codeApiClient = generatorApiClient.GenerateFile(); -// Add the necessary imports after the auto-generated comment +// Settings for Kaigen web project using Axios +var settingsAxios = new TypeScriptClientGeneratorSettings +{ + ClassName = "{controller}Client", + ClientBaseClass = "AuthorizedApiBase", + ConfigurationClass = "IConfig", + GenerateDtoTypes = true, + UseTransformOptionsMethod = true, + TypeScriptGeneratorSettings = + { + EnumStyle = TypeScriptEnumStyle.Enum, + DateTimeType = TypeScriptDateTimeType.Date, + NullValue = TypeScriptNullValue.Null, + TypeStyle = TypeScriptTypeStyle.Interface, + GenerateDefaultValues = true, + MarkOptionalProperties = true, + TypeScriptVersion = 4.3m + }, + OperationNameGenerator = new MultipleClientsFromFirstTagAndOperationIdGenerator(), + Template = TypeScriptTemplate.Axios, +}; + +var generatorApiClientAxios = new TypeScriptClientGenerator(document, settingsAxios); +var codeApiClientAxios = generatorApiClientAxios.GenerateFile(); + +// Add the necessary imports after the auto-generated comment for Fetch version var requiredImports = @" import AuthorizedApiBase from ""./AuthorizedApiBase""; import IConfig from ""./IConfig""; "; -// Find the end of the auto-generated comment and insert imports +// Find the end of the auto-generated comment and insert imports for Fetch version var autoGeneratedEndIndex = codeApiClient.IndexOf("//----------------------"); if (autoGeneratedEndIndex != -1) { @@ -72,7 +100,21 @@ if (autoGeneratedEndIndex != -1) } } +// Add the necessary imports for Axios version as well +var autoGeneratedEndIndexAxios = codeApiClientAxios.IndexOf("//----------------------"); +if (autoGeneratedEndIndexAxios != -1) +{ + // Find the second occurrence (end of the comment block) + autoGeneratedEndIndexAxios = codeApiClientAxios.IndexOf("//----------------------", autoGeneratedEndIndexAxios + 1); + if (autoGeneratedEndIndexAxios != -1) + { + autoGeneratedEndIndexAxios = codeApiClientAxios.IndexOf("\n", autoGeneratedEndIndexAxios) + 1; + codeApiClientAxios = codeApiClientAxios.Insert(autoGeneratedEndIndexAxios, requiredImports); + } +} + File.WriteAllText(Path.Combine(targetWebAppDirectory, "ManagingApi.ts"), codeApiClient); +File.WriteAllText(Path.Combine(targetKaigenWebDirectory, "ManagingApi.ts"), codeApiClientAxios); var settingsTypes = new TypeScriptClientGeneratorSettings { @@ -97,4 +139,5 @@ var generatorTypes = new TypeScriptClientGenerator(document, settingsTypes); var codeTypes = generatorTypes.GenerateFile(); File.WriteAllText(Path.Combine(targetWebAppDirectory, "ManagingApiTypes.ts"), codeTypes); -File.WriteAllText(Path.Combine(targetWeb3ProxyDirectory, "ManagingApiTypes.ts"), codeTypes); \ No newline at end of file +File.WriteAllText(Path.Combine(targetWeb3ProxyDirectory, "ManagingApiTypes.ts"), codeTypes); +File.WriteAllText(Path.Combine(targetKaigenWebDirectory, "ManagingApiTypes.ts"), codeTypes); \ No newline at end of file diff --git a/src/Managing.WebApp/src/generated/AuthorizedApiBase.ts b/src/Managing.WebApp/src/generated/AuthorizedApiBase.ts index 4601cbdc..7e267e18 100644 --- a/src/Managing.WebApp/src/generated/AuthorizedApiBase.ts +++ b/src/Managing.WebApp/src/generated/AuthorizedApiBase.ts @@ -4,9 +4,10 @@ * API clients inherit from #AuthorizedApiBase and provide the config. */ -import { Cookies } from 'react-cookie' +import {Cookies} from 'react-cookie' import type IConfig from './IConfig' + export default class AuthorizedApiBase { private readonly config: IConfig @@ -14,7 +15,7 @@ export default class AuthorizedApiBase { this.config = config } - transformOptions = (options: any): Promise => { + transformOptions = (options: any): Promise => { const cookies = new Cookies() const bearerToken = cookies.get('token') if (bearerToken) { diff --git a/src/Managing.sln b/src/Managing.sln index 9649225b..cadb2886 100644 --- a/src/Managing.sln +++ b/src/Managing.sln @@ -62,10 +62,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.ABI.GmxV2", "Managing.ABI.GmxV2\Managing.ABI.GmxV2.csproj", "{4521E1A9-AF81-4CA8-8B4D-30C261ECE977}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Aspire.AppHost", "Managing.Aspire.AppHost\Managing.Aspire.AppHost.csproj", "{2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Aspire.ServiceDefaults", "Managing.Aspire.ServiceDefaults\Managing.Aspire.ServiceDefaults.csproj", "{F58949B8-4173-4F9E-83FF-B88FA2C5C849}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Nswag", "Managing.Nswag\Managing.Nswag.csproj", "{BE50F950-C1D4-4CE0-B32E-6AAC996770D5}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Managing.Workers", "Managing.Workers\Managing.Workers.csproj", "{B7D66A73-CA3A-4DE5-8E88-59D50C4018A6}" @@ -218,22 +214,6 @@ Global {4521E1A9-AF81-4CA8-8B4D-30C261ECE977}.Release|Any CPU.Build.0 = Release|Any CPU {4521E1A9-AF81-4CA8-8B4D-30C261ECE977}.Release|x64.ActiveCfg = Release|Any CPU {4521E1A9-AF81-4CA8-8B4D-30C261ECE977}.Release|x64.Build.0 = Release|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Debug|x64.ActiveCfg = Debug|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Debug|x64.Build.0 = Debug|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Release|Any CPU.Build.0 = Release|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Release|x64.ActiveCfg = Release|Any CPU - {2A7AC5A7-B4D6-4DF2-976B-6EE771BB4C31}.Release|x64.Build.0 = Release|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Debug|x64.ActiveCfg = Debug|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Debug|x64.Build.0 = Debug|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Release|Any CPU.Build.0 = Release|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Release|x64.ActiveCfg = Release|Any CPU - {F58949B8-4173-4F9E-83FF-B88FA2C5C849}.Release|x64.Build.0 = Release|Any CPU {BE50F950-C1D4-4CE0-B32E-6AAC996770D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE50F950-C1D4-4CE0-B32E-6AAC996770D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE50F950-C1D4-4CE0-B32E-6AAC996770D5}.Debug|x64.ActiveCfg = Debug|Any CPU