Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 4x 3x 1x 1x 1x 4x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 47x 8x 39x 39x 34x 5x 1x 1x 1x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x | "use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.repairFileBlobs = repairFileBlobs;
const File_1 = require("../../Model/File");
const PassmanCrypto_1 = require("../PassmanCrypto");
const payloadCrypto_1 = require("./payloadCrypto");
const fileBlobScan_1 = require("./fileBlobScan");
const fileRefs_1 = require("./fileRefs");
async function decryptFileBlobWithWorkingKey(credential, hit, fileHit, candidates, vault) {
const downloaded = await (0, fileBlobScan_1.downloadRawFileBlob)(credential, hit.kind, {
filename: fileHit.filename,
size: 0,
mimetype: '',
data: '',
file_id: fileHit.file_id,
guid: fileHit.guid
}, vault);
Iif (!downloaded || typeof downloaded.file_data !== 'string') {
throw new Error('Failed to download file blob: ' + fileHit.filename);
}
const working = (0, payloadCrypto_1.requireCandidate)(candidates, fileHit.workingKeyId, 'file blob');
return PassmanCrypto_1.PassmanCrypto.decryptString(downloaded.file_data, working.password);
}
function findLiveFileRef(credential, fileHit) {
if (fileHit.source === 'files') {
return credential.files.find((file) => (0, fileRefs_1.fileRefMatchesHit)(file, fileHit));
}
for (const field of credential.custom_fields) {
Eif (field.field_type === 'file' && (0, fileRefs_1.isFileRef)(field.value) && (0, fileRefs_1.fileRefMatchesHit)(field.value, fileHit)) {
return field.value;
}
}
return undefined;
}
function replaceLiveFileRef(credential, fileHit, replacement) {
if (fileHit.source === 'files') {
const files = [...credential.files];
const index = files.findIndex((file) => (0, fileRefs_1.fileRefMatchesHit)(file, fileHit));
Iif (index === -1) {
throw new Error('File reference not found in files[]: ' + fileHit.filename);
}
files[index] = replacement;
credential.files = files;
return;
}
const customFields = credential.custom_fields.map((field) => ({ ...field }));
const index = customFields.findIndex((field) => field.field_type === 'file' && (0, fileRefs_1.isFileRef)(field.value) && (0, fileRefs_1.fileRefMatchesHit)(field.value, fileHit));
Iif (index === -1) {
throw new Error('File reference not found in custom_fields: ' + fileHit.filename);
}
customFields[index] = { ...customFields[index], value: replacement };
credential.custom_fields = customFields;
}
/**
* Re-uploads blobs that decrypt with a non-destination key. Partial success per file.
*
* - shared_with_us: diagnostic only (no owner writes)
* - dry-run: GET + decrypt, no upload/delete
*/
async function repairFileBlobs(vault, credential, hit, candidates, preferred, dryRun) {
if (hit.kind === 'shared_with_us') {
return;
}
const needingRewrap = hit.fileHits.filter((fileHit) => fileHit.status === 'needs_rewrap');
if (needingRewrap.length === 0) {
return;
}
if (dryRun) {
for (const fileHit of needingRewrap) {
await decryptFileBlobWithWorkingKey(credential, hit, fileHit, candidates, vault);
}
return;
}
credential.overwriteVaultKey = preferred.password;
const oldFilesToDelete = [];
const newFilesToDeleteOnFailure = [];
const errors = [];
let refsChanged = false;
for (const fileHit of needingRewrap) {
try {
const plaintext = await decryptFileBlobWithWorkingKey(credential, hit, fileHit, candidates, vault);
const original = findLiveFileRef(credential, fileHit);
Iif (!original) {
throw new Error('File reference not found on credential: ' + fileHit.filename);
}
const plainFile = {
data: plaintext,
filename: original.filename ?? fileHit.filename,
mimetype: original.mimetype,
size: original.size
};
const uploadResponse = await credential.encryptUploadFile(plainFile);
Iif (!uploadResponse) {
throw new Error('File upload returned no response');
}
const replacement = {
filename: original.filename ?? fileHit.filename,
size: original.size,
mimetype: original.mimetype,
data: '',
file_id: uploadResponse.file_id,
guid: uploadResponse.guid,
created: original.created
};
replaceLiveFileRef(credential, fileHit, replacement);
oldFilesToDelete.push({
filename: original.filename ?? fileHit.filename,
size: original.size,
mimetype: original.mimetype,
data: '',
file_id: original.file_id,
guid: original.guid
});
newFilesToDeleteOnFailure.push(replacement);
refsChanged = true;
}
catch (e) {
const message = e instanceof Error ? e.message : String(e);
errors.push(`${fileHit.filename}: ${message}`);
}
}
Eif (refsChanged) {
credential.skip_revision = true;
const response = await credential.update();
Iif (!response) {
// roll-back uploaded files on failure to not leave a mess
for (const uploaded of newFilesToDeleteOnFailure) {
await File_1.File.deleteFile(uploaded, vault.getServer());
}
throw new Error('Server rejected the credential update after file re-upload.');
}
// when everything worked well, we can delete the old inconsistently encrypted files
for (const oldFile of oldFilesToDelete) {
Eif (oldFile.file_id !== undefined) {
await File_1.File.deleteFile(oldFile, vault.getServer());
}
}
}
Iif (errors.length > 0) {
throw new Error('File rewrap incomplete: ' + errors.join('; '));
}
}
|