mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-07-01 17:53:36 +00:00
Compare commits
10 Commits
Author | SHA1 | Date | |
---|---|---|---|
9d6bbd2a04 | |||
35aea0729d | |||
c5bd4c2189 | |||
8114acf50a | |||
cd965b9ec6 | |||
f30d1f802f | |||
af479408a4 | |||
42b034270a | |||
dca340ad40 | |||
8dfabd35aa |
BIN
.github/readme/app.png
vendored
BIN
.github/readme/app.png
vendored
Binary file not shown.
Before Width: | Height: | Size: 338 KiB After Width: | Height: | Size: 456 KiB |
7
.github/scripts/signing.sh
vendored
7
.github/scripts/signing.sh
vendored
@ -10,10 +10,13 @@ security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k ci ci.keyc
|
|||||||
|
|
||||||
# Import Profiles
|
# Import Profiles
|
||||||
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
|
||||||
|
|
||||||
echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
|
echo $HOST_PROFILE_DATA | base64 -d -o Host.provisionprofile
|
||||||
HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
HOST_UUID=`grep UUID -A1 -a Host.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
||||||
cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
|
cp Host.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$HOST_UUID.provisionprofile
|
||||||
echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
|
echo $AGENT_PROFILE_DATA | base64 -d -o Agent.provisionprofile
|
||||||
AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
AGENT_UUID=`grep UUID -A1 -a Agent.provisionprofile | grep -io "[-A-F0-9]\{36\}"`
|
||||||
cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
|
cp Agent.provisionprofile ~/Library/MobileDevice/Provisioning\ Profiles/$AGENT_UUID.provisionprofile
|
||||||
|
|
||||||
|
# Create directories for ASC key
|
||||||
|
mkdir ~/.private_keys
|
||||||
|
echo -n "$APPLE_API_KEY_DATA" > ~/.private_keys/AuthKey_$APPLE_API_KEY_ID.p8
|
||||||
|
14
.github/workflows/release.yml
vendored
14
.github/workflows/release.yml
vendored
@ -16,9 +16,11 @@ jobs:
|
|||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
||||||
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
||||||
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
run: ./.github/scripts/signing.sh
|
run: ./.github/scripts/signing.sh
|
||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_12.3.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_12.5.1.app
|
||||||
- name: Test
|
- name: Test
|
||||||
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
|
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
|
||||||
build:
|
build:
|
||||||
@ -32,9 +34,11 @@ jobs:
|
|||||||
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
|
||||||
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
HOST_PROFILE_DATA: ${{ secrets.HOST_PROFILE_DATA }}
|
||||||
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
AGENT_PROFILE_DATA: ${{ secrets.AGENT_PROFILE_DATA }}
|
||||||
|
APPLE_API_KEY_DATA: ${{ secrets.APPLE_API_KEY_DATA }}
|
||||||
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
run: ./.github/scripts/signing.sh
|
run: ./.github/scripts/signing.sh
|
||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_12.2.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_12.5.1.app
|
||||||
- name: Update Build Number
|
- name: Update Build Number
|
||||||
env:
|
env:
|
||||||
TAG_NAME: ${{ github.ref }}
|
TAG_NAME: ${{ github.ref }}
|
||||||
@ -52,9 +56,9 @@ jobs:
|
|||||||
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive ./Archive.zip
|
ditto -c -k --sequesterRsrc --keepParent Archive.xcarchive ./Archive.zip
|
||||||
- name: Notarize
|
- name: Notarize
|
||||||
env:
|
env:
|
||||||
APPLE_USERNAME: ${{ secrets.APPLE_USERNAME }}
|
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||||
run: xcrun altool --notarize-app --primary-bundle-id "com.maxgoedjen.secretive.host" --username $APPLE_USERNAME --password $APPLE_PASSWORD --file Secretive.zip
|
run: xcrun altool --notarize-app --primary-bundle-id "com.maxgoedjen.secretive.host" --apiKey $APPLE_API_KEY_ID --apiIssuer $APPLE_API_ISSUER --file Secretive.zip
|
||||||
- name: Document SHAs
|
- name: Document SHAs
|
||||||
run: |
|
run: |
|
||||||
shasum -a 512 Secretive.zip
|
shasum -a 512 Secretive.zip
|
||||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@ -8,6 +8,6 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set Environment
|
- name: Set Environment
|
||||||
run: sudo xcrun xcode-select -s /Applications/Xcode_12.3.app
|
run: sudo xcrun xcode-select -s /Applications/Xcode_12.5.1.app
|
||||||
- name: Test
|
- name: Test
|
||||||
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
|
run: xcrun xcodebuild test -project Secretive.xcodeproj -scheme Secretive
|
||||||
|
@ -14,6 +14,14 @@ Secretive is designed to be easily auditable by people who are considering using
|
|||||||
|
|
||||||
All contributors must abide by the [Code of Conduct](CODE_OF_CONDUCT.md)
|
All contributors must abide by the [Code of Conduct](CODE_OF_CONDUCT.md)
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
If you make a material contribution to the app, please add yourself to the end of the [credits](https://github.com/maxgoedjen/secretive/blob/main/Secretive/Credits.rtf).
|
||||||
|
|
||||||
|
## Collaborator Status
|
||||||
|
|
||||||
|
I will not grant collaborator access to any contributors for this repository. This is basically just because collaborators [can accesss the secrets Secretive uses for the signing credentials stored in the repository](https://docs.github.com/en/actions/reference/encrypted-secrets#accessing-your-secrets).
|
||||||
|
|
||||||
## Secretive is Opinionated
|
## Secretive is Opinionated
|
||||||
|
|
||||||
I'm releasing Secretive as open source so that other people can use it and audit it, feeling comfortable in knowing that the source is available so they can see what it's doing. I have a pretty strong idea of what I'd like this project to look like, and I may respectfully decline contributions that don't line up with that vision. If you'd like to propose a change before implementing, please feel free to [Open an Issue with the proposed tag](https://github.com/maxgoedjen/secretive/issues/new?labels=proposed).
|
I'm releasing Secretive as open source so that other people can use it and audit it, feeling comfortable in knowing that the source is available so they can see what it's doing. I have a pretty strong idea of what I'd like this project to look like, and I may respectfully decline contributions that don't line up with that vision. If you'd like to propose a change before implementing, please feel free to [Open an Issue with the proposed tag](https://github.com/maxgoedjen/secretive/issues/new?labels=proposed).
|
||||||
|
BIN
Icon.sketch
BIN
Icon.sketch
Binary file not shown.
7
SecretKit/Common/BundleIDs.swift
Normal file
7
SecretKit/Common/BundleIDs.swift
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
|
extension Bundle {
|
||||||
|
public var agentBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "Host", with: "SecretAgent"))!}
|
||||||
|
public var hostBundleID: String {(self.bundleIdentifier?.replacingOccurrences(of: "SecretAgent", with: "Host"))!}
|
||||||
|
}
|
@ -49,10 +49,12 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
|
|||||||
|
|
||||||
private let _create: (String, Bool) throws -> Void
|
private let _create: (String, Bool) throws -> Void
|
||||||
private let _delete: (AnySecret) throws -> Void
|
private let _delete: (AnySecret) throws -> Void
|
||||||
|
private let _update: (AnySecret, String) throws -> Void
|
||||||
|
|
||||||
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
public init<SecretStoreType>(modifiable secretStore: SecretStoreType) where SecretStoreType: SecretStoreModifiable {
|
||||||
_create = { try secretStore.create(name: $0, requiresAuthentication: $1) }
|
_create = { try secretStore.create(name: $0, requiresAuthentication: $1) }
|
||||||
_delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
|
_delete = { try secretStore.delete(secret: $0.base as! SecretStoreType.SecretType) }
|
||||||
|
_update = { try secretStore.update(secret: $0.base as! SecretStoreType.SecretType, name: $1) }
|
||||||
super.init(secretStore)
|
super.init(secretStore)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,4 +66,7 @@ public class AnySecretStoreModifiable: AnySecretStore, SecretStoreModifiable {
|
|||||||
try _delete(secret)
|
try _delete(secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func update(secret: AnySecret, name: String) throws {
|
||||||
|
try _update(secret, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ public protocol SecretStoreModifiable: SecretStore {
|
|||||||
|
|
||||||
func create(name: String, requiresAuthentication: Bool) throws
|
func create(name: String, requiresAuthentication: Bool) throws
|
||||||
func delete(secret: SecretType) throws
|
func delete(secret: SecretType) throws
|
||||||
|
func update(secret: SecretType, name: String) throws
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ extension SecureEnclave {
|
|||||||
let deleteAttributes = [
|
let deleteAttributes = [
|
||||||
kSecClass: kSecClassKey,
|
kSecClass: kSecClassKey,
|
||||||
kSecAttrApplicationLabel: secret.id as CFData
|
kSecAttrApplicationLabel: secret.id as CFData
|
||||||
] as CFDictionary
|
] as CFDictionary
|
||||||
let status = SecItemDelete(deleteAttributes)
|
let status = SecItemDelete(deleteAttributes)
|
||||||
if status != errSecSuccess {
|
if status != errSecSuccess {
|
||||||
throw KeychainError(statusCode: status)
|
throw KeychainError(statusCode: status)
|
||||||
@ -76,6 +76,23 @@ extension SecureEnclave {
|
|||||||
reloadSecrets()
|
reloadSecrets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public func update(secret: Secret, name: String) throws {
|
||||||
|
let updateQuery = [
|
||||||
|
kSecClass: kSecClassKey,
|
||||||
|
kSecAttrApplicationLabel: secret.id as CFData
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
let updatedAttributes = [
|
||||||
|
kSecAttrLabel: name,
|
||||||
|
] as CFDictionary
|
||||||
|
|
||||||
|
let status = SecItemUpdate(updateQuery, updatedAttributes)
|
||||||
|
if status != errSecSuccess {
|
||||||
|
throw KeychainError(statusCode: status)
|
||||||
|
}
|
||||||
|
reloadSecrets()
|
||||||
|
}
|
||||||
|
|
||||||
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
|
public func sign(data: Data, with secret: SecretType, for provenance: SigningRequestProvenance) throws -> Data {
|
||||||
let context = LAContext()
|
let context = LAContext()
|
||||||
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
|
context.localizedReason = "sign a request from \"\(provenance.origin.displayName)\" using secret \"\(secret.name)\""
|
||||||
|
@ -7,9 +7,10 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */; };
|
||||||
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
50020BB024064869003D4025 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50020BAF24064869003D4025 /* AppDelegate.swift */; };
|
||||||
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
50153E20250AFCB200525160 /* UpdateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E1F250AFCB200525160 /* UpdateView.swift */; };
|
||||||
50153E22250DECA300525160 /* SecretListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListView.swift */; };
|
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50153E21250DECA300525160 /* SecretListItemView.swift */; };
|
||||||
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
5018F54F24064786002EB505 /* Notifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5018F54E24064786002EB505 /* Notifier.swift */; };
|
||||||
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; };
|
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507CE4F32420A8C10029F750 /* SigningRequestProvenance.swift */; };
|
||||||
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
50524B442420969E008DBD97 /* OpenSSHWriterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */; };
|
||||||
@ -87,6 +88,7 @@
|
|||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
||||||
50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A2240789E600AF2719 /* OpenSSHReader.swift */; };
|
50C385A3240789E600AF2719 /* OpenSSHReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A2240789E600AF2719 /* OpenSSHReader.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
|
FA0B34672599619E0013AB3A /* BundleIDs.swift in Sources */ = {isa = PBXBuildFile; fileRef = FA0B34662599619E0013AB3A /* BundleIDs.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -221,9 +223,10 @@
|
|||||||
/* End PBXCopyFilesBuildPhase section */
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenameSecretView.swift; sourceTree = "<group>"; };
|
||||||
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
50020BAF24064869003D4025 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
50153E1F250AFCB200525160 /* UpdateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdateView.swift; sourceTree = "<group>"; };
|
||||||
50153E21250DECA300525160 /* SecretListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListView.swift; sourceTree = "<group>"; };
|
50153E21250DECA300525160 /* SecretListItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretListItemView.swift; sourceTree = "<group>"; };
|
||||||
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
5018F54E24064786002EB505 /* Notifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifier.swift; sourceTree = "<group>"; };
|
||||||
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
50524B432420969D008DBD97 /* OpenSSHWriterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSSHWriterTests.swift; sourceTree = "<group>"; };
|
||||||
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
50571E0224393C2600F76F6C /* JustUpdatedChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustUpdatedChecker.swift; sourceTree = "<group>"; };
|
||||||
@ -308,6 +311,7 @@
|
|||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
||||||
50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretKit/Common/OpenSSH/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; };
|
50C385A2240789E600AF2719 /* OpenSSHReader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OpenSSHReader.swift; path = SecretKit/Common/OpenSSH/OpenSSHReader.swift; sourceTree = SOURCE_ROOT; };
|
||||||
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>"; };
|
||||||
|
FA0B34662599619E0013AB3A /* BundleIDs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleIDs.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -539,10 +543,11 @@
|
|||||||
children = (
|
children = (
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||||
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
|
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
|
||||||
50153E21250DECA300525160 /* SecretListView.swift */,
|
50153E21250DECA300525160 /* SecretListItemView.swift */,
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
||||||
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
5099A02323FD2AAA0062B6F2 /* CreateSecretView.swift */,
|
||||||
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
50B8550C24138C4F009958AC /* DeleteSecretView.swift */,
|
||||||
|
2C4A9D2E2636FFD3008CC8E2 /* RenameSecretView.swift */,
|
||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */,
|
||||||
506772C82425BB8500034DED /* NoStoresView.swift */,
|
506772C82425BB8500034DED /* NoStoresView.swift */,
|
||||||
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
50153E1F250AFCB200525160 /* UpdateView.swift */,
|
||||||
@ -591,6 +596,7 @@
|
|||||||
5068389F2415EA4F00F55094 /* Erasers */,
|
5068389F2415EA4F00F55094 /* Erasers */,
|
||||||
506838A42415EA6800F55094 /* OpenSSH */,
|
506838A42415EA6800F55094 /* OpenSSH */,
|
||||||
5068389D241471CD00F55094 /* SecretStoreList.swift */,
|
5068389D241471CD00F55094 /* SecretStoreList.swift */,
|
||||||
|
FA0B34662599619E0013AB3A /* BundleIDs.swift */,
|
||||||
);
|
);
|
||||||
path = Common;
|
path = Common;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1002,6 +1008,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
2C4A9D2F2636FFD3008CC8E2 /* RenameSecretView.swift in Sources */,
|
||||||
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
5091D2BC25183B830049FD9B /* ApplicationDirectoryController.swift in Sources */,
|
||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
@ -1019,7 +1026,7 @@
|
|||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */,
|
||||||
50617D8323FCE48E0099B055 /* App.swift in Sources */,
|
50617D8323FCE48E0099B055 /* App.swift in Sources */,
|
||||||
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
|
506772C92425BB8500034DED /* NoStoresView.swift in Sources */,
|
||||||
50153E22250DECA300525160 /* SecretListView.swift in Sources */,
|
50153E22250DECA300525160 /* SecretListItemView.swift in Sources */,
|
||||||
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
|
508A58B5241ED48F0069DC07 /* PreviewAgentStatusChecker.swift in Sources */,
|
||||||
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
|
508A58AA241E06B40069DC07 /* PreviewUpdater.swift in Sources */,
|
||||||
);
|
);
|
||||||
@ -1037,6 +1044,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
FA0B34672599619E0013AB3A /* BundleIDs.swift in Sources */,
|
||||||
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */,
|
501B7AE1251C56F700776EC7 /* SigningRequestProvenance.swift in Sources */,
|
||||||
50617DC723FCE4EA0099B055 /* SecretStore.swift in Sources */,
|
50617DC723FCE4EA0099B055 /* SecretStore.swift in Sources */,
|
||||||
5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */,
|
5099A02723FE34FA0062B6F2 /* SmartCard.swift in Sources */,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Combine
|
import Combine
|
||||||
import AppKit
|
import AppKit
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
protocol AgentStatusCheckerProtocol: ObservableObject {
|
protocol AgentStatusCheckerProtocol: ObservableObject {
|
||||||
var running: Bool { get }
|
var running: Bool { get }
|
||||||
@ -20,7 +21,7 @@ class AgentStatusChecker: ObservableObject, AgentStatusCheckerProtocol {
|
|||||||
|
|
||||||
// All processes, including ones from older versions, etc
|
// All processes, including ones from older versions, etc
|
||||||
var secretAgentProcesses: [NSRunningApplication] {
|
var secretAgentProcesses: [NSRunningApplication] {
|
||||||
NSRunningApplication.runningApplications(withBundleIdentifier: Constants.secretAgentAppID)
|
NSRunningApplication.runningApplications(withBundleIdentifier: Bundle.main.agentBundleID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The process corresponding to this instance of Secretive
|
// The process corresponding to this instance of Secretive
|
||||||
@ -37,10 +38,4 @@ class AgentStatusChecker: ObservableObject, AgentStatusCheckerProtocol {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension AgentStatusChecker {
|
|
||||||
|
|
||||||
enum Constants {
|
|
||||||
static let secretAgentAppID = "com.maxgoedjen.Secretive.SecretAgent"
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
@ -2,9 +2,10 @@ import Foundation
|
|||||||
import ServiceManagement
|
import ServiceManagement
|
||||||
import AppKit
|
import AppKit
|
||||||
import OSLog
|
import OSLog
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
struct LaunchAgentController {
|
struct LaunchAgentController {
|
||||||
|
|
||||||
func install(completion: (() -> Void)? = nil) {
|
func install(completion: (() -> Void)? = nil) {
|
||||||
Logger().debug("Installing agent")
|
Logger().debug("Installing agent")
|
||||||
_ = setEnabled(false)
|
_ = setEnabled(false)
|
||||||
@ -21,8 +22,12 @@ struct LaunchAgentController {
|
|||||||
func forceLaunch(completion: ((Bool) -> Void)?) {
|
func forceLaunch(completion: ((Bool) -> Void)?) {
|
||||||
Logger().debug("Agent is not running, attempting to force launch")
|
Logger().debug("Agent is not running, attempting to force launch")
|
||||||
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
let url = Bundle.main.bundleURL.appendingPathComponent("Contents/Library/LoginItems/SecretAgent.app")
|
||||||
NSWorkspace.shared.openApplication(at: url, configuration: NSWorkspace.OpenConfiguration()) { app, error in
|
let config = NSWorkspace.OpenConfiguration()
|
||||||
completion?(error == nil)
|
config.activates = false
|
||||||
|
NSWorkspace.shared.openApplication(at: url, configuration: config) { app, error in
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
completion?(error == nil)
|
||||||
|
}
|
||||||
if let error = error {
|
if let error = error {
|
||||||
Logger().error("Error force launching \(error.localizedDescription)")
|
Logger().error("Error force launching \(error.localizedDescription)")
|
||||||
} else {
|
} else {
|
||||||
@ -32,7 +37,7 @@ struct LaunchAgentController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func setEnabled(_ enabled: Bool) -> Bool {
|
private func setEnabled(_ enabled: Bool) -> Bool {
|
||||||
SMLoginItemSetEnabled("com.maxgoedjen.Secretive.SecretAgent" as CFString, enabled)
|
SMLoginItemSetEnabled(Bundle.main.agentBundleID as CFString, enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import Cocoa
|
import Cocoa
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
struct ShellConfigurationController {
|
struct ShellConfigurationController {
|
||||||
|
|
||||||
let socketPath = (NSHomeDirectory().replacingOccurrences(of: "com.maxgoedjen.Secretive.Host", with: "com.maxgoedjen.Secretive.SecretAgent") as NSString).appendingPathComponent("socket.ssh") as String
|
let socketPath = (NSHomeDirectory().replacingOccurrences(of: Bundle.main.hostBundleID, with: Bundle.main.agentBundleID) as NSString).appendingPathComponent("socket.ssh") as String
|
||||||
|
|
||||||
var shellInstructions: [ShellConfigInstruction] {
|
var shellInstructions: [ShellConfigInstruction] {
|
||||||
[
|
[
|
||||||
|
ShellConfigInstruction(shell: "global",
|
||||||
|
shellConfigDirectory: "~/.ssh/",
|
||||||
|
shellConfigFilename: "config",
|
||||||
|
text: "Host *\n\tIdentityAgent \(socketPath)"),
|
||||||
ShellConfigInstruction(shell: "zsh",
|
ShellConfigInstruction(shell: "zsh",
|
||||||
shellConfigDirectory: "~/",
|
shellConfigDirectory: "~/",
|
||||||
shellConfigFilename: ".zshrc",
|
shellConfigFilename: ".zshrc",
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
{\rtf1\ansi\ansicpg1252\cocoartf2511
|
{\rtf1\ansi\ansicpg1252\cocoartf2580
|
||||||
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
\cocoatextscaling0\cocoaplatform0{\fonttbl\f0\fswiss\fcharset0 Helvetica;}
|
||||||
{\colortbl;\red255\green255\blue255;}
|
{\colortbl;\red255\green255\blue255;}
|
||||||
{\*\expandedcolortbl;;}
|
{\*\expandedcolortbl;;}
|
||||||
@ -12,6 +12,19 @@
|
|||||||
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
{\field{\*\fldinst{HYPERLINK "GITHUB_BUILD_URL"}}{\fldrslt Build Log}}\
|
||||||
\
|
\
|
||||||
Special Thanks To:\
|
Special Thanks To:\
|
||||||
|
\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/maxgoedjen/secretive/graphs/contributors"}}{\fldrslt Contributors}}:\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/0xflotus"}}{\fldrslt 0xflotus}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/aaron-trout"}}{\fldrslt Aaron Trout}}\
|
||||||
|
\pard\pardeftab720\partightenfactor0
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/EppO"}}{\fldrslt \cf0 Florent Monbillard}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/vladimyr"}}{\fldrslt Dario Vladovi\uc0\u263 }}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/lavalleeale"}}{\fldrslt Alex Lavallee}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/joshheyse"}}{\fldrslt Josh}}\
|
||||||
|
{\field{\*\fldinst{HYPERLINK "https://github.com/diesal11"}}{\fldrslt Dylan Lundy}}\
|
||||||
|
\
|
||||||
|
\pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardirnatural\partightenfactor0
|
||||||
|
\cf0 Testers:\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/bdash"}}{\fldrslt Mark Rowe}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/danielctull"}}{\fldrslt Daniel Tull}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/danielctull"}}{\fldrslt Daniel Tull}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/davedelong"}}{\fldrslt Dave DeLong}}\
|
||||||
@ -20,5 +33,4 @@ Special Thanks To:\
|
|||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/marksands"}}{\fldrslt Mark Sands}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/mergesort"}}{\fldrslt Joe Fabisevich}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
{\field{\*\fldinst{HYPERLINK "https://github.com/phillco"}}{\fldrslt Phil Cohen}}\
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
{\field{\*\fldinst{HYPERLINK "https://github.com/zackdotcomputer"}}{\fldrslt Zack Sheppard}}}
|
||||||
{\field{\*\fldinst{HYPERLINK "https://github.com/zacwest"}}{\fldrslt Zac West}}}
|
|
@ -42,7 +42,6 @@ extension Preview {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StoreModifiable: Store, SecretStoreModifiable {
|
class StoreModifiable: Store, SecretStoreModifiable {
|
||||||
|
|
||||||
override var name: String { "Modifiable Preview Store" }
|
override var name: String { "Modifiable Preview Store" }
|
||||||
|
|
||||||
func create(name: String, requiresAuthentication: Bool) throws {
|
func create(name: String, requiresAuthentication: Bool) throws {
|
||||||
@ -50,8 +49,10 @@ extension Preview {
|
|||||||
|
|
||||||
func delete(secret: Preview.Secret) throws {
|
func delete(secret: Preview.Secret) throws {
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
func update(secret: Preview.Secret, name: String) throws {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Preview {
|
extension Preview {
|
||||||
|
@ -2,10 +2,10 @@ import SwiftUI
|
|||||||
import SecretKit
|
import SecretKit
|
||||||
|
|
||||||
struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||||
|
|
||||||
@ObservedObject var store: StoreType
|
@ObservedObject var store: StoreType
|
||||||
@Binding var showing: Bool
|
@Binding var showing: Bool
|
||||||
|
|
||||||
@State private var name = ""
|
@State private var name = ""
|
||||||
@State private var requiresAuthentication = true
|
@State private var requiresAuthentication = true
|
||||||
|
|
||||||
@ -26,8 +26,12 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
TextField("Shhhhh", text: $name).focusable()
|
TextField("Shhhhh", text: $name).focusable()
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Toggle(isOn: $requiresAuthentication) {
|
VStack(spacing: 20) {
|
||||||
Text("Requires Authentication (Biometrics or Password)")
|
Picker("", selection: $requiresAuthentication) {
|
||||||
|
Text("Requires Authentication (Biometrics or Password) before each use").tag(true)
|
||||||
|
Text("Authentication not required when Mac is unlocked").tag(false)
|
||||||
|
}
|
||||||
|
.pickerStyle(RadioGroupPickerStyle())
|
||||||
}
|
}
|
||||||
Spacer()
|
Spacer()
|
||||||
}
|
}
|
||||||
@ -45,7 +49,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
}.padding()
|
}.padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
func save() {
|
func save() {
|
||||||
try! store.create(name: name, requiresAuthentication: requiresAuthentication)
|
try! store.create(name: name, requiresAuthentication: requiresAuthentication)
|
||||||
showing = false
|
showing = false
|
||||||
|
@ -30,9 +30,6 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
TextField(secret.name, text: $confirm)
|
TextField(secret.name, text: $confirm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onExitCommand {
|
|
||||||
dismissalBlock(false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
HStack {
|
HStack {
|
||||||
Spacer()
|
Spacer()
|
||||||
@ -47,6 +44,9 @@ struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.frame(minWidth: 400)
|
.frame(minWidth: 400)
|
||||||
|
.onExitCommand {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func delete() {
|
func delete() {
|
||||||
|
50
Secretive/Views/RenameSecretView.swift
Normal file
50
Secretive/Views/RenameSecretView.swift
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct RenameSecretView<StoreType: SecretStoreModifiable>: View {
|
||||||
|
|
||||||
|
@ObservedObject var store: StoreType
|
||||||
|
let secret: StoreType.SecretType
|
||||||
|
var dismissalBlock: (_ renamed: Bool) -> ()
|
||||||
|
|
||||||
|
@State private var newName = ""
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Image(nsImage: NSApp.applicationIconImage)
|
||||||
|
.resizable()
|
||||||
|
.frame(width: 64, height: 64)
|
||||||
|
.padding()
|
||||||
|
VStack {
|
||||||
|
HStack {
|
||||||
|
Text("Type your new name for \"\(secret.name)\" below.")
|
||||||
|
Spacer()
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
TextField(secret.name, text: $newName).focusable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HStack {
|
||||||
|
Spacer()
|
||||||
|
Button("Rename", action: rename)
|
||||||
|
.disabled(newName.count == 0)
|
||||||
|
.keyboardShortcut(.return)
|
||||||
|
Button("Cancel") {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}.keyboardShortcut(.cancelAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding()
|
||||||
|
.frame(minWidth: 400)
|
||||||
|
.onExitCommand {
|
||||||
|
dismissalBlock(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func rename() {
|
||||||
|
try? store.update(secret: secret, name: newName)
|
||||||
|
dismissalBlock(true)
|
||||||
|
}
|
||||||
|
}
|
@ -25,9 +25,9 @@ struct SecretDetailView<SecretType: Secret>: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var dashedKeyName: String {
|
var dashedKeyName: String {
|
||||||
secret.name.replacingOccurrences(of: " ", with: "-")
|
secret.name.replacingOccurrences(of: " ", with: "-")
|
||||||
}
|
}
|
||||||
|
|
||||||
var dashedHostName: String {
|
var dashedHostName: String {
|
||||||
["secretive", Host.current().localizedName, "local"]
|
["secretive", Host.current().localizedName, "local"]
|
||||||
.compactMap { $0 }
|
.compactMap { $0 }
|
||||||
|
54
Secretive/Views/SecretListItemView.swift
Normal file
54
Secretive/Views/SecretListItemView.swift
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import SwiftUI
|
||||||
|
import SecretKit
|
||||||
|
|
||||||
|
struct SecretListItemView: View {
|
||||||
|
|
||||||
|
@ObservedObject var store: AnySecretStore
|
||||||
|
var secret: AnySecret
|
||||||
|
@Binding var activeSecret: AnySecret.ID?
|
||||||
|
|
||||||
|
@State var isDeleting: Bool = false
|
||||||
|
@State var isRenaming: Bool = false
|
||||||
|
|
||||||
|
var deletedSecret: (AnySecret) -> Void
|
||||||
|
var renamedSecret: (AnySecret) -> Void
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
let showingPopupWrapped = Binding(
|
||||||
|
get: { isDeleting || isRenaming },
|
||||||
|
set: { if $0 == false { isDeleting = false; isRenaming = false } }
|
||||||
|
)
|
||||||
|
|
||||||
|
return NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: $activeSecret) {
|
||||||
|
Text(secret.name)
|
||||||
|
}.contextMenu {
|
||||||
|
if store is AnySecretStoreModifiable {
|
||||||
|
Button(action: { isRenaming = true }) {
|
||||||
|
Text("Rename")
|
||||||
|
}
|
||||||
|
Button(action: { isDeleting = true }) {
|
||||||
|
Text("Delete")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.popover(isPresented: showingPopupWrapped) {
|
||||||
|
if let modifiable = store as? AnySecretStoreModifiable {
|
||||||
|
if isDeleting {
|
||||||
|
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
||||||
|
isDeleting = false
|
||||||
|
if deleted {
|
||||||
|
deletedSecret(secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if isRenaming {
|
||||||
|
RenameSecretView(store: modifiable, secret: secret) { renamed in
|
||||||
|
isRenaming = false
|
||||||
|
if renamed {
|
||||||
|
renamedSecret(secret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import SwiftUI
|
|
||||||
import SecretKit
|
|
||||||
|
|
||||||
struct SecretListView: View {
|
|
||||||
|
|
||||||
@ObservedObject var store: AnySecretStore
|
|
||||||
@Binding var activeSecret: AnySecret.ID?
|
|
||||||
@Binding var deletingSecret: AnySecret?
|
|
||||||
|
|
||||||
var deletedSecret: (AnySecret) -> Void
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
ForEach(store.secrets) { secret in
|
|
||||||
NavigationLink(destination: SecretDetailView(secret: secret), tag: secret.id, selection: $activeSecret) {
|
|
||||||
Text(secret.name)
|
|
||||||
}.contextMenu {
|
|
||||||
if store is AnySecretStoreModifiable {
|
|
||||||
Button(action: { delete(secret: secret) }) {
|
|
||||||
Text("Delete")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.popover(isPresented: .constant(deletingSecret == secret)) {
|
|
||||||
if let modifiable = store as? AnySecretStoreModifiable {
|
|
||||||
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
|
||||||
deletingSecret = nil
|
|
||||||
if deleted {
|
|
||||||
deletedSecret(AnySecret(secret))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func delete<SecretType: Secret>(secret: SecretType) {
|
|
||||||
deletingSecret = AnySecret(secret)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -6,10 +6,17 @@ struct StoreListView: View {
|
|||||||
@Binding var showingCreation: Bool
|
@Binding var showingCreation: Bool
|
||||||
|
|
||||||
@State private var activeSecret: AnySecret.ID?
|
@State private var activeSecret: AnySecret.ID?
|
||||||
@State private var deletingSecret: AnySecret?
|
|
||||||
|
|
||||||
@EnvironmentObject private var storeList: SecretStoreList
|
@EnvironmentObject private var storeList: SecretStoreList
|
||||||
|
|
||||||
|
private func secretDeleted(secret: AnySecret) {
|
||||||
|
activeSecret = nextDefaultSecret
|
||||||
|
}
|
||||||
|
|
||||||
|
private func secretRenamed(secret: AnySecret) {
|
||||||
|
activeSecret = nextDefaultSecret
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
List(selection: $activeSecret) {
|
List(selection: $activeSecret) {
|
||||||
@ -19,9 +26,15 @@ struct StoreListView: View {
|
|||||||
if store.secrets.isEmpty {
|
if store.secrets.isEmpty {
|
||||||
EmptyStoreView(store: store, activeSecret: $activeSecret)
|
EmptyStoreView(store: store, activeSecret: $activeSecret)
|
||||||
} else {
|
} else {
|
||||||
SecretListView(store: store, activeSecret: $activeSecret, deletingSecret: $deletingSecret, deletedSecret: { _ in
|
ForEach(store.secrets) { secret in
|
||||||
activeSecret = nextDefaultSecret
|
SecretListItemView(
|
||||||
})
|
store: store,
|
||||||
|
secret: secret,
|
||||||
|
activeSecret: $activeSecret,
|
||||||
|
deletedSecret: self.secretDeleted,
|
||||||
|
renamedSecret: self.secretRenamed
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,9 +46,7 @@ struct StoreListView: View {
|
|||||||
}
|
}
|
||||||
.frame(minWidth: 100, idealWidth: 240)
|
.frame(minWidth: 100, idealWidth: 240)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension StoreListView {
|
extension StoreListView {
|
||||||
|
Reference in New Issue
Block a user