Skip to content

feat: Allow paid guest users to access API endpoints#112

Merged
AnthonyRonning merged 2 commits intomasterfrom
feat/allow-paid-guest-users
Oct 30, 2025
Merged

feat: Allow paid guest users to access API endpoints#112
AnthonyRonning merged 2 commits intomasterfrom
feat/allow-paid-guest-users

Conversation

@AnthonyRonning
Copy link
Copy Markdown
Contributor

@AnthonyRonning AnthonyRonning commented Oct 28, 2025

Summary

This PR enables paid guest users to access API endpoints while continuing to block free guest users.

Changes

  • Added is_user_paid() method to BillingClient to check if a user is on a paid plan
  • Updated the following endpoints to allow paid guests:
    • Chat completions (proxy_openai)
    • Audio transcription (proxy_transcription)
    • Text-to-speech (proxy_tts)
    • Document upload (upload_document)
    • Document status check (check_document_status)
    • Responses API (validate_and_normalize_input)
  • Removed billing check from models endpoint (proxy_models) - now accessible to all guests
  • Maintained backward compatibility: if billing client is unavailable, all guests are blocked

Behavior

  • Paid guests (is_free = false): Allowed to use all API features
  • Free guests (is_free = true): Blocked with ApiError::Unauthorized
  • Guests without billing client: Blocked (safe default)
  • Billing check failures: Blocked (fail-safe)

Testing

  • cargo fmt passed
  • cargo clippy passed with no warnings

Summary by CodeRabbit

  • New Features

    • Guests on paid plans can access document upload, chat, transcription, and TTS where previously denied.
  • Improvements

    • Centralized billing verification with clearer logging and error handling for guest checks.
    • Added a public helper to determine paid-user status.
  • Chores

    • Appended new PCR history snapshots to development and production records.

- Add is_user_paid() method to BillingClient to check if user is on paid plan
- Update chat, transcription, TTS, documents, and Responses API to allow paid guests
- Keep models endpoint accessible to all guests (no billing check needed)
- Block free guest users from accessing paid features
- Maintain backward compatibility: billing client unavailable = guests blocked

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Oct 28, 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

The PR adds a billing-aware guest access gate across multiple web endpoints and a new BillingClient helper. Guests are now allowed only if the billing service reports they are on a paid plan. A new is_user_paid() method is added to BillingClient, and PCR history JSON files receive appended entries.

Changes

Cohort / File(s) Summary
Billing Client Enhancement
src/billing.rs
Added pub async fn is_user_paid(&self, user_id: Uuid) -> Result<bool, BillingError> which calls check_usage(user_id, false) and returns !usage.is_free.
Billing-Gated Document Access
src/web/documents.rs
Replaced unconditional guest denial in upload_document and check_document_status with a billing check: if billing client exists and is_user_paid is true, proceed; otherwise return Unauthorized. Added logging for paid/free outcomes and billing errors.
Billing-Gated OpenAI Proxy & Media Endpoints
src/web/openai.rs
Replaced hard guest denials across chat, models, transcription, and TTS with billing-aware checks (paid guests allowed, free guests denied, missing/errored billing client treated as Unauthorized). Adjusted models endpoint signature to avoid direct User usage. Preserved downstream streaming vs non-streaming behavior.
Billing-Gated Response Handlers
src/web/responses/handlers.rs
Modified guest access validation to consult billing client when present; allow paid guests, deny free guests or on billing errors, and log outcomes.
PCR History Updates
pcrDevHistory.json, pcrProdHistory.json
Appended a new PCR snapshot object (PCR0/PCR1/PCR2, timestamp, signature) to each history file, extending the recorded sequence.

Sequence Diagram

sequenceDiagram
    actor Guest as Guest User
    participant Endpoint
    participant BillingClient
    participant App as Application

    Guest->>Endpoint: Request (upload/chat/models/transcribe/tts)
    Endpoint->>Endpoint: Detect user is guest
    alt Billing client present
        Endpoint->>BillingClient: is_user_paid(user_id)
        BillingClient-->>Endpoint: Ok(true)
        rect rgba(76, 175, 80, 0.12)
            note right of Endpoint: Paid guest — allow
            Endpoint->>App: Continue processing
            App-->>Guest: Return result
        end
        BillingClient-->>Endpoint: Ok(false) or Err(...)
        rect rgba(244, 67, 54, 0.12)
            note right of Endpoint: Free guest or error — deny
            Endpoint-->>Guest: Unauthorized
        end
    else No billing client
        rect rgba(244, 67, 54, 0.12)
            note right of Endpoint: Safe default — deny
            Endpoint-->>Guest: Unauthorized
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Focus review on:
    • Correctness of is_user_paid() (negation of usage.is_free, error propagation).
    • Consistent application of billing gate across endpoints (documents, openai, responses handlers).
    • Error paths where billing client is missing or returns errors (must consistently return Unauthorized).
    • Models endpoint signature change and any downstream usages.
    • PCR JSON append correctness (format, timestamp, signature).

Possibly related PRs

  • Start on documents feature #43 — modifies src/web/documents.rs; likely related to the document upload/status flow changes here.
  • No chat guest mode #10 — introduced the prior unconditional guest denial in OpenAI proxy; this PR replaces that behavior with billing checks.
  • Tinfoil doc async #45 — alters document upload/status handlers to an async task/status model; intersects with billing gating added to those handlers.

Poem

🐰 I hopped to the billing gate, tail a-flutter, not too late,
A check for paid paws opened the way, otherwise "no play".
I twitched my whiskers, stamped my fee—now guests who pay may roam free! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "feat: Allow paid guest users to access API endpoints" directly and clearly summarizes the main objective of the changeset. The changes across src/billing.rs, src/web/documents.rs, src/web/openai.rs, and src/web/responses/handlers.rs all work together to implement exactly what the title describes: a billing-aware access control system that permits paid guest users to use API endpoints while blocking free guests. The title is concise, specific enough to convey meaningful information about the primary change, and avoids vague or generic terminology. A developer scanning PR history would immediately understand that this change introduces payment-based access restrictions for guest users.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/allow-paid-guest-users

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6d6005 and 4a2ead5.

⛔ Files ignored due to path filters (2)
  • pcrDev.json is excluded by !pcrDev.json
  • pcrProd.json is excluded by !pcrProd.json
📒 Files selected for processing (2)
  • pcrDevHistory.json (1 hunks)
  • pcrProdHistory.json (1 hunks)
⏰ Context from checks skipped due to timeout of 100000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Test Suite
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (2)
pcrProdHistory.json (1)

387-393: No concerns with the new PCR production history entry.

The appended record maintains consistent structure with existing entries (PCR0/1/2 hashes, timestamp, signature) and valid JSON formatting.

pcrDevHistory.json (1)

387-393: No concerns with the new PCR development history entry.

The appended record is consistent with existing entries and properly formatted. The timestamp (1761687326) precedes the production entry by ~19 seconds, which is an expected sequence.


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

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR adds differentiated access control for guest users based on their billing status, allowing premium guests to use API features while maintaining blocks on free guests.

Key changes:

  • Added BillingClient::is_user_paid() method to check subscription tier
  • Updated 5 endpoints to check billing status for guest users before blocking
  • Removed guest restrictions entirely from models endpoint
  • Maintains fail-safe behavior: blocks all guests if billing service unavailable

Minor inefficiency:

  • Paid guests trigger duplicate billing API calls (is_user_paid + can_user_chat), both calling check_usage() with same parameters

Confidence Score: 4/5

  • Safe to merge with minor performance inefficiency
  • The implementation correctly handles all edge cases (no billing client, billing errors, free vs paid tiers) with proper fail-safe defaults. The duplicate API calls for paid guests are inefficient but not incorrect - both checks serve different purposes (is_free vs can_use). Code follows existing patterns consistently across all endpoints.
  • Pay attention to src/web/openai.rs and src/web/documents.rs - these have duplicate billing checks that could be optimized

Important Files Changed

File Analysis

Filename Score Overview
src/web/openai.rs 4/5 Updated 3 endpoints (proxy_openai, proxy_transcription, proxy_tts) to allow paid guests, removed guest block from proxy_models. Duplicate billing API calls for paid guests.
src/web/documents.rs 4/5 Updated upload_document and check_document_status to allow paid guests. Duplicate billing API calls for paid guests.

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant E as Endpoint
    participant B as BillingClient
    participant P as Provider

    C->>E: Request
    
    alt is_guest
        E->>B: is_user_paid
        alt paid
            Note over E: Allow
        else free
            E-->>C: Unauthorized
        end
    end
    
    E->>B: can_user_chat
    alt can_use
        E->>P: Forward
        P-->>E: Result
        E-->>C: Response
    else limit reached
        E-->>C: Error
    end
Loading

4 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment thread src/web/openai.rs
Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (6)
src/web/responses/handlers.rs (1)

855-880: Paid-guest gate logic LGTM; consider DRY + log level tweak

  • Logic matches spec: allow paid guests; block free/missing/failed checks.
  • This pattern is duplicated across modules; extract a small helper (e.g., enforce_paid_guest(state, user, feature)) to avoid drift.
  • Suggest downgrading “free guest attempted …” from error to warn to reduce noise.

Example helper (outside this hunk):

pub async fn enforce_paid_guest(state: &AppState, user: &User, feature: &str) -> Result<(), ApiError> {
    if !user.is_guest() { return Ok(()); }
    let Some(billing) = &state.billing_client else {
        tracing::error!("Guest user attempted to use {} without billing client: {}", feature, user.uuid);
        return Err(ApiError::Unauthorized);
    };
    match billing.is_user_paid(user.uuid).await {
        Ok(true) => {
            tracing::debug!("Paid guest user allowed for {}: {}", feature, user.uuid);
            Ok(())
        }
        Ok(false) => {
            tracing::warn!("Free guest user attempted to use {}: {}", feature, user.uuid);
            Err(ApiError::Unauthorized)
        }
        Err(e) => {
            tracing::error!("Billing check failed for guest {} on {}: {}", user.uuid, feature, e);
            Err(ApiError::Unauthorized)
        }
    }
}
src/web/documents.rs (2)

76-101: Guest gate matches policy; centralize to helper and tone logs

  • Behavior correct; duplicates across endpoints.
  • Recommend using a shared enforce_paid_guest(...) helper.
  • Consider warn for free-guest attempts instead of error.

240-268: Same guest gate duplication; apply helper for consistency

Replicate the helper here to reduce copy/paste and ensure uniform logging/behavior.

src/web/openai.rs (3)

216-241: Chat guest gate is correct; prefer shared helper + warn for free attempts

  • Logic aligns with PR.
  • Suggest reuse via enforce_paid_guest(state, user, "chat") to avoid drift.
  • Consider warn (not error) for free-guest attempts.

1032-1057: Transcription guest gate consistent; extract helper

Same recommendation: centralize guest gating and adjust log severity to warn for expected denials.


1403-1428: TTS guest gate consistent; extract helper

Apply the shared helper for maintainability; consider warn for free-guest denials.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a564745 and c6d6005.

📒 Files selected for processing (4)
  • src/billing.rs (1 hunks)
  • src/web/documents.rs (2 hunks)
  • src/web/openai.rs (4 hunks)
  • src/web/responses/handlers.rs (1 hunks)
⏰ Context from checks skipped due to timeout of 100000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Development Reproducible Build
🔇 Additional comments (1)
src/web/openai.rs (1)

915-915: Intentional unused user binding

Renaming to _user avoids unused binding warnings after removing inline guest checks. LGTM.

Comment thread src/billing.rs
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

Greptile Overview

Greptile Summary

This PR enables paid guest users to access API endpoints while blocking free guests. Implementation adds is_user_paid() method to check billing status and updates 6 endpoints.

Key Changes

  • Added BillingClient.is_user_paid() helper that queries billing service
  • Updated chat, transcription, TTS, document upload/status, and Responses API endpoints
  • Removed guest restriction from models endpoint (now accessible to all)
  • PCR hashes updated for dev and prod environments

Issues Found

  • Duplicate billing calls: Chat and document upload endpoints call billing service twice for paid guests (is_user_paid() then can_user_chat()), causing unnecessary HTTP requests
  • Inconsistent billing checks: Transcription, TTS, and document status endpoints skip usage limit validation entirely - they only check if guest is paid but don't verify usage quotas like chat/upload do
  • Non-guest bypass: Transcription, TTS, and document status endpoints allow non-guest users to bypass all billing checks

Confidence Score: 2/5

  • This PR has critical logical inconsistencies in billing validation that could allow usage limit bypasses
  • The implementation correctly adds paid guest support but has critical flaws: (1) duplicate billing API calls waste resources, (2) transcription/TTS/document status endpoints completely skip usage limit checks that chat/upload have, creating billing enforcement gaps, and (3) inconsistent validation patterns across endpoints make the system unpredictable
  • src/web/openai.rs (transcription and TTS functions) and src/web/documents.rs (check_document_status function) need usage limit validation added

Important Files Changed

File Analysis

Filename Score Overview
src/billing.rs 5/5 Added is_user_paid() helper method to check if user is on paid plan - clean implementation
src/web/openai.rs 3/5 Updated chat, transcription, TTS endpoints for paid guest access; duplicate billing calls in chat endpoint; transcription/TTS missing usage checks
src/web/documents.rs 3/5 Updated document upload and status check for paid guests; duplicate billing calls in upload endpoint
src/web/responses/handlers.rs 5/5 Updated Responses API for paid guest access - correctly implemented without duplicate billing calls

Sequence Diagram

sequenceDiagram
    participant C as Client
    participant E as API Endpoint
    participant A as Auth
    participant B as BillingClient
    participant P as Proxy

    C->>E: Request to API
    E->>A: Authenticate
    A-->>E: User information
    
    alt Guest User Flow
        E->>B: Check paid plan status
        B->>B: Query billing service
        B-->>E: Return paid status
        
        alt Free Guest
            E-->>C: 401 Unauthorized
        else Paid Guest
            E->>E: Continue processing
        end
    end
    
    alt Chat or Document Upload
        E->>B: Check usage limits
        B->>B: Query billing service again
        B-->>E: Return usage status
        
        alt Usage Exceeded
            E-->>C: 429 UsageLimitReached
        end
    end
    
    E->>P: Forward request
    P-->>E: Response
    E-->>C: Encrypted response
Loading

4 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment thread src/web/openai.rs
Comment thread src/web/openai.rs
Comment thread src/web/documents.rs
@AnthonyRonning AnthonyRonning merged commit 48b8d89 into master Oct 30, 2025
10 checks passed
@AnthonyRonning AnthonyRonning deleted the feat/allow-paid-guest-users branch October 30, 2025 23:58
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.

1 participant