Skip to content

auth: implement backend-proxy support for GitHub device flow#441

Merged
rainxchzed merged 2 commits into
mainfrom
login-backend-migration
Apr 21, 2026
Merged

auth: implement backend-proxy support for GitHub device flow#441
rainxchzed merged 2 commits into
mainfrom
login-backend-migration

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented Apr 21, 2026

  • Introduce AuthPath (Backend or Direct) to support authentication through a proxy for users on restricted networks.
  • Add BACKEND_BASE_URL and BACKEND_ORIGIN constants to centralize backend endpoint configuration.
  • Implement backend-first authentication in AuthenticationRepositoryImpl, falling back to the Direct path only on infrastructure errors (timeouts or 5xx responses).
  • Update AuthenticationViewModel to persist and restore the active AuthPath via SavedStateHandle to ensure session continuity.
  • Enhance GitHubAuthApi with backend-specific start and poll methods, including support for X-Request-ID headers for better error tracking.
  • Refactor polling logic to allow dynamic escalation from Backend to Direct path during an active session if infrastructure failures occur.

Summary by CodeRabbit

  • New Features
    • Device-flow auth now uses a backend proxy by default for improved reliability and observability.
    • Per-session authentication path (Backend vs Direct) is persisted for consistent UX.
    • Automatic fallback to direct auth only for infrastructure/5xx-type failures; authorization errors do not trigger retries.
    • Enhanced diagnostics with propagated request IDs for better troubleshooting.
    • Backend device-flow rate limits enforced (starts/polls).

- Introduce `AuthPath` (Backend or Direct) to support authentication through a proxy for users on restricted networks.
- Add `BACKEND_BASE_URL` and `BACKEND_ORIGIN` constants to centralize backend endpoint configuration.
- Implement backend-first authentication in `AuthenticationRepositoryImpl`, falling back to the `Direct` path only on infrastructure errors (timeouts or 5xx responses).
- Update `AuthenticationViewModel` to persist and restore the active `AuthPath` via `SavedStateHandle` to ensure session continuity.
- Enhance `GitHubAuthApi` with backend-specific `start` and `poll` methods, including support for `X-Request-ID` headers for better error tracking.
- Refactor polling logic to allow dynamic escalation from `Backend` to `Direct` path during an active session if infrastructure failures occur.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 21, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a4b499fb-3650-47b2-9813-d4756f6a1b3a

📥 Commits

Reviewing files that changed from the base of the PR and between 37a1cac and fe73fb0.

📒 Files selected for processing (1)
  • feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/repository/AuthenticationRepositoryImpl.kt

Walkthrough

Device-flow auth now prefers a backend proxy path by default, persists per-session path selection, and implements controlled fallback to direct GitHub calls only for infrastructure/5xx failures; polling and start endpoints can be invoked via backend or direct APIs with request-id propagation and explicit rate-limit guidance.

Changes

Cohort / File(s) Summary
Backend Endpoints & Client
core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendEndpoints.kt, core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt
Added BACKEND_ORIGIN/BACKEND_BASE_URL constants and updated BackendApiClient to use BACKEND_BASE_URL instead of a hardcoded URL.
GitHub Auth API Layer
feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/network/GitHubAuthApi.kt
Renamed direct device-flow methods to *Direct, added BackendHttpException, added startDeviceFlowViaBackend and pollDeviceTokenViaBackend with X-Request-ID propagation, success-status helper, and richer backend-error decoding/handling.
Authentication Repository (impl)
feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/repository/AuthenticationRepositoryImpl.kt
Start now uses backend-first startDeviceFlowViaBackend(BACKEND_ORIGIN) with fallback to direct only for infrastructure/5xx errors; polling is path-aware, can escalate per-call from Backend→Direct on infra errors, and includes interpretPollResult() and isAuthInfrastructureError() logic.
Authentication Domain Types
feature/auth/domain/src/commonMain/kotlin/zed/rainxch/auth/domain/repository/AuthenticationRepository.kt
Updated repository API: startDeviceFlow()DeviceFlowStart (includes AuthPath), pollDeviceTokenOnce(deviceCode, path)PollOutcome (includes AuthPath); added AuthPath, DeviceFlowStart, and PollOutcome.
Authentication ViewModel
feature/auth/presentation/src/commonMain/kotlin/zed/rainxch/auth/presentation/AuthenticationViewModel.kt
Persisted authPath in SavedStateHandle (default Backend), capture path returned from start, pass path into polling, and update/persist path when per-call escalation occurs.
Documentation
CLAUDE.md
Added "Device-flow auth proxy" section documenting routing, escalation rules, client/backend client-id consistency, request-id logging, and backend rate limits.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant VM as AuthenticationViewModel
    participant Repo as AuthenticationRepository
    participant BackendAPI as GitHubAuthApi (Backend)
    participant DirectAPI as GitHubAuthApi (Direct)
    participant GitHub

    User->>VM: startLogin()
    VM->>Repo: startDeviceFlow()
    Repo->>BackendAPI: startDeviceFlowViaBackend()
    alt Backend infra (timeout/5xx)
        BackendAPI-->>Repo: infra error
        Repo->>DirectAPI: startDeviceFlowDirect()
        DirectAPI->>GitHub: POST /device
        GitHub-->>DirectAPI: Device start response
        DirectAPI-->>Repo: GithubDeviceStart
        Repo-->>VM: DeviceFlowStart(path: Direct)
    else Backend success
        BackendAPI->>GitHub: POST /device (proxy)
        GitHub-->>BackendAPI: Device start response
        BackendAPI-->>Repo: GithubDeviceStart
        Repo-->>VM: DeviceFlowStart(path: Backend)
    end

    VM->>VM: begin polling with authPath
    loop Polling
        VM->>Repo: pollDeviceTokenOnce(deviceCode, authPath)
        alt authPath == Backend
            Repo->>BackendAPI: pollDeviceTokenViaBackend()
            alt Backend infra (timeout/5xx)
                BackendAPI-->>Repo: infra error
                Repo->>DirectAPI: pollDeviceTokenDirect()
                DirectAPI->>GitHub: POST /token
                GitHub-->>DirectAPI: Token / pending
                DirectAPI-->>Repo: Result
                Repo-->>VM: PollOutcome(result, path: Direct)
            else Backend success/failure
                BackendAPI->>GitHub: POST /token (proxy)
                GitHub-->>BackendAPI: Token / pending
                BackendAPI-->>Repo: Result
                Repo-->>VM: PollOutcome(result, path: Backend)
            end
        else authPath == Direct
            Repo->>DirectAPI: pollDeviceTokenDirect()
            DirectAPI->>GitHub: POST /token
            GitHub-->>DirectAPI: Token / pending
            DirectAPI-->>Repo: Result
            Repo-->>VM: PollOutcome(result, path: Direct)
        end
        alt outcome.path changed
            VM->>VM: update & persist authPath
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 Through proxy tunnels I nudge the flow,

Backend first, then onward we go.
If clouds of timeout block the way,
I hop direct without delay.
Saved paths snug beneath my paw — hop! hop! 🚀

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.35% 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 'auth: implement backend-proxy support for GitHub device flow' clearly and specifically describes the main change in the PR: adding backend-proxy support for device flow authentication.
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 login-backend-migration

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
Copy Markdown
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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/network/GitHubAuthApi.kt (1)

19-19: ⚠️ Potential issue | 🟠 Major

Rethrow coroutine cancellation before wrapping poll failures.

CancellationException is caught by these generic catch (e: Exception) blocks, converting cancelled polling into a normal Result.failure instead of properly propagating cancellation, which breaks coroutine cancellation semantics.

🐛 Proposed fix
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.delay
-        } catch (e: Exception) {
+        } catch (e: CancellationException) {
+            throw e
+        } catch (e: Exception) {
             Result.failure(e)
         }
-        } catch (e: Exception) {
+        } catch (e: CancellationException) {
+            throw e
+        } catch (e: Exception) {
             Result.failure(e)
         }

Applies to lines 152-154 and 253-255 in the poll helper functions.

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

In
`@feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/network/GitHubAuthApi.kt`
at line 19, The generic catch (e: Exception) in the poll helper functions is
swallowing coroutine cancellations; modify the catch blocks in the poll helpers
(the functions named poll... in GitHubAuthApi, e.g., the two poll helper
functions around the reported lines) to rethrow CancellationException before
wrapping errors: check if e is CancellationException and throw it, otherwise
convert to Result.failure/handle as before; ensure
kotlinx.coroutines.CancellationException is imported if not already.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/repository/AuthenticationRepositoryImpl.kt`:
- Around line 329-332: The else branch in AuthenticationRepositoryImpl that
currently logs "Single poll unknown error" and returns DevicePollResult.Pending
must surface non-device-flow failures as DevicePollResult.Failed instead of
hiding them; change the fallback to return DevicePollResult.Failed with the
captured error info (preserve the errorMsg/details) so backend/client errors
stop polling. Also ensure AuthPath selection logic in
AuthenticationRepositoryImpl only escalates from Backend to Direct on
infrastructure/network errors (e.g., timeouts, connection errors) and not on
HTTP 4xx responses or valid GitHub responses—treat 4xx as terminal failures and
propagate them as Failed rather than switching path or continuing with Pending.

---

Outside diff comments:
In
`@feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/network/GitHubAuthApi.kt`:
- Line 19: The generic catch (e: Exception) in the poll helper functions is
swallowing coroutine cancellations; modify the catch blocks in the poll helpers
(the functions named poll... in GitHubAuthApi, e.g., the two poll helper
functions around the reported lines) to rethrow CancellationException before
wrapping errors: check if e is CancellationException and throw it, otherwise
convert to Result.failure/handle as before; ensure
kotlinx.coroutines.CancellationException is imported if not already.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7083f520-31af-410b-a1f6-3d70e46d6824

📥 Commits

Reviewing files that changed from the base of the PR and between b9615ce and 37a1cac.

📒 Files selected for processing (7)
  • CLAUDE.md
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendApiClient.kt
  • core/data/src/commonMain/kotlin/zed/rainxch/core/data/network/BackendEndpoints.kt
  • feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/network/GitHubAuthApi.kt
  • feature/auth/data/src/commonMain/kotlin/zed/rainxch/auth/data/repository/AuthenticationRepositoryImpl.kt
  • feature/auth/domain/src/commonMain/kotlin/zed/rainxch/auth/domain/repository/AuthenticationRepository.kt
  • feature/auth/presentation/src/commonMain/kotlin/zed/rainxch/auth/presentation/AuthenticationViewModel.kt

… Failed

- Change the fallback case in `AuthenticationRepositoryImpl` to return `DevicePollResult.Failed` instead of `DevicePollResult.Pending` when an unrecognized error occurs.
- Update logging to reflect that unknown errors are now surfaced as failures.
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