Merge branch 'moderation' into staging

This commit is contained in:
yflory 2023-09-28 17:56:46 +02:00
commit ef92d9217c
42 changed files with 1491 additions and 335 deletions

View File

@ -10,6 +10,7 @@ define([
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-interface.js', '/common/common-interface.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/hyperscript.js',
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/customize/messages.js', '/customize/messages.js',
'/components/nthen/index.js', '/components/nthen/index.js',
@ -20,7 +21,7 @@ define([
'/components/tweetnacl/nacl-fast.min.js', '/components/tweetnacl/nacl-fast.min.js',
'/components/scrypt-async/scrypt-async.min.js', // better load speed '/components/scrypt-async/scrypt-async.min.js', // better load speed
], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad, Realtime, Constants, UI, ], function ($, Listmap, Crypto, Util, NetConfig, Cred, ChainPad, Realtime, Constants, UI,
Feedback, LocalStore, Messages, nThen, Block, Hash, ServerCommand) { Feedback, h, LocalStore, Messages, nThen, Block, Hash, ServerCommand) {
var Exports = { var Exports = {
Cred: Cred, Cred: Cred,
Block: Block, Block: Block,
@ -121,6 +122,9 @@ define([
.on('ready', function () { .on('ready', function () {
setTimeout(function () { cb(void 0, rt); }); setTimeout(function () { cb(void 0, rt); });
}) })
.on('error', function (info) {
cb(info.type, {reason: info.message});
})
.on('disconnect', function (info) { .on('disconnect', function (info) {
cb('E_DISCONNECT', info); cb('E_DISCONNECT', info);
}); });
@ -210,6 +214,16 @@ define([
return void console.log("Block requires 2FA"); return void console.log("Block requires 2FA");
} }
if (err === 404 && response && response.reason) {
waitFor.abort();
w.abort();
/*
// the following block prevent users from re-using an old password
if (isRegister) { return void cb('HAS_PLACEHOLDER'); }
*/
return void cb('DELETED_USER', response);
}
// Some other error? // Some other error?
if (err) { if (err) {
console.error(err); console.error(err);
@ -291,6 +305,7 @@ define([
loadUserObject(opt, waitFor(function (err, rt) { loadUserObject(opt, waitFor(function (err, rt) {
if (err) { if (err) {
waitFor.abort(); waitFor.abort();
if (err === 'EDELETED') { return void cb('DELETED_USER', rt); }
return void cb(err); return void cb(err);
} }
@ -388,6 +403,7 @@ define([
loadUserObject(opt, waitFor(function (err, rt) { loadUserObject(opt, waitFor(function (err, rt) {
if (err) { if (err) {
waitFor.abort(); waitFor.abort();
if (err === 'EDELETED') { return void cb('DELETED_USER', rt); }
return void cb('MODERN_REGISTRATION_INIT'); return void cb('MODERN_REGISTRATION_INIT');
} }
@ -596,6 +612,17 @@ define([
}); });
}); });
break; break;
/*
case 'HAS_PLACEHOLDER':
UI.errorLoadingScreen('UNAVAILABLE', true, true);
break;
*/
case 'DELETED_USER':
UI.errorLoadingScreen(
UI.getDestroyedPlaceholder(result.reason, true), true, () => {
window.location.reload();
});
break;
case 'INVAL_PASS': case 'INVAL_PASS':
UI.removeLoadingScreen(function () { UI.removeLoadingScreen(function () {
Messages.login_notFilledPass = 'Please fill in a password'; // XXX Messages.login_notFilledPass = 'Please fill in a password'; // XXX

326
lib/archive-account.js Normal file
View File

@ -0,0 +1,326 @@
/* jshint esversion: 6, node: true */
const nThen = require('nthen');
const Pins = require('./pins');
const Util = require("./common-util");
const Store = require('./storage/file.js');
const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const Core = require("./commands/core");
const Metadata = require("./commands/metadata");
const Meta = require("./metadata");
const Logger = require("./log");
const Path = require("path");
const Fse = require("fs-extra");
const { parentPort } = require('node:worker_threads');
const COMMANDS = {};
let Log;
const mkReportPath = function (Env, safeKey) {
return Path.join(Env.paths.archive, 'accounts', safeKey);
};
const storeReport = (Env, report, cb) => {
let path = mkReportPath(Env, report.key);
let s_data;
try {
s_data = JSON.stringify(report);
Fse.outputFile(path, s_data, cb);
} catch (err) {
return void cb(err);
}
};
const readReport = (Env, key, cb) => {
let path = mkReportPath(Env, key);
Fse.readJson(path, cb);
};
const deleteReport = (Env, key, cb) => {
let path = mkReportPath(Env, key);
Fse.remove(path, cb);
};
const init = (cb) => {
const Environment = require("./env");
const config = require('./load-config');
const Env = Environment.create(config);
Env.computeMetadata = function (channel, cb) {
const ref = {};
const lineHandler = Meta.createLineHandler(ref, (err) => { console.log(err); });
return void Env.store.readChannelMetadata(channel, lineHandler, function (err) {
if (err) {
// stream errors?
return void cb(err);
}
cb(void 0, ref.meta);
});
};
nThen((waitFor) => {
Logger.create(config, waitFor(function (_) {
Log = Env.Log = _;
}));
Store.create(config, waitFor(function (err, _store) {
if (err) {
waitFor.abort();
return void cb(err);
}
Env.store = _store;
}));
Store.create({
filePath: config.pinPath,
archivePath: config.archivePath,
// archive pin logs to their own subpath
volumeId: 'pins',
}, waitFor(function (err, _) {
if (err) {
waitFor.abort();
throw err;
}
Env.pinStore = _;
}));
BlobStore.create({
blobPath: config.blobPath,
blobStagingPath: config.blobStagingPath,
archivePath: config.archivePath,
getSession: function () {},
}, waitFor(function (err, blob) {
if (err) {
waitFor.abort();
return void cb(err);
}
Env.blobStore = blob;
}));
}).nThen(() => {
cb(Env);
});
};
COMMANDS.start = (edPublic, blockId, reason) => {
const safeKey = Util.escapeKeyCharacters(edPublic);
const archiveReason = {
code: 'MODERATION_ACCOUNT',
txt: reason
};
let ref = {};
let blobsToArchive = [];
let channelsToArchive = [];
let deletedChannels = [];
let deletedBlobs = [];
let Env;
nThen((waitFor) => {
init(waitFor((_Env) => {
Env = _Env;
}));
}).nThen((waitFor) => {
let lineHandler = Pins.createLineHandler(ref, (err) => { console.log(err); });
Env.pinStore.readMessagesBin(safeKey, 0, (msgObj, readMore) => {
lineHandler(msgObj.buff.toString('utf8'));
readMore();
}, waitFor());
}).nThen((waitFor) => {
Log.info('MODERATION_ACCOUNT_ARCHIVAL_START', edPublic, waitFor());
var n = nThen;
Object.keys(ref.pins || {}).forEach((chanId) => {
n = n((w) => {
// Blobs
if (Env.blobStore.isFileId(chanId)) {
return void Env.blobStore.isOwnedBy(safeKey, chanId, w((err, owned) => {
if (err || !owned) { return; }
blobsToArchive.push(chanId);
}));
}
// Pads
Metadata.getMetadata(Env, chanId, w((err, metadata) => {
if (err) { return; } // Can't read metadata? Don't archive
if (!Core.hasOwners(metadata)) { return; } // No owner, don't archive
if (Core.isOwner(metadata, edPublic) && metadata.owners.length === 1) {
channelsToArchive.push(chanId); // Only owner: archive
}
}));
}).nThen;
});
n(waitFor());
}).nThen((waitFor) => {
Log.info('MODERATION_ACCOUNT_ARCHIVAL_LISTED', JSON.stringify({
pads: channelsToArchive.length,
blobs: blobsToArchive.length
}), waitFor());
var n = nThen;
// Archive the pads
channelsToArchive.forEach((chanId) => {
n = n((w) => {
Env.store.archiveChannel(chanId, archiveReason, w(function (err) {
if (err) {
return Log.error('MODERATION_CHANNEL_ARCHIVAL_ERROR', {
error: err,
channel: chanId,
}, w());
}
deletedChannels.push(chanId);
Log.info('MODERATION_CHANNEL_ARCHIVAL', chanId, w());
}));
}).nThen;
});
// Archive the blobs
blobsToArchive.forEach((blobId) => {
n = n((w) => {
Env.blobStore.archive.blob(blobId, archiveReason, w(function (err) {
if (err) {
return Log.error('MODERATION_BLOB_ARCHIVAL_ERROR', {
error: err,
item: blobId,
}, w());
}
deletedBlobs.push(blobId);
Log.info('MODERATION_BLOB_ARCHIVAL', blobId, w());
}));
}).nThen;
});
n(waitFor(() => {
// Archive the pin log
Env.pinStore.archiveChannel(safeKey, undefined, waitFor(function (err) {
if (err) {
return Log.error('MODERATION_ACCOUNT_PIN_LOG', err, waitFor());
}
Log.info('MODERATION_ACCOUNT_LOG', safeKey, waitFor());
}));
blockId = blockId || ref.block;
if (!blockId) { return; }
BlockStore.archive(Env, blockId, archiveReason, waitFor(function (err) {
if (err) {
blockId = undefined;
return Log.error('MODERATION_ACCOUNT_BLOCK', err, waitFor());
}
Log.info('MODERATION_ACCOUNT_BLOCK', safeKey, waitFor());
}));
}));
}).nThen((waitFor) => {
var report = {
key: safeKey,
channels: deletedChannels,
blobs: deletedBlobs,
blockId: blockId,
reason: reason
};
storeReport(Env, report, waitFor((err) => {
if (err) {
return Log.error('MODERATION_ACCOUNT_REPORT', report, waitFor());
}
}));
}).nThen(() => {
parentPort.postMessage(JSON.stringify(deletedChannels));
process.exit(0);
});
};
COMMANDS.restore = (edPublic) => {
const safeKey = Util.escapeKeyCharacters(edPublic);
let pads, blobs;
let blockId;
let errors = [];
let Env;
nThen((waitFor) => {
init(waitFor((_Env) => {
Env = _Env;
}));
}).nThen((waitFor) => {
Log.info('MODERATION_ACCOUNT_RESTORE_START', edPublic, waitFor());
readReport(Env, safeKey, waitFor((err, report) => {
if (err) { throw new Error(err); }
pads = report.channels;
blobs = report.blobs;
blockId = report.blockId;
}));
}).nThen((waitFor) => {
Log.info('MODERATION_ACCOUNT_RESTORE_LISTED', JSON.stringify({
pads: pads.length,
blobs: blobs.length
}), waitFor());
var n = nThen;
pads.forEach((chanId) => {
n = n((w) => {
Env.store.restoreArchivedChannel(chanId, w(function (err) {
if (err) {
errors.push(chanId);
return Log.error('MODERATION_CHANNEL_RESTORE_ERROR', {
error: err,
channel: chanId,
}, w());
}
Log.info('MODERATION_CHANNEL_RESTORE', chanId, w());
}));
}).nThen;
});
blobs.forEach((blobId) => {
n = n((w) => {
Env.blobStore.restore.blob(blobId, w(function (err) {
if (err) {
errors.push(blobId);
return Log.error('MODERATION_BLOB_RESTORE_ERROR', {
error: err,
item: blobId,
}, w());
}
Log.info('MODERATION_BLOB_RESTORE', blobId, w());
}));
}).nThen;
});
n(waitFor(() => {
// remove the pin logs of inactive accounts if inactive account removal is configured
Env.pinStore.restoreArchivedChannel(safeKey, waitFor(function (err) {
if (err) {
return Log.error('MODERATION_ACCOUNT_PIN_LOG_RESTORE', err, waitFor());
}
Log.info('MODERATION_ACCOUNT_LOG_RESTORE', safeKey, waitFor());
}));
if (!blockId) { return; }
BlockStore.restore(Env, blockId, waitFor(function (err) {
if (err) {
blockId = undefined;
return Log.error('MODERATION_ACCOUNT_BLOCK_RESTORE', err, waitFor());
}
Log.info('MODERATION_ACCOUNT_BLOCK_RESTORE', safeKey, waitFor());
}));
}));
}).nThen((waitFor) => {
deleteReport(Env, safeKey, waitFor((err) => {
if (err) {
return Log.error('MODERATION_ACCOUNT_REPORT_DELETE', safeKey, waitFor());
}
}));
}).nThen(() => {
parentPort.postMessage(JSON.stringify(errors));
process.exit(0);
});
};
const getStatus = (Env, edPublic, cb) => {
const safeKey = Util.escapeKeyCharacters(edPublic);
readReport(Env, safeKey, (err, report) => {
if (err) { return void cb(err); }
cb(void 0, report);
});
};
if (parentPort) {
parentPort.on('message', (message) => {
let parsed = message; //JSON.parse(message);
let command = parsed.command;
let content = parsed.content;
let block = parsed.block;
let reason = parsed.reason;
COMMANDS[command](content, block, reason);
});
parentPort.postMessage('READY');
} else {
module.exports = {
getStatus: getStatus
};
}

View File

@ -69,8 +69,8 @@ const removeBlock = Commands.REMOVE_BLOCK = function (Env, body, cb) {
}; };
removeBlock.complete = function (Env, body, cb) { removeBlock.complete = function (Env, body, cb) {
const { publicKey } = body; const { publicKey, reason } = body;
Block.removeLoginBlock(Env, publicKey, cb); Block.removeLoginBlock(Env, publicKey, reason, cb);
}; };

View File

@ -483,10 +483,10 @@ const removeBlock = Commands.TOTP_REMOVE_BLOCK = function (Env, body, cb) {
}; };
removeBlock.complete = function (Env, body, cb) { removeBlock.complete = function (Env, body, cb) {
const { publicKey } = body; const { publicKey, reason } = body;
nThen(function (w) { nThen(function (w) {
// Remove the block // Remove the block
Block.removeLoginBlock(Env, publicKey, w((err) => { Block.removeLoginBlock(Env, publicKey, reason, w((err) => {
if (err) { if (err) {
w.abort(); w.abort();
return void cb(err); return void cb(err);

View File

@ -10,6 +10,10 @@ const Core = require("./core");
const Channel = require("./channel"); const Channel = require("./channel");
const BlockStore = require("../storage/block"); const BlockStore = require("../storage/block");
const MFA = require("../storage/mfa"); const MFA = require("../storage/mfa");
const ArchiveAccount = require('../archive-account');
/* jshint ignore:start */
const { Worker } = require('node:worker_threads');
/* jshint ignore:end */
var Fs = require("fs"); var Fs = require("fs");
@ -172,20 +176,26 @@ var archiveDocument = function (Env, Server, cb, data) {
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); } 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) { switch (id.length) {
case 32: 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", { Env.Log.info("ARCHIVAL_CHANNEL_BY_ADMIN_RPC", {
channelId: id, channelId: id,
reason: reason, reason: reason,
status: err? String(err): "SUCCESS", status: err? String(err): "SUCCESS",
}); });
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', err => { Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reasonStr, err => {
if (err) { } // TODO if (err) { } // TODO
}); });
})); }));
case 48: 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", { Env.Log.info("ARCHIVAL_BLOB_BY_ADMIN_RPC", {
id: id, id: id,
reason: reason, reason: reason,
@ -210,7 +220,7 @@ var removeDocument = function (Env, Server, cb, data) {
id = args; id = args;
} else if (args && typeof(args) === 'object') { } else if (args && typeof(args) === 'object') {
id = args.id; id = args.id;
reason = args.reason; reason = `MODERATION_DESTROY:${args.reason}`;
} }
if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); } if (typeof(id) !== 'string' || id.length < 32) { return void cb("EINVAL"); }
@ -223,7 +233,7 @@ var removeDocument = function (Env, Server, cb, data) {
reason: reason, reason: reason,
status: err? String(err): "SUCCESS", status: err? String(err): "SUCCESS",
}); });
Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', err => { Channel.disconnectChannelMembers(Env, Server, id, 'EDELETED', reason, err => {
if (err) { } // TODO if (err) { } // TODO
}); });
})); }));
@ -279,6 +289,81 @@ var restoreArchivedDocument = function (Env, Server, cb, data) {
} }
}; };
// CryptPad_AsyncStore.rpc.send('ADMIN', ['ARCHIVE_ACCOUNT', {key, block, reason}], console.log)
var archiveAccount = function (Env, Server, _cb, data) {
const cb = Util.once(_cb);
const worker = new Worker('./lib/archive-account.js');
const args = Array.isArray(data) && data[1];
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
worker.on('message', message => {
if (message === 'READY') {
return worker.postMessage({
command: 'start',
content: args.key,
block: args.block, // optional, may be including in pin log
reason: args.reason
});
}
// DONE: disconnect all users from these channels
Env.Log.info('ARCHIVE_ACCOUNT_BY_ADMIN', {
safeKey: args.key,
reason: args.reason,
});
const reason = `MODERATION_ACCOUNT:${args.reason}`;
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', reason, () => {});
}), 10);
}).nThen;
});
}
cb(void 0, { state: true });
});
worker.on('error', (err) => {
console.error(err);
cb(err);
});
worker.on('exit', () => { worker.unref(); });
};
var restoreAccount = function (Env, Server, _cb, data) {
const cb = Util.once(_cb);
const worker = new Worker('./lib/archive-account.js');
const args = Array.isArray(data) && data[1];
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
worker.on('message', message => {
if (message === 'READY') {
return worker.postMessage({
command: 'restore',
content: args.key
});
}
// Response
Env.Log.info('RESTORE_ACCOUNT_BY_ADMIN', {
safeKey: args.key,
reason: args.reason,
});
cb(void 0, {
state: true,
errors: Util.tryParse(message)
});
});
worker.on('error', (err) => {
console.error(err);
cb(err);
});
worker.on('exit', () => { worker.unref(); });
};
var getAccountArchiveStatus = function (Env, Server, cb, data) {
const args = Array.isArray(data) && data[1];
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
ArchiveAccount.getStatus(Env, args.key, cb);
};
// CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_INDEX', documentID], console.log) // CryptPad_AsyncStore.rpc.send('ADMIN', ['CLEAR_CACHED_CHANNEL_INDEX', documentID], console.log)
var clearChannelIndex = function (Env, Server, cb, data) { var clearChannelIndex = function (Env, Server, cb, data) {
var id = Array.isArray(data) && data[1]; var id = Array.isArray(data) && data[1];
@ -499,6 +584,10 @@ var getDocumentStatus = function (Env, Server, cb, data) {
} }
response.archived = result; response.archived = result;
})); }));
BlockStore.readPlaceholder(Env, id, w((result) => {
if (!result) { return; }
response.placeholder = result;
}));
MFA.read(Env, id, w(function (err, v) { MFA.read(Env, id, w(function (err, v) {
if (err === 'ENOENT') { if (err === 'ENOENT') {
response.totp = 'DISABLED'; response.totp = 'DISABLED';
@ -530,6 +619,10 @@ var getDocumentStatus = function (Env, Server, cb, data) {
} }
response.archived = result; response.archived = result;
})); }));
Env.blobStore.getPlaceholder(id, w((result) => {
if (!result) { return; }
response.placeholder = result;
}));
}).nThen(function () { }).nThen(function () {
cb(void 0, response); cb(void 0, response);
}); });
@ -548,6 +641,10 @@ var getDocumentStatus = function (Env, Server, cb, data) {
} }
response.archived = result; response.archived = result;
})); }));
Env.store.getPlaceholder(id, w((result) => {
if (!result) { return; }
response.placeholder = result;
}));
}).nThen(function () { }).nThen(function () {
cb(void 0, response); cb(void 0, response);
}); });
@ -578,6 +675,8 @@ var getPinHistory = function (Env, Server, cb, data) {
cb("NOT_IMPLEMENTED"); cb("NOT_IMPLEMENTED");
}; };
/*
// NOTE: Deprecated, archive whole account now
var archivePinLog = function (Env, Server, cb, data) { var archivePinLog = function (Env, Server, cb, data) {
var args = Array.isArray(data) && data[1]; var args = Array.isArray(data) && data[1];
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); } if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
@ -586,7 +685,7 @@ var archivePinLog = function (Env, Server, cb, data) {
if (!isValidKey(key)) { return void cb("EINVAL"); } if (!isValidKey(key)) { return void cb("EINVAL"); }
var safeKey = Util.escapeKeyCharacters(key); var safeKey = Util.escapeKeyCharacters(key);
Env.pinStore.archiveChannel(safeKey, function (err) { Env.pinStore.archiveChannel(safeKey, undefined, function (err) {
Core.expireSession(Env.Sessions, safeKey); Core.expireSession(Env.Sessions, safeKey);
if (err) { if (err) {
Env.Log.error('ARCHIVE_PIN_LOG_BY_ADMIN', { Env.Log.error('ARCHIVE_PIN_LOG_BY_ADMIN', {
@ -603,6 +702,7 @@ var archivePinLog = function (Env, Server, cb, data) {
cb(err); cb(err);
}); });
}; };
*/
var archiveBlock = function (Env, Server, cb, data) { var archiveBlock = function (Env, Server, cb, data) {
var args = Array.isArray(data) && data[1]; var args = Array.isArray(data) && data[1];
@ -610,7 +710,11 @@ var archiveBlock = function (Env, Server, cb, data) {
var key = args.key; var key = args.key;
var reason = args.reason; var reason = args.reason;
if (!isValidKey(key)) { return void cb("EINVAL"); } if (!isValidKey(key)) { return void cb("EINVAL"); }
BlockStore.archive(Env, key, err => { const archiveReason = {
code: 'MODERATION_BLOCK',
txt: reason
};
BlockStore.archive(Env, key, archiveReason, err => {
Env.Log.info("ARCHIVE_BLOCK_BY_ADMIN", { Env.Log.info("ARCHIVE_BLOCK_BY_ADMIN", {
error: err, error: err,
key: key, key: key,
@ -636,6 +740,8 @@ var restoreArchivedBlock = function (Env, Server, cb, data) {
}); });
}; };
/*
// NOTE: Deprecated, archive whole account now
var restoreArchivedPinLog = function (Env, Server, cb, data) { var restoreArchivedPinLog = function (Env, Server, cb, data) {
var args = Array.isArray(data) && data[1]; var args = Array.isArray(data) && data[1];
if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); } if (!args || typeof(args) !== 'object') { return void cb("EINVAL"); }
@ -660,6 +766,7 @@ var restoreArchivedPinLog = function (Env, Server, cb, data) {
cb(err); cb(err);
}); });
}; };
*/
var archiveOwnedDocuments = function (Env, Server, cb, data) { var archiveOwnedDocuments = function (Env, Server, cb, data) {
Env.Log.debug('ARCHIVE_OWNED_DOCUMENTS', data); Env.Log.debug('ARCHIVE_OWNED_DOCUMENTS', data);
@ -770,9 +877,9 @@ var commands = {
GET_PIN_LIST: getPinList, GET_PIN_LIST: getPinList,
GET_PIN_HISTORY: getPinHistory, GET_PIN_HISTORY: getPinHistory,
ARCHIVE_PIN_LOG: archivePinLog, //ARCHIVE_PIN_LOG: archivePinLog,
ARCHIVE_OWNED_DOCUMENTS: archiveOwnedDocuments, ARCHIVE_OWNED_DOCUMENTS: archiveOwnedDocuments,
RESTORE_ARCHIVED_PIN_LOG: restoreArchivedPinLog, //RESTORE_ARCHIVED_PIN_LOG: restoreArchivedPinLog,
ARCHIVE_BLOCK: archiveBlock, ARCHIVE_BLOCK: archiveBlock,
RESTORE_ARCHIVED_BLOCK: restoreArchivedBlock, RESTORE_ARCHIVED_BLOCK: restoreArchivedBlock,
@ -780,6 +887,10 @@ var commands = {
ARCHIVE_DOCUMENT: archiveDocument, ARCHIVE_DOCUMENT: archiveDocument,
RESTORE_ARCHIVED_DOCUMENT: restoreArchivedDocument, RESTORE_ARCHIVED_DOCUMENT: restoreArchivedDocument,
ARCHIVE_ACCOUNT: archiveAccount,
RESTORE_ACCOUNT: restoreAccount,
GET_ACCOUNT_ARCHIVE_STATUS: getAccountArchiveStatus,
CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex, CLEAR_CACHED_CHANNEL_INDEX: clearChannelIndex,
GET_CACHED_CHANNEL_INDEX: getChannelIndex, GET_CACHED_CHANNEL_INDEX: getChannelIndex,
// TODO implement admin historyTrim // TODO implement admin historyTrim

View File

@ -172,10 +172,10 @@ Block.writeLoginBlock = function (Env, msg, _cb) {
information, we can just sign some constant and use that as proof. information, we can just sign some constant and use that as proof.
*/ */
Block.removeLoginBlock = function (Env, publicKey, _cb) { Block.removeLoginBlock = function (Env, publicKey, reason, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
BlockStore.archive(Env, publicKey, function (err) { BlockStore.archive(Env, publicKey, reason, function (err) {
Env.Log.info('ARCHIVAL_BLOCK_BY_OWNER_RPC', { Env.Log.info('ARCHIVAL_BLOCK_BY_OWNER_RPC', {
publicKey: publicKey, publicKey: publicKey,
status: err? String(err): 'SUCCESS', status: err? String(err): 'SUCCESS',

View File

@ -8,7 +8,7 @@ const Metadata = require("./metadata");
const HK = require("../hk-util"); const HK = require("../hk-util");
const Nacl = require("tweetnacl/nacl-fast"); 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)); var done = Util.once(Util.mkAsync(cb));
if (!Core.isValidId(channelId)) { return done('INVALID_ID'); } if (!Core.isValidId(channelId)) { return done('INVALID_ID'); }
@ -39,6 +39,7 @@ Channel.disconnectChannelMembers = function (Env, Server, channelId, code, cb) {
userId, userId,
JSON.stringify({ JSON.stringify({
error: code, //'EDELETED', error: code, //'EDELETED',
message: reason,
channel: channelId, channel: channelId,
}) })
], w()); ], w());
@ -52,6 +53,7 @@ Channel.disconnectChannelMembers = function (Env, Server, channelId, code, cb) {
Env.Log.warn('DISCONNECT_CHANNEL_MEMBERS_TIMEOUT', { Env.Log.warn('DISCONNECT_CHANNEL_MEMBERS_TIMEOUT', {
channelId, channelId,
code, code,
reason
}); });
clear(); clear();
done(); 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 _cb = Util.once(Util.mkAsync(__cb));
var unsafeKey = Util.unescapeKeyCharacters(safeKey); var unsafeKey = Util.unescapeKeyCharacters(safeKey);
reason = reason || 'ARCHIVE_OWNED';
nThen(function (w) { nThen(function (w) {
// confirm that the channel exists before worrying about whether // confirm that the channel exists before worrying about whether
// we have permission to delete it. // we have permission to delete it.
@ -130,7 +133,7 @@ var archiveOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
}).nThen(function () { }).nThen(function () {
var cb = _cb; var cb = _cb;
// temporarily archive the file // 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', { Env.Log.info('ARCHIVAL_CHANNEL_BY_OWNER_RPC', {
unsafeKey: unsafeKey, unsafeKey: unsafeKey,
channelId: channelId, channelId: channelId,
@ -141,16 +144,19 @@ var archiveOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
} }
cb(void 0, 'OK'); cb(void 0, 'OK');
Channel.disconnectChannelMembers(Env, Server, channelId, 'EDELETED', err => { Channel.disconnectChannelMembers(Env, Server, channelId, 'EDELETED', reason, err => {
if (err) { } // TODO 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 _cb = Util.once(Util.mkAsync(__cb));
var channelId = obj.channel;
var reason = obj.reason;
if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) { if (typeof(channelId) !== 'string' || !Core.isValidId(channelId)) {
return _cb('INVALID_ARGUMENTS'); return _cb('INVALID_ARGUMENTS');
} }
@ -160,9 +166,9 @@ Channel.removeOwnedChannel = function (Env, safeKey, channelId, __cb, Server) {
Env.queueDeletes(safeKey, function (next) { Env.queueDeletes(safeKey, function (next) {
var cb = Util.both(_cb, next); var cb = Util.both(_cb, next);
if (Env.blobStore.isFileId(channelId)) { 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);
}); });
}; };
@ -248,27 +254,29 @@ var ARRAY_LINE = /^\[/;
call back with true if the channel log has no content other than metadata call back with true if the channel log has no content other than metadata
otherwise false otherwise false
*/ */
Channel.isNewChannel = function (Env, channel, cb) { Channel.isNewChannel = function (Env, channel, _cb) {
var cb = Util.once(_cb);
if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); } if (!Core.isValidId(channel)) { return void cb('INVALID_CHAN'); }
if (channel.length !== HK.STANDARD_CHANNEL_LENGTH && if (channel.length !== HK.STANDARD_CHANNEL_LENGTH &&
channel.length !== HK.ADMIN_CHANNEL_LENGTH) { return void cb('INVALID_CHAN'); } channel.length !== HK.ADMIN_CHANNEL_LENGTH) { return void cb('INVALID_CHAN'); }
// TODO replace with readMessagesBin Env.msgStore.readMessagesBin(channel, 0, function (msgObj, readMore, abort) {
var done = false;
Env.msgStore.getMessages(channel, function (msg) {
if (done) { return; }
try { try {
var msg = msgObj.buff.toString('utf8');
if (typeof(msg) === 'string' && ARRAY_LINE.test(msg)) { if (typeof(msg) === 'string' && ARRAY_LINE.test(msg)) {
done = true; abort();
return void cb(void 0, false); return void cb(void 0, {isNew: false});
} }
} catch (e) { } catch (e) {
Env.WARN('invalid message read from store', e); Env.WARN('invalid message read from store', e);
} }
}, function () { readMore();
if (done) { return; } }, function (err, reason) {
// no more messages... // 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) { Pinning.removePins = function (Env, safeKey, cb) {
// FIXME respect the queue // FIXME respect the queue
Env.pinStore.archiveChannel(safeKey, function (err) { Env.pinStore.archiveChannel(safeKey, undefined, function (err) {
Core.expireSession(Env.Sessions, safeKey); Core.expireSession(Env.Sessions, safeKey);
Env.Log.info('ARCHIVAL_PIN_BY_OWNER_RPC', { Env.Log.info('ARCHIVAL_PIN_BY_OWNER_RPC', {
safeKey: safeKey, safeKey: safeKey,

View File

@ -186,8 +186,7 @@ var evictArchived = function (Env, cb) {
var handler = function (err, item, cb) { var handler = function (err, item, cb) {
if (err) { if (err) {
Log.error('EVICT_ARCHIVED_CHANNEL_ITERATION', err); return Log.error('EVICT_ARCHIVED_CHANNEL_ITERATION', err, cb);
return void cb();
} }
// don't mess with files that are freshly stored in cold storage // don't mess with files that are freshly stored in cold storage
// based on ctime because that's changed when the file is moved... // based on ctime because that's changed when the file is moved...
@ -199,13 +198,11 @@ var evictArchived = function (Env, cb) {
// expire it // expire it
store.removeArchivedChannel(item.channel, w(function (err) { store.removeArchivedChannel(item.channel, w(function (err) {
if (err) { if (err) {
Log.error('EVICT_ARCHIVED_CHANNEL_REMOVAL_ERROR', { return Log.error('EVICT_ARCHIVED_CHANNEL_REMOVAL_ERROR', {
error: err, error: err,
channel: item.channel, channel: item.channel,
}); }, cb);
return void cb();
} }
Log.info('EVICT_ARCHIVED_CHANNEL_REMOVAL', item.channel);
if (item.channel.length === 32) { if (item.channel.length === 32) {
removed++; removed++;
@ -213,7 +210,7 @@ var evictArchived = function (Env, cb) {
accounts++; accounts++;
} }
cb(); Log.info('EVICT_ARCHIVED_CHANNEL_REMOVAL', item.channel, cb);
})); }));
}; };
@ -399,8 +396,7 @@ module.exports = function (Env, cb) {
} }
if (err) { if (err) {
Log.error('EVICT_CHANNEL_CATEGORIZATION', err); return Log.error('EVICT_CHANNEL_CATEGORIZATION', err, cb);
return void cb();
} }
// if the channel has been modified recently // if the channel has been modified recently
@ -421,7 +417,7 @@ module.exports = function (Env, cb) {
Log.info('EVICT_CHANNELS_CATEGORIZED', { Log.info('EVICT_CHANNELS_CATEGORIZED', {
active: active, active: active,
channels: channels, channels: channels,
}); }, w());
}; };
Log.info('EVICT_CHANNEL_ACTIVITY_START', 'Assessing channel activity'); Log.info('EVICT_CHANNEL_ACTIVITY_START', 'Assessing channel activity');
@ -443,12 +439,10 @@ module.exports = function (Env, cb) {
} }
if (err) { if (err) {
Log.error("EVICT_BLOB_CATEGORIZATION", err); return Log.error("EVICT_BLOB_CATEGORIZATION", err, next);
return void next();
} }
if (!item) { if (!item) {
next(); return void Log.error("EVICT_BLOB_CATEGORIZATION_INVALID", item, next);
return void Log.error("EVICT_BLOB_CATEGORIZATION_INVALID", item);
} }
if (item.mtime > inactiveTime) { if (item.mtime > inactiveTime) {
activeDocs.add(item.blobId); activeDocs.add(item.blobId);
@ -462,7 +456,7 @@ module.exports = function (Env, cb) {
Log.info('EVICT_BLOBS_CATEGORIZED', { Log.info('EVICT_BLOBS_CATEGORIZED', {
active: active, active: active,
blobs: n_blobs, blobs: n_blobs,
}); }, w());
})); }));
}; };
@ -530,31 +524,27 @@ module.exports = function (Env, cb) {
// we plan to delete them, because it may be interesting information // we plan to delete them, because it may be interesting information
inactive++; inactive++;
if (PRESERVE_INACTIVE_ACCOUNTS) { if (PRESERVE_INACTIVE_ACCOUNTS) {
Log.info('EVICT_INACTIVE_ACCOUNT_PRESERVED', { pinAll(pinList);
return Log.info('EVICT_INACTIVE_ACCOUNT_PRESERVED', {
id: id, id: id,
mtime: mtime, mtime: mtime,
}); }, next);
pinAll(pinList);
return void next();
} }
if (isPremiumAccount(id)) { if (isPremiumAccount(id)) {
Log.info("EVICT_INACTIVE_PREMIUM_ACCOUNT", { pinAll(pinList);
return Log.info("EVICT_INACTIVE_PREMIUM_ACCOUNT", {
id: id, id: id,
mtime: mtime, mtime: mtime,
}); }, next);
pinAll(pinList);
return void next();
} }
// remove the pin logs of inactive accounts if inactive account removal is configured // 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) { if (err) {
Log.error('EVICT_INACTIVE_ACCOUNT_PIN_LOG', err); return Log.error('EVICT_INACTIVE_ACCOUNT_PIN_LOG', err, next);
return void next();
} }
Log.info('EVICT_INACTIVE_ACCOUNT_LOG', id); Log.info('EVICT_INACTIVE_ACCOUNT_LOG', id, next);
next();
}); });
}; };
@ -588,12 +578,10 @@ module.exports = function (Env, cb) {
blobs.list.blobs(function (err, item, next) { blobs.list.blobs(function (err, item, next) {
next = Util.mkAsync(next, THROTTLE_FACTOR); next = Util.mkAsync(next, THROTTLE_FACTOR);
if (err) { if (err) {
Log.error("EVICT_BLOB_LIST_BLOBS_ERROR", err); return Log.error("EVICT_BLOB_LIST_BLOBS_ERROR", err, next);
return void next();
} }
if (!item) { if (!item) {
next(); return void Log.error('EVICT_BLOB_LIST_BLOBS_NO_ITEM', item, next);
return void Log.error('EVICT_BLOB_LIST_BLOBS_NO_ITEM', item);
} }
total++; total++;
if (total % PROGRESS_FACTOR === 0) { if (total % PROGRESS_FACTOR === 0) {
@ -611,23 +599,21 @@ module.exports = function (Env, cb) {
if (item.mtime > inactiveTime) { return void next(); } if (item.mtime > inactiveTime) { return void next(); }
removed++; removed++;
blobs.archive.blob(item.blobId, function (err) { blobs.archive.blob(item.blobId, 'INACTIVE', function (err) {
if (err) { if (err) {
Log.error("EVICT_ARCHIVE_BLOB_ERROR", { return Log.error("EVICT_ARCHIVE_BLOB_ERROR", {
error: err, error: err,
item: item, item: item,
}); }, next);
return void next();
} }
Log.info("EVICT_ARCHIVE_BLOB", { Log.info("EVICT_ARCHIVE_BLOB", {
item: item, item: item,
}); }, next);
next();
}); });
}, w(function () { }, w(function () {
report.totalBlobs = total; report.totalBlobs = total;
report.activeBlobs = total - removed; report.activeBlobs = total - removed;
Log.info('EVICT_BLOBS_REMOVED', removed); Log.info('EVICT_BLOBS_REMOVED', removed, w());
})); }));
}; };
@ -641,12 +627,10 @@ module.exports = function (Env, cb) {
blobs.list.proofs(function (err, item, next) { blobs.list.proofs(function (err, item, next) {
next = Util.mkAsync(next, THROTTLE_FACTOR); next = Util.mkAsync(next, THROTTLE_FACTOR);
if (err) { if (err) {
next(); return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err, next);
return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err);
} }
if (!item) { if (!item) {
next(); return void Log.error('EVICT_BLOB_LIST_PROOFS_NO_ITEM', item, next);
return void Log.error('EVICT_BLOB_LIST_PROOFS_NO_ITEM', item);
} }
total++; total++;
@ -662,8 +646,7 @@ module.exports = function (Env, cb) {
blobs.size(item.blobId, w(function (err, size) { blobs.size(item.blobId, w(function (err, size) {
if (err) { if (err) {
w.abort(); w.abort();
next(); return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err, next);
return void Log.error("EVICT_BLOB_LIST_PROOFS_ERROR", err);
} }
if (size !== 0) { if (size !== 0) {
w.abort(); w.abort();
@ -672,19 +655,18 @@ module.exports = function (Env, cb) {
})); }));
}).nThen(function () { }).nThen(function () {
blobs.remove.proof(item.safeKey, item.blobId, function (err) { blobs.remove.proof(item.safeKey, item.blobId, function (err) {
next();
if (err) { if (err) {
return Log.error("EVICT_BLOB_PROOF_LONELY_ERROR", item); return Log.error("EVICT_BLOB_PROOF_LONELY_ERROR", item, next);
} }
removed++; removed++;
return Log.info("EVICT_BLOB_PROOF_LONELY", item); return Log.info("EVICT_BLOB_PROOF_LONELY", item, next);
}); });
}); });
}, w(function () { }, w(function () {
Log.info("EVICT_BLOB_PROOFS_REMOVED", { Log.info("EVICT_BLOB_PROOFS_REMOVED", {
removed, removed,
total, total,
}); }, w());
})); }));
}; };
@ -703,8 +685,7 @@ module.exports = function (Env, cb) {
} }
if (err) { if (err) {
Log.error('EVICT_CHANNEL_ITERATION', err); return Log.error('EVICT_CHANNEL_ITERATION', err, cb);
return void cb();
} }
// ignore the special admin broadcast channel // ignore the special admin broadcast channel
@ -715,14 +696,12 @@ module.exports = function (Env, cb) {
if (item.channel.length === 34) { if (item.channel.length === 34) {
return void store.removeChannel(item.channel, w(function (err) { return void store.removeChannel(item.channel, w(function (err) {
if (err) { if (err) {
Log.error('EVICT_EPHEMERAL_CHANNEL_REMOVAL_ERROR', { return Log.error('EVICT_EPHEMERAL_CHANNEL_REMOVAL_ERROR', {
error: err, error: err,
channel: item.channel, channel: item.channel,
}); }, cb);
return void cb();
} }
Log.info('EVICT_EPHEMERAL_CHANNEL_REMOVAL', item.channel); Log.info('EVICT_EPHEMERAL_CHANNEL_REMOVAL', item.channel, cb);
cb();
})); }));
} }
@ -744,20 +723,19 @@ module.exports = function (Env, cb) {
} }
// else fall through to the archival // else fall through to the archival
})); }));
}).nThen(function () { }).nThen(function (w) {
return void store.archiveChannel(item.channel, w(function (err) { return void store.archiveChannel(item.channel, 'INACTIVE', w(function (err) {
if (err) { if (err) {
Log.error('EVICT_CHANNEL_ARCHIVAL_ERROR', { Log.error('EVICT_CHANNEL_ARCHIVAL_ERROR', {
error: err, error: err,
channel: item.channel, channel: item.channel,
}); }, w());
return void cb(); return;
} }
Log.info('EVICT_CHANNEL_ARCHIVAL', item.channel); Log.info('EVICT_CHANNEL_ARCHIVAL', item.channel, w());
archived++; archived++;
cb();
})); }));
}); }).nThen(cb);
}; };
var done = function () { var done = function () {

View File

@ -124,7 +124,7 @@ var CHECKPOINT_PATTERN = /^cp\|(([A-Za-z0-9+\/=]+)\|)?/;
but for some reason are still present but for some reason are still present
*/ */
const expireChannel = HK.expireChannel = function (Env, channel) { 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", { Env.Log.info("ARCHIVAL_CHANNEL_BY_HISTORY_KEEPER_EXPIRATION", {
channelId: channel, channelId: channel,
status: err? String(err): "SUCCESS", status: err? String(err): "SUCCESS",
@ -199,10 +199,7 @@ const getMetadata = HK.getMetadata = function (Env, channelName, _cb) {
} }
MetaRPC.getMetadataRaw(Env, channelName, function (err, metadata) { MetaRPC.getMetadataRaw(Env, channelName, function (err, metadata) {
if (err) { if (err) { return void cb(err); }
console.error(err);
return void cb(err);
}
if (!(metadata && typeof(metadata.channel) === 'string' && metadata.channel.length === STANDARD_CHANNEL_LENGTH)) { if (!(metadata && typeof(metadata.channel) === 'string' && metadata.channel.length === STANDARD_CHANNEL_LENGTH)) {
return cb(); return cb();
} }
@ -526,7 +523,12 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
getHistoryOffset(Env, channelName, lastKnownHash, waitFor((err, os) => { getHistoryOffset(Env, channelName, lastKnownHash, waitFor((err, os) => {
if (err) { if (err) {
waitFor.abort(); 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; offset = os;
})); }));
@ -540,8 +542,8 @@ const getHistoryAsync = (Env, channelName, lastKnownHash, beforeHash, handler, c
const parsed = tryParse(Env, msgObj.buff.toString('utf8')); const parsed = tryParse(Env, msgObj.buff.toString('utf8'));
if (!parsed) { return void readMore(); } if (!parsed) { return void readMore(); }
handler(parsed, readMore); handler(parsed, readMore);
}, waitFor(function (err) { }, waitFor(function (err, reason) {
return void cb(err); return void cb(err, reason);
})); }));
}); });
}; };
@ -578,6 +580,7 @@ const handleFirstMessage = function (Env, channelName, metadata) {
// Set the selfdestruct flag to history keeper ID to handle server crash. // Set the selfdestruct flag to history keeper ID to handle server crash.
metadata.selfdestruct = Env.id; metadata.selfdestruct = Env.id;
} }
delete metadata.forcePlaceholder;
Env.store.writeMetadata(channelName, JSON.stringify(metadata), function (err) { Env.store.writeMetadata(channelName, JSON.stringify(metadata), function (err) {
if (err) { if (err) {
// FIXME tell the user that there was a channel error? // FIXME tell the user that there was a channel error?
@ -682,7 +685,7 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); } if (isMetadataMessage(msg) && metadata_cache[channelName]) { return readMore(); }
if (txid) { msg[0] = txid; } if (txid) { msg[0] = txid; }
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(msg)], readMore);
}, (err) => { }, (err, reason) => {
// Any error but ENOENT: abort // Any error but ENOENT: abort
// ENOENT is allowed in case we want to create a new pad // ENOENT is allowed in case we want to create a new pad
if (err && err.code !== 'ENOENT') { if (err && err.code !== 'ENOENT') {
@ -704,6 +707,11 @@ const handleGetHistory = function (Env, Server, seq, userId, parsed) {
Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]); Server.send(userId, [0, HISTORY_KEEPER_ID, 'MSG', userId, JSON.stringify(parsedMsg)]);
return; return;
} }
if (err && err.code === 'ENOENT' && reason && !metadata.forcePlaceholder) {
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 // 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. // ENOENT, this is not a pad creation so we need to abort.

View File

@ -9,6 +9,8 @@ const Logger = require("./log");
const AuthCommands = require("./http-commands"); const AuthCommands = require("./http-commands");
const MFA = require("./storage/mfa"); const MFA = require("./storage/mfa");
const Sessions = require("./storage/sessions"); const Sessions = require("./storage/sessions");
const BlobStore = require("./storage/blob");
const BlockStore = require("./storage/block");
const DEFAULT_QUERY_TIMEOUT = 5000; const DEFAULT_QUERY_TIMEOUT = 5000;
const PID = process.pid; const PID = process.pid;
@ -198,6 +200,13 @@ app.use('/blob', function (req, res, next) {
/* Head requests are used to check the size of a blob. /* Head requests are used to check the size of a blob.
Clients can configure a maximum size to download automatically, Clients can configure a maximum size to download automatically,
and can manually click to download blobs which exceed that limit. */ and can manually click to download blobs which exceed that limit. */
const url = req.url;
if (typeof(url) === "string" && Env.blobStore) {
const s = url.split('/');
if (s[1] && s[1].length === 2 && s[2] && s[2].length === Env.blobStore.BLOB_LENGTH) {
Env.blobStore.updateActivity(s[2], () => {});
}
}
if (req.method === 'HEAD') { if (req.method === 'HEAD') {
Express.static(Path.resolve(Env.paths.blob), { Express.static(Path.resolve(Env.paths.blob), {
setHeaders: function (res /*, path, stat */) { setHeaders: function (res /*, path, stat */) {
@ -404,6 +413,23 @@ app.use('/block/', function (req, res, next) {
app.use("/block", Express.static(Path.resolve(Env.paths.block), { app.use("/block", Express.static(Path.resolve(Env.paths.block), {
maxAge: "0d", maxAge: "0d",
})); }));
// In case of a 404 for the block, check if a placeholder exists
// and provide the result if that's the case
app.use("/block", (req, res, next) => {
const url = req.url;
if (typeof(url) === "string") {
const s = url.split('/');
if (s[1] && s[1].length === 2 && BlockStore.isValidKey(s[2])) {
return BlockStore.readPlaceholder(Env, s[2], (content) => {
res.status(404).json({
reason: content,
code: 404
});
});
}
}
next();
});
app.use("/customize", Express.static('customize')); app.use("/customize", Express.static('customize'));
app.use("/customize", Express.static('customize.dist')); app.use("/customize", Express.static('customize.dist'));
@ -611,6 +637,17 @@ nThen(function (w) {
// websocket traffic to the correct port (Env.websocketPort) // websocket traffic to the correct port (Env.websocketPort)
wsProxy.upgrade(req, socket, head); wsProxy.upgrade(req, socket, head);
}); });
var config = require("./load-config");
BlobStore.create({
blobPath: config.blobPath,
blobStagingPath: config.blobStagingPath,
archivePath: config.archivePath,
getSession: function () {},
}, w(function (err, blob) {
if (err) { return; }
Env.blobStore = blob;
}));
}).nThen(function () { }).nThen(function () {
// TODO inform the parent process that this worker is ready // TODO inform the parent process that this worker is ready

View File

@ -1,5 +1,6 @@
/*jshint esversion: 6 */ /*jshint esversion: 6 */
var Store = require("./storage/file"); var Store = require("./storage/file");
var Util = require("./common-util");
var Logger = module.exports; var Logger = module.exports;
@ -15,9 +16,13 @@ var messageTemplate = function (type, time, tag, info) {
var noop = function () {}; var noop = function () {};
var write = function (ctx, content) { var write = function (ctx, content, cb) {
if (!ctx.store) { return; } if (typeof(cb) !== "function") { cb = noop; }
ctx.store.log(ctx.channelName, content, noop); if (!ctx.store) {
cb = Util.mkAsync(cb);
return void cb();
}
ctx.store.log(ctx.channelName, content, cb);
}; };
// various degrees of logging // various degrees of logging
@ -37,7 +42,7 @@ var createLogType = function (ctx, type) {
if (logLevels.indexOf(type) < logLevels.indexOf(ctx.logLevel)) { if (logLevels.indexOf(type) < logLevels.indexOf(ctx.logLevel)) {
return noop; return noop;
} }
return function (tag, info) { return function (tag, info, cb) {
if (ctx.shutdown) { if (ctx.shutdown) {
throw new Error("Logger has been shut down!"); throw new Error("Logger has been shut down!");
} }
@ -51,7 +56,7 @@ var createLogType = function (ctx, type) {
if (ctx.logToStdout && typeof(handlers[type]) === 'function') { if (ctx.logToStdout && typeof(handlers[type]) === 'function') {
handlers[type](ctx, content); handlers[type](ctx, content);
} }
write(ctx, content); write(ctx, content, cb);
}; };
}; };

View File

@ -33,6 +33,29 @@ var createLineHandler = Pins.createLineHandler = function (ref, errorHandler) {
ref.index = 0; ref.index = 0;
ref.latest = 0; // the latest message (timestamp in ms) ref.latest = 0; // the latest message (timestamp in ms)
ref.surplus = 0; // how many lines exist behind a reset ref.surplus = 0; // how many lines exist behind a reset
// Extract metadata from the channel list (#block, #drive)
let sanitize = (id, isPin) => {
if (typeof(id) !== "string") { return; }
let idx = id.indexOf('#');
if (idx < 0) { return id; }
let type = id.slice(idx+1);
let sanitized = id.slice(0, idx);
if (!isPin) { return sanitized; }
if (type === 'block') { // Note: teams don't have a block
ref.block = sanitized;
return;
}
if (type === 'drive') {
ref.drive = sanitized;
return sanitized;
}
return sanitized;
};
return function (line) { return function (line) {
ref.index++; ref.index++;
if (!Boolean(line)) { return; } if (!Boolean(line)) { return; }
@ -55,17 +78,31 @@ var createLineHandler = Pins.createLineHandler = function (ref, errorHandler) {
switch (l[0]) { switch (l[0]) {
case 'RESET': { case 'RESET': {
pins = ref.pins = {}; pins = ref.pins = {};
if (l[1] && l[1].length) { l[1].forEach((x) => { ref.pins[x] = 1; }); } if (l[1] && l[1].length) {
l[1].forEach((x) => {
x = sanitize(x, true);
if (!x) { return; }
ref.pins[x] = 1;
});
}
ref.surplus = ref.index; ref.surplus = ref.index;
//jshint -W086 //jshint -W086
// fallthrough // fallthrough
} }
case 'PIN': { case 'PIN': {
l[1].forEach((x) => { pins[x] = 1; }); l[1].forEach((x) => {
x = sanitize(x, true);
if (!x) { return; }
pins[x] = 1;
});
break; break;
} }
case 'UNPIN': { case 'UNPIN': {
l[1].forEach((x) => { delete pins[x]; }); l[1].forEach((x) => {
x = sanitize(x, false);
if (!x) { return; }
delete pins[x];
});
break; break;
} }
default: default:

View File

@ -8,12 +8,14 @@ var nThen = require("nthen");
var Semaphore = require("saferphore"); var Semaphore = require("saferphore");
var Util = require("../common-util"); var Util = require("../common-util");
const BLOB_LENGTH = 48;
var isValidSafeKey = function (safeKey) { var isValidSafeKey = function (safeKey) {
return typeof(safeKey) === 'string' && !/\//.test(safeKey) && safeKey.length === 44; return typeof(safeKey) === 'string' && !/\//.test(safeKey) && safeKey.length === 44;
}; };
var isValidId = function (id) { var isValidId = function (id) {
return typeof(id) === 'string' && id.length === 48 && !/[^a-f0-9]/.test(id); return typeof(id) === 'string' && id.length === BLOB_LENGTH && !/[^a-f0-9]/.test(id);
}; };
// helpers // helpers
@ -31,6 +33,10 @@ var makeBlobPath = function (Env, blobId) {
return Path.join(Env.blobPath, blobId.slice(0, 2), blobId); return Path.join(Env.blobPath, blobId.slice(0, 2), blobId);
}; };
var makeActivityPath = function (Env, blobId) {
return makeBlobPath(Env, blobId) + '.activity';
};
// /blobstate/<safeKeyPrefix>/<safeKey> // /blobstate/<safeKeyPrefix>/<safeKey>
var makeStagePath = function (Env, safeKey) { var makeStagePath = function (Env, safeKey) {
return Path.join(Env.blobStagingPath, safeKey.slice(0, 2), safeKey); return Path.join(Env.blobStagingPath, safeKey.slice(0, 2), safeKey);
@ -41,6 +47,10 @@ var makeProofPath = function (Env, safeKey, blobId) {
return Path.join(Env.blobPath, safeKey.slice(0, 3), safeKey, blobId.slice(0, 2), 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 parseProofPath = function (path) {
var parts = path.split('/'); var parts = path.split('/');
return { return {
@ -49,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 // getUploadSize: used by
// getFileSize // getFileSize
var getUploadSize = function (Env, blobId, cb) { var getUploadSize = function (Env, blobId, cb) {
@ -57,7 +87,16 @@ var getUploadSize = function (Env, blobId, cb) {
Fs.stat(path, function (err, stats) { Fs.stat(path, function (err, stats) {
if (err) { if (err) {
// if a file was deleted, its size is 0 bytes // 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); return void cb(err.code);
} }
cb(void 0, stats.size); cb(void 0, stats.size);
@ -103,6 +142,47 @@ var makeFileStream = function (full, _cb) {
}); });
}; };
var clearActivity = function (Env, blobId, cb) {
var path = makeActivityPath(Env, blobId);
// if we fail to delete the activity file, it can still be removed later by the eviction script
Fs.unlink(path, cb);
};
var updateActivity = function (Env, blobId, cb) {
var path = makeActivityPath(Env, blobId);
var s_data = String(+new Date());
Fs.writeFile(path, s_data, cb);
};
var archiveActivity = function (Env, blobId, cb) {
var path = makeActivityPath(Env, blobId);
var archivePath = prependArchive(Env, path);
// if we fail to delete the activity file, it can still be removed later by the eviction script
Fse.move(path, archivePath, { overwrite: true }, cb);
};
var removeArchivedActivity = function (Env, blobId, cb) {
var path = makeActivityPath(Env, blobId);
var archivePath = prependArchive(Env, path);
Fs.unlink(archivePath, cb);
};
var restoreActivity = function (Env, blobId, cb) {
var path = makeActivityPath(Env, blobId);
var archivePath = prependArchive(Env, path);
Fse.move(archivePath, path, cb);
};
var getActivity = function (Env, blobId, cb) {
var path = makeActivityPath(Env, blobId);
Fs.readFile(path, function (err, content) {
if (err) { return void cb(err); }
try {
var date = new Date(+content);
cb(void 0, date);
} catch (err2) {
cb(err2);
}
});
};
/********** METHODS **************/ /********** METHODS **************/
var upload = function (Env, safeKey, content, cb) { var upload = function (Env, safeKey, content, cb) {
@ -298,10 +378,12 @@ var owned_upload_complete = function (Env, safeKey, id, cb) {
}); });
}; };
// removeBlob // removeBlob
var remove = function (Env, blobId, cb) { var remove = function (Env, blobId, cb) {
var blobPath = makeBlobPath(Env, blobId); var blobPath = makeBlobPath(Env, blobId);
Fs.unlink(blobPath, cb); Fs.unlink(blobPath, cb);
clearActivity(Env, blobId, () => {});
}; };
// removeProof // removeProof
@ -318,15 +400,18 @@ var isOwnedBy = function (Env, safeKey, blobId, cb) {
// archiveBlob // archiveBlob
var archiveBlob = function (Env, blobId, cb) { var archiveBlob = function (Env, blobId, reason, cb) {
var blobPath = makeBlobPath(Env, blobId); var blobPath = makeBlobPath(Env, blobId);
var archivePath = prependArchive(Env, blobPath); var archivePath = prependArchive(Env, blobPath);
Fse.move(blobPath, archivePath, { overwrite: true }, cb); Fse.move(blobPath, archivePath, { overwrite: true }, cb);
archiveActivity(Env, blobId, () => {});
addPlaceholder(Env, blobId, reason, () => {});
}; };
var removeArchivedBlob = function (Env, blobId, cb) { var removeArchivedBlob = function (Env, blobId, cb) {
var archivePath = prependArchive(Env, makeBlobPath(Env, blobId)); var archivePath = prependArchive(Env, makeBlobPath(Env, blobId));
Fs.unlink(archivePath, cb); Fs.unlink(archivePath, cb);
removeArchivedActivity(Env, blobId, () => {});
}; };
// restoreBlob // restoreBlob
@ -334,6 +419,8 @@ var restoreBlob = function (Env, blobId, cb) {
var blobPath = makeBlobPath(Env, blobId); var blobPath = makeBlobPath(Env, blobId);
var archivePath = prependArchive(Env, blobPath); var archivePath = prependArchive(Env, blobPath);
Fse.move(archivePath, blobPath, cb); Fse.move(archivePath, blobPath, cb);
restoreActivity(Env, blobId, () => {});
clearPlaceholder(Env, blobId, () => {});
}; };
// archiveProof // archiveProof
@ -506,6 +593,7 @@ BlobStore.create = function (config, _cb) {
Fse.writeFile(fullPath, 'PLACEHOLDER\n', w()); Fse.writeFile(fullPath, 'PLACEHOLDER\n', w());
}).nThen(function () { }).nThen(function () {
var methods = { var methods = {
BLOB_LENGTH: BLOB_LENGTH,
isFileId: isValidId, isFileId: isValidId,
status: function (safeKey, _cb) { status: function (safeKey, _cb) {
// TODO check if the final destination is a file // TODO check if the final destination is a file
@ -562,10 +650,10 @@ BlobStore.create = function (config, _cb) {
}, },
archive: { archive: {
blob: function (blobId, _cb) { blob: function (blobId, reason, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
if (!isValidId(blobId)) { return void cb("INVALID_ID"); } if (!isValidId(blobId)) { return void cb("INVALID_ID"); }
archiveBlob(Env, blobId, cb); archiveBlob(Env, blobId, reason, cb);
}, },
proof: function (safeKey, blobId, _cb) { proof: function (safeKey, blobId, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
@ -601,6 +689,9 @@ BlobStore.create = function (config, _cb) {
var path = prependArchive(Env, makeBlobPath(Env, blobId)); var path = prependArchive(Env, makeBlobPath(Env, blobId));
isFile(path, cb); isFile(path, cb);
}, },
getPlaceholder: function (blobId, cb) {
readPlaceholder(Env, blobId, cb);
},
closeBlobstage: function (safeKey) { closeBlobstage: function (safeKey) {
closeBlobstage(Env, safeKey); closeBlobstage(Env, safeKey);
@ -623,6 +714,18 @@ BlobStore.create = function (config, _cb) {
getUploadSize(Env, id, cb); getUploadSize(Env, id, cb);
}, },
// ACTIVITY
updateActivity: function (id, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidId(id)) { return void cb("INVALID_ID"); }
updateActivity(Env, id, cb);
},
getActivity: function (id, _cb) {
var cb = Util.once(Util.mkAsync(_cb));
if (!isValidId(id)) { return void cb("INVALID_ID"); }
getActivity(Env, id, cb);
},
list: { list: {
blobs: function (handler, _cb) { blobs: function (handler, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
@ -641,6 +744,7 @@ BlobStore.create = function (config, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
listBlobs(prependArchive(Env, Env.blobPath), handler, cb); listBlobs(prependArchive(Env, Env.blobPath), handler, cb);
}, },
// XXX activity
} }
}, },
}; };

View File

@ -32,7 +32,28 @@ Block.mkArchivePath = function (Env, publicKey) {
return Path.join(Env.paths.archive, 'block', safeKey.slice(0, 2), safeKey); return Path.join(Env.paths.archive, 'block', safeKey.slice(0, 2), safeKey);
}; };
Block.archive = function (Env, publicKey, _cb) { var mkPlaceholderPath = function (Env, publicKey) {
return Block.mkPath(Env, publicKey) + '.placeholder';
};
var addPlaceholder = function (Env, publicKey, reason, cb) {
if (!reason) { return cb(); }
var path = mkPlaceholderPath(Env, publicKey);
var s_data = typeof(reason) === "string" ? reason : `${reason.code}:${reason.txt}`;
Fs.writeFile(path, s_data, cb);
};
var clearPlaceholder = function (Env, publicKey, cb) {
var path = mkPlaceholderPath(Env, publicKey);
Fs.unlink(path, cb);
};
Block.readPlaceholder = function (Env, publicKey, cb) {
var path = mkPlaceholderPath(Env, publicKey);
Fs.readFile(path, function (err, content) {
if (err) { return void cb(); }
cb(content.toString('utf8'));
});
};
Block.archive = function (Env, publicKey, reason, _cb) {
var cb = Util.once(Util.mkAsync(_cb)); var cb = Util.once(Util.mkAsync(_cb));
// derive the filepath // derive the filepath
@ -52,7 +73,10 @@ Block.archive = function (Env, publicKey, _cb) {
// TODO Env.incrementBytesWritten // TODO Env.incrementBytesWritten
Fse.move(currentPath, archivePath, { Fse.move(currentPath, archivePath, {
overwrite: true, overwrite: true,
}, cb); }, (err) => {
cb(err);
if (!err && reason) { addPlaceholder(Env, publicKey, reason, () => {}); }
});
}; };
Block.restore = function (Env, publicKey, _cb) { Block.restore = function (Env, publicKey, _cb) {
@ -75,10 +99,13 @@ Block.restore = function (Env, publicKey, _cb) {
// TODO Env.incrementBytesWritten // TODO Env.incrementBytesWritten
Fse.move(archivePath, livePath, { Fse.move(archivePath, livePath, {
//overwrite: true, //overwrite: true,
}, cb); }, (err) => {
cb(err);
if (!err) { clearPlaceholder(Env, publicKey, () => {}); }
});
}; };
var isValidKey = function (publicKey) { var isValidKey = Block.isValidKey = function (publicKey) {
return typeof(publicKey) === 'string' && publicKey.length === 44; return typeof(publicKey) === 'string' && publicKey.length === 44;
}; };
@ -131,7 +158,7 @@ Block.write = function (Env, publicKey, buffer, _cb) {
cb(err); cb(err);
})); }));
}).nThen(function (w) { }).nThen(function (w) {
Block.archive(Env, publicKey, w(function (/* err */) { Block.archive(Env, publicKey, 'PASSWORD_CHANGE', w(function (/* err */) {
/* /*
we proceed even if there are errors. we proceed even if there are errors.
it might be ENOENT (there is no file to archive) it might be ENOENT (there is no file to archive)

View File

@ -70,6 +70,10 @@ var mkOffsetPath = function (env, channelId) {
return mkPath(env, channelId) + '.offset'; 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 // pass in the path so we can reuse the same function for archived files
var channelExists = function (filepath, cb) { var channelExists = function (filepath, cb) {
Fs.stat(filepath, function (err, stat) { 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) { const destroyStream = function (stream) {
if (!stream) { return; } if (!stream) { return; }
try { try {
@ -190,7 +213,16 @@ const readMessagesBin = (env, id, start, msgHandler, cb) => {
const stream = Fs.createReadStream(mkPath(env, id), { start: start }); const stream = Fs.createReadStream(mkPath(env, id), { start: start });
const collector = createIdleStreamCollector(stream); const collector = createIdleStreamCollector(stream);
const handleMessageAndKeepStreamAlive = Util.both(msgHandler, collector.keepAlive); 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, { return void readFileBin(stream, handleMessageAndKeepStreamAlive, done, {
offset: start, offset: start,
}); });
@ -360,7 +392,14 @@ var getDedicatedMetadata = function (env, channelId, handler, _cb) {
readMore(); readMore();
}, function (err) { }, function (err) {
// ENOENT => there is no metadata log // ENOENT => there is no metadata log
if (!err || err.code === 'ENOENT') { return void cb(); } if (!err || err.code === 'ENOENT') {
if (err && err.code === 'ENOENT') {
return readPlaceholder(env, channelId, (content) => {
cb(content);
});
}
return void cb();
}
// otherwise stream errors? // otherwise stream errors?
cb(err); cb(err);
}); });
@ -670,7 +709,7 @@ var listChannels = function (root, handler, cb, fast) {
// move a channel's log file from its current location // move a channel's log file from its current location
// to an equivalent location in the cold storage directory // 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? // TODO close channels before archiving them?
// ctime is the most reliable indicator of when a file was archived // ctime is the most reliable indicator of when a file was archived
@ -700,6 +739,9 @@ var archiveChannel = function (env, channelName, cb) {
})); }));
}).nThen(function (w) { }).nThen(function (w) {
clearOffset(env, channelName, w()); clearOffset(env, channelName, w());
}).nThen(function (w) {
if (!reason) { return; }
addPlaceholder(env, channelName, reason, w());
}).nThen(function (w) { }).nThen(function (w) {
// archive the dedicated metadata channel // archive the dedicated metadata channel
var metadataPath = mkMetadataPath(env, channelName); var metadataPath = mkMetadataPath(env, channelName);
@ -775,6 +817,8 @@ var unarchiveChannel = function (env, channelName, cb) {
return void CB(err); return void CB(err);
} }
})); }));
}).nThen(function (w) {
clearPlaceholder(env, channelName, w());
}).nThen(function (w) { }).nThen(function (w) {
var archiveMetadataPath = mkArchiveMetadataPath(env, channelName); var archiveMetadataPath = mkArchiveMetadataPath(env, channelName);
// TODO validate that it's ok to move metadata non-atomically // TODO validate that it's ok to move metadata non-atomically
@ -1290,12 +1334,12 @@ module.exports.create = function (conf, _cb) {
isChannelArchived(env, channelName, cb); isChannelArchived(env, channelName, cb);
}, },
// move a channel from the database to the archive, along with its metadata // 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')); } if (!isValidChannelId(channelName)) { return void cb(new Error('EINVAL')); }
// again, the semantics around archiving and appending are really muddy. // again, the semantics around archiving and appending are really muddy.
// so I'm calling this 'unordered' again // so I'm calling this 'unordered' again
schedule.unordered(channelName, function (next) { 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 // restore a channel from the archive to the database, along with its metadata
@ -1385,6 +1429,9 @@ module.exports.create = function (conf, _cb) {
channelBytes(env, channelName, Util.both(cb, next)); channelBytes(env, channelName, Util.both(cb, next));
}); });
}, },
getPlaceholder: function (channelName, cb) {
readPlaceholder(env, channelName, cb);
},
// OTHER DATABASE FUNCTIONALITY // OTHER DATABASE FUNCTIONALITY
// remove a particular channel from the cache // remove a particular channel from the cache
closeChannel: function (channelName, cb) { closeChannel: function (channelName, cb) {

View File

@ -387,8 +387,10 @@ const getPinState = function (data, cb) {
var lineHandler = Pins.createLineHandler(ref, Env.Log.error); var lineHandler = Pins.createLineHandler(ref, Env.Log.error);
// if channels aren't in memory. load them from disk // if channels aren't in memory. load them from disk
// TODO replace with readMessagesBin pinStore.readMessagesBin(safeKey, 0, (msgObj, readMore) => {
pinStore.getMessages(safeKey, lineHandler, function () { lineHandler(msgObj.buff.toString('utf8'));
readMore();
}, function () {
cb(void 0, ref.pins); // FIXME no error handling? cb(void 0, ref.pins); // FIXME no error handling?
}); });
}; };
@ -497,8 +499,13 @@ const getHashOffset = function (data, cb) {
} }
offset = msgObj.offset; offset = msgObj.offset;
abort(); abort();
}, function (err) { }, function (err, reason) {
if (err) { return void cb(err); } if (err) {
return void cb({
error: err,
reason: reason
});
}
cb(void 0, offset); cb(void 0, offset);
}); });
}; };
@ -508,6 +515,8 @@ const removeOwnedBlob = function (data, cb) {
const blobId = data.blobId; const blobId = data.blobId;
const safeKey = Util.escapeKeyCharacters(data.safeKey); const safeKey = Util.escapeKeyCharacters(data.safeKey);
const reason = data.reason || 'ARCHIVE_OWNED';
nThen(function (w) { nThen(function (w) {
// check if you have permissions // check if you have permissions
blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) { blobStore.isOwnedBy(safeKey, blobId, w(function (err, owned) {
@ -518,7 +527,7 @@ const removeOwnedBlob = function (data, cb) {
})); }));
}).nThen(function (w) { }).nThen(function (w) {
// remove the blob // 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', { Env.Log.info('ARCHIVAL_OWNED_FILE_BY_OWNER_RPC', {
safeKey: safeKey, safeKey: safeKey,
blobId: blobId, blobId: blobId,
@ -599,16 +608,18 @@ const getPinActivity = function (data, cb) {
var safeKey = Util.escapeKeyCharacters(data.key); var safeKey = Util.escapeKeyCharacters(data.key);
var first; var first;
var latest; var latest;
pinStore.getMessages(safeKey, line => { pinStore.readMessagesBin(safeKey, 0, (msgObj, readMore) => {
if (!line || !line.trim()) { return; } var line = msgObj.buff.toString('utf8');
if (!line || !line.trim()) { return readMore(); }
try { try {
var parsed = JSON.parse(line); var parsed = JSON.parse(line);
var temp = parsed[parsed.length - 1]; var temp = parsed[parsed.length - 1];
if (!temp || typeof(temp) !== 'number') { return; } if (!temp || typeof(temp) !== 'number') { return readMore(); }
latest = temp; latest = temp;
if (first) { return; } if (first) { return readMore(); }
first = latest; first = latest;
} catch (err) { } readMore();
} catch (err) { readMore(); }
}, function (err) { }, function (err) {
if (err) { return void cb(err); } if (err) { return void cb(err); }
cb(void 0, { cb(void 0, {

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({ sendCommand({
command: 'REMOVE_OWNED_BLOB', command: 'REMOVE_OWNED_BLOB',
blobId: blobId, blobId: blobId,
safeKey: safeKey, safeKey: safeKey,
reason: reason
}, cb); }, cb);
}; };

View File

@ -53,6 +53,20 @@ define([
var common; var common;
var sFrameChan; var sFrameChan;
// XXX
// TO DELETE
// Messages.admin_archivePinLog
// Messages.admin_restoreArchivedPins
// TO ADD
Messages.admin_archiveAccount = "Archive this account";
Messages.admin_archiveAccountInfo = "Including its owned documents";
Messages.admin_archiveAccountConfirm = "Please specify the reason for archival, this will be shown to the user.";
Messages.admin_restoreAccount = "Restore this account";
Messages.admin_accountSuspended = "Account archived by admin";
Messages.admin_accountReport = "Account archive report";
Messages.admin_accountReportFull = "Get detailed report";
Messages.admin_channelPlaceholder = "Destroyed document placeholder";
var categories = { var categories = {
'general': [ // Msg.admin_cat_general 'general': [ // Msg.admin_cat_general
'cp-admin-flush-cache', 'cp-admin-flush-cache',
@ -226,6 +240,7 @@ define([
data.currentlyOnline = response[0]; data.currentlyOnline = response[0];
})); }));
}).nThen(function (w) { }).nThen(function (w) {
if (!data.first) { return; }
sframeCommand('GET_USER_QUOTA', key, w((err, response) => { sframeCommand('GET_USER_QUOTA', key, w((err, response) => {
if (err || !response) { if (err || !response) {
return void console.error('quota', err, response); return void console.error('quota', err, response);
@ -236,6 +251,7 @@ define([
} }
})); }));
}).nThen(function (w) { }).nThen(function (w) {
if (!data.first) { return; }
// storage used // storage used
sframeCommand('GET_USER_TOTAL_SIZE', key, w((err, response) => { sframeCommand('GET_USER_TOTAL_SIZE', key, w((err, response) => {
if (err || !Array.isArray(response)) { if (err || !Array.isArray(response)) {
@ -246,6 +262,7 @@ define([
} }
})); }));
}).nThen(function (w) { }).nThen(function (w) {
if (!data.first) { return; }
// channels pinned // channels pinned
// files pinned // files pinned
sframeCommand('GET_USER_STORAGE_STATS', key, w((err, response) => { sframeCommand('GET_USER_STORAGE_STATS', key, w((err, response) => {
@ -268,6 +285,17 @@ define([
data.archived = response[0].archived; data.archived = response[0].archived;
} }
})); }));
}).nThen(function (w) {
if (data.first) { return; }
// Account is probably deleted
sframeCommand('GET_ACCOUNT_ARCHIVE_STATUS', {key}, w((err, response) => {
if (err || !Array.isArray(response) || !response[0]) {
console.error('account status', err, response);
} else {
console.info('account status', response);
data.archiveReport = response[0];
}
}));
}).nThen(function () { }).nThen(function () {
//console.log(data); //console.log(data);
try { try {
@ -281,6 +309,12 @@ define([
if (typeof(val) !== 'number') { return; } if (typeof(val) !== 'number') { return; }
data[`${k}_formatted`] = getPrettySize(val); data[`${k}_formatted`] = getPrettySize(val);
}); });
if (data.archiveReport) {
let formatted = Util.clone(data.archiveReport);
formatted.channels = data.archiveReport.channels.length;
formatted.blobs = data.archiveReport.blobs.length;
data['archiveReport_formatted'] = JSON.stringify(formatted, 0, 2);
}
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
@ -391,11 +425,13 @@ define([
])); ]));
} }
// First pin activity time if (data.first || data.latest) {
row(Messages.admin_firstPinTime, maybeDate(data.first)); // First pin activity time
row(Messages.admin_firstPinTime, maybeDate(data.first));
// last pin activity time // last pin activity time
row(Messages.admin_lastPinTime, maybeDate(data.latest)); row(Messages.admin_lastPinTime, maybeDate(data.latest));
}
// currently online // currently online
row(Messages.admin_currentlyOnline, localizeState(data.currentlyOnline)); row(Messages.admin_currentlyOnline, localizeState(data.currentlyOnline));
@ -407,27 +443,46 @@ define([
row(Messages.admin_note, data.note || Messages.ui_none); row(Messages.admin_note, data.note || Messages.ui_none);
// storage limit // storage limit
row(Messages.admin_planlimit, getPrettySize(data.limit)); if (data.limit) { row(Messages.admin_planlimit, getPrettySize(data.limit)); }
// data stored // data stored
row(Messages.admin_storageUsage, getPrettySize(data.usage)); if (data.usage) { row(Messages.admin_storageUsage, getPrettySize(data.usage)); }
// number of channels // number of channels
row(Messages.admin_channelCount, data.channels); if (typeof(data.channel) === "number") {
row(Messages.admin_channelCount, data.channels);
}
// number of files pinned // number of files pinned
row(Messages.admin_fileCount, data.files); if (typeof(data.channel) === "number") {
row(Messages.admin_fileCount, data.files);
}
row(Messages.admin_pinLogAvailable, localizeState(data.live)); row(Messages.admin_pinLogAvailable, localizeState(data.live));
// pin log archived // pin log archived
row(Messages.admin_pinLogArchived, localizeState(data.archived)); row(Messages.admin_pinLogArchived, localizeState(data.archived));
if (data.archiveReport) {
row(Messages.admin_accountSuspended, localizeState(Boolean(data.archiveReport)));
}
if (data.archiveReport_formatted) {
let button, pre;
row(Messages.admin_accountReport, h('div', [
pre = h('pre', data.archiveReport_formatted),
button = primary(Messages.admin_accountReportFull, () => {
$(button).remove();
$(pre).html(JSON.stringify(data.archiveReport, 0, 2));
})
]));
}
// actions // actions
if (data.archived && data.live === false) { if (data.archived && data.live === false && data.archiveReport) {
row(Messages.admin_restoreArchivedPins, primary(Messages.ui_restore, function () { row(Messages.admin_restoreAccount, primary(Messages.ui_restore, function () {
justifyRestorationDialog('', reason => { justifyRestorationDialog('', reason => {
sframeCommand('RESTORE_ARCHIVED_PIN_LOG', { sframeCommand('RESTORE_ACCOUNT', {
key: data.key, key: data.key,
reason: reason, reason: reason,
}, function (err) { }, function (err) {
@ -497,9 +552,10 @@ define([
// archive pin log // archive pin log
var archiveHandler = () => { var archiveHandler = () => {
justifyArchivalDialog(Messages.admin_archivePinLogConfirm, reason => { justifyArchivalDialog(Messages.admin_archiveAccountConfirm, reason => {
sframeCommand('ARCHIVE_PIN_LOG', { sframeCommand('ARCHIVE_ACCOUNT', {
key: data.key, key: data.key,
block: data.blockId,
reason: reason, reason: reason,
}, (err /*, response */) => { }, (err /*, response */) => {
console.error(err); console.error(err);
@ -512,7 +568,12 @@ define([
}); });
}; };
row(Messages.admin_archivePinLog, danger(Messages.admin_archiveButton, archiveHandler)); var archiveAccountLabel = h('span', [
Messages.admin_archiveAccount,
h('br'),
h('small', Messages.archiveAccountInfo)
]);
row(archiveAccountLabel, danger(Messages.admin_archiveButton, archiveHandler));
// archive owned documents // archive owned documents
/* // TODO not implemented /* // TODO not implemented
@ -678,6 +739,7 @@ define([
} }
data.live = res[0].live; data.live = res[0].live;
data.archived = res[0].archived; data.archived = res[0].archived;
data.placeholder = res[0].placeholder;
//console.error("get channel status", err, res); //console.error("get channel status", err, res);
})); }));
}).nThen(function () { }).nThen(function () {
@ -696,7 +758,6 @@ define([
/* FIXME /* FIXME
Messages.admin_getFullPinHistory = 'Pin history'; Messages.admin_getFullPinHistory = 'Pin history';
Messages.admin_archivePinLogConfirm = "All content in this user's drive will be un-listed, meaning it may be deleted if it is not in any other drive.";
Messages.admin_archiveOwnedAccountDocuments = "Archive this account's owned documents (not implemented)"; Messages.admin_archiveOwnedAccountDocuments = "Archive this account's owned documents (not implemented)";
Messages.admin_archiveOwnedDocumentsConfirm = "All content owned exclusively by this user will be archived. This means their documents, drive, and accounts will be made inaccessible. This action cannot be undone. Please save the full pin list before proceeding to ensure individual documents can be restored."; Messages.admin_archiveOwnedDocumentsConfirm = "All content owned exclusively by this user will be archived. This means their documents, drive, and accounts will be made inaccessible. This action cannot be undone. Please save the full pin list before proceeding to ensure individual documents can be restored.";
*/ */
@ -791,6 +852,11 @@ define([
} }
if (data.placeholder) {
console.warn('Placeholder code', data.placeholder);
row(Messages.admin_channelPlaceholder, UI.getDestroyedPlaceholderMessage(data.placeholder));
}
if (data.live && data.archived) { if (data.live && data.archived) {
let disableButtons; let disableButtons;
let restoreButton = danger(Messages.admin_unarchiveButton, function () { let restoreButton = danger(Messages.admin_unarchiveButton, function () {
@ -1075,6 +1141,7 @@ define([
data.live = res[0].live; data.live = res[0].live;
data.archived = res[0].archived; data.archived = res[0].archived;
data.totp = res[0].totp; data.totp = res[0].totp;
data.placeholder = res[0].placeholder;
})); }));
}).nThen(function () { }).nThen(function () {
try { try {
@ -1120,6 +1187,10 @@ define([
}); });
row(Messages.admin_archiveBlock, archiveButton); row(Messages.admin_archiveBlock, archiveButton);
} }
if (data.placeholder) {
console.warn('Placeholder code', data.placeholder);
row(Messages.admin_channelPlaceholder, UI.getDestroyedPlaceholderMessage(data.placeholder, true));
}
if (data.archived && !data.live) { if (data.archived && !data.live) {
var restoreButton = danger(Messages.ui_restore, function () { var restoreButton = danger(Messages.ui_restore, function () {
justifyRestorationDialog('', reason => { justifyRestorationDialog('', reason => {

View File

@ -1043,12 +1043,17 @@ define([
window.parent.location = href; window.parent.location = href;
}); });
if (exitable) { if (exitable) {
// XXX if true or function, ALSO add a button to leave
$(window).focus(); $(window).focus();
$(window).keydown(function (e) { // XXX what if they don't have a keyboard? $(window).keydown(function (e) { // XXX what if they don't have a keyboard?
if (e.which === 27) { if (e.which === 27) {
e.preventDefault();
e.stopPropagation();
// Function: call the function (should be a redirect)
if (typeof(exitable) === "function") { return void exitable(); }
// Otherwise remove the loading screen
$loading.hide(); $loading.hide();
$('html').toggleClass('cp-loading-noscroll', false); $('html').toggleClass('cp-loading-noscroll', false);
if (typeof(exitable) === "function") { exitable(); }
} }
}); });
} }
@ -1545,5 +1550,74 @@ define([
}; };
// XXX
Messages.dph_reason = "Reason: {0}";
Messages.dph_default = "This content is no longer available"; // default key when custom message not found
Messages.dph_account_destroyed = "This account has been deleted by its owner";
Messages.dph_account_inactive = "This account has been deleted for inactivity";
Messages.dph_account_moderated = "This account has been suspended by the moderation team";
Messages.dph_account_pw = "This account's password has been changed";
Messages.dph_pad_destroyed = "This document has been destroyed by its owner";
Messages.dph_pad_inactive = "This document has been deleted for inactivity";
Messages.dph_pad_moderated = "This document has been deleted by the moderation team";
Messages.dph_pad_moderated_account = "This document has been deleted with its owner's account"; // Keep this key ???
Messages.dph_pad_pw = "The document you are trying to open is protected with a new password. Enter the correct password to access the content.";
Messages.dph_tmp_destroyed = "This template has been destroyed by its owner";
Messages.dph_tmp_moderated = "This template has been deleted by the moderation team";
Messages.dph_tmp_moderated_account = "This template has been deleted with its owner's account"; // Keep this key ???
Messages.dph_tmp_pw = "This template is protected with a new password. Open it from your drive to enter its new password.";
UI.getDestroyedPlaceholderMessage = (code, isAccount, isTemplate) => {
var account = {
ARCHIVE_OWNED: Messages.dph_account_destroyed,
INACTIVE: Messages.dph_account_inactive,
MODERATION_ACCOUNT: Messages.dph_account_moderated,
MODERATION_BLOCK: Messages.dph_account_moderated,
PASSWORD_CHANGE: Messages.dph_account_pw,
};
var template = {
ARCHIVE_OWNED: Messages.dph_tmp_destroyed,
MODERATION_PAD: Messages.dph_tmp_moderated,
MODERATION_ACCOUNT: Messages.dph_tmp_moderated_account,
PASSWORD_CHANGE: Messages.dph_tmp_pw
};
var pad = {
ARCHIVE_OWNED: Messages.dph_pad_destroyed,
INACTIVE: Messages.dph_pad_inactive,
MODERATION_PAD: Messages.dph_pad_moderated,
MODERATION_DESTROY: Messages.dph_pad_moderated,
MODERATION_ACCOUNT: Messages.dph_pad_moderated_account,
PASSWORD_CHANGE: Messages.dph_pad_pw
};
var msg = pad[code];
if (isAccount) {
msg = account[code];
} else if (isTemplate) {
msg = template[code];
}
if (!msg) { msg = Messages.dph_default; }
return msg;
};
UI.getDestroyedPlaceholder = function (reason, isAccount) {
if (typeof(reason) !== "string") { return; }
var split = reason.split(':');
var code = split[0]; // Generated code
var input = split[1]; // User/admin manual input
var text = UI.getDestroyedPlaceholderMessage(code, isAccount);
if (!text) { return; } // XXX
var reasonBlock = input ? h('p', Messages._getKey('dph_reason', [input])) : undefined;
return h('div', [
h('p', text),
reasonBlock
]);
};
return UI; return UI;
}); });

View File

@ -317,7 +317,7 @@ define([
var key = secret.keys && secret.keys.cryptKey; var key = secret.keys && secret.keys.cryptKey;
MediaTag.fetchDecryptedMetadata(src, key, function (e, metadata) { MediaTag.fetchDecryptedMetadata(src, key, function (e, metadata) {
if (e) { if (e) {
if (e === 'XHR_ERROR') { return; } if (/^XHR_ERROR/.test(e)) { return; }
return console.error(e); return console.error(e);
} }
if (!metadata) { return console.error("NO_METADATA"); } if (!metadata) { return console.error("NO_METADATA"); }

View File

@ -2869,6 +2869,7 @@ define([
var priv = common.getMetadataMgr().getPrivateData(); var priv = common.getMetadataMgr().getPrivateData();
var sframeChan = common.getSframeChannel(); var sframeChan = common.getSframeChannel();
var msg = err.type; var msg = err.type;
var exitable = Boolean(err.loaded);
if (err.type === 'EEXPIRED') { if (err.type === 'EEXPIRED') {
msg = Messages.expiredError; msg = Messages.expiredError;
if (err.loaded) { if (err.loaded) {
@ -2885,6 +2886,22 @@ define([
delete autoStoreModal[priv.channel]; delete autoStoreModal[priv.channel];
} }
if (err.message && err.drive) {
let msg = UI.getDestroyedPlaceholder(err.message, true);
return UI.errorLoadingScreen(msg, false, () => {
// When closing error screen
if (err.message === 'PASSWORD_CHANGE') {
return common.setLoginRedirect('login');
}
return common.setLoginRedirect('');
});
}
if (err.message && err.message !== "PASSWORD_CHANGE") {
UI.errorLoadingScreen(UI.getDestroyedPlaceholder(err.message, false),
exitable, exitable);
return;
}
if (err.ownDeletion) { if (err.ownDeletion) {
if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); } if (toolbar && typeof toolbar.deleted === "function") { toolbar.deleted(); }
(cb || function () {})(); (cb || function () {})();
@ -2930,7 +2947,12 @@ define([
var error; var error;
if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); } if (isError) { error = setHTML(h('p.cp-password-error'), Messages.password_error); }
var info = h('p.cp-password-info', Messages.password_info); var pwMsg = UI.getDestroyedPlaceholderMessage('PASSWORD_CHANGE', false);
if (cfg.legacy) {
// Legacy mode: we don't know if the pad has been destroyed or its password has changed
pwMsg = Messages.password_info;
}
var info = h('p.cp-password-info', pwMsg);
var info_loaded = setHTML(h('p.cp-password-info'), Messages.errorCopy); var info_loaded = setHTML(h('p.cp-password-info'), Messages.errorCopy);
var password = UI.passwordInput({placeholder: Messages.password_placeholder}); var password = UI.passwordInput({placeholder: Messages.password_placeholder});

View File

@ -334,11 +334,11 @@
// this is resulting in some code duplication // this is resulting in some code duplication
return void CB(void 0, response); return void CB(void 0, response);
} }
if (response.status === 401) { if (response.status === 401 || response.status === 404) {
response.json().then((data) => { response.json().then((data) => {
CB(401, data); CB(response.status, data);
}).catch(() => { }).catch(() => {
CB(401); CB(response.status);
}); });
return; return;

View File

@ -193,6 +193,11 @@ define([
finish(Session, void 0, doc); finish(Session, void 0, doc);
}); });
}; };
config.onChannelError = function (info) {
finish(Session, info);
};
overwrite(config, opt); overwrite(config, opt);
start(Session, config); start(Session, config);

View File

@ -476,6 +476,7 @@ define([
common.drive.onLog = Util.mkEvent(); common.drive.onLog = Util.mkEvent();
common.drive.onChange = Util.mkEvent(); common.drive.onChange = Util.mkEvent();
common.drive.onRemove = Util.mkEvent(); common.drive.onRemove = Util.mkEvent();
common.drive.onDeleted = Util.mkEvent();
// Profile // Profile
common.getProfileEditUrl = function (cb) { common.getProfileEditUrl = function (cb) {
postMessage("GET", { key: ['profile', 'edit'] }, function (obj) { postMessage("GET", { key: ['profile', 'edit'] }, function (obj) {
@ -649,15 +650,23 @@ define([
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// If it's not in the cache or it's not a blob, try to get the value from the server // 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) { var getSize = () => {
if (obj && obj.error) { postMessage("GET_FILE_SIZE", {channel:channel}, waitFor(function (obj) {
// If disconnected, try to get the value from the channel cache (next nThen) if (obj && obj.error === "ANON_RPC_NOT_READY") { return void setTimeout(waitFor(getSize), 100); }
error = obj.error;
return; if (obj && obj.error && obj.error.code === 'ENOENT' && obj.error.reason) {
} waitFor.abort();
waitFor.abort(); cb(obj.error.reason);
cb(undefined, obj.size); } 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 () { }).nThen(function () {
Cache.getChannelCache(channel, function(err, data) { Cache.getChannelCache(channel, function(err, data) {
if (err) { return void cb(error); } if (err) { return void cb(error); }
@ -681,7 +690,7 @@ define([
var error = obj && obj.error; var error = obj && obj.error;
if (error) { return void cb(error); } if (error) { return void cb(error); }
if (!obj) { return void cb('ERROR'); } if (!obj) { return void cb('ERROR'); }
cb (null, obj.isNew); cb (null, obj.isNew, obj.reason);
}, {timeout: -1}); }, {timeout: -1});
}; };
// This function is used when we want to open a pad. We first need // This function is used when we want to open a pad. We first need
@ -711,7 +720,7 @@ define([
} else if (error) { } else if (error) {
return void cb(error); return void cb(error);
} }
cb(undefined, obj.isNew); cb(undefined, obj.isNew, obj.reason);
}, {timeout: -1}); }, {timeout: -1});
}; };
isNew(); isNew();
@ -952,9 +961,9 @@ define([
optsPut.accessKeys = keys; optsPut.accessKeys = keys;
})); }));
}).nThen(function () { }).nThen(function () {
Crypt.get(parsed.hash, function (err, val) { Crypt.get(parsed.hash, function (err, val, errData) {
if (err) { if (err) {
return void cb(err); return void cb(err, errData);
} }
if (!val) { if (!val) {
return void cb('ENOENT'); return void cb('ENOENT');
@ -996,10 +1005,10 @@ define([
optsGet.accessKeys = keys; optsGet.accessKeys = keys;
})); }));
}).nThen(function () { }).nThen(function () {
Crypt.get(parsed.hash, function (err, _val) { Crypt.get(parsed.hash, function (err, _val, errData) {
if (err) { if (err) {
_waitFor.abort(); _waitFor.abort();
return void cb(err); return void cb(err, errData);
} }
try { try {
val = JSON.parse(_val); val = JSON.parse(_val);
@ -1443,8 +1452,10 @@ define([
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
optsPut.metadata.restricted = oldMetadata.restricted; optsPut.metadata.restricted = oldMetadata.restricted;
optsPut.metadata.allowed = oldMetadata.allowed; optsPut.metadata.allowed = oldMetadata.allowed;
if (!newPassword) { optsPut.metadata.forcePlaceholder = true; }
Crypt.put(newHash, cryptgetVal, waitFor(function (err) { Crypt.put(newHash, cryptgetVal, waitFor(function (err) {
if (err) { if (err) {
if (err === "EDELETED") { err = "PASSWORD_ALREADY_USED"; }
waitFor.abort(); waitFor.abort();
return void cb({ error: err }); return void cb({ error: err });
} }
@ -1484,7 +1495,8 @@ define([
// delete the old pad // delete the old pad
common.removeOwnedChannel({ common.removeOwnedChannel({
channel: oldChannel, channel: oldChannel,
teamId: teamId teamId: teamId,
reason: 'PASSWORD_CHANGE',
}, waitFor(function (obj) { }, waitFor(function (obj) {
if (obj && obj.error) { if (obj && obj.error) {
waitFor.abort(); waitFor.abort();
@ -1620,7 +1632,8 @@ define([
// delete the old pad // delete the old pad
common.removeOwnedChannel({ common.removeOwnedChannel({
channel: oldChannel, channel: oldChannel,
teamId: teamId teamId: teamId,
reason: 'PASSWORD_CHANGE'
}, waitFor(function (obj) { }, waitFor(function (obj) {
if (obj && obj.error) { if (obj && obj.error) {
waitFor.abort(); waitFor.abort();
@ -1941,8 +1954,9 @@ define([
console.log("checking if old drive is owned"); console.log("checking if old drive is owned");
common.anonRpcMsg('GET_METADATA', secret.channel, waitFor(function (err, obj) { common.anonRpcMsg('GET_METADATA', secret.channel, waitFor(function (err, obj) {
if (err || obj.error) { return; } if (err || obj.error) { return; }
if (obj.owners && Array.isArray(obj.owners) && var md = obj[0];
obj.owners.indexOf(edPublic) !== -1) { if (md && md.owners && Array.isArray(md.owners) &&
md.owners.indexOf(edPublic) !== -1) {
oldIsOwned = true; oldIsOwned = true;
} }
})); }));
@ -1957,6 +1971,42 @@ define([
return void cb({ error: 'INVALID_CODE' }); return void cb({ error: 'INVALID_CODE' });
} }
})); }));
}).nThen(function (waitFor) {
var blockUrl = Block.getBlockUrl(blockKeys);
// Check whether there is a block at that new location
Util.getBlock(blockUrl, {}, waitFor(function (err, response) {
// If there is no block or the block is invalid, continue.
// error 401 means protected block
/*
// the following block prevent users from re-using an old password
if (err === 404 && response && response.reason) {
waitFor.abort();
return void cb({
error: 'EDELELED',
reason: response.reason
});
}
*/
if (err && err !== 401) {
console.log("no block found");
return;
}
response.arrayBuffer().then(waitFor(arraybuffer => {
var block = new Uint8Array(arraybuffer);
var decryptedBlock = Block.decrypt(block, blockKeys);
if (!decryptedBlock) {
console.error("Found a login block but failed to decrypt");
return;
}
// If there is already a valid block, abort! We risk overriding another user's data
waitFor.abort();
cb({ error: 'EEXISTS' });
}));
}));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// Create a new user hash // Create a new user hash
// Get the current content, store it in the new user file // Get the current content, store it in the new user file
@ -1985,27 +2035,6 @@ define([
} }
}), optsPut); }), optsPut);
})); }));
}).nThen(function (waitFor) {
var blockUrl = Block.getBlockUrl(blockKeys);
// Check whether there is a block at that new location
Util.fetch(blockUrl, waitFor(function (err, block) {
// If there is no block or the block is invalid, continue.
// error 401 means protected block
if (err && err !== 401) {
console.log("no block found");
return;
}
var decryptedBlock = Block.decrypt(block, blockKeys);
if (!decryptedBlock) {
console.error("Found a login block but failed to decrypt");
return;
}
// If there is already a valid block, abort! We risk overriding another user's data
waitFor.abort();
cb({ error: 'EEXISTS' });
}));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
// Write the new login block // Write the new login block
var content = { var content = {
@ -2054,10 +2083,12 @@ define([
if (!blockHash) { return; } if (!blockHash) { return; }
console.log('removing old login block'); console.log('removing old login block');
Block.removeLoginBlock({ Block.removeLoginBlock({
reason: 'PASSWORD_CHANGE',
auth: auth, auth: auth,
blockKeys: oldBlockKeys, blockKeys: oldBlockKeys,
}, waitFor(function (err) { }, waitFor(function (err) {
if (err) { return void console.error(err); } if (err) { return void console.error(err); }
common.passwordUpdated = true;
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (!oldIsOwned) { return; } if (!oldIsOwned) { return; }
@ -2065,16 +2096,15 @@ define([
common.removeOwnedChannel({ common.removeOwnedChannel({
channel: secret.channel, channel: secret.channel,
teamId: null, teamId: null,
force: true force: true,
reason: 'PASSWORD_CHANGE'
}, waitFor(function (obj) { }, waitFor(function (obj) {
if (obj && obj.error) { if (obj && obj.error) {
// Deal with it as if it was not owned // Deal with it as if it was not owned
oldIsOwned = false; oldIsOwned = false;
return; return;
} }
common.logoutFromAll(waitFor(function () { common.stopWorker();
common.stopWorker();
}));
})); }));
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (oldIsOwned) { return; } if (oldIsOwned) { return; }
@ -2087,9 +2117,7 @@ define([
if (obj && obj.error) { if (obj && obj.error) {
console.error(obj.error); console.error(obj.error);
} }
common.logoutFromAll(waitFor(function () { common.stopWorker();
common.stopWorker();
}));
})); }));
}).nThen(function () { }).nThen(function () {
// We have the new drive, with the new login block // We have the new drive, with the new login block
@ -2266,6 +2294,14 @@ define([
cb(); cb();
}; };
common.storeLogout = function (data) {
if (common.passwordUpdated) { return; }
LocalStore.logout(function () {
common.stopWorker();
common.drive.onDeleted.fire(data.reason);
});
};
var lastPing = +new Date(); var lastPing = +new Date();
var onPing = function (data, cb) { var onPing = function (data, cb) {
lastPing = +new Date(); lastPing = +new Date();
@ -2342,8 +2378,10 @@ define([
DRIVE_LOG: common.drive.onLog.fire, DRIVE_LOG: common.drive.onLog.fire,
DRIVE_CHANGE: common.drive.onChange.fire, DRIVE_CHANGE: common.drive.onChange.fire,
DRIVE_REMOVE: common.drive.onRemove.fire, DRIVE_REMOVE: common.drive.onRemove.fire,
DRIVE_DELETED: common.drive.onDeleted.fire,
// Account deletion // Account deletion
DELETE_ACCOUNT: common.startAccountDeletion, DELETE_ACCOUNT: common.startAccountDeletion,
LOGOUT: common.storeLogout,
// Loading // Loading
LOADING_DRIVE: common.loading.onDriveEvent.fire, LOADING_DRIVE: common.loading.onDriveEvent.fire,
// AutoStore // AutoStore
@ -2459,6 +2497,14 @@ define([
}); });
} }
if (err === 404) {
// Not found: account deleted
waitFor.abort();
return LocalStore.logout(function () {
f(response || err);
});
}
if (err) { if (err) {
// TODO // TODO
// it seems wrong that errors here aren't reported or handled // it seems wrong that errors here aren't reported or handled
@ -2496,6 +2542,15 @@ define([
})); }));
} }
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
var blockHash = LocalStore.getBlockHash();
var blockId = '';
try {
var blockPath = (new URL(blockHash)).pathname;
var blockSplit = blockPath.split('/');
if (blockSplit[1] === 'block') {
blockId = blockSplit[3];
}
} catch (e) { }
var cfg = { var cfg = {
init: true, init: true,
userHash: userHash || LocalStore.getUserHash(), userHash: userHash || LocalStore.getUserHash(),
@ -2508,9 +2563,10 @@ define([
neverDrive: rdyCfg.neverDrive, neverDrive: rdyCfg.neverDrive,
disableCache: localStorage['CRYPTPAD_STORE|disableCache'], disableCache: localStorage['CRYPTPAD_STORE|disableCache'],
driveEvents: !rdyCfg.noDrive, //rdyCfg.driveEvents // Boolean driveEvents: !rdyCfg.noDrive, //rdyCfg.driveEvents // Boolean
lastVisit: Number(localStorage.lastVisit) || undefined lastVisit: Number(localStorage.lastVisit) || undefined,
blockId: blockId
}; };
common.userHash = userHash; common.userHash = userHash || LocalStore.getUserHash();
// FIXME Backward compatibility // FIXME Backward compatibility
if (sessionStorage.newPadFileData) { if (sessionStorage.newPadFileData) {
@ -2786,6 +2842,9 @@ define([
LocalStore.loginReload(); LocalStore.loginReload();
} else if (o && !n) { } else if (o && !n) {
LocalStore.logout(); LocalStore.logout();
} else if (o && n && o !== n) {
common.passwordUpdated = true;
window.location.reload();
} }
}); });
LocalStore.onLogout(function () { LocalStore.onLogout(function () {

View File

@ -1152,10 +1152,36 @@ define([
var FILTER_BY = "filterBy"; 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 refresh = APP.refresh = function (cb) {
var type = APP.store[FILTER_BY]; var type = APP.store[FILTER_BY];
var path = type ? [FILTER, type, currentPath] : currentPath; 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), // `app`: true (force open wiht the app), false (force open in preview),
@ -5375,11 +5401,14 @@ define([
}); });
} }
*/ */
var nt = nThen; Messages.dph_sf_pw = "Your shared folder {0} is no longer available, it is now protected with a new password. You can remove this folder from your CryptDrive or recover access using the new password."; // XXX PLACEHOLDER
var passwordModal = function (fId, data, cb) { APP.passwordModal = function (fId, data, cb) {
var content = []; var content = [];
var folderName = '<b>'+ (data.lastTitle || Messages.fm_newFolder) +'</b>';
content.push(UI.setHTML(h('p'), Messages._getKey('drive_sfPassword', [folderName]))); var legacy = data.legacy; // Legacy mode: we don't know if the sf has been destroyed or its password has changed
var folderName = '<b>'+ (Util.fixHTML(data.lastTitle) || Messages.fm_newFolder) +'</b>';
var pwMsg = legacy ? Messages._getKey('drive_sfPassword', [folderName]) : Messages._getKey('dph_sf_pw', [folderName]);
content.push(UI.setHTML(h('p'), pwMsg));
var newPassword = UI.passwordInput({ var newPassword = UI.passwordInput({
id: 'cp-app-prop-change-password', id: 'cp-app-prop-change-password',
placeholder: Messages.settings_changePasswordNew, placeholder: Messages.settings_changePasswordNew,
@ -5430,24 +5459,7 @@ define([
onClose: cb onClose: cb
}); });
}; };
onConnectEvt.reg(function () { onConnectEvt.reg(refreshDeprecated);
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();
});
}
});
return { return {
refresh: refresh, refresh: refresh,

View File

@ -930,6 +930,7 @@ define([
}); });
} }
Messages.access_passwordUsed = "This password has already been used for this pad. It can't be used again."; // XXX NEW
var href = data.href; var href = data.href;
var isNotStored = Boolean(data.isNotStored); var isNotStored = Boolean(data.isNotStored);
sframeChan.query(q, { sframeChan.query(q, {
@ -940,8 +941,12 @@ define([
}, function (err, data) { }, function (err, data) {
$(passwordOk).text(Messages.properties_changePasswordButton); $(passwordOk).text(Messages.properties_changePasswordButton);
pLocked = false; pLocked = false;
if (err || data.error) { err = err || data.error;
console.error(err || data.error); if (err) {
if (err === "PASSWORD_ALREADY_USED") {
return void UI.alert(Messages.access_passwordUsed);
}
console.error(err);
return void UI.alert(Messages.properties_passwordError); return void UI.alert(Messages.properties_passwordError);
} }
UI.findOKButton().click(); UI.findOKButton().click();
@ -983,11 +988,17 @@ define([
if (data.warning) { if (data.warning) {
return void UI.alert(Messages.properties_passwordWarning, function () { return void UI.alert(Messages.properties_passwordWarning, function () {
if (isNotStored) {
return sframeChan.query('Q_PASSWORD_CHECK', newPass, () => { common.gotoURL(_href); });
}
common.gotoURL(_href); common.gotoURL(_href);
}, {force: true}); }, {force: true});
} }
return void UI.alert(UIElements.fixInlineBRs(Messages.properties_passwordSuccess), function () { return void UI.alert(UIElements.fixInlineBRs(Messages.properties_passwordSuccess), function () {
if (!isSharedFolder) { if (!isSharedFolder) {
if (isNotStored) {
return sframeChan.query('Q_PASSWORD_CHECK', newPass, () => { common.gotoURL(_href); });
}
common.gotoURL(_href); common.gotoURL(_href);
} }
}); });

View File

@ -235,6 +235,22 @@ var factory = function () {
config.Cache.setBlobCache(id, u8, cb); config.Cache.setBlobCache(id, u8, cb);
}; };
var headRequest = function (src, cb) {
var xhr = new XMLHttpRequest();
xhr.open("HEAD", src);
if (sendCredentials) { xhr.withCredentials = true; }
xhr.onerror = function () { return void cb("XHR_ERROR"); };
xhr.onreadystatechange = function() {
if (this.readyState === this.DONE) {
cb(null, Number(xhr.getResponseHeader("Content-Length")));
}
};
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
};
xhr.send();
};
var getFileSize = function (src, _cb) { var getFileSize = function (src, _cb) {
var cb = function (e, res) { var cb = function (e, res) {
_cb(e, res); _cb(e, res);
@ -244,25 +260,14 @@ var factory = function () {
var cacheKey = getCacheKey(src); var cacheKey = getCacheKey(src);
var check = function () { var check = function () {
var xhr = new XMLHttpRequest(); headRequest(src, cb);
xhr.open("HEAD", src);
if (sendCredentials) { xhr.withCredentials = true; }
xhr.onerror = function () { return void cb("XHR_ERROR"); };
xhr.onreadystatechange = function() {
if (this.readyState === this.DONE) {
cb(null, Number(xhr.getResponseHeader("Content-Length")));
}
};
xhr.onload = function () {
if (/^4/.test('' + this.status)) { return void cb("XHR_ERROR " + this.status); }
};
xhr.send();
}; };
if (!cacheKey) { return void check(); } if (!cacheKey) { return void check(); }
getBlobCache(cacheKey, function (err, u8) { getBlobCache(cacheKey, function (err, u8) {
if (err || !u8) { return void check(); } check(); // send the HEAD request to update the blob activity
if (err || !u8) { return; }
cb(null, 0); cb(null, 0);
}); });
}; };
@ -749,7 +754,11 @@ var factory = function () {
}); });
}; };
if (cfg.force) { dl(); return mediaObject; } if (cfg.force) {
headRequest(src, function () {}); // Update activity
dl();
return mediaObject;
}
var maxSize = typeof(config.maxDownloadSize) === "number" ? config.maxDownloadSize var maxSize = typeof(config.maxDownloadSize) === "number" ? config.maxDownloadSize
: (5 * 1024 * 1024); : (5 * 1024 * 1024);

View File

@ -407,6 +407,28 @@ define([
} }
}; };
handlers['SF_DELETED'] = function(common, data) {
var content = data.content;
var msg = content.msg;
// Display the notification
var title = Util.fixHTML(msg.content.title);
var teamName = Util.fixHTML(msg.content.teamName);
Messages.dph_sf_destroyed = "Your shared folder <em>{0}</em> has been destroyed by its owner."; // XXX
Messages.dph_sf_destroyed_team= "The shared folder <em>{0}</em> from your team <b>{1}</b> has been destroyed by its owner."; // XXX
content.getFormatText = function() {
if (teamName) {
return Messages._getKey('dph_sf_destroyed_team', [title, teamName]);
}
return Messages._getKey('dph_sf_destroyed', [title]);
};
if (!content.archived) {
content.dismissHandler = defaultDismiss(common, data);
}
};
handlers['MOVE_TODO'] = function(common, data) { handlers['MOVE_TODO'] = function(common, data) {
var content = data.content; var content = data.content;
var msg = content.msg; var msg = content.msg;

View File

@ -203,7 +203,7 @@ define([
}; };
var getUserChannelList = function () { var getUserChannelList = function () {
var userChannel = store.driveChannel; var userChannel = `${store.driveChannel}#drive`;
if (!userChannel) { return null; } if (!userChannel) { return null; }
// Get the list of pads' channel ID in your drive // Get the list of pads' channel ID in your drive
@ -245,6 +245,11 @@ define([
} }
list.push(userChannel); list.push(userChannel);
if (store.data && store.data.blockId) {
//list.push(`${store.data.blockId}#block`); // XXX 5.5.0?
}
list.sort(); list.sort();
return list; return list;
@ -360,10 +365,12 @@ define([
var channel = data; var channel = data;
var force = false; var force = false;
var teamId; var teamId;
var reason;
if (data && typeof(data) === "object") { if (data && typeof(data) === "object") {
channel = data.channel; channel = data.channel;
force = data.force; force = data.force;
teamId = data.teamId; teamId = data.teamId;
reason = data.reason;
} }
if (channel === store.driveChannel && !force) { if (channel === store.driveChannel && !force) {
@ -380,7 +387,7 @@ define([
s.rpc.removeOwnedChannel(channel, function (err) { s.rpc.removeOwnedChannel(channel, function (err) {
if (err) { delete myDeletions[channel]; } if (err) { delete myDeletions[channel]; }
cb({error:err}); cb({error:err});
}); }, reason);
}; };
var arePinsSynced = function (cb) { var arePinsSynced = function (cb) {
@ -507,11 +514,9 @@ define([
var channelId = data.channel || Hash.hrefToHexChannelId(data.href, data.password); var channelId = data.channel || Hash.hrefToHexChannelId(data.href, data.password);
store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) { store.anon_rpc.send("IS_NEW_CHANNEL", channelId, function (e, response) {
if (e) { return void cb({error: e}); } if (e) { return void cb({error: e}); }
if (response && response.length && typeof(response[0]) === 'boolean') { if (response && response.length && typeof(response[0]) === 'object') {
if (response[0]) { Cache.clearChannel(channelId); } if (response[0].isNew) { Cache.clearChannel(channelId); }
return void cb({ return void cb(response[0]);
isNew: response[0]
});
} else { } else {
cb({error: 'INVALID_RESPONSE'}); cb({error: 'INVALID_RESPONSE'});
} }
@ -634,6 +639,7 @@ define([
} }
}; };
cb(JSON.parse(JSON.stringify(metadata))); cb(JSON.parse(JSON.stringify(metadata)));
return metadata;
}; };
Store.onMaintenanceUpdate = function (uid) { Store.onMaintenanceUpdate = function (uid) {
@ -882,6 +888,7 @@ define([
}).nThen(function (waitFor) { }).nThen(function (waitFor) {
if (!blockKeys) { return; } if (!blockKeys) { return; }
Block.removeLoginBlock({ Block.removeLoginBlock({
reason: 'ARCHIVE_OWNED',
auth: auth, auth: auth,
blockKeys: blockKeys, blockKeys: blockKeys,
}, waitFor(function (err) { }, waitFor(function (err) {
@ -2720,6 +2727,7 @@ define([
loadSharedFolder: loadSharedFolder, loadSharedFolder: loadSharedFolder,
settings: proxy.settings, settings: proxy.settings,
removeOwnedChannel: function (channel, cb) { Store.removeOwnedChannel('', channel, cb); }, removeOwnedChannel: function (channel, cb) { Store.removeOwnedChannel('', channel, cb); },
store: store,
Store: Store Store: Store
}, { }, {
outer: true, outer: true,
@ -3044,6 +3052,13 @@ define([
rt.realtime.abort(); rt.realtime.abort();
sendDriveEvent('NETWORK_DISCONNECT'); sendDriveEvent('NETWORK_DISCONNECT');
} }
})
.on('error', function (info) {
if (info.error && info.error === 'EDELETED') {
broadcast([], "LOGOUT", {
reason: info.message
});
}
}); });
// Proxy handlers (reconnect only called when the proxy is ready) // Proxy handlers (reconnect only called when the proxy is ready)

View File

@ -192,7 +192,7 @@ define([
}, cb); }, cb);
}; };
Block.removeLoginBlock = function (data, cb) { Block.removeLoginBlock = function (data, cb) {
const { blockKeys, auth } = data; const { reason, blockKeys, auth } = data;
var command = 'REMOVE_BLOCK'; var command = 'REMOVE_BLOCK';
if (auth && auth.type === 'TOTP') { if (auth && auth.type === 'TOTP') {
@ -201,7 +201,8 @@ define([
ServerCommand(blockKeys.sign, { ServerCommand(blockKeys.sign, {
command: command, command: command,
auth: auth && auth.data auth: auth && auth.data,
reason: reason
}, cb); }, cb);
}; };

View File

@ -794,6 +794,29 @@ define([
cb(true); cb(true);
}; };
var sfDeleted = {};
handlers['SF_DELETED'] = function (ctx, box, data, cb) {
var msg = data.msg;
var content = msg.content;
var teamId = content.team;
var sfId = content.sfId;
if (sfDeleted[sfId]) { return void cb(true); }
sfDeleted[sfId] = 1;
// If it's a team SF, add the team name here
if (!teamId) { return void cb(false); }
var team = ctx.store.proxy.teams[teamId];
content.teamName = team.metadata && team.metadata.name;
cb(false);
};
removeHandlers['SF_DELETED'] = function (ctx, box, data) {
var id = data.content.sfId;
delete sfDeleted[id];
};
return { return {
add: function (ctx, box, data, cb) { add: function (ctx, box, data, cb) {
/** /**

View File

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

View File

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

View File

@ -149,7 +149,7 @@ define([
var list = store.manager.getChannelsList('pin'); var list = store.manager.getChannelsList('pin');
var team = ctx.store.proxy.teams[id]; var team = ctx.store.proxy.teams[id];
list.push(team.channel); list.push(`${team.channel}#drive`);
var chatChannel = Util.find(team, ['keys', 'chat', 'channel']); var chatChannel = Util.find(team, ['keys', 'chat', 'channel']);
var membersChannel = Util.find(team, ['keys', 'roster', 'channel']); var membersChannel = Util.find(team, ['keys', 'roster', 'channel']);
var mailboxChannel = Util.find(team, ['keys', 'mailbox', 'channel']); var mailboxChannel = Util.find(team, ['keys', 'mailbox', 'channel']);
@ -319,8 +319,10 @@ define([
} }
ctx.Store.removeOwnedChannel('', data, cb); ctx.Store.removeOwnedChannel('', data, cb);
}, },
Store: ctx.Store Store: ctx.Store,
store: ctx.store
}, { }, {
teamId: team.id,
outer: true, outer: true,
edPublic: keys.drive.edPublic, edPublic: keys.drive.edPublic,
loggedIn: true, loggedIn: true,
@ -423,7 +425,7 @@ define([
}; };
if (channel) { if (channel) {
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", channel, waitFor(function (e, res) { 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 // Channel is empty: remove this team
close(); close();
} }
@ -431,7 +433,7 @@ define([
} }
if (roster) { if (roster) {
ctx.store.anon_rpc.send("IS_NEW_CHANNEL", roster, waitFor(function (e, res) { 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 // Channel is empty: remove this team
close(); close();
} }

View File

@ -118,13 +118,14 @@ define([
cb(null, id); cb(null, id);
}; };
exp.deprecateSharedFolder = function (id) { exp.deprecateSharedFolder = function (id, reason) {
if (readOnly) { return; } if (readOnly) { return; }
var data = files[SHARED_FOLDERS][id]; var data = files[SHARED_FOLDERS][id];
if (!data) { return; } if (!data) { return; }
var ro = !data.href || exp.cryptor.decrypt(data.href).indexOf('#') === -1; var ro = !data.href || exp.cryptor.decrypt(data.href).indexOf('#') === -1;
if (!ro) { 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)); var paths = exp.findFile(Number(id));
exp.delete(paths, null, true); 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) { if (typeof(channel) !== 'string' || [32,48].indexOf(channel.length) === -1) {
console.error('invalid channel to remove', channel); console.error('invalid channel to remove', channel);
return void cb('INVALID_ARGUMENTS'); 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 (e) { return void cb(e); }
if (response && response.length && response[0] === "OK") { if (response && response.length && response[0] === "OK") {
cb(); cb();

View File

@ -70,8 +70,32 @@ define([
delete Env.folders[id]; delete Env.folders[id];
}; };
var sendNotification = (Env, sfId, title) => {
var mailbox = Env.store.mailbox;
if (!mailbox) { return; }
var team = Env.cfg.teamId;
var box;
if (team) {
let teams = Env.store.modules['team'].getTeamsData();
box = teams[team];
} else {
let md = Env.Store.getMetadata(null, null, () => {});
box = md.user;
}
mailbox.sendTo('SF_DELETED', {
sfId: sfId,
team: team,
title: title
}, {
curvePublic: box.curvePublic,
channel: box.notifications
}, (err) => {
console.error(err);
});
};
// Password may have changed // 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) { if (Env.folders[id] && Env.folders[id].deleting) {
// Folder is being deleted by its owner, don't deprecate it // Folder is being deleted by its owner, don't deprecate it
return; return;
@ -85,11 +109,23 @@ define([
return void Env.Store.refreshDriveUI(); return void Env.Store.refreshDriveUI();
} }
if (channel) { Env.unpinPads([channel], function () {}); } if (channel) { Env.unpinPads([channel], function () {}); }
Env.user.userObject.deprecateSharedFolder(id);
removeProxy(Env, id); // If it's explicitely a deletion, no need to deprecate, just delete
if (Env.Store && Env.Store.refreshDriveUI) { if (reason && reason !== "PASSWORD_CHANGE") {
Env.Store.refreshDriveUI(); let temp = Util.find(Env, ['user', 'proxy', UserObject.SHARED_FOLDERS]);
let title = temp[id] && temp[id].lastTitle;
if (title) { sendNotification(Env, id, title); }
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) { var restrictedProxy = function (Env, id) {
@ -632,17 +668,19 @@ define([
if (isNew) { if (isNew) {
return void cb({ error: 'ENOTFOUND' }); return void cb({ error: 'ENOTFOUND' });
} }
var newData = Util.clone(data);
var parsed = Hash.parsePadUrl(href); var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets(parsed.type, parsed.hash, newPassword); var secret = Hash.getSecrets(parsed.type, parsed.hash, newPassword);
data.password = newPassword; newData.password = newPassword;
data.channel = secret.channel; newData.channel = secret.channel;
if (secret.keys.editKeyStr) { 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, { _addSharedFolder(Env, {
path: ['root'], path: ['root'],
folderData: data, folderData: newData,
}, function () { }, function () {
delete temp[fId]; delete temp[fId];
Env.onSync(cb); Env.onSync(cb);
@ -1307,6 +1345,7 @@ define([
unpinPads: data.unpin, unpinPads: data.unpin,
onSync: data.onSync, onSync: data.onSync,
Store: data.Store, Store: data.Store,
store: data.store,
removeOwnedChannel: data.removeOwnedChannel, removeOwnedChannel: data.removeOwnedChannel,
loadSharedFolder: data.loadSharedFolder, loadSharedFolder: data.loadSharedFolder,
cfg: uoConfig, cfg: uoConfig,

View File

@ -146,6 +146,7 @@ define([
'/common/common-constants.js', '/common/common-constants.js',
'/common/common-feedback.js', '/common/common-feedback.js',
'/common/outer/local-store.js', '/common/outer/local-store.js',
'/common/outer/login-block.js',
'/common/outer/cache-store.js', '/common/outer/cache-store.js',
'/customize/application_config.js', '/customize/application_config.js',
//'/common/test.js', //'/common/test.js',
@ -153,7 +154,7 @@ define([
'optional!/api/instance' 'optional!/api/instance'
], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel, ], waitFor(function (_CpNfOuter, _Cryptpad, _Crypto, _Cryptget, _SFrameChannel,
_SecureIframe, _UnsafeIframe, _OOIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Notify, _SecureIframe, _UnsafeIframe, _OOIframe, _Messaging, _Notifier, _Hash, _Util, _Realtime, _Notify,
_Constants, _Feedback, _LocalStore, _Cache, _AppConfig, /* _Test,*/ _UserObject, _Constants, _Feedback, _LocalStore, _Block, _Cache, _AppConfig, /* _Test,*/ _UserObject,
_Instance) { _Instance) {
CpNfOuter = _CpNfOuter; CpNfOuter = _CpNfOuter;
Cryptpad = _Cryptpad; Cryptpad = _Cryptpad;
@ -176,6 +177,7 @@ define([
Utils.Notify = _Notify; Utils.Notify = _Notify;
Utils.currentPad = currentPad; Utils.currentPad = currentPad;
Utils.Instance = _Instance; Utils.Instance = _Instance;
Utils.Block = _Block;
AppConfig = _AppConfig; AppConfig = _AppConfig;
//Test = _Test; //Test = _Test;
@ -268,7 +270,20 @@ define([
// NOTE: Driveless mode should only work for existing pads, but we can't check that // NOTE: Driveless mode should only work for existing pads, but we can't check that
// before creating the worker because we need the anon RPC to do so. // before creating the worker because we need the anon RPC to do so.
// We're only going to check if a hash exists in the URL or not. // We're only going to check if a hash exists in the URL or not.
Cryptpad.ready(waitFor(), { Cryptpad.ready(waitFor((err) => {
if (err) {
waitFor.abort();
if (err.code === 404) {
sframeChan.on('EV_SET_LOGIN_REDIRECT', function (page) {
var href = Utils.Hash.hashToHref('', page);
var url = Utils.Hash.getNewPadURL(href, { href: currentPad.href });
window.location.href = url;
});
return void sframeChan.event("EV_DRIVE_DELETED", err.reason);
}
sframeChan.event('EV_LOADING_ERROR', 'ACCOUNT');
}
}), {
noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash, noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash,
neverDrive: cfg.integration, neverDrive: cfg.integration,
driveEvents: cfg.driveEvents, driveEvents: cfg.driveEvents,
@ -373,9 +388,9 @@ define([
Cryptpad.initialPath = newPad.p; Cryptpad.initialPath = newPad.p;
if (newPad.pw) { if (newPad.pw) {
try { try {
var uHash = Utils.LocalStore.getUserHash(); var uHash = Utils.LocalStore.getBlockHash();
var uSecret = Utils.Hash.getSecrets('drive', uHash); var uSecret = Utils.Block.parseBlockHash(uHash);
var uKey = uSecret.keys.cryptKey; var uKey = uSecret.keys.symmetric;
newPadPassword = Crypto.decrypt(newPad.pw, uKey); newPadPassword = Crypto.decrypt(newPad.pw, uKey);
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
} }
@ -469,12 +484,20 @@ define([
// `hasChannelHistory` doesn't work for files (not a channel) // `hasChannelHistory` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata // `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, function (e, size) { Cryptpad.getFileSize(currentPad.href, password, function (e, size) {
if (e && e !== "PASSWORD_CHANGE") {
return sframeChan.event("EV_DELETED_ERROR", e);
}
next(e, size === 0); next(e, size === 0);
}); });
return; return;
} }
// Not a file, so we can use `hasChannelHistory` // 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); sframeChan.event("EV_PAD_PASSWORD", cfg);
}; };
@ -564,20 +587,32 @@ define([
// `hasChannelHistory` doesn't work for files (not a channel) // `hasChannelHistory` doesn't work for files (not a channel)
// `getFileSize` is not adapted to channels because of metadata // `getFileSize` is not adapted to channels because of metadata
Cryptpad.getFileSize(currentPad.href, password, w(function (e, size) { 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? // 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); askPassword(true, passwordCfg);
})); }));
return; return;
} }
// Not a file, so we can use `hasChannelHistory` // 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())) { if (isNew && expire && expire < (+new Date())) {
sframeChan.event("EV_EXPIRED_ERROR"); sframeChan.event("EV_EXPIRED_ERROR");
waitFor.abort(); waitFor.abort();
return; return;
} }
if (!e && !isNew) { return void todo(); } 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)) { if (parsed.hashData.mode === 'view' && (password || !parsed.hashData.password)) {
// Error, wrong password stored, the view seed has changed with the password // Error, wrong password stored, the view seed has changed with the password
// password will never work // password will never work
@ -586,6 +621,7 @@ define([
return; return;
} }
// Wrong password or deleted file? // 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); askPassword(true, passwordCfg);
})); }));
}).nThen(done); }).nThen(done);
@ -778,6 +814,9 @@ define([
//Test.registerOuter(sframeChan); //Test.registerOuter(sframeChan);
Cryptpad.drive.onDeleted.reg(function (message) {
sframeChan.event("EV_DRIVE_DELETED", message);
});
Cryptpad.onNewVersionReconnect.reg(function () { Cryptpad.onNewVersionReconnect.reg(function () {
sframeChan.event("EV_NEW_VERSION"); sframeChan.event("EV_NEW_VERSION");
}); });
@ -1236,6 +1275,42 @@ define([
}); });
}); });
sframeChan.on('Q_PASSWORD_CHECK', function (pw, cb) {
Cryptpad.isNewChannel(currentPad.href, pw, function (e, isNew) {
if (isNew === false) {
nThen(function (w) {
// If the pad is stored, update its data
var _secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, pw);
var chan = _secret.channel;
var editH = Utils.Hash.getEditHashFromKeys(_secret);
var viewH = Utils.Hash.getViewHashFromKeys(_secret);
var href = Utils.Hash.hashToHref(editH, parsed.type);
var roHref = Utils.Hash.hashToHref(viewH, parsed.type);
Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl());
Cryptpad.setPadAttribute('channel', chan, w(), parsed.getUrl());
Cryptpad.setPadAttribute('href', href, w(), parsed.getUrl());
Cryptpad.setPadAttribute('roHref', roHref, w(), parsed.getUrl());
}).nThen(function () {
// Get redirect URL
var uHash = Utils.LocalStore.getBlockHash();
var uSecret = Utils.Block.parseBlockHash(uHash);
var uKey = uSecret.keys.symmetric;
var url = Utils.Hash.getNewPadURL(currentPad.href, {
pw: Crypto.encrypt(pw, uKey),
f: 1
});
// redirect
window.location.href = url;
document.location.reload();
});
return;
}
cb({
error: e
});
});
});
}; };
addCommonRpc(sframeChan, isSafe); addCommonRpc(sframeChan, isSafe);
@ -1895,43 +1970,6 @@ define([
}); });
}); });
sframeChan.on('Q_PASSWORD_CHECK', function (pw, cb) {
Cryptpad.isNewChannel(currentPad.href, pw, function (e, isNew) {
if (isNew === false) {
nThen(function (w) {
// If the pad is stored, update its data
var _secret = Utils.Hash.getSecrets(parsed.type, parsed.hash, pw);
var chan = _secret.channel;
var editH = Utils.Hash.getEditHashFromKeys(_secret);
var viewH = Utils.Hash.getViewHashFromKeys(_secret);
var href = Utils.Hash.hashToHref(editH, parsed.type);
var roHref = Utils.Hash.hashToHref(viewH, parsed.type);
Cryptpad.setPadAttribute('password', password, w(), parsed.getUrl());
Cryptpad.setPadAttribute('channel', chan, w(), parsed.getUrl());
Cryptpad.setPadAttribute('href', href, w(), parsed.getUrl());
Cryptpad.setPadAttribute('roHref', roHref, w(), parsed.getUrl());
}).nThen(function () {
// Get redirect URL
var uHash = Utils.LocalStore.getUserHash();
var uSecret = Utils.Hash.getSecrets('drive', uHash);
var uKey = uSecret.keys.cryptKey;
var url = Utils.Hash.getNewPadURL(currentPad.href, {
pw: Crypto.encrypt(pw, uKey),
f: 1
});
// redirect
window.location.href = url;
document.location.reload();
});
return;
}
cb({
error: e
});
});
});
sframeChan.on('Q_COPY_VIEW_URL', function (data, cb) { sframeChan.on('Q_COPY_VIEW_URL', function (data, cb) {
require(['/common/clipboard.js'], function (Clipboard) { require(['/common/clipboard.js'], function (Clipboard) {
var url = window.location.origin + var url = window.location.origin +
@ -2232,13 +2270,13 @@ define([
// server // server
Cryptpad.useTemplate({ Cryptpad.useTemplate({
href: data.template href: data.template
}, Cryptget, function (err) { }, Cryptget, function (err, errData) {
if (err) { if (err) {
// TODO: better messages in case of expired, deleted, etc.? // TODO: better messages in case of expired, deleted, etc.?
if (err === 'ERESTRICTED') { if (err === 'ERESTRICTED') {
sframeChan.event('EV_RESTRICTED_ERROR'); sframeChan.event('EV_RESTRICTED_ERROR');
} else { } else {
sframeChan.query("EV_LOADING_ERROR", "DELETED"); sframeChan.query("EV_LOADING_ERROR", errData || 'DELETED');
} }
return; return;
} }
@ -2249,13 +2287,13 @@ define([
} }
// if we open a new code from a file // if we open a new code from a file
if (Cryptpad.fromFileData && !isOO) { if (Cryptpad.fromFileData && !isOO) {
Cryptpad.useFile(Cryptget, function (err) { Cryptpad.useFile(Cryptget, function (err, errData) {
if (err) { if (err) {
// TODO: better messages in case of expired, deleted, etc.? // TODO: better messages in case of expired, deleted, etc.?
if (err === 'ERESTRICTED') { if (err === 'ERESTRICTED') {
sframeChan.event('EV_RESTRICTED_ERROR'); sframeChan.event('EV_RESTRICTED_ERROR');
} else { } else {
sframeChan.query("EV_LOADING_ERROR", "DELETED"); sframeChan.query("EV_LOADING_ERROR", errData || 'DELETED');
} }
return; return;
} }

View File

@ -848,6 +848,21 @@ define([
UI.errorLoadingScreen(Messages.restrictedError); UI.errorLoadingScreen(Messages.restrictedError);
}); });
ctx.sframeChan.on("EV_DELETED_ERROR", function (reason) {
funcs.onServerError({
type: 'EDELETED',
message: reason
});
});
ctx.sframeChan.on("EV_DRIVE_DELETED", function (reason) {
funcs.onServerError({
type: 'EDELETED',
drive: true,
message: reason
});
});
ctx.sframeChan.on("EV_PAD_PASSWORD_ERROR", function () { ctx.sframeChan.on("EV_PAD_PASSWORD_ERROR", function () {
UI.errorLoadingScreen(Messages.password_error_seed); UI.errorLoadingScreen(Messages.password_error_seed);
}); });
@ -883,13 +898,19 @@ define([
ctx.sframeChan.on('EV_LOADING_ERROR', function (err) { ctx.sframeChan.on('EV_LOADING_ERROR', function (err) {
var msg = err; var msg = err;
if (err === 'DELETED') { if (err === 'DELETED' || (err && err.type === 'EDELETED')) {
// XXX You can still use the current version in read-only mode by pressing Esc. // XXX You can still use the current version in read-only mode by pressing Esc.
// what if they don't have a keyboard (ie. mobile) // what if they don't have a keyboard (ie. mobile)
msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome; if (err.type && err.message) {
} msg = UI.getDestroyedPlaceholderMessage(err.message, false, true);
if (err === "INVALID_HASH") { } else {
msg = Messages.deletedError;
}
msg += '<br>' + Messages.errorRedirectToHome;
} else if (err === "INVALID_HASH") {
msg = Messages.invalidHashError; msg = Messages.invalidHashError;
} else if (err === 'ACCOUNT') { // block 404 but no placeholder
msg = Messages.login_unhandledError;
} }
UI.errorLoadingScreen(msg, false, function () { UI.errorLoadingScreen(msg, false, function () {
funcs.gotoURL('/drive/'); funcs.gotoURL('/drive/');

View File

@ -57,7 +57,7 @@ define([
if (!newObj || !Object.keys(newObj).length) { if (!newObj || !Object.keys(newObj).length) {
// Empty anon drive: deleted // Empty anon drive: deleted
var msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome; var msg = Messages.deletedError + '<br>' + Messages.errorRedirectToHome;
setTimeout(function () { UI.errorLoadingScreen(msg, false, function () {}); }); setTimeout(function () { UI.errorLoadingScreen(msg, false, true); });
APP.newSharedFolder = null; APP.newSharedFolder = null;
} }
} }

View File

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