Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,26 @@
"Bash(aspire mcp:*)",
"Bash(az account:*)",
"Bash(az containerapp * list:*)",
"Bash(az containerapp * logs:*)",
"Bash(az containerapp * show:*)",
"Bash(az containerapp list:*)",
"Bash(az containerapp logs:*)",
"Bash(az containerapp show:*)",
"Bash(az group list:*)",
"Bash(az group show:*)",
"Bash(az monitor * list:*)",
"Bash(az monitor * show:*)",
"Bash(az monitor activity-log:*)",
"Bash(az monitor list:*)",
"Bash(az monitor log-analytics query:*)",
"Bash(az monitor metrics:*)",
"Bash(az monitor show:*)",
"Bash(az postgres * list:*)",
"Bash(az postgres * show:*)",
"Bash(az postgres list:*)",
"Bash(az postgres show:*)",
"Bash(az resource list:*)",
"Bash(az resource show:*)",
"Bash(cat:*)",
"Bash(claude:*)",
"Bash(cmp:*)",
Expand Down Expand Up @@ -107,6 +118,7 @@
],
"deny": [
"Bash(az group delete:*)",
"Bash(az resource delete:*)",
"Bash(git push --force:*)",
"Bash(git push -f:*)",
"Bash(git reset --hard:*)",
Expand Down
1 change: 1 addition & 0 deletions MeshWeaver.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
<Project Path="test/MeshWeaver.Persistence.Test/MeshWeaver.Persistence.Test.csproj" />
<Project Path="test/MeshWeaver.Autocomplete.Test/MeshWeaver.Autocomplete.Test.csproj" />
<Project Path="test/MeshWeaver.Query.Test/MeshWeaver.Query.Test.csproj" />
<Project Path="test/Memex.Portal.Shared.Test/Memex.Portal.Shared.Test.csproj" />
</Folder>
<Project Path="test/MeshWeaver.MemexTemplate.Test/MeshWeaver.MemexTemplate.Test.csproj" />
<Folder Name="/tools/">
Expand Down
17 changes: 0 additions & 17 deletions dist/templates/samples/Graph/Data/ACME/_Access/Public_Access.json

This file was deleted.

1 change: 1 addition & 0 deletions memex/Memex.Portal.Monolith/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Memex.Portal.ServiceDefaults;
using Memex.Portal.Shared;
using MeshWeaver.AI;
using MeshWeaver.ContentCollections;
using MeshWeaver.Graph.Configuration;
using MeshWeaver.Hosting;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public class OnboardingMiddleware(RequestDelegate next, ILogger<OnboardingMiddle
"/static/",
"/favicon.ico",
"/mcp",
"/signin-",
};

public async Task InvokeAsync(HttpContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,13 @@ public class VirtualUserMiddleware(RequestDelegate next, ILogger<VirtualUserMidd
{
private const string CookieName = "meshweaver_virtual_user";

private static readonly string[] ExcludedPrefixes =
["/_framework", "/_content", "/_blazor", "/static/", "/favicon.ico", "/mcp"];

public async Task InvokeAsync(HttpContext context)
{
// Skip virtual user assignment for MCP requests — they should get 401, not a virtual identity
if (context.Request.Path.StartsWithSegments("/mcp"))
var path = context.Request.Path.Value ?? "";
if (ExcludedPrefixes.Any(p => path.StartsWith(p, StringComparison.OrdinalIgnoreCase)))
{
await next(context);
return;
Expand Down
24 changes: 17 additions & 7 deletions memex/Memex.Portal.Shared/MemexConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public static void ConfigureMemexServices(this WebApplicationBuilder builder)
// Trust forwarded headers from Azure Container Apps reverse proxy
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor
| Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
| Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedHost;
options.KnownIPNetworks.Clear();
options.KnownProxies.Clear();
});
Expand Down Expand Up @@ -360,10 +363,15 @@ public TBuilder ConfigureMemexMesh(IConfiguration configuration, bool isDevelopm
// Each hub gets its own "content" collection pointing to a subdirectory
.ConfigureDefaultNodeHub(config =>
{
// Declared before the if-block so it's available for both the "content"
// collection mapping below and the "attachments" mapping further down.
var nodePath = config.Address.ToString();

if (contentStorageConfig != null)
{
var nodePath = config.Address.ToString();
var contentSubdir = nodePath;
// Scope static media (SVG, PNG, JPG) to a per-node subdirectory
// so each hub serves only its own content files.
var contentSubdir = $"content/{nodePath}";
// Combine with original BasePath for FileSystem; for AzureBlob, subdirectory is the blob prefix
var basePath = string.IsNullOrEmpty(contentStorageConfig.BasePath)
? contentSubdir
Expand All @@ -381,6 +389,10 @@ public TBuilder ConfigureMemexMesh(IConfiguration configuration, bool isDevelopm
config = config.AddContentCollection(_ => nodeContentConfig);
}

// Map "attachments" to "storage" with per-node subdirectory
// (needed by FutuRe and other samples that store datacube.csv, etc.)
config = config.MapContentCollection("attachments", "storage", $"attachments/{nodePath}");

return config
.WithHeartBeatHandler() // silently ack heartbeats on every per-node hub
.AddDefaultLayoutAreas()
Expand Down Expand Up @@ -430,11 +442,9 @@ public static void StartMemexApplication<TApp>(this WebApplication app) where TA

// Forward headers from reverse proxy (Azure Container Apps) so OIDC
// middleware constructs redirect URIs with the correct scheme and host.
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
ForwardedHeaders = Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedFor
| Microsoft.AspNetCore.HttpOverrides.ForwardedHeaders.XForwardedProto
});
// Always enabled: in production it reads X-Forwarded-* from the ACA proxy;
// in local dev it's a no-op since no proxy sets those headers.
app.UseForwardedHeaders();

// Static files middleware must run before routing to serve _content/* paths from RCLs
app.UseStaticFiles();
Expand Down
12 changes: 7 additions & 5 deletions memex/Memex.Portal.Shared/Pages/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
}

@code {
private AccessContext? UserContext => AccessService?.Context ?? AccessService?.CircuitContext;

private bool IsRealUser =>
AccessService?.Context != null
&& !string.IsNullOrEmpty(AccessService.Context.ObjectId)
&& !AccessService.Context.IsVirtual
&& !string.Equals(AccessService.Context.ObjectId, WellKnownUsers.Anonymous, StringComparison.OrdinalIgnoreCase);
UserContext != null
&& !string.IsNullOrEmpty(UserContext.ObjectId)
&& !UserContext.IsVirtual
&& !string.Equals(UserContext.ObjectId, WellKnownUsers.Anonymous, StringComparison.OrdinalIgnoreCase);

private string UserAddress => $"User/{AccessService?.Context?.ObjectId}";
private string UserAddress => $"User/{UserContext?.ObjectId}";

protected override void OnInitialized()
{
Expand Down
16 changes: 15 additions & 1 deletion memex/Memex.Portal.Shared/Pages/Login.razor
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@page "/login"
@using Microsoft.AspNetCore.Components.Authorization
@using MeshWeaver.Blazor.Portal.Authentication
@inject IAuthenticationNavigationService AuthNavService
@inject NavigationManager Navigation
Expand Down Expand Up @@ -51,6 +52,9 @@
[SupplyParameterFromQuery(Name = "error")]
public string? ErrorMessage { get; set; }

[CascadingParameter]
private Task<AuthenticationState>? AuthStateTask { get; set; }

private IReadOnlyList<ExternalProviderConfig> Providers { get; set; } = [];
private bool ShowDevLogin { get; set; }

Expand All @@ -60,8 +64,18 @@
Navigation.NavigateTo(url, forceLoad: true);
}

protected override void OnInitialized()
protected override async Task OnInitializedAsync()
{
if (AuthStateTask is not null)
{
var authState = await AuthStateTask;
if (authState.User?.Identity?.IsAuthenticated == true)
{
Navigation.NavigateTo(ReturnUrl ?? "/", forceLoad: true);
return;
}
}

if (AuthNavService is AuthenticationNavigationService navService)
{
Providers = navService.GetAvailableProviders();
Expand Down
Loading