Split Site:Audience into Site:ExternalUrl + Site:CorsAllowedOrigins

The Site:Audience setting was conflating two distinct concepts: an OAuth
JWT audience (a single resource identifier) and a CORS allow-list (an
array of origins). Collapsing them caused several latent bugs:
- OAuth/JWT validation expected a single string while CORS WithOrigins
  accepts an array.
- Password-reset callback URLs and OAuth client RedirectUri/Origin were
  being built from what was meant to be an audience identifier, not a
  base URL.
- Yavsc.Org's main CORS policy was hardcoded to '*', with no way to
  restrict it without code changes.

Changes:
- SiteSettings.Audience (string) replaced with CorsAllowedOrigins
  (IList<string>).
- OAuth JWT Authority still reads Site:Authority; Audience now reads
  Site:ExternalUrl (Org only; Api/Blogs use ValidateAudience=false).
- MailSender and AccountController build reset-callback URLs from
  Site:ExternalUrl.
- ClientController uses Site:ExternalUrl for OAuth RedirectUri/Origin
  defaults on newly created clients.
- Yavsc.Api and Yavsc.Blogs now read CORS origins from
  Site:CorsAllowedOrigins instead of hardcoded URLs.

Add shared AddYavscCors / AddYavscJwtBearer extension methods in
Yavsc.Server/Helpers/ServiceExtensions.cs to enforce a single
configuration contract across all runtime services (Api, Blogs, Org).
Fails closed when CorsAllowedOrigins is empty; fails fast at startup
when Site:Authority is missing.

Remove obsolete ConfigurationHelpers.GetAudience (no remaining callers).

Local appsettings-*.json files (which carry deployment-specific values
and are gitignored) must be updated to add Site:CorsAllowedOrigins.
This commit is contained in:
Paul Schneider 2026-06-19 13:15:21 +01:00
commit dcf2a93ad0
11 changed files with 125 additions and 84 deletions

View file

@ -590,7 +590,7 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
_siteSettings.Title,
callbackUrl,
_siteSettings.Slogan,
_siteSettings.Audience));
_siteSettings.ExternalUrl));
// No, wait for more than a login pass submission:
// do not await _signInManager.SignInAsync(user, isPersistent: false);
@ -641,7 +641,7 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
this._localizer["ConfirmYourAccountTitle"],
string.Format(this._localizer["ConfirmYourAccountBody"],
_siteSettings.Title, callbackUrl, _siteSettings.Slogan,
_siteSettings.Audience));
_siteSettings.ExternalUrl));
return new EmailSentViewModel { EMail = user.Email, Sent = true, MessageId = res };
}
@ -654,7 +654,7 @@ IHtmlLocalizerFactory htmlLocalizerFactory,
this._localizer["AccountEmailFactorTitle"],
string.Format(this._localizer["AccountEmailFactorBody"],
_siteSettings.Title, callbackUrl, _siteSettings.Slogan,
_siteSettings.Audience, code));
_siteSettings.ExternalUrl, code));
return new EmailSentViewModel { EMail = user.Email, Sent = true, MessageId = res }; ;
}
//

View file

@ -87,13 +87,13 @@ namespace Yavsc.Controllers
dbContext.ClientRedirectUris.Add(new ClientRedirectUri
{
ClientId = client.Id,
RedirectUri = siteSettings.Audience
RedirectUri = siteSettings.ExternalUrl
});
dbContext.ClientCorsOrigins.Add(new ClientCorsOrigin
{
ClientId = client.Id,
Origin = siteSettings.Audience
Origin = siteSettings.ExternalUrl
});
foreach (String credType in new String[] { "code", "client_credentials", "password" })