Files
Teamup/src/Hosts/TeamUp.Web/Program.cs
T
soroush.asadi 61991bf6cd M1: Identity & access — members, RBAC, JWT auth, invitations
Adds the access foundation everything else enforces against.

SharedKernel (shared access contracts, no Identity dependency for consumers):
- ScopeRef/ScopeType, RoleType, Capability, AccessPolicy (role x capability matrix),
  ICurrentUser, IPermissionService (scope-chain evaluation).

Identity module:
- Member, Membership, Invitation entities; internal IdentityDbContext (schema
  "identity") + InitialIdentity migration; design-time factory.
- JWT auth (HS256) issuing membership claims; PasswordHasher<Member>; CurrentUser
  (claims -> ICurrentUser) and PermissionService implementations.
- Public IMemberDirectory contract for other modules to resolve member display info.
- Endpoints: POST /bootstrap (first owner), /auth/login, GET /me, POST /invitations,
  POST /invitations/accept. Owner-only actions enforced via IPermissionService.
- Web host wires UseAuthentication/UseAuthorization and string-enum JSON.

Verified: build green; ArchitectureTests 8/8 (Identity references only SharedKernel);
IntegrationTests 11/11 incl. a new end-to-end flow — bootstrap -> login -> /me ->
invite -> accept -> login as invitee, and a Member is 403'd from inviting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 07:59:57 +03:30

65 lines
2.0 KiB
C#

using System.Text.Json.Serialization;
using OpenTelemetry.Trace;
using Serilog;
using TeamUp.Bootstrap;
using TeamUp.Infrastructure.Observability;
using TeamUp.Infrastructure.Persistence;
var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog((context, services, configuration) => configuration
.ReadFrom.Configuration(context.Configuration)
.ReadFrom.Services(services));
builder.Services.AddOpenApi();
// Bind/serialize enums as strings across the API (e.g. ScopeType "Organization", RoleType "Member").
builder.Services.ConfigureHttpJsonOptions(options =>
options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()));
builder.Services.AddTeamUpObservability(
builder.Configuration,
serviceName: "teamup-web",
configureTracing: tracing => tracing.AddAspNetCoreInstrumentation());
builder.Services.AddTeamUpPersistence(builder.Configuration);
builder.Services.AddTeamUpModules(builder.Configuration);
var app = builder.Build();
// Apply migrations on startup when configured (default: in Development). EF Core takes a
// DB-wide lock, so the web and worker applying concurrently is safe.
if (app.Configuration.GetValue("Database:ApplyMigrationsOnStartup", app.Environment.IsDevelopment()))
{
await MigrationRunner.MigrateAllAsync(app.Services);
}
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseSerilogRequestLogging();
// Serve the built SPA from wwwroot (single deployable). UseStaticFiles (not MapStaticAssets)
// because the SPA is copied into wwwroot at publish/Docker time, after the build-time asset
// manifest is computed.
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapHealthChecks("/health");
app.MapTeamUpModules();
// SPA deep links (client-side routing) fall back to index.html.
app.MapFallbackToFile("index.html");
app.Run();
/// <summary>Exposed so the integration tests can drive the host via WebApplicationFactory.</summary>
public partial class Program
{
}