PostIt.Desktop: wire the loopback browser, parameterise PostIt RedirectUris
The previous commit set Platform.CreateBrowser to null on the desktop side, so LoginAsync would still fail with 'No browser is available'. Close that loop with an explicit desktop bootstrap. PostIt.Desktop/PlatformBootstrap.cs mirrors the Android side: it populates Platform.DefaultRedirectUri and Platform.CreateBrowser once at startup. Program.Main calls EnsureInitialized before BuildAvaloniaApp so the LoginPageViewModel sees a working browser before any login attempt. The Yavsc.Org seed now reads Site:ExternalUrl from configuration so the RedirectUri list for the PostIt client follows the same setting as the rest of the application (same value used in Administration/ClientController, AccountController, etc.). Without this, an embedded 'launch PostIt from a Yavsc.Org page' scenario would be rejected by IdentityServer (redirect_uri mismatch). BuildPostItRedirectUris is a small helper that yields the constant PostItRedirectUris (loopback + Android custom scheme) followed by Site:ExternalUrl when set. Both SeedNewPostItClient (fresh db) and MigratePostItClientToPublic (existing db) consume it. The legacy cleanup block (which used to remove https://yavsc.pschneider.fr/ and yavsc://callback) is dropped: Site:ExternalUrl is now the canonical way to authorise that path and may legitimately equal that value.
This commit is contained in:
parent
c172d1cf9e
commit
7a0944d0f5
3 changed files with 64 additions and 25 deletions
24
src/PostIt/PostIt.Desktop/PlatformBootstrap.cs
Normal file
24
src/PostIt/PostIt.Desktop/PlatformBootstrap.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using IdentityModel.OidcClient.Browser;
|
||||
using PostIt.Services;
|
||||
|
||||
namespace PostIt.Desktop;
|
||||
|
||||
/// <summary>
|
||||
/// One-shot platform bootstrap. Called from <c>Program.Main</c> so that
|
||||
/// the shared <c>LoginPageViewModel</c> sees a working <c>IBrowser</c>
|
||||
/// (the loopback listener that captures the OIDC redirect) without
|
||||
/// referencing any platform-specific API from the shared library.
|
||||
/// </summary>
|
||||
internal static class PlatformBootstrap
|
||||
{
|
||||
private static int _initialized;
|
||||
|
||||
internal static void EnsureInitialized()
|
||||
{
|
||||
if (System.Threading.Interlocked.Exchange(ref _initialized, 1) != 0)
|
||||
return;
|
||||
|
||||
Platform.DefaultRedirectUri = Settings.DefaultLoopbackRedirectUri;
|
||||
Platform.CreateBrowser = () => new LoopbackBrowser();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using Avalonia;
|
||||
|
||||
namespace PostIt.Desktop;
|
||||
|
|
@ -9,8 +9,12 @@ sealed class Program
|
|||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
[STAThread]
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
PlatformBootstrap.EnsureInitialized();
|
||||
BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
}
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
|
|
@ -21,4 +25,4 @@ sealed class Program
|
|||
#endif
|
||||
.WithInterFont()
|
||||
.LogToTrace();
|
||||
}
|
||||
}
|
||||
|
|
@ -306,7 +306,7 @@ public static class HostingExtensions
|
|||
sql => sql.MigrationsAssembly(migrationsAssembly));
|
||||
}
|
||||
|
||||
b.UseSeeding(EnsureDefaultConfiguration());
|
||||
b.UseSeeding(EnsureDefaultConfiguration(builder.Configuration));
|
||||
};
|
||||
})
|
||||
.AddOperationalStore(options =>
|
||||
|
|
@ -378,7 +378,9 @@ public static class HostingExtensions
|
|||
IdentityServer8.IdentityServerConstants.StandardScopes.Profile,
|
||||
};
|
||||
|
||||
private static Action<DbContext, bool> EnsureDefaultConfiguration()
|
||||
private static Action<DbContext, bool> EnsureDefaultConfiguration(
|
||||
IConfiguration configuration
|
||||
)
|
||||
{
|
||||
return (context, _) =>
|
||||
{
|
||||
|
|
@ -389,11 +391,11 @@ public static class HostingExtensions
|
|||
|
||||
if (existingClient is null)
|
||||
{
|
||||
SeedNewPostItClient(context);
|
||||
SeedNewPostItClient(configuration, context);
|
||||
return;
|
||||
}
|
||||
|
||||
MigratePostItClientToPublic(context, existingClient);
|
||||
MigratePostItClientToPublic(configuration, context, existingClient);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -402,7 +404,7 @@ public static class HostingExtensions
|
|||
/// client using Authorization Code + PKCE. Used the first time the
|
||||
/// ConfigurationDb is seeded.
|
||||
/// </summary>
|
||||
private static void SeedNewPostItClient(DbContext context)
|
||||
private static void SeedNewPostItClient(IConfiguration configuration, DbContext context)
|
||||
{
|
||||
// PostIt is a public client (Authorization Code + PKCE).
|
||||
// No client secret is stored or transmitted; PKCE binds the
|
||||
|
|
@ -437,7 +439,7 @@ public static class HostingExtensions
|
|||
});
|
||||
}
|
||||
|
||||
foreach (var redirectUri in PostItRedirectUris)
|
||||
foreach (var redirectUri in BuildPostItRedirectUris(configuration))
|
||||
{
|
||||
context.Set<ClientRedirectUri>().Add(new IdentityServer8.EntityFramework.Entities.ClientRedirectUri
|
||||
{
|
||||
|
|
@ -450,12 +452,29 @@ public static class HostingExtensions
|
|||
context.SaveChanges();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compose the full set of redirect URIs for the PostIt client. The base
|
||||
/// URIs cover the standalone desktop/mobile flows; the value of
|
||||
/// <c>Site:ExternalUrl</c> is appended so PostIt can also be embedded in
|
||||
/// a Yavsc.Org web page (e.g. an iframe-launched launcher).
|
||||
/// </summary>
|
||||
private static IEnumerable<string> BuildPostItRedirectUris(IConfiguration configuration)
|
||||
{
|
||||
foreach (var uri in PostItRedirectUris)
|
||||
yield return uri;
|
||||
|
||||
var externalUrl = configuration["Site:ExternalUrl"];
|
||||
if (!string.IsNullOrWhiteSpace(externalUrl))
|
||||
yield return externalUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bring an existing <c>postit</c> client up to the current public-client
|
||||
/// configuration. Idempotent: each change is applied only when the row is
|
||||
/// currently in the legacy state.
|
||||
/// </summary>
|
||||
private static void MigratePostItClientToPublic(
|
||||
IConfiguration configuration,
|
||||
DbContext context,
|
||||
IdentityServer8.EntityFramework.Entities.Client client)
|
||||
{
|
||||
|
|
@ -518,10 +537,12 @@ public static class HostingExtensions
|
|||
}
|
||||
}
|
||||
|
||||
// 5. Ensure all expected redirect URIs are present. Legacy entries
|
||||
// pointing at the OP itself (e.g. https://yavsc.pschneider.fr/)
|
||||
// are removed — they redirect back into IdentityServer's own home
|
||||
// page and create a login loop.
|
||||
// 5. Ensure all expected redirect URIs are present. The expected set
|
||||
// is built by BuildPostItRedirectUris: the standalone URIs from
|
||||
// PostItRedirectUris (desktop loopback + Android custom scheme)
|
||||
// plus Site:ExternalUrl so PostIt can be embedded in a Yavsc.Org
|
||||
// web page. Any pre-existing rows that are no longer in this set
|
||||
// are removed.
|
||||
var existingRedirects = context.Set<ClientRedirectUri>()
|
||||
.Where(r => r.Client.Id == client.Id)
|
||||
.ToList();
|
||||
|
|
@ -529,7 +550,7 @@ public static class HostingExtensions
|
|||
.Select(r => r.RedirectUri)
|
||||
.ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
foreach (var redirectUri in PostItRedirectUris)
|
||||
foreach (var redirectUri in BuildPostItRedirectUris(configuration))
|
||||
{
|
||||
if (!existingRedirectUris.Contains(redirectUri))
|
||||
{
|
||||
|
|
@ -542,16 +563,6 @@ public static class HostingExtensions
|
|||
}
|
||||
}
|
||||
|
||||
var legacyRedirects = existingRedirects
|
||||
.Where(r => r.RedirectUri == "https://yavsc.pschneider.fr/"
|
||||
|| r.RedirectUri == "yavsc://callback")
|
||||
.ToList();
|
||||
if (legacyRedirects.Count > 0)
|
||||
{
|
||||
context.Set<ClientRedirectUri>().RemoveRange(legacyRedirects);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed)
|
||||
{
|
||||
context.SaveChanges();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue