409 lines
16 KiB
JavaScript
409 lines
16 KiB
JavaScript
// SPDX-FileCopyrightText: 2023 XWiki CryptPad Team <contact@cryptpad.org> and contributors
|
||
//
|
||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||
|
||
define([
|
||
'jquery',
|
||
'/api/config',
|
||
'/common/hyperscript.js',
|
||
'/customize/messages.js',
|
||
'/components/nthen/index.js',
|
||
'/common/common-hash.js',
|
||
'/common/common-util.js',
|
||
'/common/common-constants.js',
|
||
'/common/cryptget.js',
|
||
'/common/cryptpad-common.js',
|
||
'/common/cache-store.js',
|
||
'/common/common-interface.js',
|
||
'chainpad-netflux',
|
||
'/components/chainpad-crypto/crypto.js',
|
||
'/common/user-object.js',
|
||
'/common/clipboard.js',
|
||
'/common/outer/login-block.js',
|
||
//'/common/outer/roster.js',
|
||
'/common/rpc.js',
|
||
'/common/pinpad.js',
|
||
'/common/outer/local-store.js',
|
||
|
||
|
||
'/components/tweetnacl/nacl-fast.min.js',
|
||
'css!/components/components-font-awesome/css/font-awesome.min.css',
|
||
'less!/customize/src/less2/pages/page-report.less',
|
||
], function ($, ApiConfig, h, Messages,
|
||
nThen, Hash, Util, Constants, Crypt, Cryptpad, Cache, UI, CPNetflux,
|
||
Crypto, UserObject, Clipboard, Block, /*Roster,*/ Rpc, Pinpad, LocalStore) {
|
||
var $report = $('#cp-report');
|
||
var blockHash = localStorage.Block_hash;
|
||
if (!blockHash) {
|
||
return void UI.alert(Messages.mustLogin, function () {
|
||
var href = Hash.hashToHref('', 'login');
|
||
var url = Hash.getNewPadURL(href, {
|
||
href: '/report/',
|
||
});
|
||
console.log(url);
|
||
window.location.href = url;
|
||
});
|
||
}
|
||
|
||
let report = true;
|
||
let fixRoster = false;
|
||
|
||
let urlHash = window.location.hash.slice(1);
|
||
if (urlHash) {
|
||
let s = urlHash.split('=');
|
||
let key = s[0];
|
||
let value = s[1];
|
||
if (key === "roster") {
|
||
report = false;
|
||
fixRoster = value; // teamid
|
||
}
|
||
}
|
||
|
||
|
||
var addReport = function (str) {
|
||
$report.append(h('div', str));
|
||
};
|
||
|
||
var getReportContent = window.getReportContent = function () {
|
||
try {
|
||
return $report[0].innerText;
|
||
} catch (err) {
|
||
return '';
|
||
}
|
||
};
|
||
|
||
var copyToClipboard = function () {
|
||
Clipboard.copy(getReportContent(), (err) => {
|
||
if (err) { return UI.warn(Messages.error); }
|
||
UI.log(Messages.genericCopySuccess);
|
||
});
|
||
};
|
||
|
||
var checkCache = function (chan, cb) {
|
||
Cache.getChannelCache(chan, function (err, val) {
|
||
if (err) {
|
||
addReport('Cache error:' + err);
|
||
} else {
|
||
addReport('Cache: ' + val.c.length + ' entries');
|
||
}
|
||
cb();
|
||
});
|
||
};
|
||
var onCacheReady = function (info) {
|
||
var doc;
|
||
try {
|
||
doc = info.realtime.getUserDoc();
|
||
JSON.parse(doc);
|
||
addReport('Cache ready success: ' + info.id + ' - Length: ' + doc.length);
|
||
} catch (e) {
|
||
addReport('Cache ready error: ' + info.id + ' - Length: ' + (doc || '').length);
|
||
}
|
||
};
|
||
|
||
var network;
|
||
var proxy;
|
||
var hash;
|
||
|
||
nThen(function (waitFor) {
|
||
let done = waitFor();
|
||
var parsed = Block.parseBlockHash(blockHash);
|
||
var sessionToken = LocalStore.getSessionToken() || undefined;
|
||
Util.getBlock(parsed.href, {
|
||
bearer: sessionToken
|
||
}, waitFor((err, response) => {
|
||
if (err) {
|
||
waitFor.abort();
|
||
return void UI.alert('Invalid user');
|
||
}
|
||
response.arrayBuffer().then(arraybuffer => {
|
||
arraybuffer = new Uint8Array(arraybuffer);
|
||
// use the results to load your user hash and
|
||
// put your userhash into localStorage
|
||
try {
|
||
var block_info = Block.decrypt(arraybuffer, parsed.keys);
|
||
if (!block_info) { return console.error("Failed to decrypt !"); }
|
||
hash = block_info[Constants.userHashKey];
|
||
if (!hash) {
|
||
return void console.error("failed to decrypt or decode block content");
|
||
}
|
||
} catch (e) {
|
||
return void console.error("failed to decrypt or decode block content");
|
||
}
|
||
done();
|
||
});
|
||
}));
|
||
}).nThen(function (waitFor) {
|
||
Cryptpad.makeNetwork(waitFor(function (err, _network) {
|
||
if (err) {
|
||
console.error(err);
|
||
waitFor.abort();
|
||
return void UI.errorLoadingScreen(err);
|
||
}
|
||
network = _network;
|
||
}));
|
||
}).nThen(function (waitFor) {
|
||
addReport('BEGIN REPORT');
|
||
var secret = Hash.getSecrets('drive', hash);
|
||
addReport('Load drive. ID: ' + secret.channel);
|
||
checkCache(secret.channel, waitFor());
|
||
}).nThen(function (waitFor) {
|
||
Crypt.get(hash, waitFor(function (err, val) {
|
||
if (err) {
|
||
console.error(err);
|
||
addReport('Load drive error. err: ' + err);
|
||
return void waitFor.abort();
|
||
}
|
||
try {
|
||
proxy = JSON.parse(val);
|
||
} catch (e) {
|
||
console.error(e);
|
||
addReport('Load drive error. Parse error: ' + e);
|
||
waitFor.abort();
|
||
}
|
||
}), { network: network, onCacheReady: onCacheReady});
|
||
}).nThen(function (waitFor) {
|
||
var drive = proxy.drive || {};
|
||
if (!proxy.drive) {
|
||
addReport('ERROR: no drive');
|
||
return void waitFor.abort();
|
||
}
|
||
addReport('Load drive success.');
|
||
addReport('Public key: '+proxy.edPublic);
|
||
addReport('Shared folders: ' + Object.keys(drive.sharedFolders || {}).join(', '));
|
||
addReport('Teams: ' + Object.keys(proxy.teams || {}).join(', '));
|
||
addReport('-------------------');
|
||
|
||
if (!report) { return; }
|
||
|
||
var n = nThen;
|
||
Object.keys(drive.sharedFolders || {}).forEach(function (id) {
|
||
n = n(function (w) {
|
||
var next = w();
|
||
var obj = drive.sharedFolders[id];
|
||
addReport('Load shared folder. ID: ' + id + '. Channel ID: '+ obj.channel);
|
||
if (obj.password) { addReport("Password protected"); }
|
||
if (!obj.href) { addReport("View only"); }
|
||
checkCache(obj.channel, function () {
|
||
var parsed = Hash.parsePadUrl(obj.href || obj.roHref);
|
||
Crypt.get(parsed.hash, function (err, val, errorObj) {
|
||
if (err) {
|
||
addReport('ERROR: ' + err);
|
||
if (err === "ERESTRICTED") {
|
||
addReport('RESTRICTED: ' + (errorObj && errorObj.message));
|
||
}
|
||
} else {
|
||
addReport('Load shared folder: success. Size: ' + val.length);
|
||
}
|
||
addReport('-------------------');
|
||
next();
|
||
}, {
|
||
network: network,
|
||
password: obj.password,
|
||
onCacheReady: onCacheReady
|
||
});
|
||
|
||
});
|
||
}).nThen;
|
||
});
|
||
n(waitFor());
|
||
}).nThen(function () {
|
||
addReport('===================');
|
||
var n = nThen;
|
||
Object.keys(proxy.teams || {}).forEach(function (id) {
|
||
// If we're in "repair" mode, only load the affected team
|
||
var obj = proxy.teams[id];
|
||
if (!report && fixRoster !== obj.channel) { return; }
|
||
n = n(function (w) {
|
||
var next = w();
|
||
var team;
|
||
addReport('Load team. ID: ' + id + '. Channel ID: '+ obj.channel);
|
||
addReport('Roster channel: ' + obj.keys.roster.channel);
|
||
addReport('Roster lkh: ' + obj.keys.roster.lastKnownHash);
|
||
if (!obj.hash) { addReport("View only"); }
|
||
|
||
var teamSecret = Hash.getSecrets('team', obj.hash || obj.roHash, obj.password);
|
||
var cryptor = UserObject.createCryptor(teamSecret.keys.secondaryKey);
|
||
|
||
|
||
// Repair roster mode
|
||
/*
|
||
if (!report) {
|
||
let roster = obj.keys.roster;
|
||
let rpc, anon_rpc;
|
||
if (!obj.owner || !roster.edit) {
|
||
return void addReport('Roster error: only owners can repair');
|
||
}
|
||
n(w => {
|
||
Rpc.createAnonymous(network, w(function (e, call) {
|
||
anon_rpc = call;
|
||
}));
|
||
Pinpad.create(network, proxy, w(function (e, call) {
|
||
rpc = call;
|
||
}));
|
||
}).nThen(w => {
|
||
if (!anon_rpc || !rpc) {
|
||
return void addReport('RPC error');
|
||
}
|
||
var rosterKeys = Crypto.Team.deriveMemberKeys(roster.edit, {
|
||
curvePublic: proxy.curvePublic,
|
||
curvePrivate: proxy.curvePrivate
|
||
});
|
||
let store = { anon_rpc };
|
||
Roster.create({
|
||
network: network,
|
||
channel: roster.channel,
|
||
keys: rosterKeys,
|
||
store: store,
|
||
lastKnownHash: roster.lastKnownHash
|
||
}, w(function (err, _roster) {
|
||
if (err) { return void addReport('Fix roster error', err); }
|
||
_roster.checkpoint(w((err, state, hash) => {
|
||
if (err) { addReport('Fix roster error', err); }
|
||
rpc.trimHistory({
|
||
channel: roster.channel,
|
||
hash
|
||
}, w(function (err) {
|
||
console.error(arguments);
|
||
if (err) { addReport('Trim roster error', err); }
|
||
}));
|
||
}));
|
||
}));
|
||
}).nThen(next);
|
||
return;
|
||
}*/
|
||
|
||
// Check team drive
|
||
nThen(function (ww) {
|
||
addReport('Team drive');
|
||
var _next = ww();
|
||
checkCache(obj.channel, function () {
|
||
Crypt.get(obj.hash || obj.roHash, function (err, val) {
|
||
if (err) {
|
||
addReport('ERROR: ' + err);
|
||
addReport('===================');
|
||
next();
|
||
ww.abort();
|
||
} else {
|
||
addReport('Team drive success. Size: ' + val.length);
|
||
try {
|
||
team = JSON.parse(val);
|
||
} catch (e) {
|
||
addReport('PARSE ERROR: ' + e);
|
||
addReport('===================');
|
||
next();
|
||
ww.abort();
|
||
}
|
||
addReport('Shared folders: ' + Object.keys(team.drive.sharedFolders || {}).join(', '));
|
||
}
|
||
addReport('-------------------');
|
||
_next();
|
||
}, {
|
||
network: network,
|
||
password: obj.password,
|
||
onCacheReady: onCacheReady
|
||
});
|
||
});
|
||
}).nThen(function (ww) {
|
||
var _next = ww();
|
||
var d = Util.find(obj, ['keys', 'roster']);
|
||
|
||
var rosterKeys = d.edit ? Crypto.Team.deriveMemberKeys(d.edit, proxy)
|
||
: Crypto.Team.deriveGuestKeys(d.view || '');
|
||
|
||
if (d.channel !== rosterKeys.channel) {
|
||
next();
|
||
ww.abort();
|
||
addReport("Invalid roster keys:", d.channel, rosterKeys.channel);
|
||
return;
|
||
}
|
||
addReport('Roster channel: ' + d.channel);
|
||
checkCache(d.channel, function () {
|
||
var crypto = Crypto.Team.createEncryptor(rosterKeys);
|
||
var m = 0;
|
||
CPNetflux.start({
|
||
lastKnownHash: d.lastKnownHash || -1,
|
||
network: network,
|
||
channel: d.channel,
|
||
crypto: crypto,
|
||
validateKey: rosterKeys.teamEdPublic,
|
||
Cache: Cache,
|
||
noChainPad: true,
|
||
onCacheReady: onCacheReady,
|
||
onChannelError: function (obj) {
|
||
addReport('ERROR:' + obj.error);
|
||
if (obj.error === "ERESTRICTED") {
|
||
addReport('RESTRICTED: ' + obj.message);
|
||
}
|
||
next();
|
||
ww.abort();
|
||
addReport('===================');
|
||
return;
|
||
},
|
||
onMessage: function () {
|
||
m++;
|
||
},
|
||
onReady: function () {
|
||
addReport("Roster success. Length: "+m);
|
||
addReport('-------------------');
|
||
_next();
|
||
}
|
||
});
|
||
});
|
||
}).nThen(function (ww) {
|
||
var _next = ww();
|
||
var n = nThen;
|
||
var drive = team.drive;
|
||
Object.keys(drive.sharedFolders || {}).forEach(function (id) {
|
||
n = n(function (w) {
|
||
var next = w();
|
||
var _obj = drive.sharedFolders[id];
|
||
addReport('Load shared folder. ID: ' + id + '. Channel ID: '+ _obj.channel);
|
||
if (_obj.password) { addReport("Password protected"); }
|
||
if (!_obj.href) { addReport("View only"); }
|
||
checkCache(_obj.channel, function () {
|
||
if (_obj.href && _obj.href.indexOf('#') === -1) {
|
||
_obj.href = cryptor.decrypt(_obj.href);
|
||
}
|
||
var parsed = Hash.parsePadUrl(_obj.href || _obj.roHref);
|
||
Crypt.get(parsed.hash, function (err, val, errorObj) {
|
||
if (err) {
|
||
addReport('ERROR: ' + err);
|
||
if (err === "ERESTRICTED") {
|
||
addReport('RESTRICTED: ' + (errorObj && errorObj.message));
|
||
}
|
||
} else {
|
||
addReport('Load shared folder: success. Size: ' + val.length);
|
||
}
|
||
addReport('-------------------');
|
||
next();
|
||
}, {
|
||
network: network,
|
||
password: _obj.password,
|
||
onCacheReady: onCacheReady
|
||
});
|
||
|
||
});
|
||
}).nThen;
|
||
});
|
||
n(_next);
|
||
}).nThen(next);
|
||
|
||
|
||
}).nThen;
|
||
});
|
||
n(function () {
|
||
addReport('===================');
|
||
addReport('DONE');
|
||
|
||
var copyButton = h('button.btn.btn-primary', Messages.copyToClipboard);
|
||
copyButton.onclick = copyToClipboard;
|
||
var buttonContainer = h('div#cp-report-ui', [
|
||
copyButton,
|
||
]);
|
||
|
||
document.body.appendChild(buttonContainer);
|
||
});
|
||
});
|
||
|
||
|
||
});
|