Skip to content

chore: reconfigure VPN on reinstall #84

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

Merged
merged 1 commit into from
Mar 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions Coder Desktop/Coder Desktop/Coder_DesktopApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
object: nil
)
Task {
// If there's no NE config, then the user needs to sign in.
// However, they might have a session from a previous install, so we
// need to clear it.
// If there's no NE config, but the user is logged in, such as
// from a previous install, then we need to reconfigure.
if await !vpn.loadNetworkExtensionConfig() {
state.clearSession()
state.reconfigure()
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions Coder Desktop/Coder Desktop/MenuBarIconController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ class MenuBarController {

init(menuBarExtra: FluidMenuBarExtra) {
self.menuBarExtra = menuBarExtra
// Off by default, as `vpnDidUpdate` isn't called until the VPN is configured
menuBarExtra.setOpacity(offOpacity)
}

func vpnDidUpdate(_ connection: NETunnelProviderSession) {
Expand Down
14 changes: 9 additions & 5 deletions Coder Desktop/Coder Desktop/State.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ class AppState: ObservableObject {

@Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) {
didSet {
if let onChange { onChange(tunnelProviderProtocol()) }
reconfigure()
guard persistent else { return }
UserDefaults.standard.set(useLiteralHeaders, forKey: Keys.useLiteralHeaders)
}
}

@Published var literalHeaders: [LiteralHeader] {
didSet {
if let onChange { onChange(tunnelProviderProtocol()) }
reconfigure()
guard persistent else { return }
try? UserDefaults.standard.set(JSONEncoder().encode(literalHeaders), forKey: Keys.literalHeaders)
}
Expand Down Expand Up @@ -70,9 +70,13 @@ class AppState: ObservableObject {
private let keychain: Keychain
private let persistent: Bool

// This closure must be called when any property used to configure the VPN changes
let onChange: ((NETunnelProviderProtocol?) -> Void)?

// reconfigure must be called when any property used to configure the VPN changes
public func reconfigure() {
if let onChange { onChange(tunnelProviderProtocol()) }
}

public init(onChange: ((NETunnelProviderProtocol?) -> Void)? = nil,
persistent: Bool = true)
{
Expand All @@ -97,13 +101,13 @@ class AppState: ObservableObject {
hasSession = true
self.baseAccessURL = baseAccessURL
self.sessionToken = sessionToken
if let onChange { onChange(tunnelProviderProtocol()) }
reconfigure()
}

public func clearSession() {
hasSession = false
sessionToken = nil
if let onChange { onChange(tunnelProviderProtocol()) }
reconfigure()
}

private func keychainGet(for key: String) -> String? {
Expand Down
4 changes: 2 additions & 2 deletions Coder Desktop/Coder Desktop/Views/LoginForm.swift
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ struct LoginForm: View {
.animation(.easeInOut, value: currentPage)
.onAppear {
baseAccessURL = state.baseAccessURL?.absoluteString ?? baseAccessURL
sessionToken = ""
Copy link
Member Author

Choose a reason for hiding this comment

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

We clear the Keychain password on Sign out - so I don't think this does anything, actually.

sessionToken = state.sessionToken ?? sessionToken
}
.alert("Error", isPresented: Binding(
get: { loginError != nil },
Expand Down Expand Up @@ -122,7 +122,7 @@ struct LoginForm: View {
).disabled(true)
}
Section {
SecureField("Session Token", text: $sessionToken, prompt: Text("●●●●●●●●"))
Copy link
Member Author

Choose a reason for hiding this comment

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

Also removed the prompt because it was a lil confusing, and also the dots were way too big with no way to change it.

SecureField("Session Token", text: $sessionToken)
.autocorrectionDisabled()
.privacySensitive()
.focused($focusedField, equals: .sessionToken)
Expand Down
4 changes: 4 additions & 0 deletions Coder Desktop/VPN/Manager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ actor Manager {
let sessionConfig = URLSessionConfiguration.default
// The tunnel might be asked to start before the network interfaces have woken up from sleep
sessionConfig.waitsForConnectivity = true
// URLSession's waiting for connectivity sometimes hangs even when
// the network is up so this is deliberately short (15s) to avoid a
// poor UX where it appears stuck.
sessionConfig.timeoutIntervalForResource = 15
try await download(src: dylibPath, dest: dest, urlSession: URLSession(configuration: sessionConfig))
} catch {
throw .download(error)
Expand Down
Loading