diff options
| author | Leitwolf11 | 2026-10-23 11:13:27 +0300 |
|---|---|---|
| committer | Leitwolf11 | 2026-10-23 11:13:27 +0300 |
| commit | 06dfa204e4a65c535eb9b3d775a77b5d14522060 (patch) | |
| tree | ce178c734f82acf467e1d9052c13ee54b323c164 | |
| parent | 9099e9fbb50d27b622362b0c2f0dda5085d1fa03 (diff) | |
| download | progmaweb-feature/oauth-application-types.tar.gz | |
Withdraw an allowance only from the account's own authorizationsfeature/oauth-application-types
CodeQL flagged the withdraw endpoint: the authorization was looked up by
the identifier in the address and then checked against the account. The
endpoint now searches only the signed-in account's authorizations for that
identifier and revokes the tokens by the identifier it found, so another
account's allowance is never loaded. A test withdraws as another account
and gets 404.
| -rw-r--r-- | apps/api-tests/OAuthFlowTests.cs | 4 | ||||
| -rw-r--r-- | apps/api/OAuth/Api/AllowedApplicationEndpoints.cs | 21 |
2 files changed, 17 insertions, 8 deletions
diff --git a/apps/api-tests/OAuthFlowTests.cs b/apps/api-tests/OAuthFlowTests.cs index b550e0e..a900e5d 100644 --- a/apps/api-tests/OAuthFlowTests.cs +++ b/apps/api-tests/OAuthFlowTests.cs @@ -160,6 +160,10 @@ public sealed class OAuthFlowTests HttpResponseMessage foreign = await browser.SendAsync(HttpMethod.Delete, $"/api/v1/oauth/allowed/{authorizationId}", origin: "https://attacker.example"); Assert.Equal(HttpStatusCode.Forbidden, foreign.StatusCode); + OAuthTestClient stranger = new(factory); + await stranger.RegisterAndSignInAsync("OAuthStranger4", "stranger4@example.com"); + Assert.Equal(HttpStatusCode.NotFound, (await stranger.SendAsync(HttpMethod.Delete, + $"/api/v1/oauth/allowed/{authorizationId}")).StatusCode); HttpResponseMessage withdrawn = await browser.SendAsync(HttpMethod.Delete, $"/api/v1/oauth/allowed/{authorizationId}"); Assert.Equal(HttpStatusCode.NoContent, withdrawn.StatusCode); diff --git a/apps/api/OAuth/Api/AllowedApplicationEndpoints.cs b/apps/api/OAuth/Api/AllowedApplicationEndpoints.cs index 3939c9a..03cdeba 100644 --- a/apps/api/OAuth/Api/AllowedApplicationEndpoints.cs +++ b/apps/api/OAuth/Api/AllowedApplicationEndpoints.cs @@ -84,16 +84,21 @@ internal static class AllowedApplicationEndpoints return OAuthAccount.ForeignOrigin(); } - object? authorization = await authorizations.FindByIdAsync(authorizationId, context.RequestAborted); - // Another account's authorization is treated as absent. - if (authorization is null - || await authorizations.GetSubjectAsync(authorization, context.RequestAborted) != account.Id.ToString()) + // Only the account's own authorizations are searched, so another account's identifier is simply absent. + await foreach (object authorization in authorizations.FindBySubjectAsync(account.Id.ToString(), + context.RequestAborted)) { - return Results.NotFound(); + string? id = await authorizations.GetIdAsync(authorization, context.RequestAborted); + if (id is null || !string.Equals(id, authorizationId, StringComparison.Ordinal)) + { + continue; + } + + await authorizations.TryRevokeAsync(authorization, context.RequestAborted); + await tokens.RevokeByAuthorizationIdAsync(id, context.RequestAborted); + return Results.NoContent(); } - await authorizations.TryRevokeAsync(authorization, context.RequestAborted); - await tokens.RevokeByAuthorizationIdAsync(authorizationId, context.RequestAborted); - return Results.NoContent(); + return Results.NotFound(); } } |