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 @@ -11,4 +11,11 @@ public async Task<AccessContext> CreateAsync(string action, string resource, Can
var flow = await authFlow.BeginAsync(AuthFlowType.ValidateSession, cancellationToken);
return await contextFactory.CreateAsync(flow, action, resource, ct: cancellationToken);
}


public async Task<AccessContext> CreateForUserAsync(string action, string resource, UserKey targetUserKey, CancellationToken ct = default)
{
var flow = await authFlow.BeginAsync(AuthFlowType.ValidateSession, ct);
return await contextFactory.CreateAsync(flow, action, resource, resourceId: targetUserKey.Value, ct: ct);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using CodeBeam.UltimateAuth.Authorization;
using CodeBeam.UltimateAuth.Core.Contracts;
using CodeBeam.UltimateAuth.IdentityLab.Domain;

namespace CodeBeam.UltimateAuth.IdentityLab.Authorization;

Expand All @@ -17,18 +18,27 @@ context.ActorUserKey is null ||
return false;
}

var roleStore = roleStoreFactory.Create(context.ActorTenant);
var tenant = context.ActorTenant;
var roleStore = roleStoreFactory.Create(tenant);

var adminRole = await roleStore.GetByNameAsync(LabRoles.Admin.ToUpperInvariant(), ct);
var roles = await roleStore.GetByIdsAsync(
[LabRoleIds.Admin], ct);

var adminRole = roles.FirstOrDefault(x =>
x.Id == LabRoleIds.Admin &&
x.Tenant == tenant &&
!x.IsDeleted);

if (adminRole is null)
return false;

var userRoleStore = userRoleStoreFactory.Create(context.ActorTenant);
var userRoleStore = userRoleStoreFactory.Create(tenant);

var assignments = await userRoleStore.GetAssignmentsAsync(context.ActorUserKey.Value, ct);
var assignments = await userRoleStore
.GetAssignmentsAsync(context.ActorUserKey.Value, ct);

return assignments.Any(x => x.RoleId == adminRole.Id);
return assignments.Any(x =>
x.RoleId == LabRoleIds.Admin);
}

public async Task EnsureAdminAsync(AccessContext context, CancellationToken ct = default)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace CodeBeam.UltimateAuth.IdentityLab.Authorization;

public static class LabDemoPermissions
{
public const string ProductsRead = "demo.products.read";
public const string ProductsCreate = "demo.products.create";
public const string ProductsUpdate = "demo.products.update";
public const string ProductsDelete = "demo.products.delete";
public const string OrdersCreate = "demo.orders.create";

public static readonly IReadOnlyList<string> Manager =
[
ProductsRead,
ProductsCreate,
ProductsUpdate,
ProductsDelete
];

public static readonly IReadOnlyList<string> Customer =
[
ProductsRead,
OrdersCreate
];
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
namespace CodeBeam.UltimateAuth.IdentityLab.Authorization;

public static class LabRoles
public static class LabRoles
{
public const string Admin = "Ladmin";
public const string Owner = "Owner";
public const string User = "User";

public const string Manager = "Manager";
public const string Customer = "Customer";

public static readonly IReadOnlyList<string> SystemRoles =
[
Admin,
Owner,
User
];

public static readonly IReadOnlyList<string> DemoRoles =
[
Manager,
Customer
];

public static bool IsDemoRole(string? name) => DemoRoles.Contains(name ?? "", StringComparer.OrdinalIgnoreCase);
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
@using MudExtensions
@using Microsoft.JSInterop
@implements IAsyncDisposable

@inject IJSRuntime JS

<MudPopup Open="@_open" PopupPosition="PopupPosition.Bottom" Padding="16" Elevation="6" EnableAnimation="true" Class="lab-cookie-popup">

<ChildContent>
<div class="lab-cookie-layout">

<div class="lab-cookie-content">
<div class="lab-cookie-icon">
<MudIcon Icon="@Icons.Material.Outlined.Shield" Size="Size.Medium" />
</div>

<div class="lab-cookie-copy">
<strong>Essential cookies</strong>

<p>
Identity Lab uses essential cookies to keep authentication sessions secure and the application working correctly.
These cookies are necessary for the service to function.
</p>
</div>
</div>

<MudButton Color="Color.Primary" Variant="Variant.Filled" Disabled="@_saving" OnClick="AcknowledgeAsync" Class="lab-cookie-button">
Got it
</MudButton>

</div>
</ChildContent>

</MudPopup>


@code {
private const string StorageKey = "identitylab.cookies.notice.v1";

private bool _open;
private bool _saving;
private bool _initialized;
private bool _disposed;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!firstRender || _initialized)
return;

try
{
var acknowledged = await JS.InvokeAsync<string?>("localStorage.getItem", StorageKey);

if (_disposed)
return;

_open = acknowledged != "acknowledged";
_initialized = true;

await InvokeAsync(StateHasChanged);
}
catch (JSException)
{
// Storage may be unavailable in restricted browsers.
// Show the notice without persistence.
_open = true;
_initialized = true;
await InvokeAsync(StateHasChanged);
}
catch (JSDisconnectedException)
{
// The circuit has disconnected.
}
}

private async Task AcknowledgeAsync()
{
if (_saving)
return;

_saving = true;

try
{
await JS.InvokeVoidAsync("localStorage.setItem", StorageKey, "acknowledged");
}
catch (JSException)
{
// Dismiss for this circuit even if storage is unavailable.
}
catch (JSDisconnectedException)
{
// The browser connection was lost.
}
finally
{
_open = false;
_saving = false;

if (!_disposed)
await InvokeAsync(StateHasChanged);
}
}

public ValueTask DisposeAsync()
{
_disposed = true;
return ValueTask.CompletedTask;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
::deep .lab-cookie-popup {
max-width: 100%;
border-radius: 14px;
background: var(--mud-palette-surface);
border: 1px solid var(--mud-palette-lines-default);
color: var(--mud-palette-text-primary);
z-index: 1200;
}

.lab-cookie-content {
display: flex;
align-items: flex-start;
gap: 14px;
min-width: 0;
}

.lab-cookie-icon {
width: 42px;
height: 42px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
background: var(--mud-palette-primary-hover);
color: var(--mud-palette-primary);
}

.lab-cookie-copy {
display: flex;
flex-direction: column;
gap: 5px;
max-width: 780px;
}

.lab-cookie-copy strong {
font-size: .88rem;
font-weight: 750;
}

.lab-cookie-copy p {
margin: 0;
color: var(--mud-palette-text-secondary);
font-size: .78rem;
line-height: 1.65;
}


.lab-cookie-layout {
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
width: 100%;
}

::deep .lab-cookie-button {
flex-shrink: 0;
min-width: 100px;
border-radius: 9px;
font-weight: 650;
}

@media (max-width: 600px) {
.lab-cookie-layout {
flex-direction: column;
align-items: stretch;
gap: 16px;
}

::deep .lab-cookie-button {
width: 100%;
}
}

@media (max-width: 600px) {
.lab-cookie-content {
gap: 10px;
}

.lab-cookie-icon {
width: 36px;
height: 36px;
}

.lab-cookie-copy p {
font-size: .74rem;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@using MudBlazor

<MudTooltip Placement="Placement.Top" Arrow="true" Duration="250">
<TooltipContent>
<MudStack Spacing="1" Class="pa-1" Style="max-width:300px">

<MudText Typo="Typo.subtitle2">
@Title
</MudText>

<MudText Typo="Typo.body2">
@Description
</MudText>

<MudDivider Class="my-1" />

<MudText Typo="Typo.caption">
Configuration
</MudText>

<MudText Typo="Typo.body2" Style="font-family:monospace">
@OptionPath
</MudText>

@if (!string.IsNullOrWhiteSpace(CurrentValue))
{
<MudText Typo="Typo.caption">
Current Value: @CurrentValue
</MudText>
}

</MudStack>
</TooltipContent>

<ChildContent>
<MudIcon Icon="@Icons.Material.Outlined.Info"
Size="Size.Small"
Color="Color.Secondary"
Style="cursor:help;opacity:.65" />
</ChildContent>

</MudTooltip>

@code {
[Parameter, EditorRequired]
public string Title { get; set; } = string.Empty;

[Parameter, EditorRequired]
public string Description { get; set; } = string.Empty;

[Parameter, EditorRequired]
public string OptionPath { get; set; } = string.Empty;

[Parameter]
public string? CurrentValue { get; set; }
}
Loading
Loading