Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorLeitwolf112026-10-23 11:13:27 +0300
committerLeitwolf112026-10-23 11:13:27 +0300
commit06dfa204e4a65c535eb9b3d775a77b5d14522060 (patch)
treece178c734f82acf467e1d9052c13ee54b323c164
parent9099e9fbb50d27b622362b0c2f0dda5085d1fa03 (diff)
downloadprogmaweb-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.cs4
-rw-r--r--apps/api/OAuth/Api/AllowedApplicationEndpoints.cs21
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();
}
}