Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API key support for JSON-RPC requests #763

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

joshuahamsa
Copy link

@joshuahamsa joshuahamsa commented Oct 30, 2024

This change allows JsonRpcBase to include an API key in headers when connecting to XRPL nodes that require authentication. The __init__ method now accepts an api_key argument, which is stored as an attribute and used in _request_impl.

  • Updated the _request_impl method to add the Authorization header with the API key when provided.
  • Added an api_key parameter to the __init__ method for easy instantiation with an API key.
  • This change was tested by connecting to a private XRPL server with required API authentication.

High Level Overview of Change

This PR introduces support for API key authentication in the JsonRpcBase class. An __init__ method has been added to include an optional api_key parameter, which, if provided, is used to add an Authorization header with each JSON-RPC request. This enables users to connect to private XRPL nodes that require API key-based authentication while maintaining compatibility with public nodes.

Context of Change

This change is driven by the growing use of API-key-protected XRPL nodes, often necessary to avoid rate-limiting on public nodes. Previously, JsonRpcBase could not include an API key in request headers, limiting its ability to connect to private nodes that require authentication. By adding this feature, xrpl-py can now be used more flexibly in production environments and with private infrastructure.

In considering alternatives, modifying json_rpc_base.py was selected as the most efficient approach, as this base class is shared across JSON-RPC implementations. Adding the api_key attribute at this level ensures that all inheriting classes have access to the feature, allowing for consistent usage without impacting users who do not require an API key.

Type of Change

  • New feature (non-breaking change which adds functionality)

Did you update CHANGELOG.md?

  • Yes
  • No, this change does not impact library users

Test Plan

The change could be tested by:

  • Connecting to a private XRPL server that requires an API key and verifying that transactions and data requests are successfully processed.
  • Ensuring compatibility with public nodes by omitting the api_key parameter, confirming that the change does not introduce any issues when using unauthenticated nodes.
  • Verifying optionality of the api_key parameter to ensure existing users can use JsonRpcBase without changes to their code.

Future Tasks

Future tasks related to this PR could include:

  • Extending API key support to other client classes if needed.
  • Adding additional test coverage in CI for scenarios where the API key is and isn’t provided to verify consistency across environments.

Summary by CodeRabbit

  • New Features
    • Introduced an optional api_key parameter for the JsonRpcBase class, allowing users to authenticate requests to private XRPL servers.
    • Enhanced request handling to include the API key in headers for secure communication.

These updates improve user experience by enabling secure connections to private XRPL nodes.

This change allows `JsonRpcBase` to include an API key in headers when
connecting to XRPL nodes that require authentication. The `__init__` 
method now accepts an `api_key` argument, which is stored as an 
attribute and used in `_request_impl`.

- Updated the `_request_impl` method to add the `Authorization` header
  with the API key when provided.
- Added an `api_key` parameter to the `__init__` method for easy 
  instantiation with an API key.
- This change was tested by connecting to a private XRPL server with 
  required API authentication.
Copy link
Contributor

coderabbitai bot commented Oct 30, 2024

Walkthrough

The changes involve modifications to the JsonRpcBase class within the xrpl/asyncio/clients/json_rpc_base.py file. An optional api_key parameter has been added to the constructor, allowing the client to be initialized with an API key for private XRPL server connections. The _request_impl method has been updated to include this API key in the request headers, ensuring it is sent with HTTP POST requests when available.

Changes

File Path Change Summary
xrpl/asyncio/clients/json_rpc_base.py Added optional api_key parameter to __init__ method; updated _request_impl to include API key in request headers.

Poem

In the code where rabbits play,
An API key has found its way.
With headers set and tokens bright,
Our requests now take to flight!
Hopping high with joy and cheer,
Private connections, oh so near! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

🧹 Outside diff range and nitpick comments (2)
xrpl/asyncio/clients/json_rpc_base.py (2)

25-34: Enhance API key parameter documentation.

The implementation looks good, but consider expanding the API key documentation to include:

  • Expected format (Bearer token)
  • Usage implications
  • Whether it's required for private servers

Consider updating the docstring:

     """
     Initializes a new JsonRpcBase client.

     Arguments:
         url: The URL of the XRPL node to connect to.
-        api_key: Optional API key for connecting to a private XRPL server.
+        api_key: Optional API key for authenticating with private XRPL servers.
+            When provided, it will be sent as a Bearer token in the Authorization
+            header. Required for private servers and recommended for public nodes
+            to avoid rate limiting.
     """

Line range hint 68-76: Consider enhancing error handling for authentication failures.

The current error handling doesn't distinguish between authentication failures and other HTTP errors. Consider:

  1. Adding specific handling for 401/403 status codes
  2. Creating a dedicated exception type for authentication failures

Example enhancement:

if response.status_code in (401, 403):
    raise XRPLAuthenticationException(
        f"Authentication failed: {response.text}"
    )
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between edfb99b and b34041b.

📒 Files selected for processing (1)
  • xrpl/asyncio/clients/json_rpc_base.py (3 hunks)
🔇 Additional comments (3)
xrpl/asyncio/clients/json_rpc_base.py (3)

6-6: LGTM: Import added for Optional type.

The addition of Optional import is appropriate for the new api_key parameter type hint.


65-67: LGTM: Headers properly included in request.

The headers dictionary is correctly passed to the post request, maintaining the existing functionality while adding API key support.


55-61: Verify Authorization header format with XRPL documentation.

The implementation looks solid, but there are two considerations:

  1. The comment "Adjust key name if necessary" suggests uncertainty about the header format
  2. There's no validation of the API key format

Consider:

  1. Validating the API key format in the constructor
  2. Making the header name configurable if different XRPL nodes use different header formats
✅ Verification successful

The previous search might have been too specific. Let's broaden our search to understand:

  1. How Authorization headers are used across the codebase
  2. How API keys are handled
  3. If there's any documentation about authentication requirements

The search revealed this is the first implementation of API key support in the codebase. Let's check the documentation and tests to understand if there's any guidance on the expected authentication format.


Bearer token format is appropriate for XRPL API authentication

The implementation is correct as is. Here's why:

  1. This is the first implementation of API key support in the codebase, introducing a standard way to authenticate with XRPL nodes
  2. The Bearer token format is the industry standard for API authentication
  3. The comment "Adjust key name if necessary" can be removed as the Authorization header with Bearer scheme is the correct approach

The uncertainty expressed in the review comment is unwarranted. No changes are needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for any existing Authorization header usage in the codebase
# to ensure consistency
rg -l 'Authorization.*Bearer' 

Length of output: 31


Script:

#!/bin/bash
# Let's search more broadly for any Authorization header usage
rg -i "authorization.*header" -A 3

# Also search for any API key related code
rg -i "api.?key" -A 3

# Check if there's any documentation about authentication
fd -g "*.md" -x grep -l -i "auth\|api.?key" {}

Length of output: 2026


Script:

#!/bin/bash
# Let's check the README.md content for any authentication documentation
cat README.md

# Let's also check if there are any tests that might show the expected usage
rg -i "test.*api.?key" -A 5

Length of output: 16803

@mvadari
Copy link
Collaborator

mvadari commented Oct 30, 2024

To make this a bit more general/future-proof, I would write this to allow any headers in the init (and also in the request method, in case you want to include some headers just for some requests).

@ckeshava
Copy link
Collaborator

@joshuahamsa Is the GenericRequest not helpful for this case? It allows for a generic dictionary of key-value pairs in the request. Here's the source code

@mvadari
Copy link
Collaborator

mvadari commented Nov 22, 2024

@joshuahamsa Is the GenericRequest not helpful for this case? It allows for a generic dictionary of key-value pairs in the request. Here's the source code

Headers aren't included in GenericRequest, they're a separate field.

@ckeshava
Copy link
Collaborator

@joshuahamsa Is the GenericRequest not helpful for this case? It allows for a generic dictionary of key-value pairs in the request. Here's the source code

Headers aren't included in GenericRequest, they're a separate field.

I understand that, but GenericRequest accepts a **kwargs input field. This field could theoretically accommodate any header key-value pairs.

The API keys appear to be required at the POST request construction.

api_keys: Optional[str] = None
if request.method == RequestMethod.GENERIC_REQUEST and request.api_key:
    api_keys = request.api_keys

async with AsyncClient(timeout=timeout) as http_client:
            response = await http_client.post(
                self.url,
                json=request_to_json_rpc(request),
                headers=api_keys,  # Include the headers with the optional API key
            )

Alternatively, it would be appropriate to make these changes at the top-most Client class. If a websocket connection needs to specify API-keys, this PR change cannot accommodate such use cases.

@mvadari
Copy link
Collaborator

mvadari commented Nov 22, 2024

Headers aren't included in GenericRequest, they're a separate field.

I understand that, but GenericRequest accepts a **kwargs input field. This field could theoretically accommodate any header key-value pairs.

The API keys appear to be required at the POST request construction.

api_keys: Optional[str] = None
if request.method == RequestMethod.GENERIC_REQUEST and request.api_key:
    api_keys = request.api_keys

async with AsyncClient(timeout=timeout) as http_client:
            response = await http_client.post(
                self.url,
                json=request_to_json_rpc(request),
                headers=api_keys,  # Include the headers with the optional API key
            )

That wouldn't be very developer-friendly. Why should headers be limited to just the GenericRequest model, and not allowed in other requests? GenericRequest is only supposed to be for requests that we don't have models for, such as admin methods - which you're highly unlikely to be using an API key for.

Alternatively, it would be appropriate to make these changes at the top-most Client class. If a websocket connection needs to specify API-keys, this PR change cannot accommodate such use cases.

Agreed, but adding it to just JSONRPC clients would be a good start.

@ckeshava
Copy link
Collaborator

Hmm, I assumed that this was a use case for admin-methods. rippled operators could provide a wrapper with API-keys for their users to access admin methods. But I agree with you, other Request's should also provide support for this header.

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