mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 21:00:41 +00:00
feat: add URI checksum repair functionality for ciphers
This commit is contained in:
@@ -666,6 +666,136 @@ async function getCipherKeys(
|
||||
return { enc: userEnc, mac: userMac, key: null };
|
||||
}
|
||||
|
||||
async function repairCipherLoginUris(
|
||||
cipher: Cipher,
|
||||
enc: Uint8Array,
|
||||
mac: Uint8Array
|
||||
): Promise<{ login: Cipher['login']; changed: boolean }> {
|
||||
if (!cipher.login || !Array.isArray(cipher.login.uris)) {
|
||||
return { login: cipher.login ?? null, changed: false };
|
||||
}
|
||||
|
||||
let changed = false;
|
||||
const uris: Array<Record<string, unknown>> = [];
|
||||
|
||||
for (const entry of cipher.login.uris) {
|
||||
if (!entry || typeof entry !== 'object') continue;
|
||||
const { decUri: _decUri, ...encryptedEntry } = entry as Record<string, unknown>;
|
||||
const rawUri = typeof entry.uri === 'string' ? entry.uri.trim() : '';
|
||||
if (!looksLikeCipherString(rawUri)) {
|
||||
uris.push({ ...encryptedEntry });
|
||||
continue;
|
||||
}
|
||||
|
||||
let clearUri = String(entry.decUri || '').trim();
|
||||
if (!clearUri || looksLikeCipherString(clearUri)) {
|
||||
try {
|
||||
clearUri = (await decryptStr(rawUri, enc, mac)).trim();
|
||||
} catch {
|
||||
uris.push({ ...encryptedEntry });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clearUri) {
|
||||
uris.push({ ...encryptedEntry });
|
||||
continue;
|
||||
}
|
||||
|
||||
const expectedChecksum = await sha256Base64(clearUri);
|
||||
let currentChecksumOk = false;
|
||||
const rawChecksum = typeof entry.uriChecksum === 'string' ? entry.uriChecksum.trim() : '';
|
||||
if (looksLikeCipherString(rawChecksum)) {
|
||||
try {
|
||||
currentChecksumOk = (await decryptStr(rawChecksum, enc, mac)) === expectedChecksum;
|
||||
} catch {
|
||||
currentChecksumOk = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentChecksumOk) {
|
||||
uris.push({ ...encryptedEntry });
|
||||
continue;
|
||||
}
|
||||
|
||||
uris.push({
|
||||
...encryptedEntry,
|
||||
uri: rawUri,
|
||||
uriChecksum: await encryptTextValue(expectedChecksum, enc, mac),
|
||||
match: typeof entry.match === 'number' && Number.isFinite(entry.match) ? entry.match : null,
|
||||
});
|
||||
changed = true;
|
||||
}
|
||||
|
||||
const {
|
||||
decUsername: _decUsername,
|
||||
decPassword: _decPassword,
|
||||
decTotp: _decTotp,
|
||||
...encryptedLogin
|
||||
} = cipher.login as Record<string, unknown>;
|
||||
|
||||
return {
|
||||
login: {
|
||||
...encryptedLogin,
|
||||
uris: uris as Cipher['login']['uris'],
|
||||
} as Cipher['login'],
|
||||
changed,
|
||||
};
|
||||
}
|
||||
|
||||
export async function repairCipherUriChecksums(
|
||||
authedFetch: AuthedFetch,
|
||||
session: SessionState,
|
||||
ciphers: Cipher[]
|
||||
): Promise<number> {
|
||||
if (!session.symEncKey || !session.symMacKey || !Array.isArray(ciphers) || ciphers.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const userEnc = base64ToBytes(session.symEncKey);
|
||||
const userMac = base64ToBytes(session.symMacKey);
|
||||
let repaired = 0;
|
||||
|
||||
for (const cipher of ciphers) {
|
||||
if (!cipher?.id || cipher.type !== 1 || !looksLikeCipherString(cipher.key) || !cipher.login || !Array.isArray(cipher.login.uris)) continue;
|
||||
let itemKey: Uint8Array;
|
||||
try {
|
||||
itemKey = await decryptBw(String(cipher.key).trim(), userEnc, userMac);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (itemKey.length < 64) continue;
|
||||
const keys = { enc: itemKey.slice(0, 32), mac: itemKey.slice(32, 64), key: String(cipher.key).trim() };
|
||||
const repair = await repairCipherLoginUris(cipher, keys.enc, keys.mac);
|
||||
if (!repair.changed) continue;
|
||||
|
||||
const payload: Record<string, unknown> = {
|
||||
type: cipher.type,
|
||||
folderId: cipher.folderId ?? null,
|
||||
favorite: !!cipher.favorite,
|
||||
reprompt: cipher.reprompt ?? 0,
|
||||
name: cipher.name ?? null,
|
||||
notes: cipher.notes ?? null,
|
||||
login: repair.login,
|
||||
fields: Array.isArray(cipher.fields)
|
||||
? cipher.fields.map(({ decName: _decName, decValue: _decValue, ...field }) => field)
|
||||
: null,
|
||||
key: keys.key,
|
||||
lastKnownRevisionDate: cipher.revisionDate ?? null,
|
||||
};
|
||||
|
||||
const resp = await authedFetch(`/api/ciphers/${encodeURIComponent(cipher.id)}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (!resp.ok) throw new Error(await parseErrorMessage(resp, 'Repair URI checksum failed'));
|
||||
repaired += 1;
|
||||
}
|
||||
|
||||
return repaired;
|
||||
}
|
||||
|
||||
async function buildCipherPayload(
|
||||
session: SessionState,
|
||||
draft: VaultDraft,
|
||||
|
||||
Reference in New Issue
Block a user