Files
adiuva/src/main/ai/token.ts

115 lines
3.3 KiB
TypeScript

import { safeStorage } from 'electron';
import { getStore } from '../store';
/**
* Token storage with three-tier fallback:
* 1. OS keychain via keytar (best — encrypted, per-user)
* 2. Electron safeStorage + electron-store (encrypted at rest)
* 3. Plain electron-store (last resort — e.g. WSL with no keyring)
*/
let keytar: typeof import('keytar') | null = null;
let keytarFailed = false;
try {
// eslint-disable-next-line @typescript-eslint/no-require-imports
keytar = require('keytar') as typeof import('keytar');
} catch {
keytarFailed = true;
console.log('[Token] keytar native module unavailable');
}
function useKeytar(): boolean {
return keytar !== null && !keytarFailed;
}
function canUseSafeStorage(): boolean {
try {
return safeStorage.isEncryptionAvailable();
} catch {
return false;
}
}
const SERVICE_NAME = 'adiuva';
// --- electron-store helpers (with optional safeStorage encryption) ---
function readFromStore(providerName: string): string | null {
const tokens = getStore().get('encryptedTokens');
const stored = tokens[providerName];
if (!stored) return null;
if (canUseSafeStorage()) {
try {
return safeStorage.decryptString(Buffer.from(stored, 'base64'));
} catch {
// Stored value might be plaintext from a previous fallback
return stored;
}
}
// No encryption available — value is stored as plaintext
return stored;
}
function writeToStore(providerName: string, token: string): void {
let value: string;
if (canUseSafeStorage()) {
value = safeStorage.encryptString(token).toString('base64');
} else {
// Last resort: store plaintext (WSL with no keyring)
value = token;
}
const tokens = getStore().get('encryptedTokens');
getStore().set('encryptedTokens', { ...tokens, [providerName]: value });
}
function removeFromStore(providerName: string): void {
const tokens = getStore().get('encryptedTokens');
const { [providerName]: _, ...rest } = tokens;
getStore().set('encryptedTokens', rest);
}
// --- public API ---
/** Read a stored token for the given provider. */
export async function getToken(providerName: string): Promise<string | null> {
if (useKeytar()) {
try {
return await keytar!.getPassword(SERVICE_NAME, providerName);
} catch (err) {
console.log('[Token] keytar runtime error, falling back:', (err as Error).message);
keytarFailed = true;
}
}
return readFromStore(providerName);
}
/** Store a token for the given provider. */
export async function setToken(providerName: string, token: string): Promise<void> {
if (useKeytar()) {
try {
await keytar!.setPassword(SERVICE_NAME, providerName, token);
return;
} catch (err) {
console.log('[Token] keytar runtime error, falling back:', (err as Error).message);
keytarFailed = true;
}
}
writeToStore(providerName, token);
}
/** Delete a stored token for the given provider. */
async function deleteToken(providerName: string): Promise<boolean> {
if (useKeytar()) {
try {
return await keytar!.deletePassword(SERVICE_NAME, providerName);
} catch (err) {
console.log('[Token] keytar runtime error, falling back:', (err as Error).message);
keytarFailed = true;
}
}
removeFromStore(providerName);
return true;
}