feat(infra): configure beta deployment to Test environment with hardened security and feature flags
This commit is contained in:
@@ -68,7 +68,8 @@ public static class DbInitializer
|
||||
SecurityStamp = Guid.NewGuid().ToString()
|
||||
};
|
||||
|
||||
adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, "Admin123!");
|
||||
var adminPassword = Environment.GetEnvironmentVariable("NEXUS_ADMIN_PASSWORD") ?? "Admin123!";
|
||||
adminUser.PasswordHash = passwordHasher.HashPassword(adminUser, adminPassword);
|
||||
|
||||
dbContext.Users.Add(adminUser);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
@@ -56,9 +56,14 @@ public static class DependencyInjection
|
||||
var qdrantUrl = configuration.GetConnectionString("QdrantConnection") ?? "http://localhost:6334";
|
||||
services.AddSingleton<QdrantClient>(sp => new QdrantClient(new Uri(qdrantUrl)));
|
||||
|
||||
// Neo4j Driver registration
|
||||
// Neo4j Driver registration (supports optional authentication)
|
||||
var neo4jUrl = configuration.GetConnectionString("Neo4jConnection") ?? "bolt://localhost:7687";
|
||||
services.AddSingleton<IDriver>(sp => GraphDatabase.Driver(neo4jUrl, AuthTokens.None));
|
||||
var neo4jUser = configuration["Neo4j:Username"];
|
||||
var neo4jPass = configuration["Neo4j:Password"];
|
||||
var neo4jAuth = !string.IsNullOrEmpty(neo4jUser)
|
||||
? AuthTokens.Basic(neo4jUser, neo4jPass ?? string.Empty)
|
||||
: AuthTokens.None;
|
||||
services.AddSingleton<IDriver>(sp => GraphDatabase.Driver(neo4jUrl, neo4jAuth));
|
||||
|
||||
// Hangfire registration
|
||||
if (!string.IsNullOrEmpty(pgConnectionString))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@inject IIdentityService IdentityService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JS
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
<div class="login-page-container">
|
||||
<div class="mesh-bg"></div>
|
||||
@@ -80,8 +81,14 @@
|
||||
</EditForm>
|
||||
|
||||
<div class="auth-footer">
|
||||
<a href="/account/forgot-password" class="auth-link">Zapomniałem hasła?</a>
|
||||
<p class="auth-switch">Nie masz konta? <a href="/account/register">Zarejestruj się</a></p>
|
||||
@if (_allowPasswordReset)
|
||||
{
|
||||
<a href="/account/forgot-password" class="auth-link">Zapomniałem hasła?</a>
|
||||
}
|
||||
@if (_allowRegistration)
|
||||
{
|
||||
<p class="auth-switch">Nie masz konta? <a href="/account/register">Zarejestruj się</a></p>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="auth-legal">
|
||||
@@ -106,9 +113,14 @@
|
||||
private string? _errorMessage;
|
||||
private bool _isSubmitting;
|
||||
private bool _showPassword;
|
||||
private bool _allowRegistration;
|
||||
private bool _allowPasswordReset;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
_allowRegistration = Configuration.GetValue<bool?>("Features:AllowRegistration") ?? true;
|
||||
_allowPasswordReset = Configuration.GetValue<bool?>("Features:AllowPasswordReset") ?? true;
|
||||
|
||||
if (!string.IsNullOrEmpty(ErrorCode))
|
||||
{
|
||||
_errorMessage = ErrorCode switch
|
||||
@@ -118,6 +130,7 @@
|
||||
"UserAlreadyExists" => "Użytkownik o tym adresie e-mail już istnieje. Zaloguj się tradycyjnie hasłem.",
|
||||
"LockedOut" => "Twoje konto zostało zablokowane. Spróbuj ponownie później.",
|
||||
"InvalidCredentials" => "Nieprawidłowy e-mail lub hasło.",
|
||||
"RegistrationDisabled" => "Rejestracja jest wyłączona w tym środowisku.",
|
||||
_ => "Wystąpił nieoczekiwany błąd podczas logowania."
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@inject IIdentityService IdentityService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject IJSRuntime JS
|
||||
@inject IConfiguration Configuration
|
||||
|
||||
<div class="login-page-container">
|
||||
<div class="mesh-bg"></div>
|
||||
@@ -81,6 +82,15 @@
|
||||
private string? _errorMessage;
|
||||
private bool _isSubmitting;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
var allowRegistration = Configuration.GetValue<bool?>("Features:AllowRegistration") ?? true;
|
||||
if (!allowRegistration)
|
||||
{
|
||||
NavigationManager.NavigateTo("/account/login?error=RegistrationDisabled", replace: true);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRegister()
|
||||
{
|
||||
_isSubmitting = true;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
@using NexusReader.UI.Shared.Components.Organisms
|
||||
@using NexusReader.UI.Shared.Services
|
||||
@using Microsoft.Extensions.Logging
|
||||
@using Microsoft.Extensions.Configuration
|
||||
@using NexusReader.Application.Abstractions.Services
|
||||
@using NexusReader.Application.DTOs.User
|
||||
@using NexusReader.Application.Queries.Reader
|
||||
|
||||
@@ -252,6 +252,35 @@ app.UseRouting();
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
app.UseAntiforgery();
|
||||
|
||||
// Feature flags: block registration & password reset in restricted environments (e.g. Test)
|
||||
var allowRegistration = app.Configuration.GetValue<bool?>("Features:AllowRegistration") ?? true;
|
||||
var allowPasswordReset = app.Configuration.GetValue<bool?>("Features:AllowPasswordReset") ?? true;
|
||||
|
||||
if (!allowRegistration || !allowPasswordReset)
|
||||
{
|
||||
app.Use(async (context, next) =>
|
||||
{
|
||||
var path = context.Request.Path.Value?.ToLowerInvariant();
|
||||
|
||||
if (!allowRegistration && path is "/identity/register" or "/account/register")
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await context.Response.WriteAsync("Registration is disabled in this environment.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allowPasswordReset && path is "/identity/forgotpassword" or "/account/forgot-password")
|
||||
{
|
||||
context.Response.StatusCode = StatusCodes.Status403Forbidden;
|
||||
await context.Response.WriteAsync("Password reset is disabled in this environment.");
|
||||
return;
|
||||
}
|
||||
|
||||
await next();
|
||||
});
|
||||
}
|
||||
|
||||
app.MapStaticAssets();
|
||||
app.MapHub<NexusReader.Infrastructure.RealTime.SyncHub>("/synchub");
|
||||
|
||||
@@ -520,7 +549,13 @@ app.MapGet("/identity/callback/google", async (
|
||||
return Results.Redirect("/account/login?error=LockedOut");
|
||||
}
|
||||
|
||||
// New user provisioning
|
||||
// New user provisioning (blocked when registration is disabled)
|
||||
if (!allowRegistration)
|
||||
{
|
||||
logger.LogWarning("Google provisioning blocked: registration is disabled in this environment.");
|
||||
return Results.Redirect("/account/login?error=RegistrationDisabled");
|
||||
}
|
||||
|
||||
var email = info.Principal.FindFirstValue(ClaimTypes.Email);
|
||||
if (email != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"Features": {
|
||||
"AllowRegistration": false,
|
||||
"AllowPasswordReset": false
|
||||
},
|
||||
"ApiBaseUrl": "http://localhost:5000"
|
||||
}
|
||||
Reference in New Issue
Block a user