Skip to content

feat: add OAuth grants support#197

Merged
felipefreitag merged 2 commits into
mainfrom
feat/oauth-grants
Jul 9, 2026
Merged

feat: add OAuth grants support#197
felipefreitag merged 2 commits into
mainfrom
feat/oauth-grants

Conversation

@felipefreitag

@felipefreitag felipefreitag commented Jul 9, 2026

Copy link
Copy Markdown
Member

Adds support for the two newly-launched OAuth endpoints:

  • GET /oauth/grants
  • DELETE /oauth/grants/:oauthGrantId

Changes

  • New Resend::OAuthGrants module with:
    • list(params = {}) — lists OAuth grants. Supports standard pagination params (limit, after, before) via PaginationHelper.
    • remove(oauth_grant_id) — revokes an OAuth grant.
  • Wired into lib/resend.rb.
  • Specs covering list, pagination path building, and revoke — for both static and dynamic (lambda) API keys.
  • Example in examples/oauth_grants.rb.

Usage

# List grants
grants = Resend::OAuthGrants.list
grants = Resend::OAuthGrants.list({ limit: 10, after: "grant_id" })

# Revoke a grant
Resend::OAuthGrants.remove("oauth_grant_id")

Testing

  • bundle exec rspec → 306 examples, 0 failures
  • bundle exec rubocop → no offenses

References


Summary by cubic

Add OAuth grants support to let users list and revoke OAuth grants through simple Ruby methods. This wraps the new API endpoints and adds pagination and examples.

  • New Features
    • Introduces Resend::OAuthGrants with list(params = {}) and revoke(oauth_grant_id), wrapping GET /oauth/grants and DELETE /oauth/grants/:oauth_grant_id.
    • Supports pagination (limit, after, before) via Resend::PaginationHelper.
    • Works with static and dynamic (lambda) API keys; covered by specs. Example added in examples/oauth_grants.rb.

Written for commit 547f316. Summary will update on new commits.

Review in cubic

Add Resend::OAuthGrants with list and remove methods, wrapping the two
new OAuth endpoints:

- GET /oauth/grants
- DELETE /oauth/grants/:oauth_grant_id

list supports the standard pagination params (limit, after, before) via
PaginationHelper. Includes specs and an example.
@cubic-dev-ai

cubic-dev-ai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Running ultrareview automatically — Adding OAuth grant list and revoke endpoints introduces authorization-sensitive logic where a subtle bug could expose or improperly revoke third-party access tokens, impacting data security.. I'll post findings when complete.

@felipefreitag
felipefreitag requested a review from drish July 9, 2026 18:27
Comment thread lib/resend/oauth_grants.rb Outdated
end

# https://resend.com/docs/api-reference/oauth/revoke-grant
def remove(oauth_grant_id = "")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this function should be called revoke, so that it's matching what the Node SDK has

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call — renamed to revoke to match the Node SDK in 547f316.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ultrareview completed in 3m 59s

3 issues found across 4 files

Confidence score: 4/5

  • In spec/oauth_grants_spec.rb, mocked nested OAuth response keys are symbols while production nested keys are strings, so these specs may pass against the mock but fail to catch real parsing/access regressions in production-like payloads — update the mock fixtures and assertions to use string-keyed nested hashes before merging.
  • In spec/oauth_grants_spec.rb, the two should-style example descriptions are a low-risk style/readability issue rather than a functional blocker, but they can make test intent less consistent with project conventions — rename them to declarative phrases (for example, “builds a paginated path” / “lists oauth grants”) in this PR or as immediate follow-up.
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="spec/oauth_grants_spec.rb">

<violation number="1" location="spec/oauth_grants_spec.rb:6">
P3: Custom agent: **No `should` in tests**

Use a declarative description without "should". For example, "lists oauth grants" is clearer and more direct — it describes what the test verifies rather than what the code should hypothetically do.</violation>

<violation number="2" location="spec/oauth_grants_spec.rb:12">
P2: The test mock responses use symbol keys for nested objects (via `"id":` Ruby syntax which creates `:id`), and the assertions access nested fields with `[:id]` — but in production, nested hash keys remain as strings from JSON parsing.

The SDK's `process_response` (in `request.rb`) only transforms top-level keys to symbols via `data.transform_keys!(&:to_sym)`. Nested hashes (like items in the `data` array) are **not** deep-transformed, so they retain their original string keys from the API response.

Because the mock bypasses `perform` entirely (via `allow_any_instance_of(…).to receive(:perform).and_return(resp)`), no key transformation happens at all — the mock is returned as-is with symbol keys. This means the tests pass with mocks but the assertions would fail with actual API data.

Consider using string keys (`'id' => …`) in the mock response for nested objects and accessing nested fields with string keys (`['id']`) in assertions to match the real response format.</violation>

<violation number="3" location="spec/oauth_grants_spec.rb:33">
P3: Custom agent: **No `should` in tests**

Use a declarative description without "should". "builds a paginated path" is more direct and follows the rule's guidance.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread spec/oauth_grants_spec.rb
"has_more": false,
"data": [
{
"id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The test mock responses use symbol keys for nested objects (via "id": Ruby syntax which creates :id), and the assertions access nested fields with [:id] — but in production, nested hash keys remain as strings from JSON parsing.

The SDK's process_response (in request.rb) only transforms top-level keys to symbols via data.transform_keys!(&:to_sym). Nested hashes (like items in the data array) are not deep-transformed, so they retain their original string keys from the API response.

Because the mock bypasses perform entirely (via allow_any_instance_of(…).to receive(:perform).and_return(resp)), no key transformation happens at all — the mock is returned as-is with symbol keys. This means the tests pass with mocks but the assertions would fail with actual API data.

Consider using string keys ('id' => …) in the mock response for nested objects and accessing nested fields with string keys (['id']) in assertions to match the real response format.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/oauth_grants_spec.rb, line 12:

<comment>The test mock responses use symbol keys for nested objects (via `"id":` Ruby syntax which creates `:id`), and the assertions access nested fields with `[:id]` — but in production, nested hash keys remain as strings from JSON parsing.

The SDK's `process_response` (in `request.rb`) only transforms top-level keys to symbols via `data.transform_keys!(&:to_sym)`. Nested hashes (like items in the `data` array) are **not** deep-transformed, so they retain their original string keys from the API response.

Because the mock bypasses `perform` entirely (via `allow_any_instance_of(…).to receive(:perform).and_return(resp)`), no key transformation happens at all — the mock is returned as-is with symbol keys. This means the tests pass with mocks but the assertions would fail with actual API data.

Consider using string keys (`'id' => …`) in the mock response for nested objects and accessing nested fields with string keys (`['id']`) in assertions to match the real response format.</comment>

<file context>
@@ -0,0 +1,77 @@
+          "has_more": false,
+          "data": [
+            {
+              "id": "b6d24b8e-af0b-4c3c-be0c-359bbd97381e",
+              "client_id": "client_123",
+              "scopes": ["emails:send"],
</file context>

Comment thread spec/oauth_grants_spec.rb
expect(grants[:has_more]).to be(false)
end

it "should build a paginated path" do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Custom agent: No should in tests

Use a declarative description without "should". "builds a paginated path" is more direct and follows the rule's guidance.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/oauth_grants_spec.rb, line 33:

<comment>Use a declarative description without "should". "builds a paginated path" is more direct and follows the rule's guidance.</comment>

<file context>
@@ -0,0 +1,77 @@
+        expect(grants[:has_more]).to be(false)
+      end
+
+      it "should build a paginated path" do
+        expect(Resend::PaginationHelper).to receive(:build_paginated_path)
+          .with("oauth/grants", { limit: 10 })
</file context>

Comment thread spec/oauth_grants_spec.rb
RSpec.describe "OAuth Grants" do
shared_examples "oauth grants api" do
describe "list" do
it "should list oauth grants" do

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Custom agent: No should in tests

Use a declarative description without "should". For example, "lists oauth grants" is clearer and more direct — it describes what the test verifies rather than what the code should hypothetically do.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At spec/oauth_grants_spec.rb, line 6:

<comment>Use a declarative description without "should". For example, "lists oauth grants" is clearer and more direct — it describes what the test verifies rather than what the code should hypothetically do.</comment>

<file context>
@@ -0,0 +1,77 @@
+RSpec.describe "OAuth Grants" do
+  shared_examples "oauth grants api" do
+    describe "list" do
+      it "should list oauth grants" do
+        resp = {
+          "object": "list",
</file context>

Match the Node SDK's method name (per review feedback).

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 3 files (changes from recent commits).

Requires human review: Auto-approval blocked by 3 unresolved issues from previous reviews.

Re-trigger cubic

@felipefreitag
felipefreitag merged commit b0466e1 into main Jul 9, 2026
10 checks passed
@felipefreitag
felipefreitag deleted the feat/oauth-grants branch July 9, 2026 18:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants