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>
65 lines
1.8 KiB
C#
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();
|