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();