Refactor AppConfig and update configuration settings across environments. Changed properties to use set accessors, added AllowedOrigins, and improved error handling in Program.cs. Updated appsettings files for development, production, and added new structure for user data retrieval.

This commit is contained in:
2026-01-20 15:21:58 +05:00
parent 641e1fc14d
commit 4655b4111f
5 changed files with 259 additions and 319 deletions

View File

@@ -2,14 +2,15 @@ namespace DexDemoBackend;
public class AppConfig public class AppConfig
{ {
public string DbHost { get; init; } = default!; public string DbHost { get; set; } = "postgres";
public string DbPort { get; init; } = default!; public string DbPort { get; set; } = "5440";
public string DbName { get; init; } = default!; public string DbName { get; set; } = "dexdemo";
public string DbUser { get; init; } = default!; public string DbUser { get; set; } = "dexdemo";
public string DbPassword { get; init; } = default!; public string DbPassword { get; set; } = "dexdemo";
public string Issuer { get; init; } = default!; public string Issuer { get; set; } = "https://dex.127.0.0.1.sslip.io/";
public bool InsecureDevMode { get; init; } public bool InsecureDevMode { get; set; }
public string? InsecureDevEmail { get; init; } public string? InsecureDevEmail { get; set; }
public string[] AllowedOrigins { get; set; } = ["http://localhost:3000", "https://localhost:3000"];
public string ConnectionString => public string ConnectionString =>
$"Host={DbHost};Port={DbPort};Database={DbName};Username={DbUser};Password={DbPassword}"; $"Host={DbHost};Port={DbPort};Database={DbName};Username={DbUser};Password={DbPassword}";

View File

@@ -3,150 +3,185 @@ using DexDemoBackend;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens; using Microsoft.IdentityModel.Tokens;
using Npgsql; using Npgsql;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims; using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Logging configuration is now handled in appsettings.json // Configure AppConfig using IConfiguration
// Only add console logging if not already configured builder.Services.Configure<AppConfig>(builder.Configuration.GetSection("AppConfig"));
if (!builder.Logging.Services.Any(s => s.ServiceType == typeof(ILoggerProvider))) builder.Services.AddSingleton(sp => sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<AppConfig>>().Value);
var config = builder.Configuration.GetSection("AppConfig").Get<AppConfig>() ?? new AppConfig();
ValidateConfiguration(config);
ConfigureJwtAuthentication(builder.Services, config);
builder.Services.AddAuthorization();
builder.Services.ConfigureHttpJsonOptions(options =>
{ {
builder.Logging.AddConsole(); options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower;
} });
// Logger will be created after app is built var app = builder.Build();
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
// // Force binding to all interfaces in Kubernetes // Get current config from DI (supports reload)
// Console.WriteLine($"ASPNETCORE_URLS env var: {Environment.GetEnvironmentVariable("ASPNETCORE_URLS")}"); var currentConfig = app.Services.GetRequiredService<AppConfig>();
// Console.WriteLine($"ASPNETCORE_HTTP_PORTS env var: {Environment.GetEnvironmentVariable("ASPNETCORE_HTTP_PORTS")}");
// // Clear any existing configuration and force our URL app.UseCors(policy =>
// builder.WebHost.ConfigureKestrel(options =>
// {
// options.ListenAnyIP(8000);
// });
// Configuration
var config = new AppConfig
{ {
DbHost = Environment.GetEnvironmentVariable("DB_HOST") ?? "postgres", policy.WithOrigins(currentConfig.AllowedOrigins)
DbPort = Environment.GetEnvironmentVariable("DB_PORT") ?? "5440", .AllowAnyMethod()
DbName = Environment.GetEnvironmentVariable("DB_NAME") ?? "dexdemo", .AllowAnyHeader()
DbUser = Environment.GetEnvironmentVariable("DB_USER") ?? "dexdemo", .AllowCredentials();
DbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? "dexdemo", });
Issuer = Environment.GetEnvironmentVariable("DEX_ISSUER") ?? "https://dex.127.0.0.1.sslip.io/", app.UseAuthentication();
InsecureDevMode = Environment.GetEnvironmentVariable("INSECURE_DEV_MODE")?.ToLower() == "true", app.UseAuthorization();
InsecureDevEmail = Environment.GetEnvironmentVariable("INSECURE_DEV_EMAIL")
};
// Configuration will be logged after logger is created // Global error handling middleware
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (UnauthorizedAccessException)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
await context.Response.WriteAsJsonAsync(new { error = "Unauthorized" });
}
catch (Exception)
{
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
await context.Response.WriteAsJsonAsync(new { error = "Internal server error" });
}
});
builder.Services.AddSingleton(config); app.MapGet("/api/health", () => Results.Ok(new HealthResponse("ok")));
// Configure JWT Authentication app.MapGet("/api/user-identity", [Authorize] (HttpContext context) =>
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) {
return Results.Ok(new UserIdentityResponse(
context.User.Identity?.Name,
context.User.Identity?.IsAuthenticated ?? false,
context.User.Identity?.AuthenticationType,
context.User.Claims.Select(c => new ClaimResponse(c.Type, c.Value)).ToList()
));
});
app.MapGet("/api/user-info", [Authorize] async (HttpContext context, [FromServices] AppConfig cfg) =>
{
try
{
var email = await GetUserEmail(context, cfg);
await using var conn = new NpgsqlConnection(cfg.ConnectionString);
await conn.OpenAsync();
var userData = await conn.QueryAsync<UserDataResult>(@"
SELECT
u.email, u.full_name,
o.id as org_id, o.name as org_name,
r.id as role_id, r.name as role_name, r.description as role_description,
l.id as link_id, l.title as link_title, l.url as link_url, l.description as link_description
FROM users u
LEFT JOIN organizations o ON u.organization_id = o.id
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.id
LEFT JOIN role_links rl ON r.id = rl.role_id
LEFT JOIN links l ON rl.link_id = l.id
WHERE u.email = @email
ORDER BY l.id",
new { email });
if (!userData.Any())
{
return Results.NotFound(new { detail = "User not found in database" });
}
var firstRecord = userData.First();
var organization = firstRecord.OrgId.HasValue && firstRecord.OrgName != null
? new Organization(firstRecord.OrgId.Value, firstRecord.OrgName)
: null;
var roles = userData
.Where(x => x.RoleId.HasValue)
.GroupBy(x => x.RoleId)
.Select(g => g.First())
.Select(x => new Role(x.RoleId!.Value, x.RoleName!, x.RoleDescription))
.ToList();
var links = userData
.Where(x => x.LinkId.HasValue)
.GroupBy(x => x.LinkId)
.Select(g => g.First())
.Select(x => new Link(x.LinkId!.Value, x.LinkTitle!, x.LinkUrl!, x.LinkDescription))
.ToList();
return Results.Ok(new UserInfo(firstRecord.Email, firstRecord.FullName, organization, roles, links));
}
catch (NpgsqlException ex) when (ex.IsTransient)
{
return Results.Json(new { error = "Database temporarily unavailable" }, statusCode: 503);
}
catch (NpgsqlException)
{
return Results.Json(new { error = "Database error" }, statusCode: 500);
}
});
app.Run();
static void ConfigureJwtAuthentication(IServiceCollection services, AppConfig config)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => .AddJwtBearer(options =>
{ {
options.Authority = config.Issuer; options.Authority = config.Issuer;
options.RequireHttpsMetadata = false; // For development options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters options.TokenValidationParameters = new TokenValidationParameters
{ {
ValidateIssuer = true, ValidateIssuer = true,
ValidIssuer = config.Issuer, ValidIssuer = config.Issuer,
ValidateAudience = false, // Можно включить, если DEX выдает audience ValidateAudience = false,
ValidateLifetime = true, ValidateLifetime = true,
ValidateIssuerSigningKey = true, ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(5), // Допуск 5 минут на разницу времени ClockSkew = OidcConfigConstants.ClockSkew,
NameClaimType = OidcConfigConstants.NameClaimType,
// Настройка для заполнения User.Identity.Name RoleClaimType = OidcConfigConstants.RoleClaimType
NameClaimType = "name", // Используем claim "name" для User.Identity.Name
RoleClaimType = "role" // Используем claim "role" для ролей
}; };
// Add detailed event logging for JWT authentication
options.Events = new JwtBearerEvents options.Events = new JwtBearerEvents
{ {
OnAuthenticationFailed = context =>
{
var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
jwtLogger.LogError(context.Exception, "[JWT Auth Failed] Exception: {Message}", context.Exception?.Message);
jwtLogger.LogDebug("[JWT Auth Failed] StackTrace: {StackTrace}", context.Exception?.StackTrace);
return Task.CompletedTask;
},
OnTokenValidated = context => OnTokenValidated = context =>
{ {
var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>(); var nameClaim = context.Principal?.FindFirst(OidcConfigConstants.NameClaimType);
var emailClaim = context.Principal?.FindFirst(OidcConfigConstants.EmailClaimType);
// Логируем все доступные claims для отладки
var claims = string.Join(", ", context.Principal?.Claims?.Select(c => $"{c.Type}={c.Value}") ?? new string[0]);
jwtLogger.LogDebug("[JWT Token Validated] All claims: {Claims}", claims);
// Проверяем, есть ли name claim, если нет - используем email как fallback
var nameClaim = context.Principal?.FindFirst("name");
var emailClaim = context.Principal?.FindFirst("email");
var subClaim = context.Principal?.FindFirst("sub");
if (nameClaim == null && emailClaim != null) if (nameClaim == null && emailClaim != null)
{ {
// Если нет name claim, но есть email - добавляем его как name claim
var identity = context.Principal?.Identity as ClaimsIdentity; var identity = context.Principal?.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("name", emailClaim.Value)); identity?.AddClaim(new Claim(OidcConfigConstants.NameClaimType, emailClaim.Value));
jwtLogger.LogDebug("[JWT Token Validated] Added email as name claim: {Email}", emailClaim.Value);
} }
jwtLogger.LogInformation("[JWT Token Validated] User.Identity.Name: {UserName}", context.Principal?.Identity?.Name);
jwtLogger.LogInformation("[JWT Token Validated] Email claim: {Email}", emailClaim?.Value ?? "Not found");
jwtLogger.LogInformation("[JWT Token Validated] Sub claim: {Sub}", subClaim?.Value ?? "Not found");
return Task.CompletedTask;
},
OnMessageReceived = context =>
{
var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
jwtLogger.LogDebug("[JWT Message Received] Scheme: {Scheme}, Path: {Path}, Method: {Method}",
context.Scheme.Name, context.Request.Path, context.Request.Method);
// Log Authorization header (without sensitive token data)
if (context.Request.Headers.TryGetValue("Authorization", out var authHeader))
{
var authValue = authHeader.ToString();
if (authValue.StartsWith("Bearer "))
{
var tokenPreview = authValue.Substring(7, Math.Min(20, authValue.Length - 7)) + "...";
jwtLogger.LogDebug("[JWT Message Received] Token preview: {TokenPreview}", tokenPreview);
}
}
return Task.CompletedTask;
},
OnChallenge = context =>
{
var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
jwtLogger.LogWarning("[JWT Challenge] Error: {Error}, ErrorDescription: {ErrorDescription}, ErrorUri: {ErrorUri}",
context.Error, context.ErrorDescription, context.ErrorUri);
return Task.CompletedTask; return Task.CompletedTask;
} }
}; };
// Configure OpenID Connect discovery
var httpClientHandler = new HttpClientHandler(); var httpClientHandler = new HttpClientHandler();
// В режиме разработки отключаем проверку SSL
// В продакшене это должно быть настроено правильно!
if (config.InsecureDevMode) if (config.InsecureDevMode)
{ {
// Logging will be done after logger is created
httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true; httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
} }
var discoveryUrl = $"{config.Issuer}.well-known/openid-configuration"; var discoveryUrl = $"{config.Issuer}.well-known/openid-configuration";
// Logging will be done after logger is created
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>( options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
discoveryUrl, discoveryUrl,
@@ -157,229 +192,102 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
} }
); );
}); });
builder.Services.AddAuthorization();
// Configure JSON serialization to use snake_case
builder.Services.ConfigureHttpJsonOptions(options =>
{
options.SerializerOptions.PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower;
});
builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
policy.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
});
});
var app = builder.Build();
Dapper.DefaultTypeMap.MatchNamesWithUnderscores = true;
// Create logger after app is built
var logger = app.Services.GetRequiredService<ILogger<Program>>();
// Log configuration (without sensitive data)
logger.LogInformation("[Config] Application configuration:");
logger.LogInformation("[Config] DB_HOST: {DbHost}", config.DbHost);
logger.LogInformation("[Config] DB_PORT: {DbPort}", config.DbPort);
logger.LogInformation("[Config] DB_NAME: {DbName}", config.DbName);
logger.LogInformation("[Config] DB_USER: {DbUser}", config.DbUser);
logger.LogInformation("[Config] DB_PASSWORD: [HIDDEN]");
logger.LogInformation("[Config] DEX_ISSUER: {Issuer}", config.Issuer);
logger.LogInformation("[Config] INSECURE_DEV_MODE: {InsecureDevMode}", config.InsecureDevMode);
logger.LogInformation("[Config] INSECURE_DEV_EMAIL: {InsecureDevEmail}", config.InsecureDevEmail ?? "[NOT SET]");
// Log JWT configuration
if (config.InsecureDevMode)
{
logger.LogWarning("[JWT Config] InsecureDevMode enabled - disabling SSL certificate validation");
} }
logger.LogInformation("[JWT Config] OpenID Connect discovery URL: {DiscoveryUrl}", $"{config.Issuer}.well-known/openid-configuration");
logger.LogInformation("[JWT Config] RequireHttps: {RequireHttps}", !config.InsecureDevMode);
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
logger.LogInformation("[App] Application middleware configured:"); static void ValidateConfiguration(AppConfig config)
logger.LogInformation("[App] CORS: Enabled");
logger.LogInformation("[App] Authentication: JWT Bearer");
logger.LogInformation("[App] Authorization: Enabled");
// Health check endpoint
app.MapGet("/api/health", () => Results.Ok(new { status = "ok" }));
// User identity demo endpoint
app.MapGet("/api/user-identity", [Authorize] (HttpContext context, [FromServices] ILogger<Program> logger) =>
{ {
logger.LogInformation("[UserIdentity] User.Identity.Name: {Name}", context.User.Identity?.Name ?? "NULL"); if (string.IsNullOrWhiteSpace(config.DbHost))
logger.LogInformation("[UserIdentity] User.Identity.IsAuthenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated); throw new InvalidOperationException("Configuration parameter 'AppConfig:DbHost' is required");
logger.LogInformation("[UserIdentity] User.Identity.AuthenticationType: {AuthType}", context.User.Identity?.AuthenticationType);
return Results.Ok(new if (string.IsNullOrWhiteSpace(config.DbPort))
{ throw new InvalidOperationException("Configuration parameter 'AppConfig:DbPort' is required");
name = context.User.Identity?.Name,
isAuthenticated = context.User.Identity?.IsAuthenticated,
authenticationType = context.User.Identity?.AuthenticationType,
claims = context.User.Claims.Select(c => new { type = c.Type, value = c.Value }).ToList()
});
});
// User info endpoint if (string.IsNullOrWhiteSpace(config.DbName))
app.MapGet("/api/user-info", [Authorize] async (HttpContext context, [FromServices] AppConfig cfg, [FromServices] ILogger<Program> userInfoLogger) => throw new InvalidOperationException("Configuration parameter 'AppConfig:DbName' is required");
if (string.IsNullOrWhiteSpace(config.DbUser))
throw new InvalidOperationException("Configuration parameter 'AppConfig:DbUser' is required");
if (string.IsNullOrWhiteSpace(config.DbPassword))
throw new InvalidOperationException("Configuration parameter 'AppConfig:DbPassword' is required");
if (string.IsNullOrWhiteSpace(config.Issuer))
throw new InvalidOperationException("Configuration parameter 'AppConfig:Issuer' is required");
if (!Uri.TryCreate(config.Issuer, UriKind.Absolute, out _))
throw new InvalidOperationException("Configuration parameter 'AppConfig:Issuer' must be a valid URL");
if (config.InsecureDevMode && string.IsNullOrWhiteSpace(config.InsecureDevEmail))
throw new InvalidOperationException("Configuration parameter 'AppConfig:InsecureDevEmail' is required when 'AppConfig:InsecureDevMode' is enabled");
}
static Task<string> GetUserEmail(HttpContext context, AppConfig config)
{ {
userInfoLogger.LogInformation("[UserInfo] Starting user info request for path: {Path}", context.Request.Path);
userInfoLogger.LogInformation("[UserInfo] User authenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated);
try
{
var email = await GetUserEmail(context, cfg, userInfoLogger);
userInfoLogger.LogInformation("[UserInfo] Retrieved email: {Email}", email);
await using var conn = new NpgsqlConnection(cfg.ConnectionString);
await conn.OpenAsync();
userInfoLogger.LogDebug("[UserInfo] Database connection opened successfully");
// Get user info
userInfoLogger.LogDebug("[UserInfo] Querying user info for email: {Email}", email);
var userResult = await conn.QuerySingleOrDefaultAsync<UserQueryResult>(@"
SELECT u.email, u.full_name, u.organization_id, o.name as org_name
FROM users u
LEFT JOIN organizations o ON u.organization_id = o.id
WHERE u.email = @email",
new { email });
if (userResult is null)
{
userInfoLogger.LogWarning("[UserInfo] User not found in database for email: {Email}", email);
return Results.NotFound(new { detail = "User not found in database" });
}
userInfoLogger.LogInformation("[UserInfo] User found: {Email}, FullName: {FullName}, OrgId: {OrgId}",
userResult.Email, userResult.FullName, userResult.OrganizationId);
var organization = userResult.OrganizationId.HasValue && userResult.OrgName != null
? new Organization(userResult.OrganizationId.Value, userResult.OrgName)
: null;
// Get user roles
userInfoLogger.LogDebug("[UserInfo] Querying user roles for email: {Email}", email);
var roles = (await conn.QueryAsync<Role>(@"
SELECT r.id, r.name, r.description
FROM roles r
JOIN user_roles ur ON r.id = ur.role_id
JOIN users u ON ur.user_id = u.id
WHERE u.email = @email",
new { email })).ToList();
userInfoLogger.LogInformation("[UserInfo] Found {RoleCount} roles for user", roles.Count);
// Get available links
userInfoLogger.LogDebug("[UserInfo] Querying available links for email: {Email}", email);
var links = (await conn.QueryAsync<Link>(@"
SELECT DISTINCT l.id, l.title, l.url, l.description
FROM links l
JOIN role_links rl ON l.id = rl.link_id
JOIN user_roles ur ON rl.role_id = ur.role_id
JOIN users u ON ur.user_id = u.id
WHERE u.email = @email
ORDER BY l.id",
new { email })).ToList();
userInfoLogger.LogInformation("[UserInfo] Found {LinkCount} links for user", links.Count);
userInfoLogger.LogInformation("[UserInfo] Returning user info successfully");
return Results.Ok(new UserInfo(userResult.Email, userResult.FullName, organization, roles, links));
}
catch (Exception ex)
{
userInfoLogger.LogError(ex, "[UserInfo] Error occurred: {Message}", ex.Message);
throw;
}
});
logger.LogInformation("[App] Application starting...");
logger.LogInformation("[App] Available endpoints:");
logger.LogInformation("[App] GET /api/health - Health check");
logger.LogInformation("[App] GET /api/user-identity - User identity information (requires authentication)");
logger.LogInformation("[App] GET /api/user-info - User information (requires authentication)");
app.Run();
static Task<string> GetUserEmail(HttpContext context, AppConfig config, ILogger logger)
{
logger.LogDebug("[GetUserEmail] Starting authentication check for path: {Path}", context.Request.Path);
logger.LogDebug("[GetUserEmail] Request method: {Method}", context.Request.Method);
logger.LogDebug("[GetUserEmail] User.Identity.IsAuthenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated);
logger.LogInformation("[GetUserEmail] User.Identity.Name: {Name}", context.User.Identity?.Name ?? "NULL");
logger.LogDebug("[GetUserEmail] User.Identity.AuthenticationType: {AuthType}", context.User.Identity?.AuthenticationType);
// Log all claims
if (context.User.Identity?.IsAuthenticated == true)
{
logger.LogDebug("[GetUserEmail] All user claims:");
foreach (var claim in context.User.Claims)
{
logger.LogDebug("[GetUserEmail] {ClaimType} = {ClaimValue}", claim.Type, claim.Value);
}
}
// Log all headers
logger.LogDebug("[GetUserEmail] Request headers:");
foreach (var header in context.Request.Headers)
{
if (header.Key.ToLower().Contains("auth") || header.Key.ToLower().Contains("email"))
{
logger.LogDebug("[GetUserEmail] {HeaderKey} = {HeaderValue}", header.Key, header.Value);
}
}
// Dev mode
if (config.InsecureDevMode) if (config.InsecureDevMode)
{ {
logger.LogInformation("[GetUserEmail] INSECURE_DEV_MODE: Using email {Email}", config.InsecureDevEmail);
return Task.FromResult(config.InsecureDevEmail!); return Task.FromResult(config.InsecureDevEmail!);
} }
// Try JWT token from authenticated user
if (context.User.Identity?.IsAuthenticated == true) if (context.User.Identity?.IsAuthenticated == true)
{ {
logger.LogDebug("[GetUserEmail] Checking JWT token claims for email"); var emailClaim = context.User.FindFirst(OidcConfigConstants.EmailClaimType);
var emailClaim = context.User.FindFirst("email");
if (emailClaim != null) if (emailClaim != null)
{ {
logger.LogInformation("[GetUserEmail] Found email in JWT claim: {Email}", emailClaim.Value);
return Task.FromResult(emailClaim.Value); return Task.FromResult(emailClaim.Value);
} }
else
{
logger.LogWarning("[GetUserEmail] No email claim found in JWT token");
}
}
else
{
logger.LogDebug("[GetUserEmail] User is not authenticated via JWT");
} }
// Try OAuth2 proxy header if (context.Request.Headers.TryGetValue(OidcConfigConstants.AuthRequestEmailHeader, out var emailHeader))
logger.LogDebug("[GetUserEmail] Checking OAuth2 proxy headers");
if (context.Request.Headers.TryGetValue("X-Auth-Request-Email", out var emailHeader))
{ {
logger.LogInformation("[GetUserEmail] Found email in OAuth2 proxy header: {Email}", emailHeader.ToString());
return Task.FromResult(emailHeader.ToString()); return Task.FromResult(emailHeader.ToString());
} }
else
{
logger.LogDebug("[GetUserEmail] No X-Auth-Request-Email header found");
}
logger.LogError("[GetUserEmail] No authentication information found - throwing UnauthorizedAccessException");
throw new UnauthorizedAccessException("No authentication information found"); throw new UnauthorizedAccessException("No authentication information found");
} }
// Internal query result model record UserDataResult(
record UserQueryResult(string Email, string FullName, int? OrganizationId, string? OrgName); string Email,
string FullName,
int? OrgId,
string? OrgName,
int? RoleId,
string? RoleName,
string? RoleDescription,
int? LinkId,
string? LinkTitle,
string? LinkUrl,
string? LinkDescription
);
// API Response Models
record HealthResponse(string Status);
record ClaimResponse(string Type, string Value);
record UserIdentityResponse(
string? Name,
bool IsAuthenticated,
string? AuthenticationType,
List<ClaimResponse> Claims
);
static class OidcConfigConstants
{
// Default environment variable values
public const string DefaultDbHost = "postgres";
public const string DefaultDbPort = "5440";
public const string DefaultDbName = "dexdemo";
public const string DefaultDbUser = "dexdemo";
public const string DefaultDbPassword = "dexdemo";
public const string DefaultDexIssuer = "https://dex.127.0.0.1.sslip.io/";
// JWT settings
public const string NameClaimType = "name";
public const string RoleClaimType = "role";
public const string EmailClaimType = "email";
public const string AuthRequestEmailHeader = "X-Auth-Request-Email";
// Time settings
public static readonly TimeSpan ClockSkew = TimeSpan.FromMinutes(5);
}

View File

@@ -14,6 +14,17 @@
"Microsoft.AspNetCore": "Information" "Microsoft.AspNetCore": "Information"
} }
}, },
"Urls": "http://localhost:8000" "Urls": "http://localhost:8000",
"AppConfig": {
"DbHost": "localhost",
"DbPort": "5432",
"DbName": "dexdemo",
"DbUser": "dexdemo",
"DbPassword": "dexdemo",
"Issuer": "https://dex.127.0.0.1.sslip.io/",
"InsecureDevMode": true,
"InsecureDevEmail": "test@example.com",
"AllowedOrigins": ["http://localhost:3000", "https://localhost:3000", "http://localhost:5173", "https://localhost:5173"]
}
} }

View File

@@ -14,7 +14,17 @@
"Microsoft.AspNetCore": "Warning" "Microsoft.AspNetCore": "Warning"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"AppConfig": {
"DbHost": "postgres",
"DbPort": "5432",
"DbName": "dexdemo",
"DbUser": "dexdemo",
"DbPassword": "dexdemo",
"Issuer": "https://dex.127.0.0.1.sslip.io/",
"InsecureDevMode": false,
"AllowedOrigins": ["https://yourdomain.com"]
}
} }

View File

@@ -13,6 +13,16 @@
"Microsoft.Hosting.Lifetime": "Information" "Microsoft.Hosting.Lifetime": "Information"
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"AppConfig": {
"DbHost": "postgres",
"DbPort": "5440",
"DbName": "dexdemo",
"DbUser": "dexdemo",
"DbPassword": "dexdemo",
"Issuer": "https://dex.127.0.0.1.sslip.io/",
"InsecureDevMode": false,
"AllowedOrigins": ["http://localhost:3000", "https://localhost:3000"]
}
} }