Skip to content

Commit

Permalink
keychain keys
Browse files Browse the repository at this point in the history
  • Loading branch information
ctate committed May 27, 2024
1 parent 3d339ce commit 784346c
Show file tree
Hide file tree
Showing 10 changed files with 115 additions and 30 deletions.
8 changes: 4 additions & 4 deletions platforms/apple/Crystal.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
D73C375A2C024D7500BD37E3 /* GenerateImageTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73C37592C024D7500BD37E3 /* GenerateImageTool.swift */; };
D73C375C2C024DAC00BD37E3 /* MakeRecipeTool.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73C375B2C024DAC00BD37E3 /* MakeRecipeTool.swift */; };
D73C376C2C04134D00BD37E3 /* UserSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73C376B2C04134D00BD37E3 /* UserSettings.swift */; };
D73C376E2C04411F00BD37E3 /* KeychainKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = D73C376D2C04411F00BD37E3 /* KeychainKeys.swift */; };
D76D3B102BF5D8CA0017F2BA /* ConversationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D76D3B0F2BF5D8CA0017F2BA /* ConversationManager.swift */; };
D7C9D2A82BF2FCC500697DDE /* CrystalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D2A72BF2FCC500697DDE /* CrystalApp.swift */; };
D7C9D2AA2BF2FCC500697DDE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D2A92BF2FCC500697DDE /* ContentView.swift */; };
Expand Down Expand Up @@ -44,7 +45,6 @@
D7C9D3072BF3168200697DDE /* Camera.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D3042BF3168200697DDE /* Camera.swift */; };
D7C9D3082BF3168200697DDE /* OpenAI.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D3052BF3168200697DDE /* OpenAI.swift */; };
D7C9D3092BF3168200697DDE /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D3062BF3168200697DDE /* CameraView.swift */; };
D7C9D30B2BF3169900697DDE /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D30A2BF3169900697DDE /* KeychainService.swift */; };
D7C9D30D2BF316A600697DDE /* Color.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D30C2BF316A600697DDE /* Color.swift */; };
D7C9D30F2BF316AC00697DDE /* OrbButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D30E2BF316AC00697DDE /* OrbButton.swift */; };
D7C9D3132BF316B700697DDE /* SpeechRecognizerService.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C9D3122BF316B700697DDE /* SpeechRecognizerService.swift */; };
Expand Down Expand Up @@ -101,6 +101,7 @@
D73C37662C03DF9300BD37E3 /* CrystalTests.xcconfig.sample */ = {isa = PBXFileReference; lastKnownFileType = text; path = CrystalTests.xcconfig.sample; sourceTree = "<group>"; };
D73C37682C03DF9F00BD37E3 /* CrystalUITests.xcconfig.sample */ = {isa = PBXFileReference; lastKnownFileType = text; path = CrystalUITests.xcconfig.sample; sourceTree = "<group>"; };
D73C376B2C04134D00BD37E3 /* UserSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSettings.swift; sourceTree = "<group>"; };
D73C376D2C04411F00BD37E3 /* KeychainKeys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainKeys.swift; sourceTree = "<group>"; };
D76D3B0F2BF5D8CA0017F2BA /* ConversationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationManager.swift; sourceTree = "<group>"; };
D7C9D2A42BF2FCC500697DDE /* Crystal.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Crystal.app; sourceTree = BUILT_PRODUCTS_DIR; };
D7C9D2A72BF2FCC500697DDE /* CrystalApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrystalApp.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -134,7 +135,6 @@
D7C9D3042BF3168200697DDE /* Camera.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Camera.swift; sourceTree = "<group>"; };
D7C9D3052BF3168200697DDE /* OpenAI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenAI.swift; sourceTree = "<group>"; };
D7C9D3062BF3168200697DDE /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
D7C9D30A2BF3169900697DDE /* KeychainService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
D7C9D30C2BF316A600697DDE /* Color.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Color.swift; sourceTree = "<group>"; };
D7C9D30E2BF316AC00697DDE /* OrbButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrbButton.swift; sourceTree = "<group>"; };
D7C9D3122BF316B700697DDE /* SpeechRecognizerService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpeechRecognizerService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -242,6 +242,7 @@
D73C37502C02435800BD37E3 /* Tools */,
D7C9D2A72BF2FCC500697DDE /* CrystalApp.swift */,
D7C9D32E2BF345BC00697DDE /* PlainButtonStyle.swift */,
D73C376D2C04411F00BD37E3 /* KeychainKeys.swift */,
D73C376B2C04134D00BD37E3 /* UserSettings.swift */,
D7C9D2A92BF2FCC500697DDE /* ContentView.swift */,
D7C9D3362BF3A48100697DDE /* Notification.swift */,
Expand All @@ -252,7 +253,6 @@
D7C9D30C2BF316A600697DDE /* Color.swift */,
D72B02812BF512A300D5FEA4 /* CustomTextEditor.swift */,
D7C9D2D92BF3164300697DDE /* KeychainAccess.swift */,
D7C9D30A2BF3169900697DDE /* KeychainService.swift */,
D7C9D3282BF3224A00697DDE /* WelcomeView.swift */,
D7C9D3322BF3500700697DDE /* ForceDarkMode.swift */,
D7C9D3122BF316B700697DDE /* SpeechRecognizerService.swift */,
Expand Down Expand Up @@ -500,13 +500,13 @@
D7C9D31E2BF3172200697DDE /* DocumentPicker.swift in Sources */,
D7C9D3212BF3172200697DDE /* SettingsView.swift in Sources */,
D73C375A2C024D7500BD37E3 /* GenerateImageTool.swift in Sources */,
D73C376E2C04411F00BD37E3 /* KeychainKeys.swift in Sources */,
D7C9D3022BF3167400697DDE /* ChatView.swift in Sources */,
D7C9D2FC2BF3166B00697DDE /* WikipediaCard.swift in Sources */,
D7C9D3222BF3172200697DDE /* VisionModelsView.swift in Sources */,
D7C9D3012BF3167400697DDE /* ChatHistoryView.swift in Sources */,
D7C9D30F2BF316AC00697DDE /* OrbButton.swift in Sources */,
D73C376C2C04134D00BD37E3 /* UserSettings.swift in Sources */,
D7C9D30B2BF3169900697DDE /* KeychainService.swift in Sources */,
D7C9D2E52BF3165300697DDE /* GoogleApi.swift in Sources */,
D7C9D33B2BF3D52B00697DDE /* Conversation.swift in Sources */,
D7C9D2E72BF3165300697DDE /* HackerNewsApi.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion platforms/apple/Crystal/APIs/AnthropicApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class AnthropicApi: ObservableObject {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])
}

guard let loadedData = load(key: "\(bundleIdentifier).AnthropicApiKey") else {
guard let loadedData = load(KeychainKeys.Providers.Anthropic.apiKey) else {
throw NSError(domain: "", code: 0, userInfo: [NSLocalizedDescriptionKey: "API key not found"])
}

Expand Down
4 changes: 2 additions & 2 deletions platforms/apple/Crystal/APIs/GoogleApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ struct SearchResult: Codable, Hashable {

class GoogleApi {
func fetchSearchResults(query: String, completion: @escaping ([SearchResult]) -> Void) {
guard let loadedDataApiKey = load(key: "\(bundleIdentifier).GoogleApiKey") else { return }
guard let loadedDataSearchEngineId = load(key: "\(bundleIdentifier).GoogleSearchEngineId") else { return }
guard let loadedDataApiKey = load(KeychainKeys.Integrations.Google.apiKey) else { return }
guard let loadedDataSearchEngineId = load(KeychainKeys.Integrations.Google.searchEngineId) else { return }

guard let apiKey = String(data: loadedDataApiKey, encoding: .utf8) else { return }
guard let searchEngineId = String(data: loadedDataSearchEngineId, encoding: .utf8) else { return }
Expand Down
2 changes: 1 addition & 1 deletion platforms/apple/Crystal/APIs/GroqApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class GroqApi: ObservableObject {
throw URLError(.badURL)
}

guard let loadedData = load(key: "\(bundleIdentifier).GroqApiKey"),
guard let loadedData = load(KeychainKeys.Providers.Groq.apiKey),
let apiKey = String(data: loadedData, encoding: .utf8) else {
throw NSError(domain: "GroqApi", code: 1, userInfo: [NSLocalizedDescriptionKey: "API key loading failed"])
}
Expand Down
4 changes: 2 additions & 2 deletions platforms/apple/Crystal/APIs/OpenAiApi.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct OpenAIResponse: Codable {

class OpenAiApi: ObservableObject {
func generateImage(prompt: String) async throws -> [GeneratedImage] {
guard let loadedData = load(key: "\(bundleIdentifier).OpenAIApiKey"),
guard let loadedData = load(KeychainKeys.Providers.OpenAI.apiKey),
let apiKey = String(data: loadedData, encoding: .utf8) else {
throw NSError(domain: "OpenAiApi", code: 1, userInfo: [NSLocalizedDescriptionKey: "API key loading failed"])
}
Expand Down Expand Up @@ -74,7 +74,7 @@ class OpenAiApi: ObservableObject {
}

static func makeCompletions(model: String, messages: [[String: String]], tools: [[String: Any]]?) async throws -> OpenAIResponse {
guard let loadedData = load(key: "\(bundleIdentifier).OpenAIApiKey"),
guard let loadedData = load(KeychainKeys.Providers.OpenAI.apiKey),
let apiKey = String(data: loadedData, encoding: .utf8) else {
throw NSError(domain: "OpenAiApi", code: 1, userInfo: [NSLocalizedDescriptionKey: "API key loading failed"])
}
Expand Down
6 changes: 3 additions & 3 deletions platforms/apple/Crystal/KeychainAccess.swift
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import Foundation
import Security

func delete(key: String) -> OSStatus {
func delete(_ key: String) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key ] as [String : Any]

return SecItemDelete(query as CFDictionary)
}

func load(key: String) -> Data? {
func load(_ key: String) -> Data? {
let query = [
kSecClass as String : kSecClassGenericPassword,
kSecAttrAccount as String : key,
Expand All @@ -22,7 +22,7 @@ func load(key: String) -> Data? {
return item as? Data
}

func save(key: String, data: Data) -> OSStatus {
func save(_ key: String, data: Data) -> OSStatus {
let query = [
kSecClass as String : kSecClassGenericPassword as String,
kSecAttrAccount as String : key,
Expand Down
24 changes: 24 additions & 0 deletions platforms/apple/Crystal/KeychainKeys.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import Foundation

struct KeychainKeys {
struct Integrations {
struct Google {
static let apiKey = "\(bundleIdentifier).Integrations.Google.apiKey"
static let searchEngineId = "\(bundleIdentifier).Integrations.Google.searchEngineId"
}
}
struct Providers {
struct Anthropic {
static let apiKey = "\(bundleIdentifier).Providers.Anthropic.apiKey"
}
struct Groq {
static let apiKey = "\(bundleIdentifier).Providers.Groq.apiKey"
}
struct Ollama {
static let apiKey = "\(bundleIdentifier).Providers.Ollama.apiKey"
}
struct OpenAI {
static let apiKey = "\(bundleIdentifier).Providers.OpenAI.apiKey"
}
}
}
9 changes: 0 additions & 9 deletions platforms/apple/Crystal/KeychainService.swift

This file was deleted.

72 changes: 66 additions & 6 deletions platforms/apple/Crystal/Sheets/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,18 @@ struct Integration: Identifiable {
var apiKey: String {
didSet {
if let data = apiKey.data(using: .utf8) {
_ = save(key: "\(bundleIdentifier).\(id)ApiKey", data: data)
switch id {
case "Anthropic":
_ = save(KeychainKeys.Providers.Anthropic.apiKey, data: data)
case "Groq":
_ = save(KeychainKeys.Providers.Groq.apiKey, data: data)
case "Ollama":
_ = save(KeychainKeys.Providers.Ollama.apiKey, data: data)
case "OpenAI":
_ = save(KeychainKeys.Providers.OpenAI.apiKey, data: data)
default:
print("Unknown id")
}
}
}
}
Expand All @@ -35,7 +46,18 @@ struct Integration: Identifiable {

mutating func clearApiKey() {
apiKey = ""
_ = delete(key: "\(bundleIdentifier).\(name)ApiKey")
switch name {
case "Anthropic":
_ = delete(KeychainKeys.Providers.Anthropic.apiKey)
case "Groq":
_ = delete(KeychainKeys.Providers.Groq.apiKey)
case "Ollama":
_ = delete(KeychainKeys.Providers.Ollama.apiKey)
case "OpenAI":
_ = delete(KeychainKeys.Providers.OpenAI.apiKey)
default:
print("Unknown id")
}
}
}

Expand All @@ -51,7 +73,18 @@ struct ProviderWithSettings: Identifiable {
var apiKey: String {
didSet {
if let data = apiKey.data(using: .utf8) {
_ = save(key: "\(bundleIdentifier).\(id)ApiKey", data: data)
switch id {
case "Anthropic":
_ = save(KeychainKeys.Providers.Anthropic.apiKey, data: data)
case "Groq":
_ = save(KeychainKeys.Providers.Groq.apiKey, data: data)
case "Ollama":
_ = save(KeychainKeys.Providers.Ollama.apiKey, data: data)
case "OpenAI":
_ = save(KeychainKeys.Providers.OpenAI.apiKey, data: data)
default:
print("Unknown id")
}
}
}
}
Expand Down Expand Up @@ -79,12 +112,33 @@ struct ProviderWithSettings: Identifiable {

mutating func clearApiKey() {
apiKey = ""
_ = delete(key: "\(bundleIdentifier).\(name)ApiKey")
switch name {
case "Anthropic":
_ = delete(KeychainKeys.Providers.Anthropic.apiKey)
case "Groq":
_ = delete(KeychainKeys.Providers.Groq.apiKey)
case "Ollama":
_ = delete(KeychainKeys.Providers.Ollama.apiKey)
case "OpenAI":
_ = delete(KeychainKeys.Providers.OpenAI.apiKey)
default:
print("Unknown id")
}
}
}

func loadApiKey(key: String) -> String {
guard let apiKeyData = load(key: key) else { return "" }
let keyMap = [
"Anthropic": KeychainKeys.Providers.Anthropic.apiKey,
"Groq": KeychainKeys.Providers.Groq.apiKey,
"Ollama": KeychainKeys.Providers.Ollama.apiKey,
"OpenAI": KeychainKeys.Providers.OpenAI.apiKey
]

guard let key = keyMap[key], let apiKeyData = load(key) else {
return ""
}

return String(decoding: apiKeyData, as: UTF8.self)
}

Expand All @@ -104,7 +158,13 @@ struct IntegrationDetailView: View {
.onChange(of: integration.apiKey) {
// Save the new value to the Keychain
if let data = integration.apiKey.data(using: .utf8) {
_ = save(key: "\(bundleIdentifier).\(integration.id)ApiKey", data: data)
switch integration.id {
case "Google":
_ = save(KeychainKeys.Integrations.Google.apiKey, data: data)
default:
print("Unknown integration")
}

}
}

Expand Down
14 changes: 12 additions & 2 deletions platforms/apple/Crystal/WelcomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,18 @@ struct WelcomeView: View {
UserSettings.hasCompletedOnboarding = true

let data = apiKey.data(using: .utf8)!
_ = save(key: "\(bundleIdentifier).\(selectedProvider!.id)ApiKey", data: data)

switch selectedProvider!.id {
case "Anthropic":
_ = save(KeychainKeys.Providers.Anthropic.apiKey, data: data)
case "Groq":
_ = save(KeychainKeys.Providers.Groq.apiKey, data: data)
case "Ollama":
_ = save(KeychainKeys.Providers.Ollama.apiKey, data: data)
case "OpenAI":
_ = save(KeychainKeys.Providers.OpenAI.apiKey, data: data)
default:
print("Unknown id")
}
isKeyValidated = true
}
.background(.black)
Expand Down

0 comments on commit 784346c

Please sign in to comment.