mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
fix: enable cipher key encryption feature for 2026.4.x clients and streamline key handling
This commit is contained in:
+4
-7
@@ -25,7 +25,7 @@ import {
|
||||
import { clearAuditLogs, getAuditLogSettings, listAdminInvites, listAdminUsers, listAuditLogs, saveAuditLogSettings, type AuditLogFilters } from '@/lib/api/admin';
|
||||
import { getDomainRules, saveDomainRules } from '@/lib/api/domains';
|
||||
import { getSends } from '@/lib/api/send';
|
||||
import { repairCipherKeyMismatches, repairCipherUriChecksums } from '@/lib/api/vault';
|
||||
import { repairCipherUriChecksums } from '@/lib/api/vault';
|
||||
import { getCachedVaultCoreSnapshot, loadVaultCoreSyncSnapshot } from '@/lib/api/vault-sync';
|
||||
import { silentlyRepairBackupSettingsIfNeeded } from '@/lib/backup-settings-repair';
|
||||
import {
|
||||
@@ -1086,12 +1086,9 @@ export default function App() {
|
||||
const repairKey = `${session.accessToken}:${encryptedCiphers.map((cipher) => `${cipher.id}:${cipher.revisionDate || ''}`).join(',')}`;
|
||||
if (uriChecksumRepairAttemptRef.current !== repairKey) {
|
||||
uriChecksumRepairAttemptRef.current = repairKey;
|
||||
void Promise.all([
|
||||
repairCipherKeyMismatches(authedFetch, session, result.ciphers),
|
||||
repairCipherUriChecksums(authedFetch, session, result.ciphers),
|
||||
])
|
||||
.then(([keyMismatchCount, uriChecksumCount]) => {
|
||||
if (keyMismatchCount + uriChecksumCount > 0) void refetchVaultCoreData();
|
||||
void repairCipherUriChecksums(authedFetch, session, result.ciphers)
|
||||
.then((uriChecksumCount) => {
|
||||
if (uriChecksumCount > 0) void refetchVaultCoreData();
|
||||
})
|
||||
.catch(() => {
|
||||
// Best-effort compatibility repair must not interrupt normal vault loading.
|
||||
|
||||
@@ -224,6 +224,56 @@ function optimisticCipherFromDraft(draft: VaultDraft, current?: Cipher | null):
|
||||
return next;
|
||||
}
|
||||
|
||||
function isEncryptedFieldUnresolved(raw: unknown, decrypted: unknown): boolean {
|
||||
const encrypted = String(raw || '').trim();
|
||||
if (!looksLikeCipherString(encrypted)) return false;
|
||||
const plain = String(decrypted || '').trim();
|
||||
return !plain || looksLikeCipherString(plain);
|
||||
}
|
||||
|
||||
function hasUnresolvedCipherData(cipher: Cipher): boolean {
|
||||
const checks: Array<[unknown, unknown]> = [
|
||||
[cipher.name, cipher.decName],
|
||||
[cipher.notes, cipher.decNotes],
|
||||
[cipher.login?.username, cipher.login?.decUsername],
|
||||
[cipher.login?.password, cipher.login?.decPassword],
|
||||
[cipher.login?.totp, cipher.login?.decTotp],
|
||||
...(cipher.login?.uris || []).map((uri) => [uri.uri, uri.decUri] as [unknown, unknown]),
|
||||
[cipher.card?.cardholderName, cipher.card?.decCardholderName],
|
||||
[cipher.card?.number, cipher.card?.decNumber],
|
||||
[cipher.card?.brand, cipher.card?.decBrand],
|
||||
[cipher.card?.expMonth, cipher.card?.decExpMonth],
|
||||
[cipher.card?.expYear, cipher.card?.decExpYear],
|
||||
[cipher.card?.code, cipher.card?.decCode],
|
||||
[cipher.identity?.title, cipher.identity?.decTitle],
|
||||
[cipher.identity?.firstName, cipher.identity?.decFirstName],
|
||||
[cipher.identity?.middleName, cipher.identity?.decMiddleName],
|
||||
[cipher.identity?.lastName, cipher.identity?.decLastName],
|
||||
[cipher.identity?.username, cipher.identity?.decUsername],
|
||||
[cipher.identity?.company, cipher.identity?.decCompany],
|
||||
[cipher.identity?.ssn, cipher.identity?.decSsn],
|
||||
[cipher.identity?.passportNumber, cipher.identity?.decPassportNumber],
|
||||
[cipher.identity?.licenseNumber, cipher.identity?.decLicenseNumber],
|
||||
[cipher.identity?.email, cipher.identity?.decEmail],
|
||||
[cipher.identity?.phone, cipher.identity?.decPhone],
|
||||
[cipher.identity?.address1, cipher.identity?.decAddress1],
|
||||
[cipher.identity?.address2, cipher.identity?.decAddress2],
|
||||
[cipher.identity?.address3, cipher.identity?.decAddress3],
|
||||
[cipher.identity?.city, cipher.identity?.decCity],
|
||||
[cipher.identity?.state, cipher.identity?.decState],
|
||||
[cipher.identity?.postalCode, cipher.identity?.decPostalCode],
|
||||
[cipher.identity?.country, cipher.identity?.decCountry],
|
||||
[cipher.sshKey?.privateKey, cipher.sshKey?.decPrivateKey],
|
||||
[cipher.sshKey?.publicKey, cipher.sshKey?.decPublicKey],
|
||||
[cipher.sshKey?.keyFingerprint || cipher.sshKey?.fingerprint, cipher.sshKey?.decFingerprint],
|
||||
...(cipher.fields || []).flatMap((field) => [
|
||||
[field.name, field.decName] as [unknown, unknown],
|
||||
[field.value, field.decValue] as [unknown, unknown],
|
||||
]),
|
||||
];
|
||||
return checks.some(([raw, decrypted]) => isEncryptedFieldUnresolved(raw, decrypted));
|
||||
}
|
||||
|
||||
export default function useVaultSendActions(options: UseVaultSendActionsOptions) {
|
||||
const {
|
||||
authedFetch,
|
||||
@@ -421,6 +471,9 @@ export default function useVaultSendActions(options: UseVaultSendActionsOptions)
|
||||
|
||||
async updateVaultItem(cipher: Cipher, draft: VaultDraft, options?: { addFiles?: File[]; removeAttachmentIds?: string[] }) {
|
||||
if (!session) return;
|
||||
if (hasUnresolvedCipherData(cipher)) {
|
||||
throw new Error(t('txt_decrypt_failed_2'));
|
||||
}
|
||||
const addFiles = Array.isArray(options?.addFiles) ? options.addFiles : [];
|
||||
const removeAttachmentIds = Array.isArray(options?.removeAttachmentIds) ? options.removeAttachmentIds : [];
|
||||
const previousCipher: Cipher = {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { decryptStr, decryptBw } from './crypto';
|
||||
import { looksLikeCipherString } from './app-support';
|
||||
import type { Cipher } from './types';
|
||||
|
||||
async function decryptCipherField(
|
||||
@@ -22,7 +23,7 @@ async function decryptCipherField(
|
||||
// Preserve the old raw fallback for fields that are genuinely unreadable.
|
||||
}
|
||||
}
|
||||
return value;
|
||||
return looksLikeCipherString(value) ? '' : value;
|
||||
}
|
||||
|
||||
export async function decryptSingleCipher(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { base64ToBytes, decryptBw, decryptStr } from './crypto';
|
||||
import { deriveSendKeyParts } from './app-support';
|
||||
import { deriveSendKeyParts, looksLikeCipherString } from './app-support';
|
||||
import type { Cipher, Folder, Send } from './types';
|
||||
|
||||
export interface DecryptVaultCoreArgs {
|
||||
@@ -38,7 +38,7 @@ async function decryptField(
|
||||
try {
|
||||
return await decryptStr(value, enc, mac);
|
||||
} catch {
|
||||
return value;
|
||||
return looksLikeCipherString(value) ? '' : value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ async function decryptCipherField(
|
||||
// Preserve the old raw fallback for fields that are genuinely unreadable.
|
||||
}
|
||||
}
|
||||
return value;
|
||||
return looksLikeCipherString(value) ? '' : value;
|
||||
}
|
||||
|
||||
async function decryptFieldWithSource(
|
||||
@@ -88,7 +88,7 @@ async function decryptFieldWithSource(
|
||||
// Keep plain fallback.
|
||||
}
|
||||
}
|
||||
return { text: raw, source: 'plain' };
|
||||
return { text: looksLikeCipherString(raw) ? '' : raw, source: 'plain' };
|
||||
}
|
||||
|
||||
export async function decryptVaultCore(args: DecryptVaultCoreArgs): Promise<DecryptVaultCoreResult> {
|
||||
|
||||
Reference in New Issue
Block a user