Files

115 lines
4.1 KiB
JavaScript

import {
decryptJsonWithStoragePwd,
encryptJsonWithStoragePwd,
} from './crypto-utils.js';
const DB_NAME = 'shine-ui-auth';
const DB_VERSION = 1;
const STORE_SECRETS = 'encrypted-secrets';
const STORE_SESSIONS = 'session-keys';
function normalizeLoginStorageKey(login) {
return String(login || '').trim().toLowerCase();
}
function openDb() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onupgradeneeded = () => {
const db = request.result;
if (!db.objectStoreNames.contains(STORE_SECRETS)) {
db.createObjectStore(STORE_SECRETS, { keyPath: 'login' });
}
if (!db.objectStoreNames.contains(STORE_SESSIONS)) {
db.createObjectStore(STORE_SESSIONS, { keyPath: 'login' });
}
};
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error || new Error('IndexedDB недоступен'));
});
}
async function put(storeName, value) {
const db = await openDb();
await new Promise((resolve, reject) => {
const tx = db.transaction(storeName, 'readwrite');
tx.objectStore(storeName).put(value);
tx.oncomplete = () => resolve();
tx.onerror = () => reject(tx.error || new Error('Ошибка записи в IndexedDB'));
});
db.close();
}
async function get(storeName, key) {
const db = await openDb();
const result = await new Promise((resolve, reject) => {
const tx = db.transaction(storeName, 'readonly');
const req = tx.objectStore(storeName).get(key);
req.onsuccess = () => resolve(req.result || null);
req.onerror = () => reject(req.error || new Error('Ошибка чтения из IndexedDB'));
});
db.close();
return result;
}
export async function saveEncryptedUserSecrets(login, storagePwd, keys) {
const encrypted = await encryptJsonWithStoragePwd(keys, storagePwd);
const normalizedLogin = normalizeLoginStorageKey(login);
await put(STORE_SECRETS, {
login: normalizedLogin,
encrypted,
updatedAtMs: Date.now(),
});
}
export async function loadEncryptedUserSecrets(login, storagePwd) {
const normalizedLogin = normalizeLoginStorageKey(login);
let row = await get(STORE_SECRETS, normalizedLogin);
if (!row?.encrypted && normalizedLogin !== String(login || '').trim()) {
row = await get(STORE_SECRETS, String(login || '').trim());
}
if (!row?.encrypted) {
throw new Error('На устройстве нет сохранённых ключей для этого логина');
}
return decryptJsonWithStoragePwd(row.encrypted, storagePwd);
}
export async function updateEncryptedUserSecrets(login, storagePwd, updater) {
if (typeof updater !== 'function') {
throw new Error('updateEncryptedUserSecrets: updater должен быть функцией');
}
const current = await loadEncryptedUserSecrets(login, storagePwd);
const next = await updater(current);
if (!next || typeof next !== 'object') {
throw new Error('updateEncryptedUserSecrets: updater должен вернуть объект secrets');
}
await saveEncryptedUserSecrets(login, storagePwd, next);
return next;
}
export async function saveSessionMaterial(login, material) {
const normalizedLogin = normalizeLoginStorageKey(login);
await put(STORE_SESSIONS, {
login: normalizedLogin,
...material,
updatedAtMs: Date.now(),
});
}
export async function loadSessionMaterial(login) {
const normalizedLogin = normalizeLoginStorageKey(login);
const row = await get(STORE_SESSIONS, normalizedLogin);
if (row || normalizedLogin === String(login || '').trim()) return row;
return get(STORE_SESSIONS, String(login || '').trim());
}
export async function clearClientAuthData() {
await new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(DB_NAME);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error || new Error('Не удалось очистить IndexedDB'));
request.onblocked = () => reject(new Error('Очистка IndexedDB заблокирована открытыми соединениями'));
});
}