diff --git a/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs b/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs new file mode 100644 index 00000000..eb021646 --- /dev/null +++ b/src/PostIt/PostIt.Desktop/PlatformBootstrap.cs @@ -0,0 +1,24 @@ +using IdentityModel.OidcClient.Browser; +using PostIt.Services; + +namespace PostIt.Desktop; + +/// +/// One-shot platform bootstrap. Called from Program.Main so that +/// the shared LoginPageViewModel sees a working IBrowser +/// (the loopback listener that captures the OIDC redirect) without +/// referencing any platform-specific API from the shared library. +/// +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(); + } +} \ No newline at end of file diff --git a/src/PostIt/PostIt.Desktop/Program.cs b/src/PostIt/PostIt.Desktop/Program.cs index 17776559..fb3802eb 100644 --- a/src/PostIt/PostIt.Desktop/Program.cs +++ b/src/PostIt/PostIt.Desktop/Program.cs @@ -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(); -} +} \ No newline at end of file diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs index 8c483d39..e68e1ba1 100644 --- a/src/Yavsc.Org/Extensions/HostingExtensions.cs +++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs @@ -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 EnsureDefaultConfiguration() + private static Action 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. /// - 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().Add(new IdentityServer8.EntityFramework.Entities.ClientRedirectUri { @@ -450,12 +452,29 @@ public static class HostingExtensions context.SaveChanges(); } + /// + /// Compose the full set of redirect URIs for the PostIt client. The base + /// URIs cover the standalone desktop/mobile flows; the value of + /// Site:ExternalUrl is appended so PostIt can also be embedded in + /// a Yavsc.Org web page (e.g. an iframe-launched launcher). + /// + private static IEnumerable BuildPostItRedirectUris(IConfiguration configuration) + { + foreach (var uri in PostItRedirectUris) + yield return uri; + + var externalUrl = configuration["Site:ExternalUrl"]; + if (!string.IsNullOrWhiteSpace(externalUrl)) + yield return externalUrl; + } + /// /// Bring an existing postit client up to the current public-client /// configuration. Idempotent: each change is applied only when the row is /// currently in the legacy state. /// 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() .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().RemoveRange(legacyRedirects); - changed = true; - } - if (changed) { context.SaveChanges();