Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

chore: enforce minimum coder server version of v2.20.0#90

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ethanndickson merged 6 commits intomainfromethan/require-coder-v2.20
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
26 changes: 26 additions & 0 deletionsCoder Desktop/Coder Desktop/Views/LoginForm.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
import CoderSDK
import SwiftUI
import VPNLib

struct LoginForm: View {
@EnvironmentObject var state: AppState
Expand DownExpand Up@@ -78,6 +79,22 @@ struct LoginForm: View {
loginError = .failedAuth(error)
return
}
let buildInfo: BuildInfoResponse
do {
buildInfo = try await client.buildInfo()
} catch {
loginError = .failedAuth(error)
return
}
guard let semver = buildInfo.semver else {
loginError = .missingServerVersion
return
}
// x.compare(y) is .orderedDescending if x > y
guard SignatureValidator.minimumCoderVersion.compare(semver, options: .numeric) != .orderedDescending else {
loginError = .outdatedCoderVersion
return
}
state.login(baseAccessURL: url, sessionToken: sessionToken)
dismiss()
}
Expand DownExpand Up@@ -190,6 +207,8 @@ enum LoginError: Error {
case httpsRequired
case noHost
case invalidURL
case outdatedCoderVersion
case missingServerVersion
case failedAuth(ClientError)

var description: String {
Expand All@@ -200,8 +219,15 @@ enum LoginError: Error {
"URL must have a host"
case .invalidURL:
"Invalid URL"
case .outdatedCoderVersion:
"""
The Coder deployment must be version \(SignatureValidator.minimumCoderVersion)
or higher to use Coder Desktop.
"""
case let .failedAuth(err):
"Could not authenticate with Coder deployment:\n\(err.localizedDescription)"
case .missingServerVersion:
"Coder deployment did not provide a server version"
}
}

Expand Down
41 changes: 41 additions & 0 deletionsCoder Desktop/Coder DesktopTests/LoginFormTests.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,6 +73,14 @@ struct LoginTests {
@Test
func testFailedAuthentication() async throws {
let url = URL(string: "https://testFailedAuthentication.com")!
let buildInfo = BuildInfoResponse(
version: "v2.20.0"
)
try Mock(
url: url.appendingPathComponent("/api/v2/buildinfo"),
statusCode: 200,
data: [.get: Client.encoder.encode(buildInfo)]
).register()
Mock(url: url.appendingPathComponent("/api/v2/users/me"), statusCode: 401, data: [.get: Data()]).register()

try await ViewHosting.host(view) {
Expand All@@ -87,6 +95,30 @@ struct LoginTests {
}
}

@Test
func testOutdatedServer() async throws {
let url = URL(string: "https://testOutdatedServer.com")!
let buildInfo = BuildInfoResponse(
version: "v2.19.0"
)
try Mock(
url: url.appendingPathComponent("/api/v2/buildinfo"),
statusCode: 200,
data: [.get: Client.encoder.encode(buildInfo)]
).register()

try await ViewHosting.host(view) {
try await sut.inspection.inspect { view in
try view.find(ViewType.TextField.self).setInput(url.absoluteString)
try view.find(button: "Next").tap()
#expect(throws: Never.self) { try view.find(text: "Session Token") }
try view.find(ViewType.SecureField.self).setInput("valid-token")
try await view.actualView().submit()
#expect(throws: Never.self) { try view.find(ViewType.Alert.self) }
}
}
}

@Test
func testSuccessfulLogin() async throws {
let url = URL(string: "https://testSuccessfulLogin.com")!
Expand All@@ -95,13 +127,22 @@ struct LoginTests {
id: UUID(),
username: "admin"
)
let buildInfo = BuildInfoResponse(
version: "v2.20.0"
)

try Mock(
url: url.appendingPathComponent("/api/v2/users/me"),
statusCode: 200,
data: [.get: Client.encoder.encode(user)]
).register()

try Mock(
url: url.appendingPathComponent("/api/v2/buildinfo"),
statusCode: 200,
data: [.get: Client.encoder.encode(buildInfo)]
).register()

try await ViewHosting.host(view) {
try await sut.inspection.inspect { view in
try view.find(ViewType.TextField.self).setInput(url.absoluteString)
Expand Down
4 changes: 2 additions & 2 deletionsCoder Desktop/VPN/Manager.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -31,9 +31,9 @@ actor Manager {
// 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
// the network is up so this is deliberately short (30s) to avoid a
// poor UX where it appears stuck.
sessionConfig.timeoutIntervalForResource =15
sessionConfig.timeoutIntervalForResource =30
try await download(src: dylibPath, dest: dest, urlSession: URLSession(configuration: sessionConfig))
} catch {
throw .download(error)
Expand Down
24 changes: 23 additions & 1 deletionCoder Desktop/VPNLib/Download.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -10,6 +10,7 @@ public enum ValidationError: Error {
case invalidTeamIdentifier(identifier: String?)
case missingInfoPList
case invalidVersion(version: String?)
case belowMinimumCoderVersion

public var description: String {
switch self {
Expand All@@ -29,13 +30,21 @@ public enum ValidationError: Error {
"Invalid team identifier: \(identifier ?? "unknown")."
case .missingInfoPList:
"Info.plist is not embedded within the dylib."
case .belowMinimumCoderVersion:
"""
The Coder deployment must be version \(SignatureValidator.minimumCoderVersion)
or higher to use Coder Desktop.
"""
}
}

public var localizedDescription: String { description }
}

public class SignatureValidator {
// Whilst older dylibs exist, this app assumes v2.20 or later.
public static let minimumCoderVersion = "2.20.0"

private static let expectedName = "CoderVPN"
private static let expectedIdentifier = "com.coder.Coder-Desktop.VPN.dylib"
private static let expectedTeamIdentifier = "4399GN35BJ"
Expand DownExpand Up@@ -87,6 +96,10 @@ public class SignatureValidator {
throw .missingInfoPList
}

try validateInfo(infoPlist: infoPlist, expectedVersion: expectedVersion)
}

private static func validateInfo(infoPlist: [String: AnyObject], expectedVersion: String) throws(ValidationError) {
guard let plistIdent = infoPlist[infoIdentifierKey] as? String, plistIdent == expectedIdentifier else {
throw .invalidIdentifier(identifier: infoPlist[infoIdentifierKey] as? String)
}
Expand All@@ -95,11 +108,20 @@ public class SignatureValidator {
throw .invalidIdentifier(identifier: infoPlist[infoNameKey] as? String)
}

// Downloaded dylib must match the version of the server
guard let dylibVersion = infoPlist[infoShortVersionKey] as? String,
expectedVersion.compare(dylibVersion, options: .numeric) != .orderedDescending
expectedVersion == dylibVersion
else {
throw .invalidVersion(version: infoPlist[infoShortVersionKey] as? String)
}

// Downloaded dylib must be at least the minimum Coder server version
guard let dylibVersion = infoPlist[infoShortVersionKey] as? String,
// x.compare(y) is .orderedDescending if x > y
minimumCoderVersion.compare(dylibVersion, options: .numeric) != .orderedDescending
else {
throw .belowMinimumCoderVersion
}
}
}

Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp