Add Credential, SampleLocationData, StorageKeys, PrivateNotesKey (+13 more); Remove Credential, SampleLocationData, StorageKeys, AppVersionKey (+13 more)
This commit is contained in:
parent
c32ac4b601
commit
6db8ab5bc4
27
README.md
27
README.md
@ -41,8 +41,19 @@ SharedPackage/
|
|||||||
└── UserProfile.swift
|
└── UserProfile.swift
|
||||||
SecureStorgageSample/
|
SecureStorgageSample/
|
||||||
├── ContentView.swift # Tabbed navigation
|
├── ContentView.swift # Tabbed navigation
|
||||||
|
├── Models/
|
||||||
|
│ ├── Credential.swift
|
||||||
|
│ └── SampleLocationData.swift
|
||||||
├── StorageKeys.swift # 12 example key definitions
|
├── StorageKeys.swift # 12 example key definitions
|
||||||
|
├── StorageKeys/
|
||||||
|
│ ├── UserDefaults/
|
||||||
|
│ ├── Keychain/
|
||||||
|
│ ├── FileSystem/
|
||||||
|
│ ├── EncryptedFileSystem/
|
||||||
|
│ └── Platform/
|
||||||
├── WatchOptimized.swift # Watch data models
|
├── WatchOptimized.swift # Watch data models
|
||||||
|
├── Services/
|
||||||
|
│ └── WatchConnectivityService.swift
|
||||||
└── Views/
|
└── Views/
|
||||||
├── UserDefaultsDemo.swift
|
├── UserDefaultsDemo.swift
|
||||||
├── KeychainDemo.swift
|
├── KeychainDemo.swift
|
||||||
@ -52,8 +63,16 @@ SecureStorgageSample/
|
|||||||
SecureStorageSample Watch App/
|
SecureStorageSample Watch App/
|
||||||
├── SecureStorageSampleApp.swift
|
├── SecureStorageSampleApp.swift
|
||||||
├── ContentView.swift
|
├── ContentView.swift
|
||||||
|
├── Models/
|
||||||
|
│ └── UserProfile.swift
|
||||||
|
├── Protocols/
|
||||||
|
│ └── WatchDataHandling.swift
|
||||||
|
├── State/
|
||||||
|
│ └── WatchProfileStore.swift
|
||||||
└── Services/
|
└── Services/
|
||||||
└── WatchConnectivityService.swift
|
├── WatchConnectivityService.swift
|
||||||
|
└── Handlers/
|
||||||
|
└── UserProfileWatchHandler.swift
|
||||||
```
|
```
|
||||||
|
|
||||||
## Storage Key Examples
|
## Storage Key Examples
|
||||||
@ -87,6 +106,12 @@ The app demonstrates various storage configurations:
|
|||||||
- [LocalData](../localPackages/LocalData) - Local package for typed secure storage
|
- [LocalData](../localPackages/LocalData) - Local package for typed secure storage
|
||||||
- SharedKit - Local package for shared iOS/watch models and constants
|
- SharedKit - Local package for shared iOS/watch models and constants
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Storage keys are now split into one file per key and grouped by domain; platform-focused keys live in `StorageKeys/Platform` with comments calling out availability/sync focus.
|
||||||
|
- The shared model/constants live in `SharedPackage` (`SharedKit`) to keep the watch/iOS data contract centralized.
|
||||||
|
- The watch app uses a handler-based WatchConnectivity layer so new payload types can be added in `Services/Handlers` without bloating the main service.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This sample is provided for demonstration purposes.
|
This sample is provided for demonstration purposes.
|
||||||
|
|||||||
@ -9,6 +9,8 @@
|
|||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
EA179D562F17379800B1D54A /* LocalData in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D552F17379800B1D54A /* LocalData */; };
|
EA179D562F17379800B1D54A /* LocalData in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D552F17379800B1D54A /* LocalData */; };
|
||||||
EA65D70D2F17DDEB00C48466 /* SecureStorageSample Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
EA65D70D2F17DDEB00C48466 /* SecureStorageSample Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||||
|
EA65D9442F17EAD800C48466 /* SharedKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA65D7312F17DDEB00C48466 /* SharedKit */; };
|
||||||
|
EA65D9452F17EAD800C48466 /* SharedKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA65D7312F17DDEB00C48466 /* SharedKit */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -110,6 +112,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
EA65D9442F17EAD800C48466 /* SharedKit in Frameworks */,
|
||||||
EA179D562F17379800B1D54A /* LocalData in Frameworks */,
|
EA179D562F17379800B1D54A /* LocalData in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
@ -118,6 +121,7 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
EA65D9452F17EAD800C48466 /* SharedKit in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
8
SecureStorgageSample/Models/Credential.swift
Normal file
8
SecureStorgageSample/Models/Credential.swift
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Simple credential model for keychain storage demo.
|
||||||
|
nonisolated
|
||||||
|
struct Credential: Codable, Sendable {
|
||||||
|
let username: String
|
||||||
|
let password: String
|
||||||
|
}
|
||||||
8
SecureStorgageSample/Models/SampleLocationData.swift
Normal file
8
SecureStorgageSample/Models/SampleLocationData.swift
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import Foundation
|
||||||
|
|
||||||
|
/// Location data model.
|
||||||
|
nonisolated
|
||||||
|
struct SampleLocationData: Codable, Sendable {
|
||||||
|
let lat: Double
|
||||||
|
let lon: Double
|
||||||
|
}
|
||||||
@ -1,271 +0,0 @@
|
|||||||
//
|
|
||||||
// StorageKeys.swift
|
|
||||||
// SecureStorgageSample
|
|
||||||
//
|
|
||||||
// Example StorageKey implementations demonstrating all variations
|
|
||||||
// supported by the LocalData package.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import LocalData
|
|
||||||
import SharedKit
|
|
||||||
|
|
||||||
// MARK: - Sample Data Models
|
|
||||||
|
|
||||||
/// Simple credential model for keychain storage demo.
|
|
||||||
nonisolated
|
|
||||||
struct Credential: Codable, Sendable {
|
|
||||||
let username: String
|
|
||||||
let password: String
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Location data model.
|
|
||||||
nonisolated
|
|
||||||
struct SampleLocationData: Codable, Sendable {
|
|
||||||
let lat: Double
|
|
||||||
let lon: Double
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - UserDefaults Keys
|
|
||||||
|
|
||||||
extension StorageKeys {
|
|
||||||
|
|
||||||
/// Stores the app version in standard UserDefaults.
|
|
||||||
/// - Domain: UserDefaults (standard)
|
|
||||||
/// - Security: None
|
|
||||||
/// - Sync: Automatic for small data
|
|
||||||
struct AppVersionKey: StorageKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
let name = "last_app_version"
|
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<String> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .automaticSmall
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores user preferences in a custom suite.
|
|
||||||
/// - Domain: UserDefaults (custom suite)
|
|
||||||
/// - Security: None
|
|
||||||
/// - Sync: Never
|
|
||||||
struct UserPreferencesKey: StorageKey {
|
|
||||||
typealias Value = [String: AnyCodable]
|
|
||||||
|
|
||||||
let name = "user_preferences"
|
|
||||||
let domain: StorageDomain = .userDefaults(suite: "group.com.example.securestorage")
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<[String: AnyCodable]> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Keychain Keys
|
|
||||||
|
|
||||||
extension StorageKeys {
|
|
||||||
|
|
||||||
/// Stores user credentials securely in keychain.
|
|
||||||
/// Configurable accessibility and access control.
|
|
||||||
struct CredentialsKey: StorageKey {
|
|
||||||
typealias Value = Credential
|
|
||||||
|
|
||||||
let name = "user_credentials"
|
|
||||||
let domain: StorageDomain = .keychain(service: "com.example.securestorage")
|
|
||||||
let security: SecurityPolicy
|
|
||||||
let serializer: Serializer<Credential> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
|
|
||||||
init(accessibility: KeychainAccessibility = .afterFirstUnlock, accessControl: KeychainAccessControl? = nil) {
|
|
||||||
self.security = .keychain(accessibility: accessibility, accessControl: accessControl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores sensitive location data in keychain with biometric protection.
|
|
||||||
struct LastLocationKey: StorageKey {
|
|
||||||
typealias Value = SampleLocationData
|
|
||||||
|
|
||||||
let name = "last_known_location"
|
|
||||||
let domain: StorageDomain = .keychain(service: "com.example.app.security")
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .afterFirstUnlock,
|
|
||||||
accessControl: .userPresence
|
|
||||||
)
|
|
||||||
let serializer: Serializer<SampleLocationData> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores API token in keychain.
|
|
||||||
struct APITokenKey: StorageKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
let name = "api_token"
|
|
||||||
let domain: StorageDomain = .keychain(service: "com.example.securestorage.api")
|
|
||||||
let security: SecurityPolicy = .keychain(
|
|
||||||
accessibility: .whenUnlockedThisDeviceOnly,
|
|
||||||
accessControl: nil
|
|
||||||
)
|
|
||||||
let serializer: Serializer<String> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - File System Keys
|
|
||||||
|
|
||||||
extension StorageKeys {
|
|
||||||
|
|
||||||
/// Stores user profile as JSON file in documents.
|
|
||||||
struct UserProfileFileKey: StorageKey {
|
|
||||||
typealias Value = UserProfile
|
|
||||||
|
|
||||||
let name = UserProfile.storageKeyName
|
|
||||||
let domain: StorageDomain
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<UserProfile> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .phoneWithWatchSync
|
|
||||||
let syncPolicy: SyncPolicy = .automaticSmall
|
|
||||||
|
|
||||||
init(directory: FileDirectory = .documents) {
|
|
||||||
self.domain = .fileSystem(directory: directory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores cached data files.
|
|
||||||
struct CachedDataKey: StorageKey {
|
|
||||||
typealias Value = Data
|
|
||||||
|
|
||||||
let name = "cached_data.bin"
|
|
||||||
let domain: StorageDomain = .fileSystem(directory: .caches)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<Data> = .data
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores settings as property list.
|
|
||||||
struct SettingsPlistKey: StorageKey {
|
|
||||||
typealias Value = [String: AnyCodable]
|
|
||||||
|
|
||||||
let name = "settings.plist"
|
|
||||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<[String: AnyCodable]> = .plist
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Encrypted File System Keys
|
|
||||||
|
|
||||||
extension StorageKeys {
|
|
||||||
|
|
||||||
/// Stores session logs with full encryption.
|
|
||||||
/// Configurable PBKDF2 iterations.
|
|
||||||
struct SessionLogsKey: StorageKey {
|
|
||||||
typealias Value = [String]
|
|
||||||
|
|
||||||
let name = "session_logs.json"
|
|
||||||
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
|
|
||||||
let security: SecurityPolicy
|
|
||||||
let serializer: Serializer<[String]> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
|
|
||||||
init(iterations: Int = 10_000) {
|
|
||||||
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stores private notes with encryption.
|
|
||||||
struct PrivateNotesKey: StorageKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
let name = "private_notes.enc"
|
|
||||||
let domain: StorageDomain = .encryptedFileSystem(directory: .documents)
|
|
||||||
let security: SecurityPolicy = .encrypted(
|
|
||||||
.aes256(keyDerivation: .pbkdf2(iterations: 50_000))
|
|
||||||
)
|
|
||||||
let serializer: Serializer<String> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .phoneOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Platform-Specific Keys
|
|
||||||
|
|
||||||
extension StorageKeys {
|
|
||||||
|
|
||||||
/// Watch-only setting for vibration.
|
|
||||||
struct WatchVibrationKey: StorageKey {
|
|
||||||
typealias Value = Bool
|
|
||||||
|
|
||||||
let name = "watch_vibration_enabled"
|
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<Bool> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .watchOnly
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Syncable setting with configurable platform and sync policy.
|
|
||||||
struct SyncableSettingKey: StorageKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
let name = "syncable_setting"
|
|
||||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<String> = .json
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability
|
|
||||||
let syncPolicy: SyncPolicy
|
|
||||||
|
|
||||||
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) {
|
|
||||||
self.availability = availability
|
|
||||||
self.syncPolicy = syncPolicy
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Custom Serializer Example
|
|
||||||
|
|
||||||
extension StorageKeys {
|
|
||||||
|
|
||||||
/// Example using custom serializer for specialized encoding.
|
|
||||||
struct CustomEncodedKey: StorageKey {
|
|
||||||
typealias Value = String
|
|
||||||
|
|
||||||
let name = "custom_encoded"
|
|
||||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
|
||||||
let security: SecurityPolicy = .none
|
|
||||||
let serializer: Serializer<String> = .custom(
|
|
||||||
encode: { value in
|
|
||||||
// Example: Base64 encode
|
|
||||||
Data(value.utf8).base64EncodedData()
|
|
||||||
},
|
|
||||||
decode: { data in
|
|
||||||
guard let decoded = Data(base64Encoded: data),
|
|
||||||
let string = String(data: decoded, encoding: .utf8) else {
|
|
||||||
throw StorageError.deserializationFailed
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
)
|
|
||||||
let owner = "SampleApp"
|
|
||||||
let availability: PlatformAvailability = .all
|
|
||||||
let syncPolicy: SyncPolicy = .never
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores private notes with encryption.
|
||||||
|
struct PrivateNotesKey: StorageKey {
|
||||||
|
typealias Value = String
|
||||||
|
|
||||||
|
let name = "private_notes.enc"
|
||||||
|
let domain: StorageDomain = .encryptedFileSystem(directory: .documents)
|
||||||
|
let security: SecurityPolicy = .encrypted(
|
||||||
|
.aes256(keyDerivation: .pbkdf2(iterations: 50_000))
|
||||||
|
)
|
||||||
|
let serializer: Serializer<String> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .phoneOnly
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores session logs with full encryption.
|
||||||
|
/// Configurable PBKDF2 iterations.
|
||||||
|
struct SessionLogsKey: StorageKey {
|
||||||
|
typealias Value = [String]
|
||||||
|
|
||||||
|
let name = "session_logs.json"
|
||||||
|
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
|
||||||
|
let security: SecurityPolicy
|
||||||
|
let serializer: Serializer<[String]> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .phoneOnly
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
|
||||||
|
init(iterations: Int = 10_000) {
|
||||||
|
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores cached data files.
|
||||||
|
struct CachedDataKey: StorageKey {
|
||||||
|
typealias Value = Data
|
||||||
|
|
||||||
|
let name = "cached_data.bin"
|
||||||
|
let domain: StorageDomain = .fileSystem(directory: .caches)
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<Data> = .data
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .all
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Example using custom serializer for specialized encoding.
|
||||||
|
struct CustomEncodedKey: StorageKey {
|
||||||
|
typealias Value = String
|
||||||
|
|
||||||
|
let name = "custom_encoded"
|
||||||
|
let domain: StorageDomain = .fileSystem(directory: .documents)
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<String> = .custom(
|
||||||
|
encode: { value in
|
||||||
|
Data(value.utf8).base64EncodedData()
|
||||||
|
},
|
||||||
|
decode: { data in
|
||||||
|
guard let decoded = Data(base64Encoded: data),
|
||||||
|
let string = String(data: decoded, encoding: .utf8) else {
|
||||||
|
throw StorageError.deserializationFailed
|
||||||
|
}
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
)
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .all
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores settings as property list.
|
||||||
|
struct SettingsPlistKey: StorageKey {
|
||||||
|
typealias Value = [String: AnyCodable]
|
||||||
|
|
||||||
|
let name = "settings.plist"
|
||||||
|
let domain: StorageDomain = .fileSystem(directory: .documents)
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<[String: AnyCodable]> = .plist
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .all
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
import SharedKit
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores user profile as JSON file in documents.
|
||||||
|
struct UserProfileFileKey: StorageKey {
|
||||||
|
typealias Value = UserProfile
|
||||||
|
|
||||||
|
let name = UserProfile.storageKeyName
|
||||||
|
let domain: StorageDomain
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<UserProfile> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .phoneWithWatchSync
|
||||||
|
let syncPolicy: SyncPolicy = .automaticSmall
|
||||||
|
|
||||||
|
init(directory: FileDirectory = .documents) {
|
||||||
|
self.domain = .fileSystem(directory: directory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
20
SecureStorgageSample/StorageKeys/Keychain/APITokenKey.swift
Normal file
20
SecureStorgageSample/StorageKeys/Keychain/APITokenKey.swift
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores API token in keychain.
|
||||||
|
struct APITokenKey: StorageKey {
|
||||||
|
typealias Value = String
|
||||||
|
|
||||||
|
let name = "api_token"
|
||||||
|
let domain: StorageDomain = .keychain(service: "com.example.securestorage.api")
|
||||||
|
let security: SecurityPolicy = .keychain(
|
||||||
|
accessibility: .whenUnlockedThisDeviceOnly,
|
||||||
|
accessControl: nil
|
||||||
|
)
|
||||||
|
let serializer: Serializer<String> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .phoneOnly
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores user credentials securely in keychain.
|
||||||
|
/// Configurable accessibility and access control.
|
||||||
|
struct CredentialsKey: StorageKey {
|
||||||
|
typealias Value = Credential
|
||||||
|
|
||||||
|
let name = "user_credentials"
|
||||||
|
let domain: StorageDomain = .keychain(service: "com.example.securestorage")
|
||||||
|
let security: SecurityPolicy
|
||||||
|
let serializer: Serializer<Credential> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .phoneOnly
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
|
||||||
|
init(accessibility: KeychainAccessibility = .afterFirstUnlock, accessControl: KeychainAccessControl? = nil) {
|
||||||
|
self.security = .keychain(accessibility: accessibility, accessControl: accessControl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores sensitive location data in keychain with biometric protection.
|
||||||
|
struct LastLocationKey: StorageKey {
|
||||||
|
typealias Value = SampleLocationData
|
||||||
|
|
||||||
|
let name = "last_known_location"
|
||||||
|
let domain: StorageDomain = .keychain(service: "com.example.app.security")
|
||||||
|
let security: SecurityPolicy = .keychain(
|
||||||
|
accessibility: .afterFirstUnlock,
|
||||||
|
accessControl: .userPresence
|
||||||
|
)
|
||||||
|
let serializer: Serializer<SampleLocationData> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .phoneOnly
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Syncable setting with configurable platform and sync policy.
|
||||||
|
/// Grouped under Platform to highlight availability/sync behavior.
|
||||||
|
struct SyncableSettingKey: StorageKey {
|
||||||
|
typealias Value = String
|
||||||
|
|
||||||
|
let name = "syncable_setting"
|
||||||
|
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<String> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability
|
||||||
|
let syncPolicy: SyncPolicy
|
||||||
|
|
||||||
|
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) {
|
||||||
|
self.availability = availability
|
||||||
|
self.syncPolicy = syncPolicy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,18 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Watch-only setting for vibration.
|
||||||
|
/// Grouped under Platform to highlight watch-only availability.
|
||||||
|
struct WatchVibrationKey: StorageKey {
|
||||||
|
typealias Value = Bool
|
||||||
|
|
||||||
|
let name = "watch_vibration_enabled"
|
||||||
|
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<Bool> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .watchOnly
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores the app version in standard UserDefaults.
|
||||||
|
/// - Domain: UserDefaults (standard)
|
||||||
|
/// - Security: None
|
||||||
|
/// - Sync: Automatic for small data
|
||||||
|
struct AppVersionKey: StorageKey {
|
||||||
|
typealias Value = String
|
||||||
|
|
||||||
|
let name = "last_app_version"
|
||||||
|
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<String> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .all
|
||||||
|
let syncPolicy: SyncPolicy = .automaticSmall
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
import Foundation
|
||||||
|
import LocalData
|
||||||
|
|
||||||
|
extension StorageKeys {
|
||||||
|
/// Stores user preferences in a custom suite.
|
||||||
|
/// - Domain: UserDefaults (custom suite)
|
||||||
|
/// - Security: None
|
||||||
|
/// - Sync: Never
|
||||||
|
struct UserPreferencesKey: StorageKey {
|
||||||
|
typealias Value = [String: AnyCodable]
|
||||||
|
|
||||||
|
let name = "user_preferences"
|
||||||
|
let domain: StorageDomain = .userDefaults(suite: "group.com.example.securestorage")
|
||||||
|
let security: SecurityPolicy = .none
|
||||||
|
let serializer: Serializer<[String: AnyCodable]> = .json
|
||||||
|
let owner = "SampleApp"
|
||||||
|
let availability: PlatformAvailability = .all
|
||||||
|
let syncPolicy: SyncPolicy = .never
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user