Skip to content

feat: enhance Raycast integration and redirect URI validation - #165

Open
Zingzy wants to merge 3 commits into
mainfrom
fix/allow-wild-card-redirect-apps
Open

feat: enhance Raycast integration and redirect URI validation#165
Zingzy wants to merge 3 commits into
mainfrom
fix/allow-wild-card-redirect-apps

Conversation

@Zingzy

@Zingzy Zingzy commented Apr 25, 2026

Copy link
Copy Markdown
Member
  • Updated Raycast extension description to clarify link management functionality.
  • Added redirect URI allowlist validation to ensure secure handling of redirect URIs in device authentication.
  • Improved the validate_redirect_uri method to support exact and prefix matching for redirect URIs, enhancing security for OAuth clients.

Summary by Sourcery

Add wildcard-aware redirect URI validation for device auth and update Raycast app configuration.

New Features:

  • Support wildcard-style redirect URIs for device-auth apps, allowing exact and prefix matches in the allowlist.
  • Configure the Raycast extension app with redirect URIs, Raycast-specific link, and explicit permissions.

Enhancements:

  • Align device auth callback redirect handling with the shared redirect URI validation logic.
  • Clarify the Raycast extension description to reflect broader link management capabilities.

Summary by CodeRabbit

  • New Features

    • Support for wildcard-suffix redirect URIs for OAuth, allowing flexible redirect patterns.
    • Raycast app entry updated: improved description, explicit permissions, redirect URIs, and live status.
  • Tests

    • Added unit and integration tests validating wildcard and exact redirect URI behaviors.

- Updated Raycast extension description to clarify link management functionality.
- Added redirect URI allowlist validation to ensure secure handling of redirect URIs in device authentication.
- Improved the `validate_redirect_uri` method to support exact and prefix matching for redirect URIs, enhancing security for OAuth clients.
Copilot AI review requested due to automatic review settings April 25, 2026 21:33
@Zingzy Zingzy self-assigned this Apr 25, 2026
@sourcery-ai

sourcery-ai Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Implements stricter and more flexible redirect URI validation for device auth (including prefix wildcards) and configures the Raycast app to use it, along with updating its metadata.

Sequence diagram for device auth callback redirect with enhanced redirect URI validation

sequenceDiagram
    actor RaycastUser
    participant RaycastExtension
    participant DeviceAuthEndpoint
    participant DeviceAuthService
    participant AppEntry

    RaycastUser->>RaycastExtension: Complete device auth in Raycast
    RaycastExtension->>DeviceAuthEndpoint: GET /auth/device/callback?code&state&redirect_uri
    DeviceAuthEndpoint->>DeviceAuthService: resolve_app(app_id)
    DeviceAuthService-->>DeviceAuthEndpoint: AppEntry

    DeviceAuthEndpoint->>DeviceAuthService: validate_redirect_uri(redirect_uri, app)
    DeviceAuthService->>AppEntry: read redirect_uris allowlist
    alt redirect_uri empty
        DeviceAuthService-->>DeviceAuthEndpoint: true
    else redirect_uri matches exact allowlist entry
        DeviceAuthService-->>DeviceAuthEndpoint: true
    else redirect_uri matches prefix of entry ending with *
        DeviceAuthService-->>DeviceAuthEndpoint: true
    else no match
        DeviceAuthService-->>DeviceAuthEndpoint: false
    end

    alt redirect_uri allowed
        DeviceAuthEndpoint->>DeviceAuthEndpoint: build redirect_uri with code and state
        DeviceAuthEndpoint-->>RaycastExtension: 302 Redirect to redirect_uri
    else redirect_uri not allowed
        DeviceAuthEndpoint->>DeviceAuthEndpoint: build default /auth/device/callback URL
        DeviceAuthEndpoint-->>RaycastExtension: 302 Redirect to default callback
    end
Loading

File-Level Changes

Change Details Files
Introduce shared redirect URI allowlist matching logic for device auth callbacks, including support for explicit wildcard prefixes.
  • Add a helper that checks a redirect_uri against an app’s redirect_uris using exact match or prefix match when the allowlist entry ends with '*'
  • Use the new helper in the device auth callback redirect builder to decide whether to redirect to the provided redirect_uri or the default callback page
routes/auth/device.py
Strengthen redirect URI validation in the device auth service to support explicit wildcard entries while keeping empty redirect URIs allowed.
  • Replace simple membership check with loop that supports exact matches and prefix matches for allowlist entries ending with '*'
  • Preserve behavior that an empty redirect_uri is considered valid while disallowing any non-matching URIs
services/auth/device.py
Configure Raycast device app metadata and redirect URI allowlist to align with new validation behavior and describe its capabilities.
  • Update Raycast app description to clarify that it can shorten and manage links
  • Add Raycast-specific redirect_uris entry using a wildcard suffix to allow variable query strings
  • Add Raycast-specific links and permissions to document integration and required scopes
config/apps.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds wildcard prefix matching for OAuth redirect URI validation, updates device auth callback to use the service validator, and updates the Raycast app registry entry with redirect URIs, links, permissions, and status.

Changes

Cohort / File(s) Summary
Raycast App Configuration
config/apps.yaml
Updated apps.spoo-raycast entry: expanded description, statuslive, added redirect_uris, links, and detailed permissions.
Device Auth Service & Tests
services/auth/device.py, tests/unit/services/test_redirect_uri.py, tests/integration/test_device_auth.py
validate_redirect_uri now allows empty URIs, exact matches, and prefix matches for allowlist entries ending with *. Added unit tests for wildcard, exact, multiple entries, and "*" behavior; updated integration test mock behavior accordingly.
Auth Route Callback Logic
routes/auth/device.py
Callback redirect builder now receives DeviceAuthSvc and uses svc.validate_redirect_uri(redirect_uri, app) instead of direct membership checks; calls updated in device_login and device_consent_approve.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant Route as DeviceAuthRoute
  participant Svc as DeviceAuthSvc
  participant AppReg as AppRegistry

  Client->>Route: request device callback (with redirect_uri)
  Route->>AppReg: load app entry
  Route->>Svc: validate_redirect_uri(redirect_uri, app)
  Svc-->>Route: valid / invalid
  alt valid
    Route->>Client: 302 redirect to chosen callback
  else invalid
    Route->>Client: 400 or error page
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A little rabbit hops to say,
Wildcards wander, finding their way.
Redirects stretched with gentle art,
Callbacks choose the proper part.
Configs updated — off we dart! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.84% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 changes: enhancing Raycast integration (updating app config) and adding redirect URI validation with wildcard support across the codebase.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/allow-wild-card-redirect-apps

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.

@sourcery-ai sourcery-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.

Hey - I've found 1 issue, and left some high level feedback:

  • The redirect URI allowlist logic is duplicated between _redirect_uri_allowed in routes/auth/device.py and validate_redirect_uri in DeviceAuthService; consider extracting a shared helper or reusing the service method in the route to avoid future divergence.
  • When doing prefix-based redirect URI checks with startswith, consider normalizing and validating the URI components (e.g., scheme/host, using urlparse) so a crafted URL that only shares a string prefix cannot bypass intended domain or protocol constraints.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The redirect URI allowlist logic is duplicated between `_redirect_uri_allowed` in `routes/auth/device.py` and `validate_redirect_uri` in `DeviceAuthService`; consider extracting a shared helper or reusing the service method in the route to avoid future divergence.
- When doing prefix-based redirect URI checks with `startswith`, consider normalizing and validating the URI components (e.g., scheme/host, using `urlparse`) so a crafted URL that only shares a string prefix cannot bypass intended domain or protocol constraints.

## Individual Comments

### Comment 1
<location path="services/auth/device.py" line_range="70-85" />
<code_context>
+    """
+    if not redirect_uri:
+        return False
+    for allowed in app.redirect_uris:
+        if allowed.endswith("*"):
+            if redirect_uri.startswith(allowed[:-1]):
+                return True
+        elif redirect_uri == allowed:
+            return True
+    return False
</code_context>
<issue_to_address>
**🚨 suggestion (security):** The prefix wildcard matching may be more permissive than intended for some redirect URI patterns.

Using `startswith` on the prefix before `*` means `https://raycast.com/redirect*` will also match `https://raycast.com/redirect-malicious`, not just `.../redirect?…` or `.../redirect/...`. If only that exact path with arbitrary query params is intended, consider tightening the match (e.g., checking the next character is `?` or `/`) or clearly requiring integrators to include a trailing `/` or `?` in wildcard entries to avoid over-broad matches.

```suggestion
        """Return True if redirect_uri is empty, exact-matches an allowlist
        entry, or path-matches an allowlist entry ending with ``*``.

        The ``*`` suffix is used for OAuth clients (e.g. Raycast) that append
        a varying query string or sub-path to a fixed redirect URL.

        For wildcard entries, the prefix before ``*`` must either match the
        redirect URI exactly, or be followed by ``?`` or ``/``. This avoids
        over-broad matches such as allowing ``.../redirect-malicious`` for an
        entry of ``.../redirect*``.
        """
        if not redirect_uri:
            return True
        for allowed in app.redirect_uris:
            if allowed.endswith("*"):
                prefix = allowed[:-1]
                if (
                    redirect_uri == prefix
                    or redirect_uri.startswith(prefix + "?")
                    or redirect_uri.startswith(prefix + "/")
                ):
                    return True
            elif redirect_uri == allowed:
                return True
        return False
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread services/auth/device.py

Copilot AI left a comment

Copy link
Copy Markdown

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 enhances the device-auth (extension/desktop) OAuth-like flow by tightening redirect URI validation and updating the Raycast app registry entry to support Raycast’s redirect behavior.

Changes:

  • Extend redirect URI validation to allow exact matches and explicit *-suffix prefix matches.
  • Update device-auth redirect building to use the new allowlist matching rules.
  • Update the Raycast app registry entry (description + redirect URI allowlist + links/permissions).

Reviewed changes

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

File Description
services/auth/device.py Implements *-suffix prefix matching in validate_redirect_uri.
routes/auth/device.py Adds route-layer allowlist matcher for building callback redirects.
config/apps.yaml Updates Raycast app metadata and adds a wildcard redirect URI entry.

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

Comment thread config/apps.yaml Outdated
Comment thread services/auth/device.py
Comment thread services/auth/device.py
Comment thread routes/auth/device.py Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@config/apps.yaml`:
- Around line 85-86: Update the redirect_uris entry so it only allows the fixed
/redirect path with an optional query string instead of the broad prefix; in
config/apps.yaml replace the value under redirect_uris (currently
"https://raycast.com/redirect*") with a pattern that anchors the wildcard at the
query boundary such as "https://raycast.com/redirect" and
"https://raycast.com/redirect?*" (or a single pattern
"https://raycast.com/redirect?*") to prevent matching sibling paths; ensure you
modify the redirect_uris array entry for the Raycast app accordingly.

In `@routes/auth/device.py`:
- Around line 69-94: There are two duplicated redirect-URI allowlist
implementations: the route-level _redirect_uri_allowed and
DeviceAuthService.validate_redirect_uri; remove the route-local helper and
delegate to the service to avoid divergence. Update _build_callback_redirect to
call DeviceAuthService.validate_redirect_uri (inject or import the service
instance) and handle empty redirect behavior in the route layer (either by
passing an allow_empty flag to the service or by checking bool(redirect_uri)
before calling the service), then delete _redirect_uri_allowed so only the
service implements the matching logic.

In `@services/auth/device.py`:
- Around line 69-85: The validate_redirect_uri method currently accepts
degenerate wildcards and unanchored prefixes; update validate_redirect_uri to
reject a bare "*" (i.e., require prefix := allowed[:-1] to be non-empty),
require the prefix to start with "http://" or "https://", and when matching a
wildcard ensure redirect_uri.startswith(prefix) AND that the character
immediately following the prefix in redirect_uri (if any) is one of '/', '?',
'#' or end-of-string (to anchor on a path/query/fragment boundary and prevent
matches like "/redirector"); also harmonize this logic with the near-duplicate
_redirect_uri_allowed in routes/auth/device.py so both handle empty redirect_uri
the same way, and add unit tests for validate_redirect_uri covering exact
matches, valid wildcard matches, bare "*" rejection, and unanchored-prefix
rejection instead of relying only on the existing integration mock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27bac235-3640-480b-ba14-cf169d23084d

📥 Commits

Reviewing files that changed from the base of the PR and between 0b16002 and 8fbb002.

📒 Files selected for processing (3)
  • config/apps.yaml
  • routes/auth/device.py
  • services/auth/device.py

Comment thread config/apps.yaml Outdated
Comment thread routes/auth/device.py Outdated
Comment thread services/auth/device.py
Zingzy added 2 commits April 26, 2026 04:02
- Changed the status of the Raycast app in the configuration file to reflect its current availability, enhancing clarity for users and developers.
- Updated the redirect URI format in the configuration for the Raycast app to support query parameters.
- Refactored the redirect URI validation logic to utilize a service method for improved security and maintainability.
- Added comprehensive unit tests for the new validation logic, including support for wildcard matching and various edge cases.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/unit/services/test_redirect_uri.py (1)

16-17: Optional: instantiate via the public constructor (or a fixture) instead of __new__.

DeviceAuthService.__new__(DeviceAuthService) works only because validate_redirect_uri happens to be a pure function of its arguments today. The moment anyone touches self.<dep> inside it, every test here turns into an AttributeError that's unrelated to what's being tested. A small fixture that constructs the service with stub dependencies (or — if the method really is stateless — making it a @staticmethod) would be more robust.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/services/test_redirect_uri.py` around lines 16 - 17, The test uses
DeviceAuthService.__new__(DeviceAuthService) in setup_method which bypasses
initialization and risks AttributeError if validate_redirect_uri later touches
instance dependencies; replace this by constructing the service via its public
constructor (DeviceAuthService(...)) or a pytest fixture that supplies stub/mock
dependencies so the instance is fully initialized before calling
validate_redirect_uri, or alternatively make validate_redirect_uri a
`@staticmethod` on DeviceAuthService if it truly has no reliance on instance
state; update setup_method/tests to use the chosen approach instead of __new__.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/services/test_redirect_uri.py`:
- Around line 72-77: The test in test_bare_star_matches_everything locks in a
dangerous behavior: a bare "*" in an app allowlist lets any redirect pass;
instead modify DeviceAuthService.validate_redirect_uri to explicitly reject bare
"*" or any allowlist entry that collapses to an empty prefix after stripping a
trailing '*' (i.e., treat entries equal to "*" or where a[:-1].strip() == "" as
invalid), return False (or raise a validation error) for those entries, and
update the test to assert that such entries are rejected; alternatively, enforce
that any wildcard entry must include a scheme+host (ensure the substring before
the trailing '*' contains "://" and a "/") when checking in
validate_redirect_uri so misconfigured bare wildcards fail at service layer.

---

Nitpick comments:
In `@tests/unit/services/test_redirect_uri.py`:
- Around line 16-17: The test uses DeviceAuthService.__new__(DeviceAuthService)
in setup_method which bypasses initialization and risks AttributeError if
validate_redirect_uri later touches instance dependencies; replace this by
constructing the service via its public constructor (DeviceAuthService(...)) or
a pytest fixture that supplies stub/mock dependencies so the instance is fully
initialized before calling validate_redirect_uri, or alternatively make
validate_redirect_uri a `@staticmethod` on DeviceAuthService if it truly has no
reliance on instance state; update setup_method/tests to use the chosen approach
instead of __new__.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 155d09a8-27d9-44d2-899c-3417158123a7

📥 Commits

Reviewing files that changed from the base of the PR and between 8fbb002 and 0e73272.

📒 Files selected for processing (4)
  • config/apps.yaml
  • routes/auth/device.py
  • tests/integration/test_device_auth.py
  • tests/unit/services/test_redirect_uri.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • routes/auth/device.py

Comment on lines +72 to +77
def test_bare_star_matches_everything(self):
"""A bare '*' entry matches any URI — intentional if configured."""
app = _app(["*"])
assert (
self.svc.validate_redirect_uri("https://anything.com/whatever", app) is True
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Bare * allowlist is a footgun — consider rejecting it explicitly.

Documenting this behavior in a test locks in a configuration mode where a single typo (- "*") in apps.yaml silently disables redirect URI validation for an app, allowing an attacker to receive a freshly minted device auth code on any host. There's no legitimate need for a bare * entry today (every real app has a fixed callback or a host-anchored prefix), so it's safer to treat it as a misconfiguration.

Suggest either:

  • Rejecting bare * (and any allowlist entry whose prefix collapses to "" after stripping the trailing *) inside DeviceAuthService.validate_redirect_uri and/or at app-registry load time, and replacing this test with one that asserts the rejection; or
  • At minimum, also requiring a scheme+host before the * (e.g. enforce that a[:-1] contains :// and a /).

Tracking this at the service layer is preferable to relying on YAML hygiene.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/services/test_redirect_uri.py` around lines 72 - 77, The test in
test_bare_star_matches_everything locks in a dangerous behavior: a bare "*" in
an app allowlist lets any redirect pass; instead modify
DeviceAuthService.validate_redirect_uri to explicitly reject bare "*" or any
allowlist entry that collapses to an empty prefix after stripping a trailing '*'
(i.e., treat entries equal to "*" or where a[:-1].strip() == "" as invalid),
return False (or raise a validation error) for those entries, and update the
test to assert that such entries are rejected; alternatively, enforce that any
wildcard entry must include a scheme+host (ensure the substring before the
trailing '*' contains "://" and a "/") when checking in validate_redirect_uri so
misconfigured bare wildcards fail at service layer.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: 🏗️ In Progress

Development

Successfully merging this pull request may close these issues.

2 participants