Placeholder on file deletion

This commit is contained in:
yflory 2023-09-07 17:03:20 +02:00
parent dda4b8777c
commit 91af47994b
23 changed files with 305 additions and 108 deletions

View File

@ -94,8 +94,13 @@ const init = (cb) => {
}).nThen(cb);
};
COMMANDS.start = (edPublic, blockId) => {
COMMANDS.start = (edPublic, blockId, reason) => {
const safeKey = Util.escapeKeyCharacters(edPublic);
const archiveReason = {
code: 'MODERATION_ACCOUNT',
txt: reason
};
let ref = {};
let blobsToArchive = [];
let channelsToArchive = [];
@ -142,7 +147,7 @@ COMMANDS.start = (edPublic, blockId) => {
// Archive the pads
channelsToArchive.forEach((chanId) => {
n = n((w) => {
Env.store.archiveChannel(chanId, w(function (err) {
Env.store.archiveChannel(chanId, archiveReason, w(function (err) {
if (err) {
return Log.error('MODERATION_CHANNEL_ARCHIVAL_ERROR', {
error: err,
@ -157,7 +162,7 @@ COMMANDS.start = (edPublic, blockId) => {
// Archive the blobs
blobsToArchive.forEach((blobId) => {
n = n((w) => {
Env.blobStore.archive.blob(blobId, w(function (err) {
Env.blobStore.archive.blob(blobId, archiveReason, w(function (err) {
if (err) {
return Log.error('MODERATION_BLOB_ARCHIVAL_ERROR', {
error: err,
@ -171,7 +176,7 @@ COMMANDS.start = (edPublic, blockId) => {
});
n(waitFor(() => {
// Archive the pin log
Env.pinStore.archiveChannel(safeKey, waitFor(function (err) {
Env.pinStore.archiveChannel(safeKey, undefined, waitFor(function (err) {
if (err) {
return Log.error('MODERATION_ACCOUNT_PIN_LOG', err, waitFor());
}

View File

@ -175,20 +175,26 @@ var archiveDocument = function (Env, Server, cb, data) {
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
const archiveReason = {
code: 'MODERATION_PAD',
txt: reason
};
const reasonStr = `MODERATION_PAD:${reason}`;
switch (id.length) {
case 32:
return void Env.msgStore.archiveChannel(id, Util.both(cb, function (err) {
return void Env.msgStore.archiveChannel(id, archiveReason, Util.both(cb, function (err) {
Env.Log.info("ARCHIVAL_CHANNEL_BY_ADMIN_RPC", {
channelId: id,
reason: reason,
status: err? String(err): "SUCCESS",
});
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', err => {
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reasonStr, err => {
if (err) { } // TODO
});
}));
case 48:
return void Env.blobStore.archive.blob(id, Util.both(cb, function (err) {
return void Env.blobStore.archive.blob(id, archiveReason, Util.both(cb, function (err) {
Env.Log.info("ARCHIVAL_BLOB_BY_ADMIN_RPC", {
id: id,
reason: reason,
@ -213,7 +219,7 @@ var removeDocument = function (Env, Server, cb, data) {
id = args;
} else if (args && typeof(args) === 'object') {
id = args.id;
reason = args.reason;
reason = `MODERATION_DESTROY:${args.reason}`;
}
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
@ -226,7 +232,7 @@ var removeDocument = function (Env, Server, cb, data) {
reason: reason,
status: err? String(err): "SUCCESS",
});
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', err => {
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reason, err => {
if (err) { } // TODO
});
}));
@ -291,17 +297,20 @@ var archiveAccount = function (Env, Server, _cb, data) {
return worker.postMessage({
command: 'start',
content: data && data[1],
block: data && data[2]
block: data && data[2],
reason: data && data[3]
});
}
// DONE: disconnect all users from these channels
const reason = `MODERATION_ACCOUNT:${data && data[3]}`;
var deletedChannels = Util.tryParse(message);
if (Array.isArray(deletedChannels)) {
let n = nThen;
deletedChannels.forEach((chanId) => {
n = n((w) => {
setTimeout(w(() => {
Channel.disconnectChannelMembers(Env, Server, chanId, 'EDELETED', () => {});
Channel.disconnectChannelMembers(Env, Server, chanId, 'EDELETED', reason, () => {});
}), 10);
}).nThen;
});
@ -645,7 +654,7 @@ var archivePinLog = function (Env, Server, cb, data) {
if (!isValidKey(key)) { return void cb("EINVAL"); }
var safeKey = Util.escapeKeyCharacters(key);
Env.pinStore.archiveChannel(safeKey, function (err) {
Env.pinStore.archiveChannel(safeKey, undefined, function (err) {
Core.expireSession(Env.Sessions, safeKey);
if (err) {
Env.Log.error('ARCHIVE_PIN_LOG_BY_ADMIN', {

View File

@ -8,7 +8,7 @@ const Metadata = require("./metadata");
const HK = require("../hk-util");
const Nacl = require("tweetnacl/nacl-fast");
Channel.disconnectChannelMembers = function (Env, Server, channelId, code, cb) {
Channel.disconnectChannelMembers = function (Env, Server, channelId, code, reason, cb) {
var done = Util.once(Util.mkAsync(cb));
if (!Core.isValidId(channelId)) { return done('INVALID_ID'); }
@ -39,6 +39,7 @@ Channel.disconnectChannelMembers = function (Env, Server, channelId, code, cb) {
userId,
JSON.stringify({
error: code, //'EDELETED',
message: reason,
channel: channelId,
})
], w());
@ -52,6 +53,7 @@ Channel.disconnectChannelMembers = function (Env, Server, channelId, code, cb) {
Env.Log.warn('DISCONNECT_CHANNEL_MEMBERS_TIMEOUT', {
channelId,
code,
reason
});
clear();
done();
@ -105,9 +107,10 @@ Channel.clearOwnedChannel = function (Env, safeKey, channelId, cb, Server) {
});
};
var archiveOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
var archiveOwnedChannel = function (Env, safeKey, channelId, reason, __cb, Server) {
var _cb = Util.once(Util.mkAsync(__cb));
var unsafeKey = Util.unescapeKeyCharacters(safeKey);
reason = reason || 'ARCHIVE_OWNED';
nThen(function (w) {
// confirm that the channel exists before worrying about whether
// we have permission to delete it.
@ -130,7 +133,7 @@ var archiveOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
}).nThen(function () {
var cb = _cb;
// temporarily archive the file
return void Env.msgStore.archiveChannel(channelId, function (e) {
return void Env.msgStore.archiveChannel(channelId, reason, function (e) {
Env.Log.info('ARCHIVAL_CHANNEL_BY_OWNER_RPC', {
unsafeKey: unsafeKey,
channelId: channelId,
@ -141,16 +144,19 @@ var archiveOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
}
cb(void 0, 'OK');
Channel.disconnectChannelMembers(Env, Server, channelId, 'EDELETED', err => {
Channel.disconnectChannelMembers(Env, Server, channelId, 'EDELETED', reason, err => {
if (err) { } // TODO
});
});
});
};
Channel.removeOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
Channel.removeOwnedChannel = function (Env, safeKey, obj, __cb, Server) {
var _cb = Util.once(Util.mkAsync(__cb));
var channelId = obj.channel;
var reason = obj.reason;
if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) {
return _cb('INVALID_ARGUMENTS');
}
@ -160,9 +166,9 @@ Channel.removeOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
Env.queueDeletes(safeKey, function (next) {
var cb = Util.both(_cb, next);
if (Env.blobStore.isFileId(channelId)) {
return void Env.removeOwnedBlob(channelId, safeKey, cb);
return void Env.removeOwnedBlob(channelId, safeKey, reason, cb);
}
archiveOwnedChannel(Env, safeKey, channelId, cb, Server);
archiveOwnedChannel(Env, safeKey, channelId, reason, cb, Server);
});
};
@ -258,15 +264,18 @@ Channel.isNewChannel = function (Env, channel, cb) {
var msg = msgObj.buff.toString('utf8');
if (typeof(msg) === 'string' && ARRAY_LINE.test(msg)) {
abort();
return void cb(void 0, false);
return void cb(void 0, {isNew: false});
}
} catch (e) {
Env.WARN('invalid message read from store', e);
}
readMore();
}, function () {
}, function (err, reason) {
// no more messages...
cb(void 0, true);
cb(void 0, {
isNew: true,
reason: reason
});
});
};

View File

@ -115,7 +115,7 @@ Pinning.getTotalSize = function (Env, safeKey, cb) {
*/
Pinning.removePins = function (Env, safeKey, cb) {
// FIXME respect the queue
Env.pinStore.archiveChannel(safeKey, function (err) {
Env.pinStore.archiveChannel(safeKey, undefined, function (err) {
Core.expireSession(Env.Sessions, safeKey);
Env.Log.info('ARCHIVAL_PIN_BY_OWNER_RPC', {
safeKey: safeKey,

View File

@ -540,7 +540,7 @@ module.exports = function (Env, cb) {
}
// remove the pin logs of inactive accounts if inactive account removal is configured
pinStore.archiveChannel(id, function (err) {
pinStore.archiveChannel(id, undefined, function (err) {
if (err) {
return Log.error('EVICT_INACTIVE_ACCOUNT_PIN_LOG', err, next);
}
@ -599,7 +599,7 @@ module.exports = function (Env, cb) {
if (item.mtime > inactiveTime) { return void next(); }
removed++;
blobs.archive.blob(item.blobId, function (err) {
blobs.archive.blob(item.blobId, 'INACTIVE', function (err) {
if (err) {
return Log.error("EVICT_ARCHIVE_BLOB_ERROR", {
error: err,
@ -724,7 +724,7 @@ module.exports = function (Env, cb) {
// else fall through to the archival
}));
}).nThen(function (w) {
return void store.archiveChannel(item.channel, w(function (err) {
return void store.archiveChannel(item.channel, 'INACTIVE', w(function (err) {
if (err) {
Log.error('EVICT_CHANNEL_ARCHIVAL_ERROR', {
error: err,

View File

@ -124,7 +124,7 @@ var CHECKPOINT_PATTERN = /^cp\|(([A-Za-z0-9+\/=]+)\|)?/;
but for some reason are still present
*/
const expireChannel = HK.expireChannel = function (Env, channel) {
return void Env.store.archiveChannel(channel, function (err) {
return void Env.store.archiveChannel(channel, 'EXPIRED', function (err) {
Env.Log.info("ARCHIVAL_CHANNEL_BY_HISTORY_KEEPER_EXPIRATION", {
channelId: channel,
status: err? String(err): "SUCCESS",
@ -526,7 +526,12 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
getHistoryOffset(Env, channelName, lastKnownHash, waitFor((err, os) => {
if (err) {
waitFor.abort();
return void cb(err);
var reason;
if (err && err.reason) {
reason = err.reason;
err = err.error;
}
return void cb(err, reason);
}
offset = os;
}));
@ -540,8 +545,8 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
const parsed = tryParse(Env, msgObj.buff.toString('utf8'));
if (!parsed) { return void readMore(); }
handler(parsed, readMore);
}, waitFor(function (err) {
return void cb(err);
}, waitFor(function (err, reason) {
return void cb(err, reason);
}));
});
};
@ -682,7 +687,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
if (txid) { msg[0] = txid; }
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
}, (err) => {
}, (err, reason) => {
// Any error but ENOENT: abort
// ENOENT is allowed in case we want to create a new pad
if (err && err.code !== 'ENOENT') {
@ -700,10 +705,15 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
stack: err && err.stack,
}); }
// FIXME err.message isn't useful for users
const parsedMsg = {error:err.message, channel: channelName, txid: txid};
const parsedMsg = {error:err.message, test:'a', channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return;
}
if (err && err.code === 'ENOENT' && reason) {
const parsedMsg2 = {error:'EDELETED', message: reason, channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg2)]);
return;
}
// If we're asking for a specific version (lastKnownHash) but we receive an
// ENOENT, this is not a pad creation so we need to abort.
@ -716,7 +726,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
is no longer on the server so they don't abuse the data and so that they don't unintentionally continue
to edit it in a broken state.
*/
const parsedMsg2 = {error:'EDELETED', channel: channelName, txid: txid};
const parsedMsg2 = {error:'EDELETED', test:'b',channel: channelName, txid: txid};
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg2)]);
return;
}

View File

@ -47,6 +47,10 @@ var makeProofPath = function (Env, safeKey, blobId) {
return Path.join(Env.blobPath, safeKey.slice(0, 3), safeKey, blobId.slice(0, 2), blobId);
};
var mkPlaceholderPath = function (Env, blobId) {
return makeBlobPath(Env, blobId) + '.placeholder';
};
var parseProofPath = function (path) {
var parts = path.split('/');
return {
@ -55,6 +59,26 @@ var parseProofPath = function (path) {
};
};
// Placeholder for deleted files
var addPlaceholder = function (Env, blobId, reason, cb) {
if (!reason) { return cb(); }
var path = mkPlaceholderPath(Env, blobId);
var s_data = typeof(reason) === "string" ? reason : `${reason.code}:${reason.txt}`;
Fs.writeFile(path, s_data, cb);
};
var clearPlaceholder = function (Env, blobId, cb) {
var path = mkPlaceholderPath(Env, blobId);
Fs.unlink(path, cb);
};
var readPlaceholder = function (Env, blobId, cb) {
var path = mkPlaceholderPath(Env, blobId);
Fs.readFile(path, function (err, content) {
if (err) { return void cb(); }
cb(content.toString('utf8'));
});
};
// getUploadSize: used by
// getFileSize
var getUploadSize = function (Env, blobId, cb) {
@ -63,7 +87,16 @@ var getUploadSize = function (Env, blobId, cb) {
Fs.stat(path, function (err, stats) {
if (err) {
// if a file was deleted, its size is 0 bytes
if (err.code === 'ENOENT') { return cb(void 0, 0); }
if (err.code === 'ENOENT') {
return readPlaceholder(Env, blobId, (content) => {
if (!content) { return cb(void 0, 0); }
cb({
code: err.code,
reason: content
});
});
}
return void cb(err.code);
}
cb(void 0, stats.size);
@ -345,6 +378,7 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
});
};
// removeBlob
var remove = function (Env, blobId, cb) {
var blobPath = makeBlobPath(Env, blobId);
@ -366,11 +400,12 @@ var isOwnedBy = function (Env, safeKey, blobId, cb) {
// archiveBlob
var archiveBlob = function (Env, blobId, cb) {
var archiveBlob = function (Env, blobId, reason, cb) {
var blobPath = makeBlobPath(Env, blobId);
var archivePath = prependArchive(Env, blobPath);
Fse.move(blobPath, archivePath, { overwrite: true }, cb);
archiveActivity(Env, blobId, () => {});
addPlaceholder(Env, blobId, reason, () => {});
};
var removeArchivedBlob = function (Env, blobId, cb) {
@ -385,6 +420,7 @@ var restoreBlob = function (Env, blobId, cb) {
var archivePath = prependArchive(Env, blobPath);
Fse.move(archivePath, blobPath, cb);
restoreActivity(Env, blobId, () => {});
clearPlaceholder(Env, blobId, () => {});
};
// archiveProof
@ -614,10 +650,10 @@ BlobStore.create = function (config, _cb) {
},
archive: {
blob: function (blobId, _cb) {
blob: function (blobId, reason, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidId(blobId)) { return void cb("INVALID_ID"); }
archiveBlob(Env, blobId, cb);
archiveBlob(Env, blobId, reason, cb);
},
proof: function (safeKey, blobId, _cb) {
var cb = Util.once(Util.mkAsync(_cb));

View File

@ -70,6 +70,10 @@ var mkOffsetPath = function (env, channelId) {
return mkPath(env, channelId) + '.offset';
};
var mkPlaceholderPath = function (env, channelId) {
return mkPath(env, channelId) + '.placeholder';
};
// pass in the path so we can reuse the same function for archived files
var channelExists = function (filepath, cb) {
Fs.stat(filepath, function (err, stat) {
@ -141,6 +145,25 @@ var isChannelArchived = function (env, channelName, cb) {
});
};
var addPlaceholder = function (env, channelId, reason, cb) {
if (!reason) { return cb(); }
var path = mkPlaceholderPath(env, channelId);
var s_data = typeof(reason) === "string" ? reason : `${reason.code}:${reason.txt}`;
Fs.writeFile(path, s_data, cb);
};
var clearPlaceholder = function (env, channelId, cb) {
var path = mkPlaceholderPath(env, channelId);
Fs.unlink(path, cb);
};
var readPlaceholder = function (env, channelId, cb) {
var path = mkPlaceholderPath(env, channelId);
Fs.readFile(path, function (err, content) {
if (err) { return void cb(); }
cb(content.toString('utf8'));
});
};
const destroyStream = function (stream) {
if (!stream) { return; }
try {
@ -190,7 +213,16 @@ const readMessagesBin = (env, id, start, msgHandler, cb) => {
const stream = Fs.createReadStream(mkPath(env, id), { start: start });
const collector = createIdleStreamCollector(stream);
const handleMessageAndKeepStreamAlive = Util.both(msgHandler, collector.keepAlive);
const done = Util.both(cb, collector);
const done = Util.both((err) => {
if (err && err.code === 'ENOENT') {
// If the channel doesn't exists, look for a placeholder.
// If a placeholder exists, call back with its content in addition to the original error
return readPlaceholder(env, id, (content) => {
cb(err, content);
});
}
cb(err);
}, collector);
return void readFileBin(stream, handleMessageAndKeepStreamAlive, done, {
offset: start,
});
@ -670,7 +702,7 @@ var listChannels = function (root, handler, cb, fast) {
// move a channel's log file from its current location
// to an equivalent location in the cold storage directory
var archiveChannel = function (env, channelName, cb) {
var archiveChannel = function (env, channelName, reason, cb) {
// TODO close channels before archiving them?
// ctime is the most reliable indicator of when a file was archived
@ -700,6 +732,9 @@ var archiveChannel = function (env, channelName, cb) {
}));
}).nThen(function (w) {
clearOffset(env, channelName, w());
}).nThen(function (w) {
if (!reason) { return; }
addPlaceholder(env, channelName, reason, w());
}).nThen(function (w) {
// archive the dedicated metadata channel
var metadataPath = mkMetadataPath(env, channelName);
@ -775,6 +810,8 @@ var unarchiveChannel = function (env, channelName, cb) {
return void CB(err);
}
}));
}).nThen(function (w) {
clearPlaceholder(env, channelName, w());
}).nThen(function (w) {
var archiveMetadataPath = mkArchiveMetadataPath(env, channelName);
// TODO validate that it's ok to move metadata non-atomically
@ -1290,12 +1327,12 @@ module.exports.create = function (conf, _cb) {
isChannelArchived(env, channelName, cb);
},
// move a channel from the database to the archive, along with its metadata
archiveChannel: function (channelName, cb) {
archiveChannel: function (channelName, reason, cb) {
if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
// again, the semantics around archiving and appending are really muddy.
// so I'm calling this 'unordered' again
schedule.unordered(channelName, function (next) {
archiveChannel(env, channelName, Util.both(cb, next));
archiveChannel(env, channelName, reason, Util.both(cb, next));
});
},
// restore a channel from the archive to the database, along with its metadata

View File

@ -499,8 +499,13 @@ const getHashOffset = function (data, cb) {
}
offset = msgObj.offset;
abort();
}, function (err) {
if (err) { return void cb(err); }
}, function (err, reason) {
if (err) {
return void cb({
error: err,
reason: reason
});
}
cb(void 0, offset);
});
};
@ -510,6 +515,8 @@ const removeOwnedBlob = function (data, cb) {
const blobId = data.blobId;
const safeKey = Util.escapeKeyCharacters(data.safeKey);
const reason = data.reason || 'ARCHIVE_OWNED';
nThen(function (w) {
// check if you have permissions
blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) {
@ -520,7 +527,7 @@ const removeOwnedBlob = function (data, cb) {
}));
}).nThen(function (w) {
// remove the blob
blobStore.archive.blob(blobId, w(function (err) {
blobStore.archive.blob(blobId, reason, w(function (err) {
Env.Log.info('ARCHIVAL_OWNED_FILE_BY_OWNER_RPC', {
safeKey: safeKey,
blobId: blobId,

View File

@ -382,11 +382,12 @@ Workers.initialize = function (Env, config, _cb) {
});
};
Env.removeOwnedBlob = function (blobId, safeKey, cb) {
Env.removeOwnedBlob = function (blobId, safeKey, reason, cb) {
sendCommand({
command: 'REMOVE_OWNED_BLOB',
blobId: blobId,
safeKey: safeKey,
reason: reason
}, cb);
};

View File

@ -2876,6 +2876,15 @@ define([
delete autoStoreModal[priv.channel];
}
if (err.message && err.message !== "PASSWORD_CHANGE") {
UI.errorLoadingScreen(h('div', [
h('p', Messages.deletedError),
h('p', err.message) // XXX custom message for each reason
// XXX reasons: ARCHIVE_OWNED, INACTIVE, MODERATION_ACCOUNT:{reason}, MODERATION_PAD:{reason}
]), Boolean(err.loaded), Boolean(err.loaded));
return;
}
if (err.ownDeletion) {
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
(cb || function () {})();
@ -2921,6 +2930,12 @@ define([
var error;
if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); }
// XXX
if (cfg.legacy) {
// XXX Legacy mode: we don't know if pad destroyed or password changed
}
var info = h('p.cp-password-info', Messages.password_info);
var info_loaded = setHTML(h('p.cp-password-info'), Messages.errorCopy);

View File

@ -649,15 +649,23 @@ define([
}).nThen(function (waitFor) {
// If it's not in the cache or it's not a blob, try to get the value from the server
postMessage("GET_FILE_SIZE", {channel:channel}, waitFor(function (obj) {
if (obj && obj.error) {
// If disconnected, try to get the value from the channel cache (next nThen)
error = obj.error;
return;
}
waitFor.abort();
cb(undefined, obj.size);
}));
var getSize = () => {
postMessage("GET_FILE_SIZE", {channel:channel}, waitFor(function (obj) {
if (obj && obj.error === "ANON_RPC_NOT_READY") { return void setTimeout(waitFor(getSize), 100); }
if (obj && obj.error && obj.error.code === 'ENOENT' && obj.error.reason) {
waitFor.abort();
cb(obj.error.reason);
} else if (obj && obj.error) {
// If disconnected, try to get the value from the channel cache (next nThen)
error = obj.error;
return;
}
waitFor.abort();
cb(undefined, obj.size);
}));
};
getSize();
}).nThen(function () {
Cache.getChannelCache(channel, function(err, data) {
if (err) { return void cb(error); }
@ -681,7 +689,7 @@ define([
var error = obj && obj.error;
if (error) { return void cb(error); }
if (!obj) { return void cb('ERROR'); }
cb (null, obj.isNew);
cb (null, obj.isNew, obj.reason);
}, {timeout: -1});
};
// This function is used when we want to open a pad. We first need
@ -711,7 +719,7 @@ define([
} else if (error) {
return void cb(error);
}
cb(undefined, obj.isNew);
cb(undefined, obj.isNew, obj.reason);
}, {timeout: -1});
};
isNew();
@ -1484,7 +1492,8 @@ define([
// delete the old pad
common.removeOwnedChannel({
channel: oldChannel,
teamId: teamId
teamId: teamId,
reason: 'PASSWORD_CHANGE',
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();
@ -1620,7 +1629,8 @@ define([
// delete the old pad
common.removeOwnedChannel({
channel: oldChannel,
teamId: teamId
teamId: teamId,
reason: 'PASSWORD_CHANGE'
}, waitFor(function (obj) {
if (obj && obj.error) {
waitFor.abort();

View File

@ -1152,10 +1152,36 @@ define([
var FILTER_BY = "filterBy";
var refreshDeprecated = function () {
if (!APP.passwordModal) { return; }
var deprecated = files.sharedFoldersTemp;
if (JSONSortify(deprecated) === APP.deprecatedSF) { return; }
APP.deprecatedSF = JSONSortify(deprecated);
if (typeof (deprecated) === "object" && Object.keys(deprecated).length) {
var nt = nThen;
Object.keys(deprecated).forEach(function (fId) {
var data = deprecated[fId];
var sfId = manager.user.userObject.getSFIdFromHref(data.href);
if (folders[fId] || sfId) { // This shared folder is already stored in the drive...
return void manager.delete([['sharedFoldersTemp', fId]], function () { });
}
nt = nt(function (waitFor) {
UI.openCustomModal(APP.passwordModal(fId, data, waitFor()));
}).nThen;
});
nt(function () {
APP.refresh();
});
}
};
var refresh = APP.refresh = function (cb) {
var type = APP.store[FILTER_BY];
var path = type ? [FILTER, type, currentPath] : currentPath;
APP.displayDirectory(path, undefined, cb);
APP.displayDirectory(path, undefined, () => {
refreshDeprecated();
if (typeof(cb) === "function") { cb(); }
});
};
// `app`: true (force open wiht the app), false (force open in preview),
@ -5370,9 +5396,13 @@ define([
});
}
*/
var nt = nThen;
var passwordModal = function (fId, data, cb) {
APP.passwordModal = function (fId, data, cb) {
var content = [];
var legacy = data.legacy; // XXX in legacy mode, it's either deletion or password change
legacy = legacy;
// XXX when legacy is false, we know it's a password change
var folderName = '<b>'+ (data.lastTitle || Messages.fm_newFolder) +'</b>';
content.push(UI.setHTML(h('p'), Messages._getKey('drive_sfPassword', [folderName])));
var newPassword = UI.passwordInput({
@ -5425,24 +5455,7 @@ define([
onClose: cb
});
};
onConnectEvt.reg(function () {
var deprecated = files.sharedFoldersTemp;
if (typeof (deprecated) === "object" && Object.keys(deprecated).length) {
Object.keys(deprecated).forEach(function (fId) {
var data = deprecated[fId];
var sfId = manager.user.userObject.getSFIdFromHref(data.href);
if (folders[fId] || sfId) { // This shared folder is already stored in the drive...
return void manager.delete([['sharedFoldersTemp', fId]], function () { });
}
nt = nt(function (waitFor) {
UI.openCustomModal(passwordModal(fId, data, waitFor()));
}).nThen;
});
nt(function () {
refresh();
});
}
});
onConnectEvt.reg(refreshDeprecated);
return {
refresh: refresh,

View File

@ -365,10 +365,12 @@ define([
var channel = data;
var force = false;
var teamId;
var reason;
if (data && typeof(data) === "object") {
channel = data.channel;
force = data.force;
teamId = data.teamId;
reason = data.reason;
}
if (channel === store.driveChannel && !force) {
@ -385,7 +387,7 @@ define([
s.rpc.removeOwnedChannel(channel, function (err) {
if (err) { delete myDeletions[channel]; }
cb({error:err});
});
}, reason);
};
var arePinsSynced = function (cb) {
@ -512,11 +514,9 @@ define([
var channelId = data.channel || Hash.hrefToHexChannelId(data.href, data.password);
store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) {
if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'boolean') {
if (response[0]) { Cache.clearChannel(channelId); }
return void cb({
isNew: response[0]
});
if (response && response.length && typeof(response[0]) === 'object') {
if (response[0].isNew) { Cache.clearChannel(channelId); }
return void cb(response[0]);
} else {
cb({error: 'INVALID_RESPONSE'});
}

View File

@ -251,8 +251,8 @@ define([
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", channel, function (e, response) {
if (e) { return void cb({error: e}); }
var isNew;
if (response && response.length && typeof(response[0]) === 'boolean') {
isNew = response[0];
if (response && response.length && typeof(response[0]) === 'object') {
isNew = response[0].isNew;
} else {
cb({error: 'INVALID_RESPONSE'});
}

View File

@ -120,7 +120,7 @@ define([
}).nThen(function (waitFor) {
isNewChannel(null, { channel: secret.channel }, waitFor(function (obj) {
if (obj.isNew && !isNew) {
store.manager.deprecateProxy(id, secret.channel);
store.manager.deprecateProxy(id, secret.channel, obj.reason);
waitFor.abort();
return void cb(null);
}
@ -245,7 +245,7 @@ define([
// Deprecate the shared folder from each team
// We can only hide it
sf.teams.forEach(function (obj) {
obj.store.manager.deprecateProxy(obj.id, secret.channel);
obj.store.manager.deprecateProxy(obj.id, secret.channel, info.message);
if (obj.store.handleSharedFolder) {
obj.store.handleSharedFolder(obj.id, null);
}

View File

@ -423,7 +423,7 @@ define([
};
if (channel) {
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", channel, waitFor(function (e, res) {
if (res && res.length && typeof(res[0]) === 'boolean' && res[0]) {
if (res && res.length && typeof(res[0]) === 'object' && res[0].isNew) {
// Channel is empty: remove this team
close();
}
@ -431,7 +431,7 @@ define([
}
if (roster) {
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", roster, waitFor(function (e, res) {
if (res && res.length && typeof(res[0]) === 'boolean' && res[0]) {
if (res && res.length && typeof(res[0]) === 'object' && res[0].isNew) {
// Channel is empty: remove this team
close();
}

View File

@ -118,13 +118,14 @@ define([
cb(null, id);
};
exp.deprecateSharedFolder = function (id) {
exp.deprecateSharedFolder = function (id, reason) {
if (readOnly) { return; }
var data = files[SHARED_FOLDERS][id];
if (!data) { return; }
var ro = !data.href || exp.cryptor.decrypt(data.href).indexOf('#') === -1;
if (!ro) {
files[SHARED_FOLDERS_TEMP][id] = JSON.parse(JSON.stringify(data));
var obj = files[SHARED_FOLDERS_TEMP][id] = JSON.parse(JSON.stringify(data));
obj.legacy = reason !== "PASSWORD_CHANGE";
}
var paths = exp.findFile(Number(id));
exp.delete(paths, null, true);

View File

@ -131,12 +131,15 @@ var factory = function (Util, Rpc) {
});
};
exp.removeOwnedChannel = function (channel, cb) {
exp.removeOwnedChannel = function (channel, cb, reason) {
if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) {
console.error('invalid channel to remove', channel);
return void cb('INVALID_ARGUMENTS');
}
rpc.send('REMOVE_OWNED_CHANNEL', channel, function (e, response) {
rpc.send('REMOVE_OWNED_CHANNEL', {
channel: channel,
reason: reason
}, function (e, response) {
if (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") {
cb();

View File

@ -71,7 +71,7 @@ define([
};
// Password may have changed
var deprecateProxy = function (Env, id, channel) {
var deprecateProxy = function (Env, id, channel, reason) {
if (Env.folders[id] && Env.folders[id].deleting) {
// Folder is being deleted by its owner, don't deprecate it
return;
@ -85,11 +85,19 @@ define([
return void Env.Store.refreshDriveUI();
}
if (channel) { Env.unpinPads([channel], function () {}); }
Env.user.userObject.deprecateSharedFolder(id);
removeProxy(Env, id);
if (Env.Store && Env.Store.refreshDriveUI) {
Env.Store.refreshDriveUI();
// If it's explicitely a deletion, no need to deprecate, just delete
if (reason && reason !== "PASSWORD_CHANGE") {
var temp = Util.find(Env, ['user', 'proxy', UserObject.SHARED_FOLDERS]);
delete temp[id];
if (Env.Store && Env.Store.refreshDriveUI) { Env.Store.refreshDriveUI(); }
return;
}
// It's explicitely a password change, better message in drive: provide the "reason" to the UI
Env.user.userObject.deprecateSharedFolder(id, reason);
removeProxy(Env, id);
if (Env.Store && Env.Store.refreshDriveUI) { Env.Store.refreshDriveUI(); }
};
var restrictedProxy = function (Env, id) {
@ -632,17 +640,19 @@ define([
if (isNew) {
return void cb({ error: 'ENOTFOUND' });
}
var newData = Util.clone(data);
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash, newPassword);
data.password = newPassword;
data.channel = secret.channel;
newData.password = newPassword;
newData.channel = secret.channel;
if (secret.keys.editKeyStr) {
data.href = '/drive/#'+Hash.getEditHashFromKeys(secret);
newData.href = '/drive/#'+Hash.getEditHashFromKeys(secret);
}
data.roHref = '/drive/#'+Hash.getViewHashFromKeys(secret);
newData.roHref = '/drive/#'+Hash.getViewHashFromKeys(secret);
delete newData.legacy;
_addSharedFolder(Env, {
path: ['root'],
folderData: data,
folderData: newData,
}, function () {
delete temp[fId];
Env.onSync(cb);

View File

@ -469,12 +469,20 @@ define([
// `hasChannelHistory` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
if (e && e !== "PASSWORD_CHANGE") {
return sframeChan.event("EV_DELETED_ERROR", e);
}
next(e, size === 0);
});
return;
}
// Not a file, so we can use `hasChannelHistory`
Cryptpad.hasChannelHistory(currentPad.href, password, next);
Cryptpad.hasChannelHistory(currentPad.href, password, (e, isNew, reason) => {
if (isNew && reason && reason !== "PASSWORD_CHANGE") {
return sframeChan.event("EV_DELETED_ERROR", reason);
}
next();
});
});
sframeChan.event("EV_PAD_PASSWORD", cfg);
};
@ -564,20 +572,32 @@ define([
// `hasChannelHistory` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) {
if (size !== 0) { return void todo(); }
if (e && e !== "PASSWORD_CHANGE") {
sframeChan.event("EV_DELETED_ERROR", e);
waitFor.abort();
return;
}
if (!e && size !== 0) { return void todo(); }
// Wrong password or deleted file?
passwordCfg.legacy = !e; // Legacy means we don't know if it's a deletion or pw change
askPassword(true, passwordCfg);
}));
return;
}
// Not a file, so we can use `hasChannelHistory`
Cryptpad.hasChannelHistory(currentPad.href, password, w(function(e, isNew) {
Cryptpad.hasChannelHistory(currentPad.href, password, w(function(e, isNew, reason) {
if (isNew && expire && expire < (+new Date())) {
sframeChan.event("EV_EXPIRED_ERROR");
waitFor.abort();
return;
}
if (!e && !isNew) { return void todo(); }
// NOTE: Legacy mode ==> no reason may indicate a password change
if (isNew && reason && reason !== "PASSWORD_CHANGE") {
sframeChan.event("EV_DELETED_ERROR", reason);
waitFor.abort();
return;
}
if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) {
// Error, wrong password stored, the view seed has changed with the password
// password will never work
@ -586,6 +606,7 @@ define([
return;
}
// Wrong password or deleted file?
passwordCfg.legacy = !reason; // Legacy means we don't know if it's a deletion or pw change
askPassword(true, passwordCfg);
}));
}).nThen(done);
@ -1915,6 +1936,8 @@ define([
// redirect
window.location.href = url;
document.location.reload();
// XXX XXX XXX TEST TEST TEST
// XXX XXX XXX TEST TEST TEST
});
return;

View File

@ -844,6 +844,13 @@ define([
UI.errorLoadingScreen(Messages.restrictedError);
});
ctx.sframeChan.on("EV_DELETED_ERROR", function (reason) {
funcs.onServerError({
type: 'EDELETED',
message: reason
});
});
ctx.sframeChan.on("EV_PAD_PASSWORD_ERROR", function () {
UI.errorLoadingScreen(Messages.password_error_seed);
});

View File

@ -20,6 +20,7 @@ define([
};
SFCommonO.start({
cache: true,
noDrive: true,
hash: hash,
href: href,
noRealtime: true,