Repository navigation
iroh/auth hygiene: expiry-scheduled token refresh; visible + recoverable relay policy outage #10909
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
Changes from all commits
98c03a6
21bba9c
6ed798b
01c9410
721d8bc
017a52e
226af47
2b5b6c4
e49eecb
ca8eb73
4f2daab
51198c1
0bd0ddb
71df508
1ae07e4
9bbb072
defdffe
7bd2a24
2db949d
d3d80f8
4be3540
e964d32
a62e906
83268d0
fb56f99
bab67f9
9179965
e4c8887
e37b144
b4ddad5
00da9ae
0d3a58b
e3c16d6
c1f25f6
ed80b8d
9489483
7e783c9
6309838
5d9a460
8405058
f5f40f5
484a6c4
498a9be
9fb464e
0343583
e1ed22e
60ba949
cdfea9e
767ea35
554cdea
c0214d8
bf5de1c
4661bae
d17f788
728f72b
de2eede
c0950bc
acd2278
1823b87
8700ea6
f30713e
616fc69
eeef2db
e841b8a
2090293
67f0428
e88dfc8
4e4111b
2fe7eed
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,9 @@ | ||
| import CryptoKit | ||
| public import Foundation | ||
|
|
||
| /// Persists one active account's broker binding and relay capability. | ||
| /// Persists one active account's broker binding. The Keychain-backed secure | ||
| /// store survives only to delete legacy relay-credential records; no relay | ||
| /// credentials exist any more (relay admission is the relay's allow hook). | ||
|
Comment on lines
+4
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Confirm no caller remains for the private secure-store helpers.
rg -n -C3 --type=swift 'writeSecureRecord|readSecureRecord' Packages SourcesRepository: manaflow-ai/cmux Length of output: 6478 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- repository conventions and learnings ---'
find /tmp/coderabbit-repo-knowledge/manaflow-ai/cmux-b0f68d40 -maxdepth 2 -type f -name '*.md' -print
printf '%s\n' '--- credential repository ---'
cat -n Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohBrokerCredentialRepository.swift
printf '%s\n' '--- host policy cache callers and local helpers ---'
cat -n Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohHostPolicyCache.swiftRepository: manaflow-ai/cmux Length of output: 291 🏁 Script executed: #!/bin/bash
set -e
file='Packages/Shared/CmuxIrohTransport/Sources/CmuxIrohTransport/CmxIrohBrokerCredentialRepository.swift'
printf '%s\n' '--- credential repository outline ---'
ast-grep outline "$file" 2>/dev/null || true
printf '%s\n' '--- credential repository ---'
cat -n "$file"
printf '%s\n' '--- exact helper references in the transport package ---'
rg -n -C3 --type=swift 'CmxIrohBrokerCredentialRepository|writeSecureRecord|readSecureRecord|activeStorageMutationCount' Packages/Shared/CmuxIrohTransportRepository: manaflow-ai/cmux Length of output: 43879 Remove the unused secure-store helpers.
🤖 Prompt for AI Agents |
||
| public actor CmxIrohBrokerCredentialRepository { | ||
| private static let activeScopeKey = "cmux.iroh.broker-credentials.scope.v1" | ||
| private static let bindingKey = "cmux.iroh.broker-credentials.binding.v1" | ||
|
|
@@ -53,7 +55,8 @@ public actor CmxIrohBrokerCredentialRepository { | |
| ) | ||
| } | ||
|
|
||
| /// Saves an exact broker binding, invalidating relay credentials if it changed. | ||
| /// Saves an exact broker binding, deleting any legacy token-era secure | ||
| /// record if the binding changed. | ||
| /// | ||
| /// - Parameters: | ||
| /// - binding: The binding tuple returned by registration or discovery. | ||
|
|
@@ -82,123 +85,6 @@ public actor CmxIrohBrokerCredentialRepository { | |
| installState.set(String(decoding: encoded, as: UTF8.self), forKey: Self.bindingKey) | ||
| } | ||
|
|
||
| /// Loads a fresh relay credential for one exact binding and managed fleet. | ||
| /// | ||
| /// Stale, corrupt, wrong-binding, and wrong-fleet capabilities are deleted | ||
| /// and returned as a cache miss. | ||
| /// | ||
| /// - Parameters: | ||
| /// - accountID: The authenticated account identifier. | ||
| /// - binding: The exact active binding tuple. | ||
| /// - expectedRelayFleet: The complete configured managed relay fleet. | ||
| /// - now: The validation time. | ||
| /// - Returns: A validated relay credential, or `nil` when a new mint is required. | ||
| /// - Throws: A scope-validation or secure-storage error. | ||
| public func loadRelayCredential( | ||
| accountID: String, | ||
| binding: CmxIrohBrokerBindingMetadata, | ||
| expectedRelayFleet: Set<String>, | ||
| now: Date | ||
| ) async throws -> CmxIrohRelayTokenResponse? { | ||
| let epoch = try beginOperation() | ||
| let scope = try await prepareScope( | ||
| accountID: accountID, | ||
| appInstanceID: binding.appInstanceID, | ||
| epoch: epoch | ||
| ) | ||
| guard try await loadBinding( | ||
| scope: scope, | ||
| appInstanceID: binding.appInstanceID, | ||
| epoch: epoch | ||
| ) == binding else { | ||
| try await deleteSecureRecord(account: scope, epoch: epoch) | ||
| return nil | ||
| } | ||
| guard let data = try await readSecureRecord(account: scope, epoch: epoch), | ||
| let stored = try? JSONDecoder().decode( | ||
| CmxIrohStoredRelayCredential.self, | ||
| from: data | ||
| ), | ||
| stored.version == CmxIrohStoredRelayCredential.currentVersion, | ||
| stored.binding == binding, | ||
| hasExactFleet(stored.response.relayFleet, expected: expectedRelayFleet), | ||
| (try? stored.response.relayConfigurations(now: now))?.count | ||
| == expectedRelayFleet.count else { | ||
| try await deleteSecureRecord(account: scope, epoch: epoch) | ||
| return nil | ||
| } | ||
| try requireCurrent(epoch) | ||
| return stored.response | ||
| } | ||
|
|
||
| /// Saves a fresh relay credential for one exact binding and managed fleet. | ||
| /// | ||
| /// - Parameters: | ||
| /// - response: The relay token response returned by the trust broker. | ||
| /// - accountID: The authenticated account identifier. | ||
| /// - binding: The exact active binding tuple. | ||
| /// - expectedRelayFleet: The complete configured managed relay fleet. | ||
| /// - now: The validation time. | ||
| /// - Throws: A validation, encoding, or secure-storage error. | ||
| public func saveRelayCredential( | ||
| _ response: CmxIrohRelayTokenResponse, | ||
| accountID: String, | ||
| binding: CmxIrohBrokerBindingMetadata, | ||
| expectedRelayFleet: Set<String>, | ||
| now: Date | ||
| ) async throws { | ||
| let epoch = try beginOperation() | ||
| let scope = try await prepareScope( | ||
| accountID: accountID, | ||
| appInstanceID: binding.appInstanceID, | ||
| epoch: epoch | ||
| ) | ||
| guard let storedBinding = try await loadBinding( | ||
| scope: scope, | ||
| appInstanceID: binding.appInstanceID, | ||
| epoch: epoch | ||
| ) else { | ||
| throw CmxIrohBrokerCredentialRepositoryError.bindingNotStored | ||
| } | ||
| guard storedBinding == binding else { | ||
| try await deleteSecureRecord(account: scope, epoch: epoch) | ||
| throw CmxIrohBrokerCredentialRepositoryError.bindingMismatch | ||
| } | ||
| guard hasExactFleet(response.relayFleet, expected: expectedRelayFleet) else { | ||
| throw CmxIrohBrokerCredentialRepositoryError.relayFleetMismatch | ||
| } | ||
| guard (try? response.relayConfigurations(now: now))?.count | ||
| == expectedRelayFleet.count else { | ||
| throw CmxIrohBrokerCredentialRepositoryError.invalidRelayCredential | ||
| } | ||
| let record = CmxIrohStoredRelayCredential(binding: binding, response: response) | ||
| try await writeSecureRecord( | ||
| JSONEncoder().encode(record), | ||
| account: scope, | ||
| accessibility: .afterFirstUnlockThisDeviceOnly, | ||
| epoch: epoch | ||
| ) | ||
| } | ||
|
|
||
| /// Removes a relay credential while preserving its broker binding. | ||
| /// | ||
| /// - Parameters: | ||
| /// - accountID: The authenticated account identifier. | ||
| /// - appInstanceID: The installation's lowercase app-instance UUID. | ||
| /// - Throws: A scope-validation or secure-storage error. | ||
| public func deleteRelayCredential( | ||
| accountID: String, | ||
| appInstanceID: String | ||
| ) async throws { | ||
| let epoch = try beginOperation() | ||
| let scope = try await prepareScope( | ||
| accountID: accountID, | ||
| appInstanceID: appInstanceID, | ||
| epoch: epoch | ||
| ) | ||
| try await deleteSecureRecord(account: scope, epoch: epoch) | ||
| } | ||
|
|
||
| /// Removes a broker binding and every capability scoped to it. | ||
| /// | ||
| /// - Parameters: | ||
|
|
@@ -347,12 +233,6 @@ public actor CmxIrohBrokerCredentialRepository { | |
| } | ||
| } | ||
|
|
||
| private func hasExactFleet(_ fleet: [String], expected: Set<String>) -> Bool { | ||
| (1 ... CmxIrohRelayPolicyVerifier.maximumRelayCount).contains(expected.count) | ||
| && fleet.count == expected.count | ||
| && Set(fleet) == expected | ||
| } | ||
|
|
||
| private static func scope(accountID: String, appInstanceID: String) -> String { | ||
| let transcript = Data( | ||
| "cmux/iroh/broker-credential-scope/v1\0\(accountID)\0\(appInstanceID)".utf8 | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Hoist the dial-phase timeout default into one constant.
Three initializers each hard-code
.seconds(5). The endpoint-only initializer at Line 98 cannot be overridden by callers, so a future change to the default must be applied at three sites to stay consistent. Define one static default and reference it.♻️ Proposed refactor
Also applies to: 98-98, 108-109
🤖 Prompt for AI Agents