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

View File

@@ -3,383 +3,291 @@ using DexDemoBackend;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
using Npgsql;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
// Logging configuration is now handled in appsettings.json
// Only add console logging if not already configured
if (!builder.Logging.Services.Any(s => s.ServiceType == typeof(ILoggerProvider)))
{
builder.Logging.AddConsole();
}
// Configure AppConfig using IConfiguration
builder.Services.Configure<AppConfig>(builder.Configuration.GetSection("AppConfig"));
builder.Services.AddSingleton(sp => sp.GetRequiredService<Microsoft.Extensions.Options.IOptions<AppConfig>>().Value);
// Logger will be created after app is built
var config = builder.Configuration.GetSection("AppConfig").Get<AppConfig>() ?? new AppConfig();
// // Force binding to all interfaces in Kubernetes
// Console.WriteLine($"ASPNETCORE_URLS env var: {Environment.GetEnvironmentVariable("ASPNETCORE_URLS")}");
// Console.WriteLine($"ASPNETCORE_HTTP_PORTS env var: {Environment.GetEnvironmentVariable("ASPNETCORE_HTTP_PORTS")}");
ValidateConfiguration(config);
// // Clear any existing configuration and force our URL
// builder.WebHost.ConfigureKestrel(options =>
// {
// options.ListenAnyIP(8000);
// });
// Configuration
var config = new AppConfig
{
DbHost = Environment.GetEnvironmentVariable("DB_HOST") ?? "postgres",
DbPort = Environment.GetEnvironmentVariable("DB_PORT") ?? "5440",
DbName = Environment.GetEnvironmentVariable("DB_NAME") ?? "dexdemo",
DbUser = Environment.GetEnvironmentVariable("DB_USER") ?? "dexdemo",
DbPassword = Environment.GetEnvironmentVariable("DB_PASSWORD") ?? "dexdemo",
Issuer = Environment.GetEnvironmentVariable("DEX_ISSUER") ?? "https://dex.127.0.0.1.sslip.io/",
InsecureDevMode = Environment.GetEnvironmentVariable("INSECURE_DEV_MODE")?.ToLower() == "true",
InsecureDevEmail = Environment.GetEnvironmentVariable("INSECURE_DEV_EMAIL")
};
// Configuration will be logged after logger is created
builder.Services.AddSingleton(config);
// Configure JWT Authentication
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = config.Issuer;
options.RequireHttpsMetadata = false; // For development
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = false, // Можно включить, если DEX выдает audience
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = TimeSpan.FromMinutes(5), // Допуск 5 минут на разницу времени
// Настройка для заполнения User.Identity.Name
NameClaimType = "name", // Используем claim "name" для User.Identity.Name
RoleClaimType = "role" // Используем claim "role" для ролей
};
// Add detailed event logging for JWT authentication
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 =>
{
var jwtLogger = context.HttpContext.RequestServices.GetRequiredService<ILogger<JwtBearerHandler>>();
// Логируем все доступные 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)
{
// Если нет name claim, но есть email - добавляем его как name claim
var identity = context.Principal?.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim("name", 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;
}
};
// Configure OpenID Connect discovery
var httpClientHandler = new HttpClientHandler();
// В режиме разработки отключаем проверку SSL
// В продакшене это должно быть настроено правильно!
if (config.InsecureDevMode)
{
// Logging will be done after logger is created
httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
}
var discoveryUrl = $"{config.Issuer}.well-known/openid-configuration";
// Logging will be done after logger is created
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
discoveryUrl,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever(new HttpClient(httpClientHandler))
{
RequireHttps = !config.InsecureDevMode
}
);
});
ConfigureJwtAuthentication(builder.Services, config);
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>>();
// Get current config from DI (supports reload)
var currentConfig = app.Services.GetRequiredService<AppConfig>();
// 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)
app.UseCors(policy =>
{
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();
policy.WithOrigins(currentConfig.AllowedOrigins)
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials();
});
app.UseAuthentication();
app.UseAuthorization();
logger.LogInformation("[App] Application middleware configured:");
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) =>
// Global error handling middleware
app.Use(async (context, next) =>
{
logger.LogInformation("[UserIdentity] User.Identity.Name: {Name}", context.User.Identity?.Name ?? "NULL");
logger.LogInformation("[UserIdentity] User.Identity.IsAuthenticated: {IsAuthenticated}", context.User.Identity?.IsAuthenticated);
logger.LogInformation("[UserIdentity] User.Identity.AuthenticationType: {AuthType}", context.User.Identity?.AuthenticationType);
return Results.Ok(new
{
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
app.MapGet("/api/user-info", [Authorize] async (HttpContext context, [FromServices] AppConfig cfg, [FromServices] ILogger<Program> userInfoLogger) =>
{
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 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" });
}
});
app.MapGet("/api/health", () => Results.Ok(new HealthResponse("ok")));
app.MapGet("/api/user-identity", [Authorize] (HttpContext context) =>
{
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();
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
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
WHERE u.email = @email",
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 (userResult is null)
if (!userData.Any())
{
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)
var firstRecord = userData.First();
var organization = firstRecord.OrgId.HasValue && firstRecord.OrgName != null
? new Organization(firstRecord.OrgId.Value, firstRecord.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();
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();
userInfoLogger.LogInformation("[UserInfo] Found {RoleCount} roles for user", roles.Count);
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();
// 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));
return Results.Ok(new UserInfo(firstRecord.Email, firstRecord.FullName, organization, roles, links));
}
catch (Exception ex)
catch (NpgsqlException ex) when (ex.IsTransient)
{
userInfoLogger.LogError(ex, "[UserInfo] Error occurred: {Message}", ex.Message);
throw;
return Results.Json(new { error = "Database temporarily unavailable" }, statusCode: 503);
}
catch (NpgsqlException)
{
return Results.Json(new { error = "Database error" }, statusCode: 500);
}
});
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)
static void ConfigureJwtAuthentication(IServiceCollection services, AppConfig config)
{
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)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
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);
}
}
options.Authority = config.Issuer;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateAudience = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ClockSkew = OidcConfigConstants.ClockSkew,
NameClaimType = OidcConfigConstants.NameClaimType,
RoleClaimType = OidcConfigConstants.RoleClaimType
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var nameClaim = context.Principal?.FindFirst(OidcConfigConstants.NameClaimType);
var emailClaim = context.Principal?.FindFirst(OidcConfigConstants.EmailClaimType);
// Dev mode
if (nameClaim == null && emailClaim != null)
{
var identity = context.Principal?.Identity as ClaimsIdentity;
identity?.AddClaim(new Claim(OidcConfigConstants.NameClaimType, emailClaim.Value));
}
return Task.CompletedTask;
}
};
var httpClientHandler = new HttpClientHandler();
if (config.InsecureDevMode)
{
httpClientHandler.ServerCertificateCustomValidationCallback = (_, _, _, _) => true;
}
var discoveryUrl = $"{config.Issuer}.well-known/openid-configuration";
options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
discoveryUrl,
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever(new HttpClient(httpClientHandler))
{
RequireHttps = !config.InsecureDevMode
}
);
});
}
static void ValidateConfiguration(AppConfig config)
{
if (string.IsNullOrWhiteSpace(config.DbHost))
throw new InvalidOperationException("Configuration parameter 'AppConfig:DbHost' is required");
if (string.IsNullOrWhiteSpace(config.DbPort))
throw new InvalidOperationException("Configuration parameter 'AppConfig:DbPort' is required");
if (string.IsNullOrWhiteSpace(config.DbName))
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)
{
if (config.InsecureDevMode)
{
logger.LogInformation("[GetUserEmail] INSECURE_DEV_MODE: Using email {Email}", config.InsecureDevEmail);
return Task.FromResult(config.InsecureDevEmail!);
}
// Try JWT token from authenticated user
if (context.User.Identity?.IsAuthenticated == true)
{
logger.LogDebug("[GetUserEmail] Checking JWT token claims for email");
var emailClaim = context.User.FindFirst("email");
var emailClaim = context.User.FindFirst(OidcConfigConstants.EmailClaimType);
if (emailClaim != null)
{
logger.LogInformation("[GetUserEmail] Found email in JWT claim: {Email}", 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
logger.LogDebug("[GetUserEmail] Checking OAuth2 proxy headers");
if (context.Request.Headers.TryGetValue("X-Auth-Request-Email", out var emailHeader))
if (context.Request.Headers.TryGetValue(OidcConfigConstants.AuthRequestEmailHeader, out var emailHeader))
{
logger.LogInformation("[GetUserEmail] Found email in OAuth2 proxy header: {Email}", 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");
}
// Internal query result model
record UserQueryResult(string Email, string FullName, int? OrganizationId, string? OrgName);
record UserDataResult(
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"
}
},
"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"
}
},
"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"
}
},
"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"]
}
}