diff options
| author | Leitwolf11 | 2026-10-16 06:00:57 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-16 06:00:57 +0300 |
| commit | bc653270652c1ab44d2144ddd17ae49b404e7cd2 (patch) | |
| tree | 2cc8867a56273c910d00855ac1eeac22fdee1bd6 /apps | |
| parent | 3877e3318e875cdebc3d7b9bc970b4496e5183a9 (diff) | |
| download | progmaweb-bc653270652c1ab44d2144ddd17ae49b404e7cd2.tar.gz | |
Return a failed Google callback to sign-in and correct stale statements
A Google callback that could not finish ended in an unhandled 500: a
visitor who declined on Google's consent screen, a callback whose state
cookie had expired, and a direct request to the callback path all got an
empty error page. The account API document already promised a redirect
to /login?google=failed for a failed callback; the handler of the Google
scheme now does that through OnRemoteFailure. Three new tests cover a
callback without state, a declined consent and a forged state.
Three statements no longer matched the sites:
- the bug report template still named the catalog paths without the
language and the build system; they are /langs/tala/<Publisher>/<Name>/
and /dslplugins/tazel/<Publisher>/<Name>/;
- the operations guide placed Tala content on xsharp-lang.xyz, which only
redirects to tala.run now;
- the architecture guide described the ViGet routing before the catalogs
per language and build system: it now gives the rewrites of /langs/tala
and /dslplugins/tazel, the redirects of /langs and /dslplugins, three
indexed ViGet pages and a sitemap with the home page and both catalogs.
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/api-tests/AccountEndpointTests.cs | 15 | ||||
| -rw-r--r-- | apps/api/Program.cs | 8 |
2 files changed, 23 insertions, 0 deletions
diff --git a/apps/api-tests/AccountEndpointTests.cs b/apps/api-tests/AccountEndpointTests.cs index 9459081..c5c305e 100644 --- a/apps/api-tests/AccountEndpointTests.cs +++ b/apps/api-tests/AccountEndpointTests.cs @@ -33,6 +33,21 @@ public sealed class AccountEndpointTests Assert.Contains("state=", location.Query, StringComparison.Ordinal); } + [Theory] + [InlineData("/api/v1/accounts/oauth/google/callback")] + [InlineData("/api/v1/accounts/oauth/google/callback?error=access_denied&state=declined")] + [InlineData("/api/v1/accounts/oauth/google/callback?code=forged&state=forged")] + public async Task GoogleCallbackThatCannotFinishReturnsToTheSignInPage(string callback) + { + await using AccountApiFactory factory = new(); + using HttpClient client = factory.CreateAccountClient(handleCookies: false); + + HttpResponseMessage response = await client.GetAsync(callback, TestContext.Current.CancellationToken); + + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal("/login?google=failed", response.Headers.Location?.OriginalString); + } + [Fact] public async Task GoogleStartRejectsInvalidAccountNameBeforeLeavingTheSite() { diff --git a/apps/api/Program.cs b/apps/api/Program.cs index 3eb96c9..688fe7d 100644 --- a/apps/api/Program.cs +++ b/apps/api/Program.cs @@ -44,6 +44,14 @@ if (!string.IsNullOrWhiteSpace(googleClientId) && !string.IsNullOrWhiteSpace(goo options.CallbackPath = "/api/v1/accounts/oauth/google/callback"; options.SignInScheme = AccountEndpoints.ExternalCookieScheme; options.SaveTokens = false; + // A callback that cannot finish (the visitor declined on Google, or the state cookie is missing or + // expired) would otherwise end in an unhandled 500. The sign-in page explains a failed Google sign-in. + options.Events.OnRemoteFailure = context => + { + context.Response.Redirect("/login?google=failed"); + context.HandleResponse(); + return Task.CompletedTask; + }; }); } builder.Services.AddProblemDetails(); |