Skip to content

Fix on demand & auth pop-up#85

Merged
pappz merged 5 commits into
netbirdio:mainfrom
evgeniyChepelev:fix-on-demand
Apr 21, 2026
Merged

Fix on demand & auth pop-up#85
pappz merged 5 commits into
netbirdio:mainfrom
evgeniyChepelev:fix-on-demand

Conversation

@evgeniyChepelev
Copy link
Copy Markdown
Collaborator

@evgeniyChepelev evgeniyChepelev commented Apr 9, 2026

Summary by CodeRabbit

  • New Features

    • Authentication alert now includes a "Login" button for direct re-authentication
  • Bug Fixes

    • More reliable detection of login-required during background polling
    • Improved VPN state display that preserves user intent during connect/disconnect transitions
    • Prevented overlapping extension status checks to avoid stale UI updates
    • Extension now signals login-required immediately and aborts restart when needed
    • When login is required, On Demand is disabled and a local notification is scheduled
  • Other

    • Silent-login behavior adjusted to keep the VPN extension alive for subsequent user actions

PR Description: This PR fixes an issue with On-Demand (Always On) VPN where users could not re-authenticate after session (peer) expiration.

Problem: When On-Demand was enabled and the session expired:

The app did not show the login screen
User was unable to re-authenticate
Disabling On-Demand immediately resolved the issue (login flow worked as expected)

Steps to Reproduce (Before Fix)

  1. Enable VPN On-Demand (Always On)
  2. Connect VPN
  3. Wait until session expires
    (or install the app fresh and attempt initial login)
  4. Tap the main connect button
    Result:

App attempts to connect
Login screen is not shown
User cannot re-authenticate

What’s Fixed
• Added reliable detection of login-required state during background polling
• Improved VPN state handling to preserve correct UI behavior during transitions
• Prevented overlapping extension status checks (avoids stale UI state)

How to Test (After Fix)

  1. Enable VPN On-Demand
  2. Connect VPN
  3. Wait until session expires
  4. Observe:
    User is prompted with a re-login pop-up
  5. Complete login
    Authentication succeeds
    VPN reconnects correctly

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 9, 2026

📝 Walkthrough

Walkthrough

Adds periodic login-required detection to the iOS polling loop, preserves UI state during VPN connecting/disconnecting transitions, guards against overlapping extension-status requests, updates Network Extension login/start behavior to signal login earlier, and changes the authentication alert to offer "Login" or "Later".

Changes

Cohort / File(s) Summary
VPN State & Login Detection
NetBird/Source/App/ViewModels/MainViewModel.swift, NetBird/Source/App/Views/MainView.swift
Added periodic checkLoginRequiredFlag() in polling; adjusted updateVPNDisplayState() to avoid clearing connectPressed on .connecting and to conditionally suppress disconnect UI on .disconnecting; added isCheckingExtensionState guard to serialize status requests; authentication alert now has "Login" and "Later" actions.
Network Extension Adapter
NetbirdKit/NetworkExtensionAdapter.swift
Persist NETunnelProviderManager prefs only when existing manager is disabled; loginIfRequired() now may call startVPNConnection() when no browser is shown; tightened performLogin() URL handling; changed login(completion:) to (String?) -> Void and handle missing session/extension responses; getExtensionStatus now completes .disconnected when manager is missing or prefs fail to load.
Packet Tunnel Provider
NetbirdNetworkExtension/PacketTunnelProvider.swift
When adapter.needsLogin() is true, startTunnel immediately signals login required and returns an error; restartClient re-checks needsLogin() after stopping, signals login and cancels restart if required, and avoids starting the adapter in that case.

Sequence Diagram(s)

sequenceDiagram
    participant App as MainViewModel
    participant Poll as PollingLoop
    participant Adapter as NetworkExtensionAdapter
    participant Ext as NetworkExtension
    participant UI as MainView

    Poll->>App: startPollingDetails()
    activate Poll
    Note over Poll: periodic cycle

    Poll->>App: checkExtensionState()
    App->>Adapter: getExtensionStatus()
    Adapter->>Ext: query status
    Ext-->>Adapter: status
    Adapter-->>App: status completion
    App->>App: updateVPNDisplayState()

    Poll->>App: checkLoginRequiredFlag()
    App->>Adapter: needsLogin()
    Adapter->>Ext: query needsLogin()
    Ext-->>Adapter: login required
    Adapter-->>App: notify login required

    App->>App: clear connectPressed, update UI
    App->>Adapter: setOnDemandEnabled(false)
    App->>UI: schedule local notification / show alert

    UI->>App: user taps "Login"
    App->>Adapter: loginIfRequired()
    Adapter->>Ext: performLogin()
    Ext-->>Adapter: login result
    Adapter->>Ext: startVPNConnection() (if showBrowser == false)
    deactivate Poll
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • mlsmaycon
  • doromaraujo

Poem

🐰 I hopped through polls and status lines today,
I nudged the flags that hide and show the way,
I guarded races, told the extension "wait",
Now login knocks — choose "Login" or "Later" to relate,
A carrot-coded patch to brighten the relay.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, missing the required description template content entirely. Add a detailed description following the repository template, explaining the changes made and any relevant testing or deployment notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix on demand & auth pop-up' directly relates to the main changes: addressing on-demand VPN behavior and authentication popup flow improvements across multiple files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@evgeniyChepelev evgeniyChepelev changed the title Fix on demand Fix on demand & auth pop-up Apr 9, 2026
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.

🧹 Nitpick comments (1)
NetBird/Source/App/ViewModels/MainViewModel.swift (1)

491-505: Concurrency guard logic is correct, but consider a timeout safeguard.

The guard effectively prevents overlapping getExtensionStatus calls and out-of-order completions. Since the class is @MainActor, the flag access is thread-safe.

One edge case: if getExtensionStatus never invokes its completion handler (e.g., due to a bug or hang in loadAllFromPreferences), isCheckingExtensionState remains true indefinitely, blocking all future status checks. Consider adding a timeout reset:

🛡️ Optional: Add timeout safeguard
 func checkExtensionState() {
     guard !isCheckingExtensionState else { return }
     isCheckingExtensionState = true
+    
+    // Safety timeout in case completion is never called
+    DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
+        self?.isCheckingExtensionState = false
+    }
+    
     networkExtensionAdapter.getExtensionStatus { status in
         DispatchQueue.main.async {
             self.isCheckingExtensionState = false
             self.applyExtensionStatus(status)
         }
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@NetBird/Source/App/ViewModels/MainViewModel.swift` around lines 491 - 505,
Add a timeout safeguard to avoid permanently blocking future checks if
networkExtensionAdapter.getExtensionStatus never calls its completion: in
checkExtensionState (which uses isCheckingExtensionState) start a fallback timer
(e.g. DispatchQueue.main.asyncAfter) when you set isCheckingExtensionState =
true that, after a short interval, resets isCheckingExtensionState = false and
optionally calls applyExtensionStatus(.unknown) or triggers another safe retry;
cancel or ignore the fallback when the real completion runs so only one reset
occurs; reference checkExtensionState, isCheckingExtensionState,
networkExtensionAdapter.getExtensionStatus, and applyExtensionStatus when
implementing this.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@NetBird/Source/App/ViewModels/MainViewModel.swift`:
- Around line 491-505: Add a timeout safeguard to avoid permanently blocking
future checks if networkExtensionAdapter.getExtensionStatus never calls its
completion: in checkExtensionState (which uses isCheckingExtensionState) start a
fallback timer (e.g. DispatchQueue.main.asyncAfter) when you set
isCheckingExtensionState = true that, after a short interval, resets
isCheckingExtensionState = false and optionally calls
applyExtensionStatus(.unknown) or triggers another safe retry; cancel or ignore
the fallback when the real completion runs so only one reset occurs; reference
checkExtensionState, isCheckingExtensionState,
networkExtensionAdapter.getExtensionStatus, and applyExtensionStatus when
implementing this.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0bfa629b-0843-4c15-976a-607112dc19be

📥 Commits

Reviewing files that changed from the base of the PR and between f2878a0 and 6767b82.

📒 Files selected for processing (1)
  • NetBird/Source/App/ViewModels/MainViewModel.swift

@pappz pappz merged commit 52f9e4e into netbirdio:main Apr 21, 2026
4 checks passed
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.

2 participants