leumas-private-shared
Version:
Private React JSX Package For Leumas Shared Components, Headers, Footers, Asides, Login Pages, API Key Manager and much more. Styles and everything reusable to avoid DRY code across all of our subdomains
227 lines (181 loc) • 7.52 kB
JSX
// Importing necessary dependencies
import { openDB } from 'idb';
// Open a database
const dbPromise = openDB('apiKeysDB', 1, {
upgrade(db) {
db.createObjectStore('apiKeys');
},
});
// Function to generate an encryption key using the Web Cryptography API
// Assuming you now store {encryptedData, iv, salt} for each user
// Updated key generation to include salt parameter
// Assuming the presence of a generateEncryptionKey function that optionally generates a new salt
// Helper function to convert Base64 string to Uint8Array
function base64ToUint8Array(base64) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes;
}
// Adjusted generateEncryptionKey function
export const generateEncryptionKey = async (userId, generateNewSalt = false, salt) => {
const enc = new TextEncoder();
// If salt is provided as a string, assume it's in Base64 and convert it
let saltArray;
if (typeof salt === 'string') {
saltArray = base64ToUint8Array(salt);
} else if (salt instanceof Uint8Array) {
saltArray = salt;
} else if (!salt && generateNewSalt) {
saltArray = window.crypto.getRandomValues(new Uint8Array(16)); // Generate new salt if needed
} else {
throw new Error('Salt is missing or in an unsupported format');
}
const keyMaterial = await window.crypto.subtle.importKey(
"raw",
enc.encode(userId),
{ name: "PBKDF2" },
false,
["deriveKey"]
);
const key = await window.crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: saltArray,
iterations: 100000,
hash: "SHA-256",
},
keyMaterial,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
return { key, salt: saltArray };
};
// Function to encrypt data
export const encryptData = async (data, key) => {
const iv = window.crypto.getRandomValues(new Uint8Array(12)); // Initialization vector
const enc = new TextEncoder();
const encryptedData = await window.crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
key,
enc.encode(data)
);
return { encryptedData, iv };
};
// Function to decrypt data
export const decryptData = async ({ encryptedData, iv }, key) => {
const dec = new TextDecoder();
const decryptedData = await window.crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv,
},
key,
encryptedData
);
return dec.decode(decryptedData);
};
// Function to save data to IndexedDB
// Function to append a new API key data to the existing array for a user
export const saveToIndexedDB = async (storeName, userId, newData) => {
const db = await dbPromise;
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
let currentData = await store.get(userId);
// Ensure currentData is an array
if (!Array.isArray(currentData)) {
currentData = currentData ? [currentData] : [];
}
// Append the new data (ensuring it doesn't duplicate existing entries if needed)
currentData.push(newData);
await store.put(currentData, userId);
await tx.done;
console.log('API keys updated successfully.');
};
export const deleteApiKeyFromDB = async (storeName, userId, indexToDelete) => {
const db = await dbPromise;
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
let currentData = await store.get(userId);
if (!Array.isArray(currentData)) {
console.error('Data structure error: Expected an array of API keys');
throw new Error('Data structure error: Expected an array of API keys');
}
// Remove the API key at the specified index
currentData.splice(indexToDelete, 1);
// Save the updated array back to IndexedDB
await store.put(currentData, userId);
await tx.done;
console.log('API key removed successfully.');
};
export const deleteAllApiKeysFromDB = async (storeName, userId) => {
const db = await dbPromise;
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
// Instead of modifying and saving the array, simply clear all entries for the user
await store.delete(userId);
await tx.done;
console.log('All API keys for user removed successfully.');
};
// Function to get data from IndexedDB
// Adjusted function to ensure it always returns an array
export const getFromIndexedDB = async (storeName, userId) => {
const db = await dbPromise;
const data = await db.transaction(storeName).objectStore(storeName).get(userId);
// Ensure the return value is always an array
return Array.isArray(data) ? data : data ? [data] : [];
};
// Fetching API Key for another component
// In your cryptoUtils.js or a similar utility file
export const fetchApiKeys = async (userId) => {
try {
const dataFromDB = await getFromIndexedDB('apiKeys', userId);
if (!dataFromDB || dataFromDB.length === 0) {
console.log('No API keys found for this user.');
return [];
}
const apiKeysArray = Array.isArray(dataFromDB) ? dataFromDB : [dataFromDB];
const filteredApiKeysArray = apiKeysArray.filter(item => !Array.isArray(item));
const keys = await Promise.all(filteredApiKeysArray.map(async (keyObj) => {
const { encryptedData, iv, salt } = keyObj;
const { key: encryptionKey } = await generateEncryptionKey(userId, false, salt);
const decryptedData = await decryptData({ encryptedData, iv }, encryptionKey);
return JSON.parse(decryptedData);
}));
return keys;
} catch (error) {
console.error('Error fetching API keys:', error);
throw error; // Rethrow the error to be handled by the caller
}
};
export const fetchApiKeysOfType = async (userId, apiKeyType) => {
try {
const dataFromDB = await getFromIndexedDB('apiKeys', userId);
if (!dataFromDB || dataFromDB.length === 0) {
console.log('No API keys found for this user.');
return [];
}
// Convert to array if not already, and filter non-array items
const apiKeysArray = Array.isArray(dataFromDB) ? dataFromDB : [dataFromDB];
const filteredApiKeysArray = apiKeysArray.filter(item => !Array.isArray(item));
const keys = await Promise.all(filteredApiKeysArray.map(async (keyObj) => {
const { encryptedData, iv, salt } = keyObj;
const { key: encryptionKey } = await generateEncryptionKey(userId, false, salt);
const decryptedData = await decryptData({ encryptedData, iv }, encryptionKey);
return JSON.parse(decryptedData);
}));
// Filter the keys by the specified apiKeyType
const keysOfType = keys.filter(key => key.apiKeyType === apiKeyType);
return keysOfType;
} catch (error) {
console.error(`Error fetching API keys of type ${apiKeyType}:`, error);
throw error; // Rethrow the error to be handled by the caller
}
};