generated from obsidianmd/obsidian-sample-plugin
-
-
Notifications
You must be signed in to change notification settings - Fork 244
/
encryptionService.ts
120 lines (104 loc) · 3.91 KB
/
encryptionService.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { CopilotSettings } from "@/settings/SettingsPage";
import { Platform } from "obsidian";
// Dynamically import electron to access safeStorage
// @ts-ignore
let safeStorage: Electron.SafeStorage | null = null;
if (Platform.isDesktop) {
// eslint-disable-next-line @typescript-eslint/no-var-requires
safeStorage = require("electron")?.remote?.safeStorage;
}
export default class EncryptionService {
private settings: CopilotSettings;
private static ENCRYPTION_PREFIX = "enc_";
private static DECRYPTION_PREFIX = "dec_";
constructor(settings: CopilotSettings) {
this.settings = settings;
}
private isPlainText(key: string): boolean {
return (
!key.startsWith(EncryptionService.ENCRYPTION_PREFIX) &&
!key.startsWith(EncryptionService.DECRYPTION_PREFIX)
);
}
private isDecrypted(keyBuffer: string): boolean {
return keyBuffer.startsWith(EncryptionService.DECRYPTION_PREFIX);
}
public encryptAllKeys(): void {
const keysToEncrypt = Object.keys(this.settings).filter((key) =>
key.toLowerCase().includes("apikey".toLowerCase())
);
for (const key of keysToEncrypt) {
const apiKey = this.settings[key as keyof CopilotSettings] as string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(this.settings[key as keyof CopilotSettings] as any) = this.getEncryptedKey(apiKey);
}
if (Array.isArray(this.settings.activeModels)) {
this.settings.activeModels = this.settings.activeModels.map((model) => ({
...model,
apiKey: this.getEncryptedKey(model.apiKey || ""),
}));
}
}
public getEncryptedKey(apiKey: string): string {
if (
!apiKey ||
!this.settings.enableEncryption ||
apiKey.startsWith(EncryptionService.ENCRYPTION_PREFIX)
) {
return apiKey;
}
if (this.isDecrypted(apiKey)) {
apiKey = apiKey.replace(EncryptionService.DECRYPTION_PREFIX, "");
}
if (safeStorage && safeStorage.isEncryptionAvailable()) {
// Convert the encrypted buffer to a Base64 string and prepend the prefix
const encryptedBuffer = safeStorage.encryptString(apiKey) as Buffer;
// Convert the encrypted buffer to a Base64 string and prepend the prefix
return EncryptionService.ENCRYPTION_PREFIX + encryptedBuffer.toString("base64");
} else {
// Simple fallback for mobile (just for demonstration)
const encoder = new TextEncoder();
const data = encoder.encode(apiKey);
return EncryptionService.ENCRYPTION_PREFIX + this.arrayBufferToBase64(data);
}
}
public getDecryptedKey(apiKey: string): string {
if (!apiKey || this.isPlainText(apiKey)) {
return apiKey;
}
if (this.isDecrypted(apiKey)) {
return apiKey.replace(EncryptionService.DECRYPTION_PREFIX, "");
}
const base64Data = apiKey.replace(EncryptionService.ENCRYPTION_PREFIX, "");
try {
if (safeStorage && safeStorage.isEncryptionAvailable()) {
const buffer = Buffer.from(base64Data, "base64");
return safeStorage.decryptString(buffer) as string;
} else {
// Simple fallback for mobile (just for demonstration)
const data = this.base64ToArrayBuffer(base64Data);
const decoder = new TextDecoder();
return decoder.decode(data);
}
} catch (err) {
console.error("Decryption failed:", err);
return "Copilot failed to decrypt API keys!";
}
}
private arrayBufferToBase64(buffer: ArrayBuffer): string {
const bytes = new Uint8Array(buffer);
let binary = "";
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
private base64ToArrayBuffer(base64: string): ArrayBuffer {
const binaryString = window.atob(base64);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
}