diff --git a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs index 70877733..6e9f0d06 100644 --- a/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs +++ b/src/PostIt/PostIt/ViewModels/LoginPageViewModel.cs @@ -103,6 +103,7 @@ public partial class LoginPageViewModel : ViewModelBase { try { + this.IsBusy = true; Settings.Load().Wait(); // The platform project picks the right redirect URI and browser @@ -146,4 +147,4 @@ public partial class LoginPageViewModel : ViewModelBase StatusMessage = "Error: "+ex.Message; } } -} \ No newline at end of file +} diff --git a/src/PostIt/PostIt/Views/LoginPage.axaml b/src/PostIt/PostIt/Views/LoginPage.axaml index 87c8844e..24be3131 100644 --- a/src/PostIt/PostIt/Views/LoginPage.axaml +++ b/src/PostIt/PostIt/Views/LoginPage.axaml @@ -54,7 +54,7 @@ IsEnabled="{Binding HasForgotPasswordUrl}" Click="OnForgotPasswordClick"/> - + diff --git a/src/Yavsc.Org/Extensions/HostingExtensions.cs b/src/Yavsc.Org/Extensions/HostingExtensions.cs index e68e1ba1..c29540a5 100644 --- a/src/Yavsc.Org/Extensions/HostingExtensions.cs +++ b/src/Yavsc.Org/Extensions/HostingExtensions.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.IdentityModel.Tokens.Jwt; using System.Reflection; +using System.Security.Cryptography.X509Certificates; using Google.Apis.Util.Store; using IdentityModel; using IdentityServer8; @@ -330,6 +331,47 @@ public static class HostingExtensions { identityServerBuilder.AddDeveloperSigningCredential(); } + else + { + // Production: reuse the Let's Encrypt certificate that Kestrel + // already loads for TLS so IdentityServer has a stable signing + // key (and a JWKS endpoint). The cert is renewed by the ACME + // hook and a service restart picks up the new key automatically. + // + // The path comes from Kestrel:Endpoints:Https:Certificate to + // avoid maintaining a separate setting; fullchain.pem bundles + // the leaf + chain, which X509Certificate2 needs for chain + // validation by relying parties. + var certPath = builder.Configuration["Kestrel:Endpoints:Https:Certificate:Path"]; + var keyPath = builder.Configuration["Kestrel:Endpoints:Https:Certificate:KeyPath"]; + if (string.IsNullOrWhiteSpace(certPath) || string.IsNullOrWhiteSpace(keyPath)) + { + throw new InvalidOperationException( + "Production IdentityServer requires a signing certificate. " + + "Configure Kestrel:Endpoints:Https:Certificate:{Path,KeyPath}."); + } + // CreateFromPemFile loads the leaf cert + its private key from + // PEM files without writing to the Windows certificate store + // (irrelevant on Linux, but keeps the call cross-platform). + var signingCert = X509Certificate2.CreateFromPemFile(certPath, keyPath); + // Pick the JWT signing algorithm from the cert's key type. Let's + // Encrypt may issue either RSA or ECDSA certificates depending on + // the ACME account's preferred chain; IdentityServer would 500 if + // we forced RS256 against an ECDSA key. + var algorithm = signingCert.GetECDsaPrivateKey() is not null ? "ES256" : "RS256"; + identityServerBuilder.AddSigningCredential(signingCert, algorithm); + } + + // Override the advertised jwks_uri to the canonical + // /.well-known/jwks endpoint that UseIdentityServer() actually + // mounts. IdentityServer8's default convention here is + // /.well-known/openid-configuration/jwks, which is not what most + // OIDC clients (including IdentityModel.OidcClient) expect, and + // would otherwise need a parallel route to be wired up. + identityServerBuilder.Services.Configure(options => + { + options.Discovery.CustomEntries["jwks_uri"] = "/.well-known/jwks"; + }); return identityServerBuilder; }