mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-11-24 03:00:56 +00:00
Consolidating persistence
This commit is contained in:
parent
9b1c3fdc88
commit
0fc4bf46f2
@ -0,0 +1,69 @@
|
|||||||
|
import LocalAuthentication
|
||||||
|
|
||||||
|
/// A context describing a persisted authentication.
|
||||||
|
package final class PersistentAuthenticationContext<SecretType: Secret>: PersistedAuthenticationContext {
|
||||||
|
|
||||||
|
/// The Secret to persist authentication for.
|
||||||
|
let secret: SecretType
|
||||||
|
/// The LAContext used to authorize the persistent context.
|
||||||
|
package nonisolated(unsafe) let context: LAContext
|
||||||
|
/// An expiration date for the context.
|
||||||
|
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
||||||
|
let monotonicExpiration: UInt64
|
||||||
|
|
||||||
|
/// Initializes a context.
|
||||||
|
/// - Parameters:
|
||||||
|
/// - secret: The Secret to persist authentication for.
|
||||||
|
/// - context: The LAContext used to authorize the persistent context.
|
||||||
|
/// - duration: The duration of the authorization context, in seconds.
|
||||||
|
init(secret: SecretType, context: LAContext, duration: TimeInterval) {
|
||||||
|
self.secret = secret
|
||||||
|
unsafe self.context = context
|
||||||
|
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
||||||
|
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A boolean describing whether or not the context is still valid.
|
||||||
|
package var valid: Bool {
|
||||||
|
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
||||||
|
}
|
||||||
|
|
||||||
|
package var expiration: Date {
|
||||||
|
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
|
||||||
|
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
|
||||||
|
return Date(timeIntervalSinceNow: remainingInSeconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
package actor PersistentAuthenticationHandler<SecretType: Secret>: Sendable {
|
||||||
|
|
||||||
|
private var persistedAuthenticationContexts: [SecretType: PersistentAuthenticationContext<SecretType>] = [:]
|
||||||
|
|
||||||
|
package init() {
|
||||||
|
}
|
||||||
|
|
||||||
|
package func existingPersistedAuthenticationContext(secret: SecretType) -> PersistentAuthenticationContext<SecretType>? {
|
||||||
|
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
||||||
|
return persisted
|
||||||
|
}
|
||||||
|
|
||||||
|
package func persistAuthentication(secret: SecretType, forDuration duration: TimeInterval) async throws {
|
||||||
|
let newContext = LAContext()
|
||||||
|
newContext.touchIDAuthenticationAllowableReuseDuration = duration
|
||||||
|
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
||||||
|
|
||||||
|
let formatter = DateComponentsFormatter()
|
||||||
|
formatter.unitsStyle = .spellOut
|
||||||
|
formatter.allowedUnits = [.hour, .minute, .day]
|
||||||
|
|
||||||
|
|
||||||
|
let durationString = formatter.string(from: duration)!
|
||||||
|
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
||||||
|
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
||||||
|
guard success else { return }
|
||||||
|
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
||||||
|
persistedAuthenticationContexts[secret] = context
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
import LocalAuthentication
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
extension SecureEnclave {
|
|
||||||
|
|
||||||
/// A context describing a persisted authentication.
|
|
||||||
final class PersistentAuthenticationContext: PersistedAuthenticationContext {
|
|
||||||
|
|
||||||
/// The Secret to persist authentication for.
|
|
||||||
let secret: Secret
|
|
||||||
/// The LAContext used to authorize the persistent context.
|
|
||||||
nonisolated(unsafe) let context: LAContext
|
|
||||||
/// An expiration date for the context.
|
|
||||||
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
|
||||||
let monotonicExpiration: UInt64
|
|
||||||
|
|
||||||
/// Initializes a context.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - secret: The Secret to persist authentication for.
|
|
||||||
/// - context: The LAContext used to authorize the persistent context.
|
|
||||||
/// - duration: The duration of the authorization context, in seconds.
|
|
||||||
init(secret: Secret, context: LAContext, duration: TimeInterval) {
|
|
||||||
self.secret = secret
|
|
||||||
unsafe self.context = context
|
|
||||||
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
|
||||||
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A boolean describing whether or not the context is still valid.
|
|
||||||
var valid: Bool {
|
|
||||||
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
|
||||||
}
|
|
||||||
|
|
||||||
var expiration: Date {
|
|
||||||
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
|
|
||||||
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
|
|
||||||
return Date(timeIntervalSinceNow: remainingInSeconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actor PersistentAuthenticationHandler: Sendable {
|
|
||||||
|
|
||||||
private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
|
|
||||||
|
|
||||||
func existingPersistedAuthenticationContext(secret: Secret) -> PersistentAuthenticationContext? {
|
|
||||||
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
|
||||||
return persisted
|
|
||||||
}
|
|
||||||
|
|
||||||
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
|
|
||||||
let newContext = LAContext()
|
|
||||||
newContext.touchIDAuthenticationAllowableReuseDuration = duration
|
|
||||||
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
|
||||||
|
|
||||||
let formatter = DateComponentsFormatter()
|
|
||||||
formatter.unitsStyle = .spellOut
|
|
||||||
formatter.allowedUnits = [.hour, .minute, .day]
|
|
||||||
|
|
||||||
|
|
||||||
let durationString = formatter.string(from: duration)!
|
|
||||||
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
|
||||||
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
|
||||||
guard success else { return }
|
|
||||||
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
|
||||||
persistedAuthenticationContexts[secret] = context
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -17,7 +17,7 @@ extension SecureEnclave {
|
|||||||
}
|
}
|
||||||
public let id = UUID()
|
public let id = UUID()
|
||||||
public let name = String(localized: .secureEnclave)
|
public let name = String(localized: .secureEnclave)
|
||||||
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
|
private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
@MainActor public init() {
|
@MainActor public init() {
|
||||||
|
|||||||
@ -1,68 +0,0 @@
|
|||||||
import LocalAuthentication
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
extension SmartCard {
|
|
||||||
|
|
||||||
/// A context describing a persisted authentication.
|
|
||||||
final class PersistentAuthenticationContext: PersistedAuthenticationContext {
|
|
||||||
|
|
||||||
/// The Secret to persist authentication for.
|
|
||||||
let secret: Secret
|
|
||||||
/// The LAContext used to authorize the persistent context.
|
|
||||||
nonisolated(unsafe) let context: LAContext
|
|
||||||
/// An expiration date for the context.
|
|
||||||
/// - Note - Monotonic time instead of Date() to prevent people setting the clock back.
|
|
||||||
let monotonicExpiration: UInt64
|
|
||||||
|
|
||||||
/// Initializes a context.
|
|
||||||
/// - Parameters:
|
|
||||||
/// - secret: The Secret to persist authentication for.
|
|
||||||
/// - context: The LAContext used to authorize the persistent context.
|
|
||||||
/// - duration: The duration of the authorization context, in seconds.
|
|
||||||
init(secret: Secret, context: LAContext, duration: TimeInterval) {
|
|
||||||
self.secret = secret
|
|
||||||
unsafe self.context = context
|
|
||||||
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
|
|
||||||
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A boolean describing whether or not the context is still valid.
|
|
||||||
var valid: Bool {
|
|
||||||
clock_gettime_nsec_np(CLOCK_MONOTONIC) < monotonicExpiration
|
|
||||||
}
|
|
||||||
|
|
||||||
var expiration: Date {
|
|
||||||
let remainingNanoseconds = monotonicExpiration - clock_gettime_nsec_np(CLOCK_MONOTONIC)
|
|
||||||
let remainingInSeconds = Measurement(value: Double(remainingNanoseconds), unit: UnitDuration.nanoseconds).converted(to: .seconds).value
|
|
||||||
return Date(timeIntervalSinceNow: remainingInSeconds)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
actor PersistentAuthenticationHandler: Sendable {
|
|
||||||
|
|
||||||
private var persistedAuthenticationContexts: [Secret: PersistentAuthenticationContext] = [:]
|
|
||||||
|
|
||||||
func existingPersistedAuthenticationContext(secret: Secret) -> PersistentAuthenticationContext? {
|
|
||||||
guard let persisted = persistedAuthenticationContexts[secret], persisted.valid else { return nil }
|
|
||||||
return persisted
|
|
||||||
}
|
|
||||||
|
|
||||||
func persistAuthentication(secret: Secret, forDuration duration: TimeInterval) async throws {
|
|
||||||
let newContext = LAContext()
|
|
||||||
newContext.touchIDAuthenticationAllowableReuseDuration = duration
|
|
||||||
newContext.localizedCancelTitle = String(localized: .authContextRequestDenyButton)
|
|
||||||
|
|
||||||
let formatter = DateComponentsFormatter()
|
|
||||||
formatter.unitsStyle = .spellOut
|
|
||||||
formatter.allowedUnits = [.hour, .minute, .day]
|
|
||||||
|
|
||||||
|
|
||||||
let durationString = formatter.string(from: duration)!
|
|
||||||
newContext.localizedReason = String(localized: .authContextPersistForDuration(secretName: secret.name, duration: durationString))
|
|
||||||
let success = try await newContext.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: newContext.localizedReason)
|
|
||||||
guard success else { return }
|
|
||||||
let context = PersistentAuthenticationContext(secret: secret, context: newContext, duration: duration)
|
|
||||||
persistedAuthenticationContexts[secret] = context
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -34,7 +34,7 @@ extension SmartCard {
|
|||||||
public var secrets: [Secret] {
|
public var secrets: [Secret] {
|
||||||
state.secrets
|
state.secrets
|
||||||
}
|
}
|
||||||
private let persistentAuthenticationHandler = PersistentAuthenticationHandler()
|
private let persistentAuthenticationHandler = PersistentAuthenticationHandler<Secret>()
|
||||||
|
|
||||||
/// Initializes a Store.
|
/// Initializes a Store.
|
||||||
public init() {
|
public init() {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user