Add OAuth grants support (list + revoke)#128
Conversation
Implements the two new OAuth endpoints: - GET /oauth/grants -> OAuthGrantListAsync - DELETE /oauth/grants/:id -> OAuthGrantRevokeAsync Adds the OAuthGrant / OAuthGrantClient / OAuthGrantRevoked models, the IResend methods, the ResendClient implementation, CLI commands (oauth-grant list|revoke), a fake ApiServer controller, and tests. Field shapes follow resend-openapi#70 and resend-node#998.
|
Running ultrareview automatically — This PR adds OAuth grant list and revoke endpoints, which involve authentication/authorization logic and public API contract changes, where a subtle bug could lead to security issues or data integrity problems.. I'll post findings when complete. |
There was a problem hiding this comment.
Ultrareview completed in 10m 23s
2 issues found across 9 files
Confidence score: 3/5
- In
src/Resend/OAuthGrant.cs, modeling grant identifiers (id,client_id, revoke response id, and path id) asGuidwhile the API contract saysstringcan cause deserialization or request-construction failures when non-GUID values appear, leading to broken OAuth grant flows for valid API responses — align these fields and route parameters tostringbefore merging. - In
src/Resend/OAuthGrant.cs, exposing a publicResourcemember that is not in the current schema can mislead SDK users into depending on data the API does not return, creating avoidable integration confusion and future breaking cleanup — remove or clearly mark it as unsupported before release.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/Resend/OAuthGrant.cs">
<violation number="1" location="src/Resend/OAuthGrant.cs:20">
P2: The OAuth grant identifiers are narrower than the current API contract: the OpenAPI schema defines the grant id, `client_id`, revoke response id, and path parameter as plain strings, but the model uses `Guid`. If the API returns or accepts a non-UUID string identifier, list/revoke calls will fail in the SDK before the caller can handle the response. Consider using `string` for the OAuth grant id fields and matching revoke parameter unless the API contract is tightened to `format: uuid`.</violation>
<violation number="2" location="src/Resend/OAuthGrant.cs:33">
P3: This adds a public `Resource` member that is no longer in the current OAuth grant schema. Keeping it in the first release of the SDK shape can mislead callers into depending on a field the API does not return, and it becomes a public API member that is harder to remove later. Consider dropping `Resource` unless the endpoint contract is restored to include it.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| /// Identifier of the OAuth client the grant was issued to. | ||
| /// </summary> | ||
| [JsonPropertyName( "client_id" )] | ||
| public Guid ClientId { get; set; } |
There was a problem hiding this comment.
P2: The OAuth grant identifiers are narrower than the current API contract: the OpenAPI schema defines the grant id, client_id, revoke response id, and path parameter as plain strings, but the model uses Guid. If the API returns or accepts a non-UUID string identifier, list/revoke calls will fail in the SDK before the caller can handle the response. Consider using string for the OAuth grant id fields and matching revoke parameter unless the API contract is tightened to format: uuid.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/Resend/OAuthGrant.cs, line 20:
<comment>The OAuth grant identifiers are narrower than the current API contract: the OpenAPI schema defines the grant id, `client_id`, revoke response id, and path parameter as plain strings, but the model uses `Guid`. If the API returns or accepts a non-UUID string identifier, list/revoke calls will fail in the SDK before the caller can handle the response. Consider using `string` for the OAuth grant id fields and matching revoke parameter unless the API contract is tightened to `format: uuid`.</comment>
<file context>
@@ -0,0 +1,108 @@
+ /// Identifier of the OAuth client the grant was issued to.
+ /// </summary>
+ [JsonPropertyName( "client_id" )]
+ public Guid ClientId { get; set; }
+
+ /// <summary>
</file context>
| /// </summary> | ||
| [JsonPropertyName( "resource" )] | ||
| [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )] | ||
| public string? Resource { get; set; } |
There was a problem hiding this comment.
P3: This adds a public Resource member that is no longer in the current OAuth grant schema. Keeping it in the first release of the SDK shape can mislead callers into depending on a field the API does not return, and it becomes a public API member that is harder to remove later. Consider dropping Resource unless the endpoint contract is restored to include it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/Resend/OAuthGrant.cs, line 33:
<comment>This adds a public `Resource` member that is no longer in the current OAuth grant schema. Keeping it in the first release of the SDK shape can mislead callers into depending on a field the API does not return, and it becomes a public API member that is harder to remove later. Consider dropping `Resource` unless the endpoint contract is restored to include it.</comment>
<file context>
@@ -0,0 +1,108 @@
+ /// </summary>
+ [JsonPropertyName( "resource" )]
+ [JsonIgnore( Condition = JsonIgnoreCondition.WhenWritingNull )]
+ public string? Resource { get; set; }
+
+ /// <summary>
</file context>
…rkup - OAuthGrantListAsync now takes PaginatedQuery and returns PaginatedResult<OAuthGrant>, matching the SDK's paginated list pattern (limit/before/after + has_more) instead of a bare List. - OAuthGrantRevoked exposes the 'object' discriminator, like other result models (AutomationStopResult, etc.). - Fake ApiServer list endpoint returns PaginatedResult with has_more. - Escape API-provided name/scopes before Spectre markup rendering. Kept Guid ids (SDK-wide convention; grant/client ids are UUIDs) and the 'resource' field (present in resend-node#998 / resend-openapi#70).
|
Thanks for the review. Addressed in 0396a9e: Fixed (4):
Kept as-is (2), with reasoning:
|
There was a problem hiding this comment.
0 issues found across 6 files (changes from recent commits).
Requires human review: Auto-approval blocked by 2 unresolved issues from previous reviews.
Re-trigger cubic
Implements the two new OAuth endpoints announced for the API:
GET /oauth/grants→IResend.OAuthGrantListAsyncDELETE /oauth/grants/:oauthGrantId→IResend.OAuthGrantRevokeAsyncField shapes were verified against resend-openapi#70, resend-node#998, and the docs (list-grants / revoke-grant).
What's included
src/Resend/OAuthGrant.cs):OAuthGrant(id,client_id,scopes,resource,created_at,revoked_at,revoked_reason, nestedclient),OAuthGrantClient(name,logo_uri), andOAuthGrantRevokedfor the revoke response.#region OAuth GrantsinIResend; implementation in a newResendClient.OAuthGrants.cspartial, reusing the existingListOf<T>wrapper andExecutehelpers.resend oauth-grant list(with-j|--json) andresend oauth-grant revoke <id>.ApiServer(newOAuthGrantController). Full suite passes on .NET 8.Design notes / decisions for review
*DeleteAsyncmethods (which returnResendResponse), this endpoint returns{ object, id, revoked_at, revoked_reason }, soOAuthGrantRevokeAsyncreturnsResendResponse<OAuthGrantRevoked>(matching resend-node). Named Revoke rather than Delete to match the documented operation. Happy to switch to a void...DeleteAsyncif you'd rather match the delete convention.DateTime. The docs showcreated_atin a Postgres-style format (2026-04-08 00:11:13.110779+00) while revoke uses ISO; I verified both parse correctly through the existingJsonUtcDateTimeConverter, so I kept the idiomaticDateTime/DateTime?typing rather than opaque strings.GET /oauth/grantssupportslimit/after/beforeand returnshas_more, but the SDK's list convention (ListOf<T>) drops the wrapper and existing list methods (api-keys, domains) take no paging params. Kept consistent; surfacing pagination would be a broader SDK change across all list endpoints.oauthGrantIdtypedGuidto match the SDK's id convention (ids are UUIDs in practice), though the OpenAPI path param is an unconstrained string.Note: resend-openapi#70 and resend-node#998 are still open, so shapes could shift before GA.
Summary by cubic
Add OAuth grants support to the SDK and CLI: list grants with pagination and revoke a grant. This lets users see connected clients and invalidate their tokens when needed.
New Features
OAuthGrantListAsync(PaginatedQuery?)→ResendResponse<PaginatedResult<OAuthGrant>>;OAuthGrantRevokeAsync(Guid)→ResendResponse<OAuthGrantRevoked>.OAuthGrant,OAuthGrantClient,OAuthGrantRevoked(includesobjectdiscriminator).ResendClient.OAuthGrants.cs; interface updates inIResend.resend oauth-grant list(supports--json) andresend oauth-grant revoke <id>.ApiServercontroller.Notes
limit,before,after) and returnshas_more.ResendResponse<OAuthGrantRevoked>and is named “Revoke”.Guid; date fields use the existing UTCDateTimeconverter.Written for commit 0396a9e. Summary will update on new commits.