Compare commits

...

2 Commits

Author SHA1 Message Date
beo3000 8a92555ce9 add authorizazion und authentication 2025-12-21 19:31:54 +01:00
beo3000 b8e18a52b3 seed super-admin 2025-12-21 14:58:46 +01:00
30 changed files with 676 additions and 28 deletions

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Koogle.Application.DTOs
{
public record LoginDto
{
public string Email { get; set; } = string.Empty;
public string Password { get; set; } = string.Empty;
public string ClubName { get; set; } = string.Empty;
public bool RememberMe { get; set; }
public string? ReturnUrl { get; set; }
}
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Koogle.Infrastructure.Identity;
namespace Koogle.Application.DTOs
{
public record UserDto
{
public ApplicationUser ApplicationUser { get; set; } = null!;
}
}

View File

@ -26,8 +26,8 @@ namespace Koogle.Application
//services.AddScoped<IDayService, DayService>();
//services.AddScoped<IYearService, YearService>();
//services.AddScoped<ICompanyService, CompanyService>();
//services.AddScoped<IUserService, UserService>();
//services.AddScoped<ITagService, TagService>();
services.AddScoped<IUserService, UserService>();
services.AddScoped<ICurrentClubContext, CurrentClubContext>();
//services.AddScoped<IAuthorizationService, AuthorizationService>();
return services;

View File

@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Koogle.Application.Interfaces;
/// <summary>
/// Ermöglicht den Zugriff auf den aktuellen Club-Kontext des angemeldeten Benutzers.
/// </summary>
public interface ICurrentClubContext
{
/// <summary>
/// Id des aktuellen Clubs des angemeldeten Benutzers.
/// </summary>
Guid ClubId { get; }
/// <summary>
/// Name des aktuellen Clubs des angemeldeten Benutzers.
/// </summary>
string ClubName { get; }
}

View File

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Koogle.Application.DTOs;
namespace Koogle.Application.Interfaces
{
public interface IUserService
{
Task<UserDto?> LoginAsync(LoginDto login, CancellationToken cancellationToken = default);
Task SignOutAsync();
}
}

View File

@ -0,0 +1,28 @@
using Koogle.Application.Interfaces;
using Microsoft.AspNetCore.Http;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace Koogle.Application.Services;
/// <inheritdoc />
public class CurrentClubContext(IHttpContextAccessor http) : ICurrentClubContext
{
/// <inheritdoc />
public Guid ClubId
{
get
{
var value = http.HttpContext?.User?.FindFirstValue("current_club_id");
return Guid.TryParse(value, out var id) ? id : Guid.Empty;
}
}
/// <inheritdoc />
public string ClubName =>
http.HttpContext?.User?.FindFirstValue("current_club_name") ?? string.Empty;
}

View File

@ -0,0 +1,128 @@
using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Infrastructure.Identity;
using KoogleApp.Data;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace Koogle.Application.Services;
public class UserService : IUserService
{
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly UserManager<ApplicationUser> _userManager;
private readonly AppDbContext _appDb;
private readonly IHostEnvironmentAuthenticationStateProvider? _hostAuthentication;
public UserService(SignInManager<ApplicationUser> signInManager,
UserManager<ApplicationUser> userManager,
IHostEnvironmentAuthenticationStateProvider? hostAuthentication,
AppDbContext appDb)
{
_signInManager = signInManager;
_userManager = userManager;
_appDb = appDb;
_hostAuthentication = hostAuthentication;
}
public async Task<UserDto?> LoginAsync(LoginDto login, CancellationToken cancellationToken)
{
// 1) User finden
var user = await _userManager.FindByEmailAsync(login.Email);
if (user == null)
{
return null;
}
// 2) Passwort prüfen
var result = await _signInManager.CheckPasswordSignInAsync(user, login.Password, lockoutOnFailure: false);
if (!result.Succeeded)
{
return null;
}
// 3) Club anhand Name finden (case-insensitive)
var clubName = login.ClubName?.Trim();
if (string.IsNullOrWhiteSpace(clubName))
{
return null;
}
var club = await _appDb.Clubs
.IgnoreQueryFilters() // optional, falls du SoftDelete filterst und trotzdem finden willst
.FirstOrDefaultAsync(c => c.Name.ToLower() == clubName.ToLower() && !c.IsDeleted);
if (club == null)
{
return null;
}
// 4) Berechtigung prüfen: SuperAdmin ODER Mitglied im Club
var isSuperAdmin = await _userManager.IsInRoleAsync(user, "SuperAdmin");
if (!isSuperAdmin)
{
var profile = await _appDb.UserProfiles
.FirstOrDefaultAsync(p => p.IdentityUserId == user.Id);
if (profile == null)
{
return null;
}
// Mitgliedschaft prüfen (UserProfileClub)
var isMember = await _appDb.UserProfileClubs.AnyAsync(upc =>
upc.UserProfileId == profile.Id &&
upc.ClubId == club.Id &&
!upc.Club.IsDeleted &&
!upc.UserProfile.IsDeleted);
if (!isMember)
{
return null;
}
}
// 5) Claims für aktuellen Club setzen (im Cookie!)
var additionalClaims = new List<Claim>
{
new Claim("current_club_id", club.Id.ToString()),
new Claim("current_club_name", club.Name)
};
//ClaimsPrincipal principal = await _signInManager.CreateUserPrincipalAsync(user);
//_signInManager.Context.User = principal;
//_signInManager.SignInAsync()
//_hostAuthentication!.SetAuthenticationState(
// Task.FromResult(new AuthenticationState(principal)));
// 6) SignIn mit zusätzlichen Claims (werden im Cookie gespeichert) [1](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.identity.signinmanager-1.signinwithclaimsasync?view=aspnetcore-9.0)
try
{
await _signInManager.SignInWithClaimsAsync(user, isPersistent: login.RememberMe, additionalClaims: additionalClaims);
}
catch (Exception)
{
throw;
}
// 7) UserDto zurückgeben
return new UserDto
{
ApplicationUser = user
};
}
public async Task SignOutAsync()
{
await _signInManager.SignOutAsync();
}
}

View File

@ -53,7 +53,12 @@ public static class DependencyInjection
.AddDefaultTokenProviders()
.AddClaimsPrincipalFactory<CustomClaimsPrincipalFactory>();
services.AddAuthentication("Cookies")
.AddCookie("Cookies", options =>
{
options.LoginPath = "/login";
options.AccessDeniedPath = "/login";
});
services.AddAuthorization(options =>
{

View File

@ -0,0 +1,137 @@
using Koogle.Domain.Entities;
using Koogle.Infrastructure.Identity;
using KoogleApp.Data;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace Koogle.Infrastructure.Security;
public static class BootstrapSeeder
{
public static async Task SeedAsync(IServiceProvider services, IConfiguration config, IHostEnvironment env)
{
// Nur in Dev automatisch Prod nur mit explizitem Flag
var allowSeed = env.IsDevelopment() || config.GetValue<bool>("Bootstrap:EnableSeeding");
if (!allowSeed) return;
using var scope = services.CreateScope();
var roleManager = scope.ServiceProvider.GetRequiredService<RoleManager<ApplicationRole>>();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var appDb = scope.ServiceProvider.GetRequiredService<AppDbContext>();
// 1) Rollen
var roles = new[] { "SuperAdmin", "Admin", "Editor", "Viewer" };
foreach (var r in roles)
{
if (!await roleManager.RoleExistsAsync(r))
await roleManager.CreateAsync(new ApplicationRole { Name = r });
}
// 2) SuperAdmin User (Identity)
var adminEmail = config["Bootstrap:SuperAdmin:Email"];
var adminUserName = config["Bootstrap:SuperAdmin:UserName"] ?? adminEmail;
var adminPassword = config["Bootstrap:SuperAdmin:Password"];
if (string.IsNullOrWhiteSpace(adminEmail) || string.IsNullOrWhiteSpace(adminPassword))
throw new InvalidOperationException("Bootstrap SuperAdmin credentials missing. Set Bootstrap:SuperAdmin:Email and :Password.");
var adminUser = await userManager.FindByEmailAsync(adminEmail);
if (adminUser == null)
{
adminUser = new ApplicationUser
{
UserName = adminUserName,
Email = adminEmail,
EmailConfirmed = env.IsDevelopment() // Dev: praktisch, Prod: lieber false + Confirm Flow
};
var createResult = await userManager.CreateAsync(adminUser, adminPassword);
if (!createResult.Succeeded)
throw new InvalidOperationException("Failed to create SuperAdmin: " +
string.Join("; ", createResult.Errors.Select(e => $"{e.Code}:{e.Description}")));
}
// 3) Rolle zuweisen
if (!await userManager.IsInRoleAsync(adminUser, "SuperAdmin"))
await userManager.AddToRoleAsync(adminUser, "SuperAdmin");
// 4) Domain UserProfile anlegen
var profile = await appDb.UserProfiles.SingleOrDefaultAsync(p => p.IdentityUserId == adminUser.Id);
if (profile == null)
{
profile = new UserProfile
{
IdentityUserId = adminUser.Id,
DisplayName = "Super Admin",
Locale = "de-DE",
TimeZone = "Europe/Berlin",
CreatedAt = DateTime.UtcNow
};
appDb.UserProfiles.Add(profile);
await appDb.SaveChangesAsync();
}
// Optional: Default-Club erstellen + Membership + RoleAssignments (nur wenn gewünscht)
if (config.GetValue<bool>("Bootstrap:CreateDefaultClub"))
{
var clubName = config["Bootstrap:DefaultClub:Name"] ?? "Default Club";
var club = await appDb.Clubs.FirstOrDefaultAsync(c => c.Name == clubName);
if (club == null)
{
club = new Club { Name = clubName, CreatedAt = DateTime.UtcNow };
appDb.Clubs.Add(club);
await appDb.SaveChangesAsync();
}
// Membership (UserProfileClub) wenn du das nutzt
var membership = await appDb.UserProfileClubs
.FindAsync(profile.Id, club.Id);
if (membership == null)
{
membership = new UserProfileClub
{
UserProfileId = profile.Id,
ClubId = club.Id,
IsDefault = true,
AssignedAt = DateTime.UtcNow,
AssignedById = adminUser.Id
};
appDb.UserProfileClubs.Add(membership);
await appDb.SaveChangesAsync();
}
// Club-Rollen assignments (B2): Admin/Editor/Viewer o.ä.
// Hier: Admin im Default-Club
var roleExists = await appDb.UserProfileClubRoleAssignments.AnyAsync(a =>
a.UserProfileId == profile.Id && a.ClubId == club.Id && a.RoleName == "Admin" && !a.IsDeleted);
if (!roleExists)
{
appDb.UserProfileClubRoleAssignments.Add(new UserProfileClubRoleAssignment
{
UserProfileId = profile.Id,
ClubId = club.Id,
RoleId = Guid.Empty, // optional, wenn du RoleId nicht zwingend brauchst
RoleName = "Admin",
AssignedAt = DateTime.UtcNow,
AssignedById = adminUser.Id,
CreatedAt = DateTime.UtcNow
});
await appDb.SaveChangesAsync();
}
}
}
}

View File

@ -7,13 +7,25 @@
<base href="/" />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["Koogle.Web.styles.css"]" />
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="@Assets["_content/MudBlazor/MudBlazor.min.css"]" rel="stylesheet" />
<ImportMap />
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<Routes @rendermode="RenderModeForPage" />
<script src="_framework/blazor.web.js"></script>
<script src="_content/MudBlazor/MudBlazor.min.js"></script>
</body>
</html>
@code {
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
? new InteractiveServerRenderMode(prerender: false)
: new InteractiveServerRenderMode(prerender: true);
}

View File

@ -1,9 +1,13 @@
@using Microsoft.AspNetCore.Authorization
@using Koogle.Application.Interfaces
@using Koogle.Application.Services
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@inject IAuthorizationService AuthorizationService
@inject AuthenticationStateProvider AuthStateProvider
@inject IUserService UserService
@inject NavigationManager NavigationManager
@inject ICurrentClubContext CurrentClubContext
<h3>AuthTest-Component</h3>
@ -14,7 +18,7 @@
}
else
{
<p>Keine Berechtigung</p>
<p>Keine Berechtigung zum Editieren</p>
}
@ -24,10 +28,14 @@ else
</Authorized>
</AuthorizeView>
<AuthorizeView>
aktueller Club: @CurrentClubContext.ClubName
<LogoutButton/>
</AuthorizeView>
@code {
[Parameter] public Guid ClubId { get; set; }
private bool _canEdit;
protected override async Task OnParametersSetAsync()
@ -35,7 +43,7 @@ else
var state = await AuthStateProvider.GetAuthenticationStateAsync();
var user = state.User;
var result = await AuthorizationService.AuthorizeAsync(user, ClubId, "ClubEditor");
var result = await AuthorizationService.AuthorizeAsync(user, CurrentClubContext.ClubId, "ClubEditor");
_canEdit = result.Succeeded;
}

View File

@ -1,5 +1,15 @@
@inherits LayoutComponentBase
@* Required *@
<MudThemeProvider />
<MudPopoverProvider />
@* Needed for dialogs *@
<MudDialogProvider />
@* Needed for snackbars *@
<MudSnackbarProvider />
@Body
<div id="blazor-error-ui" data-nosnippet>

View File

@ -0,0 +1,94 @@
@page "/account/login"
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.WebUtilities
@inject NavigationManager NavigationManager
@inject Microsoft.AspNetCore.Antiforgery.IAntiforgery Antiforgery
@inject IHttpContextAccessor HttpContextAccessor
<form method="post" action="/auth/login">
<!-- Hidden Fields -->
<input type="hidden" name="ReturnUrl" value="@_returnUrl" />
<input type="hidden" name="__RequestVerificationToken" value="@_antiToken" />
<MudPaper Class="pa-6" Elevation="4" MaxWidth="400px">
@if (!string.IsNullOrWhiteSpace(_error))
{
<MudAlert Severity="Severity.Error">@_error</MudAlert>
}
<MudText Typo="Typo.h5" Class="mb-4">
Anmeldung
</MudText>
<MudTextField T="string"
Name="Email"
Label="E-Mail"
Variant="Variant.Outlined"
Required="true"
InputType="InputType.Email"
AutoComplete="username"
Class="mb-3" />
<MudTextField T="string"
Name="Password"
Label="Passwort"
Variant="Variant.Outlined"
Required="true"
InputType="InputType.Password"
AutoComplete="current-password"
Class="mb-3" />
<MudTextField T="string"
Name="ClubName"
Label="Club"
Variant="Variant.Outlined"
Required="true"
Placeholder="Clubname"
Class="mb-3" />
<MudCheckBox T="bool"
Name="RememberMe"
Label="Angemeldet bleiben"
Value="true"
Class="mb-4" />
<MudButton
ButtonType="ButtonType.Submit"
Variant="Variant.Filled"
Color="Color.Primary"
FullWidth="true">
Anmelden
</MudButton>
</MudPaper>
</form>
@code {
private string _returnUrl = "/";
private string? _error = string.Empty;
private string _antiToken = "";
protected override void OnInitialized()
{
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
var query = QueryHelpers.ParseQuery(uri.Query);
if (query.TryGetValue("returnUrl", out var ru)) _returnUrl = ru!;
if (query.TryGetValue("error", out var err))
{
_error = "Login fehlgeschlagen";
}
// Antiforgery Token generieren (klassischer MVC Token)
var http = HttpContextAccessor.HttpContext!;
var tokens = Antiforgery.GetAndStoreTokens(http);
_antiToken = tokens.RequestToken!;
}
}

View File

@ -1,6 +1,9 @@
@page "/"
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@attribute [Authorize]
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>

View File

@ -1,16 +1,23 @@
@using Microsoft.AspNetCore.Components.Authorization
@using Koogle.Web.Components.Layout
@using Microsoft.AspNetCore.Components.Authorization
<Fluxor.Blazor.Web.StoreInitializer />
<CascadingAuthenticationState>
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
<FocusOnNavigate RouteData="routeData" Selector="h1" />
<AuthorizeRouteView RouteData="@routeData"
DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<RedirectToLogin />
</NotAuthorized>
</AuthorizeRouteView>
</Found>
<NotFound>
<LayoutView Layout="@typeof(Layout.MainLayout)">
<p>Sorry, hier gibts nichts unter dieser Adresse.</p>
<p>Seite nicht gefunden.</p>
</LayoutView>
</NotFound>

View File

@ -0,0 +1,26 @@
@using Microsoft.AspNetCore.Antiforgery
@inject IAntiforgery Antiforgery
@inject IHttpContextAccessor HttpContextAccessor
<form method="post" action="/auth/logout">
<input type="hidden" name="__RequestVerificationToken" value="@_token" />
<input type="hidden" name="returnUrl" value="@ReturnUrl" />
<MudButton ButtonType="ButtonType.Submit">@Text</MudButton>
</form>
@code {
[Parameter] public string ReturnUrl { get; set; } = "/account/login";
[Parameter] public string CssClass { get; set; } = "btn btn-link";
[Parameter] public string Text { get; set; } = "Abmelden";
private string _token = string.Empty;
protected override void OnInitialized()
{
var http = HttpContextAccessor.HttpContext
?? throw new InvalidOperationException("Kein HttpContext verfügbar.");
var tokens = Antiforgery.GetAndStoreTokens(http);
_token = tokens.RequestToken ?? string.Empty;
}
}

View File

@ -0,0 +1,8 @@
@inject NavigationManager Navigation
@code {
protected override void OnInitialized()
{
Navigation.NavigateTo("/account/login", true);
}
}

View File

@ -8,3 +8,14 @@
@using Microsoft.JSInterop
@using Koogle.Web
@using Koogle.Web.Components
@* MudBlazor UI Components *@
@using MudBlazor
@* Fluxor State Management *@
@using Fluxor
@using Fluxor.Blazor.Web.Components
@* Koogle Components *@
@using Koogle.Web.Components.Shared

View File

@ -0,0 +1,55 @@
using Koogle.Application.DTOs;
using Koogle.Application.Interfaces;
using Koogle.Infrastructure.Identity;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
namespace Koogle.Web.Controllers
{
/// <summary>
/// Controller für Authentifizierungsaktionen (Login, Logout)
/// </summary>
/// <remarks>
/// UserManagerService nutzt SignInManager der wiederum nicht in einer SignalR methode funktioniert.
/// Aus dem Grund werden Login/Logout Aktionen hier im MVC Controller mit einem "normalen" Post-Request abgewickelt.
/// </remarks>
[Route("auth")]
public class AuthController : Controller
{
private readonly IUserService _userService;
public AuthController(IUserService userService)
{
_userService = userService;
}
[HttpPost("login")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Login(
[FromForm] LoginDto input
)
{
var returnUrl = string.IsNullOrWhiteSpace(input.ReturnUrl) ? "/" : input.ReturnUrl;
var user = await _userService.LoginAsync(input);
if (user != null)
{
return LocalRedirect(returnUrl);
}
return LocalRedirect("/account/login?error=true");
}
[HttpPost("logout")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout([FromForm] string? returnUrl = null)
{
await _userService.SignOutAsync();
// Nach Logout per Redirect neu laden (neuer Request -> neue Auth-Session)
returnUrl ??= "/account/login";
return LocalRedirect(returnUrl);
}
}
}

View File

@ -7,14 +7,21 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Fluxor.Blazor.Web" Version="6.9.0" />
<PackageReference Include="Fluxor.Blazor.Web.ReduxDevTools" Version="6.9.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.11">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="MudBlazor" Version="8.15.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Koogle.Application\Koogle.Application.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Store\" />
</ItemGroup>
</Project>

View File

@ -1,7 +1,12 @@
using Fluxor;
using Fluxor.Blazor.Web.ReduxDevTools;
using Koogle.Application;
using Koogle.Infrastructure;
using Koogle.Infrastructure.Security;
using Koogle.Web.Components;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using MudBlazor.Services;
var builder = WebApplication.CreateBuilder(args);
@ -13,12 +18,30 @@ builder.Services.AddInfrastructure(builder.Configuration);
builder.Services.AddApplication();
// Fluxor State Management
builder.Services.AddFluxor(options =>
{
//options.UseRouting();
options.ScanAssemblies(typeof(Program).Assembly);
#if DEBUG
options.UseReduxDevTools();
#endif
});
builder.Services.AddHttpContextAccessor();
builder.Services.AddControllersWithViews();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// https://dev.to/masanori_msl/blazor-server-signin-with-custom-user-2e74
builder.Services.AddScoped<IHostEnvironmentAuthenticationStateProvider>(sp =>
(ServerAuthenticationStateProvider)sp.GetRequiredService<AuthenticationStateProvider>()
);
builder.Services.AddMudServices();
var app = builder.Build();
@ -32,7 +55,7 @@ if (!app.Environment.IsDevelopment())
app.UseHttpsRedirection();
app.MapControllers();
app.UseAntiforgery();
app.MapStaticAssets();
@ -51,7 +74,7 @@ using (var scope = app.Services.CreateScope())
{
await IdentityRoleSeeder.SeedAsync(scope.ServiceProvider);
}
await BootstrapSeeder.SeedAsync(app.Services, app.Configuration, app.Environment);

View File

@ -1,4 +1,18 @@
{
"Bootstrap": {
"EnableSeeding": true,
"CreateDefaultClub": true,
"SuperAdmin": {
"Email": "ch@koogle.de",
"UserName": "ch@koogle.de",
"Password": "DEV_only3000!"
},
"DefaultClub": {
"Name": "Koogle Demo Club"
}
},
"Logging": {
"LogLevel": {
"Default": "Information",

View File

@ -35,7 +35,7 @@
if (UserId is null || Email is null || Code is null)
{
RedirectManager.RedirectToWithStatus(
"Account/Login", "Error: Invalid email change confirmation link.", HttpContext);
"Account/LoginAsync", "Error: Invalid email change confirmation link.", HttpContext);
}
var user = await UserManager.FindByIdAsync(UserId);

View File

@ -71,13 +71,13 @@
{
if (RemoteError is not null)
{
RedirectManager.RedirectToWithStatus("Account/Login", $"Error from external provider: {RemoteError}", HttpContext);
RedirectManager.RedirectToWithStatus("Account/LoginAsync", $"Error from external provider: {RemoteError}", HttpContext);
}
var info = await SignInManager.GetExternalLoginInfoAsync();
if (info is null)
{
RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext);
RedirectManager.RedirectToWithStatus("Account/LoginAsync", "Error loading external login information.", HttpContext);
}
externalLoginInfo = info;
@ -92,7 +92,7 @@
// We should only reach this page via the login callback, so redirect back to
// the login page if we get here some other way.
RedirectManager.RedirectTo("Account/Login");
RedirectManager.RedirectTo("Account/LoginAsync");
}
}
@ -100,7 +100,7 @@
{
if (externalLoginInfo is null)
{
RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext);
RedirectManager.RedirectToWithStatus("Account/LoginAsync", "Error loading external login information.", HttpContext);
}
// Sign in the user with this external login provider if the user already has a login.
@ -134,7 +134,7 @@
{
if (externalLoginInfo is null)
{
RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information during confirmation.", HttpContext);
RedirectManager.RedirectToWithStatus("Account/LoginAsync", "Error loading external login information during confirmation.", HttpContext);
}
var emailStore = GetEmailStore();

View File

@ -1,4 +1,4 @@
@page "/Account/Login"
@page "/Account/LoginAsync"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Authentication

View File

@ -41,7 +41,7 @@
</div>
<p>
Don't have access to your authenticator device? You can
<a href="Account/LoginWithRecoveryCode?ReturnUrl=@ReturnUrl">log in with a recovery code</a>.
<a href="Account/LoginAsyncWithRecoveryCode?ReturnUrl=@ReturnUrl">log in with a recovery code</a>.
</p>
@code {

View File

@ -15,7 +15,7 @@
<StatusMessage />
@if (currentLogins?.Count > 0)
{
<h3>Registered Logins</h3>
<h3>Registered LoginAsyncs</h3>
<table class="table">
<tbody>
@foreach (var login in currentLogins)

View File

@ -16,7 +16,7 @@
</Authorized>
<NotAuthorized>
<MudLink Color="Color.Inherit" Href="/Account/Login">Log in</MudLink>
<MudLink Color="Color.Inherit" Href="/Account/LoginAsync">Log in</MudLink>
</NotAuthorized>
</AuthorizeView>

View File

@ -3,6 +3,6 @@
@code {
protected override void OnInitialized()
{
NavigationManager.NavigateTo($"Account/Login?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true);
NavigationManager.NavigateTo($"Account/LoginAsync?returnUrl={Uri.EscapeDataString(NavigationManager.Uri)}", forceLoad: true);
}
}

View File

@ -15,6 +15,6 @@ Welcome to your new app.
</Authorized>
<NotAuthorized>
<a href="/account/Register">Register</a>
<a href="/account/Login">Login</a>
<a href="/account/Login">LoginAsync</a>
</NotAuthorized>
</AuthorizeView>