mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-21 15:50:59 +00:00
Modularize Keychain and Secrets
This commit is contained in:
parent
06b84a0aa7
commit
f6f065e143
18 changed files with 173 additions and 62 deletions
5
Keychain/.gitignore
vendored
Normal file
5
Keychain/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
31
Keychain/Package.swift
Normal file
31
Keychain/Package.swift
Normal file
|
@ -0,0 +1,31 @@
|
|||
// swift-tools-version:5.3
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Keychain",
|
||||
platforms: [
|
||||
.iOS(.v14),
|
||||
.macOS(.v11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Keychain",
|
||||
targets: ["Keychain"]),
|
||||
.library(
|
||||
name: "MockKeychain",
|
||||
targets: ["MockKeychain"])
|
||||
],
|
||||
dependencies: [],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Keychain",
|
||||
dependencies: []),
|
||||
.target(
|
||||
name: "MockKeychain",
|
||||
dependencies: ["Keychain"]),
|
||||
.testTarget(
|
||||
name: "KeychainTests",
|
||||
dependencies: ["MockKeychain"])
|
||||
]
|
||||
)
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public protocol KeychainService {
|
||||
public protocol Keychain {
|
||||
static func setGenericPassword(data: Data, forAccount key: String, service: String) throws
|
||||
static func deleteGenericPassword(account: String, service: String) throws
|
||||
static func getGenericPassword(account: String, service: String) throws -> Data?
|
||||
|
@ -11,9 +11,9 @@ public protocol KeychainService {
|
|||
static func deleteKey(applicationTag: String) throws
|
||||
}
|
||||
|
||||
public struct LiveKeychainService {}
|
||||
public struct LiveKeychain {}
|
||||
|
||||
extension LiveKeychainService: KeychainService {
|
||||
extension LiveKeychain: Keychain {
|
||||
public static func setGenericPassword(data: Data, forAccount account: String, service: String) throws {
|
||||
var query = genericPasswordQueryDictionary(account: account, service: service)
|
||||
|
||||
|
@ -115,7 +115,7 @@ extension LiveKeychainService: KeychainService {
|
|||
}
|
||||
}
|
||||
|
||||
private extension LiveKeychainService {
|
||||
private extension LiveKeychain {
|
||||
static func genericPasswordQueryDictionary(account: String, service: String) -> [String: Any] {
|
||||
[kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: account,
|
|
@ -1,17 +1,17 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import ServiceLayer
|
||||
import Keychain
|
||||
|
||||
public struct MockKeychainService {}
|
||||
public struct MockKeychain {}
|
||||
|
||||
public extension MockKeychainService {
|
||||
public extension MockKeychain {
|
||||
static func reset() {
|
||||
items = [String: Data]()
|
||||
}
|
||||
}
|
||||
|
||||
extension MockKeychainService: KeychainService {
|
||||
extension MockKeychain: Keychain {
|
||||
public static func setGenericPassword(data: Data, forAccount key: String, service: String) throws {
|
||||
items[key] = data
|
||||
}
|
||||
|
@ -37,6 +37,6 @@ extension MockKeychainService: KeychainService {
|
|||
}
|
||||
}
|
||||
|
||||
private extension MockKeychainService {
|
||||
private extension MockKeychain {
|
||||
static var items = [String: Data]()
|
||||
}
|
10
Keychain/Tests/KeychainTests/KeychainTests.swift
Normal file
10
Keychain/Tests/KeychainTests/KeychainTests.swift
Normal file
|
@ -0,0 +1,10 @@
|
|||
import XCTest
|
||||
@testable import Keychain
|
||||
|
||||
final class KeychainTests: XCTestCase {
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
}
|
||||
}
|
|
@ -7,18 +7,19 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
D0175CAC24FE2D6300B085F6 /* PreviewViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0175CAB24FE2D6300B085F6 /* PreviewViewModels */; };
|
||||
D01F41D724F880C400D55A2D /* StatusTableViewCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = D01F41D424F880C400D55A2D /* StatusTableViewCell.xib */; };
|
||||
D01F41D824F880C400D55A2D /* StatusTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D524F880C400D55A2D /* StatusTableViewCell.swift */; };
|
||||
D01F41D924F880C400D55A2D /* TouchFallthroughTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41D624F880C400D55A2D /* TouchFallthroughTextView.swift */; };
|
||||
D01F41E424F8889700D55A2D /* AttachmentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F41E224F8889700D55A2D /* AttachmentsView.swift */; };
|
||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */ = {isa = PBXBuildFile; productRef = D06B492224D4611300642749 /* KingfisherSwiftUI */; };
|
||||
D0BDF66B24FD7CEC00C7FA1C /* ServiceLayer in Frameworks */ = {isa = PBXBuildFile; productRef = D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */; };
|
||||
D0BEB1F324F8EE8C001B0F04 /* AttachmentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F224F8EE8C001B0F04 /* AttachmentView.swift */; };
|
||||
D0BEB1F724F9A84B001B0F04 /* LoadingTableFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1F624F9A84B001B0F04 /* LoadingTableFooterView.swift */; };
|
||||
D0BEB1FF24F9E5BB001B0F04 /* ListsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */; };
|
||||
D0BEB20524FA1107001B0F04 /* FiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB20424FA1107001B0F04 /* FiltersView.swift */; };
|
||||
D0BEB21124FA2A91001B0F04 /* EditFilterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */; };
|
||||
D0BECB982501C0FC002C1B13 /* Secrets in Frameworks */ = {isa = PBXBuildFile; productRef = D0BECB972501C0FC002C1B13 /* Secrets */; };
|
||||
D0BECB9A2501C15F002C1B13 /* Mastodon in Frameworks */ = {isa = PBXBuildFile; productRef = D0BECB992501C15F002C1B13 /* Mastodon */; };
|
||||
D0BECB9C2501C731002C1B13 /* PreviewViewModels in Frameworks */ = {isa = PBXBuildFile; productRef = D0BECB9B2501C731002C1B13 /* PreviewViewModels */; };
|
||||
D0C7D49724F7616A001EBDBB /* IdentitiesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42224F76169001EBDBB /* IdentitiesView.swift */; };
|
||||
D0C7D49824F7616A001EBDBB /* CustomEmojiText.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42324F76169001EBDBB /* CustomEmojiText.swift */; };
|
||||
D0C7D49924F7616A001EBDBB /* AddIdentityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C7D42424F76169001EBDBB /* AddIdentityView.swift */; };
|
||||
|
@ -92,6 +93,8 @@
|
|||
D0BEB1FE24F9E5BB001B0F04 /* ListsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListsView.swift; sourceTree = "<group>"; };
|
||||
D0BEB20424FA1107001B0F04 /* FiltersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FiltersView.swift; sourceTree = "<group>"; };
|
||||
D0BEB21024FA2A90001B0F04 /* EditFilterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditFilterView.swift; sourceTree = "<group>"; };
|
||||
D0BECB952501B3DD002C1B13 /* Keychain */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Keychain; sourceTree = "<group>"; };
|
||||
D0BECB962501BCE0002C1B13 /* Secrets */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Secrets; sourceTree = "<group>"; };
|
||||
D0BFDAF524FC7C5300C86618 /* HTTP */ = {isa = PBXFileReference; lastKnownFileType = folder; path = HTTP; sourceTree = "<group>"; };
|
||||
D0C7D41E24F76169001EBDBB /* Metatext.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Metatext.entitlements; sourceTree = "<group>"; };
|
||||
D0C7D41F24F76169001EBDBB /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
|
@ -132,7 +135,7 @@
|
|||
files = (
|
||||
D06B492324D4611300642749 /* KingfisherSwiftUI in Frameworks */,
|
||||
D0E2C1D124FD97F000854680 /* ViewModels in Frameworks */,
|
||||
D0175CAC24FE2D6300B085F6 /* PreviewViewModels in Frameworks */,
|
||||
D0BECB9C2501C731002C1B13 /* PreviewViewModels in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -147,7 +150,8 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
D0BDF66B24FD7CEC00C7FA1C /* ServiceLayer in Frameworks */,
|
||||
D0BECB982501C0FC002C1B13 /* Secrets in Frameworks */,
|
||||
D0BECB9A2501C15F002C1B13 /* Mastodon in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -181,10 +185,12 @@
|
|||
D0C7D46824F76169001EBDBB /* Extensions */,
|
||||
D0666A7924C7745A00F3F04B /* Frameworks */,
|
||||
D0BFDAF524FC7C5300C86618 /* HTTP */,
|
||||
D0BECB952501B3DD002C1B13 /* Keychain */,
|
||||
D0C7D45624F76169001EBDBB /* Localizations */,
|
||||
D0E0F1E424FC49FC002C04BF /* Mastodon */,
|
||||
D0E5361A24E3EB4D00FB1CE1 /* Notification Service Extension */,
|
||||
D047FA8D24C3E21200AF17C5 /* Products */,
|
||||
D0BECB962501BCE0002C1B13 /* Secrets */,
|
||||
D0BDF66524FD7A6400C7FA1C /* ServiceLayer */,
|
||||
D0C7D41D24F76169001EBDBB /* Supporting Files */,
|
||||
D0C7D45324F76169001EBDBB /* System */,
|
||||
|
@ -323,7 +329,7 @@
|
|||
packageProductDependencies = (
|
||||
D06B492224D4611300642749 /* KingfisherSwiftUI */,
|
||||
D0E2C1D024FD97F000854680 /* ViewModels */,
|
||||
D0175CAB24FE2D6300B085F6 /* PreviewViewModels */,
|
||||
D0BECB9B2501C731002C1B13 /* PreviewViewModels */,
|
||||
);
|
||||
productName = "Metatext (iOS)";
|
||||
productReference = D047FA8C24C3E21200AF17C5 /* Metatext.app */;
|
||||
|
@ -363,7 +369,8 @@
|
|||
);
|
||||
name = "Notification Service Extension";
|
||||
packageProductDependencies = (
|
||||
D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */,
|
||||
D0BECB972501C0FC002C1B13 /* Secrets */,
|
||||
D0BECB992501C15F002C1B13 /* Mastodon */,
|
||||
);
|
||||
productName = "Notification Service Extension";
|
||||
productReference = D0E5361924E3EB4D00FB1CE1 /* Notification Service Extension.appex */;
|
||||
|
@ -841,18 +848,22 @@
|
|||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
D0175CAB24FE2D6300B085F6 /* PreviewViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PreviewViewModels;
|
||||
};
|
||||
D06B492224D4611300642749 /* KingfisherSwiftUI */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = D06B492124D4611300642749 /* XCRemoteSwiftPackageReference "Kingfisher" */;
|
||||
productName = KingfisherSwiftUI;
|
||||
};
|
||||
D0BDF66A24FD7CEC00C7FA1C /* ServiceLayer */ = {
|
||||
D0BECB972501C0FC002C1B13 /* Secrets */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = ServiceLayer;
|
||||
productName = Secrets;
|
||||
};
|
||||
D0BECB992501C15F002C1B13 /* Mastodon */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = Mastodon;
|
||||
};
|
||||
D0BECB9B2501C731002C1B13 /* PreviewViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
productName = PreviewViewModels;
|
||||
};
|
||||
D0E2C1D024FD97F000854680 /* ViewModels */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
|
||||
import UserNotifications
|
||||
import CryptoKit
|
||||
import Keychain
|
||||
import Mastodon
|
||||
import ServiceLayer
|
||||
import Secrets
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
|
@ -91,7 +92,7 @@ private extension NotificationService {
|
|||
let serverPublicKeyData = Data(base64Encoded: serverPublicKeyBase64)
|
||||
else { throw NotificationServiceError.userInfoDataAbsent }
|
||||
|
||||
let secretsService = SecretsService(identityID: identityID, keychainService: LiveKeychainService.self)
|
||||
let secretsService = Secrets(identityID: identityID, keychain: LiveKeychain.self)
|
||||
|
||||
guard
|
||||
let auth = try secretsService.getPushAuth(),
|
||||
|
|
5
Secrets/.gitignore
vendored
Normal file
5
Secrets/.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
.DS_Store
|
||||
/.build
|
||||
/Packages
|
||||
/*.xcodeproj
|
||||
xcuserdata/
|
27
Secrets/Package.swift
Normal file
27
Secrets/Package.swift
Normal file
|
@ -0,0 +1,27 @@
|
|||
// swift-tools-version:5.3
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let package = Package(
|
||||
name: "Secrets",
|
||||
platforms: [
|
||||
.iOS(.v14),
|
||||
.macOS(.v11)
|
||||
],
|
||||
products: [
|
||||
.library(
|
||||
name: "Secrets",
|
||||
targets: ["Secrets"])
|
||||
],
|
||||
dependencies: [
|
||||
.package(path: "Keychain")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "Secrets",
|
||||
dependencies: ["Keychain"]),
|
||||
.testTarget(
|
||||
name: "SecretsTests",
|
||||
dependencies: ["Secrets"])
|
||||
]
|
||||
)
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © 2020 Metabolist. All rights reserved.
|
||||
|
||||
import Foundation
|
||||
import Keychain
|
||||
|
||||
public protocol SecretsStorable {
|
||||
var dataStoredInSecrets: Data { get }
|
||||
|
@ -11,17 +12,17 @@ enum SecretsStorableError: Error {
|
|||
case conversionFromDataStoredInSecrets(Data)
|
||||
}
|
||||
|
||||
public struct SecretsService {
|
||||
public struct Secrets {
|
||||
public let identityID: UUID
|
||||
private let keychainService: KeychainService.Type
|
||||
private let keychain: Keychain.Type
|
||||
|
||||
public init(identityID: UUID, keychainService: KeychainService.Type) {
|
||||
public init(identityID: UUID, keychain: Keychain.Type) {
|
||||
self.identityID = identityID
|
||||
self.keychainService = keychainService
|
||||
self.keychain = keychain
|
||||
}
|
||||
}
|
||||
|
||||
public extension SecretsService {
|
||||
public extension Secrets {
|
||||
enum Item: String, CaseIterable {
|
||||
case clientID
|
||||
case clientSecret
|
||||
|
@ -35,7 +36,7 @@ enum SecretsServiceError: Error {
|
|||
case itemAbsent
|
||||
}
|
||||
|
||||
extension SecretsService.Item {
|
||||
extension Secrets.Item {
|
||||
enum Kind {
|
||||
case genericPassword
|
||||
case key
|
||||
|
@ -49,16 +50,16 @@ extension SecretsService.Item {
|
|||
}
|
||||
}
|
||||
|
||||
public extension SecretsService {
|
||||
public extension Secrets {
|
||||
func set(_ data: SecretsStorable, forItem item: Item) throws {
|
||||
try keychainService.setGenericPassword(
|
||||
try keychain.setGenericPassword(
|
||||
data: data.dataStoredInSecrets,
|
||||
forAccount: key(item: item),
|
||||
service: Self.keychainServiceName)
|
||||
}
|
||||
|
||||
func item<T: SecretsStorable>(_ item: Item) throws -> T {
|
||||
guard let data = try keychainService.getGenericPassword(
|
||||
guard let data = try keychain.getGenericPassword(
|
||||
account: key(item: item),
|
||||
service: Self.keychainServiceName) else {
|
||||
throw SecretsServiceError.itemAbsent
|
||||
|
@ -68,26 +69,26 @@ public extension SecretsService {
|
|||
}
|
||||
|
||||
func deleteAllItems() throws {
|
||||
for item in SecretsService.Item.allCases {
|
||||
for item in Secrets.Item.allCases {
|
||||
switch item.kind {
|
||||
case .genericPassword:
|
||||
try keychainService.deleteGenericPassword(
|
||||
try keychain.deleteGenericPassword(
|
||||
account: key(item: item),
|
||||
service: Self.keychainServiceName)
|
||||
case .key:
|
||||
try keychainService.deleteKey(applicationTag: key(item: item))
|
||||
try keychain.deleteKey(applicationTag: key(item: item))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func generatePushKeyAndReturnPublicKey() throws -> Data {
|
||||
try keychainService.generateKeyAndReturnPublicKey(
|
||||
try keychain.generateKeyAndReturnPublicKey(
|
||||
applicationTag: key(item: .pushKey),
|
||||
attributes: PushKey.attributes)
|
||||
}
|
||||
|
||||
func getPushKey() throws -> Data? {
|
||||
try keychainService.getPrivateKey(
|
||||
try keychain.getPrivateKey(
|
||||
applicationTag: key(item: .pushKey),
|
||||
attributes: PushKey.attributes)
|
||||
}
|
||||
|
@ -109,7 +110,7 @@ public extension SecretsService {
|
|||
}
|
||||
}
|
||||
|
||||
private extension SecretsService {
|
||||
private extension Secrets {
|
||||
static let keychainServiceName = "com.metabolist.metatext"
|
||||
|
||||
func key(item: Item) -> String {
|
10
Secrets/Tests/SecretsTests/SecretsTests.swift
Normal file
10
Secrets/Tests/SecretsTests/SecretsTests.swift
Normal file
|
@ -0,0 +1,10 @@
|
|||
import XCTest
|
||||
@testable import Secrets
|
||||
|
||||
final class SecretsTests: XCTestCase {
|
||||
func testExample() {
|
||||
// This is an example of a functional test case.
|
||||
// Use XCTAssert and related functions to verify your tests produce the correct
|
||||
// results.
|
||||
}
|
||||
}
|
|
@ -19,15 +19,20 @@ let package = Package(
|
|||
dependencies: [
|
||||
.package(url: "https://github.com/groue/CombineExpectations.git", .upToNextMajor(from: "0.5.0")),
|
||||
.package(path: "DB"),
|
||||
.package(path: "Mastodon")
|
||||
.package(path: "Keychain"),
|
||||
.package(path: "Mastodon"),
|
||||
.package(path: "Secrets")
|
||||
],
|
||||
targets: [
|
||||
.target(
|
||||
name: "ServiceLayer",
|
||||
dependencies: ["DB"]),
|
||||
dependencies: ["DB", "Secrets"]),
|
||||
.target(
|
||||
name: "ServiceLayerMocks",
|
||||
dependencies: ["ServiceLayer", .product(name: "MastodonStubs", package: "Mastodon")]),
|
||||
dependencies: [
|
||||
"ServiceLayer",
|
||||
.product(name: "MastodonStubs", package: "Mastodon"),
|
||||
.product(name: "MockKeychain", package: "Keychain")]),
|
||||
.testTarget(
|
||||
name: "ServiceLayerTests",
|
||||
dependencies: ["CombineExpectations", "ServiceLayerMocks"])
|
||||
|
|
|
@ -4,6 +4,7 @@ import DB
|
|||
import Foundation
|
||||
import Combine
|
||||
import Mastodon
|
||||
import Secrets
|
||||
|
||||
public struct AllIdentitiesService {
|
||||
public let mostRecentlyUsedIdentityID: AnyPublisher<UUID?, Never>
|
||||
|
@ -34,24 +35,24 @@ public extension AllIdentitiesService {
|
|||
}
|
||||
|
||||
func authorizeIdentity(id: UUID, instanceURL: URL) -> AnyPublisher<Never, Error> {
|
||||
let secretsService = SecretsService(identityID: id, keychainService: environment.keychainServiceType)
|
||||
let secrets = Secrets(identityID: id, keychain: environment.keychain)
|
||||
let authenticationService = AuthenticationService(environment: environment)
|
||||
|
||||
return authenticationService.authorizeApp(instanceURL: instanceURL)
|
||||
.tryMap { appAuthorization -> (URL, AppAuthorization) in
|
||||
try secretsService.set(appAuthorization.clientId, forItem: .clientID)
|
||||
try secretsService.set(appAuthorization.clientSecret, forItem: .clientSecret)
|
||||
try secrets.set(appAuthorization.clientId, forItem: .clientID)
|
||||
try secrets.set(appAuthorization.clientSecret, forItem: .clientSecret)
|
||||
|
||||
return (instanceURL, appAuthorization)
|
||||
}
|
||||
.flatMap(authenticationService.authenticate(instanceURL:appAuthorization:))
|
||||
.tryMap { try secretsService.set($0.accessToken, forItem: .accessToken) }
|
||||
.tryMap { try secrets.set($0.accessToken, forItem: .accessToken) }
|
||||
.ignoreOutput()
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
func deleteIdentity(_ identity: Identity) -> AnyPublisher<Never, Error> {
|
||||
let secretsService = SecretsService(identityID: identity.id, keychainService: environment.keychainServiceType)
|
||||
let secrets = Secrets(identityID: identity.id, keychain: environment.keychain)
|
||||
let networkClient = APIClient(session: environment.session)
|
||||
|
||||
networkClient.instanceURL = identity.url
|
||||
|
@ -60,14 +61,14 @@ public extension AllIdentitiesService {
|
|||
.collect()
|
||||
.tryMap { _ in
|
||||
DeletionEndpoint.oauthRevoke(
|
||||
token: try secretsService.item(.accessToken),
|
||||
clientID: try secretsService.item(.clientID),
|
||||
clientSecret: try secretsService.item(.clientSecret))
|
||||
token: try secrets.item(.accessToken),
|
||||
clientID: try secrets.item(.clientID),
|
||||
clientSecret: try secrets.item(.clientSecret))
|
||||
}
|
||||
.flatMap(networkClient.request)
|
||||
.collect()
|
||||
.tryMap { _ in
|
||||
try secretsService.deleteAllItems()
|
||||
try secrets.deleteAllItems()
|
||||
try ContentDatabase.delete(forIdentityID: identity.id)
|
||||
}
|
||||
.ignoreOutput()
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
import DB
|
||||
import Foundation
|
||||
import HTTP
|
||||
import Keychain
|
||||
import Mastodon
|
||||
import UserNotifications
|
||||
|
||||
public struct AppEnvironment {
|
||||
let session: Session
|
||||
let webAuthSessionType: WebAuthSession.Type
|
||||
let keychainServiceType: KeychainService.Type
|
||||
let keychain: Keychain.Type
|
||||
let userDefaults: UserDefaults
|
||||
let userNotificationClient: UserNotificationClient
|
||||
let inMemoryContent: Bool
|
||||
|
@ -17,14 +18,14 @@ public struct AppEnvironment {
|
|||
|
||||
public init(session: Session,
|
||||
webAuthSessionType: WebAuthSession.Type,
|
||||
keychainServiceType: KeychainService.Type,
|
||||
keychain: Keychain.Type,
|
||||
userDefaults: UserDefaults,
|
||||
userNotificationClient: UserNotificationClient,
|
||||
inMemoryContent: Bool,
|
||||
identityFixture: IdentityFixture?) {
|
||||
self.session = session
|
||||
self.webAuthSessionType = webAuthSessionType
|
||||
self.keychainServiceType = keychainServiceType
|
||||
self.keychain = keychain
|
||||
self.userDefaults = userDefaults
|
||||
self.userNotificationClient = userNotificationClient
|
||||
self.inMemoryContent = inMemoryContent
|
||||
|
@ -37,7 +38,7 @@ public extension AppEnvironment {
|
|||
Self(
|
||||
session: Session(configuration: .default),
|
||||
webAuthSessionType: LiveWebAuthSession.self,
|
||||
keychainServiceType: LiveKeychainService.self,
|
||||
keychain: LiveKeychain.self,
|
||||
userDefaults: .standard,
|
||||
userNotificationClient: .live(userNotificationCenter),
|
||||
inMemoryContent: false,
|
||||
|
|
|
@ -4,6 +4,7 @@ import DB
|
|||
import Foundation
|
||||
import Combine
|
||||
import Mastodon
|
||||
import Secrets
|
||||
|
||||
public class IdentityService {
|
||||
@Published public private(set) var identity: Identity
|
||||
|
@ -13,7 +14,7 @@ public class IdentityService {
|
|||
private let contentDatabase: ContentDatabase
|
||||
private let environment: AppEnvironment
|
||||
private let networkClient: APIClient
|
||||
private let secretsService: SecretsService
|
||||
private let secrets: Secrets
|
||||
private let observationErrorsInput = PassthroughSubject<Error, Never>()
|
||||
|
||||
init(identityID: UUID,
|
||||
|
@ -33,12 +34,12 @@ public class IdentityService {
|
|||
guard let identity = initialIdentity else { throw IdentityDatabaseError.identityNotFound }
|
||||
|
||||
self.identity = identity
|
||||
secretsService = SecretsService(
|
||||
secrets = Secrets(
|
||||
identityID: identityID,
|
||||
keychainService: environment.keychainServiceType)
|
||||
keychain: environment.keychain)
|
||||
networkClient = APIClient(session: environment.session)
|
||||
networkClient.instanceURL = identity.url
|
||||
networkClient.accessToken = try? secretsService.item(.accessToken)
|
||||
networkClient.accessToken = try? secrets.item(.accessToken)
|
||||
|
||||
contentDatabase = try ContentDatabase(identityID: identityID, inMemory: environment.inMemoryContent)
|
||||
|
||||
|
@ -168,8 +169,8 @@ public extension IdentityService {
|
|||
let auth: String
|
||||
|
||||
do {
|
||||
publicKey = try secretsService.generatePushKeyAndReturnPublicKey().base64EncodedString()
|
||||
auth = try secretsService.generatePushAuth().base64EncodedString()
|
||||
publicKey = try secrets.generatePushKeyAndReturnPublicKey().base64EncodedString()
|
||||
auth = try secrets.generatePushAuth().base64EncodedString()
|
||||
} catch {
|
||||
return Fail(error: error).eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import DB
|
||||
import Foundation
|
||||
import HTTP
|
||||
import MockKeychain
|
||||
import ServiceLayer
|
||||
import Stubbing
|
||||
|
||||
|
@ -11,7 +12,7 @@ public extension AppEnvironment {
|
|||
AppEnvironment(
|
||||
session: Session(configuration: .stubbing),
|
||||
webAuthSessionType: SuccessfulMockWebAuthSession.self,
|
||||
keychainServiceType: MockKeychainService.self,
|
||||
keychain: MockKeychain.self,
|
||||
userDefaults: MockUserDefaults(),
|
||||
userNotificationClient: .mock,
|
||||
inMemoryContent: true,
|
||||
|
|
|
@ -5,6 +5,7 @@ import Combine
|
|||
import CombineExpectations
|
||||
import HTTP
|
||||
import Mastodon
|
||||
import MockKeychain
|
||||
import ServiceLayer
|
||||
import ServiceLayerMocks
|
||||
@testable import ViewModels
|
||||
|
@ -48,7 +49,7 @@ class AddIdentityViewModelTests: XCTestCase {
|
|||
let environment = AppEnvironment(
|
||||
session: Session(configuration: .stubbing),
|
||||
webAuthSessionType: CanceledLoginMockWebAuthSession.self,
|
||||
keychainServiceType: MockKeychainService.self,
|
||||
keychain: MockKeychain.self,
|
||||
userDefaults: MockUserDefaults(),
|
||||
userNotificationClient: .mock,
|
||||
inMemoryContent: true,
|
||||
|
|
Loading…
Reference in a new issue