mirror of
https://github.com/metabolist/metatext.git
synced 2024-11-25 09:41:00 +00:00
Refactoring
This commit is contained in:
parent
fb4e3f907f
commit
f5cc39f256
4 changed files with 88 additions and 73 deletions
|
@ -1,40 +0,0 @@
|
||||||
// Copyright © 2020 Metabolist. All rights reserved.
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import Keychain
|
|
||||||
import Secrets
|
|
||||||
|
|
||||||
extension Secrets {
|
|
||||||
private static let passphraseByteCount = 64
|
|
||||||
|
|
||||||
static func databasePassphrase(identityID: UUID?, keychain: Keychain.Type) throws -> String {
|
|
||||||
let scopedSecrets: Secrets?
|
|
||||||
|
|
||||||
if let identityID = identityID {
|
|
||||||
scopedSecrets = Secrets(identityID: identityID, keychain: keychain)
|
|
||||||
} else {
|
|
||||||
scopedSecrets = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
return try scopedSecrets?.item(.databasePassphrase) ?? unscopedItem(.databasePassphrase, keychain: keychain)
|
|
||||||
} catch SecretsError.itemAbsent {
|
|
||||||
var bytes = [Int8](repeating: 0, count: passphraseByteCount)
|
|
||||||
let status = SecRandomCopyBytes(kSecRandomDefault, passphraseByteCount, &bytes)
|
|
||||||
|
|
||||||
if status == errSecSuccess {
|
|
||||||
let passphrase = Data(bytes: bytes, count: passphraseByteCount).base64EncodedString()
|
|
||||||
|
|
||||||
if let scopedSecrets = scopedSecrets {
|
|
||||||
try scopedSecrets.set(passphrase, forItem: .databasePassphrase)
|
|
||||||
} else {
|
|
||||||
try setUnscoped(passphrase, forItem: .databasePassphrase, keychain: keychain)
|
|
||||||
}
|
|
||||||
|
|
||||||
return passphrase
|
|
||||||
} else {
|
|
||||||
throw NSError(status: status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -52,38 +52,35 @@ extension Secrets.Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Secrets {
|
public extension Secrets {
|
||||||
static func setUnscoped(_ data: SecretsStorable, forItem item: Item, keychain: Keychain.Type) throws {
|
static func databasePassphrase(identityID: UUID?, keychain: Keychain.Type) throws -> String {
|
||||||
try keychain.setGenericPassword(
|
let scopedSecrets: Secrets?
|
||||||
data: data.dataStoredInSecrets,
|
|
||||||
forAccount: item.rawValue,
|
|
||||||
service: keychainServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
static func unscopedItem<T: SecretsStorable>(_ item: Item, keychain: Keychain.Type) throws -> T {
|
if let identityID = identityID {
|
||||||
guard let data = try keychain.getGenericPassword(
|
scopedSecrets = Secrets(identityID: identityID, keychain: keychain)
|
||||||
account: item.rawValue,
|
} else {
|
||||||
service: Self.keychainServiceName) else {
|
scopedSecrets = nil
|
||||||
throw SecretsError.itemAbsent
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return try T.fromDataStoredInSecrets(data)
|
do {
|
||||||
}
|
return try scopedSecrets?.item(.databasePassphrase) ?? unscopedItem(.databasePassphrase, keychain: keychain)
|
||||||
|
} catch SecretsError.itemAbsent {
|
||||||
|
var bytes = [Int8](repeating: 0, count: databasePassphraseByteCount)
|
||||||
|
let status = SecRandomCopyBytes(kSecRandomDefault, databasePassphraseByteCount, &bytes)
|
||||||
|
|
||||||
func set(_ data: SecretsStorable, forItem item: Item) throws {
|
if status == errSecSuccess {
|
||||||
try keychain.setGenericPassword(
|
let passphrase = Data(bytes: bytes, count: databasePassphraseByteCount).base64EncodedString()
|
||||||
data: data.dataStoredInSecrets,
|
|
||||||
forAccount: scopedKey(item: item),
|
|
||||||
service: Self.keychainServiceName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func item<T: SecretsStorable>(_ item: Item) throws -> T {
|
if let scopedSecrets = scopedSecrets {
|
||||||
guard let data = try keychain.getGenericPassword(
|
try scopedSecrets.set(passphrase, forItem: .databasePassphrase)
|
||||||
account: scopedKey(item: item),
|
} else {
|
||||||
service: Self.keychainServiceName) else {
|
try setUnscoped(passphrase, forItem: .databasePassphrase, keychain: keychain)
|
||||||
throw SecretsError.itemAbsent
|
}
|
||||||
|
|
||||||
|
return passphrase
|
||||||
|
} else {
|
||||||
|
throw NSError(status: status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return try T.fromDataStoredInSecrets(data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteAllItems() throws {
|
func deleteAllItems() throws {
|
||||||
|
@ -99,6 +96,30 @@ public extension Secrets {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getClientID() throws -> String {
|
||||||
|
try item(.clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClientID(_ clientID: String) throws {
|
||||||
|
try set(clientID, forItem: .clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientSecret() throws -> String {
|
||||||
|
try item(.clientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setClientSecret(_ clientSecret: String) throws {
|
||||||
|
try set(clientSecret, forItem: .clientSecret)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getAccessToken() throws -> String {
|
||||||
|
try item(.accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setAccessToken(_ accessToken: String) throws {
|
||||||
|
try set(accessToken, forItem: .accessToken)
|
||||||
|
}
|
||||||
|
|
||||||
func generatePushKeyAndReturnPublicKey() throws -> Data {
|
func generatePushKeyAndReturnPublicKey() throws -> Data {
|
||||||
try keychain.generateKeyAndReturnPublicKey(
|
try keychain.generateKeyAndReturnPublicKey(
|
||||||
applicationTag: scopedKey(item: .pushKey),
|
applicationTag: scopedKey(item: .pushKey),
|
||||||
|
@ -130,10 +151,44 @@ public extension Secrets {
|
||||||
|
|
||||||
private extension Secrets {
|
private extension Secrets {
|
||||||
static let keychainServiceName = "com.metabolist.metatext"
|
static let keychainServiceName = "com.metabolist.metatext"
|
||||||
|
static let databasePassphraseByteCount = 64
|
||||||
|
|
||||||
|
private static func set(_ data: SecretsStorable, forAccount account: String, keychain: Keychain.Type) throws {
|
||||||
|
try keychain.setGenericPassword(
|
||||||
|
data: data.dataStoredInSecrets,
|
||||||
|
forAccount: account,
|
||||||
|
service: keychainServiceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func get<T: SecretsStorable>(account: String, keychain: Keychain.Type) throws -> T {
|
||||||
|
guard let data = try keychain.getGenericPassword(
|
||||||
|
account: account,
|
||||||
|
service: keychainServiceName) else {
|
||||||
|
throw SecretsError.itemAbsent
|
||||||
|
}
|
||||||
|
|
||||||
|
return try T.fromDataStoredInSecrets(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func setUnscoped(_ data: SecretsStorable, forItem item: Item, keychain: Keychain.Type) throws {
|
||||||
|
try set(data, forAccount: item.rawValue, keychain: keychain)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func unscopedItem<T: SecretsStorable>(_ item: Item, keychain: Keychain.Type) throws -> T {
|
||||||
|
try get(account: item.rawValue, keychain: keychain)
|
||||||
|
}
|
||||||
|
|
||||||
func scopedKey(item: Item) -> String {
|
func scopedKey(item: Item) -> String {
|
||||||
identityID.uuidString + "." + item.rawValue
|
identityID.uuidString + "." + item.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func set(_ data: SecretsStorable, forItem item: Item) throws {
|
||||||
|
try Self.set(data, forAccount: scopedKey(item: item), keychain: keychain)
|
||||||
|
}
|
||||||
|
|
||||||
|
func item<T: SecretsStorable>(_ item: Item) throws -> T {
|
||||||
|
try Self.get(account: scopedKey(item: item), keychain: keychain)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Data: SecretsStorable {
|
extension Data: SecretsStorable {
|
||||||
|
|
|
@ -42,13 +42,13 @@ public extension AllIdentitiesService {
|
||||||
|
|
||||||
return authenticationService.authorizeApp(instanceURL: instanceURL)
|
return authenticationService.authorizeApp(instanceURL: instanceURL)
|
||||||
.tryMap { appAuthorization -> (URL, AppAuthorization) in
|
.tryMap { appAuthorization -> (URL, AppAuthorization) in
|
||||||
try secrets.set(appAuthorization.clientId, forItem: .clientID)
|
try secrets.setClientID(appAuthorization.clientId)
|
||||||
try secrets.set(appAuthorization.clientSecret, forItem: .clientSecret)
|
try secrets.setClientSecret(appAuthorization.clientSecret)
|
||||||
|
|
||||||
return (instanceURL, appAuthorization)
|
return (instanceURL, appAuthorization)
|
||||||
}
|
}
|
||||||
.flatMap(authenticationService.authenticate(instanceURL:appAuthorization:))
|
.flatMap(authenticationService.authenticate(instanceURL:appAuthorization:))
|
||||||
.tryMap { try secrets.set($0.accessToken, forItem: .accessToken) }
|
.tryMap { try secrets.setAccessToken($0.accessToken) }
|
||||||
.ignoreOutput()
|
.ignoreOutput()
|
||||||
.eraseToAnyPublisher()
|
.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
@ -63,9 +63,9 @@ public extension AllIdentitiesService {
|
||||||
.collect()
|
.collect()
|
||||||
.tryMap { _ in
|
.tryMap { _ in
|
||||||
DeletionEndpoint.oauthRevoke(
|
DeletionEndpoint.oauthRevoke(
|
||||||
token: try secrets.item(.accessToken),
|
token: try secrets.getAccessToken(),
|
||||||
clientID: try secrets.item(.clientID),
|
clientID: try secrets.getClientID(),
|
||||||
clientSecret: try secrets.item(.clientSecret))
|
clientSecret: try secrets.getClientSecret())
|
||||||
}
|
}
|
||||||
.flatMap(mastodonAPIClient.request)
|
.flatMap(mastodonAPIClient.request)
|
||||||
.collect()
|
.collect()
|
||||||
|
|
|
@ -40,7 +40,7 @@ public class IdentityService {
|
||||||
keychain: environment.keychain)
|
keychain: environment.keychain)
|
||||||
mastodonAPIClient = MastodonAPIClient(session: environment.session)
|
mastodonAPIClient = MastodonAPIClient(session: environment.session)
|
||||||
mastodonAPIClient.instanceURL = identity.url
|
mastodonAPIClient.instanceURL = identity.url
|
||||||
mastodonAPIClient.accessToken = try? secrets.item(.accessToken)
|
mastodonAPIClient.accessToken = try? secrets.getAccessToken()
|
||||||
|
|
||||||
contentDatabase = try ContentDatabase(identityID: identityID,
|
contentDatabase = try ContentDatabase(identityID: identityID,
|
||||||
inMemory: environment.inMemoryContent,
|
inMemory: environment.inMemoryContent,
|
||||||
|
|
Loading…
Reference in a new issue