Skip to content

feat: enhance OAuth2 support in snippet generation#6592

Merged
bijin-bruno merged 8 commits intousebruno:mainfrom
stupidly-logical:feat/add_oauth_header_on_codegen
Jan 21, 2026
Merged

feat: enhance OAuth2 support in snippet generation#6592
bijin-bruno merged 8 commits intousebruno:mainfrom
stupidly-logical:feat/add_oauth_header_on_codegen

Conversation

@stupidly-logical
Copy link
Contributor

@stupidly-logical stupidly-logical commented Dec 31, 2025

  • Updated getAuthHeaders function to handle OAuth2 authentication, including token retrieval and placement.
  • Added tests for OAuth2 scenarios, ensuring correct Authorization header generation and handling of edge cases.
  • Improved error handling for access token retrieval from stored credentials.

Description

This PR closes #6591
Adds support to OAuth 2.0 token inclusion in generated code snippet.
Screenshot 2025-12-31 at 17 43 11
Screenshot 2025-12-31 at 17 43 20

Contribution Checklist:

  • I've used AI significantly to create this pull request
  • The pull request only addresses one issue or adds one feature.
  • The pull request does not introduce any breaking changes
  • I have added screenshots or gifs to help explain the change if applicable.
  • I have read the contribution guidelines.
  • Create an issue and link to the pull request.

Note: Keeping the PR small and focused helps make it easier to review and merge. If you have multiple changes you want to make, please consider submitting them as separate pull requests.

Publishing to New Package Managers

Summary by CodeRabbit

  • New Features

    • Context-aware OAuth2 header generation that uses collection/item context to resolve tokens
    • Improved token resolution from stored credentials with graceful fallback to placeholders
    • Flexible token placement (header or URL) and customizable token prefix handling
  • Tests

    • Added comprehensive OAuth2 tests covering header insertion, URL placement, prefix variations, and missing tokens

✏️ Tip: You can customize this high-level summary in your review settings.

* Updated getAuthHeaders function to handle OAuth2 authentication, including token retrieval and placement.
* Added tests for OAuth2 scenarios, ensuring correct Authorization header generation and handling of edge cases.
* Improved error handling for access token retrieval from stored credentials.
Copilot AI review requested due to automatic review settings December 31, 2025 12:16
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 31, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

getAuthHeaders gained optional collection and item parameters and now resolves OAuth2 persisted credentials (via interpolated auth/access-token URLs and grant type) to build Authorization headers (respecting tokenPlacement and tokenHeaderPrefix); call sites and tests updated accordingly.

Changes

Cohort / File(s) Summary
Core OAuth2 implementation
packages/bruno-app/src/utils/codegenerator/auth.js
Signature changed to getAuthHeaders(collectionRootAuth, requestAuth, collection = null, item = null). Adds OAuth2 handling: interpolate URLs, lookup persisted credentials (by url/collectionUid/credentialsId), extract access_token or fallback placeholder, respect tokenPlacement (header/url) and tokenHeaderPrefix.
Call site updates
packages/bruno-app/src/components/Sidebar/.../snippet-generator.js
Updated call sites to pass collection and item into getAuthHeaders, increasing its arity.
Test coverage
packages/bruno-app/src/components/Sidebar/.../snippet-generator.spec.js
New tests mocking getAuthHeaders to validate OAuth2 scenarios: header vs url placement, default/custom/empty tokenHeaderPrefix, and placeholder behavior when access token is missing; asserts produced HAR headers.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as Snippet Generator
    participant Auth as getAuthHeaders
    participant Store as Persisted Credentials Store
    participant HAR as HAR Builder

    rect rgb(240,248,255)
    note right of UI: Trigger: generate snippet\n(with collection & item)
    UI->>Auth: getAuthHeaders(collectionAuth, requestAuth,\ncollection, item)
    end

    rect rgb(255,250,240)
    Auth->>Store: interpolate auth/accessToken URL\n(using collection+item variables)
    alt matching credentials found
        Store-->>Auth: credentials (access_token)
    else not found / error
        Store-->>Auth: none / error
    end
    Auth-->>Auth: build token string\napply tokenHeaderPrefix (if any)\nrespect tokenPlacement
    Auth-->>UI: headers[] (or empty)
    end

    rect rgb(240,255,245)
    UI->>HAR: attach headers (unless tokenPlacement == url)
    HAR-->>UI: final HAR request
    UI-->>User: generated code snippet
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • lohit-bruno
  • naman-bruno
  • bijin-bruno

Poem

A token found, or placeholder sings,
Prefixed, trimmed, on header wings.
From store through interp’late light,
Snippets carry OAuth’s might. ✨

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: enhanced OAuth2 support in code snippet generation, matching the core objective of issue #6591.
Linked Issues check ✅ Passed Changes implement all coding requirements from #6591: OAuth2 token retrieval from stored credentials, Authorization header generation, tokenPlacement handling, and comprehensive test coverage.
Out of Scope Changes check ✅ Passed All changes are scoped to OAuth2 support: getAuthHeaders signature extension, credential lookup logic, header formatting, and test additions. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds OAuth2 support to the code snippet generation feature, enabling generated code snippets to include OAuth2 access tokens in Authorization headers. The implementation retrieves stored OAuth2 credentials from the collection and includes them in the generated snippets.

  • Enhanced the getAuthHeaders function to handle OAuth2 authentication with token retrieval from stored credentials
  • Added comprehensive test coverage for various OAuth2 scenarios including different token placements and header prefixes
  • Updated the snippet generator to pass collection and item context to enable credential lookup

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

File Description
packages/bruno-app/src/utils/codegenerator/auth.js Added OAuth2 case handling with token retrieval logic, supporting both header and URL token placement, with fallback to placeholder tokens
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js Updated getAuthHeaders call to include collection and item parameters for OAuth2 credential lookup
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js Added comprehensive test suite for OAuth2 authentication scenarios covering header placement, custom prefixes, URL placement, and edge cases

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js (1)

590-746: Consider adding a test for OAuth2 auth inheritance.

Tests cover direct OAuth2 auth well, but there's no coverage for when request.auth.mode === 'inherit' with OAuth2 configured at the collection level. This is an edge case worth validating.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce9773b and 55ad8a9.

📒 Files selected for processing (3)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
🧠 Learnings (5)
📓 Common learnings
Learnt from: bijin-bruno
Repo: usebruno/bruno PR: 6263
File: packages/bruno-requests/src/auth/oauth2-helper.ts:249-249
Timestamp: 2025-12-02T07:24:50.311Z
Learning: In OAuth2 Basic Auth headers for Bruno, clientSecret is optional and can be omitted. When constructing the Authorization header in `packages/bruno-requests/src/auth/oauth2-helper.ts`, use `clientSecret || ''` instead of `clientSecret!` to properly handle cases where only clientId is provided, per community requests.
📚 Learning: 2025-12-02T07:24:50.311Z
Learnt from: bijin-bruno
Repo: usebruno/bruno PR: 6263
File: packages/bruno-requests/src/auth/oauth2-helper.ts:249-249
Timestamp: 2025-12-02T07:24:50.311Z
Learning: In OAuth2 Basic Auth headers for Bruno, clientSecret is optional and can be omitted. When constructing the Authorization header in `packages/bruno-requests/src/auth/oauth2-helper.ts`, use `clientSecret || ''` instead of `clientSecret!` to properly handle cases where only clientId is provided, per community requests.

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
📚 Learning: 2025-12-16T07:16:23.647Z
Learnt from: sanish-bruno
Repo: usebruno/bruno PR: 6090
File: tests/scripting/hooks/init-user-data/ui-state-snapshot.json:1-8
Timestamp: 2025-12-16T07:16:23.647Z
Learning: For e2e tests in the bruno repository: Collections that are shared between CLI and UI tests (comprehensive test suites testing core functionality) should be placed in `packages/bruno-tests/` to avoid duplication. The `tests/**/fixtures/collection` pattern should be used for test-specific collections that test particular UI behaviors or are specific to a single test file.

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
🧬 Code graph analysis (3)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js (1)
packages/bruno-app/src/utils/codegenerator/auth.js (2)
  • getAuthHeaders (6-118)
  • getAuthHeaders (6-118)
packages/bruno-app/src/utils/codegenerator/auth.js (3)
packages/bruno-cli/src/runner/prepare-request.js (1)
  • get (1-1)
packages/bruno-electron/src/ipc/network/prepare-request.js (2)
  • grantType (75-75)
  • grantType (192-192)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js (1)
  • variables (12-12)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js (3)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js (3)
  • require (10-10)
  • collectionAuth (22-22)
  • generateSnippet (7-50)
packages/bruno-app/src/utils/auth/index.js (1)
  • collectionAuth (25-25)
packages/bruno-app/src/utils/codegenerator/auth.js (4)
  • oauth2Config (55-55)
  • tokenHeaderPrefix (57-57)
  • accessToken (62-62)
  • headerValue (101-103)
🪛 Biome (2.1.2)
packages/bruno-app/src/utils/codegenerator/auth.js

[error] 55-55: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 56-56: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 57-57: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: CLI Tests
  • GitHub Check: Playwright E2E Tests
  • GitHub Check: Unit Tests
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Windows
🔇 Additional comments (4)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js (1)

21-24: LGTM!

The updated call to getAuthHeaders correctly passes collection and item context, enabling OAuth2 credential resolution. Both variables are properly in scope.

packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js (1)

558-588: Good test coverage for OAuth2 scenarios.

The mock implementation correctly mirrors the production logic for OAuth2 header generation. Tests effectively validate the header output through buildHarRequest mock calls.

packages/bruno-app/src/utils/codegenerator/auth.js (2)

2-6: LGTM!

New imports are all used in the OAuth2 implementation, and the function signature maintains backward compatibility with null defaults.


66-77: Good implementation of OAuth2 token resolution.

The logic correctly differentiates between implicit grant (using authorizationUrl) and other grant types (using accessTokenUrl). URL interpolation ensures variable substitution matches persisted credentials accurately.

* Updated comparison operators in the getAuthHeaders function to use strict equality (===) for improved consistency and reliability in credential checks.
* Added missing block structure for the 'oauth2' case in the getAuthHeaders function to ensure proper execution flow and maintain code clarity.
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

* Updated getAuthHeaders function to support retrieval of stored OAuth2 credentials based on collection and item context.
* Improved access token handling by checking for existing credentials before falling back to default values.
* Enhanced test coverage for OAuth2 scenarios to ensure accurate token management and error handling.
* Updated snippet-generator.spec.js to ensure that the tokenHeaderPrefix from OAuth2 configuration is preserved, allowing for empty string scenarios.
* Default to 'Bearer' only if the tokenHeaderPrefix is undefined, enhancing flexibility in token management.
…andling

* Updated getAuthHeaders function to always trim the final result of the authorization header for consistent formatting.
* Adjusted snippet-generator.spec.js to reflect the same trimming logic for the access token, enhancing test reliability.
* Updated comments in the getAuthHeaders function to specify that when tokenPlacement is 'url', no auth headers are added, and that token placement in the URL/query params must be managed separately.
…ction

* Updated getAuthHeaders function to default to an empty array when accessing oauth2Credentials, preventing potential errors when no credentials are available.
@vercel
Copy link

vercel bot commented Dec 31, 2025

@stupidly-logical is attempting to deploy a commit to the Bruno Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js (1)

619-650: Consider adding a test for stored OAuth2 credentials lookup.

The tests cover direct accessToken from config, but don't exercise the credential lookup path where collection.oauth2Credentials is populated and matched by URL/collectionUid/credentialsId. This is a key feature of the implementation.

🔎 Example test structure
it('should use stored credentials when available', () => {
  const collectionWithCreds = {
    ...baseCollection,
    uid: 'test-collection-uid',
    oauth2Credentials: [
      {
        url: 'https://auth.example.com/token',
        collectionUid: 'test-collection-uid',
        credentialsId: 'credentials',
        credentials: { access_token: 'stored-token-789' }
      }
    ]
  };

  const item = {
    uid: 'oauth-req-stored',
    request: {
      method: 'GET',
      url: 'https://api.example.com/users',
      headers: [],
      auth: {
        mode: 'oauth2',
        oauth2: {
          grantType: 'client_credentials',
          accessTokenUrl: 'https://auth.example.com/token',
          tokenPlacement: 'header',
          tokenHeaderPrefix: 'Bearer'
        }
      }
    }
  };

  generateSnippet({ language, item, collection: collectionWithCreds, shouldInterpolate: false });

  const harUtils = require('utils/codegenerator/har');
  const harCall = harUtils.buildHarRequest.mock.calls[0][0];
  expect(harCall.headers).toEqual(
    expect.arrayContaining([
      expect.objectContaining({
        name: 'Authorization',
        value: 'Bearer stored-token-789'
      })
    ])
  );
});
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b22731b and cfd3ab2.

📒 Files selected for processing (2)
  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (CODING_STANDARDS.md)

**/*.{js,jsx,ts,tsx}: Use 2 spaces for indentation. No tabs, just spaces
Stick to single quotes for strings. For JSX/TSX attributes, use double quotes (e.g., )
Always add semicolons at the end of statements
No trailing commas
Always use parentheses around parameters in arrow functions, even for single params
For multiline constructs, put opening braces on the same line, and ensure consistency. Minimum 2 elements for multiline
No newlines inside function parentheses
Space before and after the arrow in arrow functions. () => {} is good
No space between function name and parentheses. func() not func ()
Semicolons go at the end of the line, not on a new line
Names for functions need to be concise and descriptive
Add in JSDoc comments to add more details to the abstractions if needed
Add in meaningful comments instead of obvious ones where complex code flow is explained properly

Files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
🧠 Learnings (5)
📓 Common learnings
Learnt from: bijin-bruno
Repo: usebruno/bruno PR: 6263
File: packages/bruno-requests/src/auth/oauth2-helper.ts:249-249
Timestamp: 2025-12-02T07:24:50.311Z
Learning: In OAuth2 Basic Auth headers for Bruno, clientSecret is optional and can be omitted. When constructing the Authorization header in `packages/bruno-requests/src/auth/oauth2-helper.ts`, use `clientSecret || ''` instead of `clientSecret!` to properly handle cases where only clientId is provided, per community requests.
📚 Learning: 2025-12-05T20:31:33.005Z
Learnt from: CR
Repo: usebruno/bruno PR: 0
File: CODING_STANDARDS.md:0-0
Timestamp: 2025-12-05T20:31:33.005Z
Learning: Applies to **/*.test.{js,jsx,ts,tsx} : Add tests for any new functionality or meaningful changes. If code is added, removed, or significantly modified, corresponding tests should be updated or created

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
📚 Learning: 2025-12-16T07:16:23.647Z
Learnt from: sanish-bruno
Repo: usebruno/bruno PR: 6090
File: tests/scripting/hooks/init-user-data/ui-state-snapshot.json:1-8
Timestamp: 2025-12-16T07:16:23.647Z
Learning: For e2e tests in the bruno repository: Collections that are shared between CLI and UI tests (comprehensive test suites testing core functionality) should be placed in `packages/bruno-tests/` to avoid duplication. The `tests/**/fixtures/collection` pattern should be used for test-specific collections that test particular UI behaviors or are specific to a single test file.

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
📚 Learning: 2025-12-02T07:24:50.311Z
Learnt from: bijin-bruno
Repo: usebruno/bruno PR: 6263
File: packages/bruno-requests/src/auth/oauth2-helper.ts:249-249
Timestamp: 2025-12-02T07:24:50.311Z
Learning: In OAuth2 Basic Auth headers for Bruno, clientSecret is optional and can be omitted. When constructing the Authorization header in `packages/bruno-requests/src/auth/oauth2-helper.ts`, use `clientSecret || ''` instead of `clientSecret!` to properly handle cases where only clientId is provided, per community requests.

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
📚 Learning: 2025-12-17T21:41:24.730Z
Learnt from: naman-bruno
Repo: usebruno/bruno PR: 6407
File: packages/bruno-app/src/components/Environments/ConfirmCloseEnvironment/index.js:5-41
Timestamp: 2025-12-17T21:41:24.730Z
Learning: Do not suggest PropTypes validation for React components in the Bruno codebase. The project does not use PropTypes, so reviews should avoid proposing PropTypes and rely on the existing typing/validation approach (e.g., TypeScript or alternative runtime checks) if applicable. This guideline applies broadly to all JavaScript/JSX components in the repo.

Applied to files:

  • packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js
  • packages/bruno-app/src/utils/codegenerator/auth.js
🧬 Code graph analysis (2)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js (2)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js (2)
  • require (10-10)
  • generateSnippet (7-50)
packages/bruno-app/src/utils/codegenerator/auth.js (8)
  • tokenPlacement (56-56)
  • tokenHeaderPrefix (57-57)
  • accessToken (62-62)
  • grantType (66-66)
  • urlToLookup (68-70)
  • credentialsId (71-71)
  • collectionUid (72-72)
  • headerValue (102-106)
packages/bruno-app/src/utils/codegenerator/auth.js (5)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/CodeView/index.js (1)
  • collection (24-32)
packages/bruno-cli/src/runner/prepare-request.js (2)
  • get (1-1)
  • find (4-4)
packages/bruno-electron/src/ipc/network/prepare-request.js (2)
  • grantType (75-75)
  • grantType (192-192)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.js (1)
  • variables (12-12)
packages/bruno-js/src/sandbox/quickjs/shims/bru.js (1)
  • interpolate (32-34)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: SSL Tests - macOS
  • GitHub Check: SSL Tests - Linux
  • GitHub Check: SSL Tests - Windows
  • GitHub Check: Unit Tests
  • GitHub Check: CLI Tests
  • GitHub Check: Playwright E2E Tests
🔇 Additional comments (5)
packages/bruno-app/src/components/Sidebar/Collections/Collection/CollectionItem/GenerateCodeItem/utils/snippet-generator.spec.js (1)

562-617: Mock implementation is comprehensive and well-structured.

The mock correctly handles the full getAuthHeaders signature (collectionRootAuth, requestAuth, collection, item) and mirrors the actual OAuth2 logic including credential lookup, tokenPlacement handling, and prefix formatting.

One observation: the mock duplicates significant logic from the actual implementation. While this ensures the test validates integration behavior, it also means changes to auth.js require corresponding mock updates. Consider whether a partial mock or spy approach might reduce maintenance burden for future changes.

packages/bruno-app/src/utils/codegenerator/auth.js (4)

1-4: Imports look good.

New imports for find, interpolate, and getAllVariables are correctly added to support the OAuth2 credential lookup functionality.


6-6: Signature extension maintains backward compatibility.

Default values for collection and item ensure existing callers continue to work without modification.


54-119: OAuth2 implementation is well-structured.

Block scope, strict equality, consistent trimming, and safe fallback for oauth2Credentials are all properly implemented. The try-catch with console.error provides graceful degradation when credential lookup fails.

One minor note: the tokenHeaderPrefix default handling with get(oauth2Config, 'tokenHeaderPrefix', 'Bearer') correctly preserves an explicit empty string '' since lodash get only applies the default when the value is undefined, not when it's falsy.


66-71: The grant type URL selection logic is correct and properly covers all OAuth2 flows. The schema defines four grant types: client_credentials, password, authorization_code, and implicit. The code correctly uses authorizationUrl for implicit (which has no separate token endpoint) and accessTokenUrl for the other three types (all requiring token exchange). This aligns with OAuth2 specification and the schema validation rules.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

import { find } from 'lodash';
import { interpolate } from '@usebruno/common';
import { getAllVariables } from 'utils/collections/index';

Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

The getAuthHeaders function signature has been extended with two new optional parameters (collection and item), but there's no JSDoc or inline documentation explaining when these parameters are needed and what their purpose is. Since other auth modes (basic, bearer, apikey) don't use these parameters, it would be helpful to document that these are specifically needed for OAuth2 token resolution from stored credentials.

Suggested change
/**
* Builds authorization headers for a request based on collection-level and request-level auth settings.
*
* For most auth modes (basic, bearer, apikey), only {@link collectionRootAuth} and {@link requestAuth}
* are required. The {@link collection} and {@link item} parameters are optional and are used
* specifically for OAuth2 flows where the access token needs to be resolved from stored
* credentials or variables defined at the collection/request level.
*
* @param {Object|null} collectionRootAuth Auth configuration defined at the collection root.
* @param {Object|null} requestAuth Auth configuration defined on the request (may inherit from collection).
* @param {Object|null} [collection=null] Optional collection object used for resolving OAuth2 tokens
* from stored credentials/variables. Ignored by non-OAuth2 modes.
* @param {Object|null} [item=null] Optional request/item object used for resolving OAuth2 tokens
* from stored credentials/variables. Ignored by non-OAuth2 modes.
* @returns {Array<Object>} An array of header objects to apply to the request.
*/

Copilot uses AI. Check for mistakes.
}
} catch (error) {
console.error('Error retrieving OAuth2 access token:', error);
// Fall back to placeholder if lookup fails
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

The error handling catches all errors during OAuth2 token retrieval but only logs them to the console. In a production environment, this might make debugging difficult if token lookup fails silently. Consider whether additional error handling is needed, such as setting a more descriptive placeholder value like '<error_retrieving_token>' to help users understand that token retrieval failed, or exposing this error through the UI in some way.

Suggested change
// Fall back to placeholder if lookup fails
// Indicate that an error occurred during token retrieval
accessToken = '<error_retrieving_token>';

Copilot uses AI. Check for mistakes.
Comment on lines +558 to +776
describe('generateSnippet with OAuth2 authentication', () => {
const language = { target: 'shell', client: 'curl' };
const baseCollection = { root: { request: { auth: { mode: 'none' }, headers: [] } } };

beforeEach(() => {
jest.clearAllMocks();
// Mock getAuthHeaders to return OAuth2 headers based on the auth config
const authUtils = require('utils/codegenerator/auth');
authUtils.getAuthHeaders.mockImplementation((collectionRootAuth, requestAuth, collection = null, item = null) => {
if (requestAuth?.mode === 'oauth2') {
const oauth2Config = requestAuth.oauth2 || {};
const tokenPlacement = oauth2Config.tokenPlacement || 'header';
// Use the actual value from config, defaulting to 'Bearer' only if undefined
// Empty string should be preserved to test no-prefix scenarios
const tokenHeaderPrefix = oauth2Config.tokenHeaderPrefix !== undefined
? oauth2Config.tokenHeaderPrefix
: 'Bearer';
let accessToken = oauth2Config.accessToken || '<access_token>';

// If collection and item are provided, try to look up stored credentials
if (collection && item && collection.oauth2Credentials) {
const grantType = oauth2Config.grantType || '';
const urlToLookup = grantType === 'implicit'
? oauth2Config.authorizationUrl || ''
: oauth2Config.accessTokenUrl || '';
const credentialsId = oauth2Config.credentialsId || 'credentials';
const collectionUid = collection.uid;

if (urlToLookup && collectionUid) {
// Look up stored credentials (simplified - assumes URL is already interpolated in test data)
const credentialsData = collection.oauth2Credentials.find(
(creds) =>
creds?.url === urlToLookup
&& creds?.collectionUid === collectionUid
&& creds?.credentialsId === credentialsId
);

if (credentialsData?.credentials?.access_token) {
accessToken = credentialsData.credentials.access_token;
}
}
}

if (tokenPlacement === 'header') {
// Always trim the final result for consistent formatting
const headerValue = tokenHeaderPrefix
? `${tokenHeaderPrefix} ${accessToken}`.trim()
: accessToken.trim();
return [
{
enabled: true,
name: 'Authorization',
value: headerValue
}
];
}
}
return [];
});
});

it('should include OAuth2 Bearer token in Authorization header when tokenPlacement is header', () => {
const item = {
uid: 'oauth-req',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'Bearer',
accessToken: 'test-access-token-123'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'Bearer test-access-token-123'
})
])
);
});

it('should use custom tokenHeaderPrefix when provided', () => {
const item = {
uid: 'oauth-req-custom',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'OAuth',
accessToken: 'custom-token-456'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'OAuth custom-token-456'
})
])
);
});

it('should not include Authorization header when tokenPlacement is url', () => {
const item = {
uid: 'oauth-req-url',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'url',
tokenQueryKey: 'access_token',
accessToken: 'token-in-url'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
const authHeader = harCall.headers.find((h) => h.name === 'Authorization');
expect(authHeader).toBeUndefined();
});

it('should use placeholder when accessToken is not available', () => {
const item = {
uid: 'oauth-req-placeholder',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'Bearer'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'Bearer <access_token>'
})
])
);
});

it('should handle empty tokenHeaderPrefix', () => {
const item = {
uid: 'oauth-req-no-prefix',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: '',
accessToken: 'token-without-prefix'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'token-without-prefix'
})
])
);
});
});
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

The tests don't cover the scenario where OAuth2 credentials are retrieved from stored credentials in the collection (collection.oauth2Credentials). All test cases provide the accessToken directly in the oauth2Config. Consider adding a test case that verifies the credential lookup logic works correctly, including testing with interpolated URLs and matching collectionUid, credentialsId, and URL values. This would ensure the core feature of retrieving tokens from persisted credentials is properly tested.

Copilot uses AI. Check for mistakes.
Comment on lines +558 to +776
describe('generateSnippet with OAuth2 authentication', () => {
const language = { target: 'shell', client: 'curl' };
const baseCollection = { root: { request: { auth: { mode: 'none' }, headers: [] } } };

beforeEach(() => {
jest.clearAllMocks();
// Mock getAuthHeaders to return OAuth2 headers based on the auth config
const authUtils = require('utils/codegenerator/auth');
authUtils.getAuthHeaders.mockImplementation((collectionRootAuth, requestAuth, collection = null, item = null) => {
if (requestAuth?.mode === 'oauth2') {
const oauth2Config = requestAuth.oauth2 || {};
const tokenPlacement = oauth2Config.tokenPlacement || 'header';
// Use the actual value from config, defaulting to 'Bearer' only if undefined
// Empty string should be preserved to test no-prefix scenarios
const tokenHeaderPrefix = oauth2Config.tokenHeaderPrefix !== undefined
? oauth2Config.tokenHeaderPrefix
: 'Bearer';
let accessToken = oauth2Config.accessToken || '<access_token>';

// If collection and item are provided, try to look up stored credentials
if (collection && item && collection.oauth2Credentials) {
const grantType = oauth2Config.grantType || '';
const urlToLookup = grantType === 'implicit'
? oauth2Config.authorizationUrl || ''
: oauth2Config.accessTokenUrl || '';
const credentialsId = oauth2Config.credentialsId || 'credentials';
const collectionUid = collection.uid;

if (urlToLookup && collectionUid) {
// Look up stored credentials (simplified - assumes URL is already interpolated in test data)
const credentialsData = collection.oauth2Credentials.find(
(creds) =>
creds?.url === urlToLookup
&& creds?.collectionUid === collectionUid
&& creds?.credentialsId === credentialsId
);

if (credentialsData?.credentials?.access_token) {
accessToken = credentialsData.credentials.access_token;
}
}
}

if (tokenPlacement === 'header') {
// Always trim the final result for consistent formatting
const headerValue = tokenHeaderPrefix
? `${tokenHeaderPrefix} ${accessToken}`.trim()
: accessToken.trim();
return [
{
enabled: true,
name: 'Authorization',
value: headerValue
}
];
}
}
return [];
});
});

it('should include OAuth2 Bearer token in Authorization header when tokenPlacement is header', () => {
const item = {
uid: 'oauth-req',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'Bearer',
accessToken: 'test-access-token-123'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'Bearer test-access-token-123'
})
])
);
});

it('should use custom tokenHeaderPrefix when provided', () => {
const item = {
uid: 'oauth-req-custom',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'OAuth',
accessToken: 'custom-token-456'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'OAuth custom-token-456'
})
])
);
});

it('should not include Authorization header when tokenPlacement is url', () => {
const item = {
uid: 'oauth-req-url',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'url',
tokenQueryKey: 'access_token',
accessToken: 'token-in-url'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
const authHeader = harCall.headers.find((h) => h.name === 'Authorization');
expect(authHeader).toBeUndefined();
});

it('should use placeholder when accessToken is not available', () => {
const item = {
uid: 'oauth-req-placeholder',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: 'Bearer'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'Bearer <access_token>'
})
])
);
});

it('should handle empty tokenHeaderPrefix', () => {
const item = {
uid: 'oauth-req-no-prefix',
request: {
method: 'GET',
url: 'https://api.example.com/users',
headers: [],
auth: {
mode: 'oauth2',
oauth2: {
grantType: 'client_credentials',
tokenPlacement: 'header',
tokenHeaderPrefix: '',
accessToken: 'token-without-prefix'
}
}
}
};

generateSnippet({ language, item, collection: baseCollection, shouldInterpolate: false });

const harUtils = require('utils/codegenerator/har');
const harCall = harUtils.buildHarRequest.mock.calls[0][0];
expect(harCall.headers).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: 'Authorization',
value: 'token-without-prefix'
})
])
);
});
});
Copy link

Copilot AI Dec 31, 2025

Choose a reason for hiding this comment

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

The tests don't cover the scenario where grantType is 'implicit', which causes the code to look up credentials using authorizationUrl instead of accessTokenUrl. Consider adding a test case that verifies this logic path works correctly to ensure both credential lookup mechanisms are tested.

Copilot uses AI. Check for mistakes.
@bijin-bruno bijin-bruno merged commit 0bf1695 into usebruno:main Jan 21, 2026
14 of 15 checks passed
FraCata00 pushed a commit to FraCata00/bruno that referenced this pull request Feb 9, 2026
* feat: enhance OAuth2 support in snippet generation

* Updated getAuthHeaders function to handle OAuth2 authentication, including token retrieval and placement.
* Added tests for OAuth2 scenarios, ensuring correct Authorization header generation and handling of edge cases.
* Improved error handling for access token retrieval from stored credentials.

* refactor: standardize comparison operators in getAuthHeaders function

* Updated comparison operators in the getAuthHeaders function to use strict equality (===) for improved consistency and reliability in credential checks.

* fix: correct block structure in OAuth2 case of getAuthHeaders function

* Added missing block structure for the 'oauth2' case in the getAuthHeaders function to ensure proper execution flow and maintain code clarity.

* feat: enhance OAuth2 credential retrieval in getAuthHeaders function

* Updated getAuthHeaders function to support retrieval of stored OAuth2 credentials based on collection and item context.
* Improved access token handling by checking for existing credentials before falling back to default values.
* Enhanced test coverage for OAuth2 scenarios to ensure accurate token management and error handling.

* fix: preserve tokenHeaderPrefix value in OAuth2 configuration

* Updated snippet-generator.spec.js to ensure that the tokenHeaderPrefix from OAuth2 configuration is preserved, allowing for empty string scenarios.
* Default to 'Bearer' only if the tokenHeaderPrefix is undefined, enhancing flexibility in token management.

* fix: ensure consistent formatting of authorization header in OAuth2 handling

* Updated getAuthHeaders function to always trim the final result of the authorization header for consistent formatting.
* Adjusted snippet-generator.spec.js to reflect the same trimming logic for the access token, enhancing test reliability.

* fix: clarify token placement handling in getAuthHeaders function

* Updated comments in the getAuthHeaders function to specify that when tokenPlacement is 'url', no auth headers are added, and that token placement in the URL/query params must be managed separately.

* fix: ensure safe handling of OAuth2 credentials in getAuthHeaders function

* Updated getAuthHeaders function to default to an empty array when accessing oauth2Credentials, preventing potential errors when no credentials are available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support OAuth 2.0 token addition on Code Snippet Generation

3 participants