-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/#64 - 온보딩 Touch ID 인증 및 iCloud 동기화 연동 #66
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
03f0fd2
e923b0a
3f86628
7026851
086f6cf
8173d43
6c05b05
5598e9a
6bcf58c
c71ac4e
7e8d627
4df3308
2698c0f
59fab04
0d19438
cf6b72c
a384934
f409515
0adc5b8
680c27c
6b84665
821137d
58d51e2
371256b
a441e77
02a0a21
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,6 @@ | ||
| [tools] | ||
| tuist = "4.191.0" | ||
|
|
||
| # 이 저장소 안에서만 scripts/의 명령을 접두사 없이 실행할 수 있게 한다. (예: generate-local) | ||
| [env] | ||
| _.path = ["{{config_root}}/scripts"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| // Copyright © 2026 Devault. All rights reserved | ||
|
|
||
| /// Devault.entitlements에 등록된 iCloud(CloudKit) 컨테이너 식별자. | ||
| /// entitlements 파일 자체는 plist라 이 상수를 참조할 수 없으니, 값을 바꿀 땐 함께 맞춰줘야 한다. | ||
| public enum ICloudContainer { | ||
| public static let identifier = "iCloud.com.devault.app" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| // Copyright © 2026 Devault. All rights reserved | ||
|
|
||
| import Foundation | ||
|
|
||
| // 기기 간 동기화가 필요한 설정이 생기면 여기에 케이스를 추가한다. | ||
| //enum UbiquitousStoreKey: String {} | ||
| // | ||
| //extension NSUbiquitousKeyValueStore { | ||
| // func bool(forKey key: UbiquitousStoreKey) -> Bool { | ||
| // bool(forKey: key.rawValue) | ||
| // } | ||
| // | ||
| // func set(_ value: Bool, forKey key: UbiquitousStoreKey) { | ||
| // set(value, forKey: key.rawValue) | ||
| // } | ||
| //} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // Copyright © 2026 Devault. All rights reserved | ||
|
|
||
| import CloudKit | ||
| import DVCore | ||
| import DVDomain | ||
|
|
||
| public struct CloudKitAccountServiceImpl: ICloudAccountService { | ||
|
|
||
| private let container: CKContainer | ||
|
|
||
| /// - Parameter containerIdentifier: entitlements에 등록된 iCloud 컨테이너 식별자 | ||
| public init(containerIdentifier: String) { | ||
| self.container = CKContainer(identifier: containerIdentifier) | ||
| } | ||
|
|
||
| public func fetchAccountStatus() async -> ICloudAccountStatus { | ||
| do { | ||
| switch try await container.accountStatus() { | ||
| case .available: | ||
| return .available | ||
| case .noAccount: | ||
| return .noAccount | ||
| case .restricted: | ||
| return .restricted | ||
| case .temporarilyUnavailable: | ||
| return .temporarilyUnavailable | ||
| case .couldNotDetermine: | ||
| return .couldNotDetermine | ||
| @unknown default: | ||
| return .couldNotDetermine | ||
| } | ||
| } catch let error as CKError { | ||
| switch error.code { | ||
| case .networkUnavailable, .networkFailure: | ||
| return .networkUnavailable | ||
| case .notAuthenticated: | ||
| return .noAccount | ||
| case .badContainer, .missingEntitlement: | ||
| // container identifier 오타나 entitlement 누락 등 배포 설정 문제. 재시도로는 해결되지 않으므로 로그 추가 | ||
| Log.error("iCloud 컨테이너 설정 오류: \(error)", category: .security) | ||
| return .configurationUnavailable | ||
| default: | ||
| return .couldNotDetermine | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } catch { | ||
| return .couldNotDetermine | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,18 +7,18 @@ import Security | |
|
|
||
| struct KeychainKeyStore: Sendable { | ||
| private let service: String | ||
|
|
||
| /// Keychain generic password item을 구분할 service namespace를 설정한다. | ||
| init(service: String) { | ||
| self.service = service | ||
| } | ||
|
|
||
| /// tag에 해당하는 symmetric key를 조회하고, 없으면 새 key를 생성해 Keychain에 저장한다. | ||
| func getOrCreateSymmetricKey(tag: String) throws -> SymmetricKey { | ||
| if let existing = try loadKeyData(tag: tag) { | ||
| return SymmetricKey(data: existing) | ||
| } | ||
|
|
||
| let generated = try generateKeyData() | ||
| let resolved = try saveOrLoadExistingKeyData(generated, tag: tag) | ||
| return SymmetricKey(data: resolved) | ||
|
|
@@ -29,7 +29,7 @@ struct KeychainKeyStore: Sendable { | |
| guard let data = try loadKeyData(tag: tag) else { | ||
| throw SecretCryptoError.keyUnavailable | ||
| } | ||
|
|
||
| return SymmetricKey(data: data) | ||
| } | ||
| } | ||
|
|
@@ -38,6 +38,7 @@ extension KeychainKeyStore { | |
| /// Keychain에서 tag에 해당하는 raw key Data를 조회한다. | ||
| private func loadKeyData(tag: String) throws -> Data? { | ||
| var query = keyQuery(tag: tag) | ||
| query[kSecAttrSynchronizable] = kSecAttrSynchronizableAny | ||
|
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. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 6 \
'getOrCreateSymmetricKey|loadKeyData|saveOrLoadExistingKeyData|SecItem(Update|Add|CopyMatching|Delete)|kSecAttrSynchronizable' \
Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swiftRepository: DevaultProject/Devault-macOS Length of output: 2799 🏁 Script executed: #!/bin/bash
set -euo pipefail
# Inspect the relevant KeychainKeyStore implementation without executing repository code.
sed -n '1,120p' Projects/DVData/Sources/ServiceImpl/Security/Keychain/KeychainKeyStore.swift
# Locate all callers of getOrCreateSymmetricKey to assess write path impact.
rg -n -C 4 'getOrCreateSymmetricKey\(' Projects/DVData/Sources/ServiceImplRepository: DevaultProject/Devault-macOS Length of output: 5925 기존 비동기화 Keychain 항목을 동기화 항목으로 마이그레이션하세요.
🤖 Prompt for AI Agents |
||
| let attributes: [CFString: Any] = [ | ||
| kSecReturnData: true, | ||
| kSecMatchLimit: kSecMatchLimitOne, | ||
|
|
@@ -65,7 +66,8 @@ extension KeychainKeyStore { | |
|
|
||
| let attributes: [CFString: Any] = [ | ||
| kSecValueData: data, | ||
| kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly, | ||
| kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked, | ||
| kSecAttrSynchronizable: true, | ||
| ] | ||
|
|
||
| var addQuery = query | ||
|
|
@@ -93,6 +95,7 @@ extension KeychainKeyStore { | |
| kSecClass: kSecClassGenericPassword, | ||
| kSecAttrService: service, | ||
| kSecAttrAccount: tag, | ||
| kSecUseDataProtectionKeychain: true, | ||
| ] | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| // Copyright © 2026 Devault. All rights reserved | ||
|
|
||
| import DVCore | ||
| import SwiftData | ||
|
|
||
| public final class LocalStorage { | ||
|
|
@@ -9,11 +10,22 @@ public final class LocalStorage { | |
| self.modelContainer = modelContainer | ||
| } | ||
|
|
||
| public static func makeDefault() throws -> LocalStorage { | ||
| let configuration = ModelConfiguration(isStoredInMemoryOnly: false) | ||
| public static func makeDefault(iCloudSyncEnabled: Bool) throws -> LocalStorage { | ||
| let syncedConfiguration = ModelConfiguration( | ||
| "Synced", | ||
| schema: Schema.syncedSchema, | ||
| isStoredInMemoryOnly: false, | ||
| cloudKitDatabase: iCloudSyncEnabled ? .private(ICloudContainer.identifier) : .none | ||
| ) | ||
|
Comment on lines
+13
to
+19
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. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# appSchema에 BackupRecord가 포함되는지와 저장소 구성을 확인합니다.
rg -n -C 4 --glob '*.swift' 'appSchema|BackupRecord|ModelConfiguration|filePath' \
Projects/DVData/Sources Projects/Devault/SourcesRepository: DevaultProject/Devault-macOS Length of output: 5923
📍 Affects 2 files
🤖 Prompt for AI Agents |
||
| let localOnlyConfiguration = ModelConfiguration( | ||
| "LocalOnly", | ||
| schema: Schema.localOnlySchema, | ||
| isStoredInMemoryOnly: false, | ||
| cloudKitDatabase: .none | ||
| ) | ||
| let modelContainer = try ModelContainer( | ||
| for: Schema.appSchema, | ||
| configurations: configuration | ||
| configurations: syncedConfiguration, localOnlyConfiguration | ||
| ) | ||
|
|
||
| return LocalStorage(modelContainer: modelContainer) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.