Multi tenant integration #10
@@ -67,7 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<span class="user-name">@context.User.Identity?.Name</span>
|
<span class="user-name">@context.User.Identity?.Name</span>
|
||||||
<span class="user-status">@_companyName</span>
|
<span class="user-status">@GetUserStatus(context.User.Identity?.Name)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@@ -159,4 +159,12 @@
|
|||||||
return parts[0][..Math.Min(2, parts[0].Length)].ToUpper();
|
return parts[0][..Math.Min(2, parts[0].Length)].ToUpper();
|
||||||
return $"{parts[0][0]}{parts[1][0]}".ToUpper();
|
return $"{parts[0][0]}{parts[1][0]}".ToUpper();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string GetUserStatus(string? username)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(username)) return "";
|
||||||
|
if (username.Equals("marc", StringComparison.OrdinalIgnoreCase)) return "SuperAdmin";
|
||||||
|
if (_companyName == "Hauptdomain") return "Einzelnutzer";
|
||||||
|
return _companyName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +102,7 @@ else
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
<MudChip T="string" Color="Color.Default" Variant="Variant.Text" Size="Size.Small">
|
<MudChip T="string" Color="Color.Default" Variant="Variant.Text" Size="Size.Small">
|
||||||
Hauptdomain (SuperUser)
|
@(context.Username == "marc" ? "SuperAdmin" : "Einzelnutzer")
|
||||||
</MudChip>
|
</MudChip>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,6 +91,16 @@ else
|
|||||||
Zurück zum Login
|
Zurück zum Login
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
} else {
|
||||||
|
@* ── Header / Navigation ── *@
|
||||||
|
@if (_activeTab == 2)
|
||||||
|
{
|
||||||
|
@* ── Back Button for Firm Registration ── *@
|
||||||
|
<MudStack Row="true" AlignItems="AlignItems.Center" Class="mb-4">
|
||||||
|
<MudIconButton Icon="@Icons.Material.Filled.ArrowBack" OnClick="@(() => SetTab(0))" Color="Color.Primary" />
|
||||||
|
<MudText Typo="Typo.h6" Style="font-weight:700">Firma registrieren</MudText>
|
||||||
|
</MudStack>
|
||||||
|
<MudDivider Class="mb-6" />
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -106,11 +116,11 @@ else
|
|||||||
Variant="@(_activeTab == 1 ? Variant.Filled : Variant.Text)"
|
Variant="@(_activeTab == 1 ? Variant.Filled : Variant.Text)"
|
||||||
Color="Color.Primary"
|
Color="Color.Primary"
|
||||||
Style="min-width: 120px; border-radius: 20px;">
|
Style="min-width: 120px; border-radius: 20px;">
|
||||||
@(_tenantInfo.IsTenant ? "Registrieren" : "Firma registrieren")
|
Registrieren
|
||||||
</MudButton>
|
</MudButton>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
|
|
||||||
<MudDivider Class="mb-6" />
|
<MudDivider Class="mb-6" />
|
||||||
|
}
|
||||||
|
|
||||||
@if (_activeTab == 0)
|
@if (_activeTab == 0)
|
||||||
{
|
{
|
||||||
@@ -156,7 +166,7 @@ else
|
|||||||
</EditForm>
|
</EditForm>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
else
|
else if (_activeTab == 1)
|
||||||
{
|
{
|
||||||
@if (_tenantInfo.IsTenant)
|
@if (_tenantInfo.IsTenant)
|
||||||
{
|
{
|
||||||
@@ -210,6 +220,66 @@ else
|
|||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
@* ── Single User Register Form ── *@
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
@if (_error != null)
|
||||||
|
{
|
||||||
|
<MudAlert Severity="Severity.Error" Dense="true">@_error</MudAlert>
|
||||||
|
}
|
||||||
|
<EditForm Model="@_registerModel" OnValidSubmit="HandleSingleUserRegister">
|
||||||
|
<MudStack Spacing="3">
|
||||||
|
<MudTextField T="string"
|
||||||
|
Label="Benutzername"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Person"
|
||||||
|
@bind-Value="_registerModel.Username"
|
||||||
|
Required="true"
|
||||||
|
HelperText="Mindestens 3 Zeichen" />
|
||||||
|
<MudTextField T="string"
|
||||||
|
Label="Passwort"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||||
|
InputType="InputType.Password"
|
||||||
|
@bind-Value="_registerModel.Password"
|
||||||
|
Required="true"
|
||||||
|
HelperText="Mindestens 6 Zeichen" />
|
||||||
|
<MudTextField T="string"
|
||||||
|
Label="Passwort wiederholen"
|
||||||
|
Variant="Variant.Outlined"
|
||||||
|
Adornment="Adornment.Start"
|
||||||
|
AdornmentIcon="@Icons.Material.Filled.Lock"
|
||||||
|
InputType="InputType.Password"
|
||||||
|
@bind-Value="_confirmPassword"
|
||||||
|
Required="true" />
|
||||||
|
<input type="text" style="display: none;" tabindex="-1" autocomplete="off" @bind="_honeypot" />
|
||||||
|
|
||||||
|
<MudCheckBox @bind-Value="_acceptAgb" Color="Color.Secondary" Class="mt-1">
|
||||||
|
<MudText Typo="Typo.body2">Ich akzeptiere die <a href="/agb" target="_blank" style="color: var(--mud-palette-secondary); text-decoration: underline; font-weight: 600;">AGB</a>.</MudText>
|
||||||
|
</MudCheckBox>
|
||||||
|
|
||||||
|
<MudButton ButtonType="ButtonType.Submit"
|
||||||
|
Variant="Variant.Filled"
|
||||||
|
Color="Color.Secondary"
|
||||||
|
FullWidth="true"
|
||||||
|
Size="Size.Large"
|
||||||
|
StartIcon="@Icons.Material.Filled.PersonAdd"
|
||||||
|
Class="mt-2"
|
||||||
|
Disabled="_loading || !_acceptAgb">
|
||||||
|
@if (_loading)
|
||||||
|
{
|
||||||
|
<MudProgressCircular Size="Size.Small" Indeterminate="true" Class="mr-2" />
|
||||||
|
}
|
||||||
|
Konto erstellen
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
|
</EditForm>
|
||||||
|
</MudStack>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (_activeTab == 2)
|
||||||
{
|
{
|
||||||
@* ── Firm Register Form ── *@
|
@* ── Firm Register Form ── *@
|
||||||
<MudStack Spacing="3">
|
<MudStack Spacing="3">
|
||||||
@@ -288,6 +358,18 @@ else
|
|||||||
</EditForm>
|
</EditForm>
|
||||||
</MudStack>
|
</MudStack>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (!_tenantInfo.IsTenant && _activeTab != 2)
|
||||||
|
{
|
||||||
|
<MudDivider Class="my-4" />
|
||||||
|
<MudStack AlignItems="AlignItems.Center">
|
||||||
|
<MudText Typo="Typo.body2" Color="Color.Secondary">
|
||||||
|
Sie möchten Timetracker für Ihr Unternehmen nutzen?
|
||||||
|
</MudText>
|
||||||
|
<MudButton Variant="Variant.Text" Color="Color.Primary" OnClick="@(() => SetTab(2))" StartIcon="@Icons.Material.Filled.Business">
|
||||||
|
Eigene Firma registrieren
|
||||||
|
</MudButton>
|
||||||
|
</MudStack>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</MudPaper>
|
</MudPaper>
|
||||||
@@ -303,6 +385,7 @@ else
|
|||||||
private string _honeypot = "";
|
private string _honeypot = "";
|
||||||
private bool _acceptAgb;
|
private bool _acceptAgb;
|
||||||
private bool _firmRegisteredSuccessfully;
|
private bool _firmRegisteredSuccessfully;
|
||||||
|
private string _confirmPassword = "";
|
||||||
|
|
||||||
private readonly AuthModel _loginModel = new();
|
private readonly AuthModel _loginModel = new();
|
||||||
private readonly AuthModel _registerModel = new();
|
private readonly AuthModel _registerModel = new();
|
||||||
@@ -432,6 +515,44 @@ else
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task HandleSingleUserRegister()
|
||||||
|
{
|
||||||
|
if (!_acceptAgb)
|
||||||
|
{
|
||||||
|
_error = "Du musst die AGB akzeptieren.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_registerModel.Password != _confirmPassword)
|
||||||
|
{
|
||||||
|
_error = "Passwörter stimmen nicht überein.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loading = true;
|
||||||
|
_error = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (user, error) = await AuthService.RegisterAsync(_registerModel.Username, _registerModel.Password, _honeypot);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
await JSRuntime.InvokeVoidAsync("localStorage.setItem", "showOnboarding", "true");
|
||||||
|
Nav.NavigateTo("/", forceLoad: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_error = error ?? "Registrierung fehlgeschlagen.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_error = $"Registrierungs Fehler: {ex.Message}";
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task HandleRegisterFirm()
|
private async Task HandleRegisterFirm()
|
||||||
{
|
{
|
||||||
if (!_acceptAgb)
|
if (!_acceptAgb)
|
||||||
|
|||||||
@@ -97,15 +97,15 @@ public class AuthService(
|
|||||||
return (null, "Passwort muss mindestens 6 Zeichen lang sein.");
|
return (null, "Passwort muss mindestens 6 Zeichen lang sein.");
|
||||||
|
|
||||||
var tenantId = tenantProvider.TenantId;
|
var tenantId = tenantProvider.TenantId;
|
||||||
if (tenantId == null)
|
if (tenantId == null && username.Equals("marc", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
return (null, "Mitarbeiter-Registrierung ist nur über die Firmen-Subdomain möglich.");
|
return (null, "Dieser Benutzername ist reserviert.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await using var db = await factory.CreateDbContextAsync();
|
await using var db = await factory.CreateDbContextAsync();
|
||||||
// Filter handles checking uniqueness within the same tenant
|
// Filter handles checking uniqueness within the same tenant context
|
||||||
if (await db.Users.AnyAsync(u => u.Username == username))
|
if (await db.Users.AnyAsync(u => u.Username == username))
|
||||||
return (null, "Benutzername in dieser Firma bereits vergeben.");
|
return (null, tenantId.HasValue ? "Benutzername in dieser Firma bereits vergeben." : "Benutzername bereits vergeben.");
|
||||||
|
|
||||||
var (hash, salt) = HashPassword(password);
|
var (hash, salt) = HashPassword(password);
|
||||||
var user = new User
|
var user = new User
|
||||||
@@ -113,7 +113,7 @@ public class AuthService(
|
|||||||
Username = username,
|
Username = username,
|
||||||
PasswordHash = hash,
|
PasswordHash = hash,
|
||||||
PasswordSalt = salt,
|
PasswordSalt = salt,
|
||||||
TenantId = tenantId.Value
|
TenantId = tenantId
|
||||||
};
|
};
|
||||||
db.Users.Add(user);
|
db.Users.Add(user);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
|
|||||||
@@ -219,9 +219,9 @@ app.MapPost("/api/auth/login", async (HttpContext ctx, [FromBody] LoginRequest r
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (user.Username != "marc")
|
if (user.Username != "marc" && user.TenantId != null)
|
||||||
{
|
{
|
||||||
return Results.BadRequest("Anmeldung auf der Hauptdomain nur für Administratoren.");
|
return Results.BadRequest("Benutzer gehört zu einer Firma. Bitte melden Sie sich über Ihre Firmen-Subdomain an.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user