mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-09-19 14:48:01 +02:00
Fixing up tests
This commit is contained in:
@@ -8,7 +8,7 @@ public final class AuthenticationContext: AuthenticationContextProtocol {
|
|||||||
/// The Secret to persist authentication for.
|
/// The Secret to persist authentication for.
|
||||||
public let secret: AnySecret
|
public let secret: AnySecret
|
||||||
/// The LAContext used to authorize the persistent context.
|
/// The LAContext used to authorize the persistent context.
|
||||||
public let laContext: LAContext
|
public let laContext: LAContext?
|
||||||
|
|
||||||
enum Validity {
|
enum Validity {
|
||||||
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
||||||
@@ -55,12 +55,21 @@ public final class AuthenticationContext: AuthenticationContextProtocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func evaluate() async throws -> Bool {
|
||||||
|
guard let laContext else { return false }
|
||||||
|
return try await laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: laContext.localizedReason)
|
||||||
|
}
|
||||||
|
|
||||||
|
public func cancel() async {
|
||||||
|
laContext?.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor public protocol AuthenticationHandlerProtocol: Observable {
|
@MainActor public protocol AuthenticationHandlerProtocol: Observable {
|
||||||
|
var batchableRequests: [[SignatureRequest]] { get }
|
||||||
func setBatchAuthHandler(_ handler: @escaping () async throws -> Void)
|
func setBatchAuthHandler(_ handler: @escaping () async throws -> Void)
|
||||||
func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol
|
func waitForAuthentication(for request: SignatureRequest) async throws -> any AuthenticationContextProtocol
|
||||||
var batchableRequests: [[SignatureRequest]] { get }
|
|
||||||
func persistAuthentication<SecretType: Secret>(secret: SecretType, forDuration duration: TimeInterval) async throws
|
func persistAuthentication<SecretType: Secret>(secret: SecretType, forDuration duration: TimeInterval) async throws
|
||||||
func requestAuthentication(for requests: Set<SignatureRequest>) async throws
|
func requestAuthentication(for requests: Set<SignatureRequest>) async throws
|
||||||
}
|
}
|
||||||
@@ -70,7 +79,7 @@ public final class AuthenticationContext: AuthenticationContextProtocol {
|
|||||||
private var persistedContexts: [AnySecret: AuthenticationContext] = [:]
|
private var persistedContexts: [AnySecret: AuthenticationContext] = [:]
|
||||||
private var holdingRequests: Set<SignatureRequest> = []
|
private var holdingRequests: Set<SignatureRequest> = []
|
||||||
private var activeTask: Task<Bool, any Error>?
|
private var activeTask: Task<Bool, any Error>?
|
||||||
private var activeContext: LAContext?
|
private var activeContext: (any AuthenticationContextProtocol)?
|
||||||
|
|
||||||
private var lastBatchAuthPresentation: Set<SignatureRequest>?
|
private var lastBatchAuthPresentation: Set<SignatureRequest>?
|
||||||
private var presentBatchAuth: (() async throws -> Void)?
|
private var presentBatchAuth: (() async throws -> Void)?
|
||||||
@@ -102,7 +111,7 @@ public final class AuthenticationContext: AuthenticationContextProtocol {
|
|||||||
lastBatchAuthPresentation = holdingRequests
|
lastBatchAuthPresentation = holdingRequests
|
||||||
logger.log("Requesting batch auth presentation")
|
logger.log("Requesting batch auth presentation")
|
||||||
try await presentBatchAuth?()
|
try await presentBatchAuth?()
|
||||||
activeContext?.invalidate()
|
await activeContext?.cancel()
|
||||||
logger.log("Requested batch auth presentation")
|
logger.log("Requested batch auth presentation")
|
||||||
}
|
}
|
||||||
if let preauthorized = existingAuthenticationContext(for: request) {
|
if let preauthorized = existingAuthenticationContext(for: request) {
|
||||||
@@ -117,11 +126,11 @@ public final class AuthenticationContext: AuthenticationContextProtocol {
|
|||||||
laContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: request.provenance.origin.displayName, secretName: request.secret.name))
|
laContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: request.provenance.origin.displayName, secretName: request.secret.name))
|
||||||
laContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
laContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||||
let context = AuthenticationContext(secret: request.secret, context: laContext, requestID: request.id)
|
let context = AuthenticationContext(secret: request.secret, context: laContext, requestID: request.id)
|
||||||
activeContext = laContext
|
activeContext = context
|
||||||
|
|
||||||
activeTask = Task {
|
activeTask = Task {
|
||||||
logger.log("Beginning individual auth prompt")
|
logger.log("Beginning individual auth prompt")
|
||||||
let result = (try? await laContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: laContext.localizedReason)) ?? false
|
let result = (try? await context.evaluate()) ?? false
|
||||||
logger.log("Ended individual auth prompt")
|
logger.log("Ended individual auth prompt")
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,11 @@ import LocalAuthentication
|
|||||||
|
|
||||||
/// Protocol describing an authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
/// Protocol describing an authentication context. This is an authorization that can be reused for multiple access to a secret that requires authentication for a specific period of time.
|
||||||
public protocol AuthenticationContextProtocol: Sendable, Identifiable {
|
public protocol AuthenticationContextProtocol: Sendable, Identifiable {
|
||||||
/// Whether the context remains valid.
|
|
||||||
|
|
||||||
var secret: AnySecret { get }
|
var secret: AnySecret { get }
|
||||||
|
|
||||||
var laContext: LAContext { get }
|
|
||||||
|
|
||||||
func valid(for request: SignatureRequest) -> Bool
|
func valid(for request: SignatureRequest) -> Bool
|
||||||
|
var laContext: LAContext? { get }
|
||||||
|
func evaluate() async throws -> Bool
|
||||||
|
func cancel() async
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SignatureRequest: Identifiable, Hashable, Sendable, Comparable {
|
public struct SignatureRequest: Identifiable, Hashable, Sendable, Comparable {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import CertificateKit
|
|||||||
// MARK: Identity Listing
|
// MARK: Identity Listing
|
||||||
|
|
||||||
@Test func emptyStores() async throws {
|
@Test func emptyStores() async throws {
|
||||||
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
|
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
||||||
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestIdentitiesEmpty)
|
#expect(response == Constants.Responses.requestIdentitiesEmpty)
|
||||||
@@ -19,7 +19,7 @@ import CertificateKit
|
|||||||
|
|
||||||
@Test func identitiesList() async throws {
|
@Test func identitiesList() async throws {
|
||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestIdentities)
|
||||||
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ import CertificateKit
|
|||||||
|
|
||||||
@Test func noMatchingIdentities() async throws {
|
@Test func noMatchingIdentities() async throws {
|
||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignatureWithNoneMatching)
|
||||||
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
@@ -43,7 +43,7 @@ import CertificateKit
|
|||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
guard case SSHAgent.Request.signRequest(let context) = request else { return }
|
guard case SSHAgent.Request.signRequest(let context) = request else { return }
|
||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler())
|
||||||
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
let responseReader = OpenSSHReader(data: response)
|
let responseReader = OpenSSHReader(data: response)
|
||||||
let length = try responseReader.readNextBytes(as: UInt32.self)
|
let length = try responseReader.readNextBytes(as: UInt32.self)
|
||||||
@@ -78,7 +78,7 @@ import CertificateKit
|
|||||||
let witness = StubWitness(speakNow: { _,_ in
|
let witness = StubWitness(speakNow: { _,_ in
|
||||||
return true
|
return true
|
||||||
}, witness: { _, _ in })
|
}, witness: { _, _ in })
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler(), witness: witness)
|
||||||
let response = await agent.handle(request: .signRequest(.empty), provenance: .test, hosts: nil)
|
let response = await agent.handle(request: .signRequest(.empty), provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,7 @@ import CertificateKit
|
|||||||
}, witness: { _, trace in
|
}, witness: { _, trace in
|
||||||
witnessed = true
|
witnessed = true
|
||||||
})
|
})
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler(), witness: witness)
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
_ = await agent.handle(request: request, provenance: .test, hosts: nil)
|
_ = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(witnessed)
|
#expect(witnessed)
|
||||||
@@ -107,7 +107,7 @@ import CertificateKit
|
|||||||
}, witness: { _, trace in
|
}, witness: { _, trace in
|
||||||
witnessTrace = trace
|
witnessTrace = trace
|
||||||
})
|
})
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore(), witness: witness)
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler(), witness: witness)
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
_ = await agent.handle(request: request, provenance: .test, hosts: nil)
|
_ = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(witnessTrace == speakNowTrace)
|
#expect(witnessTrace == speakNowTrace)
|
||||||
@@ -120,7 +120,7 @@ import CertificateKit
|
|||||||
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
let list = await storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
|
||||||
let store = list.stores.first?.base as! Stub.Store
|
let store = list.stores.first?.base as! Stub.Store
|
||||||
store.shouldThrow = true
|
store.shouldThrow = true
|
||||||
let agent = Agent(storeList: list, certificateStore: CertificateStore())
|
let agent = Agent(storeList: list, certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler())
|
||||||
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
let request = try SSHAgentInputParser().parse(data: Constants.Requests.requestSignature)
|
||||||
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
let response = await agent.handle(request: request, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
@@ -129,7 +129,7 @@ import CertificateKit
|
|||||||
// MARK: Unsupported
|
// MARK: Unsupported
|
||||||
|
|
||||||
@Test func unhandledAdd() async throws {
|
@Test func unhandledAdd() async throws {
|
||||||
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore())
|
let agent = Agent(storeList: SecretStoreList(), certificateStore: CertificateStore(), authenticationHandler: AuthenticationHandler())
|
||||||
let response = await agent.handle(request: .addIdentity, provenance: .test, hosts: nil)
|
let response = await agent.handle(request: .addIdentity, provenance: .test, hosts: nil)
|
||||||
#expect(response == Constants.Responses.requestFailure)
|
#expect(response == Constants.Responses.requestFailure)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import Testing
|
||||||
|
import SecretAgentKit
|
||||||
|
|
||||||
|
@Suite @MainActor struct AuthenticationHandlerTests {
|
||||||
|
|
||||||
|
@Test func singleImmediatelyRequests() async throws {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test func authRequiredDoesntBlockNoAuthRequired() async throws {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ extension StubWitness: SigningWitness {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws {
|
func witness(accessTo secret: AnySecret, from store: AnySecretStore, by provenance: SigningRequestProvenance, target: SigningRequestTarget?, offerPersistence: Bool) async throws {
|
||||||
witness(secret, provenance)
|
witness(secret, provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,6 @@
|
|||||||
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */; };
|
50BDCB742E6436CA0072D2E7 /* ErrorStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */; };
|
||||||
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
50BDCB762E6450950072D2E7 /* ConfigurationItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
|
||||||
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145B2EDB9CDF00B121F1 /* Common */; };
|
50E0145C2EDB9CDF00B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145B2EDB9CDF00B121F1 /* Common */; };
|
||||||
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
|
50E0145E2EDB9CE400B121F1 /* Common in Frameworks */ = {isa = PBXBuildFile; productRef = 50E0145D2EDB9CE400B121F1 /* Common */; };
|
||||||
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204E82FA9D12700402380 /* CertificateDetailView.swift */; };
|
50E204E92FA9D12700402380 /* CertificateDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E204E82FA9D12700402380 /* CertificateDetailView.swift */; };
|
||||||
@@ -294,7 +293,6 @@
|
|||||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorStyle.swift; sourceTree = "<group>"; };
|
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorStyle.swift; sourceTree = "<group>"; };
|
||||||
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
50BDCB752E6450950072D2E7 /* ConfigurationItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationItemView.swift; sourceTree = "<group>"; };
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
|
||||||
50E204E82FA9D12700402380 /* CertificateDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetailView.swift; sourceTree = "<group>"; };
|
50E204E82FA9D12700402380 /* CertificateDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetailView.swift; sourceTree = "<group>"; };
|
||||||
50E204EC2FAA997F00402380 /* CertificateListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateListItemView.swift; sourceTree = "<group>"; };
|
50E204EC2FAA997F00402380 /* CertificateListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateListItemView.swift; sourceTree = "<group>"; };
|
||||||
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveCertificateParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
50E205142FAAB81C00402380 /* SecretiveCertificateParser.xpc */ = {isa = PBXFileReference; explicitFileType = "wrapper.xpc-service"; includeInIndex = 0; path = SecretiveCertificateParser.xpc; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
@@ -385,7 +383,6 @@
|
|||||||
504788ED2E681EB200B4556F /* Modifiers */ = {
|
504788ED2E681EB200B4556F /* Modifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
|
||||||
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
50BDCB732E6436C60072D2E7 /* ErrorStyle.swift */,
|
||||||
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
504789222E697DD300B4556F /* BoxBackgroundStyle.swift */,
|
||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||||
@@ -912,7 +909,6 @@
|
|||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */,
|
504788F62E68206F00B4556F /* GettingStartedView.swift in Sources */,
|
||||||
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */,
|
|
||||||
50E204ED2FAA997F00402380 /* CertificateListItemView.swift in Sources */,
|
50E204ED2FAA997F00402380 /* CertificateListItemView.swift in Sources */,
|
||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
||||||
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
|
|
||||||
struct PrimaryButtonModifier: ViewModifier {
|
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
|
||||||
@Environment(\.isEnabled) var isEnabled
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
// Tinted glass prominent is really hard to read on 26.0.
|
|
||||||
if #available(macOS 26.0, *), colorScheme == .dark, isEnabled {
|
|
||||||
content.buttonStyle(.glassProminent)
|
|
||||||
} else {
|
|
||||||
content.buttonStyle(.borderedProminent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func primaryButton() -> some View {
|
|
||||||
modifier(PrimaryButtonModifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ToolbarCircleButtonModifier: ViewModifier {
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(macOS 26.0, *) {
|
|
||||||
content
|
|
||||||
.glassEffect(.regular.tint(.white.opacity(0.1)), in: .circle)
|
|
||||||
} else {
|
|
||||||
content
|
|
||||||
.buttonStyle(.borderless)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func toolbarCircleButton() -> some View {
|
|
||||||
modifier(ToolbarCircleButtonModifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct NormalButtonModifier: ViewModifier {
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
if #available(macOS 26.0, *) {
|
|
||||||
content.buttonStyle(.glass)
|
|
||||||
} else {
|
|
||||||
content.buttonStyle(.bordered)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func normalButton() -> some View {
|
|
||||||
modifier(NormalButtonModifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DangerButtonModifier: ViewModifier {
|
|
||||||
|
|
||||||
@Environment(\.colorScheme) var colorScheme
|
|
||||||
|
|
||||||
func body(content: Content) -> some View {
|
|
||||||
// Tinted glass prominent is really hard to read on 26.0.
|
|
||||||
if #available(macOS 26.0, *), colorScheme == .dark {
|
|
||||||
content.buttonStyle(.glassProminent)
|
|
||||||
.tint(.red)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
} else {
|
|
||||||
content.buttonStyle(.borderedProminent)
|
|
||||||
.tint(.red)
|
|
||||||
.foregroundStyle(.white)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
extension View {
|
|
||||||
|
|
||||||
func danger() -> some View {
|
|
||||||
modifier(DangerButtonModifier())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user