books/backend/Books.Api/Program.cs
Nicolaj Hartmann 8e05171b66 Full product audit: fix security, compliance, UX, and wire broken features
Security (Phase 1):
- Add authentication middleware on /graphql endpoint
- Filter company queries by user access (prevent IDOR)
- Add role-based authorization on mutations (owner/accountant)
- Reduce API key cache TTL from 24h to 5 minutes
- Hide exception details in production GraphQL errors
- Fix RBAC in frontend companyStore (was hardcoded)

Wiring broken features (Phase 2):
- Wire Kassekladde submit/void/copy to GraphQL mutations
- Wire Kontooversigt account creation to createAccount mutation
- Wire Settings save to updateCompany mutation
- Wire CreateFiscalYearModal and CloseFiscalYearWizard to mutations
- Replace Momsindberetning mock data with real useVatReport query
- Remove Dashboard hardcoded percentages and fake VAT deadline
- Fix Kreditnotaer invoice selector to use real data
- Fix mutation retry from 1 to 0 (prevent duplicate operations)

Accounting compliance (Phase 3):
- Add balanced entry validation (debit==credit) in JournalEntryDraftAggregate
- Add fiscal year boundary enforcement (status, date range checks)
- Add PostedAt timestamp to posted events (Bogføringsloven §7)
- Add account number uniqueness check within company
- Add fiscal year overlap and gap checks
- Add sequential invoice auto-numbering
- Fix InvoiceLine VAT rate to use canonical VatCodes
- Fix SAF-T account type mapping (financial → Expense)
- Add DraftLine validation (cannot have both debit and credit > 0)

UX improvements (Phase 4):
- Fix Danish character encoding across 15+ files (ø, æ, å)
- Deploy DemoDataDisclaimer on pages with mock/incomplete data
- Adopt PageHeader component universally across all pages
- Standardize active/inactive filtering to Switch pattern
- Fix dead buttons in Header (Help, Notifications)
- Remove hardcoded mock data from Settings
- Fix Sidebar controlled state and Kontooversigt navigation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 21:35:26 +01:00

65 lines
1.8 KiB
C#

using Books.Api;
using Books.Api.Authorization;
using Books.Api.GraphQL;
using GraphQL;
using GraphQL.Server.Ui.Altair;
using Hangfire;
// Enable legacy timestamp behavior for Npgsql 6.0+
// This allows DateTimeOffset with non-UTC offsets to be written to timestamptz columns
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
var builder = WebApplication.CreateBuilder(args);
// Configure EventFlow, Hangfire, GraphQL and all services
Startup.ConfigureServices(builder.Services, builder.Configuration, builder.Environment);
var app = builder.Build();
// Configure the HTTP request pipeline
if (app.Environment.IsDevelopment())
{
app.UseHangfireDashboard();
}
app.UseHttpsRedirection();
// CORS must come before auth
app.UseCors();
// Authentication & Authorization
app.UseAuthentication();
app.UseAuthorization();
// Company context middleware - extracts X-Company-Id header and validates user access
app.UseCompanyContext();
// Require authentication for the GraphQL endpoint
app.UseWhen(
context => context.Request.Path.StartsWithSegments("/graphql"),
appBuilder => appBuilder.Use(async (context, next) =>
{
if (context.User.Identity?.IsAuthenticated != true)
{
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
await context.Response.WriteAsync("{\"errors\":[{\"message\":\"Authentication required\"}]}");
return;
}
await next();
})
);
// Map controllers (for AuthController)
app.MapControllers();
// GraphQL endpoint
app.UseGraphQL<BooksSchema>("/graphql");
// GraphQL UI (development only) - use external tools like Altair, Insomnia, or GraphiQL
if (app.Environment.IsDevelopment())
{
app.UseGraphQLAltair("/graphql/ui");
}
app.Run();