Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
@* <link rel="stylesheet" href="@Assets["lib/bootstrap/dist/css/bootstrap.min.css"]" /> *@
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["UltimateAuth.Sample.UAuthHub.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/png" href="~/UltimateAuth-Logo.png" />
<link rel="icon" type="image/png" href="UltimateAuth-Logo.png" />

<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" />
<link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,101 @@
@using CodeBeam.UltimateAuth.Core.Abstractions
@using CodeBeam.UltimateAuth.Server.Infrastructure
@inherits LayoutComponentBase
@inherits UAuthHubLayoutComponentBase
@inject IUAuthClient UAuthClient
@inject ISnackbar Snackbar
@inject NavigationManager Nav

@Body
@if (!IsHubAuthorized)
{
<MudAppBar Class="uauth-blur" Color="Color.Transparent" Dense="true" Elevation="0">
<UAuthLogo />
<MudText Class="ml-2 cursor-pointer" Style="user-select: none" @onclick="@(() => Nav.NavigateTo("/home", true))"><b>UltimateAuth</b></MudText>
<MudDivider Class="ml-3 mr-1" Vertical="true" />
<MudText Class="ml-2" Style="line-height: 14px" Typo="Typo.subtitle2">UAuthHub Sample</MudText>

<div id="blazor-error-ui" data-nosnippet>
<MudSpacer />

<MudIconButton Icon="@(DarkModeManager.IsDarkMode? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)" OnClick="@(() => DarkModeManager.ToggleAsync())" />
</MudAppBar>

<MudPage Class="d-flex align-center justify-center" FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
<MudPaper Class="pa-8" Elevation="4" Style="max-width: 520px; width: 100%;">
<MudStack Spacing="3" AlignItems="AlignItems.Center">
<UAuthLogo Size="72" />

<MudText Typo="Typo.h5"><b>Access Denied</b></MudText>

<MudText Typo="Typo.body2" Align="Align.Center">
This page cannot be accessed directly.
UAuthHub login flows can only be initiated by an authorized client application.
</MudText>

<MudDivider Class="my-2" />

<MudText Typo="Typo.caption" Color="Color.Secondary">
UltimateAuth protects this resource based on your session and permissions.
</MudText>
</MudStack>
</MudPaper>
</MudPage>
return;
}

<MudLayout>
<MudAppBar Class="uauth-blur" Color="Color.Transparent" Dense="true" Elevation="0">
<UAuthLogo />
<MudText Class="ml-2 cursor-pointer" Style="user-select: none" @onclick="@(() => Nav.NavigateTo("/home", true))"><b>UltimateAuth</b></MudText>
<MudDivider Class="ml-3 mr-1" Vertical="true" />
<MudText Class="ml-2" Style="line-height: 14px" Typo="Typo.subtitle2">UAuthHub Sample</MudText>

<MudSpacer />

<MudIconButton Icon="@(DarkModeManager.IsDarkMode? Icons.Material.Filled.LightMode : Icons.Material.Filled.DarkMode)" OnClick="@(() => DarkModeManager.ToggleAsync())" />

<UAuthStateView>
<Authorized Context="state">
<MudMenu PopoverClass="mud-theme-primary uauth-menu-popover" AnchorOrigin="Origin.BottomRight" TransformOrigin="Origin.TopRight">
<ActivatorContent>
<div @onclick="@(() => context.ToggleAsync())">
<MudBadge Overlap="true" Dot="true" Color="@GetBadgeColor()">
<MudAvatar Class="cursor-pointer mud-ripple" Variant="Variant.Filled" Color="Color.Primary">
<MudText Typo="Typo.subtitle2">@((state.Identity?.DisplayName ?? "?").Trim() is var n ? (n.Length >= 2 ? n[..2] : n[..1]) : "?")</MudText>
</MudAvatar>
</MudBadge>
</div>
</ActivatorContent>

<ChildContent>
<MudText Class="px-4 py-2"><b>@state.Identity?.DisplayName</b></MudText>
<MudText Class="px-4" Typo="Typo.subtitle2">@string.Join(", ", state.Claims.Roles)</MudText>

<MudDivider />

<MudMenuItem Icon="@Icons.Material.Filled.Refresh" IconColor="Color.Secondary" Label="Refresh Session" OnClick="@Refresh" />
<MudMenuItem Icon="@Icons.Material.Filled.VerifiedUser" IconColor="Color.Secondary" Label="Validate Session" OnClick="@Validate" />
<MudMenuItem Icon="@Icons.Material.Filled.Logout" IconColor="Color.Secondary" Label="Logout" OnClick="@Logout" />

@if (state.Identity?.SessionState is not null && state.Identity.SessionState != SessionState.Active)
{
<MudDivider />
<MudMenuItem Icon="@Icons.Material.Filled.Login" Label="Reauthenticate" OnClick="@GoToLoginWithReturn" />
}
</ChildContent>
</MudMenu>
</Authorized>

<NotAuthorized>
<MudChip T="string" Style="width: fit-content" Text="Sign In" Color="Color.Primary" Variant="Variant.Filled" Class="cursor-pointer" OnClick="@HandleSignInClick" />
</NotAuthorized>
</UAuthStateView>
</MudAppBar>

<MudMainContent>
@Body
</MudMainContent>
</MudLayout>


<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
Original file line number Diff line number Diff line change
@@ -1,7 +1,130 @@
namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout
using CodeBeam.UltimateAuth.Client;
using CodeBeam.UltimateAuth.Client.Errors;
using CodeBeam.UltimateAuth.Core.Contracts;
using CodeBeam.UltimateAuth.Core.Domain;
using CodeBeam.UltimateAuth.Core.Errors;
using CodeBeam.UltimateAuth.Sample.UAuthHub.Infrastructure;
using Microsoft.AspNetCore.Components;
using MudBlazor;

namespace CodeBeam.UltimateAuth.Sample.UAuthHub.Components.Layout;

public partial class MainLayout
{
public partial class MainLayout
[CascadingParameter]
public UAuthState UAuth { get; set; } = default!;

[CascadingParameter]
public DarkModeManager DarkModeManager { get; set; } = default!;

private async Task Refresh()
{

await UAuthClient.Flows.RefreshAsync();
}

private async Task Logout()
{
await UAuthClient.Flows.LogoutAsync();
}

private Color GetBadgeColor()
{
if (UAuth is null || !UAuth.IsAuthenticated)
return Color.Error;

if (UAuth.IsStale)
return Color.Warning;

var state = UAuth.Identity?.SessionState;

if (state is null || state == SessionState.Active)
return Color.Success;

if (state == SessionState.Invalid)
return Color.Error;

return Color.Warning;
}

private void HandleSignInClick()
{
var uri = Nav.ToAbsoluteUri(Nav.Uri);

if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase))
{
Nav.NavigateTo("/login?focus=1", replace: true, forceLoad: true);
return;
}

GoToLoginWithReturn();
}

private async Task Validate()
{
try
{
var result = await UAuthClient.Flows.ValidateAsync();

if (result.IsValid)
{
if (result.Snapshot?.Identity.UserStatus == UserStatus.SelfSuspended)
{
Snackbar.Add("Your account is suspended by you.", Severity.Warning);
return;
}
Snackbar.Add($"Session active • Tenant: {result.Snapshot?.Identity?.Tenant.Value} • User: {result.Snapshot?.Identity?.PrimaryUserName}", Severity.Success);
}
else
{
switch (result.State)
{
case SessionState.Expired:
Snackbar.Add("Session expired. Please sign in again.", Severity.Warning);
break;

case SessionState.DeviceMismatch:
Snackbar.Add("Session invalid for this device.", Severity.Error);
break;

default:
Snackbar.Add($"Session state: {result.State}", Severity.Error);
break;
}
}
}
catch (UAuthTransportException)
{
Snackbar.Add("Network error.", Severity.Error);
}
catch (UAuthProtocolException)
{
Snackbar.Add("Invalid response.", Severity.Error);
}
catch (UAuthException ex)
{
Snackbar.Add($"UAuth error: {ex.Message}", Severity.Error);
}
catch (Exception ex)
{
Snackbar.Add($"Unexpected error: {ex.Message}", Severity.Error);
}
}

private void GoToLoginWithReturn()
{
var uri = Nav.ToAbsoluteUri(Nav.Uri);

if (uri.AbsolutePath.EndsWith("/login", StringComparison.OrdinalIgnoreCase))
{
Nav.NavigateTo("/login", replace: true);
return;
}

var current = Nav.ToBaseRelativePath(uri.ToString());
if (string.IsNullOrWhiteSpace(current))
current = "home";

var returnUrl = Uri.EscapeDataString("/" + current.TrimStart('/'));
Nav.NavigateTo($"/login?returnUrl={returnUrl}", replace: true);
}
}
Loading
Loading