175 lines
6.1 KiB
C#
175 lines
6.1 KiB
C#
using Microsoft.AspNetCore.Identity;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using BudgetApp.Components;
|
|
using BudgetApp.Data;
|
|
using BudgetApp.Data.Entities;
|
|
using BudgetApp.Data.Repositories;
|
|
using BudgetApp.Services;
|
|
using BudgetApp.Data.Interfaces;
|
|
|
|
var builder = WebApplication.CreateBuilder(args);
|
|
|
|
// Database
|
|
builder.Services.AddDbContext<ApplicationDbContext>(options =>
|
|
options.UseSqlite(
|
|
builder.Configuration.GetConnectionString("DefaultConnection") ?? "Data Source=budgetapp.db",
|
|
o => o.MigrationsAssembly("BudgetApp.Data")));
|
|
|
|
// Identity
|
|
builder.Services.AddIdentity<ApplicationUser, IdentityRole>(options =>
|
|
{
|
|
options.Password.RequireDigit = true;
|
|
options.Password.RequireUppercase = true;
|
|
options.Password.RequireLowercase = true;
|
|
options.Password.RequireNonAlphanumeric = false;
|
|
options.Password.RequiredLength = 8;
|
|
options.Password.RequiredUniqueChars = 4;
|
|
options.SignIn.RequireConfirmedAccount = false;
|
|
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
|
|
options.Lockout.MaxFailedAccessAttempts = 5;
|
|
options.Lockout.AllowedForNewUsers = true;
|
|
})
|
|
.AddEntityFrameworkStores<ApplicationDbContext>()
|
|
.AddDefaultTokenProviders();
|
|
|
|
builder.Services.ConfigureApplicationCookie(options =>
|
|
{
|
|
options.LoginPath = "/login";
|
|
options.LogoutPath = "/logout";
|
|
});
|
|
|
|
// External authentication providers (only registered when configured)
|
|
var authBuilder = builder.Services.AddAuthentication();
|
|
|
|
var googleSection = builder.Configuration.GetSection("Authentication:Google");
|
|
if (!string.IsNullOrEmpty(googleSection["ClientId"]) && !string.IsNullOrEmpty(googleSection["ClientSecret"]))
|
|
{
|
|
authBuilder.AddGoogle(options =>
|
|
{
|
|
options.ClientId = googleSection["ClientId"]!;
|
|
options.ClientSecret = googleSection["ClientSecret"]!;
|
|
});
|
|
}
|
|
|
|
var microsoftSection = builder.Configuration.GetSection("Authentication:Microsoft");
|
|
if (!string.IsNullOrEmpty(microsoftSection["ClientId"]) && !string.IsNullOrEmpty(microsoftSection["ClientSecret"]))
|
|
{
|
|
authBuilder.AddMicrosoftAccount(options =>
|
|
{
|
|
options.ClientId = microsoftSection["ClientId"]!;
|
|
options.ClientSecret = microsoftSection["ClientSecret"]!;
|
|
});
|
|
}
|
|
|
|
var facebookSection = builder.Configuration.GetSection("Authentication:Facebook");
|
|
if (!string.IsNullOrEmpty(facebookSection["ClientId"]) && !string.IsNullOrEmpty(facebookSection["ClientSecret"]))
|
|
{
|
|
authBuilder.AddFacebook(options =>
|
|
{
|
|
options.ClientId = facebookSection["ClientId"]!;
|
|
options.ClientSecret = facebookSection["ClientSecret"]!;
|
|
});
|
|
}
|
|
|
|
// Application services
|
|
builder.Services.AddScoped<IAccountRepository, AccountRepository>();
|
|
builder.Services.AddScoped<ITransactionRepository, TransactionRepository>();
|
|
builder.Services.AddScoped<ICategoryRepository, CategoryRepository>();
|
|
builder.Services.AddScoped<ISalaryRepository, SalaryRepository>();
|
|
builder.Services.AddScoped<DashboardService>();
|
|
builder.Services.AddScoped<ToastService>();
|
|
builder.Services.AddScoped<AppState>();
|
|
|
|
builder.Services.AddCascadingAuthenticationState();
|
|
|
|
// Add services to the container.
|
|
builder.Services.AddRazorComponents()
|
|
.AddInteractiveServerComponents();
|
|
|
|
var app = builder.Build();
|
|
|
|
// Auto-migrate database
|
|
using (var scope = app.Services.CreateScope())
|
|
{
|
|
var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
|
db.Database.Migrate();
|
|
}
|
|
|
|
// Configure the HTTP request pipeline.
|
|
if (!app.Environment.IsDevelopment())
|
|
{
|
|
app.UseExceptionHandler("/Error", createScopeForErrors: true);
|
|
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
|
app.UseHsts();
|
|
}
|
|
app.UseStatusCodePagesWithReExecute("/not-found");
|
|
app.UseHttpsRedirection();
|
|
|
|
app.UseAuthentication();
|
|
app.UseAuthorization();
|
|
app.UseAntiforgery();
|
|
|
|
app.MapStaticAssets();
|
|
app.MapRazorComponents<App>()
|
|
.AddInteractiveServerRenderMode();
|
|
|
|
// External authentication endpoints
|
|
app.MapPost("/api/auth/external-login", (HttpContext context) =>
|
|
{
|
|
var provider = context.Request.Form["provider"].ToString();
|
|
var returnUrl = context.Request.Form["returnUrl"].ToString();
|
|
if (string.IsNullOrEmpty(returnUrl)) returnUrl = "/";
|
|
|
|
var redirectUrl = $"/api/auth/external-callback?returnUrl={Uri.EscapeDataString(returnUrl)}";
|
|
var properties = new Microsoft.AspNetCore.Authentication.AuthenticationProperties { RedirectUri = redirectUrl };
|
|
return Results.Challenge(properties, [provider]);
|
|
});
|
|
|
|
app.MapGet("/api/auth/external-callback", async (
|
|
HttpContext context,
|
|
SignInManager<ApplicationUser> signInManager,
|
|
UserManager<ApplicationUser> userManager,
|
|
string? returnUrl) =>
|
|
{
|
|
returnUrl ??= "/";
|
|
var info = await signInManager.GetExternalLoginInfoAsync();
|
|
if (info is null)
|
|
return Results.Redirect("/login");
|
|
|
|
var signInResult = await signInManager.ExternalLoginSignInAsync(
|
|
info.LoginProvider, info.ProviderKey, isPersistent: false);
|
|
|
|
if (signInResult.Succeeded)
|
|
return Results.Redirect(returnUrl);
|
|
|
|
// Auto-register: create user from external claims
|
|
var email = info.Principal.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
|
|
var userName = email ?? info.Principal.FindFirst(System.Security.Claims.ClaimTypes.Name)?.Value
|
|
?? $"{info.LoginProvider}_{info.ProviderKey}";
|
|
|
|
var user = new ApplicationUser { UserName = userName, Email = email };
|
|
var createResult = await userManager.CreateAsync(user);
|
|
if (createResult.Succeeded)
|
|
{
|
|
await userManager.AddLoginAsync(user, info);
|
|
await signInManager.SignInAsync(user, isPersistent: false);
|
|
return Results.Redirect(returnUrl);
|
|
}
|
|
|
|
// If the email is already taken, try linking
|
|
if (email is not null)
|
|
{
|
|
var existingUser = await userManager.FindByEmailAsync(email);
|
|
if (existingUser is not null)
|
|
{
|
|
await userManager.AddLoginAsync(existingUser, info);
|
|
await signInManager.SignInAsync(existingUser, isPersistent: false);
|
|
return Results.Redirect(returnUrl);
|
|
}
|
|
}
|
|
|
|
return Results.Redirect("/login");
|
|
});
|
|
|
|
app.Run();
|